geneva_drive 0.6.0 → 0.8.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/MANUAL.md +260 -1
- data/README.md +2 -0
- data/Rakefile +14 -1
- data/bin/matrix +76 -0
- data/bin/md2html +114 -0
- data/lib/generators/geneva_drive/install/install_generator.rb +5 -0
- data/lib/generators/geneva_drive/install/templates/add_resumable_step_support.rb +5 -9
- data/lib/generators/geneva_drive/install/templates/add_signals_support.rb +89 -0
- data/lib/geneva_drive/executor.rb +131 -10
- data/lib/geneva_drive/flow_control.rb +43 -3
- data/lib/geneva_drive/jobs/housekeeping_job.rb +83 -3
- data/lib/geneva_drive/json_column.rb +72 -0
- data/lib/geneva_drive/migration_helpers.rb +27 -0
- data/lib/geneva_drive/removed_step_definition.rb +34 -0
- data/lib/geneva_drive/signal.rb +245 -0
- data/lib/geneva_drive/signal_matcher.rb +60 -0
- data/lib/geneva_drive/step_collection.rb +31 -7
- data/lib/geneva_drive/step_definition.rb +91 -1
- data/lib/geneva_drive/step_execution.rb +39 -2
- data/lib/geneva_drive/test_helpers.rb +89 -2
- data/lib/geneva_drive/version.rb +1 -1
- data/lib/geneva_drive/workflow.rb +497 -24
- data/lib/geneva_drive.rb +24 -0
- data/test/dsl/signal_step_definition_test.rb +132 -0
- data/test/dummy/config/database.yml +40 -5
- data/test/dummy/config/initializers/geneva_drive.rb +46 -0
- data/test/dummy_install/db/migrate/20260217112949_create_geneva_drive_workflows.rb +76 -0
- data/test/dummy_install/db/migrate/20260217112950_create_geneva_drive_step_executions.rb +99 -0
- data/test/dummy_install/db/migrate/20260217112951_add_finished_at_to_geneva_drive_step_executions.rb +27 -0
- data/test/dummy_install/db/migrate/20260217112952_add_error_class_name_to_geneva_drive_step_executions.rb +9 -0
- data/test/dummy_install/db/migrate/20260217112953_add_resumable_step_support_to_geneva_drive_step_executions.rb +128 -0
- data/test/dummy_install/db/migrate/20260217112954_add_chained_step_executions_to_geneva_drive_step_executions.rb +136 -0
- data/test/dummy_install/db/schema.rb +75 -0
- data/test/dummy_install/log/test.log +1852 -0
- data/test/jobs/housekeeping_signals_test.rb +198 -0
- data/test/migration_helpers_test.rb +1 -1
- data/test/test_helper.rb +53 -13
- data/test/workflow/removed_step_test.rb +294 -0
- data/test/workflow/resume_at_test.rb +170 -0
- data/test/workflow/signal_consumption_test.rb +397 -0
- data/test/workflow/signal_delivery_test.rb +255 -0
- data/test/workflow/signal_durability_test.rb +164 -0
- data/test/workflow/signal_rendezvous_test.rb +581 -0
- data/test/workflow/signals_without_migration_test.rb +97 -0
- metadata +26 -2
|
@@ -46,6 +46,8 @@ module GenevaDrive::TestHelpers
|
|
|
46
46
|
step_execution = workflow.current_execution
|
|
47
47
|
break unless step_execution
|
|
48
48
|
|
|
49
|
+
raise_if_parked!(step_execution, "speedrun_workflow")
|
|
50
|
+
|
|
49
51
|
# interruptible: false so resumable steps run to completion
|
|
50
52
|
step_execution.execute!(interruptible: false)
|
|
51
53
|
iterations += 1
|
|
@@ -79,6 +81,8 @@ module GenevaDrive::TestHelpers
|
|
|
79
81
|
step_execution = workflow.current_execution
|
|
80
82
|
return nil unless step_execution
|
|
81
83
|
|
|
84
|
+
raise_if_parked!(step_execution, "perform_next_step")
|
|
85
|
+
|
|
82
86
|
step_execution.execute!
|
|
83
87
|
workflow.reload
|
|
84
88
|
step_execution
|
|
@@ -117,8 +121,9 @@ module GenevaDrive::TestHelpers
|
|
|
117
121
|
"Available steps: #{available}"
|
|
118
122
|
end
|
|
119
123
|
|
|
120
|
-
# Cancel any existing scheduled step executions to satisfy
|
|
121
|
-
|
|
124
|
+
# Cancel any existing scheduled or parked step executions to satisfy the
|
|
125
|
+
# uniqueness constraint
|
|
126
|
+
workflow.step_executions.where(state: %w[scheduled waiting]).update_all(
|
|
122
127
|
state: "canceled",
|
|
123
128
|
outcome: "canceled",
|
|
124
129
|
canceled_at: Time.current
|
|
@@ -194,11 +199,14 @@ module GenevaDrive::TestHelpers
|
|
|
194
199
|
step_execution = workflow.current_execution
|
|
195
200
|
return nil unless step_execution
|
|
196
201
|
|
|
202
|
+
raise_if_parked!(step_execution, "speedrun_current_step")
|
|
203
|
+
|
|
197
204
|
executions = 0
|
|
198
205
|
original_step_name = step_execution.step_name
|
|
199
206
|
|
|
200
207
|
loop do
|
|
201
208
|
step_execution.reload
|
|
209
|
+
raise_if_parked!(step_execution, "speedrun_current_step")
|
|
202
210
|
|
|
203
211
|
# Follow the execution chain: a completed execution that spawned a
|
|
204
212
|
# successor means the step is still going (suspend! and skip_to! create
|
|
@@ -289,4 +297,83 @@ module GenevaDrive::TestHelpers
|
|
|
289
297
|
assert successor, "Expected successor execution for step #{step_name}, but none found"
|
|
290
298
|
assert successor.scheduled?, "Expected successor to be scheduled, but was #{successor.state}"
|
|
291
299
|
end
|
|
300
|
+
|
|
301
|
+
# === Signal Test Helpers ===
|
|
302
|
+
|
|
303
|
+
# Asserts that the workflow is parked waiting for a signal.
|
|
304
|
+
#
|
|
305
|
+
# @param workflow [GenevaDrive::Workflow] the workflow to check
|
|
306
|
+
# @param signal_name [String, Symbol, nil] the signal name the step waits for
|
|
307
|
+
# @return [void]
|
|
308
|
+
#
|
|
309
|
+
# @example Check that the workflow is waiting
|
|
310
|
+
# assert_waiting_for_signal(workflow, :payment_confirmed)
|
|
311
|
+
#
|
|
312
|
+
def assert_waiting_for_signal(workflow, signal_name = nil)
|
|
313
|
+
workflow.reload
|
|
314
|
+
execution = workflow.step_executions.where(state: "waiting").first
|
|
315
|
+
|
|
316
|
+
assert execution,
|
|
317
|
+
"Expected #{workflow.class.name} to be waiting for a signal, but no parked step execution was found " \
|
|
318
|
+
"(current execution: #{workflow.current_execution&.state.inspect})"
|
|
319
|
+
|
|
320
|
+
return unless signal_name
|
|
321
|
+
|
|
322
|
+
matcher = execution.step_definition&.signal_matcher
|
|
323
|
+
assert_equal signal_name.to_s, matcher_name(matcher),
|
|
324
|
+
"Expected step '#{execution.step_name}' to be waiting for #{signal_name.inspect}, " \
|
|
325
|
+
"but it waits for #{matcher}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Asserts that a signal with the given name exists in the given state.
|
|
329
|
+
#
|
|
330
|
+
# @param workflow [GenevaDrive::Workflow] the workflow to check
|
|
331
|
+
# @param signal_name [String, Symbol] the signal name
|
|
332
|
+
# @param state [String, Symbol] the expected signal state
|
|
333
|
+
# @return [void]
|
|
334
|
+
#
|
|
335
|
+
# @example Check that the signal was consumed
|
|
336
|
+
# assert_signal_state(workflow, :payment_confirmed, :consumed)
|
|
337
|
+
#
|
|
338
|
+
def assert_signal_state(workflow, signal_name, state)
|
|
339
|
+
signal = workflow.signals.where(name: signal_name.to_s).order(:created_at, :id).last
|
|
340
|
+
|
|
341
|
+
assert signal, "Expected a #{signal_name.inspect} signal on #{workflow.class.name}, but none was found"
|
|
342
|
+
assert_equal state.to_s, signal.state,
|
|
343
|
+
"Expected signal #{signal_name.inspect} to be #{state}, but was #{signal.state}"
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
private
|
|
347
|
+
|
|
348
|
+
# The signal name a matcher accepts, when it has one to give. Custom
|
|
349
|
+
# matchers own their whole predicate and need not expose a name.
|
|
350
|
+
#
|
|
351
|
+
# @param matcher [Object, nil] anything responding to #matches?(signal)
|
|
352
|
+
# @return [String, nil]
|
|
353
|
+
def matcher_name(matcher)
|
|
354
|
+
matcher.name if matcher.respond_to?(:name)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Raises a descriptive error when a step-driving helper runs into a parked
|
|
358
|
+
# execution. Without this a parked workflow would look like an infinite
|
|
359
|
+
# loop (or a silently passing assertion) instead of "you forgot to signal".
|
|
360
|
+
#
|
|
361
|
+
# @param step_execution [GenevaDrive::StepExecution] the execution about to be driven
|
|
362
|
+
# @param helper_name [String] the helper that was called
|
|
363
|
+
# @return [void]
|
|
364
|
+
def raise_if_parked!(step_execution, helper_name)
|
|
365
|
+
return unless step_execution.waiting?
|
|
366
|
+
|
|
367
|
+
matcher = begin
|
|
368
|
+
step_execution.step_definition&.signal_matcher
|
|
369
|
+
rescue
|
|
370
|
+
nil
|
|
371
|
+
end
|
|
372
|
+
waiting_for = matcher ? " waiting for #{matcher}" : ""
|
|
373
|
+
example_name = matcher_name(matcher)&.to_sym
|
|
374
|
+
|
|
375
|
+
raise "#{helper_name} cannot drive step '#{step_execution.step_name}': its execution is parked" \
|
|
376
|
+
"#{waiting_for}. Deliver the signal first, e.g. " \
|
|
377
|
+
"workflow.signal!(#{example_name.inspect}), then call #{helper_name} again."
|
|
378
|
+
end
|
|
292
379
|
end
|
data/lib/geneva_drive/version.rb
CHANGED