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.
Files changed (225) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +3 -4
  4. data/Dockerfile +9 -0
  5. data/Gemfile +6 -0
  6. data/Rakefile +1 -0
  7. data/doc/pages/Gemfile +1 -0
  8. data/doc/pages/Rakefile +1 -0
  9. data/doc/pages/plugins/alert_block.rb +1 -0
  10. data/doc/pages/plugins/div_tag.rb +1 -0
  11. data/doc/pages/plugins/graphviz.rb +1 -0
  12. data/doc/pages/plugins/plantuml.rb +1 -0
  13. data/doc/pages/plugins/play.rb +1 -0
  14. data/doc/pages/plugins/tags.rb +1 -0
  15. data/doc/pages/plugins/toc.rb +1 -0
  16. data/docker-compose.yml +41 -0
  17. data/dynflow.gemspec +1 -0
  18. data/examples/clock_benchmark.rb +1 -0
  19. data/examples/example_helper.rb +19 -2
  20. data/examples/future_execution.rb +2 -1
  21. data/examples/memory_limit_watcher.rb +1 -0
  22. data/examples/orchestrate.rb +4 -5
  23. data/examples/orchestrate_evented.rb +3 -2
  24. data/examples/remote_executor.rb +68 -0
  25. data/examples/singletons.rb +4 -3
  26. data/examples/sub_plan_concurrency_control.rb +2 -1
  27. data/examples/sub_plans.rb +3 -2
  28. data/examples/termination.rb +1 -0
  29. data/lib/dynflow.rb +20 -0
  30. data/lib/dynflow/action.rb +28 -3
  31. data/lib/dynflow/action/cancellable.rb +1 -0
  32. data/lib/dynflow/action/format.rb +1 -0
  33. data/lib/dynflow/action/missing.rb +1 -0
  34. data/lib/dynflow/action/polling.rb +3 -1
  35. data/lib/dynflow/action/progress.rb +1 -0
  36. data/lib/dynflow/action/rescue.rb +1 -0
  37. data/lib/dynflow/action/singleton.rb +1 -0
  38. data/lib/dynflow/action/suspended.rb +9 -2
  39. data/lib/dynflow/action/timeouts.rb +2 -1
  40. data/lib/dynflow/action/with_bulk_sub_plans.rb +2 -1
  41. data/lib/dynflow/action/with_polling_sub_plans.rb +7 -5
  42. data/lib/dynflow/action/with_sub_plans.rb +1 -0
  43. data/lib/dynflow/active_job/queue_adapter.rb +1 -0
  44. data/lib/dynflow/actor.rb +13 -5
  45. data/lib/dynflow/actors.rb +1 -0
  46. data/lib/dynflow/actors/execution_plan_cleaner.rb +1 -0
  47. data/lib/dynflow/clock.rb +27 -47
  48. data/lib/dynflow/config.rb +11 -2
  49. data/lib/dynflow/connectors.rb +1 -0
  50. data/lib/dynflow/connectors/abstract.rb +1 -0
  51. data/lib/dynflow/connectors/database.rb +1 -0
  52. data/lib/dynflow/connectors/direct.rb +1 -0
  53. data/lib/dynflow/coordinator.rb +1 -0
  54. data/lib/dynflow/coordinator_adapters.rb +1 -0
  55. data/lib/dynflow/coordinator_adapters/abstract.rb +1 -0
  56. data/lib/dynflow/coordinator_adapters/sequel.rb +1 -0
  57. data/lib/dynflow/dead_letter_silencer.rb +2 -0
  58. data/lib/dynflow/debug/telemetry/persistence.rb +1 -0
  59. data/lib/dynflow/delayed_executors.rb +1 -0
  60. data/lib/dynflow/delayed_executors/abstract.rb +1 -0
  61. data/lib/dynflow/delayed_executors/abstract_core.rb +1 -0
  62. data/lib/dynflow/delayed_executors/polling.rb +1 -0
  63. data/lib/dynflow/delayed_plan.rb +1 -0
  64. data/lib/dynflow/director.rb +80 -15
  65. data/lib/dynflow/director/execution_plan_manager.rb +17 -3
  66. data/lib/dynflow/director/flow_manager.rb +1 -0
  67. data/lib/dynflow/director/{work_queue.rb → queue_hash.rb} +9 -8
  68. data/lib/dynflow/director/running_steps_manager.rb +55 -18
  69. data/lib/dynflow/director/sequence_cursor.rb +1 -0
  70. data/lib/dynflow/director/sequential_manager.rb +12 -2
  71. data/lib/dynflow/dispatcher.rb +4 -2
  72. data/lib/dynflow/dispatcher/abstract.rb +1 -0
  73. data/lib/dynflow/dispatcher/client_dispatcher.rb +6 -4
  74. data/lib/dynflow/dispatcher/executor_dispatcher.rb +13 -1
  75. data/lib/dynflow/errors.rb +1 -0
  76. data/lib/dynflow/execution_history.rb +1 -0
  77. data/lib/dynflow/execution_plan.rb +3 -2
  78. data/lib/dynflow/execution_plan/dependency_graph.rb +1 -0
  79. data/lib/dynflow/execution_plan/hooks.rb +1 -0
  80. data/lib/dynflow/execution_plan/output_reference.rb +2 -1
  81. data/lib/dynflow/execution_plan/steps.rb +1 -0
  82. data/lib/dynflow/execution_plan/steps/abstract.rb +10 -5
  83. data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +2 -0
  84. data/lib/dynflow/execution_plan/steps/error.rb +1 -0
  85. data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -0
  86. data/lib/dynflow/execution_plan/steps/plan_step.rb +1 -0
  87. data/lib/dynflow/execution_plan/steps/run_step.rb +1 -0
  88. data/lib/dynflow/executors.rb +1 -1
  89. data/lib/dynflow/executors/abstract/core.rb +132 -0
  90. data/lib/dynflow/executors/parallel.rb +24 -11
  91. data/lib/dynflow/executors/parallel/core.rb +10 -91
  92. data/lib/dynflow/executors/parallel/pool.rb +4 -2
  93. data/lib/dynflow/executors/parallel/worker.rb +2 -1
  94. data/lib/dynflow/executors/sidekiq/core.rb +121 -0
  95. data/lib/dynflow/executors/sidekiq/internal_job_base.rb +24 -0
  96. data/lib/dynflow/executors/sidekiq/orchestrator_jobs.rb +60 -0
  97. data/lib/dynflow/executors/sidekiq/redis_locking.rb +69 -0
  98. data/lib/dynflow/executors/sidekiq/serialization.rb +33 -0
  99. data/lib/dynflow/executors/sidekiq/worker_jobs.rb +42 -0
  100. data/lib/dynflow/flows.rb +1 -0
  101. data/lib/dynflow/flows/abstract.rb +1 -0
  102. data/lib/dynflow/flows/abstract_composed.rb +1 -0
  103. data/lib/dynflow/flows/atom.rb +1 -0
  104. data/lib/dynflow/flows/concurrence.rb +1 -0
  105. data/lib/dynflow/flows/sequence.rb +1 -0
  106. data/lib/dynflow/logger_adapters.rb +1 -0
  107. data/lib/dynflow/logger_adapters/abstract.rb +1 -0
  108. data/lib/dynflow/logger_adapters/delegator.rb +1 -0
  109. data/lib/dynflow/logger_adapters/formatters.rb +1 -0
  110. data/lib/dynflow/logger_adapters/formatters/abstract.rb +1 -0
  111. data/lib/dynflow/logger_adapters/formatters/exception.rb +1 -0
  112. data/lib/dynflow/logger_adapters/simple.rb +1 -0
  113. data/lib/dynflow/middleware.rb +1 -0
  114. data/lib/dynflow/middleware/common/singleton.rb +1 -0
  115. data/lib/dynflow/middleware/common/transaction.rb +1 -0
  116. data/lib/dynflow/middleware/register.rb +1 -0
  117. data/lib/dynflow/middleware/resolver.rb +1 -0
  118. data/lib/dynflow/middleware/stack.rb +1 -0
  119. data/lib/dynflow/middleware/world.rb +1 -0
  120. data/lib/dynflow/persistence.rb +3 -2
  121. data/lib/dynflow/persistence_adapters.rb +1 -0
  122. data/lib/dynflow/persistence_adapters/abstract.rb +1 -0
  123. data/lib/dynflow/persistence_adapters/sequel.rb +10 -7
  124. data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +1 -0
  125. data/lib/dynflow/persistence_adapters/sequel_migrations/002_incremental_progress.rb +1 -0
  126. data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +1 -0
  127. data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +1 -0
  128. data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +1 -0
  129. data/lib/dynflow/persistence_adapters/sequel_migrations/006_fix_data_length.rb +1 -0
  130. data/lib/dynflow/persistence_adapters/sequel_migrations/007_future_execution.rb +1 -0
  131. data/lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb +1 -0
  132. data/lib/dynflow/persistence_adapters/sequel_migrations/009_fix_mysql_data_length.rb +1 -0
  133. data/lib/dynflow/persistence_adapters/sequel_migrations/010_add_execution_plans_label.rb +1 -0
  134. data/lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb +1 -0
  135. data/lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb +1 -0
  136. data/lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb +1 -0
  137. data/lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb +1 -0
  138. data/lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb +1 -0
  139. data/lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb +1 -0
  140. data/lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb +1 -0
  141. data/lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb +1 -0
  142. data/lib/dynflow/persistence_adapters/sequel_migrations/019_update_mysql_time_precision.rb +48 -0
  143. data/lib/dynflow/rails.rb +1 -0
  144. data/lib/dynflow/rails/configuration.rb +6 -3
  145. data/lib/dynflow/rails/daemon.rb +1 -0
  146. data/lib/dynflow/round_robin.rb +1 -0
  147. data/lib/dynflow/semaphores.rb +1 -0
  148. data/lib/dynflow/semaphores/abstract.rb +1 -0
  149. data/lib/dynflow/semaphores/aggregating.rb +1 -0
  150. data/lib/dynflow/semaphores/dummy.rb +1 -0
  151. data/lib/dynflow/semaphores/stateful.rb +1 -0
  152. data/lib/dynflow/serializable.rb +13 -4
  153. data/lib/dynflow/serializer.rb +24 -0
  154. data/lib/dynflow/serializers.rb +1 -0
  155. data/lib/dynflow/serializers/abstract.rb +1 -0
  156. data/lib/dynflow/serializers/noop.rb +1 -0
  157. data/lib/dynflow/stateful.rb +1 -0
  158. data/lib/dynflow/telemetry.rb +1 -0
  159. data/lib/dynflow/telemetry_adapters/abstract.rb +1 -0
  160. data/lib/dynflow/telemetry_adapters/dummy.rb +1 -0
  161. data/lib/dynflow/telemetry_adapters/statsd.rb +1 -0
  162. data/lib/dynflow/testing.rb +1 -0
  163. data/lib/dynflow/testing/assertions.rb +6 -5
  164. data/lib/dynflow/testing/dummy_execution_plan.rb +1 -0
  165. data/lib/dynflow/testing/dummy_executor.rb +19 -2
  166. data/lib/dynflow/testing/dummy_planned_action.rb +1 -0
  167. data/lib/dynflow/testing/dummy_step.rb +3 -1
  168. data/lib/dynflow/testing/dummy_world.rb +9 -0
  169. data/lib/dynflow/testing/factories.rb +6 -1
  170. data/lib/dynflow/testing/in_thread_executor.rb +22 -3
  171. data/lib/dynflow/testing/in_thread_world.rb +9 -0
  172. data/lib/dynflow/testing/managed_clock.rb +1 -0
  173. data/lib/dynflow/testing/mimic.rb +1 -0
  174. data/lib/dynflow/throttle_limiter.rb +1 -0
  175. data/lib/dynflow/transaction_adapters.rb +1 -0
  176. data/lib/dynflow/transaction_adapters/abstract.rb +1 -0
  177. data/lib/dynflow/transaction_adapters/active_record.rb +1 -0
  178. data/lib/dynflow/transaction_adapters/none.rb +1 -0
  179. data/lib/dynflow/utils.rb +1 -0
  180. data/lib/dynflow/utils/indifferent_hash.rb +1 -0
  181. data/lib/dynflow/utils/priority_queue.rb +1 -0
  182. data/lib/dynflow/version.rb +2 -1
  183. data/lib/dynflow/watchers/memory_consumption_watcher.rb +1 -0
  184. data/lib/dynflow/web.rb +1 -0
  185. data/lib/dynflow/web/console.rb +1 -0
  186. data/lib/dynflow/web/console_helpers.rb +1 -0
  187. data/lib/dynflow/web/filtering_helpers.rb +1 -0
  188. data/lib/dynflow/web/world_helpers.rb +1 -0
  189. data/lib/dynflow/web_console.rb +1 -0
  190. data/lib/dynflow/world.rb +11 -1
  191. data/lib/dynflow/world/invalidation.rb +7 -1
  192. data/test/abnormal_states_recovery_test.rb +41 -40
  193. data/test/action_test.rb +160 -110
  194. data/test/activejob_adapter_test.rb +1 -0
  195. data/test/batch_sub_tasks_test.rb +12 -11
  196. data/test/clock_test.rb +2 -1
  197. data/test/concurrency_control_test.rb +20 -19
  198. data/test/coordinator_test.rb +20 -21
  199. data/test/daemon_test.rb +2 -1
  200. data/test/dead_letter_silencer_test.rb +9 -7
  201. data/test/dispatcher_test.rb +2 -1
  202. data/test/execution_plan_cleaner_test.rb +13 -12
  203. data/test/execution_plan_hooks_test.rb +3 -2
  204. data/test/execution_plan_test.rb +33 -32
  205. data/test/executor_test.rb +533 -489
  206. data/test/future_execution_test.rb +45 -44
  207. data/test/memory_cosumption_watcher_test.rb +5 -4
  208. data/test/middleware_test.rb +55 -54
  209. data/test/persistence_test.rb +56 -53
  210. data/test/rescue_test.rb +36 -35
  211. data/test/round_robin_test.rb +13 -12
  212. data/test/semaphores_test.rb +31 -30
  213. data/test/support/code_workflow_example.rb +1 -0
  214. data/test/support/dummy_example.rb +14 -1
  215. data/test/support/middleware_example.rb +2 -1
  216. data/test/support/rails/config/environment.rb +1 -0
  217. data/test/support/rescue_example.rb +1 -0
  218. data/test/support/test_execution_log.rb +1 -0
  219. data/test/test_helper.rb +18 -17
  220. data/test/testing_test.rb +45 -44
  221. data/test/utils_test.rb +18 -17
  222. data/test/web_console_test.rb +1 -0
  223. data/test/world_test.rb +7 -6
  224. metadata +13 -4
  225. data/lib/dynflow/executors/abstract.rb +0 -40
@@ -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?.must_equal true
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
@@ -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
- describe "executor" do
22
+ [::Dynflow::Executors::Parallel::Core, ::Dynflow::Executors::Sidekiq::Core].each do |executor|
23
+ describe executor do
24
+ include PlanAssertions
8
25
 
9
- include PlanAssertions
26
+ after do
27
+ ::Dynflow.instance_variable_set('@process_world', nil)
28
+ end
10
29
 
11
- let(:world) { WorldFactory.create_world }
30
+ before do
31
+ executor.any_instance.stubs(:begin_startup!)
32
+ end
12
33
 
13
- let :issues_data do
14
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
15
- { 'author' => 'John Doe', 'text' => 'Internal server error' }]
16
- end
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
- let :failing_issues_data do
19
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
20
- { 'author' => 'John Doe', 'text' => 'trolling' }]
21
- end
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
- let :finalize_failing_issues_data do
24
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
25
- { 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
26
- end
45
+ let :failing_issues_data do
46
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
47
+ { 'author' => 'John Doe', 'text' => 'trolling' }]
48
+ end
27
49
 
28
- let :failed_execution_plan do
29
- plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
30
- plan = world.execute(plan.id).value
31
- plan.state.must_equal :paused
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
- let :finalize_failed_execution_plan do
36
- plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
37
- plan = world.execute(plan.id).value
38
- plan.state.must_equal :paused
39
- plan
40
- end
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
- let :persisted_plan do
43
- world.persistence.load_execution_plan(execution_plan.id)
44
- end
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
- let :executor_class do
47
- Executors::Parallel
48
- end
69
+ let :persisted_plan do
70
+ world.persistence.load_execution_plan(execution_plan.id)
71
+ end
49
72
 
50
- describe "execution plan state" do
73
+ describe "execution plan state" do
51
74
 
52
- describe "after successful planning" do
75
+ describe "after successful planning" do
53
76
 
54
- let :execution_plan do
55
- world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
56
- end
77
+ let :execution_plan do
78
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
79
+ end
57
80
 
58
- it "is pending" do
59
- execution_plan.state.must_equal :planned
60
- end
81
+ it "is pending" do
82
+ _(execution_plan.state).must_equal :planned
83
+ end
61
84
 
62
- describe "when finished successfully" do
63
- it "is stopped" do
64
- world.execute(execution_plan.id).value.tap do |plan|
65
- plan.state.must_equal :stopped
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
- describe "when finished with error" do
71
- it "is paused" do
72
- world.execute(failed_execution_plan.id).value.tap do |plan|
73
- plan.state.must_equal :paused
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
- describe "after error in planning" do
102
+ describe "after error in planning" do
80
103
 
81
- class FailingAction < Dynflow::Action
82
- def plan
83
- raise "I failed"
104
+ class FailingAction < Dynflow::Action
105
+ def plan
106
+ raise "I failed"
107
+ end
84
108
  end
85
- end
86
109
 
87
- let :execution_plan do
88
- world.plan(FailingAction)
89
- end
110
+ let :execution_plan do
111
+ world.plan(FailingAction)
112
+ end
90
113
 
91
- it "is stopped" do
92
- execution_plan.state.must_equal :stopped
93
- end
114
+ it "is stopped" do
115
+ _(execution_plan.state).must_equal :stopped
116
+ end
94
117
 
95
- end
118
+ end
96
119
 
97
- describe "when being executed" do
98
- include TestHelpers
120
+ describe "when being executed" do
121
+ include TestHelpers
99
122
 
100
- let :execution_plan do
101
- world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
102
- end
123
+ let :execution_plan do
124
+ world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
125
+ end
103
126
 
104
- before do
105
- TestPause.setup
106
- @execution = world.execute(execution_plan.id)
107
- end
127
+ before do
128
+ TestPause.setup
129
+ @execution = world.execute(execution_plan.id)
130
+ end
108
131
 
109
- after do
110
- @execution.wait
111
- TestPause.teardown
112
- end
132
+ after do
133
+ @execution.wait
134
+ TestPause.teardown
135
+ end
113
136
 
114
- it "is running" do
115
- TestPause.when_paused do
116
- plan = world.persistence.load_execution_plan(execution_plan.id)
117
- plan.state.must_equal :running
118
- triage = plan.run_steps.find do |s|
119
- s.action_class == Support::CodeWorkflowExample::Triage
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
- it "fails when trying to execute again" do
129
- TestPause.when_paused do
130
- assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
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
- it "handles when the execution plan is deleted" do
135
- TestPause.when_paused do
136
- world.persistence.delete_execution_plans(uuid: [execution_plan.id])
137
- end
138
- director = get_director(world)
139
- wait_for('execution plan removed from executor') do
140
- !director.current_execution_plan_ids.include?(execution_plan.id)
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
- describe "execution of run flow" do
168
+ describe "execution of run flow" do
148
169
 
149
- before do
150
- TestExecutionLog.setup
151
- end
170
+ before do
171
+ TestExecutionLog.setup
172
+ end
152
173
 
153
- let :result do
154
- world.execute(execution_plan.id).value!
155
- end
174
+ let :result do
175
+ world.execute(execution_plan.id).value!
176
+ end
156
177
 
157
- after do
158
- TestExecutionLog.teardown
159
- end
178
+ after do
179
+ TestExecutionLog.teardown
180
+ end
160
181
 
161
- def persisted_plan
162
- result
163
- super
164
- end
182
+ def persisted_plan
183
+ result
184
+ super
185
+ end
165
186
 
166
- describe 'cancellable action' do
167
- describe 'successful' do
168
- let :execution_plan do
169
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
170
- end
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
- it "doesn't cause problems" do
173
- result.result.must_equal :success
174
- result.state.must_equal :stopped
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
- describe 'canceled' do
179
- let :execution_plan do
180
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
181
- end
199
+ describe 'canceled' do
200
+ let :execution_plan do
201
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
202
+ end
182
203
 
183
- it 'cancels' do
184
- result.result.must_equal :success
185
- result.state.must_equal :stopped
186
- action = world.persistence.load_action result.steps[2]
187
- action.output[:task][:progress].must_equal 30
188
- action.output[:task][:cancelled].must_equal true
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
- describe 'canceled failed' do
193
- let :execution_plan do
194
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
195
- end
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
- it 'fails' do
198
- result.result.must_equal :error
199
- result.state.must_equal :paused
200
- step = result.steps[2]
201
- step.error.message.must_equal 'action cancelled'
202
- action = world.persistence.load_action step
203
- action.output[:task][:progress].must_equal 30
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
- describe 'suspended action' do
209
- describe 'handling errors in setup' do
210
- let :execution_plan do
211
- world.plan(Support::DummyExample::Polling,
212
- external_task_id: '123',
213
- text: 'troll setup')
214
- end
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
- it 'fails' do
217
- assert_equal :error, result.result
218
- assert_equal :paused, result.state
219
- assert_equal :error, result.run_steps.first.state
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
- describe 'running' do
224
- let :execution_plan do
225
- world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
226
- end
244
+ describe 'events' do
245
+ include TestHelpers
227
246
 
228
- it "doesn't cause problems" do
229
- result.result.must_equal :success
230
- result.state.must_equal :stopped
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
- it 'does set times' do
234
- result.started_at.wont_be_nil
235
- result.ended_at.wont_be_nil
236
- result.execution_time.must_be :<, result.real_time
267
+ describe 'running' do
268
+ let :execution_plan do
269
+ world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
270
+ end
237
271
 
238
- step_sum = result.steps.values.map(&:execution_time).reduce(:+)
272
+ it "doesn't cause problems" do
273
+ _(result.result).must_equal :success
274
+ _(result.state).must_equal :stopped
275
+ end
239
276
 
240
- # Storing floats can lead to slight deviations, 1ns precision should be enough
241
- result.execution_time.must_be_close_to step_sum, 0.000_001
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
- plan_step = result.steps[1]
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
- run_step = result.steps[2]
249
- run_step.started_at.wont_be_nil
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
- describe 'progress' do
256
- before do
257
- TestPause.setup
258
- @running_plan = world.execute(execution_plan.id)
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
- after do
262
- @running_plan.wait
263
- TestPause.teardown
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 'plan with one action' do
267
- let :execution_plan do
268
- world.plan(Support::DummyExample::Polling,
269
- { external_task_id: '123',
270
- text: 'pause in progress 20%' })
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
- it 'determines the progress of the execution plan in percents' do
274
- TestPause.when_paused do
275
- plan = world.persistence.load_execution_plan(execution_plan.id)
276
- plan.progress.round(2).must_equal 0.2
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 'plan with more action' do
341
+ describe 'works when resumed after error' do
282
342
  let :execution_plan do
283
- world.plan(Support::DummyExample::WeightedPolling,
343
+ world.plan(Support::DummyExample::Polling,
284
344
  { external_task_id: '123',
285
- text: 'pause in progress 20%' })
345
+ text: 'troll progress' })
286
346
  end
287
347
 
288
- it 'takes the steps weight in account' do
289
- TestPause.when_paused do
290
- plan = world.persistence.load_execution_plan(execution_plan.id)
291
- plan.progress.round(2).must_equal 0.42
292
- end
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 'works when resumed after error' do
362
+ describe "action with empty flows" do
363
+
298
364
  let :execution_plan do
299
- world.plan(Support::DummyExample::Polling,
300
- { external_task_id: '123',
301
- text: 'troll progress' })
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
- specify do
305
- assert_equal :paused, result.state
306
- assert_equal :error, result.result
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
- let :execution_plan do
321
- world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
322
- assert_equal plan.run_flow.size, 0
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
- it 'will not run again' do
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
- end
340
-
341
- describe 'action with empty run flow but some finalize flow' do
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
- let :execution_plan do
344
- world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
345
- assert_equal plan.run_flow.size, 0
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
- end
356
-
357
- describe 'running' do
358
- let :execution_plan do
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
- it "runs all the steps in the run flow" do
363
- assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
364
- Dynflow::Flows::Concurrence
365
- Dynflow::Flows::Sequence
366
- 4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
367
- 7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
368
- 9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
369
- Dynflow::Flows::Sequence
370
- 13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
371
- 16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
372
- 18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
373
- EXECUTED_RUN_FLOW
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
- after do
387
- TestExecutionLog.teardown
388
- end
389
-
390
- describe "when run flow successful" do
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
- it "runs all the steps in the finalize flow" do
396
- assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
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
- describe "when run flow failed" do
404
- let :execution_plan do
405
- failed_execution_plan
406
- end
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
- it "doesn't run the steps in the finalize flow" do
409
- TestExecutionLog.finalize.size.must_equal 0
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
- end
448
+ describe "when run flow failed" do
449
+ let :execution_plan do
450
+ failed_execution_plan
451
+ end
414
452
 
415
- describe "re-execution of run flow after fix in run phase" do
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
- let :resumed_execution_plan do
422
- failed_step = failed_execution_plan.steps.values.find do |step|
423
- step.state == :error
460
+ describe "re-execution of run flow after fix in run phase" do
461
+ after do
462
+ TestExecutionLog.teardown
424
463
  end
425
- world.persistence.load_action(failed_step).tap do |action|
426
- action.input[:text] = "ok"
427
- world.persistence.save_action(failed_step.execution_plan_id, action)
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
- it "runs all the steps in the run flow" do
434
- resumed_execution_plan.state.must_equal :stopped
435
- resumed_execution_plan.result.must_equal :success
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
- run_triages = TestExecutionLog.run.find_all do |action_class, input|
438
- action_class == Support::CodeWorkflowExample::Triage
439
- end
440
- run_triages.size.must_equal 1
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
- assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
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
- end
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
- let :resumed_execution_plan do
464
- failed_step = finalize_failed_execution_plan.steps.values.find do |step|
465
- step.state == :error
501
+ describe "re-execution of run flow after fix in finalize phase" do
502
+
503
+ after do
504
+ TestExecutionLog.teardown
466
505
  end
467
- world.persistence.load_action(failed_step).tap do |action|
468
- action.input[:text] = "ok"
469
- world.persistence.save_action(failed_step.execution_plan_id, action)
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
- it "runs all the steps in the finalize flow" do
476
- resumed_execution_plan.state.must_equal :stopped
477
- resumed_execution_plan.result.must_equal :success
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
- run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
480
- action_class == Support::CodeWorkflowExample::Triage
481
- end
482
- run_triages.size.must_equal 2
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
- assert_finalize_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
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
- end
536
+ end
493
537
 
494
- end
538
+ end
495
539
 
496
- describe "re-execution of run flow after skipping" do
540
+ describe "re-execution of run flow after skipping" do
497
541
 
498
- after do
499
- TestExecutionLog.teardown
500
- end
542
+ after do
543
+ TestExecutionLog.teardown
544
+ end
501
545
 
502
- let :resumed_execution_plan do
503
- failed_step = failed_execution_plan.steps.values.find do |step|
504
- step.state == :error
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
- it "runs all pending steps except skipped" do
512
- resumed_execution_plan.state.must_equal :stopped
513
- resumed_execution_plan.result.must_equal :warning
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
- run_triages = TestExecutionLog.run.find_all do |action_class, input|
516
- action_class == Support::CodeWorkflowExample::Triage
517
- end
518
- run_triages.size.must_equal 0
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
- assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
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
- assert_finalize_flow <<-FINALIZE_FLOW, resumed_execution_plan
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
- describe 'FlowManager' do
545
- let :execution_plan do
546
- world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
547
- end
588
+ describe 'FlowManager' do
589
+ let :execution_plan do
590
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
591
+ end
548
592
 
549
- let(:manager) { Director::FlowManager.new execution_plan, execution_plan.run_flow }
593
+ let(:manager) { Director::FlowManager.new execution_plan, execution_plan.run_flow }
550
594
 
551
- def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
552
- if finished_step_id
553
- step = manager.execution_plan.steps[finished_step_id]
554
- next_steps = manager.cursor_index[step.id].what_is_next(step, success)
555
- else
556
- next_steps = manager.start
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
- describe 'what_is_next' do
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
- describe 'what_is_next with errors' do
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
- it "doesn't return next steps if requirements failed" do
578
- assert_next_steps([4, 13])
579
- assert_next_steps([], 4, false)
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
- end
637
+ describe 'Pool::JobStorage' do
638
+ FakeStep ||= Struct.new(:execution_plan_id)
592
639
 
593
- describe 'Pool::JobStorage' do
594
- FakeStep ||= Struct.new(:execution_plan_id)
595
-
596
- let(:storage) { Dynflow::Executors::Parallel::Pool::JobStorage.new }
597
- it do
598
- storage.must_be_empty
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
- end
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
- describe 'termination' do
636
- let(:world) { WorldFactory.create_world }
637
-
638
- it 'waits for currently running actions' do
639
- $slow_actions_done = 0
640
- running = world.trigger(Support::DummyExample::Slow, 1)
641
- suspended = world.trigger(Support::DummyExample::EventedAction, :timeout => 3 )
642
- sleep 0.2
643
- world.terminate.wait
644
- $slow_actions_done.must_equal 1
645
- [running, suspended].each do |triggered|
646
- plan = world.persistence.load_execution_plan(triggered.id)
647
- plan.state.must_equal :paused
648
- plan.result.must_equal :pending
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 'before_termination hooks' do
653
- it 'runs before temination hooks' do
654
- hook_run = false
655
- world.before_termination { hook_run = true }
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
- assert hook_run
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
- it 'continues when some hook fails' do
661
- run_hooks, failed_hooks = [], []
662
- world.before_termination { run_hooks << 1 }
663
- world.before_termination { run_hooks << 2; failed_hooks << 2; raise 'error' }
664
- world.before_termination { run_hooks << 3 }
665
- world.terminate.wait
666
- run_hooks.must_equal [1, 2, 3]
667
- failed_hooks.must_equal [2]
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
- it 'does not accept new work' do
672
- assert world.terminate.wait
673
- ::Dynflow::Coordinator::PlanningLock.any_instance.stubs(:validate!)
674
- result = world.trigger(Support::DummyExample::Slow, 0.02)
675
- result.must_be :planned?
676
- result.finished.wait
677
- assert result.finished.rejected?
678
- result.finished.reason.must_be_kind_of Concurrent::Actor::ActorTerminated
679
- end
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
- it 'it terminates when no work right after initialization' do
682
- assert world.terminate.wait
683
- end
725
+ it 'it terminates when no work right after initialization' do
726
+ assert world.terminate.wait
727
+ end
684
728
 
685
- it 'second terminate works' do
686
- assert world.terminate.wait
687
- assert world.terminate.wait
688
- end
729
+ it 'second terminate works' do
730
+ assert world.terminate.wait
731
+ assert world.terminate.wait
732
+ end
689
733
 
690
- it 'second terminate works concurrently' do
691
- assert [world.terminate, world.terminate].map(&:value).all?
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