adsp 1.0.2 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/{test → lib/adsp/test}/common.rb +4 -1
  3. data/{test → lib/adsp/test}/coverage_helper.rb +0 -0
  4. data/lib/adsp/test/file.rb +120 -0
  5. data/{test → lib/adsp/test}/minitest.rb +1 -0
  6. data/{test → lib/adsp/test}/mock/common.rb +1 -0
  7. data/{test → lib/adsp/test}/mock/file.rb +1 -0
  8. data/{test → lib/adsp/test}/mock/stream/raw/compressor.rb +1 -0
  9. data/{test → lib/adsp/test}/mock/stream/raw/decompressor.rb +1 -0
  10. data/{test → lib/adsp/test}/mock/stream/raw/native_compressor.rb +1 -0
  11. data/{test → lib/adsp/test}/mock/stream/raw/native_decompressor.rb +1 -0
  12. data/{test → lib/adsp/test}/mock/stream/reader.rb +1 -0
  13. data/{test → lib/adsp/test}/mock/stream/writer.rb +1 -0
  14. data/{test → lib/adsp/test}/mock/string.rb +1 -0
  15. data/{test → lib/adsp/test}/option.rb +1 -0
  16. data/{test → lib/adsp/test}/stream/abstract.rb +1 -0
  17. data/lib/adsp/test/stream/minitar.rb +50 -0
  18. data/{test → lib/adsp/test}/stream/raw/abstract.rb +1 -0
  19. data/lib/adsp/test/stream/raw/compressor.rb +165 -0
  20. data/lib/adsp/test/stream/raw/decompressor.rb +165 -0
  21. data/lib/adsp/test/stream/reader.rb +642 -0
  22. data/lib/adsp/test/stream/reader_helpers.rb +421 -0
  23. data/lib/adsp/test/stream/writer.rb +609 -0
  24. data/lib/adsp/test/stream/writer_helpers.rb +267 -0
  25. data/lib/adsp/test/string.rb +95 -0
  26. data/{test → lib/adsp/test}/validation.rb +7 -0
  27. data/lib/adsp/test/version.rb +18 -0
  28. data/lib/adsp/version.rb +1 -1
  29. data/test/file.test.rb +3 -116
  30. data/test/stream/minitar.test.rb +3 -46
  31. data/test/stream/raw/compressor.test.rb +3 -162
  32. data/test/stream/raw/decompressor.test.rb +3 -162
  33. data/test/stream/reader.test.rb +3 -639
  34. data/test/stream/reader_helpers.test.rb +3 -417
  35. data/test/stream/writer.test.rb +3 -606
  36. data/test/stream/writer_helpers.test.rb +3 -263
  37. data/test/string.test.rb +3 -91
  38. data/test/version.test.rb +3 -14
  39. metadata +28 -19
@@ -0,0 +1,609 @@
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 "../option"
11
+ require_relative "../mock/stream/writer"
12
+ require_relative "../mock/string"
13
+
14
+ module ADSP
15
+ module Test
16
+ # ADSP::Test::Stream module.
17
+ module Stream
18
+ # ADSP::Test::Stream::Writer class.
19
+ class Writer < Abstract
20
+ Target = Mock::Stream::Writer
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[destination_buffer_length].freeze
32
+ BUFFER_LENGTH_MAPPING = { :destination_buffer_length => :destination_buffer_length }.freeze
33
+ FINISH_MODES = OCG.new(
34
+ :flush_nonblock => Option::BOOLS,
35
+ :close_nonblock => Option::BOOLS
36
+ )
37
+ .freeze
38
+
39
+ NONBLOCK_SERVER_MODES = {
40
+ :request => 0,
41
+ :response => 1
42
+ }
43
+ .freeze
44
+
45
+ NONBLOCK_SERVER_TIMEOUT = 0.1
46
+
47
+ def initialize(*args)
48
+ super(*args)
49
+
50
+ @nonblock_client_lock = ::Mutex.new
51
+ @nonblock_client_id = 0
52
+ end
53
+
54
+ def test_invalid_initialize
55
+ get_invalid_compressor_options do |invalid_options|
56
+ assert_raises ValidateError do
57
+ target.new ::StringIO.new, invalid_options
58
+ end
59
+ end
60
+
61
+ super
62
+ end
63
+
64
+ # -- synchronous --
65
+
66
+ def test_invalid_write
67
+ instance = target.new Validation::StringIOWithoutWrite.new
68
+
69
+ assert_raises ValidateError do
70
+ instance.write ""
71
+ end
72
+
73
+ assert_raises ValidateError do
74
+ instance.flush
75
+ end
76
+
77
+ assert_raises ValidateError do
78
+ instance.rewind
79
+ end
80
+
81
+ assert_raises ValidateError do
82
+ instance.close
83
+ end
84
+ end
85
+
86
+ def test_write
87
+ parallel_compressor_options do |compressor_options|
88
+ TEXTS.each do |text|
89
+ PORTION_LENGTHS.each do |portion_length|
90
+ sources = get_sources text, portion_length
91
+ io = ::StringIO.new
92
+ instance = target.new io, compressor_options
93
+
94
+ begin
95
+ sources.each_slice 2 do |current_sources|
96
+ instance.write(*current_sources)
97
+ instance.flush
98
+ end
99
+
100
+ assert_equal instance.pos, text.bytesize
101
+ assert_equal instance.pos, instance.tell
102
+ ensure
103
+ refute_predicate instance, :closed?
104
+ instance.close
105
+ assert_predicate instance, :closed?
106
+ end
107
+
108
+ compressed_text = io.string
109
+
110
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
111
+ check_text text, compressed_text, decompressor_options
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def test_write_with_large_texts
119
+ options_generator = OCG.new(
120
+ :text => LARGE_TEXTS,
121
+ :portion_length => LARGE_PORTION_LENGTHS
122
+ )
123
+
124
+ Common.parallel_options options_generator do |options|
125
+ text = options[:text]
126
+ portion_length = options[:portion_length]
127
+
128
+ sources = get_sources text, portion_length
129
+ io = ::StringIO.new
130
+ instance = target.new io
131
+
132
+ begin
133
+ sources.each_slice 2 do |current_sources|
134
+ instance.write(*current_sources)
135
+ instance.flush
136
+ end
137
+ ensure
138
+ instance.close
139
+ end
140
+
141
+ compressed_text = io.string
142
+ check_text text, compressed_text
143
+ end
144
+ end
145
+
146
+ def test_encoding
147
+ parallel_compressor_options do |compressor_options|
148
+ TEXTS.each do |text|
149
+ # We don't need to transcode between same encodings.
150
+ (ENCODINGS - [text.encoding]).each do |external_encoding|
151
+ target_text = text.encode external_encoding, **TRANSCODE_OPTIONS
152
+ io = ::StringIO.new
153
+
154
+ instance = target.new(
155
+ io,
156
+ compressor_options,
157
+ :external_encoding => external_encoding,
158
+ :transcode_options => TRANSCODE_OPTIONS
159
+ )
160
+
161
+ assert_equal external_encoding, instance.external_encoding
162
+ assert_equal TRANSCODE_OPTIONS, instance.transcode_options
163
+
164
+ begin
165
+ instance.set_encoding external_encoding, nil, TRANSCODE_OPTIONS
166
+ assert_equal external_encoding, instance.external_encoding
167
+ assert_equal TRANSCODE_OPTIONS, instance.transcode_options
168
+
169
+ instance.write text
170
+ ensure
171
+ instance.close
172
+ end
173
+
174
+ compressed_text = io.string
175
+
176
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
177
+ check_text target_text, compressed_text, decompressor_options
178
+ assert_predicate target_text, :valid_encoding?
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ def test_rewind
186
+ parallel_compressor_options do |compressor_options, worker_index|
187
+ archive_path = Common.get_path ARCHIVE_PATH, worker_index
188
+
189
+ compressed_texts = []
190
+
191
+ ::File.open archive_path, "wb" do |file|
192
+ instance = target.new file, compressor_options
193
+
194
+ begin
195
+ TEXTS.each do |text|
196
+ instance.write text
197
+ instance.flush
198
+
199
+ assert_equal instance.pos, text.bytesize
200
+ assert_equal instance.pos, instance.tell
201
+
202
+ assert_equal 0, instance.rewind
203
+
204
+ compressed_texts << ::File.read(archive_path, :mode => "rb")
205
+
206
+ assert_equal 0, instance.pos
207
+ assert_equal instance.pos, instance.tell
208
+
209
+ file.truncate 0
210
+ end
211
+ ensure
212
+ instance.close
213
+ end
214
+ end
215
+
216
+ TEXTS.each.with_index do |text, index|
217
+ compressed_text = compressed_texts[index]
218
+
219
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
220
+ check_text text, compressed_text, decompressor_options
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ # -- asynchronous --
227
+
228
+ def test_invalid_write_nonblock
229
+ instance = target.new Validation::StringIOWithoutWriteNonblock.new
230
+
231
+ assert_raises ValidateError do
232
+ instance.write_nonblock ""
233
+ end
234
+
235
+ assert_raises ValidateError do
236
+ instance.flush_nonblock
237
+ end
238
+
239
+ assert_raises ValidateError do
240
+ instance.close_nonblock
241
+ end
242
+ end
243
+
244
+ def test_write_nonblock
245
+ nonblock_server do |server|
246
+ parallel_compressor_options do |compressor_options|
247
+ TEXTS.each do |text|
248
+ PORTION_LENGTHS.each do |portion_length|
249
+ sources = get_sources text, portion_length
250
+
251
+ FINISH_MODES.each do |finish_mode|
252
+ nonblock_test server, text, portion_length, compressor_options do |instance, socket|
253
+ # write
254
+
255
+ sources.each.with_index do |source, index|
256
+ if index.even?
257
+ loop do
258
+ begin
259
+ bytes_written = instance.write_nonblock source
260
+ rescue ::IO::WaitWritable
261
+ socket.wait_writable
262
+ retry
263
+ end
264
+
265
+ source = source.byteslice bytes_written, source.bytesize - bytes_written
266
+ break if source.bytesize.zero?
267
+ end
268
+ else
269
+ instance.write source
270
+ end
271
+ end
272
+
273
+ # flush
274
+
275
+ if finish_mode[:flush_nonblock]
276
+ loop do
277
+ begin
278
+ is_flushed = instance.flush_nonblock
279
+ rescue ::IO::WaitWritable
280
+ socket.wait_writable
281
+ retry
282
+ end
283
+
284
+ break if is_flushed
285
+ end
286
+ else
287
+ instance.flush
288
+ end
289
+
290
+ assert_equal instance.pos, text.bytesize
291
+ assert_equal instance.pos, instance.tell
292
+
293
+ ensure
294
+ # close
295
+
296
+ refute_predicate instance, :closed?
297
+
298
+ if finish_mode[:close_nonblock]
299
+ loop do
300
+ begin
301
+ is_closed = instance.close_nonblock
302
+ rescue ::IO::WaitWritable
303
+ socket.wait_writable
304
+ retry
305
+ end
306
+
307
+ break if is_closed
308
+ end
309
+ else
310
+ instance.close
311
+ end
312
+
313
+ assert_predicate instance, :closed?
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ def test_write_nonblock_with_large_texts
323
+ nonblock_server do |server|
324
+ Common.parallel LARGE_TEXTS do |text|
325
+ LARGE_PORTION_LENGTHS.each do |portion_length|
326
+ sources = get_sources text, portion_length
327
+
328
+ FINISH_MODES.each do |finish_mode|
329
+ nonblock_test server, text, portion_length do |instance, socket|
330
+ # write
331
+
332
+ sources.each.with_index do |source, index|
333
+ if index.even?
334
+ loop do
335
+ begin
336
+ bytes_written = instance.write_nonblock source
337
+ rescue ::IO::WaitWritable
338
+ socket.wait_writable
339
+ retry
340
+ end
341
+
342
+ source = source.byteslice bytes_written, source.bytesize - bytes_written
343
+ break if source.bytesize.zero?
344
+ end
345
+ else
346
+ instance.write source
347
+ end
348
+ end
349
+
350
+ # flush
351
+
352
+ if finish_mode[:flush_nonblock]
353
+ loop do
354
+ begin
355
+ is_flushed = instance.flush_nonblock
356
+ rescue ::IO::WaitWritable
357
+ socket.wait_writable
358
+ retry
359
+ end
360
+
361
+ break if is_flushed
362
+ end
363
+ else
364
+ instance.flush
365
+ end
366
+
367
+ ensure
368
+ # close
369
+
370
+ if finish_mode[:close_nonblock]
371
+ loop do
372
+ begin
373
+ is_closed = instance.close_nonblock
374
+ rescue ::IO::WaitWritable
375
+ socket.wait_writable
376
+ retry
377
+ end
378
+
379
+ break if is_closed
380
+ end
381
+ else
382
+ instance.close
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ def test_invalid_rewind_nonblock
392
+ instance = target.new Validation::StringIOWithoutWriteNonblock.new
393
+
394
+ assert_raises ValidateError do
395
+ instance.rewind_nonblock
396
+ end
397
+ end
398
+
399
+ def test_rewind_nonblock
400
+ return unless Common.file_can_be_used_nonblock?
401
+
402
+ parallel_compressor_options do |compressor_options, worker_index|
403
+ archive_path = Common.get_path ARCHIVE_PATH, worker_index
404
+
405
+ compressed_texts = []
406
+
407
+ ::File.open archive_path, "wb" do |file|
408
+ instance = target.new file, compressor_options
409
+
410
+ begin
411
+ TEXTS.each do |text|
412
+ instance.write text
413
+ instance.flush
414
+
415
+ assert_equal instance.pos, text.bytesize
416
+ assert_equal instance.pos, instance.tell
417
+
418
+ loop do
419
+ begin
420
+ is_rewinded = instance.rewind_nonblock
421
+ rescue ::IO::WaitWritable
422
+ file.wait_writable
423
+ retry
424
+ end
425
+
426
+ break if is_rewinded
427
+ end
428
+
429
+ compressed_texts << ::File.read(archive_path, :mode => "rb")
430
+
431
+ assert_equal 0, instance.pos
432
+ assert_equal instance.pos, instance.tell
433
+
434
+ file.truncate 0
435
+ end
436
+ ensure
437
+ instance.close
438
+ end
439
+ end
440
+
441
+ TEXTS.each.with_index do |text, index|
442
+ compressed_text = compressed_texts[index]
443
+
444
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
445
+ check_text text, compressed_text, decompressor_options
446
+ end
447
+ end
448
+ end
449
+ end
450
+
451
+ # -- nonblock test --
452
+
453
+ protected def nonblock_server
454
+ # We need to test close nonblock.
455
+ # This method writes remaining data and closes socket.
456
+ # Server is not able to send response immediately.
457
+ # Client has to reconnect to server once again.
458
+
459
+ ::TCPServer.open 0 do |server|
460
+ # Server loop will be processed in separate (parent) thread.
461
+ # Child threads will be collected for later usage.
462
+ child_lock = ::Mutex.new
463
+ child_threads = ::Set.new
464
+
465
+ # Server need to maintain mapping between client id and result.
466
+ results_lock = ::Mutex.new
467
+ results = {}
468
+
469
+ parent_thread = ::Thread.new do
470
+ loop do
471
+ child_thread = ::Thread.start server.accept do |socket|
472
+ # Reading head.
473
+ client_id, portion_length, mode = socket.read(13).unpack "NQC"
474
+
475
+ if mode == NONBLOCK_SERVER_MODES[:request]
476
+ # Reading result from client.
477
+ result = "".b
478
+
479
+ loop do
480
+ result << socket.read_nonblock(portion_length)
481
+ rescue ::IO::WaitReadable
482
+ socket.wait_readable
483
+ rescue ::EOFError
484
+ break
485
+ end
486
+
487
+ # Saving result for client.
488
+ results_lock.synchronize { results[client_id] = result }
489
+
490
+ next
491
+ end
492
+
493
+ loop do
494
+ # Waiting when result will be ready.
495
+ result = results_lock.synchronize { results[client_id] }
496
+
497
+ unless result.nil?
498
+ # Sending result to client.
499
+ socket.write result
500
+
501
+ break
502
+ end
503
+
504
+ sleep NONBLOCK_SERVER_TIMEOUT
505
+ end
506
+
507
+ # Removing result for client.
508
+ results_lock.synchronize { results.delete client_id }
509
+
510
+ ensure
511
+ socket.close
512
+
513
+ # Removing current child thread.
514
+ child_lock.synchronize { child_threads.delete ::Thread.current }
515
+ end
516
+
517
+ # Adding new child thread.
518
+ child_lock.synchronize { child_threads.add child_thread }
519
+ end
520
+ end
521
+
522
+ # Processing client.
523
+ begin
524
+ yield server
525
+ ensure
526
+ # We need to kill parent thread when client has finished.
527
+ # So server won't be able to create new child threads.
528
+ # Than we can join all remaining child threads.
529
+ parent_thread.kill.join
530
+ child_threads.each(&:join)
531
+ end
532
+ end
533
+ end
534
+
535
+ protected def nonblock_test(server, text, portion_length, compressor_options = {}, &_block)
536
+ port = server.addr[1]
537
+ client_id = @nonblock_client_lock.synchronize { @nonblock_client_id += 1 }
538
+
539
+ # Writing request.
540
+ ::TCPSocket.open "localhost", port do |socket|
541
+ # Writing head.
542
+ head = [client_id, portion_length, NONBLOCK_SERVER_MODES[:request]].pack "NQC"
543
+ socket.write head
544
+
545
+ # Instance is going to write compressed text.
546
+ instance = target.new socket, compressor_options
547
+
548
+ begin
549
+ yield instance, socket
550
+ ensure
551
+ instance.close
552
+ end
553
+ end
554
+
555
+ # Reading response.
556
+ compressed_text = ::TCPSocket.open "localhost", port do |socket|
557
+ # Writing head.
558
+ head = [client_id, portion_length, NONBLOCK_SERVER_MODES[:response]].pack "NQC"
559
+ socket.write head
560
+
561
+ # Reading compressed text.
562
+ socket.read
563
+ end
564
+
565
+ # Testing compressed text.
566
+ if compressor_options.empty?
567
+ check_text text, compressed_text
568
+ else
569
+ get_compatible_decompressor_options compressor_options do |decompressor_options|
570
+ check_text text, compressed_text, decompressor_options
571
+ end
572
+ end
573
+ end
574
+
575
+ # -----
576
+
577
+ protected def get_sources(text, portion_length)
578
+ sources = text
579
+ .chars
580
+ .each_slice(portion_length)
581
+ .map(&:join)
582
+
583
+ return ["".b] if sources.empty?
584
+
585
+ sources
586
+ end
587
+
588
+ protected def check_text(text, compressed_text, decompressor_options = {})
589
+ decompressed_text = String.decompress compressed_text, decompressor_options
590
+ decompressed_text.force_encoding text.encoding
591
+
592
+ assert_equal text, decompressed_text
593
+ end
594
+
595
+ def get_invalid_compressor_options(&block)
596
+ Option.get_invalid_compressor_options BUFFER_LENGTH_NAMES, &block
597
+ end
598
+
599
+ def parallel_compressor_options(&block)
600
+ Common.parallel_options Option.get_compressor_options_generator(BUFFER_LENGTH_NAMES), &block
601
+ end
602
+
603
+ def get_compatible_decompressor_options(compressor_options, &block)
604
+ Option.get_compatible_decompressor_options compressor_options, BUFFER_LENGTH_MAPPING, &block
605
+ end
606
+ end
607
+ end
608
+ end
609
+ end