rapidflow 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/README.md +78 -63
- data/lib/rapidflow/batch.rb +9 -25
- data/lib/rapidflow/batch_builder.rb +16 -0
- data/lib/rapidflow/counter.rb +1 -1
- data/lib/rapidflow/errors.rb +7 -0
- data/lib/rapidflow/pipeline.rb +1 -1
- data/lib/rapidflow/stage.rb +9 -1
- data/lib/rapidflow/version.rb +2 -2
- data/lib/rapidflow/work_item.rb +1 -1
- data/lib/rapidflow.rb +3 -1
- data/scripts/benchmark/benchmark_api_request_process_and_storing.rb +11 -11
- data/scripts/benchmark/benchmark_images.rb +6 -6
- data/scripts/benchmark/simulated_data_processing.rb +6 -6
- data/sig/rapidflow.rbs +1 -1
- data/test/rapidflow/batch/config_error_test.rb +43 -0
- data/test/rapidflow/batch/error_handling_test.rb +211 -0
- data/test/rapidflow/batch_test.rb +71 -222
- data/test/rapidflow/counter_test.rb +1 -1
- data/test/rapidflow/pipeline_test.rb +67 -0
- data/test/rapidflow/stage_test.rb +110 -0
- data/test/rapidflow/work_item_test.rb +1 -1
- metadata +7 -2
- data/.github/workflows/main.yml +0 -35
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "test_helper"
|
|
2
3
|
|
|
3
|
-
module
|
|
4
|
+
module RapidFlow
|
|
4
5
|
class BatchTest < Minitest::Test
|
|
5
6
|
def test_basic_functionality_with_arg_tasks
|
|
6
|
-
|
|
7
|
+
batch = Batch.new(
|
|
7
8
|
{ fn: ->(data) { data.upcase }, workers: 4 },
|
|
8
9
|
{ fn: ->(data) { data + "!" }, workers: 4 }
|
|
9
10
|
)
|
|
10
|
-
|
|
11
|
+
batch.start
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
batch.push("hello")
|
|
14
|
+
batch.push("world")
|
|
14
15
|
|
|
15
|
-
results =
|
|
16
|
+
results = batch.results
|
|
16
17
|
|
|
17
18
|
assert_equal 2, results.length
|
|
18
19
|
assert_equal ["HELLO!", nil], results[0]
|
|
@@ -20,7 +21,7 @@ module Rapidflow
|
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def test_basic_functionality_with_build
|
|
23
|
-
|
|
24
|
+
batch = Batch.build do
|
|
24
25
|
# first stage to up case string
|
|
25
26
|
stage ->(data) { data.upcase }
|
|
26
27
|
|
|
@@ -28,38 +29,23 @@ module Rapidflow
|
|
|
28
29
|
stage ->(data) { data + "!" }
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
batch.push("hello")
|
|
33
|
+
batch.push("world")
|
|
33
34
|
|
|
34
|
-
results =
|
|
35
|
+
results = batch.results
|
|
35
36
|
|
|
36
37
|
assert_equal 2, results.length
|
|
37
38
|
assert_equal ["HELLO!", nil], results[0]
|
|
38
39
|
assert_equal ["WORLD!", nil], results[1]
|
|
39
40
|
end
|
|
40
41
|
|
|
41
|
-
def test_no_stages_with_build
|
|
42
|
-
assert_raises(Batch::ConfigError, "Unable to start the belt without any stages") do
|
|
43
|
-
Batch.build do
|
|
44
|
-
# no stages
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def test_no_stages_belt_start
|
|
50
|
-
assert_raises(Batch::ConfigError, "Unable to start the belt without any stages") do
|
|
51
|
-
belt = Batch.new
|
|
52
|
-
belt.start
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
42
|
def test_concurrent_execution_is_faster_than_sequential
|
|
57
43
|
# Each lambda sleeps for 0.5 seconds
|
|
58
44
|
# With 4 items and 2 stages:
|
|
59
45
|
# - Sequential would take: 4 items * 0.5s * 2 stages = 4 seconds
|
|
60
46
|
# - Concurrent (4 workers per stage) should take: max(0.5s, 0.5s) = ~0.5-1s
|
|
61
47
|
|
|
62
|
-
|
|
48
|
+
batch = Batch.build do
|
|
63
49
|
stage ->(data) {
|
|
64
50
|
sleep(0.5)
|
|
65
51
|
data
|
|
@@ -72,8 +58,8 @@ module Rapidflow
|
|
|
72
58
|
|
|
73
59
|
start_time = Time.now
|
|
74
60
|
|
|
75
|
-
4.times { |i|
|
|
76
|
-
results =
|
|
61
|
+
4.times { |i| batch.push(i) }
|
|
62
|
+
results = batch.results
|
|
77
63
|
|
|
78
64
|
elapsed = Time.now - start_time
|
|
79
65
|
|
|
@@ -89,7 +75,7 @@ module Rapidflow
|
|
|
89
75
|
stage1_executing = []
|
|
90
76
|
stage2_executing = []
|
|
91
77
|
|
|
92
|
-
|
|
78
|
+
batch = Batch.build do
|
|
93
79
|
stage ->(data) {
|
|
94
80
|
execution_tracker.synchronize { stage1_executing << data }
|
|
95
81
|
sleep(0.3)
|
|
@@ -105,7 +91,7 @@ module Rapidflow
|
|
|
105
91
|
end
|
|
106
92
|
|
|
107
93
|
# Push multiple items quickly
|
|
108
|
-
10.times { |i|
|
|
94
|
+
10.times { |i| batch.push(i) }
|
|
109
95
|
|
|
110
96
|
# Give threads time to start processing
|
|
111
97
|
sleep(0.1)
|
|
@@ -117,7 +103,7 @@ module Rapidflow
|
|
|
117
103
|
"Expected concurrent execution, but only #{stage1_executing.length} items processing"
|
|
118
104
|
end
|
|
119
105
|
|
|
120
|
-
results =
|
|
106
|
+
results = batch.results
|
|
121
107
|
assert_equal 10, results.length
|
|
122
108
|
end
|
|
123
109
|
|
|
@@ -125,7 +111,7 @@ module Rapidflow
|
|
|
125
111
|
# Track execution order to verify pipeline behavior
|
|
126
112
|
execution_log = Queue.new
|
|
127
113
|
|
|
128
|
-
|
|
114
|
+
batch = Batch.build do
|
|
129
115
|
stage ->(data) {
|
|
130
116
|
execution_log.push("stage1_start_#{data}")
|
|
131
117
|
sleep(0.2)
|
|
@@ -140,11 +126,11 @@ module Rapidflow
|
|
|
140
126
|
}
|
|
141
127
|
end
|
|
142
128
|
|
|
143
|
-
|
|
129
|
+
batch.push("A")
|
|
144
130
|
sleep(0.1) # Let A start processing
|
|
145
|
-
|
|
131
|
+
batch.push("B")
|
|
146
132
|
|
|
147
|
-
|
|
133
|
+
batch.results
|
|
148
134
|
|
|
149
135
|
# Convert log to array
|
|
150
136
|
log = []
|
|
@@ -162,49 +148,23 @@ module Rapidflow
|
|
|
162
148
|
assert stage1_start_b, "B should have started in stage1"
|
|
163
149
|
end
|
|
164
150
|
|
|
165
|
-
def test_error_handling_captures_exceptions
|
|
166
|
-
belt = Batch.build do
|
|
167
|
-
stage ->(data) {
|
|
168
|
-
raise "Error in stage 1" if data == "bad"
|
|
169
|
-
data
|
|
170
|
-
}
|
|
171
|
-
stage ->(data) { data.upcase }
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
belt.push("good")
|
|
175
|
-
belt.push("bad")
|
|
176
|
-
|
|
177
|
-
results = belt.results
|
|
178
|
-
|
|
179
|
-
assert_equal 2, results.length
|
|
180
|
-
|
|
181
|
-
# Good result should complete both stages
|
|
182
|
-
assert_equal "GOOD", results[0][0]
|
|
183
|
-
assert_nil results[0][1]
|
|
184
|
-
|
|
185
|
-
# Bad result should have error from stage 1 and not be processed by stage 2
|
|
186
|
-
assert_equal "bad", results[1][0] # Original data preserved
|
|
187
|
-
assert_instance_of RuntimeError, results[1][1]
|
|
188
|
-
assert_equal "Error in stage 1", results[1][1].message
|
|
189
|
-
end
|
|
190
|
-
|
|
191
151
|
def test_cannot_push_after_results_called
|
|
192
|
-
|
|
152
|
+
batch = Batch.build do
|
|
193
153
|
stage ->(data) { data }
|
|
194
154
|
end
|
|
195
155
|
|
|
196
|
-
|
|
197
|
-
|
|
156
|
+
batch.push("item1")
|
|
157
|
+
batch.results
|
|
198
158
|
|
|
199
|
-
assert_raises(
|
|
200
|
-
|
|
201
|
-
|
|
159
|
+
error = assert_raises(RapidFlow::RunError) { batch.push("item2") }
|
|
160
|
+
|
|
161
|
+
assert_equal "Cannot push to a locked batch when results are requested", error.message
|
|
202
162
|
end
|
|
203
163
|
|
|
204
164
|
def test_results_waits_for_all_processing_to_complete
|
|
205
165
|
completion_times = Queue.new
|
|
206
166
|
|
|
207
|
-
|
|
167
|
+
batch = Batch.build do
|
|
208
168
|
stage ->(data) {
|
|
209
169
|
sleep(0.5)
|
|
210
170
|
data
|
|
@@ -215,11 +175,11 @@ module Rapidflow
|
|
|
215
175
|
}
|
|
216
176
|
end
|
|
217
177
|
|
|
218
|
-
|
|
219
|
-
|
|
178
|
+
batch.push("item1")
|
|
179
|
+
batch.push("item2")
|
|
220
180
|
|
|
221
181
|
Time.now
|
|
222
|
-
results =
|
|
182
|
+
results = batch.results
|
|
223
183
|
results_end = Time.now
|
|
224
184
|
|
|
225
185
|
# All items should have completed before results returns
|
|
@@ -237,7 +197,7 @@ module Rapidflow
|
|
|
237
197
|
def test_high_throughput_with_many_items
|
|
238
198
|
item_count = 100
|
|
239
199
|
|
|
240
|
-
|
|
200
|
+
batch = Batch.build do
|
|
241
201
|
stage ->(data) {
|
|
242
202
|
sleep(0.01)
|
|
243
203
|
data * 2
|
|
@@ -249,8 +209,8 @@ module Rapidflow
|
|
|
249
209
|
end
|
|
250
210
|
|
|
251
211
|
start_time = Time.now
|
|
252
|
-
item_count.times { |i|
|
|
253
|
-
results =
|
|
212
|
+
item_count.times { |i| batch.push(i) }
|
|
213
|
+
results = batch.results
|
|
254
214
|
elapsed = Time.now - start_time
|
|
255
215
|
|
|
256
216
|
assert_equal item_count, results.length
|
|
@@ -266,7 +226,7 @@ module Rapidflow
|
|
|
266
226
|
end
|
|
267
227
|
|
|
268
228
|
def test_three_stage_pipeline
|
|
269
|
-
|
|
229
|
+
batch = Batch.build do
|
|
270
230
|
stage ->(data) {
|
|
271
231
|
sleep(0.1)
|
|
272
232
|
data.upcase
|
|
@@ -281,10 +241,10 @@ module Rapidflow
|
|
|
281
241
|
}
|
|
282
242
|
end
|
|
283
243
|
|
|
284
|
-
|
|
285
|
-
|
|
244
|
+
batch.push("hello")
|
|
245
|
+
batch.push("world")
|
|
286
246
|
|
|
287
|
-
results =
|
|
247
|
+
results = batch.results
|
|
288
248
|
|
|
289
249
|
assert_equal 2, results.length
|
|
290
250
|
assert_equal ["HELLO!HELLO!", nil], results[0]
|
|
@@ -293,7 +253,7 @@ module Rapidflow
|
|
|
293
253
|
|
|
294
254
|
def test_results_preserve_input_order
|
|
295
255
|
# Even though items complete at different times, results should match push order
|
|
296
|
-
|
|
256
|
+
batch = Batch.build do
|
|
297
257
|
stage ->(data) {
|
|
298
258
|
# Make later items finish faster
|
|
299
259
|
sleep_time = (data[:id] == 0) ? 0.5 : 0.1
|
|
@@ -304,9 +264,9 @@ module Rapidflow
|
|
|
304
264
|
|
|
305
265
|
# Push items in order 0, 1, 2, 3
|
|
306
266
|
# But item 0 will take longer to complete
|
|
307
|
-
4.times { |i|
|
|
267
|
+
4.times { |i| batch.push({ id: i }) }
|
|
308
268
|
|
|
309
|
-
results =
|
|
269
|
+
results = batch.results
|
|
310
270
|
|
|
311
271
|
# Results should still be in order 0, 1, 2, 3
|
|
312
272
|
assert_equal 4, results.length
|
|
@@ -317,14 +277,14 @@ module Rapidflow
|
|
|
317
277
|
end
|
|
318
278
|
|
|
319
279
|
def test_single_stage_pipeline
|
|
320
|
-
|
|
280
|
+
batch = Batch.build do
|
|
321
281
|
stage ->(data) { data * 2 }
|
|
322
282
|
end
|
|
323
283
|
|
|
324
|
-
|
|
325
|
-
|
|
284
|
+
batch.push(5)
|
|
285
|
+
batch.push(10)
|
|
326
286
|
|
|
327
|
-
results =
|
|
287
|
+
results = batch.results
|
|
328
288
|
|
|
329
289
|
assert_equal 2, results.length
|
|
330
290
|
assert_equal [10, nil], results[0]
|
|
@@ -332,78 +292,13 @@ module Rapidflow
|
|
|
332
292
|
end
|
|
333
293
|
|
|
334
294
|
def test_empty_pipeline
|
|
335
|
-
|
|
295
|
+
batch = Batch.build { stage ->(_data) { } }
|
|
336
296
|
|
|
337
|
-
results =
|
|
297
|
+
results = batch.results
|
|
338
298
|
|
|
339
299
|
assert_equal 0, results.length
|
|
340
300
|
end
|
|
341
301
|
|
|
342
|
-
def test_error_in_middle_stage
|
|
343
|
-
belt = Batch.build do
|
|
344
|
-
stage ->(data) { data.upcase }
|
|
345
|
-
stage ->(data) {
|
|
346
|
-
raise "Error in stage 2" if data == "BAD"
|
|
347
|
-
data
|
|
348
|
-
}
|
|
349
|
-
stage ->(data) { data + "!" }
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
belt.push("good")
|
|
353
|
-
belt.push("bad")
|
|
354
|
-
belt.push("also_good")
|
|
355
|
-
|
|
356
|
-
results = belt.results
|
|
357
|
-
|
|
358
|
-
assert_equal 3, results.length
|
|
359
|
-
assert_equal ["GOOD!", nil], results[0]
|
|
360
|
-
assert_equal ["BAD", results[1][1]], [results[1][0], results[1][1]]
|
|
361
|
-
assert_equal "Error in stage 2", results[1][1].message
|
|
362
|
-
assert_equal ["ALSO_GOOD!", nil], results[2]
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
def test_error_in_last_stage
|
|
366
|
-
belt = Batch.build do
|
|
367
|
-
stage ->(data) { data.upcase }
|
|
368
|
-
stage ->(data) {
|
|
369
|
-
raise "Error in final stage" if data == "BAD"
|
|
370
|
-
data
|
|
371
|
-
}
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
belt.push("good")
|
|
375
|
-
belt.push("bad")
|
|
376
|
-
|
|
377
|
-
results = belt.results
|
|
378
|
-
|
|
379
|
-
assert_equal 2, results.length
|
|
380
|
-
assert_equal ["GOOD", nil], results[0]
|
|
381
|
-
assert_equal ["BAD", results[1][1]], [results[1][0], results[1][1]]
|
|
382
|
-
assert_equal "Error in final stage", results[1][1].message
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
def test_multiple_errors_in_sequence
|
|
386
|
-
belt = Batch.build do
|
|
387
|
-
stage ->(data) {
|
|
388
|
-
raise "Error at #{data}" if data.start_with?("bad")
|
|
389
|
-
data
|
|
390
|
-
}
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
belt.push("good1")
|
|
394
|
-
belt.push("bad1")
|
|
395
|
-
belt.push("bad2")
|
|
396
|
-
belt.push("good2")
|
|
397
|
-
|
|
398
|
-
results = belt.results
|
|
399
|
-
|
|
400
|
-
assert_equal 4, results.length
|
|
401
|
-
assert_equal ["good1", nil], results[0]
|
|
402
|
-
assert_instance_of RuntimeError, results[1][1]
|
|
403
|
-
assert_instance_of RuntimeError, results[2][1]
|
|
404
|
-
assert_equal ["good2", nil], results[3]
|
|
405
|
-
end
|
|
406
|
-
|
|
407
302
|
def test_different_worker_counts
|
|
408
303
|
# Test with 1 worker per stage (sequential at each stage)
|
|
409
304
|
j1 = Batch.build do
|
|
@@ -431,15 +326,15 @@ module Rapidflow
|
|
|
431
326
|
end
|
|
432
327
|
|
|
433
328
|
def test_complex_data_types
|
|
434
|
-
|
|
329
|
+
batch = Batch.build do
|
|
435
330
|
stage ->(data) { { original: data, processed: true } }
|
|
436
331
|
stage ->(data) { data.merge(stage2: Time.now.to_i) }
|
|
437
332
|
end
|
|
438
333
|
|
|
439
|
-
|
|
440
|
-
|
|
334
|
+
batch.push({ id: 1, name: "test" })
|
|
335
|
+
batch.push([1, 2, 3])
|
|
441
336
|
|
|
442
|
-
results =
|
|
337
|
+
results = batch.results
|
|
443
338
|
|
|
444
339
|
assert_equal 2, results.length
|
|
445
340
|
assert results[0][0].is_a?(Hash)
|
|
@@ -449,15 +344,15 @@ module Rapidflow
|
|
|
449
344
|
end
|
|
450
345
|
|
|
451
346
|
def test_nil_values
|
|
452
|
-
|
|
347
|
+
batch = Batch.build do
|
|
453
348
|
stage ->(data) { data.nil? ? "was_nil" : data }
|
|
454
349
|
stage ->(data) { data.upcase }
|
|
455
350
|
end
|
|
456
351
|
|
|
457
|
-
|
|
458
|
-
|
|
352
|
+
batch.push(nil)
|
|
353
|
+
batch.push("hello")
|
|
459
354
|
|
|
460
|
-
results =
|
|
355
|
+
results = batch.results
|
|
461
356
|
|
|
462
357
|
assert_equal 2, results.length
|
|
463
358
|
assert_equal ["WAS_NIL", nil], results[0]
|
|
@@ -467,15 +362,15 @@ module Rapidflow
|
|
|
467
362
|
def test_large_dataset_stress_test
|
|
468
363
|
item_count = 500
|
|
469
364
|
|
|
470
|
-
|
|
365
|
+
batch = Batch.build do
|
|
471
366
|
stage ->(data) { data * 2 }, workers: 8
|
|
472
367
|
stage ->(data) { data + 1 }, workers: 8
|
|
473
368
|
stage ->(data) { data.to_s }, workers: 8
|
|
474
369
|
end
|
|
475
370
|
|
|
476
|
-
item_count.times { |i|
|
|
371
|
+
item_count.times { |i| batch.push(i) }
|
|
477
372
|
|
|
478
|
-
results =
|
|
373
|
+
results = batch.results
|
|
479
374
|
|
|
480
375
|
assert_equal item_count, results.length
|
|
481
376
|
|
|
@@ -488,7 +383,7 @@ module Rapidflow
|
|
|
488
383
|
|
|
489
384
|
def test_varying_processing_times
|
|
490
385
|
# Simulate real-world scenario with varying processing times
|
|
491
|
-
|
|
386
|
+
batch = Batch.build do
|
|
492
387
|
stage ->(data) {
|
|
493
388
|
sleep(rand * 0.1) # Random 0-100ms
|
|
494
389
|
data.upcase
|
|
@@ -500,9 +395,9 @@ module Rapidflow
|
|
|
500
395
|
end
|
|
501
396
|
|
|
502
397
|
words = %w[apple banana cherry date elderberry fig grape]
|
|
503
|
-
words.each { |word|
|
|
398
|
+
words.each { |word| batch.push(word) }
|
|
504
399
|
|
|
505
|
-
results =
|
|
400
|
+
results = batch.results
|
|
506
401
|
|
|
507
402
|
assert_equal words.length, results.length
|
|
508
403
|
words.each_with_index do |word, i|
|
|
@@ -511,61 +406,15 @@ module Rapidflow
|
|
|
511
406
|
end
|
|
512
407
|
end
|
|
513
408
|
|
|
514
|
-
def test_exception_types_preserved
|
|
515
|
-
belt = Batch.build do
|
|
516
|
-
stage ->(data) {
|
|
517
|
-
case data
|
|
518
|
-
when "argument_error"
|
|
519
|
-
raise ArgumentError, "Bad argument"
|
|
520
|
-
when "runtime_error"
|
|
521
|
-
raise "Runtime problem"
|
|
522
|
-
when "custom_error"
|
|
523
|
-
raise StandardError, "Custom error"
|
|
524
|
-
else
|
|
525
|
-
data
|
|
526
|
-
end
|
|
527
|
-
}
|
|
528
|
-
end
|
|
529
|
-
|
|
530
|
-
belt.push("good")
|
|
531
|
-
belt.push("argument_error")
|
|
532
|
-
belt.push("runtime_error")
|
|
533
|
-
belt.push("custom_error")
|
|
534
|
-
|
|
535
|
-
results = belt.results
|
|
536
|
-
|
|
537
|
-
assert_equal 4, results.length
|
|
538
|
-
assert_equal ["good", nil], results[0]
|
|
539
|
-
assert_instance_of ArgumentError, results[1][1]
|
|
540
|
-
assert_instance_of RuntimeError, results[2][1]
|
|
541
|
-
assert_instance_of StandardError, results[3][1]
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
def test_all_items_fail
|
|
545
|
-
belt = Batch.build do
|
|
546
|
-
stage ->(data) { raise "Always fails" }
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
5.times { |i| belt.push(i) }
|
|
550
|
-
|
|
551
|
-
results = belt.results
|
|
552
|
-
|
|
553
|
-
assert_equal 5, results.length
|
|
554
|
-
results.each do |result, error|
|
|
555
|
-
assert_instance_of RuntimeError, error
|
|
556
|
-
assert_equal "Always fails", error.message
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
|
|
560
409
|
def test_push_many_items_quickly
|
|
561
|
-
|
|
410
|
+
batch = Batch.build do
|
|
562
411
|
stage ->(data) { data }
|
|
563
412
|
end
|
|
564
413
|
|
|
565
414
|
# Push 1000 items as fast as possible
|
|
566
|
-
1000.times { |i|
|
|
415
|
+
1000.times { |i| batch.push(i) }
|
|
567
416
|
|
|
568
|
-
results =
|
|
417
|
+
results = batch.results
|
|
569
418
|
|
|
570
419
|
assert_equal 1000, results.length
|
|
571
420
|
# Verify order is maintained
|
|
@@ -575,22 +424,22 @@ module Rapidflow
|
|
|
575
424
|
end
|
|
576
425
|
|
|
577
426
|
def test_idempotent_results_calls_not_allowed
|
|
578
|
-
|
|
427
|
+
batch = Batch.build do
|
|
579
428
|
stage ->(data) { data }
|
|
580
429
|
end
|
|
581
430
|
|
|
582
|
-
|
|
583
|
-
|
|
431
|
+
batch.push(1)
|
|
432
|
+
batch.results
|
|
584
433
|
|
|
585
434
|
# Can't call results again or push again
|
|
586
|
-
assert_raises(RuntimeError) {
|
|
435
|
+
assert_raises(RuntimeError) { batch.push(2) }
|
|
587
436
|
end
|
|
588
437
|
|
|
589
438
|
def test_thread_safety_of_shared_state
|
|
590
439
|
shared_counter = { count: 0 }
|
|
591
440
|
mutex = Mutex.new
|
|
592
441
|
|
|
593
|
-
|
|
442
|
+
batch = Batch.build do
|
|
594
443
|
stage ->(data) {
|
|
595
444
|
# Safely increment shared counter
|
|
596
445
|
mutex.synchronize { shared_counter[:count] += 1 }
|
|
@@ -598,8 +447,8 @@ module Rapidflow
|
|
|
598
447
|
}, workers: 10
|
|
599
448
|
end
|
|
600
449
|
|
|
601
|
-
100.times { |i|
|
|
602
|
-
results =
|
|
450
|
+
100.times { |i| batch.push(i) }
|
|
451
|
+
results = batch.results
|
|
603
452
|
|
|
604
453
|
assert_equal 100, results.length
|
|
605
454
|
assert_equal 100, shared_counter[:count]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module RapidFlow
|
|
6
|
+
class PipelineTest < Minitest::Test
|
|
7
|
+
def test_empty_pipeline
|
|
8
|
+
pipeline = Pipeline.new(0, 1)
|
|
9
|
+
|
|
10
|
+
pipeline.wait_for_completion
|
|
11
|
+
pipeline.shutdown
|
|
12
|
+
|
|
13
|
+
assert pipeline.results_empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_pipeline_with_single_stage
|
|
17
|
+
pipeline = Pipeline.new(1, 1)
|
|
18
|
+
|
|
19
|
+
pipeline.enqueue(0, "test_item")
|
|
20
|
+
|
|
21
|
+
# Simulate stage processing
|
|
22
|
+
item = pipeline.dequeue(0)
|
|
23
|
+
pipeline.enqueue(1, item.upcase)
|
|
24
|
+
pipeline.decrement_active_workers
|
|
25
|
+
|
|
26
|
+
result = pipeline.dequeue_result
|
|
27
|
+
|
|
28
|
+
assert_equal "TEST_ITEM", result
|
|
29
|
+
|
|
30
|
+
pipeline.shutdown
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_pipeline_queues_created_correctly
|
|
34
|
+
pipeline = Pipeline.new(3, 2)
|
|
35
|
+
|
|
36
|
+
# Pipeline with 3 stages should have 4 queues (one per stage + results queue)
|
|
37
|
+
(0..3).each do |i|
|
|
38
|
+
pipeline.enqueue(i, "item_#{i}")
|
|
39
|
+
|
|
40
|
+
result = pipeline.dequeue(i)
|
|
41
|
+
assert_equal "item_#{i}", result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
pipeline.shutdown
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_active_workers_tracking
|
|
48
|
+
pipeline = Pipeline.new(1, 1)
|
|
49
|
+
|
|
50
|
+
pipeline.enqueue(0, "item1")
|
|
51
|
+
pipeline.enqueue(0, "item2")
|
|
52
|
+
|
|
53
|
+
# Simulate processing
|
|
54
|
+
pipeline.dequeue(0)
|
|
55
|
+
pipeline.decrement_active_workers
|
|
56
|
+
|
|
57
|
+
pipeline.dequeue(0)
|
|
58
|
+
pipeline.decrement_active_workers
|
|
59
|
+
|
|
60
|
+
pipeline.wait_for_completion
|
|
61
|
+
|
|
62
|
+
pipeline.shutdown
|
|
63
|
+
|
|
64
|
+
assert pipeline.results_empty?
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|