adsp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +300 -0
  5. data/lib/adsp/error.rb +14 -0
  6. data/lib/adsp/file.rb +76 -0
  7. data/lib/adsp/option.rb +51 -0
  8. data/lib/adsp/stream/abstract.rb +206 -0
  9. data/lib/adsp/stream/delegates.rb +39 -0
  10. data/lib/adsp/stream/raw/abstract.rb +69 -0
  11. data/lib/adsp/stream/raw/compressor.rb +110 -0
  12. data/lib/adsp/stream/raw/decompressor.rb +80 -0
  13. data/lib/adsp/stream/raw/native_compressor.rb +58 -0
  14. data/lib/adsp/stream/raw/native_decompressor.rb +44 -0
  15. data/lib/adsp/stream/reader.rb +234 -0
  16. data/lib/adsp/stream/reader_helpers.rb +219 -0
  17. data/lib/adsp/stream/stat.rb +80 -0
  18. data/lib/adsp/stream/writer.rb +206 -0
  19. data/lib/adsp/stream/writer_helpers.rb +102 -0
  20. data/lib/adsp/string.rb +58 -0
  21. data/lib/adsp/validation.rb +46 -0
  22. data/lib/adsp/version.rb +7 -0
  23. data/lib/adsp.rb +8 -0
  24. data/test/common.rb +108 -0
  25. data/test/coverage_helper.rb +18 -0
  26. data/test/file.test.rb +120 -0
  27. data/test/minitest.rb +20 -0
  28. data/test/mock/common.rb +57 -0
  29. data/test/mock/file.rb +60 -0
  30. data/test/mock/stream/raw/compressor.rb +20 -0
  31. data/test/mock/stream/raw/decompressor.rb +20 -0
  32. data/test/mock/stream/raw/native_compressor.rb +82 -0
  33. data/test/mock/stream/raw/native_decompressor.rb +70 -0
  34. data/test/mock/stream/reader.rb +18 -0
  35. data/test/mock/stream/writer.rb +18 -0
  36. data/test/mock/string.rb +44 -0
  37. data/test/option.rb +66 -0
  38. data/test/stream/abstract.rb +125 -0
  39. data/test/stream/minitar.test.rb +50 -0
  40. data/test/stream/raw/abstract.rb +45 -0
  41. data/test/stream/raw/compressor.test.rb +166 -0
  42. data/test/stream/raw/decompressor.test.rb +166 -0
  43. data/test/stream/reader.test.rb +643 -0
  44. data/test/stream/reader_helpers.test.rb +421 -0
  45. data/test/stream/writer.test.rb +610 -0
  46. data/test/stream/writer_helpers.test.rb +267 -0
  47. data/test/string.test.rb +95 -0
  48. data/test/validation.rb +71 -0
  49. data/test/version.test.rb +18 -0
  50. metadata +274 -0
data/lib/adsp.rb ADDED
@@ -0,0 +1,8 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "adsp/stream/reader"
5
+ require_relative "adsp/stream/writer"
6
+ require_relative "adsp/file"
7
+ require_relative "adsp/string"
8
+ require_relative "adsp/version"
data/test/common.rb ADDED
@@ -0,0 +1,108 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "parallel"
5
+ require "securerandom"
6
+ require "tempfile"
7
+
8
+ require_relative "coverage_helper"
9
+
10
+ module ADSP
11
+ module Test
12
+ module Common
13
+ BASE_PATH = ::File.expand_path(::File.join(::File.dirname(__FILE__), "..")).freeze
14
+ TEMP_PATH = ::File.join(BASE_PATH, "tmp").freeze
15
+
16
+ SOURCE_PATH = ::File.join(TEMP_PATH, "source").freeze
17
+ ARCHIVE_PATH = ::File.join(TEMP_PATH, "archive").freeze
18
+
19
+ [
20
+ SOURCE_PATH,
21
+ ARCHIVE_PATH
22
+ ]
23
+ .each { |path| FileUtils.touch path }
24
+
25
+ def self.get_path(path, suffix)
26
+ "#{path}_#{suffix}"
27
+ end
28
+
29
+ ENCODINGS = %w[
30
+ binary
31
+ UTF-8
32
+ UTF-16LE
33
+ ]
34
+ .map { |encoding_name| ::Encoding.find encoding_name }
35
+ .freeze
36
+
37
+ TRANSCODE_OPTIONS = {
38
+ :invalid => :replace,
39
+ :undef => :replace,
40
+ :replace => "?"
41
+ }
42
+ .freeze
43
+
44
+ def self.generate_texts(*sources)
45
+ sources.flat_map do |source|
46
+ ENCODINGS.map do |encoding|
47
+ source.encode encoding, **TRANSCODE_OPTIONS
48
+ end
49
+ end
50
+ end
51
+
52
+ TEXTS = generate_texts(
53
+ "",
54
+ ::SecureRandom.random_bytes(1 << 8) # 256 B
55
+ )
56
+ .freeze
57
+
58
+ LARGE_TEXTS = generate_texts(
59
+ ::SecureRandom.random_bytes(1 << 21) # 2 MB
60
+ )
61
+ .freeze
62
+
63
+ # It is better to have text lengths not divisible by portion lengths.
64
+ PORTION_LENGTHS = [
65
+ 10**2,
66
+ 5 * (10**2)
67
+ ]
68
+ .freeze
69
+
70
+ # It is better to have large text lengths not divisible by large portion lengths.
71
+ LARGE_PORTION_LENGTHS = [
72
+ 10**6,
73
+ 5 * (10**6)
74
+ ]
75
+ .freeze
76
+
77
+ # We need at least 2 threads for testing multiple threads support.
78
+ THREADS_COUNT = [Parallel.processor_count, 2].max.freeze
79
+
80
+ def self.parallel(producer, &_block)
81
+ Parallel.each producer, :in_threads => THREADS_COUNT do |item|
82
+ yield item, Parallel.worker_number
83
+ end
84
+ end
85
+
86
+ def self.parallel_options(generator, &block)
87
+ producer = proc do
88
+ next Parallel::Stop if generator.finished?
89
+
90
+ generator.next
91
+ end
92
+
93
+ parallel producer, &block
94
+ end
95
+
96
+ def self.file_can_be_used_nonblock?
97
+ ::File.open(::Tempfile.new, "w") do |file|
98
+ file.write_nonblock "text"
99
+ end
100
+ rescue Errno::EBADF
101
+ # Nonblock operations with files are not available on Windows.
102
+ false
103
+ else
104
+ true
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,18 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ if ENV["CI"]
5
+ require "codecov"
6
+ require "simplecov"
7
+
8
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(
9
+ [
10
+ SimpleCov::Formatter::HTMLFormatter,
11
+ SimpleCov::Formatter::Codecov
12
+ ]
13
+ )
14
+
15
+ SimpleCov.start do
16
+ add_filter %r{^/test/}
17
+ end
18
+ end
data/test/file.test.rb ADDED
@@ -0,0 +1,120 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/file"
5
+
6
+ require_relative "common"
7
+ require_relative "minitest"
8
+ require_relative "mock/file"
9
+ require_relative "option"
10
+ require_relative "validation"
11
+
12
+ module ADSP
13
+ module Test
14
+ class File < Minitest::Test
15
+ Option = Test::Option
16
+ Target = Mock::File
17
+
18
+ SOURCE_PATH = Common::SOURCE_PATH
19
+ ARCHIVE_PATH = Common::ARCHIVE_PATH
20
+ TEXTS = Common::TEXTS
21
+ LARGE_TEXTS = Common::LARGE_TEXTS
22
+
23
+ BUFFER_LENGTH_NAMES = %i[source_buffer_length destination_buffer_length].freeze
24
+ BUFFER_LENGTH_MAPPING = {
25
+ :source_buffer_length => :destination_buffer_length,
26
+ :destination_buffer_length => :source_buffer_length
27
+ }
28
+ .freeze
29
+
30
+ def test_invalid_arguments
31
+ Validation::INVALID_STRINGS.each do |invalid_path|
32
+ assert_raises ValidateError do
33
+ Target.compress invalid_path, ARCHIVE_PATH
34
+ end
35
+
36
+ assert_raises ValidateError do
37
+ Target.compress SOURCE_PATH, invalid_path
38
+ end
39
+
40
+ assert_raises ValidateError do
41
+ Target.decompress invalid_path, SOURCE_PATH
42
+ end
43
+
44
+ assert_raises ValidateError do
45
+ Target.decompress ARCHIVE_PATH, invalid_path
46
+ end
47
+ end
48
+
49
+ get_invalid_compressor_options do |invalid_options|
50
+ assert_raises ValidateError do
51
+ Target.compress SOURCE_PATH, ARCHIVE_PATH, invalid_options
52
+ end
53
+ end
54
+
55
+ get_invalid_decompressor_options do |invalid_options|
56
+ assert_raises ValidateError do
57
+ Target.decompress ARCHIVE_PATH, SOURCE_PATH, invalid_options
58
+ end
59
+ end
60
+ end
61
+
62
+ def test_texts
63
+ parallel_compressor_options do |compressor_options, worker_index|
64
+ source_path = Common.get_path SOURCE_PATH, worker_index
65
+ archive_path = Common.get_path ARCHIVE_PATH, worker_index
66
+
67
+ TEXTS.each do |text|
68
+ ::File.write source_path, text, :mode => "wb"
69
+ Target.compress source_path, archive_path, compressor_options
70
+
71
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
72
+ Target.decompress archive_path, source_path, decompressor_options
73
+
74
+ decompressed_text = ::File.read source_path, :mode => "rb"
75
+ decompressed_text.force_encoding text.encoding
76
+
77
+ assert_equal text, decompressed_text
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def test_large_texts
84
+ Common.parallel LARGE_TEXTS do |text, worker_index|
85
+ source_path = Common.get_path SOURCE_PATH, worker_index
86
+ archive_path = Common.get_path ARCHIVE_PATH, worker_index
87
+
88
+ ::File.write source_path, text, :mode => "wb"
89
+ Target.compress source_path, archive_path
90
+ Target.decompress archive_path, source_path
91
+
92
+ decompressed_text = ::File.read source_path, :mode => "rb"
93
+ decompressed_text.force_encoding text.encoding
94
+
95
+ assert_equal text, decompressed_text
96
+ end
97
+ end
98
+
99
+ # -----
100
+
101
+ def get_invalid_compressor_options(&block)
102
+ self.class::Option.get_invalid_compressor_options BUFFER_LENGTH_NAMES, &block
103
+ end
104
+
105
+ def get_invalid_decompressor_options(&block)
106
+ self.class::Option.get_invalid_decompressor_options BUFFER_LENGTH_NAMES, &block
107
+ end
108
+
109
+ def parallel_compressor_options(&block)
110
+ Common.parallel_options self.class::Option.get_compressor_options_generator(BUFFER_LENGTH_NAMES), &block
111
+ end
112
+
113
+ def get_compatible_decompressor_options(compressor_options, &block)
114
+ self.class::Option.get_compatible_decompressor_options compressor_options, BUFFER_LENGTH_MAPPING, &block
115
+ end
116
+ end
117
+
118
+ Minitest << File
119
+ end
120
+ end
data/test/minitest.rb ADDED
@@ -0,0 +1,20 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "minitest"
5
+ require "minitest/autorun"
6
+
7
+ module Minitest
8
+ Minitest.module_eval do
9
+ class << self
10
+ def <<(klass)
11
+ Runnable.runnables << klass unless Runnable.runnables.include? klass
12
+ nil
13
+ end
14
+ end
15
+ end
16
+
17
+ Runnable.instance_eval do
18
+ def self.inherited(_klass); end # rubocop:disable Lint/MissingSuper
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/string"
5
+
6
+ require_relative "../common"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ module Common
12
+ DEFAULT_DESTINATION_BUFFER_LENGTH = 1 << 18 # 256 KB
13
+
14
+ # Flipping bytes and packing them as 16-bit values in network byte order.
15
+ # This method will make compressed data bytesize more than original bytesize.
16
+ # It will help to improve testing coverage.
17
+ def self.native_compress(source, destination_buffer_length)
18
+ source_length = source.bytesize
19
+ destination_buffer_length = DEFAULT_DESTINATION_BUFFER_LENGTH if destination_buffer_length.zero?
20
+
21
+ bytes_read =
22
+ if destination_buffer_length.nil?
23
+ source_length
24
+ else
25
+ [source_length, destination_buffer_length / 2].min
26
+ end
27
+
28
+ result = source
29
+ .byteslice(0, bytes_read)
30
+ .unpack("C*")
31
+ .map { |byte| byte ^ 0xFF }
32
+ .pack "n*"
33
+ [result, bytes_read]
34
+ end
35
+
36
+ def self.native_decompress(source, destination_buffer_length)
37
+ source_length = source.bytesize / 2 * 2
38
+ destination_buffer_length = DEFAULT_DESTINATION_BUFFER_LENGTH if destination_buffer_length.zero?
39
+
40
+ bytes_read =
41
+ if destination_buffer_length.nil?
42
+ source_length
43
+ else
44
+ [source_length, destination_buffer_length * 2].min
45
+ end
46
+
47
+ result = source
48
+ .byteslice(0, bytes_read)
49
+ .unpack("n*")
50
+ .map { |byte| byte ^ 0xFF }
51
+ .pack "C*"
52
+ [result, bytes_read]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
data/test/mock/file.rb ADDED
@@ -0,0 +1,60 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/file"
5
+
6
+ require_relative "./common"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ class File < ADSP::File
12
+ PORTION_LENGTH = 10**6
13
+
14
+ def self.native_compress_io(source_io, destination_io, options)
15
+ destination_buffer_length = options[:destination_buffer_length]
16
+ source = "".b
17
+
18
+ loop do
19
+ begin
20
+ source << source_io.readpartial(PORTION_LENGTH)
21
+ rescue ::EOFError
22
+ break
23
+ end
24
+
25
+ until source.empty?
26
+ data, bytes_read = Common.native_compress source, destination_buffer_length
27
+ source = source.byteslice bytes_read, source.bytesize - bytes_read
28
+
29
+ destination_io.write data
30
+ end
31
+ end
32
+
33
+ nil
34
+ end
35
+
36
+ def self.native_decompress_io(source_io, destination_io, options)
37
+ destination_buffer_length = options[:destination_buffer_length]
38
+ source = "".b
39
+
40
+ loop do
41
+ begin
42
+ source << source_io.readpartial(PORTION_LENGTH)
43
+ rescue ::EOFError
44
+ break
45
+ end
46
+
47
+ until source.empty?
48
+ data, bytes_read = Common.native_decompress source, destination_buffer_length
49
+ source = source.byteslice bytes_read, source.bytesize - bytes_read
50
+
51
+ destination_io.write data
52
+ end
53
+ end
54
+
55
+ nil
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/stream/raw/compressor"
5
+
6
+ require_relative "native_compressor"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ module Stream
12
+ module Raw
13
+ class Compressor < ADSP::Stream::Raw::Compressor
14
+ NativeCompressor = Raw::NativeCompressor
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/stream/raw/decompressor"
5
+
6
+ require_relative "native_decompressor"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ module Stream
12
+ module Raw
13
+ class Decompressor < ADSP::Stream::Raw::Decompressor
14
+ NativeDecompressor = Raw::NativeDecompressor
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,82 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/error"
5
+ require "adsp/validation"
6
+
7
+ require_relative "../../common"
8
+
9
+ module ADSP
10
+ module Test
11
+ module Mock
12
+ module Stream
13
+ module Raw
14
+ class NativeCompressor
15
+ def initialize(options)
16
+ ADSP::Validation.validate_hash options
17
+
18
+ destination_buffer_length = options[:destination_buffer_length]
19
+ ADSP::Validation.validate_not_negative_integer destination_buffer_length
20
+
21
+ destination_buffer_length = Common::DEFAULT_DESTINATION_BUFFER_LENGTH \
22
+ if destination_buffer_length.zero?
23
+
24
+ @destination_buffer = "".b
25
+ @destination_buffer_length = destination_buffer_length
26
+
27
+ @is_closed = false
28
+ end
29
+
30
+ def write(source)
31
+ ADSP::Validation.validate_string source
32
+
33
+ do_not_use_after_close
34
+
35
+ remaining_destination_buffer_length = @destination_buffer_length - @destination_buffer.bytesize
36
+ return [0, true] if remaining_destination_buffer_length.zero?
37
+
38
+ data, bytes_read = Common.native_compress source, remaining_destination_buffer_length
39
+ needs_more_destination = bytes_read < source.bytesize
40
+ @destination_buffer << data
41
+
42
+ [bytes_read, needs_more_destination]
43
+ end
44
+
45
+ def read_result
46
+ do_not_use_after_close
47
+
48
+ result = @destination_buffer
49
+ @destination_buffer = "".b
50
+
51
+ result
52
+ end
53
+
54
+ def flush
55
+ do_not_use_after_close
56
+
57
+ nil
58
+ end
59
+
60
+ def finish
61
+ do_not_use_after_close
62
+
63
+ nil
64
+ end
65
+
66
+ def close
67
+ do_not_use_after_close
68
+
69
+ @is_closed = true
70
+
71
+ nil
72
+ end
73
+
74
+ protected def do_not_use_after_close
75
+ raise UsedAfterCloseError if @is_closed
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,70 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/error"
5
+ require "adsp/validation"
6
+
7
+ require_relative "../../common"
8
+
9
+ module ADSP
10
+ module Test
11
+ module Mock
12
+ module Stream
13
+ module Raw
14
+ class NativeDecompressor
15
+ def initialize(options)
16
+ ADSP::Validation.validate_hash options
17
+
18
+ destination_buffer_length = options[:destination_buffer_length]
19
+ ADSP::Validation.validate_not_negative_integer destination_buffer_length
20
+
21
+ destination_buffer_length = Common::DEFAULT_DESTINATION_BUFFER_LENGTH \
22
+ if destination_buffer_length.zero?
23
+
24
+ @destination_buffer = "".b
25
+ @destination_buffer_length = destination_buffer_length
26
+
27
+ @is_closed = false
28
+ end
29
+
30
+ def read(source)
31
+ ADSP::Validation.validate_string source
32
+
33
+ do_not_use_after_close
34
+
35
+ remaining_destination_buffer_length = @destination_buffer_length - @destination_buffer.bytesize
36
+ return [0, true] if remaining_destination_buffer_length.zero?
37
+
38
+ data, bytes_read = Common.native_decompress source, remaining_destination_buffer_length
39
+ needs_more_destination = bytes_read < source.bytesize
40
+ @destination_buffer << data
41
+
42
+ [bytes_read, needs_more_destination]
43
+ end
44
+
45
+ def read_result
46
+ do_not_use_after_close
47
+
48
+ result = @destination_buffer
49
+ @destination_buffer = "".b
50
+
51
+ result
52
+ end
53
+
54
+ def close
55
+ do_not_use_after_close
56
+
57
+ @is_closed = true
58
+
59
+ nil
60
+ end
61
+
62
+ protected def do_not_use_after_close
63
+ raise UsedAfterCloseError if @is_closed
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,18 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/stream/reader"
5
+
6
+ require_relative "raw/decompressor"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ module Stream
12
+ class Reader < ADSP::Stream::Reader
13
+ RawDecompressor = Mock::Stream::Raw::Decompressor
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/stream/writer"
5
+
6
+ require_relative "raw/compressor"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ module Stream
12
+ class Writer < ADSP::Stream::Writer
13
+ RawCompressor = Mock::Stream::Raw::Compressor
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "adsp/string"
5
+
6
+ require_relative "./common"
7
+
8
+ module ADSP
9
+ module Test
10
+ module Mock
11
+ class String < ADSP::String
12
+ def self.native_compress_string(source, options)
13
+ destination_buffer_length = options[:destination_buffer_length]
14
+ result = "".b
15
+
16
+ loop do
17
+ data, bytes_read = Common.native_compress source, destination_buffer_length
18
+ source = source.byteslice bytes_read, source.bytesize - bytes_read
19
+ result << data
20
+
21
+ break if source.bytesize.zero?
22
+ end
23
+
24
+ result
25
+ end
26
+
27
+ def self.native_decompress_string(source, options)
28
+ destination_buffer_length = options[:destination_buffer_length]
29
+ result = "".b
30
+
31
+ loop do
32
+ data, bytes_read = Common.native_decompress source, destination_buffer_length
33
+ source = source.byteslice bytes_read, source.bytesize - bytes_read
34
+ result << data
35
+
36
+ break if source.bytesize.zero?
37
+ end
38
+
39
+ result
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end