dynflow 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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