simple_flow 0.1.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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +4 -0
- data/COMMITS.md +196 -0
- data/LICENSE +21 -0
- data/README.md +481 -0
- data/Rakefile +15 -0
- data/benchmarks/parallel_vs_sequential.rb +98 -0
- data/benchmarks/pipeline_overhead.rb +130 -0
- data/docs/api/middleware.md +468 -0
- data/docs/api/parallel-step.md +363 -0
- data/docs/api/pipeline.md +382 -0
- data/docs/api/result.md +375 -0
- data/docs/concurrent/best-practices.md +687 -0
- data/docs/concurrent/introduction.md +246 -0
- data/docs/concurrent/parallel-steps.md +418 -0
- data/docs/concurrent/performance.md +481 -0
- data/docs/core-concepts/flow-control.md +452 -0
- data/docs/core-concepts/middleware.md +389 -0
- data/docs/core-concepts/overview.md +219 -0
- data/docs/core-concepts/pipeline.md +315 -0
- data/docs/core-concepts/result.md +168 -0
- data/docs/core-concepts/steps.md +391 -0
- data/docs/development/benchmarking.md +443 -0
- data/docs/development/contributing.md +380 -0
- data/docs/development/dagwood-concepts.md +435 -0
- data/docs/development/testing.md +514 -0
- data/docs/getting-started/examples.md +197 -0
- data/docs/getting-started/installation.md +62 -0
- data/docs/getting-started/quick-start.md +218 -0
- data/docs/guides/choosing-concurrency-model.md +441 -0
- data/docs/guides/complex-workflows.md +440 -0
- data/docs/guides/data-fetching.md +478 -0
- data/docs/guides/error-handling.md +635 -0
- data/docs/guides/file-processing.md +505 -0
- data/docs/guides/validation-patterns.md +496 -0
- data/docs/index.md +169 -0
- data/examples/.gitignore +3 -0
- data/examples/01_basic_pipeline.rb +112 -0
- data/examples/02_error_handling.rb +178 -0
- data/examples/03_middleware.rb +186 -0
- data/examples/04_parallel_automatic.rb +221 -0
- data/examples/05_parallel_explicit.rb +279 -0
- data/examples/06_real_world_ecommerce.rb +288 -0
- data/examples/07_real_world_etl.rb +277 -0
- data/examples/08_graph_visualization.rb +246 -0
- data/examples/09_pipeline_visualization.rb +266 -0
- data/examples/10_concurrency_control.rb +235 -0
- data/examples/11_sequential_dependencies.rb +243 -0
- data/examples/12_none_constant.rb +161 -0
- data/examples/README.md +374 -0
- data/examples/regression_test/01_basic_pipeline.txt +38 -0
- data/examples/regression_test/02_error_handling.txt +92 -0
- data/examples/regression_test/03_middleware.txt +61 -0
- data/examples/regression_test/04_parallel_automatic.txt +86 -0
- data/examples/regression_test/05_parallel_explicit.txt +80 -0
- data/examples/regression_test/06_real_world_ecommerce.txt +53 -0
- data/examples/regression_test/07_real_world_etl.txt +58 -0
- data/examples/regression_test/08_graph_visualization.txt +429 -0
- data/examples/regression_test/09_pipeline_visualization.txt +305 -0
- data/examples/regression_test/10_concurrency_control.txt +96 -0
- data/examples/regression_test/11_sequential_dependencies.txt +86 -0
- data/examples/regression_test/12_none_constant.txt +64 -0
- data/examples/regression_test.rb +105 -0
- data/lib/simple_flow/dependency_graph.rb +120 -0
- data/lib/simple_flow/dependency_graph_visualizer.rb +326 -0
- data/lib/simple_flow/middleware.rb +36 -0
- data/lib/simple_flow/parallel_executor.rb +80 -0
- data/lib/simple_flow/pipeline.rb +405 -0
- data/lib/simple_flow/result.rb +88 -0
- data/lib/simple_flow/step_tracker.rb +58 -0
- data/lib/simple_flow/version.rb +5 -0
- data/lib/simple_flow.rb +41 -0
- data/mkdocs.yml +146 -0
- data/pipeline_graph.dot +51 -0
- data/pipeline_graph.html +60 -0
- data/pipeline_graph.mmd +19 -0
- metadata +127 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
============================================================
|
|
2
|
+
Direct Pipeline Visualization
|
|
3
|
+
============================================================
|
|
4
|
+
|
|
5
|
+
Example 1: Simple Pipeline Visualization
|
|
6
|
+
------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
Dependency Graph
|
|
9
|
+
============================================================
|
|
10
|
+
|
|
11
|
+
Dependencies:
|
|
12
|
+
:validate
|
|
13
|
+
└─ depends on: (none)
|
|
14
|
+
:fetch_data
|
|
15
|
+
└─ depends on: :validate
|
|
16
|
+
:process_data
|
|
17
|
+
└─ depends on: :fetch_data
|
|
18
|
+
|
|
19
|
+
Execution Order (sequential):
|
|
20
|
+
:validate
|
|
21
|
+
↓ :fetch_data
|
|
22
|
+
↓ :process_data
|
|
23
|
+
|
|
24
|
+
Parallel Execution Groups:
|
|
25
|
+
Group 1:
|
|
26
|
+
└─ :validate (sequential)
|
|
27
|
+
Group 2:
|
|
28
|
+
└─ :fetch_data (sequential)
|
|
29
|
+
Group 3:
|
|
30
|
+
└─ :process_data (sequential)
|
|
31
|
+
|
|
32
|
+
Execution Tree:
|
|
33
|
+
├─ :validate
|
|
34
|
+
├─ :fetch_data
|
|
35
|
+
└─ :process_data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
============================================================
|
|
39
|
+
Example 2: E-commerce Pipeline (Automatic Visualization)
|
|
40
|
+
============================================================
|
|
41
|
+
|
|
42
|
+
Dependency Graph
|
|
43
|
+
============================================================
|
|
44
|
+
|
|
45
|
+
Dependencies:
|
|
46
|
+
:validate_order
|
|
47
|
+
└─ depends on: (none)
|
|
48
|
+
:check_inventory
|
|
49
|
+
└─ depends on: :validate_order
|
|
50
|
+
:calculate_shipping
|
|
51
|
+
└─ depends on: :validate_order
|
|
52
|
+
:calculate_totals
|
|
53
|
+
└─ depends on: :calculate_shipping, :check_inventory
|
|
54
|
+
:process_payment
|
|
55
|
+
└─ depends on: :calculate_totals
|
|
56
|
+
:reserve_inventory
|
|
57
|
+
└─ depends on: :process_payment
|
|
58
|
+
:create_shipment
|
|
59
|
+
└─ depends on: :reserve_inventory
|
|
60
|
+
:send_email
|
|
61
|
+
└─ depends on: :create_shipment
|
|
62
|
+
:send_sms
|
|
63
|
+
└─ depends on: :create_shipment
|
|
64
|
+
:finalize_order
|
|
65
|
+
└─ depends on: :send_email, :send_sms
|
|
66
|
+
|
|
67
|
+
Execution Order (sequential):
|
|
68
|
+
:validate_order
|
|
69
|
+
↓ :check_inventory
|
|
70
|
+
↓ :calculate_shipping
|
|
71
|
+
↓ :calculate_totals
|
|
72
|
+
↓ :process_payment
|
|
73
|
+
↓ :reserve_inventory
|
|
74
|
+
↓ :create_shipment
|
|
75
|
+
↓ :send_email
|
|
76
|
+
↓ :send_sms
|
|
77
|
+
↓ :finalize_order
|
|
78
|
+
|
|
79
|
+
Parallel Execution Groups:
|
|
80
|
+
Group 1:
|
|
81
|
+
└─ :validate_order (sequential)
|
|
82
|
+
Group 2:
|
|
83
|
+
├─ Parallel execution of 2 steps:
|
|
84
|
+
├─ :calculate_shipping
|
|
85
|
+
└─ :check_inventory
|
|
86
|
+
Group 3:
|
|
87
|
+
└─ :calculate_totals (sequential)
|
|
88
|
+
Group 4:
|
|
89
|
+
└─ :process_payment (sequential)
|
|
90
|
+
Group 5:
|
|
91
|
+
└─ :reserve_inventory (sequential)
|
|
92
|
+
Group 6:
|
|
93
|
+
└─ :create_shipment (sequential)
|
|
94
|
+
Group 7:
|
|
95
|
+
├─ Parallel execution of 2 steps:
|
|
96
|
+
├─ :send_email
|
|
97
|
+
└─ :send_sms
|
|
98
|
+
Group 8:
|
|
99
|
+
└─ :finalize_order (sequential)
|
|
100
|
+
|
|
101
|
+
Execution Tree:
|
|
102
|
+
├─ :validate_order
|
|
103
|
+
├─ [Parallel]
|
|
104
|
+
│ ├─ :calculate_shipping
|
|
105
|
+
│ └─ :check_inventory
|
|
106
|
+
├─ :calculate_totals
|
|
107
|
+
├─ :process_payment
|
|
108
|
+
├─ :reserve_inventory
|
|
109
|
+
├─ :create_shipment
|
|
110
|
+
├─ [Parallel]
|
|
111
|
+
│ ├─ :send_email
|
|
112
|
+
│ └─ :send_sms
|
|
113
|
+
└─ :finalize_order
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
============================================================
|
|
117
|
+
Example 3: Execution Plan (Direct from Pipeline)
|
|
118
|
+
============================================================
|
|
119
|
+
|
|
120
|
+
Execution Plan
|
|
121
|
+
============================================================
|
|
122
|
+
|
|
123
|
+
Total Steps: 10
|
|
124
|
+
Execution Phases: 8
|
|
125
|
+
|
|
126
|
+
Phase 1:
|
|
127
|
+
→ Execute :validate_order
|
|
128
|
+
|
|
129
|
+
Phase 2:
|
|
130
|
+
⚡ Execute in parallel:
|
|
131
|
+
• :calculate_shipping (after :validate_order)
|
|
132
|
+
• :check_inventory (after :validate_order)
|
|
133
|
+
|
|
134
|
+
Phase 3:
|
|
135
|
+
→ Execute :calculate_totals
|
|
136
|
+
|
|
137
|
+
Phase 4:
|
|
138
|
+
→ Execute :process_payment
|
|
139
|
+
|
|
140
|
+
Phase 5:
|
|
141
|
+
→ Execute :reserve_inventory
|
|
142
|
+
|
|
143
|
+
Phase 6:
|
|
144
|
+
→ Execute :create_shipment
|
|
145
|
+
|
|
146
|
+
Phase 7:
|
|
147
|
+
⚡ Execute in parallel:
|
|
148
|
+
• :send_email (after :create_shipment)
|
|
149
|
+
• :send_sms (after :create_shipment)
|
|
150
|
+
|
|
151
|
+
Phase 8:
|
|
152
|
+
→ Execute :finalize_order
|
|
153
|
+
|
|
154
|
+
Performance Estimate:
|
|
155
|
+
Sequential execution: 10 time units
|
|
156
|
+
Parallel execution: 8 time units
|
|
157
|
+
Potential speedup: 1.25x
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
============================================================
|
|
161
|
+
Example 4: Export Formats (Direct from Pipeline)
|
|
162
|
+
============================================================
|
|
163
|
+
|
|
164
|
+
✓ Exported to Graphviz DOT: pipeline_graph.dot
|
|
165
|
+
Generate image: dot -Tpng pipeline_graph.dot -o pipeline.png
|
|
166
|
+
|
|
167
|
+
✓ Exported to Mermaid: pipeline_graph.mmd
|
|
168
|
+
View at: https://mermaid.live/
|
|
169
|
+
|
|
170
|
+
✓ Exported to HTML: pipeline_graph.html
|
|
171
|
+
Open in browser for interactive visualization
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
============================================================
|
|
175
|
+
Example 5: ETL Pipeline Visualization
|
|
176
|
+
============================================================
|
|
177
|
+
|
|
178
|
+
Execution Plan
|
|
179
|
+
============================================================
|
|
180
|
+
|
|
181
|
+
Total Steps: 10
|
|
182
|
+
Execution Phases: 5
|
|
183
|
+
|
|
184
|
+
Phase 1:
|
|
185
|
+
⚡ Execute in parallel:
|
|
186
|
+
• :extract_orders (no dependencies)
|
|
187
|
+
• :extract_products (no dependencies)
|
|
188
|
+
• :extract_users (no dependencies)
|
|
189
|
+
|
|
190
|
+
Phase 2:
|
|
191
|
+
⚡ Execute in parallel:
|
|
192
|
+
• :transform_orders (after :extract_orders)
|
|
193
|
+
• :transform_products (after :extract_products)
|
|
194
|
+
• :transform_users (after :extract_users)
|
|
195
|
+
|
|
196
|
+
Phase 3:
|
|
197
|
+
⚡ Execute in parallel:
|
|
198
|
+
• :aggregate_category_stats (after :transform_products)
|
|
199
|
+
• :aggregate_user_stats (after :transform_orders, :transform_users)
|
|
200
|
+
|
|
201
|
+
Phase 4:
|
|
202
|
+
→ Execute :validate_data
|
|
203
|
+
|
|
204
|
+
Phase 5:
|
|
205
|
+
→ Execute :prepare_output
|
|
206
|
+
|
|
207
|
+
Performance Estimate:
|
|
208
|
+
Sequential execution: 10 time units
|
|
209
|
+
Parallel execution: 5 time units
|
|
210
|
+
Potential speedup: 2.0x
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
============================================================
|
|
214
|
+
Example 6: Checking Visualization Availability
|
|
215
|
+
============================================================
|
|
216
|
+
|
|
217
|
+
Named pipeline has dependency graph? true
|
|
218
|
+
Unnamed pipeline has dependency graph? false
|
|
219
|
+
|
|
220
|
+
Note: Only pipelines with named steps and dependencies can be auto-visualized
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
============================================================
|
|
224
|
+
Example 7: Advanced - Access Dependency Graph
|
|
225
|
+
============================================================
|
|
226
|
+
|
|
227
|
+
Pipeline dependency information:
|
|
228
|
+
Total steps: 10
|
|
229
|
+
Execution phases: 8
|
|
230
|
+
Parallel groups:
|
|
231
|
+
Phase 1: validate_order
|
|
232
|
+
Phase 2: calculate_shipping, check_inventory
|
|
233
|
+
Phase 3: calculate_totals
|
|
234
|
+
Phase 4: process_payment
|
|
235
|
+
Phase 5: reserve_inventory
|
|
236
|
+
Phase 6: create_shipment
|
|
237
|
+
Phase 7: send_email, send_sms
|
|
238
|
+
Phase 8: finalize_order
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
============================================================
|
|
242
|
+
Example 8: Pipeline Structure Comparison
|
|
243
|
+
============================================================
|
|
244
|
+
|
|
245
|
+
Linear Pipeline:
|
|
246
|
+
Execution Plan
|
|
247
|
+
============================================================
|
|
248
|
+
|
|
249
|
+
Total Steps: 4
|
|
250
|
+
Execution Phases: 4
|
|
251
|
+
|
|
252
|
+
Phase 1:
|
|
253
|
+
→ Execute :step1
|
|
254
|
+
|
|
255
|
+
Phase 2:
|
|
256
|
+
→ Execute :step2
|
|
257
|
+
|
|
258
|
+
Phase 3:
|
|
259
|
+
→ Execute :step3
|
|
260
|
+
|
|
261
|
+
Phase 4:
|
|
262
|
+
→ Execute :step4
|
|
263
|
+
|
|
264
|
+
Performance Estimate:
|
|
265
|
+
Sequential execution: 4 time units
|
|
266
|
+
Parallel execution: 4 time units
|
|
267
|
+
Potential speedup: 1.0x
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
Parallel Pipeline:
|
|
271
|
+
Execution Plan
|
|
272
|
+
============================================================
|
|
273
|
+
|
|
274
|
+
Total Steps: 5
|
|
275
|
+
Execution Phases: 3
|
|
276
|
+
|
|
277
|
+
Phase 1:
|
|
278
|
+
→ Execute :start
|
|
279
|
+
|
|
280
|
+
Phase 2:
|
|
281
|
+
⚡ Execute in parallel:
|
|
282
|
+
• :task1 (after :start)
|
|
283
|
+
• :task2 (after :start)
|
|
284
|
+
• :task3 (after :start)
|
|
285
|
+
|
|
286
|
+
Phase 3:
|
|
287
|
+
→ Execute :end
|
|
288
|
+
|
|
289
|
+
Performance Estimate:
|
|
290
|
+
Sequential execution: 5 time units
|
|
291
|
+
Parallel execution: 3 time units
|
|
292
|
+
Potential speedup: 1.67x
|
|
293
|
+
|
|
294
|
+
============================================================
|
|
295
|
+
Direct pipeline visualization completed!
|
|
296
|
+
|
|
297
|
+
Key Takeaway:
|
|
298
|
+
No need to manually recreate dependency structures!
|
|
299
|
+
Just call pipeline.visualize_ascii, pipeline.visualize_dot, etc.
|
|
300
|
+
|
|
301
|
+
Generated files:
|
|
302
|
+
- pipeline_graph.dot
|
|
303
|
+
- pipeline_graph.mmd
|
|
304
|
+
- pipeline_graph.html
|
|
305
|
+
============================================================
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
============================================================
|
|
2
|
+
Per-Pipeline Concurrency Control
|
|
3
|
+
============================================================
|
|
4
|
+
|
|
5
|
+
Async gem available: ✓ Yes
|
|
6
|
+
|
|
7
|
+
Example 1: Auto-Detect Concurrency (Default)
|
|
8
|
+
------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
Pipeline concurrency setting: auto
|
|
11
|
+
Will use: Async (fiber-based)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Example 2: Force Threads
|
|
15
|
+
------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
Pipeline concurrency setting: threads
|
|
18
|
+
Will use: Ruby Threads (even if async is available)
|
|
19
|
+
[Thread-based] Task A running...
|
|
20
|
+
[Thread-based] Task B running...
|
|
21
|
+
[Thread-based] Task C running...
|
|
22
|
+
Result context: {task_c: :done}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Example 3: Force Async
|
|
26
|
+
------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
Pipeline concurrency setting: async
|
|
29
|
+
Will use: Async (fiber-based concurrency)
|
|
30
|
+
[Async] Validating...
|
|
31
|
+
[Async] Fetching A...
|
|
32
|
+
[Async] Fetching B...
|
|
33
|
+
[Async] Merging results...
|
|
34
|
+
Result: Complete
|
|
35
|
+
Context: {a: :done, b: :done}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Example 4: Mixed Concurrency in Same Application
|
|
39
|
+
------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
User pipeline uses: threads
|
|
42
|
+
Batch pipeline uses: async
|
|
43
|
+
|
|
44
|
+
Running user pipeline...
|
|
45
|
+
[User/Threads] Validating user...
|
|
46
|
+
[User/Threads] Fetching profile...
|
|
47
|
+
[User/Threads] Fetching settings...
|
|
48
|
+
User result: {profile: {name: "John"}, settings: {theme: "dark"}}
|
|
49
|
+
|
|
50
|
+
Running batch pipeline...
|
|
51
|
+
[Batch/Async] Loading batch...
|
|
52
|
+
[Batch/Async] Processing batch 1...
|
|
53
|
+
[Batch/Async] Processing batch 2...
|
|
54
|
+
Batch result: {batch_1: :done, batch_2: :done}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
Example 5: Error Handling
|
|
58
|
+
------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
Valid options: :auto, :threads, :async
|
|
61
|
+
|
|
62
|
+
✓ Caught expected error for invalid concurrency:
|
|
63
|
+
Invalid concurrency option: :invalid. Valid options: [:auto, :threads, :async]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Example 6: Inspecting Concurrency Settings
|
|
68
|
+
------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
Pipeline 1:
|
|
71
|
+
Concurrency: auto
|
|
72
|
+
Async available: true
|
|
73
|
+
|
|
74
|
+
Pipeline 2:
|
|
75
|
+
Concurrency: auto
|
|
76
|
+
Async available: true
|
|
77
|
+
|
|
78
|
+
Pipeline 3:
|
|
79
|
+
Concurrency: threads
|
|
80
|
+
Async available: true
|
|
81
|
+
|
|
82
|
+
Pipeline 4:
|
|
83
|
+
Concurrency: async
|
|
84
|
+
Async available: true
|
|
85
|
+
|
|
86
|
+
============================================================
|
|
87
|
+
Concurrency control examples completed!
|
|
88
|
+
============================================================
|
|
89
|
+
|
|
90
|
+
Key Takeaways:
|
|
91
|
+
• concurrency: :auto (default) - auto-detects best option
|
|
92
|
+
• concurrency: :threads - always uses Ruby threads
|
|
93
|
+
• concurrency: :async - requires async gem, uses fibers
|
|
94
|
+
• Different pipelines can use different concurrency models
|
|
95
|
+
• Choose based on your specific workload requirements
|
|
96
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
============================================================
|
|
2
|
+
Sequential Step Dependencies
|
|
3
|
+
============================================================
|
|
4
|
+
|
|
5
|
+
Example 1: All Steps Succeed
|
|
6
|
+
------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
[Step 1] Starting workflow...
|
|
9
|
+
[Step 2] Processing data...
|
|
10
|
+
[Step 3] Finalizing...
|
|
11
|
+
|
|
12
|
+
Final result: 22
|
|
13
|
+
Continue? true
|
|
14
|
+
Expected: ((5 + 1) * 2) + 10 = 22
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Example 2: Pipeline Halts on Error
|
|
18
|
+
------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
[Step 1] Validating input...
|
|
21
|
+
[Step 2] Checking business rules...
|
|
22
|
+
|
|
23
|
+
Final result: -5
|
|
24
|
+
Continue? false
|
|
25
|
+
Errors: {business_rule: ["Value must be positive"]}
|
|
26
|
+
Note: Step 3 never executed because Step 2 halted
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Example 3: Early Validation Pattern
|
|
30
|
+
------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
Scenario A: System in maintenance mode
|
|
33
|
+
[Pre-flight] Checking system health...
|
|
34
|
+
Continue? false
|
|
35
|
+
Errors: {system: ["Maintenance mode active"]}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Scenario B: System healthy
|
|
39
|
+
[Pre-flight] Checking system health...
|
|
40
|
+
[Pre-flight] System healthy, proceeding...
|
|
41
|
+
[Step 1] Processing order...
|
|
42
|
+
[Step 2] Charging payment...
|
|
43
|
+
[Step 3] Sending confirmation...
|
|
44
|
+
Continue? true
|
|
45
|
+
All steps executed successfully!
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Example 4: Error Accumulation Before Halting
|
|
49
|
+
------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
[Step 1] Collecting validation errors...
|
|
52
|
+
[Step 1] Found 2 validation error(s)
|
|
53
|
+
|
|
54
|
+
Continue? false
|
|
55
|
+
Errors found: 2
|
|
56
|
+
All errors: ["Email required", "Must be 18+"]
|
|
57
|
+
Note: Step 2 didn't execute because validation failed
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
Example 5: Sequential vs Parallel Behavior
|
|
61
|
+
------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
Sequential execution order:
|
|
64
|
+
[Sequential 1] Step A
|
|
65
|
+
[Sequential 2] Step B (depends on A)
|
|
66
|
+
[Sequential 3] Step C (depends on B)
|
|
67
|
+
|
|
68
|
+
Parallel execution (for comparison):
|
|
69
|
+
[Parallel] Step A (no dependencies)
|
|
70
|
+
[Parallel] Step B (depends on A)
|
|
71
|
+
[Parallel] Step C (depends on A, runs parallel with B)
|
|
72
|
+
|
|
73
|
+
Note: Sequential steps have implicit dependencies
|
|
74
|
+
Parallel steps require explicit depends_on declarations
|
|
75
|
+
|
|
76
|
+
============================================================
|
|
77
|
+
Sequential dependencies examples completed!
|
|
78
|
+
============================================================
|
|
79
|
+
|
|
80
|
+
Key Takeaways:
|
|
81
|
+
• Unnamed steps execute sequentially in definition order
|
|
82
|
+
• Each step implicitly depends on the previous step's success
|
|
83
|
+
• Pipeline halts immediately when any step returns result.halt
|
|
84
|
+
• Subsequent steps after a halt are never executed
|
|
85
|
+
• Use named steps with depends_on for parallel execution
|
|
86
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
============================================================
|
|
2
|
+
Reserved Dependency Symbols Usage
|
|
3
|
+
============================================================
|
|
4
|
+
|
|
5
|
+
Example 1: Using :none Symbol for Better Readability
|
|
6
|
+
------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
[Step 1] Validating input...
|
|
9
|
+
[Step 2a] Fetching orders in parallel...
|
|
10
|
+
[Step 2b] Fetching products in parallel...
|
|
11
|
+
[Step 3] Merging results...
|
|
12
|
+
|
|
13
|
+
Result: {orders: [1, 2, 3], products: [:a, :b, :c], total: 6}
|
|
14
|
+
Context: validated=true
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Example 2: :none Symbol vs Empty Array
|
|
18
|
+
------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
[:none syntax] Root step with no dependencies
|
|
21
|
+
[Array syntax] Root step with no dependencies
|
|
22
|
+
|
|
23
|
+
Both produce the same result: 6 == 6
|
|
24
|
+
The :none symbol is simply more readable!
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Example 3: Multiple Independent Root Steps
|
|
28
|
+
------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
[Root C] Authenticating API...
|
|
31
|
+
[Root B] Connecting to database...
|
|
32
|
+
[Root A] Loading configuration...
|
|
33
|
+
[Final] Initializing application with all resources...
|
|
34
|
+
|
|
35
|
+
Result: Application ready
|
|
36
|
+
All independent steps ran in parallel!
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Example 4: Reserved Dependency Symbols
|
|
40
|
+
------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
You can use :none or :nothing to indicate no dependencies:
|
|
43
|
+
|
|
44
|
+
• :step_a dependencies: []
|
|
45
|
+
• :step_b dependencies: []
|
|
46
|
+
• :step_c dependencies: [:step_a, :step_b] (filtered!)
|
|
47
|
+
|
|
48
|
+
Reserved symbols :none and :nothing:
|
|
49
|
+
• Automatically filtered from dependency arrays
|
|
50
|
+
• Functionally equivalent to []
|
|
51
|
+
• More semantically clear than empty array
|
|
52
|
+
• Cannot be used as step names (reserved)
|
|
53
|
+
• A signal to readers: 'this step has no dependencies'
|
|
54
|
+
|
|
55
|
+
============================================================
|
|
56
|
+
Reserved dependency symbols examples completed!
|
|
57
|
+
============================================================
|
|
58
|
+
|
|
59
|
+
Key Takeaways:
|
|
60
|
+
• Use depends_on: :none for better readability
|
|
61
|
+
• Equivalent to [] but more semantic
|
|
62
|
+
• Can mix in arrays: [:step_a, :none] becomes [:step_a]
|
|
63
|
+
• Makes dependency graphs easier to understand
|
|
64
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Regression Test Runner
|
|
5
|
+
# Runs all example scripts and saves their output to regression_test/ directory
|
|
6
|
+
# for comparison purposes.
|
|
7
|
+
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
|
|
10
|
+
class RegressionTestRunner
|
|
11
|
+
EXAMPLES_DIR = File.dirname(__FILE__)
|
|
12
|
+
OUTPUT_DIR = File.join(EXAMPLES_DIR, 'regression_test')
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@examples = Dir.glob(File.join(EXAMPLES_DIR, '*.rb'))
|
|
16
|
+
.reject { |f| File.basename(f) == 'regression_test.rb' }
|
|
17
|
+
.sort
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
puts "Regression Test Runner"
|
|
22
|
+
puts "=" * 60
|
|
23
|
+
puts "Output directory: #{OUTPUT_DIR}"
|
|
24
|
+
puts "Found #{@examples.size} example files"
|
|
25
|
+
puts
|
|
26
|
+
|
|
27
|
+
ensure_output_directory
|
|
28
|
+
|
|
29
|
+
results = @examples.map { |example| run_example(example) }
|
|
30
|
+
|
|
31
|
+
print_summary(results)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def ensure_output_directory
|
|
37
|
+
FileUtils.mkdir_p(OUTPUT_DIR) unless Dir.exist?(OUTPUT_DIR)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def run_example(example_path)
|
|
41
|
+
basename = File.basename(example_path, '.rb')
|
|
42
|
+
output_path = File.join(OUTPUT_DIR, "#{basename}.txt")
|
|
43
|
+
|
|
44
|
+
print "Running #{basename}... "
|
|
45
|
+
|
|
46
|
+
start_time = Time.now
|
|
47
|
+
output, status = capture_output(example_path)
|
|
48
|
+
elapsed = Time.now - start_time
|
|
49
|
+
|
|
50
|
+
File.write(output_path, output)
|
|
51
|
+
|
|
52
|
+
if status.success?
|
|
53
|
+
puts "OK (#{format('%.2f', elapsed)}s) -> #{File.basename(output_path)}"
|
|
54
|
+
{ name: basename, success: true, elapsed: elapsed, output_path: output_path }
|
|
55
|
+
else
|
|
56
|
+
puts "FAILED (exit code: #{status.exitstatus})"
|
|
57
|
+
{ name: basename, success: false, elapsed: elapsed, output_path: output_path, exit_code: status.exitstatus }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def capture_output(example_path)
|
|
62
|
+
output = `cd #{EXAMPLES_DIR} && ruby #{File.basename(example_path)} 2>&1`
|
|
63
|
+
[output, $?]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def print_summary(results)
|
|
67
|
+
puts
|
|
68
|
+
puts "=" * 60
|
|
69
|
+
puts "SUMMARY"
|
|
70
|
+
puts "=" * 60
|
|
71
|
+
|
|
72
|
+
successful = results.count { |r| r[:success] }
|
|
73
|
+
failed = results.count { |r| !r[:success] }
|
|
74
|
+
total_time = results.sum { |r| r[:elapsed] }
|
|
75
|
+
|
|
76
|
+
puts "Total examples: #{results.size}"
|
|
77
|
+
puts "Successful: #{successful}"
|
|
78
|
+
puts "Failed: #{failed}"
|
|
79
|
+
puts "Total time: #{format('%.2f', total_time)}s"
|
|
80
|
+
puts
|
|
81
|
+
|
|
82
|
+
if failed > 0
|
|
83
|
+
puts "Failed examples:"
|
|
84
|
+
results.reject { |r| r[:success] }.each do |result|
|
|
85
|
+
puts " - #{result[:name]} (exit code: #{result[:exit_code]})"
|
|
86
|
+
end
|
|
87
|
+
puts
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
puts "Output files saved to: #{OUTPUT_DIR}/"
|
|
91
|
+
puts
|
|
92
|
+
|
|
93
|
+
# List generated files
|
|
94
|
+
puts "Generated files:"
|
|
95
|
+
results.each do |result|
|
|
96
|
+
file_size = File.size(result[:output_path])
|
|
97
|
+
puts " #{File.basename(result[:output_path])} (#{file_size} bytes)"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if __FILE__ == $PROGRAM_NAME
|
|
103
|
+
runner = RegressionTestRunner.new
|
|
104
|
+
runner.run
|
|
105
|
+
end
|