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
@@ -0,0 +1,643 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "set"
5
+ require "socket"
6
+ require "stringio"
7
+
8
+ require_relative "abstract"
9
+ require_relative "../common"
10
+ require_relative "../minitest"
11
+ require_relative "../option"
12
+ require_relative "../validation"
13
+ require_relative "../mock/stream/reader"
14
+ require_relative "../mock/string"
15
+
16
+ module ADSP
17
+ module Test
18
+ module Stream
19
+ class Reader < Abstract
20
+ Target = Mock::Stream::Reader
21
+ String = Mock::String
22
+
23
+ ARCHIVE_PATH = Common::ARCHIVE_PATH
24
+ ENCODINGS = Common::ENCODINGS
25
+ TRANSCODE_OPTIONS = Common::TRANSCODE_OPTIONS
26
+ TEXTS = Common::TEXTS
27
+ LARGE_TEXTS = Common::LARGE_TEXTS
28
+ PORTION_LENGTHS = Common::PORTION_LENGTHS
29
+ LARGE_PORTION_LENGTHS = Common::LARGE_PORTION_LENGTHS
30
+
31
+ BUFFER_LENGTH_NAMES = %i[source_buffer_length destination_buffer_length].freeze
32
+ BUFFER_LENGTH_MAPPING = {
33
+ :source_buffer_length => :destination_buffer_length,
34
+ :destination_buffer_length => :source_buffer_length
35
+ }
36
+ .freeze
37
+
38
+ def test_invalid_initialize
39
+ get_invalid_decompressor_options do |invalid_options|
40
+ assert_raises ValidateError do
41
+ target.new ::StringIO.new, invalid_options
42
+ end
43
+ end
44
+
45
+ super
46
+ end
47
+
48
+ # -- synchronous --
49
+
50
+ def test_invalid_read
51
+ instance = target.new ::StringIO.new
52
+
53
+ (Validation::INVALID_NOT_NEGATIVE_INTEGERS - [nil]).each do |invalid_integer|
54
+ assert_raises ValidateError do
55
+ instance.read invalid_integer
56
+ end
57
+ end
58
+
59
+ (Validation::INVALID_STRINGS - [nil]).each do |invalid_string|
60
+ assert_raises ValidateError do
61
+ instance.read nil, invalid_string
62
+ end
63
+ end
64
+
65
+ assert_raises ValidateError do
66
+ instance = target.new Validation::StringIOWithoutRead.new
67
+ instance.read
68
+ end
69
+
70
+ assert_raises ValidateError do
71
+ instance = target.new Validation::StringIOWithoutEOF.new
72
+ instance.read
73
+ end
74
+ end
75
+
76
+ def test_read
77
+ parallel_compressor_options do |compressor_options|
78
+ TEXTS.each do |text|
79
+ archive = get_archive text, compressor_options
80
+ prev_result = "".b
81
+
82
+ Option::BOOLS.each do |with_buffer|
83
+ PORTION_LENGTHS.each do |portion_length|
84
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
85
+ instance = target.new ::StringIO.new(archive), decompressor_options
86
+ decompressed_text = "".b
87
+
88
+ begin
89
+ result = instance.read 0
90
+ assert_equal "", result
91
+
92
+ loop do
93
+ prev_eof = instance.eof?
94
+
95
+ result =
96
+ if with_buffer
97
+ instance.read portion_length, prev_result
98
+ else
99
+ instance.read portion_length
100
+ end
101
+
102
+ if result.nil?
103
+ assert_predicate instance, :eof?
104
+ break
105
+ end
106
+
107
+ refute prev_eof unless archive.bytesize.zero?
108
+
109
+ assert_equal prev_result, result if with_buffer
110
+ decompressed_text << result
111
+ end
112
+
113
+ assert_equal instance.pos, decompressed_text.bytesize
114
+ assert_equal instance.pos, instance.tell
115
+ ensure
116
+ refute_predicate instance, :closed?
117
+ instance.close
118
+ assert_predicate instance, :closed?
119
+ end
120
+
121
+ decompressed_text.force_encoding text.encoding
122
+ assert_equal text, decompressed_text
123
+ end
124
+ end
125
+
126
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
127
+ instance = target.new ::StringIO.new(archive), decompressor_options
128
+ decompressed_text = nil
129
+
130
+ begin
131
+ prev_eof = instance.eof?
132
+
133
+ if with_buffer
134
+ decompressed_text = instance.read nil, prev_result
135
+ assert_equal prev_result, decompressed_text
136
+ else
137
+ decompressed_text = instance.read
138
+ end
139
+
140
+ assert_predicate instance, :eof?
141
+ refute prev_eof unless archive.bytesize.zero?
142
+
143
+ assert_equal instance.pos, decompressed_text.bytesize
144
+ assert_equal instance.pos, instance.tell
145
+ ensure
146
+ refute_predicate instance, :closed?
147
+ instance.close
148
+ assert_predicate instance, :closed?
149
+ end
150
+
151
+ decompressed_text.force_encoding text.encoding
152
+ assert_equal text, decompressed_text
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ def test_read_with_large_texts
160
+ options_generator = OCG.new(
161
+ :text => LARGE_TEXTS,
162
+ :with_buffer => Option::BOOLS
163
+ )
164
+
165
+ Common.parallel_options options_generator do |options|
166
+ text = options[:text]
167
+ with_buffer = options[:with_buffer]
168
+
169
+ archive = get_archive text
170
+ prev_result = "".b
171
+
172
+ LARGE_PORTION_LENGTHS.each do |portion_length|
173
+ instance = target.new ::StringIO.new(archive)
174
+ decompressed_text = "".b
175
+
176
+ begin
177
+ loop do
178
+ result =
179
+ if with_buffer
180
+ instance.read portion_length, prev_result
181
+ else
182
+ instance.read portion_length
183
+ end
184
+
185
+ break if result.nil?
186
+
187
+ assert_equal prev_result, result if with_buffer
188
+ decompressed_text << result
189
+ end
190
+ ensure
191
+ instance.close
192
+ end
193
+
194
+ decompressed_text.force_encoding text.encoding
195
+ assert_equal text, decompressed_text
196
+ end
197
+
198
+ instance = target.new ::StringIO.new(archive)
199
+ decompressed_text = nil
200
+
201
+ begin
202
+ if with_buffer
203
+ decompressed_text = instance.read nil, prev_result
204
+ assert_equal prev_result, decompressed_text
205
+ else
206
+ decompressed_text = instance.read
207
+ end
208
+ ensure
209
+ instance.close
210
+ end
211
+
212
+ decompressed_text.force_encoding text.encoding
213
+ assert_equal text, decompressed_text
214
+ end
215
+ end
216
+
217
+ def test_encoding
218
+ parallel_compressor_options do |compressor_options|
219
+ TEXTS.each do |text|
220
+ external_encoding = text.encoding
221
+ archive = get_archive text, compressor_options
222
+
223
+ PORTION_LENGTHS.each do |portion_length|
224
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
225
+ instance = target.new ::StringIO.new(archive), decompressor_options
226
+ decompressed_text = "".b
227
+
228
+ begin
229
+ result = instance.read 0
230
+ assert_equal Encoding::BINARY, result.encoding
231
+
232
+ loop do
233
+ result = instance.read portion_length
234
+ break if result.nil?
235
+
236
+ assert_equal Encoding::BINARY, result.encoding
237
+ decompressed_text << result
238
+ end
239
+ ensure
240
+ instance.close
241
+ end
242
+
243
+ decompressed_text.force_encoding external_encoding
244
+ assert_equal text, decompressed_text
245
+ end
246
+ end
247
+
248
+ # We don't need to transcode between same encodings.
249
+ (ENCODINGS - [external_encoding]).each do |internal_encoding|
250
+ target_text = text.encode internal_encoding, **TRANSCODE_OPTIONS
251
+
252
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
253
+ instance = target.new(
254
+ ::StringIO.new(archive),
255
+ decompressor_options,
256
+ :external_encoding => external_encoding,
257
+ :internal_encoding => internal_encoding,
258
+ :transcode_options => TRANSCODE_OPTIONS
259
+ )
260
+
261
+ assert_equal external_encoding, instance.external_encoding
262
+ assert_equal internal_encoding, instance.internal_encoding
263
+ assert_equal TRANSCODE_OPTIONS, instance.transcode_options
264
+
265
+ decompressed_text = nil
266
+
267
+ begin
268
+ instance.set_encoding external_encoding, internal_encoding, TRANSCODE_OPTIONS
269
+ assert_equal external_encoding, instance.external_encoding
270
+ assert_equal internal_encoding, instance.internal_encoding
271
+ assert_equal TRANSCODE_OPTIONS, instance.transcode_options
272
+
273
+ decompressed_text = instance.read
274
+ assert_equal internal_encoding, decompressed_text.encoding
275
+ ensure
276
+ instance.close
277
+ end
278
+
279
+ assert_equal target_text, decompressed_text
280
+ assert_predicate target_text, :valid_encoding?
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ def test_rewind
288
+ parallel_compressor_options do |compressor_options, worker_index|
289
+ archive_path = Common.get_path ARCHIVE_PATH, worker_index
290
+
291
+ TEXTS.each do |text|
292
+ write_archive archive_path, text, compressor_options
293
+
294
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
295
+ decompressed_text = nil
296
+
297
+ ::File.open archive_path, "rb" do |file|
298
+ instance = target.new file, decompressor_options
299
+
300
+ begin
301
+ result_1 = instance.read
302
+
303
+ assert_equal 0, instance.rewind
304
+ assert_equal 0, instance.pos
305
+ assert_equal instance.pos, instance.tell
306
+
307
+ result_2 = instance.read
308
+ assert_equal result_1, result_2
309
+
310
+ decompressed_text = result_1
311
+ ensure
312
+ instance.close
313
+ end
314
+ end
315
+
316
+ decompressed_text.force_encoding text.encoding
317
+ assert_equal text, decompressed_text
318
+ end
319
+ end
320
+ end
321
+ end
322
+
323
+ def test_invalid_eof
324
+ assert_raises ValidateError do
325
+ instance = target.new Validation::StringIOWithoutEOF.new
326
+ instance.eof?
327
+ end
328
+ end
329
+
330
+ def test_eof
331
+ compressed_text = String.compress "ab"
332
+ instance = target.new ::StringIO.new(compressed_text)
333
+
334
+ refute_predicate instance, :eof?
335
+
336
+ byte = instance.read 1
337
+ refute_predicate instance, :eof?
338
+ assert_equal "a", byte
339
+
340
+ byte = instance.read 1
341
+ assert_predicate instance, :eof?
342
+ assert_equal "b", byte
343
+ end
344
+
345
+ # -- asynchronous --
346
+
347
+ def test_invalid_readpartial_and_read_nonblock
348
+ instance = target.new ::StringIO.new
349
+
350
+ Validation::INVALID_NOT_NEGATIVE_INTEGERS.each do |invalid_integer|
351
+ assert_raises ValidateError do
352
+ instance.readpartial invalid_integer
353
+ end
354
+ assert_raises ValidateError do
355
+ instance.read_nonblock invalid_integer
356
+ end
357
+ end
358
+
359
+ (Validation::INVALID_STRINGS - [nil]).each do |invalid_string|
360
+ assert_raises ValidateError do
361
+ instance.readpartial 0, invalid_string
362
+ end
363
+ assert_raises ValidateError do
364
+ instance.read_nonblock 0, invalid_string
365
+ end
366
+ end
367
+
368
+ assert_raises ValidateError do
369
+ instance = target.new Validation::StringIOWithoutReadpartial.new
370
+ instance.readpartial 1
371
+ end
372
+
373
+ assert_raises ValidateError do
374
+ instance = target.new Validation::StringIOWithoutReadNonblock.new
375
+ instance.read_nonblock 1
376
+ end
377
+ end
378
+
379
+ def test_readpartial
380
+ IO.pipe do |read_io, write_io|
381
+ instance = target.new read_io
382
+ write_io.close
383
+
384
+ assert_raises ::EOFError do
385
+ instance.readpartial 1
386
+ end
387
+ end
388
+
389
+ nonblock_server do |server|
390
+ parallel_compressor_options do |compressor_options|
391
+ TEXTS.each do |text|
392
+ PORTION_LENGTHS.each do |portion_length|
393
+ Option::BOOLS.each do |with_buffer|
394
+ nonblock_test server, text, portion_length, compressor_options do |instance|
395
+ prev_result = "".b
396
+ decompressed_text = "".b
397
+
398
+ loop do
399
+ if with_buffer
400
+ result = instance.readpartial portion_length, prev_result
401
+ assert_equal prev_result, result
402
+ else
403
+ result = instance.readpartial portion_length
404
+ end
405
+
406
+ decompressed_text << result
407
+ rescue ::EOFError
408
+ break
409
+ end
410
+
411
+ decompressed_text
412
+ end
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end
418
+ end
419
+
420
+ def test_read_nonblock
421
+ IO.pipe do |read_io, write_io|
422
+ instance = target.new read_io
423
+
424
+ assert_raises ::IO::WaitReadable do
425
+ instance.read_nonblock 1
426
+ end
427
+
428
+ write_io.close
429
+
430
+ assert_raises ::EOFError do
431
+ instance.read_nonblock 1
432
+ end
433
+ end
434
+
435
+ nonblock_server do |server|
436
+ parallel_compressor_options do |compressor_options|
437
+ TEXTS.each do |text|
438
+ PORTION_LENGTHS.each do |portion_length|
439
+ nonblock_test server, text, portion_length, compressor_options do |instance, socket|
440
+ decompressed_text = "".b
441
+
442
+ loop do
443
+ begin
444
+ decompressed_text << instance.read_nonblock(portion_length)
445
+ rescue ::IO::WaitReadable
446
+ socket.wait_readable
447
+ retry
448
+ rescue ::EOFError
449
+ break
450
+ end
451
+
452
+ begin
453
+ decompressed_text << instance.readpartial(portion_length)
454
+ rescue ::EOFError
455
+ break
456
+ end
457
+
458
+ result = instance.read portion_length
459
+ break if result.nil?
460
+
461
+ decompressed_text << result
462
+ end
463
+
464
+ decompressed_text
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ def test_read_nonblock_with_large_texts
473
+ nonblock_server do |server|
474
+ Common.parallel LARGE_TEXTS do |text|
475
+ LARGE_PORTION_LENGTHS.each do |portion_length|
476
+ nonblock_test server, text, portion_length do |instance, socket|
477
+ decompressed_text = "".b
478
+
479
+ loop do
480
+ begin
481
+ decompressed_text << instance.read_nonblock(portion_length)
482
+ rescue ::IO::WaitReadable
483
+ socket.wait_readable
484
+ retry
485
+ rescue ::EOFError
486
+ break
487
+ end
488
+
489
+ begin
490
+ decompressed_text << instance.readpartial(portion_length)
491
+ rescue ::EOFError
492
+ break
493
+ end
494
+
495
+ result = instance.read portion_length
496
+ break if result.nil?
497
+
498
+ decompressed_text << result
499
+ end
500
+
501
+ decompressed_text
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ # -- nonblock test --
509
+
510
+ protected def nonblock_server
511
+ # Server need just to redirect content for client.
512
+
513
+ ::TCPServer.open 0 do |server|
514
+ # Server loop will be processed in separate (parent) thread.
515
+ # Child threads will be collected for later usage.
516
+ child_lock = ::Mutex.new
517
+ child_threads = ::Set.new
518
+
519
+ parent_thread = ::Thread.new do
520
+ loop do
521
+ child_thread = ::Thread.start server.accept do |socket|
522
+ result = "".b
523
+
524
+ # Reading head.
525
+ result_size, portion_length = socket.read(16).unpack "QQ"
526
+ next if result_size.zero?
527
+
528
+ # Reading result.
529
+ loop do
530
+ begin
531
+ result << socket.read_nonblock(portion_length)
532
+ rescue ::IO::WaitReadable
533
+ socket.wait_readable
534
+ retry
535
+ end
536
+
537
+ break if result.bytesize == result_size
538
+ end
539
+
540
+ # Writing result.
541
+ loop do
542
+ begin
543
+ bytes_written = socket.write_nonblock result
544
+ rescue ::IO::WaitWritable
545
+ socket.wait_writable
546
+ retry
547
+ end
548
+
549
+ result = result.byteslice bytes_written, result.bytesize - bytes_written
550
+ result_size -= bytes_written
551
+
552
+ break if result_size.zero?
553
+ end
554
+
555
+ ensure
556
+ socket.close
557
+
558
+ # Removing current child thread.
559
+ child_lock.synchronize { child_threads.delete ::Thread.current }
560
+ end
561
+
562
+ # Adding new child thread.
563
+ child_lock.synchronize { child_threads.add child_thread }
564
+ end
565
+ end
566
+
567
+ # Processing client.
568
+ begin
569
+ yield server
570
+ ensure
571
+ # We need to kill parent thread when client has finished.
572
+ # So server won't be able to create new child threads.
573
+ # Than we can join all remaining child threads.
574
+ parent_thread.kill.join
575
+ child_threads.each(&:join)
576
+ end
577
+ end
578
+ end
579
+
580
+ protected def nonblock_test(server, text, portion_length, compressor_options = {}, &_block)
581
+ port = server.addr[1]
582
+ compressed_text = String.compress text, compressor_options
583
+
584
+ processor = proc do |decompressor_options|
585
+ decompressed_text = ::TCPSocket.open "localhost", port do |socket|
586
+ # Writing head.
587
+ head = [compressed_text.bytesize, portion_length].pack "QQ"
588
+ socket.write head
589
+
590
+ # Writing compressed text.
591
+ socket.write compressed_text
592
+
593
+ instance = target.new socket, decompressor_options
594
+
595
+ begin
596
+ yield instance, socket
597
+ ensure
598
+ instance.close
599
+ end
600
+ end
601
+
602
+ # Testing decompressed text.
603
+ decompressed_text.force_encoding text.encoding
604
+ assert_equal text, decompressed_text
605
+ end
606
+
607
+ if compressor_options.empty?
608
+ processor.call({})
609
+ else
610
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
611
+ processor.call decompressor_options
612
+ end
613
+ end
614
+ end
615
+
616
+ # -----
617
+
618
+ protected def get_archive(text, compressor_options = {})
619
+ String.compress text, compressor_options
620
+ end
621
+
622
+ protected def write_archive(archive_path, text, compressor_options = {})
623
+ compressed_text = String.compress text, compressor_options
624
+ ::File.write archive_path, compressed_text, :mode => "wb"
625
+ end
626
+
627
+ def get_invalid_decompressor_options(&block)
628
+ Option.get_invalid_decompressor_options BUFFER_LENGTH_NAMES, &block
629
+ end
630
+
631
+ def parallel_compressor_options(&block)
632
+ Common.parallel_options Option.get_compressor_options_generator(BUFFER_LENGTH_NAMES), &block
633
+ end
634
+
635
+ def get_compatible_decompressor_options(compressor_options, &block)
636
+ Option.get_compatible_decompressor_options compressor_options, BUFFER_LENGTH_MAPPING, &block
637
+ end
638
+ end
639
+
640
+ Minitest << Reader
641
+ end
642
+ end
643
+ end