dynflow 1.3.0 → 1.4.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/.rubocop.yml +1 -1
- data/.travis.yml +3 -4
- data/Dockerfile +9 -0
- data/Gemfile +6 -0
- data/Rakefile +1 -0
- data/doc/pages/Gemfile +1 -0
- data/doc/pages/Rakefile +1 -0
- data/doc/pages/plugins/alert_block.rb +1 -0
- data/doc/pages/plugins/div_tag.rb +1 -0
- data/doc/pages/plugins/graphviz.rb +1 -0
- data/doc/pages/plugins/plantuml.rb +1 -0
- data/doc/pages/plugins/play.rb +1 -0
- data/doc/pages/plugins/tags.rb +1 -0
- data/doc/pages/plugins/toc.rb +1 -0
- data/docker-compose.yml +41 -0
- data/dynflow.gemspec +1 -0
- data/examples/clock_benchmark.rb +1 -0
- data/examples/example_helper.rb +19 -2
- data/examples/future_execution.rb +2 -1
- data/examples/memory_limit_watcher.rb +1 -0
- data/examples/orchestrate.rb +4 -5
- data/examples/orchestrate_evented.rb +3 -2
- data/examples/remote_executor.rb +68 -0
- data/examples/singletons.rb +4 -3
- data/examples/sub_plan_concurrency_control.rb +2 -1
- data/examples/sub_plans.rb +3 -2
- data/examples/termination.rb +1 -0
- data/lib/dynflow.rb +20 -0
- data/lib/dynflow/action.rb +28 -3
- data/lib/dynflow/action/cancellable.rb +1 -0
- data/lib/dynflow/action/format.rb +1 -0
- data/lib/dynflow/action/missing.rb +1 -0
- data/lib/dynflow/action/polling.rb +3 -1
- data/lib/dynflow/action/progress.rb +1 -0
- data/lib/dynflow/action/rescue.rb +1 -0
- data/lib/dynflow/action/singleton.rb +1 -0
- data/lib/dynflow/action/suspended.rb +9 -2
- data/lib/dynflow/action/timeouts.rb +2 -1
- data/lib/dynflow/action/with_bulk_sub_plans.rb +2 -1
- data/lib/dynflow/action/with_polling_sub_plans.rb +7 -5
- data/lib/dynflow/action/with_sub_plans.rb +1 -0
- data/lib/dynflow/active_job/queue_adapter.rb +1 -0
- data/lib/dynflow/actor.rb +13 -5
- data/lib/dynflow/actors.rb +1 -0
- data/lib/dynflow/actors/execution_plan_cleaner.rb +1 -0
- data/lib/dynflow/clock.rb +27 -47
- data/lib/dynflow/config.rb +11 -2
- data/lib/dynflow/connectors.rb +1 -0
- data/lib/dynflow/connectors/abstract.rb +1 -0
- data/lib/dynflow/connectors/database.rb +1 -0
- data/lib/dynflow/connectors/direct.rb +1 -0
- data/lib/dynflow/coordinator.rb +1 -0
- data/lib/dynflow/coordinator_adapters.rb +1 -0
- data/lib/dynflow/coordinator_adapters/abstract.rb +1 -0
- data/lib/dynflow/coordinator_adapters/sequel.rb +1 -0
- data/lib/dynflow/dead_letter_silencer.rb +2 -0
- data/lib/dynflow/debug/telemetry/persistence.rb +1 -0
- data/lib/dynflow/delayed_executors.rb +1 -0
- data/lib/dynflow/delayed_executors/abstract.rb +1 -0
- data/lib/dynflow/delayed_executors/abstract_core.rb +1 -0
- data/lib/dynflow/delayed_executors/polling.rb +1 -0
- data/lib/dynflow/delayed_plan.rb +1 -0
- data/lib/dynflow/director.rb +80 -15
- data/lib/dynflow/director/execution_plan_manager.rb +17 -3
- data/lib/dynflow/director/flow_manager.rb +1 -0
- data/lib/dynflow/director/{work_queue.rb → queue_hash.rb} +9 -8
- data/lib/dynflow/director/running_steps_manager.rb +55 -18
- data/lib/dynflow/director/sequence_cursor.rb +1 -0
- data/lib/dynflow/director/sequential_manager.rb +12 -2
- data/lib/dynflow/dispatcher.rb +4 -2
- data/lib/dynflow/dispatcher/abstract.rb +1 -0
- data/lib/dynflow/dispatcher/client_dispatcher.rb +6 -4
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +13 -1
- data/lib/dynflow/errors.rb +1 -0
- data/lib/dynflow/execution_history.rb +1 -0
- data/lib/dynflow/execution_plan.rb +3 -2
- data/lib/dynflow/execution_plan/dependency_graph.rb +1 -0
- data/lib/dynflow/execution_plan/hooks.rb +1 -0
- data/lib/dynflow/execution_plan/output_reference.rb +2 -1
- data/lib/dynflow/execution_plan/steps.rb +1 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +10 -5
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +2 -0
- data/lib/dynflow/execution_plan/steps/error.rb +1 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +1 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +1 -0
- data/lib/dynflow/executors.rb +1 -1
- data/lib/dynflow/executors/abstract/core.rb +132 -0
- data/lib/dynflow/executors/parallel.rb +24 -11
- data/lib/dynflow/executors/parallel/core.rb +10 -91
- data/lib/dynflow/executors/parallel/pool.rb +4 -2
- data/lib/dynflow/executors/parallel/worker.rb +2 -1
- data/lib/dynflow/executors/sidekiq/core.rb +121 -0
- data/lib/dynflow/executors/sidekiq/internal_job_base.rb +24 -0
- data/lib/dynflow/executors/sidekiq/orchestrator_jobs.rb +60 -0
- data/lib/dynflow/executors/sidekiq/redis_locking.rb +69 -0
- data/lib/dynflow/executors/sidekiq/serialization.rb +33 -0
- data/lib/dynflow/executors/sidekiq/worker_jobs.rb +42 -0
- data/lib/dynflow/flows.rb +1 -0
- data/lib/dynflow/flows/abstract.rb +1 -0
- data/lib/dynflow/flows/abstract_composed.rb +1 -0
- data/lib/dynflow/flows/atom.rb +1 -0
- data/lib/dynflow/flows/concurrence.rb +1 -0
- data/lib/dynflow/flows/sequence.rb +1 -0
- data/lib/dynflow/logger_adapters.rb +1 -0
- data/lib/dynflow/logger_adapters/abstract.rb +1 -0
- data/lib/dynflow/logger_adapters/delegator.rb +1 -0
- data/lib/dynflow/logger_adapters/formatters.rb +1 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +1 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +1 -0
- data/lib/dynflow/logger_adapters/simple.rb +1 -0
- data/lib/dynflow/middleware.rb +1 -0
- data/lib/dynflow/middleware/common/singleton.rb +1 -0
- data/lib/dynflow/middleware/common/transaction.rb +1 -0
- data/lib/dynflow/middleware/register.rb +1 -0
- data/lib/dynflow/middleware/resolver.rb +1 -0
- data/lib/dynflow/middleware/stack.rb +1 -0
- data/lib/dynflow/middleware/world.rb +1 -0
- data/lib/dynflow/persistence.rb +3 -2
- data/lib/dynflow/persistence_adapters.rb +1 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +10 -7
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/002_incremental_progress.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/006_fix_data_length.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/007_future_execution.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/009_fix_mysql_data_length.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/010_add_execution_plans_label.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb +1 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/019_update_mysql_time_precision.rb +48 -0
- data/lib/dynflow/rails.rb +1 -0
- data/lib/dynflow/rails/configuration.rb +6 -3
- data/lib/dynflow/rails/daemon.rb +1 -0
- data/lib/dynflow/round_robin.rb +1 -0
- data/lib/dynflow/semaphores.rb +1 -0
- data/lib/dynflow/semaphores/abstract.rb +1 -0
- data/lib/dynflow/semaphores/aggregating.rb +1 -0
- data/lib/dynflow/semaphores/dummy.rb +1 -0
- data/lib/dynflow/semaphores/stateful.rb +1 -0
- data/lib/dynflow/serializable.rb +13 -4
- data/lib/dynflow/serializer.rb +24 -0
- data/lib/dynflow/serializers.rb +1 -0
- data/lib/dynflow/serializers/abstract.rb +1 -0
- data/lib/dynflow/serializers/noop.rb +1 -0
- data/lib/dynflow/stateful.rb +1 -0
- data/lib/dynflow/telemetry.rb +1 -0
- data/lib/dynflow/telemetry_adapters/abstract.rb +1 -0
- data/lib/dynflow/telemetry_adapters/dummy.rb +1 -0
- data/lib/dynflow/telemetry_adapters/statsd.rb +1 -0
- data/lib/dynflow/testing.rb +1 -0
- data/lib/dynflow/testing/assertions.rb +6 -5
- data/lib/dynflow/testing/dummy_execution_plan.rb +1 -0
- data/lib/dynflow/testing/dummy_executor.rb +19 -2
- data/lib/dynflow/testing/dummy_planned_action.rb +1 -0
- data/lib/dynflow/testing/dummy_step.rb +3 -1
- data/lib/dynflow/testing/dummy_world.rb +9 -0
- data/lib/dynflow/testing/factories.rb +6 -1
- data/lib/dynflow/testing/in_thread_executor.rb +22 -3
- data/lib/dynflow/testing/in_thread_world.rb +9 -0
- data/lib/dynflow/testing/managed_clock.rb +1 -0
- data/lib/dynflow/testing/mimic.rb +1 -0
- data/lib/dynflow/throttle_limiter.rb +1 -0
- data/lib/dynflow/transaction_adapters.rb +1 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +1 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +1 -0
- data/lib/dynflow/transaction_adapters/none.rb +1 -0
- data/lib/dynflow/utils.rb +1 -0
- data/lib/dynflow/utils/indifferent_hash.rb +1 -0
- data/lib/dynflow/utils/priority_queue.rb +1 -0
- data/lib/dynflow/version.rb +2 -1
- data/lib/dynflow/watchers/memory_consumption_watcher.rb +1 -0
- data/lib/dynflow/web.rb +1 -0
- data/lib/dynflow/web/console.rb +1 -0
- data/lib/dynflow/web/console_helpers.rb +1 -0
- data/lib/dynflow/web/filtering_helpers.rb +1 -0
- data/lib/dynflow/web/world_helpers.rb +1 -0
- data/lib/dynflow/web_console.rb +1 -0
- data/lib/dynflow/world.rb +11 -1
- data/lib/dynflow/world/invalidation.rb +7 -1
- data/test/abnormal_states_recovery_test.rb +41 -40
- data/test/action_test.rb +160 -110
- data/test/activejob_adapter_test.rb +1 -0
- data/test/batch_sub_tasks_test.rb +12 -11
- data/test/clock_test.rb +2 -1
- data/test/concurrency_control_test.rb +20 -19
- data/test/coordinator_test.rb +20 -21
- data/test/daemon_test.rb +2 -1
- data/test/dead_letter_silencer_test.rb +9 -7
- data/test/dispatcher_test.rb +2 -1
- data/test/execution_plan_cleaner_test.rb +13 -12
- data/test/execution_plan_hooks_test.rb +3 -2
- data/test/execution_plan_test.rb +33 -32
- data/test/executor_test.rb +533 -489
- data/test/future_execution_test.rb +45 -44
- data/test/memory_cosumption_watcher_test.rb +5 -4
- data/test/middleware_test.rb +55 -54
- data/test/persistence_test.rb +56 -53
- data/test/rescue_test.rb +36 -35
- data/test/round_robin_test.rb +13 -12
- data/test/semaphores_test.rb +31 -30
- data/test/support/code_workflow_example.rb +1 -0
- data/test/support/dummy_example.rb +14 -1
- data/test/support/middleware_example.rb +2 -1
- data/test/support/rails/config/environment.rb +1 -0
- data/test/support/rescue_example.rb +1 -0
- data/test/support/test_execution_log.rb +1 -0
- data/test/test_helper.rb +18 -17
- data/test/testing_test.rb +45 -44
- data/test/utils_test.rb +18 -17
- data/test/web_console_test.rb +1 -0
- data/test/world_test.rb +7 -6
- metadata +13 -4
- data/lib/dynflow/executors/abstract.rb +0 -40
data/test/execution_plan_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative 'test_helper'
|
2
3
|
|
3
4
|
module Dynflow
|
@@ -30,8 +31,8 @@ module Dynflow
|
|
30
31
|
|
31
32
|
it 'restores the plan properly' do
|
32
33
|
assert deserialized_execution_plan.valid?
|
33
|
-
deserialized_execution_plan.id.must_equal execution_plan.id
|
34
|
-
deserialized_execution_plan.label.must_equal execution_plan.label
|
34
|
+
_(deserialized_execution_plan.id).must_equal execution_plan.id
|
35
|
+
_(deserialized_execution_plan.label).must_equal execution_plan.label
|
35
36
|
|
36
37
|
assert_steps_equal execution_plan.root_plan_step, deserialized_execution_plan.root_plan_step
|
37
38
|
assert_equal execution_plan.steps.keys, deserialized_execution_plan.steps.keys
|
@@ -77,8 +78,8 @@ module Dynflow
|
|
77
78
|
end
|
78
79
|
|
79
80
|
it 'is determined by the action#label method of entry action' do
|
80
|
-
execution_plan.label.must_equal 'Support::CodeWorkflowExample::FastCommit'
|
81
|
-
dummy_execution_plan.label.must_equal 'dummy_action'
|
81
|
+
_(execution_plan.label).must_equal 'Support::CodeWorkflowExample::FastCommit'
|
82
|
+
_(dummy_execution_plan.label).must_equal 'dummy_action'
|
82
83
|
end
|
83
84
|
end
|
84
85
|
describe '#result' do
|
@@ -92,8 +93,8 @@ module Dynflow
|
|
92
93
|
before { execution_plan.steps[2].set_state :error, true }
|
93
94
|
|
94
95
|
it 'should be :error' do
|
95
|
-
execution_plan.result.must_equal :error
|
96
|
-
execution_plan.error
|
96
|
+
_(execution_plan.result).must_equal :error
|
97
|
+
_(execution_plan.error?).must_equal true
|
97
98
|
end
|
98
99
|
|
99
100
|
end
|
@@ -106,7 +107,7 @@ module Dynflow
|
|
106
107
|
end
|
107
108
|
|
108
109
|
it 'should be :error' do
|
109
|
-
execution_plan.result.must_equal :error
|
110
|
+
_(execution_plan.result).must_equal :error
|
110
111
|
end
|
111
112
|
|
112
113
|
end
|
@@ -119,7 +120,7 @@ module Dynflow
|
|
119
120
|
end
|
120
121
|
|
121
122
|
it 'should be :pending' do
|
122
|
-
execution_plan.result.must_equal :pending
|
123
|
+
_(execution_plan.result).must_equal :pending
|
123
124
|
end
|
124
125
|
|
125
126
|
end
|
@@ -134,7 +135,7 @@ module Dynflow
|
|
134
135
|
end
|
135
136
|
|
136
137
|
it 'should be :warning' do
|
137
|
-
execution_plan.result.must_equal :warning
|
138
|
+
_(execution_plan.result).must_equal :warning
|
138
139
|
end
|
139
140
|
|
140
141
|
end
|
@@ -148,7 +149,7 @@ module Dynflow
|
|
148
149
|
|
149
150
|
it 'does not have itself as a sub plan' do
|
150
151
|
assert execution_plan.actions.count >= 2
|
151
|
-
execution_plan.sub_plans.must_be :empty?
|
152
|
+
_(execution_plan.sub_plans).must_be :empty?
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
@@ -185,9 +186,9 @@ module Dynflow
|
|
185
186
|
end
|
186
187
|
|
187
188
|
it 'stores the ids for plan, run and finalize steps' do
|
188
|
-
action.plan_step_id.must_equal 3
|
189
|
-
action.run_step_id.must_equal 4
|
190
|
-
action.finalize_step_id.must_equal 5
|
189
|
+
_(action.plan_step_id).must_equal 3
|
190
|
+
_(action.run_step_id).must_equal 4
|
191
|
+
_(action.finalize_step_id).must_equal 5
|
191
192
|
end
|
192
193
|
end
|
193
194
|
|
@@ -200,7 +201,7 @@ module Dynflow
|
|
200
201
|
end
|
201
202
|
|
202
203
|
it 'allows setting custom id for the execution plan' do
|
203
|
-
execution_plan.id.must_equal sample_uuid
|
204
|
+
_(execution_plan.id).must_equal sample_uuid
|
204
205
|
end
|
205
206
|
end
|
206
207
|
|
@@ -233,7 +234,7 @@ module Dynflow
|
|
233
234
|
end
|
234
235
|
|
235
236
|
it 'stops the planning right after the first error occurred' do
|
236
|
-
execution_plan.steps.size.must_equal 2
|
237
|
+
_(execution_plan.steps.size).must_equal 2
|
237
238
|
end
|
238
239
|
end
|
239
240
|
|
@@ -320,7 +321,7 @@ module Dynflow
|
|
320
321
|
end
|
321
322
|
end
|
322
323
|
cancel_events = plan.cancel
|
323
|
-
cancel_events.size.must_equal 1
|
324
|
+
_(cancel_events.size).must_equal 1
|
324
325
|
cancel_events.each(&:wait)
|
325
326
|
finished.wait
|
326
327
|
end
|
@@ -334,7 +335,7 @@ module Dynflow
|
|
334
335
|
end
|
335
336
|
end
|
336
337
|
cancel_events = plan.cancel true
|
337
|
-
cancel_events.size.must_equal 1
|
338
|
+
_(cancel_events.size).must_equal 1
|
338
339
|
cancel_events.each(&:wait)
|
339
340
|
finished.wait
|
340
341
|
end
|
@@ -346,9 +347,9 @@ module Dynflow
|
|
346
347
|
end
|
347
348
|
|
348
349
|
it 'provides the access to the actions data via steps #action' do
|
349
|
-
execution_plan.steps.size.must_equal 20
|
350
|
+
_(execution_plan.steps.size).must_equal 20
|
350
351
|
execution_plan.steps.each do |_, step|
|
351
|
-
step.action(execution_plan).phase.must_equal Action::Present
|
352
|
+
_(step.action(execution_plan).phase).must_equal Action::Present
|
352
353
|
end
|
353
354
|
end
|
354
355
|
end
|
@@ -359,9 +360,9 @@ module Dynflow
|
|
359
360
|
error = ExecutionPlan::Steps::Error.new_from_hash(exception_class: "RenamedError",
|
360
361
|
message: "This errror is not longer here",
|
361
362
|
backtrace: [])
|
362
|
-
error.exception_class.name.must_equal "RenamedError"
|
363
|
-
error.exception_class.to_s.must_equal "Dynflow::Errors::UnknownError[RenamedError]"
|
364
|
-
error.exception.inspect.must_equal "Dynflow::Errors::UnknownError[RenamedError]: This errror is not longer here"
|
363
|
+
_(error.exception_class.name).must_equal "RenamedError"
|
364
|
+
_(error.exception_class.to_s).must_equal "Dynflow::Errors::UnknownError[RenamedError]"
|
365
|
+
_(error.exception.inspect).must_equal "Dynflow::Errors::UnknownError[RenamedError]: This errror is not longer here"
|
365
366
|
end
|
366
367
|
|
367
368
|
end
|
@@ -379,26 +380,26 @@ module Dynflow
|
|
379
380
|
|
380
381
|
it 'unlocks the locks on transition to stopped' do
|
381
382
|
plan = world.plan(SingletonAction)
|
382
|
-
plan.state.must_equal :planned
|
383
|
+
_(plan.state).must_equal :planned
|
383
384
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
384
385
|
.unique_filter plan.entry_action.class.name
|
385
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
386
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
386
387
|
plan = world.execute(plan.id).wait!.value
|
387
|
-
plan.state.must_equal :stopped
|
388
|
-
plan.result.must_equal :success
|
389
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 0
|
388
|
+
_(plan.state).must_equal :stopped
|
389
|
+
_(plan.result).must_equal :success
|
390
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 0
|
390
391
|
end
|
391
392
|
|
392
393
|
it 'unlocks the locks on transition to paused' do
|
393
394
|
plan = world.plan(SingletonAction, :fail => true)
|
394
|
-
plan.state.must_equal :planned
|
395
|
+
_(plan.state).must_equal :planned
|
395
396
|
lock_filter = ::Dynflow::Coordinator::SingletonActionLock
|
396
397
|
.unique_filter plan.entry_action.class.name
|
397
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 1
|
398
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 1
|
398
399
|
plan = world.execute(plan.id).wait!.value
|
399
|
-
plan.state.must_equal :paused
|
400
|
-
plan.result.must_equal :error
|
401
|
-
world.coordinator.find_locks(lock_filter).count.must_equal 0
|
400
|
+
_(plan.state).must_equal :paused
|
401
|
+
_(plan.result).must_equal :error
|
402
|
+
_(world.coordinator.find_locks(lock_filter).count).must_equal 0
|
402
403
|
end
|
403
404
|
end
|
404
405
|
end
|
data/test/executor_test.rb
CHANGED
@@ -1,445 +1,489 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
require_relative 'test_helper'
|
3
4
|
require 'mocha/minitest'
|
4
5
|
|
6
|
+
require 'sidekiq'
|
7
|
+
require 'sidekiq/api'
|
8
|
+
require 'sidekiq/testing'
|
9
|
+
::Sidekiq::Testing::inline!
|
10
|
+
require 'dynflow/executors/sidekiq/core'
|
11
|
+
|
12
|
+
module RedisMocks
|
13
|
+
def release_orchestrator_lock; end
|
14
|
+
def wait_for_orchestrator_lock; end
|
15
|
+
def reacquire_orchestrator_lock; end
|
16
|
+
end
|
17
|
+
|
18
|
+
::Dynflow::Executors::Sidekiq::Core.send(:prepend, RedisMocks)
|
19
|
+
|
5
20
|
module Dynflow
|
6
21
|
module ExecutorTest
|
7
|
-
|
22
|
+
[::Dynflow::Executors::Parallel::Core, ::Dynflow::Executors::Sidekiq::Core].each do |executor|
|
23
|
+
describe executor do
|
24
|
+
include PlanAssertions
|
8
25
|
|
9
|
-
|
26
|
+
after do
|
27
|
+
::Dynflow.instance_variable_set('@process_world', nil)
|
28
|
+
end
|
10
29
|
|
11
|
-
|
30
|
+
before do
|
31
|
+
executor.any_instance.stubs(:begin_startup!)
|
32
|
+
end
|
12
33
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
34
|
+
let(:world) do
|
35
|
+
world = WorldFactory.create_world { |c| c.executor = executor }
|
36
|
+
::Dynflow.instance_variable_set('@process_world', world)
|
37
|
+
world
|
38
|
+
end
|
17
39
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
40
|
+
let :issues_data do
|
41
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
42
|
+
{ 'author' => 'John Doe', 'text' => 'Internal server error' }]
|
43
|
+
end
|
22
44
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
45
|
+
let :failing_issues_data do
|
46
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
47
|
+
{ 'author' => 'John Doe', 'text' => 'trolling' }]
|
48
|
+
end
|
27
49
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
plan
|
33
|
-
end
|
50
|
+
let :finalize_failing_issues_data do
|
51
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
52
|
+
{ 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
|
53
|
+
end
|
34
54
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
55
|
+
let :failed_execution_plan do
|
56
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
|
57
|
+
plan = world.execute(plan.id).value
|
58
|
+
_(plan.state).must_equal :paused
|
59
|
+
plan
|
60
|
+
end
|
41
61
|
|
42
|
-
|
43
|
-
|
44
|
-
|
62
|
+
let :finalize_failed_execution_plan do
|
63
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
|
64
|
+
plan = world.execute(plan.id).value
|
65
|
+
_(plan.state).must_equal :paused
|
66
|
+
plan
|
67
|
+
end
|
45
68
|
|
46
|
-
|
47
|
-
|
48
|
-
|
69
|
+
let :persisted_plan do
|
70
|
+
world.persistence.load_execution_plan(execution_plan.id)
|
71
|
+
end
|
49
72
|
|
50
|
-
|
73
|
+
describe "execution plan state" do
|
51
74
|
|
52
|
-
|
75
|
+
describe "after successful planning" do
|
53
76
|
|
54
|
-
|
55
|
-
|
56
|
-
|
77
|
+
let :execution_plan do
|
78
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
79
|
+
end
|
57
80
|
|
58
|
-
|
59
|
-
|
60
|
-
|
81
|
+
it "is pending" do
|
82
|
+
_(execution_plan.state).must_equal :planned
|
83
|
+
end
|
61
84
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
85
|
+
describe "when finished successfully" do
|
86
|
+
it "is stopped" do
|
87
|
+
world.execute(execution_plan.id).value.tap do |plan|
|
88
|
+
_(plan.state).must_equal :stopped
|
89
|
+
end
|
66
90
|
end
|
67
91
|
end
|
68
|
-
end
|
69
92
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
93
|
+
describe "when finished with error" do
|
94
|
+
it "is paused" do
|
95
|
+
world.execute(failed_execution_plan.id).value.tap do |plan|
|
96
|
+
_(plan.state).must_equal :paused
|
97
|
+
end
|
74
98
|
end
|
75
99
|
end
|
76
100
|
end
|
77
|
-
end
|
78
101
|
|
79
|
-
|
102
|
+
describe "after error in planning" do
|
80
103
|
|
81
|
-
|
82
|
-
|
83
|
-
|
104
|
+
class FailingAction < Dynflow::Action
|
105
|
+
def plan
|
106
|
+
raise "I failed"
|
107
|
+
end
|
84
108
|
end
|
85
|
-
end
|
86
109
|
|
87
|
-
|
88
|
-
|
89
|
-
|
110
|
+
let :execution_plan do
|
111
|
+
world.plan(FailingAction)
|
112
|
+
end
|
90
113
|
|
91
|
-
|
92
|
-
|
93
|
-
|
114
|
+
it "is stopped" do
|
115
|
+
_(execution_plan.state).must_equal :stopped
|
116
|
+
end
|
94
117
|
|
95
|
-
|
118
|
+
end
|
96
119
|
|
97
|
-
|
98
|
-
|
120
|
+
describe "when being executed" do
|
121
|
+
include TestHelpers
|
99
122
|
|
100
|
-
|
101
|
-
|
102
|
-
|
123
|
+
let :execution_plan do
|
124
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
|
125
|
+
end
|
103
126
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
127
|
+
before do
|
128
|
+
TestPause.setup
|
129
|
+
@execution = world.execute(execution_plan.id)
|
130
|
+
end
|
108
131
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
132
|
+
after do
|
133
|
+
@execution.wait
|
134
|
+
TestPause.teardown
|
135
|
+
end
|
113
136
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
137
|
+
it "is running" do
|
138
|
+
TestPause.when_paused do
|
139
|
+
plan = world.persistence.load_execution_plan(execution_plan.id)
|
140
|
+
_(plan.state).must_equal :running
|
141
|
+
triage = plan.run_steps.find do |s|
|
142
|
+
s.action_class == Support::CodeWorkflowExample::Triage
|
143
|
+
end
|
144
|
+
_(triage.state).must_equal :running
|
145
|
+
_(world.persistence.load_step(triage.execution_plan_id, triage.id, world).state).must_equal :running
|
120
146
|
end
|
121
|
-
triage.state.must_equal :running
|
122
|
-
world.persistence.
|
123
|
-
load_step(triage.execution_plan_id, triage.id, world).
|
124
|
-
state.must_equal :running
|
125
147
|
end
|
126
|
-
end
|
127
148
|
|
128
|
-
|
129
|
-
|
130
|
-
|
149
|
+
it "fails when trying to execute again" do
|
150
|
+
TestPause.when_paused do
|
151
|
+
assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
|
152
|
+
end
|
131
153
|
end
|
132
|
-
end
|
133
154
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
155
|
+
it "handles when the execution plan is deleted" do
|
156
|
+
TestPause.when_paused do
|
157
|
+
world.persistence.delete_execution_plans(uuid: [execution_plan.id])
|
158
|
+
end
|
159
|
+
director = get_director(world)
|
160
|
+
wait_for('execution plan removed from executor') do
|
161
|
+
!director.current_execution_plan_ids.include?(execution_plan.id)
|
162
|
+
end
|
163
|
+
_(world.persistence.find_execution_plans(filters: { uuid: [execution_plan.id] })).must_be :empty?
|
141
164
|
end
|
142
|
-
world.persistence.find_execution_plans(filters: { uuid: [execution_plan.id] }).must_be :empty?
|
143
165
|
end
|
144
166
|
end
|
145
|
-
end
|
146
167
|
|
147
|
-
|
168
|
+
describe "execution of run flow" do
|
148
169
|
|
149
|
-
|
150
|
-
|
151
|
-
|
170
|
+
before do
|
171
|
+
TestExecutionLog.setup
|
172
|
+
end
|
152
173
|
|
153
|
-
|
154
|
-
|
155
|
-
|
174
|
+
let :result do
|
175
|
+
world.execute(execution_plan.id).value!
|
176
|
+
end
|
156
177
|
|
157
|
-
|
158
|
-
|
159
|
-
|
178
|
+
after do
|
179
|
+
TestExecutionLog.teardown
|
180
|
+
end
|
160
181
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
182
|
+
def persisted_plan
|
183
|
+
result
|
184
|
+
super
|
185
|
+
end
|
165
186
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
187
|
+
describe 'cancellable action' do
|
188
|
+
describe 'successful' do
|
189
|
+
let :execution_plan do
|
190
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
|
191
|
+
end
|
171
192
|
|
172
|
-
|
173
|
-
|
174
|
-
|
193
|
+
it "doesn't cause problems" do
|
194
|
+
_(result.result).must_equal :success
|
195
|
+
_(result.state).must_equal :stopped
|
196
|
+
end
|
175
197
|
end
|
176
|
-
end
|
177
198
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
199
|
+
describe 'canceled' do
|
200
|
+
let :execution_plan do
|
201
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
|
202
|
+
end
|
182
203
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
204
|
+
it 'cancels' do
|
205
|
+
_(result.result).must_equal :success
|
206
|
+
_(result.state).must_equal :stopped
|
207
|
+
action = world.persistence.load_action result.steps[2]
|
208
|
+
_(action.output[:task][:progress]).must_equal 30
|
209
|
+
_(action.output[:task][:cancelled]).must_equal true
|
210
|
+
end
|
189
211
|
end
|
190
|
-
end
|
191
212
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
213
|
+
describe 'canceled failed' do
|
214
|
+
let :execution_plan do
|
215
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
|
216
|
+
end
|
196
217
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
218
|
+
it 'fails' do
|
219
|
+
_(result.result).must_equal :error
|
220
|
+
_(result.state).must_equal :paused
|
221
|
+
step = result.steps[2]
|
222
|
+
_(step.error.message).must_equal 'action cancelled'
|
223
|
+
action = world.persistence.load_action step
|
224
|
+
_(action.output[:task][:progress]).must_equal 30
|
225
|
+
end
|
204
226
|
end
|
205
227
|
end
|
206
|
-
end
|
207
228
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
229
|
+
describe 'suspended action' do
|
230
|
+
describe 'handling errors in setup' do
|
231
|
+
let :execution_plan do
|
232
|
+
world.plan(Support::DummyExample::Polling,
|
233
|
+
external_task_id: '123',
|
234
|
+
text: 'troll setup')
|
235
|
+
end
|
215
236
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
237
|
+
it 'fails' do
|
238
|
+
assert_equal :error, result.result
|
239
|
+
assert_equal :paused, result.state
|
240
|
+
assert_equal :error, result.run_steps.first.state
|
241
|
+
end
|
220
242
|
end
|
221
|
-
end
|
222
243
|
|
223
|
-
|
224
|
-
|
225
|
-
world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
|
226
|
-
end
|
244
|
+
describe 'events' do
|
245
|
+
include TestHelpers
|
227
246
|
|
228
|
-
|
229
|
-
|
230
|
-
|
247
|
+
let(:clock) { Dynflow::Testing::ManagedClock.new }
|
248
|
+
let :execution_plan do
|
249
|
+
world.plan(Support::DummyExample::PlanEventsAction, ping_time: 0.5)
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'handles planning events' do
|
253
|
+
world.stub(:clock, clock) do
|
254
|
+
world.execute(execution_plan.id)
|
255
|
+
ping = wait_for do
|
256
|
+
clock.pending_pings.first
|
257
|
+
end
|
258
|
+
assert ping.what.value.is_a?(Director::Event)
|
259
|
+
clock.progress
|
260
|
+
wait_for do
|
261
|
+
world.persistence.load_execution_plan(execution_plan.id).result == :success
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
231
265
|
end
|
232
266
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
267
|
+
describe 'running' do
|
268
|
+
let :execution_plan do
|
269
|
+
world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
|
270
|
+
end
|
237
271
|
|
238
|
-
|
272
|
+
it "doesn't cause problems" do
|
273
|
+
_(result.result).must_equal :success
|
274
|
+
_(result.state).must_equal :stopped
|
275
|
+
end
|
239
276
|
|
240
|
-
|
241
|
-
|
277
|
+
it 'does set times' do
|
278
|
+
refute_nil result.started_at
|
279
|
+
refute_nil result.ended_at
|
280
|
+
_(result.execution_time).must_be :<, result.real_time
|
242
281
|
|
243
|
-
|
244
|
-
plan_step.started_at.wont_be_nil
|
245
|
-
plan_step.ended_at.wont_be_nil
|
246
|
-
plan_step.execution_time.must_equal plan_step.real_time
|
282
|
+
step_sum = result.steps.values.map(&:execution_time).reduce(:+)
|
247
283
|
|
248
|
-
|
249
|
-
|
250
|
-
run_step.ended_at.wont_be_nil
|
251
|
-
run_step.execution_time.must_be :<, run_step.real_time
|
252
|
-
end
|
253
|
-
end
|
284
|
+
# Storing floats can lead to slight deviations, 1ns precision should be enough
|
285
|
+
_(result.execution_time).must_be_close_to step_sum, 0.000_001
|
254
286
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
287
|
+
plan_step = result.steps[1]
|
288
|
+
refute_nil plan_step.started_at
|
289
|
+
refute_nil plan_step.ended_at
|
290
|
+
_(plan_step.execution_time).must_equal plan_step.real_time
|
260
291
|
|
261
|
-
|
262
|
-
|
263
|
-
|
292
|
+
run_step = result.steps[2]
|
293
|
+
refute_nil run_step.started_at
|
294
|
+
refute_nil run_step.ended_at
|
295
|
+
_(run_step.execution_time).must_be :<, run_step.real_time
|
296
|
+
end
|
264
297
|
end
|
265
298
|
|
266
|
-
describe '
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
299
|
+
describe 'progress' do
|
300
|
+
before do
|
301
|
+
TestPause.setup
|
302
|
+
@running_plan = world.execute(execution_plan.id)
|
303
|
+
end
|
304
|
+
|
305
|
+
after do
|
306
|
+
@running_plan.wait
|
307
|
+
TestPause.teardown
|
308
|
+
end
|
309
|
+
|
310
|
+
describe 'plan with one action' do
|
311
|
+
let :execution_plan do
|
312
|
+
world.plan(Support::DummyExample::Polling,
|
313
|
+
{ external_task_id: '123',
|
314
|
+
text: 'pause in progress 20%' })
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'determines the progress of the execution plan in percents' do
|
318
|
+
TestPause.when_paused do
|
319
|
+
plan = world.persistence.load_execution_plan(execution_plan.id)
|
320
|
+
_(plan.progress.round(2)).must_equal 0.2
|
321
|
+
end
|
322
|
+
end
|
271
323
|
end
|
272
324
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
325
|
+
describe 'plan with more action' do
|
326
|
+
let :execution_plan do
|
327
|
+
world.plan(Support::DummyExample::WeightedPolling,
|
328
|
+
{ external_task_id: '123',
|
329
|
+
text: 'pause in progress 20%' })
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'takes the steps weight in account' do
|
333
|
+
TestPause.when_paused do
|
334
|
+
plan = world.persistence.load_execution_plan(execution_plan.id)
|
335
|
+
_(plan.progress.round(2)).must_equal 0.42
|
336
|
+
end
|
277
337
|
end
|
278
338
|
end
|
279
339
|
end
|
280
340
|
|
281
|
-
describe '
|
341
|
+
describe 'works when resumed after error' do
|
282
342
|
let :execution_plan do
|
283
|
-
world.plan(Support::DummyExample::
|
343
|
+
world.plan(Support::DummyExample::Polling,
|
284
344
|
{ external_task_id: '123',
|
285
|
-
text: '
|
345
|
+
text: 'troll progress' })
|
286
346
|
end
|
287
347
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
348
|
+
specify do
|
349
|
+
assert_equal :paused, result.state
|
350
|
+
assert_equal :error, result.result
|
351
|
+
assert_equal :error, result.run_steps.first.state
|
352
|
+
|
353
|
+
ep = world.execute(result.id).value
|
354
|
+
assert_equal :stopped, ep.state
|
355
|
+
assert_equal :success, ep.result
|
356
|
+
assert_equal :success, ep.run_steps.first.state
|
293
357
|
end
|
294
358
|
end
|
359
|
+
|
295
360
|
end
|
296
361
|
|
297
|
-
describe
|
362
|
+
describe "action with empty flows" do
|
363
|
+
|
298
364
|
let :execution_plan do
|
299
|
-
world.plan(Support::
|
300
|
-
|
301
|
-
|
365
|
+
world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
|
366
|
+
assert_equal plan.run_flow.size, 0
|
367
|
+
assert_equal plan.finalize_flow.size, 0
|
368
|
+
end.tap do |w|
|
369
|
+
w
|
370
|
+
end
|
302
371
|
end
|
303
372
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
assert_equal :error, result.run_steps.first.state
|
308
|
-
|
309
|
-
ep = world.execute(result.id).value
|
310
|
-
assert_equal :stopped, ep.state
|
311
|
-
assert_equal :success, ep.result
|
312
|
-
assert_equal :success, ep.run_steps.first.state
|
373
|
+
it "doesn't cause problems" do
|
374
|
+
_(result.result).must_equal :success
|
375
|
+
_(result.state).must_equal :stopped
|
313
376
|
end
|
314
|
-
end
|
315
|
-
|
316
|
-
end
|
317
|
-
|
318
|
-
describe "action with empty flows" do
|
319
377
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
assert_equal plan.finalize_flow.size, 0
|
324
|
-
end.tap do |w|
|
325
|
-
w
|
378
|
+
it 'will not run again' do
|
379
|
+
world.execute(execution_plan.id)
|
380
|
+
assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
|
326
381
|
end
|
327
|
-
end
|
328
382
|
|
329
|
-
it "doesn't cause problems" do
|
330
|
-
result.result.must_equal :success
|
331
|
-
result.state.must_equal :stopped
|
332
383
|
end
|
333
384
|
|
334
|
-
|
335
|
-
world.execute(execution_plan.id)
|
336
|
-
assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
|
337
|
-
end
|
385
|
+
describe 'action with empty run flow but some finalize flow' do
|
338
386
|
|
339
|
-
|
340
|
-
|
341
|
-
|
387
|
+
let :execution_plan do
|
388
|
+
world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
|
389
|
+
assert_equal plan.run_flow.size, 0
|
390
|
+
assert_equal plan.finalize_flow.size, 1
|
391
|
+
end
|
392
|
+
end
|
342
393
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
assert_equal plan.finalize_flow.size, 1
|
394
|
+
it "doesn't cause problems" do
|
395
|
+
_(result.result).must_equal :success
|
396
|
+
_(result.state).must_equal :stopped
|
347
397
|
end
|
348
|
-
end
|
349
398
|
|
350
|
-
it "doesn't cause problems" do
|
351
|
-
result.result.must_equal :success
|
352
|
-
result.state.must_equal :stopped
|
353
399
|
end
|
354
400
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
360
|
-
end
|
401
|
+
describe 'running' do
|
402
|
+
let :execution_plan do
|
403
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
404
|
+
end
|
361
405
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
406
|
+
it "runs all the steps in the run flow" do
|
407
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
|
408
|
+
Dynflow::Flows::Concurrence
|
409
|
+
Dynflow::Flows::Sequence
|
410
|
+
4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
411
|
+
7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
412
|
+
9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
413
|
+
Dynflow::Flows::Sequence
|
414
|
+
13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
415
|
+
16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
416
|
+
18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
417
|
+
EXECUTED_RUN_FLOW
|
418
|
+
end
|
374
419
|
end
|
375
|
-
end
|
376
420
|
|
377
|
-
end
|
378
|
-
|
379
|
-
describe "execution of finalize flow" do
|
380
|
-
before do
|
381
|
-
TestExecutionLog.setup
|
382
|
-
result = world.execute(execution_plan.id).value
|
383
|
-
raise result if result.is_a? Exception
|
384
421
|
end
|
385
422
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
let :execution_plan do
|
392
|
-
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
423
|
+
describe "execution of finalize flow" do
|
424
|
+
before do
|
425
|
+
TestExecutionLog.setup
|
426
|
+
result = world.execute(execution_plan.id).value
|
427
|
+
raise result if result.is_a? Exception
|
393
428
|
end
|
394
429
|
|
395
|
-
|
396
|
-
|
397
|
-
{ "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
|
398
|
-
assert_finalized(Support::CodeWorkflowExample::Triage,
|
399
|
-
{ "author" => "Peter Smith", "text" => "Failing test" })
|
430
|
+
after do
|
431
|
+
TestExecutionLog.teardown
|
400
432
|
end
|
401
|
-
end
|
402
433
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
434
|
+
describe "when run flow successful" do
|
435
|
+
let :execution_plan do
|
436
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
437
|
+
end
|
407
438
|
|
408
|
-
|
409
|
-
|
439
|
+
it "runs all the steps in the finalize flow" do
|
440
|
+
assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
|
441
|
+
{ "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" },
|
442
|
+
{ "author" => "John Doe", "text" => "Internal server error" }] })
|
443
|
+
assert_finalized(Support::CodeWorkflowExample::Triage,
|
444
|
+
{ "author" => "Peter Smith", "text" => "Failing test" })
|
445
|
+
end
|
410
446
|
end
|
411
|
-
end
|
412
447
|
|
413
|
-
|
448
|
+
describe "when run flow failed" do
|
449
|
+
let :execution_plan do
|
450
|
+
failed_execution_plan
|
451
|
+
end
|
414
452
|
|
415
|
-
|
453
|
+
it "doesn't run the steps in the finalize flow" do
|
454
|
+
_(TestExecutionLog.finalize.size).must_equal 0
|
455
|
+
end
|
456
|
+
end
|
416
457
|
|
417
|
-
after do
|
418
|
-
TestExecutionLog.teardown
|
419
458
|
end
|
420
459
|
|
421
|
-
|
422
|
-
|
423
|
-
|
460
|
+
describe "re-execution of run flow after fix in run phase" do
|
461
|
+
after do
|
462
|
+
TestExecutionLog.teardown
|
424
463
|
end
|
425
|
-
|
426
|
-
|
427
|
-
|
464
|
+
|
465
|
+
let :resumed_execution_plan do
|
466
|
+
failed_step = failed_execution_plan.steps.values.find do |step|
|
467
|
+
step.state == :error
|
468
|
+
end
|
469
|
+
world.persistence.load_action(failed_step).tap do |action|
|
470
|
+
action.input[:text] = "ok"
|
471
|
+
world.persistence.save_action(failed_step.execution_plan_id, action)
|
472
|
+
end
|
473
|
+
TestExecutionLog.setup
|
474
|
+
world.execute(failed_execution_plan.id).value
|
428
475
|
end
|
429
|
-
TestExecutionLog.setup
|
430
|
-
world.execute(failed_execution_plan.id).value
|
431
|
-
end
|
432
476
|
|
433
|
-
|
434
|
-
|
435
|
-
|
477
|
+
it "runs all the steps in the run flow" do
|
478
|
+
_(resumed_execution_plan.state).must_equal :stopped
|
479
|
+
_(resumed_execution_plan.result).must_equal :success
|
436
480
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
481
|
+
run_triages = TestExecutionLog.run.find_all do |action_class, input|
|
482
|
+
action_class == Support::CodeWorkflowExample::Triage
|
483
|
+
end
|
484
|
+
_(run_triages.size).must_equal 1
|
441
485
|
|
442
|
-
|
486
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
443
487
|
Dynflow::Flows::Concurrence
|
444
488
|
Dynflow::Flows::Sequence
|
445
489
|
4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
@@ -450,38 +494,38 @@ module Dynflow
|
|
450
494
|
16: UpdateIssue(success) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
451
495
|
18: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
452
496
|
EXECUTED_RUN_FLOW
|
453
|
-
|
454
|
-
|
455
|
-
end
|
456
|
-
|
457
|
-
describe "re-execution of run flow after fix in finalize phase" do
|
497
|
+
end
|
458
498
|
|
459
|
-
after do
|
460
|
-
TestExecutionLog.teardown
|
461
499
|
end
|
462
500
|
|
463
|
-
|
464
|
-
|
465
|
-
|
501
|
+
describe "re-execution of run flow after fix in finalize phase" do
|
502
|
+
|
503
|
+
after do
|
504
|
+
TestExecutionLog.teardown
|
466
505
|
end
|
467
|
-
|
468
|
-
|
469
|
-
|
506
|
+
|
507
|
+
let :resumed_execution_plan do
|
508
|
+
failed_step = finalize_failed_execution_plan.steps.values.find do |step|
|
509
|
+
step.state == :error
|
510
|
+
end
|
511
|
+
world.persistence.load_action(failed_step).tap do |action|
|
512
|
+
action.input[:text] = "ok"
|
513
|
+
world.persistence.save_action(failed_step.execution_plan_id, action)
|
514
|
+
end
|
515
|
+
TestExecutionLog.setup
|
516
|
+
world.execute(finalize_failed_execution_plan.id).value
|
470
517
|
end
|
471
|
-
TestExecutionLog.setup
|
472
|
-
world.execute(finalize_failed_execution_plan.id).value
|
473
|
-
end
|
474
518
|
|
475
|
-
|
476
|
-
|
477
|
-
|
519
|
+
it "runs all the steps in the finalize flow" do
|
520
|
+
_(resumed_execution_plan.state).must_equal :stopped
|
521
|
+
_(resumed_execution_plan.result).must_equal :success
|
478
522
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
523
|
+
run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
|
524
|
+
action_class == Support::CodeWorkflowExample::Triage
|
525
|
+
end
|
526
|
+
_(run_triages.size).must_equal 2
|
483
527
|
|
484
|
-
|
528
|
+
assert_finalize_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
485
529
|
Dynflow::Flows::Sequence
|
486
530
|
5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
487
531
|
10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
@@ -489,35 +533,35 @@ module Dynflow
|
|
489
533
|
19: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
490
534
|
20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling in finalize\"}]} --> {}
|
491
535
|
EXECUTED_RUN_FLOW
|
492
|
-
|
536
|
+
end
|
493
537
|
|
494
|
-
|
538
|
+
end
|
495
539
|
|
496
|
-
|
540
|
+
describe "re-execution of run flow after skipping" do
|
497
541
|
|
498
|
-
|
499
|
-
|
500
|
-
|
542
|
+
after do
|
543
|
+
TestExecutionLog.teardown
|
544
|
+
end
|
501
545
|
|
502
|
-
|
503
|
-
|
504
|
-
|
546
|
+
let :resumed_execution_plan do
|
547
|
+
failed_step = failed_execution_plan.steps.values.find do |step|
|
548
|
+
step.state == :error
|
549
|
+
end
|
550
|
+
failed_execution_plan.skip(failed_step)
|
551
|
+
TestExecutionLog.setup
|
552
|
+
world.execute(failed_execution_plan.id).value
|
505
553
|
end
|
506
|
-
failed_execution_plan.skip(failed_step)
|
507
|
-
TestExecutionLog.setup
|
508
|
-
world.execute(failed_execution_plan.id).value
|
509
|
-
end
|
510
554
|
|
511
|
-
|
512
|
-
|
513
|
-
|
555
|
+
it "runs all pending steps except skipped" do
|
556
|
+
_(resumed_execution_plan.state).must_equal :stopped
|
557
|
+
_(resumed_execution_plan.result).must_equal :warning
|
514
558
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
559
|
+
run_triages = TestExecutionLog.run.find_all do |action_class, input|
|
560
|
+
action_class == Support::CodeWorkflowExample::Triage
|
561
|
+
end
|
562
|
+
_(run_triages.size).must_equal 0
|
519
563
|
|
520
|
-
|
564
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
521
565
|
Dynflow::Flows::Concurrence
|
522
566
|
Dynflow::Flows::Sequence
|
523
567
|
4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
@@ -529,7 +573,7 @@ module Dynflow
|
|
529
573
|
18: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
|
530
574
|
EXECUTED_RUN_FLOW
|
531
575
|
|
532
|
-
|
576
|
+
assert_finalize_flow <<-FINALIZE_FLOW, resumed_execution_plan
|
533
577
|
Dynflow::Flows::Sequence
|
534
578
|
5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
535
579
|
10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
@@ -538,159 +582,159 @@ module Dynflow
|
|
538
582
|
20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"}]} --> {}
|
539
583
|
FINALIZE_FLOW
|
540
584
|
|
585
|
+
end
|
541
586
|
end
|
542
|
-
end
|
543
587
|
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
588
|
+
describe 'FlowManager' do
|
589
|
+
let :execution_plan do
|
590
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
591
|
+
end
|
548
592
|
|
549
|
-
|
593
|
+
let(:manager) { Director::FlowManager.new execution_plan, execution_plan.run_flow }
|
550
594
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
595
|
+
def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
|
596
|
+
if finished_step_id
|
597
|
+
step = manager.execution_plan.steps[finished_step_id]
|
598
|
+
next_steps = manager.cursor_index[step.id].what_is_next(step, success)
|
599
|
+
else
|
600
|
+
next_steps = manager.start
|
601
|
+
end
|
602
|
+
next_step_ids = next_steps.map(&:id)
|
603
|
+
assert_equal Set.new(expected_next_step_ids), Set.new(next_step_ids)
|
604
|
+
end
|
605
|
+
|
606
|
+
describe 'what_is_next' do
|
607
|
+
it 'returns next steps after required steps were finished' do
|
608
|
+
assert_next_steps([4, 13])
|
609
|
+
assert_next_steps([7], 4)
|
610
|
+
assert_next_steps([9], 7)
|
611
|
+
assert_next_steps([], 9)
|
612
|
+
assert_next_steps([16], 13)
|
613
|
+
assert_next_steps([18], 16)
|
614
|
+
assert_next_steps([], 18)
|
615
|
+
assert manager.done?
|
616
|
+
end
|
557
617
|
end
|
558
|
-
next_step_ids = next_steps.map(&:id)
|
559
|
-
assert_equal Set.new(expected_next_step_ids), Set.new(next_step_ids)
|
560
|
-
end
|
561
618
|
|
562
|
-
|
563
|
-
it 'returns next steps after required steps were finished' do
|
564
|
-
assert_next_steps([4, 13])
|
565
|
-
assert_next_steps([7], 4)
|
566
|
-
assert_next_steps([9], 7)
|
567
|
-
assert_next_steps([], 9)
|
568
|
-
assert_next_steps([16], 13)
|
569
|
-
assert_next_steps([18], 16)
|
570
|
-
assert_next_steps([], 18)
|
571
|
-
assert manager.done?
|
572
|
-
end
|
573
|
-
end
|
619
|
+
describe 'what_is_next with errors' do
|
574
620
|
|
575
|
-
|
621
|
+
it "doesn't return next steps if requirements failed" do
|
622
|
+
assert_next_steps([4, 13])
|
623
|
+
assert_next_steps([], 4, false)
|
624
|
+
end
|
576
625
|
|
577
|
-
|
578
|
-
|
579
|
-
|
626
|
+
it "is not done while other steps can be finished" do
|
627
|
+
assert_next_steps([4, 13])
|
628
|
+
assert_next_steps([], 4, false)
|
629
|
+
assert !manager.done?
|
630
|
+
assert_next_steps([], 13, false)
|
631
|
+
assert manager.done?
|
632
|
+
end
|
580
633
|
end
|
581
634
|
|
582
|
-
it "is not done while other steps can be finished" do
|
583
|
-
assert_next_steps([4, 13])
|
584
|
-
assert_next_steps([], 4, false)
|
585
|
-
assert !manager.done?
|
586
|
-
assert_next_steps([], 13, false)
|
587
|
-
assert manager.done?
|
588
|
-
end
|
589
635
|
end
|
590
636
|
|
591
|
-
|
637
|
+
describe 'Pool::JobStorage' do
|
638
|
+
FakeStep ||= Struct.new(:execution_plan_id)
|
592
639
|
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
storage.queue_size.must_equal(0)
|
600
|
-
storage.pop.must_be_nil
|
601
|
-
storage.pop.must_be_nil
|
602
|
-
|
603
|
-
storage.add s = FakeStep.new(1)
|
604
|
-
storage.queue_size.must_equal(1)
|
605
|
-
storage.pop.must_equal s
|
606
|
-
storage.must_be_empty
|
607
|
-
storage.pop.must_be_nil
|
608
|
-
|
609
|
-
storage.add s11 = FakeStep.new(1)
|
610
|
-
storage.add s12 = FakeStep.new(1)
|
611
|
-
storage.add s13 = FakeStep.new(1)
|
612
|
-
storage.add s21 = FakeStep.new(2)
|
613
|
-
storage.add s22 = FakeStep.new(2)
|
614
|
-
storage.add s31 = FakeStep.new(3)
|
615
|
-
|
616
|
-
storage.queue_size(1).must_equal(3)
|
617
|
-
storage.queue_size(4).must_equal(0)
|
618
|
-
storage.queue_size.must_equal(6)
|
619
|
-
|
620
|
-
storage.pop.must_equal s11
|
621
|
-
storage.pop.must_equal s12
|
622
|
-
storage.pop.must_equal s13
|
623
|
-
storage.pop.must_equal s21
|
624
|
-
storage.pop.must_equal s22
|
625
|
-
storage.pop.must_equal s31
|
626
|
-
|
627
|
-
storage.must_be_empty
|
628
|
-
storage.queue_size.must_equal(0)
|
629
|
-
storage.pop.must_be_nil
|
630
|
-
end
|
631
|
-
end
|
640
|
+
let(:storage) { Dynflow::Executors::Parallel::Pool::JobStorage.new }
|
641
|
+
it do
|
642
|
+
_(storage).must_be_empty
|
643
|
+
_(storage.queue_size).must_equal(0)
|
644
|
+
assert_nil storage.pop
|
645
|
+
assert_nil storage.pop
|
632
646
|
|
633
|
-
|
647
|
+
storage.add s = FakeStep.new(1)
|
648
|
+
_(storage.queue_size).must_equal(1)
|
649
|
+
_(storage.pop).must_equal s
|
650
|
+
_(storage).must_be_empty
|
651
|
+
assert_nil storage.pop
|
634
652
|
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
653
|
+
storage.add s11 = FakeStep.new(1)
|
654
|
+
storage.add s12 = FakeStep.new(1)
|
655
|
+
storage.add s13 = FakeStep.new(1)
|
656
|
+
storage.add s21 = FakeStep.new(2)
|
657
|
+
storage.add s22 = FakeStep.new(2)
|
658
|
+
storage.add s31 = FakeStep.new(3)
|
659
|
+
|
660
|
+
_(storage.queue_size(1)).must_equal(3)
|
661
|
+
_(storage.queue_size(4)).must_equal(0)
|
662
|
+
_(storage.queue_size).must_equal(6)
|
663
|
+
|
664
|
+
_(storage.pop).must_equal s11
|
665
|
+
_(storage.pop).must_equal s12
|
666
|
+
_(storage.pop).must_equal s13
|
667
|
+
_(storage.pop).must_equal s21
|
668
|
+
_(storage.pop).must_equal s22
|
669
|
+
_(storage.pop).must_equal s31
|
670
|
+
|
671
|
+
_(storage).must_be_empty
|
672
|
+
_(storage.queue_size).must_equal(0)
|
673
|
+
assert_nil storage.pop
|
674
|
+
end
|
649
675
|
end
|
676
|
+
|
650
677
|
end
|
651
678
|
|
652
|
-
describe '
|
653
|
-
|
654
|
-
|
655
|
-
|
679
|
+
describe 'termination' do
|
680
|
+
let(:world) { WorldFactory.create_world }
|
681
|
+
|
682
|
+
it 'waits for currently running actions' do
|
683
|
+
$slow_actions_done = 0
|
684
|
+
running = world.trigger(Support::DummyExample::Slow, 1)
|
685
|
+
suspended = world.trigger(Support::DummyExample::DeprecatedEventedAction, :timeout => 3 )
|
686
|
+
sleep 0.2
|
656
687
|
world.terminate.wait
|
657
|
-
|
688
|
+
_($slow_actions_done).must_equal 1
|
689
|
+
[running, suspended].each do |triggered|
|
690
|
+
plan = world.persistence.load_execution_plan(triggered.id)
|
691
|
+
_(plan.state).must_equal :paused
|
692
|
+
_(plan.result).must_equal :pending
|
693
|
+
end
|
658
694
|
end
|
659
695
|
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
696
|
+
describe 'before_termination hooks' do
|
697
|
+
it 'runs before temination hooks' do
|
698
|
+
hook_run = false
|
699
|
+
world.before_termination { hook_run = true }
|
700
|
+
world.terminate.wait
|
701
|
+
assert hook_run
|
702
|
+
end
|
703
|
+
|
704
|
+
it 'continues when some hook fails' do
|
705
|
+
run_hooks, failed_hooks = [], []
|
706
|
+
world.before_termination { run_hooks << 1 }
|
707
|
+
world.before_termination { run_hooks << 2; failed_hooks << 2; raise 'error' }
|
708
|
+
world.before_termination { run_hooks << 3 }
|
709
|
+
world.terminate.wait
|
710
|
+
_(run_hooks).must_equal [1, 2, 3]
|
711
|
+
_(failed_hooks).must_equal [2]
|
712
|
+
end
|
668
713
|
end
|
669
|
-
end
|
670
714
|
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
715
|
+
it 'does not accept new work' do
|
716
|
+
assert world.terminate.wait
|
717
|
+
::Dynflow::Coordinator::PlanningLock.any_instance.stubs(:validate!)
|
718
|
+
result = world.trigger(Support::DummyExample::Slow, 0.02)
|
719
|
+
_(result).must_be :planned?
|
720
|
+
result.finished.wait
|
721
|
+
assert result.finished.rejected?
|
722
|
+
_(result.finished.reason).must_be_kind_of Concurrent::Actor::ActorTerminated
|
723
|
+
end
|
680
724
|
|
681
|
-
|
682
|
-
|
683
|
-
|
725
|
+
it 'it terminates when no work right after initialization' do
|
726
|
+
assert world.terminate.wait
|
727
|
+
end
|
684
728
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
729
|
+
it 'second terminate works' do
|
730
|
+
assert world.terminate.wait
|
731
|
+
assert world.terminate.wait
|
732
|
+
end
|
689
733
|
|
690
|
-
|
691
|
-
|
734
|
+
it 'second terminate works concurrently' do
|
735
|
+
assert [world.terminate, world.terminate].map(&:value).all?
|
736
|
+
end
|
692
737
|
end
|
693
738
|
end
|
694
|
-
|
695
739
|
end
|
696
740
|
end
|