adsp 1.0.2 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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