dynflow 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +16 -1
  3. data/Gemfile +13 -1
  4. data/doc/pages/source/_drafts/2015-03-01-new-documentation.markdown +10 -0
  5. data/doc/pages/source/_includes/menu.html +1 -0
  6. data/doc/pages/source/_includes/menu_right.html +1 -1
  7. data/doc/pages/source/_sass/_bootstrap-variables.sass +1 -0
  8. data/doc/pages/source/_sass/_style.scss +4 -0
  9. data/doc/pages/source/blog/index.html +12 -0
  10. data/doc/pages/source/documentation/index.md +330 -5
  11. data/dynflow.gemspec +3 -1
  12. data/examples/example_helper.rb +18 -11
  13. data/examples/orchestrate_evented.rb +2 -1
  14. data/examples/remote_executor.rb +53 -20
  15. data/lib/dynflow.rb +16 -6
  16. data/lib/dynflow/action/suspended.rb +1 -1
  17. data/lib/dynflow/action/with_sub_plans.rb +3 -6
  18. data/lib/dynflow/actor.rb +56 -0
  19. data/lib/dynflow/clock.rb +43 -38
  20. data/lib/dynflow/config.rb +107 -0
  21. data/lib/dynflow/connectors.rb +7 -0
  22. data/lib/dynflow/connectors/abstract.rb +41 -0
  23. data/lib/dynflow/connectors/database.rb +175 -0
  24. data/lib/dynflow/connectors/direct.rb +71 -0
  25. data/lib/dynflow/coordinator.rb +280 -0
  26. data/lib/dynflow/coordinator_adapters.rb +8 -0
  27. data/lib/dynflow/coordinator_adapters/abstract.rb +28 -0
  28. data/lib/dynflow/coordinator_adapters/sequel.rb +29 -0
  29. data/lib/dynflow/dispatcher.rb +58 -0
  30. data/lib/dynflow/dispatcher/abstract.rb +14 -0
  31. data/lib/dynflow/dispatcher/client_dispatcher.rb +139 -0
  32. data/lib/dynflow/dispatcher/executor_dispatcher.rb +86 -0
  33. data/lib/dynflow/errors.rb +7 -1
  34. data/lib/dynflow/execution_history.rb +46 -0
  35. data/lib/dynflow/execution_plan.rb +19 -15
  36. data/lib/dynflow/executors.rb +0 -1
  37. data/lib/dynflow/executors/abstract.rb +5 -10
  38. data/lib/dynflow/executors/parallel.rb +16 -13
  39. data/lib/dynflow/executors/parallel/core.rb +76 -78
  40. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +4 -5
  41. data/lib/dynflow/executors/parallel/pool.rb +22 -52
  42. data/lib/dynflow/executors/parallel/running_steps_manager.rb +9 -2
  43. data/lib/dynflow/executors/parallel/worker.rb +5 -10
  44. data/lib/dynflow/persistence.rb +14 -0
  45. data/lib/dynflow/persistence_adapters/abstract.rb +14 -3
  46. data/lib/dynflow/persistence_adapters/sequel.rb +142 -38
  47. data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +14 -0
  48. data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +14 -0
  49. data/lib/dynflow/round_robin.rb +37 -0
  50. data/lib/dynflow/serializable.rb +1 -2
  51. data/lib/dynflow/serializer.rb +46 -0
  52. data/lib/dynflow/testing/dummy_executor.rb +2 -2
  53. data/lib/dynflow/testing/dummy_world.rb +1 -1
  54. data/lib/dynflow/transaction_adapters/abstract.rb +0 -5
  55. data/lib/dynflow/transaction_adapters/active_record.rb +0 -10
  56. data/lib/dynflow/version.rb +1 -1
  57. data/lib/dynflow/web.rb +26 -0
  58. data/lib/dynflow/web/console.rb +108 -0
  59. data/lib/dynflow/web/console_helpers.rb +158 -0
  60. data/lib/dynflow/web/filtering_helpers.rb +85 -0
  61. data/lib/dynflow/web/world_helpers.rb +9 -0
  62. data/lib/dynflow/web_console.rb +3 -310
  63. data/lib/dynflow/world.rb +188 -119
  64. data/test/abnormal_states_recovery_test.rb +152 -0
  65. data/test/action_test.rb +2 -3
  66. data/test/clock_test.rb +1 -5
  67. data/test/coordinator_test.rb +152 -0
  68. data/test/dispatcher_test.rb +146 -0
  69. data/test/execution_plan_test.rb +2 -1
  70. data/test/executor_test.rb +534 -612
  71. data/test/middleware_test.rb +4 -4
  72. data/test/persistence_test.rb +17 -0
  73. data/test/prepare_travis_env.sh +35 -0
  74. data/test/rescue_test.rb +5 -3
  75. data/test/round_robin_test.rb +28 -0
  76. data/test/support/code_workflow_example.rb +0 -73
  77. data/test/support/dummy_example.rb +130 -0
  78. data/test/support/test_execution_log.rb +41 -0
  79. data/test/test_helper.rb +222 -116
  80. data/test/testing_test.rb +10 -10
  81. data/test/web_console_test.rb +3 -3
  82. data/test/world_test.rb +23 -0
  83. data/web/assets/images/logo-square.png +0 -0
  84. data/web/assets/stylesheets/application.css +9 -0
  85. data/web/assets/vendor/bootstrap/config.json +429 -0
  86. data/web/assets/vendor/bootstrap/css/bootstrap-theme.css +479 -0
  87. data/web/assets/vendor/bootstrap/css/bootstrap-theme.min.css +10 -0
  88. data/web/assets/vendor/bootstrap/css/bootstrap.css +5377 -4980
  89. data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -8
  90. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  91. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  92. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  93. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  94. data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  95. data/web/assets/vendor/bootstrap/js/bootstrap.js +1674 -1645
  96. data/web/assets/vendor/bootstrap/js/bootstrap.min.js +11 -5
  97. data/web/views/execution_history.erb +17 -0
  98. data/web/views/index.erb +4 -6
  99. data/web/views/layout.erb +44 -8
  100. data/web/views/show.erb +4 -5
  101. data/web/views/worlds.erb +26 -0
  102. metadata +116 -23
  103. checksums.yaml +0 -15
  104. data/lib/dynflow/daemon.rb +0 -30
  105. data/lib/dynflow/executors/remote_via_socket.rb +0 -43
  106. data/lib/dynflow/executors/remote_via_socket/core.rb +0 -184
  107. data/lib/dynflow/future.rb +0 -173
  108. data/lib/dynflow/listeners.rb +0 -7
  109. data/lib/dynflow/listeners/abstract.rb +0 -17
  110. data/lib/dynflow/listeners/serialization.rb +0 -77
  111. data/lib/dynflow/listeners/socket.rb +0 -117
  112. data/lib/dynflow/micro_actor.rb +0 -102
  113. data/lib/dynflow/simple_world.rb +0 -19
  114. data/test/remote_via_socket_test.rb +0 -170
  115. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +0 -1109
  116. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +0 -9
  117. data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
  118. data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
@@ -5,7 +5,8 @@ module Dynflow
5
5
  describe ExecutionPlan do
6
6
 
7
7
  include PlanAssertions
8
- include WorldInstance
8
+
9
+ let(:world) { WorldFactory.create_world }
9
10
 
10
11
  let :issues_data do
11
12
  [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require_relative 'test_helper'
2
3
 
3
4
  module Dynflow
@@ -6,752 +7,673 @@ module Dynflow
6
7
 
7
8
  include PlanAssertions
8
9
 
9
- [:world, :remote_world].each do |world_method|
10
+ let(:world) { WorldFactory.create_world }
10
11
 
11
- describe world_method.to_s do
12
+ let :issues_data do
13
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
14
+ { 'author' => 'John Doe', 'text' => 'Internal server error' }]
15
+ end
12
16
 
13
- let(:world) { WorldInstance.send world_method }
17
+ let :failing_issues_data do
18
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
19
+ { 'author' => 'John Doe', 'text' => 'trolling' }]
20
+ end
14
21
 
15
- let :issues_data do
16
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
17
- { 'author' => 'John Doe', 'text' => 'Internal server error' }]
18
- end
22
+ let :finalize_failing_issues_data do
23
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
24
+ { 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
25
+ end
19
26
 
20
- let :failing_issues_data do
21
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
22
- { 'author' => 'John Doe', 'text' => 'trolling' }]
23
- end
27
+ let :failed_execution_plan do
28
+ plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
29
+ plan = world.execute(plan.id).value
30
+ plan.state.must_equal :paused
31
+ plan
32
+ end
24
33
 
25
- let :finalize_failing_issues_data do
26
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
27
- { 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
28
- end
34
+ let :finalize_failed_execution_plan do
35
+ plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
36
+ plan = world.execute(plan.id).value
37
+ plan.state.must_equal :paused
38
+ plan
39
+ end
29
40
 
30
- let :failed_execution_plan do
31
- plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
32
- plan = world.execute(plan.id).value
33
- plan.state.must_equal :paused
34
- plan
41
+ let :persisted_plan do
42
+ world.persistence.load_execution_plan(execution_plan.id)
43
+ end
44
+
45
+ let :executor_class do
46
+ Executors::Parallel
47
+ end
48
+
49
+ describe "execution plan state" do
50
+
51
+ describe "after successful planning" do
52
+
53
+ let :execution_plan do
54
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
35
55
  end
36
56
 
37
- let :finalize_failed_execution_plan do
38
- plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
39
- plan = world.execute(plan.id).value
40
- plan.state.must_equal :paused
41
- plan
57
+ it "is pending" do
58
+ execution_plan.state.must_equal :planned
42
59
  end
43
60
 
44
- let :persisted_plan do
45
- world.persistence.load_execution_plan(execution_plan.id)
61
+ describe "when finished successfully" do
62
+ it "is stopped" do
63
+ world.execute(execution_plan.id).value.tap do |plan|
64
+ plan.state.must_equal :stopped
65
+ end
66
+ end
46
67
  end
47
68
 
48
- let :executor_class do
49
- Executors::Parallel
69
+ describe "when finished with error" do
70
+ it "is paused" do
71
+ world.execute(failed_execution_plan.id).value.tap do |plan|
72
+ plan.state.must_equal :paused
73
+ end
74
+ end
50
75
  end
76
+ end
51
77
 
52
- describe "execution plan state" do
78
+ describe "after error in planning" do
53
79
 
54
- describe "after successful planning" do
80
+ class FailingAction < Dynflow::Action
81
+ def plan
82
+ raise "I failed"
83
+ end
84
+ end
55
85
 
56
- let :execution_plan do
57
- world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
58
- end
86
+ let :execution_plan do
87
+ world.plan(FailingAction)
88
+ end
59
89
 
60
- it "is pending" do
61
- execution_plan.state.must_equal :planned
62
- end
90
+ it "is stopped" do
91
+ execution_plan.state.must_equal :stopped
92
+ end
63
93
 
64
- describe "when finished successfully" do
65
- it "is stopped" do
66
- world.execute(execution_plan.id).value.tap do |plan|
67
- plan.state.must_equal :stopped
68
- end
69
- end
70
- end
94
+ end
71
95
 
72
- describe "when finished with error" do
73
- it "is paused" do
74
- world.execute(failed_execution_plan.id).value.tap do |plan|
75
- plan.state.must_equal :paused
76
- end
77
- end
78
- end
79
- end
96
+ describe "when being executed" do
80
97
 
81
- describe "after error in planning" do
98
+ let :execution_plan do
99
+ world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
100
+ end
82
101
 
83
- class FailingAction < Dynflow::Action
84
- def plan
85
- raise "I failed"
86
- end
87
- end
102
+ before do
103
+ TestPause.setup
104
+ @execution = world.execute(execution_plan.id)
105
+ end
88
106
 
89
- let :execution_plan do
90
- world.plan(FailingAction)
91
- end
107
+ after do
108
+ @execution.wait
109
+ TestPause.teardown
110
+ end
92
111
 
93
- it "is stopped" do
94
- execution_plan.state.must_equal :stopped
112
+ it "is running" do
113
+ TestPause.when_paused do
114
+ plan = world.persistence.load_execution_plan(execution_plan.id)
115
+ plan.state.must_equal :running
116
+ triage = plan.steps.values.find do |s|
117
+ s.is_a?(Dynflow::ExecutionPlan::Steps::RunStep) &&
118
+ s.action_class == Support::CodeWorkflowExample::Triage
95
119
  end
120
+ triage.state.must_equal :running
121
+ world.persistence.
122
+ load_step(triage.execution_plan_id, triage.id, world).
123
+ state.must_equal :running
124
+ end
125
+ end
96
126
 
127
+ it "fails when trying to execute again" do
128
+ TestPause.when_paused do
129
+ assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
97
130
  end
131
+ end
132
+ end
133
+ end
98
134
 
99
- describe "when being executed" do
135
+ describe "execution of run flow" do
100
136
 
101
- let :execution_plan do
102
- world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
103
- end
137
+ before do
138
+ TestExecutionLog.setup
139
+ end
104
140
 
105
- before do
106
- TestPause.setup
107
- @execution = world.execute(execution_plan.id)
108
- end
141
+ let :result do
142
+ world.execute(execution_plan.id).value!
143
+ end
109
144
 
110
- after do
111
- @execution.wait
112
- TestPause.teardown
113
- end
145
+ after do
146
+ TestExecutionLog.teardown
147
+ end
114
148
 
115
- it "is running" do
116
- TestPause.when_paused do
117
- plan = world.persistence.load_execution_plan(execution_plan.id)
118
- plan.state.must_equal :running
119
- triage = plan.steps.values.find do |s|
120
- s.is_a?(Dynflow::ExecutionPlan::Steps::RunStep) &&
121
- s.action_class == Support::CodeWorkflowExample::Triage
122
- end
123
- triage.state.must_equal :running
124
- world.persistence.
125
- load_step(triage.execution_plan_id, triage.id, world).
126
- state.must_equal :running
127
- end
128
- end
149
+ def persisted_plan
150
+ result
151
+ super
152
+ end
129
153
 
130
- it "fails when trying to execute again" do
131
- TestPause.when_paused do
132
- assert_raises(Dynflow::Error) { world.execute(execution_plan.id) }
133
- end
134
- end
154
+ describe 'cancellable action' do
155
+ describe 'successful' do
156
+ let :execution_plan do
157
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
135
158
  end
136
- end
137
-
138
- describe "execution of run flow" do
139
159
 
140
- before do
141
- TestExecutionLog.setup
160
+ it "doesn't cause problems" do
161
+ result.result.must_equal :success
162
+ result.state.must_equal :stopped
142
163
  end
164
+ end
143
165
 
144
- let :result do
145
- world.execute(execution_plan.id).value!
166
+ describe 'canceled' do
167
+ let :execution_plan do
168
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
146
169
  end
147
170
 
148
- after do
149
- TestExecutionLog.teardown
171
+ it 'cancels' do
172
+ result.result.must_equal :success
173
+ result.state.must_equal :stopped
174
+ action = world.persistence.load_action result.steps[2]
175
+ action.output[:task][:progress].must_equal 30
176
+ action.output[:task][:cancelled].must_equal true
150
177
  end
178
+ end
151
179
 
152
- def persisted_plan
153
- result
154
- super
180
+ describe 'canceled failed' do
181
+ let :execution_plan do
182
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
155
183
  end
156
184
 
157
- describe 'cancellable action' do
158
-
159
- describe event_class = Listeners::Serialization::Protocol::Event do
160
- it 'de/serializes' do
161
- Klass = Class.new do
162
- def initialize(v)
163
- @v = v
164
- end
165
-
166
- def to_s
167
- @v.to_s
168
- end
169
-
170
- def ==(other)
171
- @v == other.instance_variable_get(:@v)
172
- end
173
- end
174
-
175
- object = Klass.new :value
176
- event = event_class['uuid', 0, object]
177
- hash = event.to_hash
178
- json = MultiJson.dump(hash)
179
- hash_loaded = MultiJson.load(json)
180
- assert_equal event[:event], event_class.from_hash(hash_loaded)[:event]
181
- assert_equal event, event_class.from_hash(hash_loaded)
182
-
183
- ExecutorTest.send :remove_const, :Klass
184
- end
185
- end
185
+ it 'fails' do
186
+ result.result.must_equal :error
187
+ result.state.must_equal :paused
188
+ step = result.steps[2]
189
+ step.error.message.must_equal 'action cancelled'
190
+ action = world.persistence.load_action step
191
+ action.output[:task][:progress].must_equal 30
192
+ end
193
+ end
194
+ end
186
195
 
187
- describe 'successful' do
188
- let :execution_plan do
189
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
190
- end
196
+ describe 'suspended action' do
197
+ describe 'handling errors in setup' do
198
+ let :execution_plan do
199
+ world.plan(Support::DummyExample::Polling,
200
+ external_task_id: '123',
201
+ text: 'troll setup')
202
+ end
191
203
 
192
- it "doesn't cause problems" do
193
- result.result.must_equal :success
194
- result.state.must_equal :stopped
195
- end
196
- end
204
+ it 'fails' do
205
+ assert_equal :error, result.result
206
+ assert_equal :paused, result.state
207
+ assert_equal :error,
208
+ result.steps.values.
209
+ find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.
210
+ state
211
+ end
212
+ end
197
213
 
198
- describe 'canceled' do
199
- let :execution_plan do
200
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
201
- end
214
+ describe 'running' do
215
+ let :execution_plan do
216
+ world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
217
+ end
202
218
 
203
- it 'cancels' do
204
- result.result.must_equal :success
205
- result.state.must_equal :stopped
206
- action = world.persistence.load_action result.steps[2]
207
- action.output[:task][:progress].must_equal 30
208
- action.output[:task][:cancelled].must_equal true
209
- end
210
- end
219
+ it "doesn't cause problems" do
220
+ result.result.must_equal :success
221
+ result.state.must_equal :stopped
222
+ end
211
223
 
212
- describe 'canceled failed' do
213
- let :execution_plan do
214
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
215
- end
224
+ it 'does set times' do
225
+ result.started_at.wont_be_nil
226
+ result.ended_at.wont_be_nil
227
+ result.execution_time.must_be :<, result.real_time
228
+ result.execution_time.must_equal(
229
+ result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
216
230
 
217
- it 'fails' do
218
- result.result.must_equal :error
219
- result.state.must_equal :paused
220
- step = result.steps[2]
221
- step.error.message.must_equal 'action cancelled'
222
- action = world.persistence.load_action step
223
- action.output[:task][:progress].must_equal 30
224
- end
225
- end
231
+ plan_step = result.steps[1]
232
+ plan_step.started_at.wont_be_nil
233
+ plan_step.ended_at.wont_be_nil
234
+ plan_step.execution_time.must_equal plan_step.real_time
226
235
 
227
- if world_method == :remote_world
228
- describe 'canceled externally' do
229
- let :execution_plan do
230
- world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-external' })
231
- end
232
-
233
- it 'cancels' do
234
- finished = world.execute(execution_plan.id)
235
- sleep 0.05
236
- world.
237
- event(execution_plan.id, 2,
238
- Support::CodeWorkflowExample::CancelableSuspended::Cancel).
239
- value!.must_equal true
240
- result = finished.value!
241
-
242
- result.result.must_equal :success
243
- result.state.must_equal :stopped
244
- action = world.persistence.load_action result.steps[2]
245
- action.output[:task][:progress].must_be :<=, 30
246
- action.output[:task][:cancelled].must_equal true
247
- end
248
- end
249
- end
236
+ run_step = result.steps[2]
237
+ run_step.started_at.wont_be_nil
238
+ run_step.ended_at.wont_be_nil
239
+ run_step.execution_time.must_be :<, run_step.real_time
240
+ end
241
+ end
250
242
 
243
+ describe 'progress' do
244
+ before do
245
+ TestPause.setup
246
+ @running_plan = world.execute(execution_plan.id)
251
247
  end
252
248
 
253
- describe 'suspended action' do
254
- describe 'handling errors in setup' do
255
- let :execution_plan do
256
- world.plan(Support::CodeWorkflowExample::DummySuspended,
257
- external_task_id: '123',
258
- text: 'troll setup')
259
- end
249
+ after do
250
+ @running_plan.wait
251
+ TestPause.teardown
252
+ end
260
253
 
261
- it 'fails' do
262
- assert_equal :error, result.result
263
- assert_equal :paused, result.state
264
- assert_equal :error,
265
- result.steps.values.
266
- find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.
267
- state
268
- end
254
+ describe 'plan with one action' do
255
+ let :execution_plan do
256
+ world.plan(Support::DummyExample::Polling,
257
+ { external_task_id: '123',
258
+ text: 'pause in progress 20%' })
269
259
  end
270
260
 
271
- describe 'running' do
272
- let :execution_plan do
273
- world.plan(Support::CodeWorkflowExample::DummySuspended, { :external_task_id => '123' })
274
- end
275
-
276
- it "doesn't cause problems" do
277
- result.result.must_equal :success
278
- result.state.must_equal :stopped
279
- end
280
-
281
- it 'does set times' do
282
- result.started_at.wont_be_nil
283
- result.ended_at.wont_be_nil
284
- result.execution_time.must_be :<, result.real_time
285
- result.execution_time.must_equal(
286
- result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
287
-
288
- plan_step = result.steps[1]
289
- plan_step.started_at.wont_be_nil
290
- plan_step.ended_at.wont_be_nil
291
- plan_step.execution_time.must_equal plan_step.real_time
292
-
293
- run_step = result.steps[2]
294
- run_step.started_at.wont_be_nil
295
- run_step.ended_at.wont_be_nil
296
- run_step.execution_time.must_be :<, run_step.real_time
261
+ it 'determines the progress of the execution plan in percents' do
262
+ TestPause.when_paused do
263
+ plan = world.persistence.load_execution_plan(execution_plan.id)
264
+ plan.progress.round(2).must_equal 0.2
297
265
  end
298
266
  end
267
+ end
299
268
 
300
- describe 'progress' do
301
- before do
302
- TestPause.setup
303
- @running_plan = world.execute(execution_plan.id)
304
- end
305
-
306
- after do
307
- @running_plan.wait
308
- TestPause.teardown
309
- end
310
-
311
- describe 'plan with one action' do
312
- let :execution_plan do
313
- world.plan(Support::CodeWorkflowExample::DummySuspended,
314
- { external_task_id: '123',
315
- text: 'pause in progress 20%' })
316
- end
317
-
318
- it 'determines the progress of the execution plan in percents' do
319
- TestPause.when_paused do
320
- plan = world.persistence.load_execution_plan(execution_plan.id)
321
- plan.progress.round(2).must_equal 0.2
322
- end
323
- end
324
- end
325
-
326
- describe 'plan with more action' do
327
- let :execution_plan do
328
- world.plan(Support::CodeWorkflowExample::DummyHeavyProgress,
329
- { external_task_id: '123',
330
- text: 'pause in progress 20%' })
331
- end
332
-
333
- it 'takes the steps weight in account' do
334
- TestPause.when_paused do
335
- plan = world.persistence.load_execution_plan(execution_plan.id)
336
- plan.progress.round(2).must_equal 0.42
337
- end
338
- end
339
- end
269
+ describe 'plan with more action' do
270
+ let :execution_plan do
271
+ world.plan(Support::DummyExample::WeightedPolling,
272
+ { external_task_id: '123',
273
+ text: 'pause in progress 20%' })
340
274
  end
341
275
 
342
- describe 'works when resumed after error' do
343
- let :execution_plan do
344
- world.plan(Support::CodeWorkflowExample::DummySuspended,
345
- { external_task_id: '123',
346
- text: 'troll progress' })
347
- end
348
-
349
- specify do
350
- assert_equal :paused, result.state
351
- assert_equal :error, result.result
352
- assert_equal :error, result.steps.values.
353
- find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
354
-
355
- ep = world.execute(result.id).value
356
- assert_equal :stopped, ep.state
357
- assert_equal :success, ep.result
358
- assert_equal :success, ep.steps.values.
359
- find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
276
+ it 'takes the steps weight in account' do
277
+ TestPause.when_paused do
278
+ plan = world.persistence.load_execution_plan(execution_plan.id)
279
+ plan.progress.round(2).must_equal 0.42
360
280
  end
361
281
  end
282
+ end
283
+ end
362
284
 
285
+ describe 'works when resumed after error' do
286
+ let :execution_plan do
287
+ world.plan(Support::DummyExample::Polling,
288
+ { external_task_id: '123',
289
+ text: 'troll progress' })
363
290
  end
364
291
 
365
- describe "action with empty flows" do
292
+ specify do
293
+ assert_equal :paused, result.state
294
+ assert_equal :error, result.result
295
+ assert_equal :error, result.steps.values.
296
+ find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
366
297
 
367
- let :execution_plan do
368
- world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
369
- assert_equal plan.run_flow.size, 0
370
- assert_equal plan.finalize_flow.size, 0
371
- end.tap do |w|
372
- w
373
- end
374
- end
298
+ ep = world.execute(result.id).value
299
+ assert_equal :stopped, ep.state
300
+ assert_equal :success, ep.result
301
+ assert_equal :success, ep.steps.values.
302
+ find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
303
+ end
304
+ end
375
305
 
376
- it "doesn't cause problems" do
377
- result.result.must_equal :success
378
- result.state.must_equal :stopped
379
- end
306
+ end
380
307
 
381
- it 'will not run again' do
382
- world.execute(execution_plan.id)
383
- assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
384
- end
308
+ describe "action with empty flows" do
385
309
 
310
+ let :execution_plan do
311
+ world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
312
+ assert_equal plan.run_flow.size, 0
313
+ assert_equal plan.finalize_flow.size, 0
314
+ end.tap do |w|
315
+ w
386
316
  end
317
+ end
387
318
 
388
- describe 'action with empty run flow but some finalize flow' do
389
-
390
- let :execution_plan do
391
- world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
392
- assert_equal plan.run_flow.size, 0
393
- assert_equal plan.finalize_flow.size, 1
394
- end
395
- end
319
+ it "doesn't cause problems" do
320
+ result.result.must_equal :success
321
+ result.state.must_equal :stopped
322
+ end
396
323
 
397
- it "doesn't cause problems" do
398
- result.result.must_equal :success
399
- result.state.must_equal :stopped
400
- end
324
+ it 'will not run again' do
325
+ world.execute(execution_plan.id)
326
+ assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
327
+ end
401
328
 
402
- end
329
+ end
403
330
 
404
- describe 'running' do
405
- let :execution_plan do
406
- world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
407
- end
331
+ describe 'action with empty run flow but some finalize flow' do
408
332
 
409
- it "runs all the steps in the run flow" do
410
- assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
411
- Dynflow::Flows::Concurrence
412
- Dynflow::Flows::Sequence
413
- 4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
414
- 7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
415
- 9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
416
- Dynflow::Flows::Sequence
417
- 13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
418
- 16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
419
- 18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
420
- EXECUTED_RUN_FLOW
421
- end
333
+ let :execution_plan do
334
+ world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
335
+ assert_equal plan.run_flow.size, 0
336
+ assert_equal plan.finalize_flow.size, 1
422
337
  end
338
+ end
423
339
 
340
+ it "doesn't cause problems" do
341
+ result.result.must_equal :success
342
+ result.state.must_equal :stopped
424
343
  end
425
344
 
426
- describe "execution of finalize flow" do
427
- before do
428
- TestExecutionLog.setup
429
- result = world.execute(execution_plan.id).value
430
- raise result if result.is_a? Exception
431
- end
345
+ end
432
346
 
433
- after do
434
- TestExecutionLog.teardown
435
- end
347
+ describe 'running' do
348
+ let :execution_plan do
349
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
350
+ end
436
351
 
437
- describe "when run flow successful" do
438
- let :execution_plan do
439
- world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
440
- end
352
+ it "runs all the steps in the run flow" do
353
+ assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
354
+ Dynflow::Flows::Concurrence
355
+ Dynflow::Flows::Sequence
356
+ 4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
357
+ 7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
358
+ 9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
359
+ Dynflow::Flows::Sequence
360
+ 13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
361
+ 16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
362
+ 18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
363
+ EXECUTED_RUN_FLOW
364
+ end
365
+ end
441
366
 
442
- it "runs all the steps in the finalize flow" do
443
- assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
444
- { "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
445
- assert_finalized(Support::CodeWorkflowExample::Triage,
446
- { "author" => "Peter Smith", "text" => "Failing test" })
447
- end
448
- end
367
+ end
449
368
 
450
- describe "when run flow failed" do
451
- let :execution_plan do
452
- failed_execution_plan
453
- end
369
+ describe "execution of finalize flow" do
370
+ before do
371
+ TestExecutionLog.setup
372
+ result = world.execute(execution_plan.id).value
373
+ raise result if result.is_a? Exception
374
+ end
454
375
 
455
- it "doesn't run the steps in the finalize flow" do
456
- TestExecutionLog.finalize.size.must_equal 0
457
- end
458
- end
376
+ after do
377
+ TestExecutionLog.teardown
378
+ end
459
379
 
380
+ describe "when run flow successful" do
381
+ let :execution_plan do
382
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
460
383
  end
461
384
 
462
- describe "re-execution of run flow after fix in run phase" do
385
+ it "runs all the steps in the finalize flow" do
386
+ assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
387
+ { "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
388
+ assert_finalized(Support::CodeWorkflowExample::Triage,
389
+ { "author" => "Peter Smith", "text" => "Failing test" })
390
+ end
391
+ end
463
392
 
464
- after do
465
- TestExecutionLog.teardown
466
- end
393
+ describe "when run flow failed" do
394
+ let :execution_plan do
395
+ failed_execution_plan
396
+ end
467
397
 
468
- let :resumed_execution_plan do
469
- failed_step = failed_execution_plan.steps.values.find do |step|
470
- step.state == :error
471
- end
472
- world.persistence.load_action(failed_step).tap do |action|
473
- action.input[:text] = "ok"
474
- world.persistence.save_action(failed_step.execution_plan_id, action)
475
- end
476
- TestExecutionLog.setup
477
- world.execute(failed_execution_plan.id).value
478
- end
398
+ it "doesn't run the steps in the finalize flow" do
399
+ TestExecutionLog.finalize.size.must_equal 0
400
+ end
401
+ end
479
402
 
480
- it "runs all the steps in the run flow" do
481
- resumed_execution_plan.state.must_equal :stopped
482
- resumed_execution_plan.result.must_equal :success
403
+ end
483
404
 
484
- run_triages = TestExecutionLog.run.find_all do |action_class, input|
485
- action_class == Support::CodeWorkflowExample::Triage
486
- end
487
- run_triages.size.must_equal 1
488
-
489
- assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
490
- Dynflow::Flows::Concurrence
491
- Dynflow::Flows::Sequence
492
- 4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
493
- 7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
494
- 9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
495
- Dynflow::Flows::Sequence
496
- 13: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
497
- 16: UpdateIssue(success) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
498
- 18: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
499
- EXECUTED_RUN_FLOW
500
- end
405
+ describe "re-execution of run flow after fix in run phase" do
501
406
 
502
- end
407
+ after do
408
+ TestExecutionLog.teardown
409
+ end
503
410
 
504
- describe "re-execution of run flow after fix in finalize phase" do
411
+ let :resumed_execution_plan do
412
+ failed_step = failed_execution_plan.steps.values.find do |step|
413
+ step.state == :error
414
+ end
415
+ world.persistence.load_action(failed_step).tap do |action|
416
+ action.input[:text] = "ok"
417
+ world.persistence.save_action(failed_step.execution_plan_id, action)
418
+ end
419
+ TestExecutionLog.setup
420
+ world.execute(failed_execution_plan.id).value
421
+ end
505
422
 
506
- after do
507
- TestExecutionLog.teardown
508
- end
423
+ it "runs all the steps in the run flow" do
424
+ resumed_execution_plan.state.must_equal :stopped
425
+ resumed_execution_plan.result.must_equal :success
509
426
 
510
- let :resumed_execution_plan do
511
- failed_step = finalize_failed_execution_plan.steps.values.find do |step|
512
- step.state == :error
513
- end
514
- world.persistence.load_action(failed_step).tap do |action|
515
- action.input[:text] = "ok"
516
- world.persistence.save_action(failed_step.execution_plan_id, action)
517
- end
518
- TestExecutionLog.setup
519
- world.execute(finalize_failed_execution_plan.id).value
520
- end
427
+ run_triages = TestExecutionLog.run.find_all do |action_class, input|
428
+ action_class == Support::CodeWorkflowExample::Triage
429
+ end
430
+ run_triages.size.must_equal 1
431
+
432
+ assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
433
+ Dynflow::Flows::Concurrence
434
+ Dynflow::Flows::Sequence
435
+ 4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
436
+ 7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
437
+ 9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
438
+ Dynflow::Flows::Sequence
439
+ 13: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
440
+ 16: UpdateIssue(success) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
441
+ 18: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
442
+ EXECUTED_RUN_FLOW
443
+ end
521
444
 
522
- it "runs all the steps in the finalize flow" do
523
- resumed_execution_plan.state.must_equal :stopped
524
- resumed_execution_plan.result.must_equal :success
445
+ end
525
446
 
526
- run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
527
- action_class == Support::CodeWorkflowExample::Triage
528
- end
529
- run_triages.size.must_equal 2
447
+ describe "re-execution of run flow after fix in finalize phase" do
530
448
 
531
- assert_finalize_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
532
- Dynflow::Flows::Sequence
533
- 5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
534
- 10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
535
- 14: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
536
- 19: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
537
- 20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling in finalize\"}]} --> {}
538
- EXECUTED_RUN_FLOW
539
- end
449
+ after do
450
+ TestExecutionLog.teardown
451
+ end
540
452
 
453
+ let :resumed_execution_plan do
454
+ failed_step = finalize_failed_execution_plan.steps.values.find do |step|
455
+ step.state == :error
541
456
  end
457
+ world.persistence.load_action(failed_step).tap do |action|
458
+ action.input[:text] = "ok"
459
+ world.persistence.save_action(failed_step.execution_plan_id, action)
460
+ end
461
+ TestExecutionLog.setup
462
+ world.execute(finalize_failed_execution_plan.id).value
463
+ end
542
464
 
543
- describe "re-execution of run flow after skipping" do
544
-
545
- after do
546
- TestExecutionLog.teardown
547
- end
548
-
549
- let :resumed_execution_plan do
550
- failed_step = failed_execution_plan.steps.values.find do |step|
551
- step.state == :error
552
- end
553
- failed_execution_plan.skip(failed_step)
554
- TestExecutionLog.setup
555
- world.execute(failed_execution_plan.id).value
556
- end
465
+ it "runs all the steps in the finalize flow" do
466
+ resumed_execution_plan.state.must_equal :stopped
467
+ resumed_execution_plan.result.must_equal :success
557
468
 
558
- it "runs all pending steps except skipped" do
559
- resumed_execution_plan.state.must_equal :stopped
560
- resumed_execution_plan.result.must_equal :warning
469
+ run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
470
+ action_class == Support::CodeWorkflowExample::Triage
471
+ end
472
+ run_triages.size.must_equal 2
561
473
 
562
- run_triages = TestExecutionLog.run.find_all do |action_class, input|
563
- action_class == Support::CodeWorkflowExample::Triage
564
- end
565
- run_triages.size.must_equal 0
566
-
567
- assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
568
- Dynflow::Flows::Concurrence
569
- Dynflow::Flows::Sequence
570
- 4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
571
- 7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
572
- 9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
573
- Dynflow::Flows::Sequence
574
- 13: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
575
- 16: UpdateIssue(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>Step(13).output[:classification][:assignee], \"severity\"=>Step(13).output[:classification][:severity]} --> {}
576
- 18: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
577
- EXECUTED_RUN_FLOW
578
-
579
- assert_finalize_flow <<-FINALIZE_FLOW, resumed_execution_plan
474
+ assert_finalize_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
580
475
  Dynflow::Flows::Sequence
581
476
  5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
582
477
  10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
583
- 14: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
584
- 19: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
585
- 20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"}]} --> {}
586
- FINALIZE_FLOW
478
+ 14: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
479
+ 19: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
480
+ 20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling in finalize\"}]} --> {}
481
+ EXECUTED_RUN_FLOW
482
+ end
587
483
 
588
- end
589
- end
484
+ end
590
485
 
591
- describe 'FlowManager' do
592
- let :execution_plan do
593
- world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
594
- end
486
+ describe "re-execution of run flow after skipping" do
595
487
 
596
- let(:manager) { Executors::Parallel::FlowManager.new execution_plan, execution_plan.run_flow }
488
+ after do
489
+ TestExecutionLog.teardown
490
+ end
597
491
 
598
- def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
599
- if finished_step_id
600
- step = manager.execution_plan.steps[finished_step_id]
601
- next_steps = manager.cursor_index[step.id].what_is_next(step, success)
602
- else
603
- next_steps = manager.start
604
- end
605
- next_step_ids = next_steps.map(&:id)
606
- assert_equal Set.new(expected_next_step_ids), Set.new(next_step_ids)
607
- end
492
+ let :resumed_execution_plan do
493
+ failed_step = failed_execution_plan.steps.values.find do |step|
494
+ step.state == :error
495
+ end
496
+ failed_execution_plan.skip(failed_step)
497
+ TestExecutionLog.setup
498
+ world.execute(failed_execution_plan.id).value
499
+ end
608
500
 
609
- describe 'what_is_next' do
610
- it 'returns next steps after required steps were finished' do
611
- assert_next_steps([4, 13])
612
- assert_next_steps([7], 4)
613
- assert_next_steps([9], 7)
614
- assert_next_steps([], 9)
615
- assert_next_steps([16], 13)
616
- assert_next_steps([18], 16)
617
- assert_next_steps([], 18)
618
- assert manager.done?
619
- end
620
- end
501
+ it "runs all pending steps except skipped" do
502
+ resumed_execution_plan.state.must_equal :stopped
503
+ resumed_execution_plan.result.must_equal :warning
621
504
 
622
- describe 'what_is_next with errors' do
505
+ run_triages = TestExecutionLog.run.find_all do |action_class, input|
506
+ action_class == Support::CodeWorkflowExample::Triage
507
+ end
508
+ run_triages.size.must_equal 0
509
+
510
+ assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
511
+ Dynflow::Flows::Concurrence
512
+ Dynflow::Flows::Sequence
513
+ 4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
514
+ 7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
515
+ 9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
516
+ Dynflow::Flows::Sequence
517
+ 13: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
518
+ 16: UpdateIssue(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>Step(13).output[:classification][:assignee], \"severity\"=>Step(13).output[:classification][:severity]} --> {}
519
+ 18: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
520
+ EXECUTED_RUN_FLOW
521
+
522
+ assert_finalize_flow <<-FINALIZE_FLOW, resumed_execution_plan
523
+ Dynflow::Flows::Sequence
524
+ 5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
525
+ 10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
526
+ 14: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
527
+ 19: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
528
+ 20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"}]} --> {}
529
+ FINALIZE_FLOW
623
530
 
624
- it "doesn't return next steps if requirements failed" do
625
- assert_next_steps([4, 13])
626
- assert_next_steps([], 4, false)
627
- end
531
+ end
532
+ end
628
533
 
534
+ describe 'FlowManager' do
535
+ let :execution_plan do
536
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
537
+ end
629
538
 
630
- it "is not done while other steps can be finished" do
631
- assert_next_steps([4, 13])
632
- assert_next_steps([], 4, false)
633
- assert !manager.done?
634
- assert_next_steps([], 13, false)
635
- assert manager.done?
636
- end
637
- end
539
+ let(:manager) { Executors::Parallel::FlowManager.new execution_plan, execution_plan.run_flow }
638
540
 
541
+ def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
542
+ if finished_step_id
543
+ step = manager.execution_plan.steps[finished_step_id]
544
+ next_steps = manager.cursor_index[step.id].what_is_next(step, success)
545
+ else
546
+ next_steps = manager.start
639
547
  end
548
+ next_step_ids = next_steps.map(&:id)
549
+ assert_equal Set.new(expected_next_step_ids), Set.new(next_step_ids)
550
+ end
640
551
 
641
- describe 'Pool::RoundRobin' do
642
- let(:rr) { Dynflow::Executors::Parallel::Pool::RoundRobin.new }
643
- it do
644
- rr.next.must_be_nil
645
- rr.next.must_be_nil
646
- rr.must_be_empty
647
- rr.add 1
648
- rr.next.must_equal 1
649
- rr.next.must_equal 1
650
- rr.add 2
651
- rr.next.must_equal 2
652
- rr.next.must_equal 1
653
- rr.next.must_equal 2
654
- rr.delete 1
655
- rr.next.must_equal 2
656
- rr.next.must_equal 2
657
- rr.delete 2
658
- rr.next.must_be_nil
659
- rr.must_be_empty
660
- end
552
+ describe 'what_is_next' do
553
+ it 'returns next steps after required steps were finished' do
554
+ assert_next_steps([4, 13])
555
+ assert_next_steps([7], 4)
556
+ assert_next_steps([9], 7)
557
+ assert_next_steps([], 9)
558
+ assert_next_steps([16], 13)
559
+ assert_next_steps([18], 16)
560
+ assert_next_steps([], 18)
561
+ assert manager.done?
661
562
  end
563
+ end
662
564
 
663
- describe 'Pool::JobStorage' do
664
- FakeStep ||= Struct.new(:execution_plan_id)
665
-
666
- let(:storage) { Dynflow::Executors::Parallel::Pool::JobStorage.new }
667
- it do
668
- storage.must_be_empty
669
- storage.pop.must_be_nil
670
- storage.pop.must_be_nil
671
-
672
- storage.add s = FakeStep.new(1)
673
- storage.pop.must_equal s
674
- storage.must_be_empty
675
- storage.pop.must_be_nil
676
-
677
- storage.add s11 = FakeStep.new(1)
678
- storage.add s12 = FakeStep.new(1)
679
- storage.add s13 = FakeStep.new(1)
680
- storage.add s21 = FakeStep.new(2)
681
- storage.add s22 = FakeStep.new(2)
682
- storage.add s31 = FakeStep.new(3)
683
-
684
- storage.pop.must_equal s21
685
- storage.pop.must_equal s31
686
- storage.pop.must_equal s11
687
- storage.pop.must_equal s22
688
- storage.pop.must_equal s12
689
- storage.pop.must_equal s13
690
-
691
- storage.must_be_empty
692
- storage.pop.must_be_nil
693
- end
565
+ describe 'what_is_next with errors' do
566
+
567
+ it "doesn't return next steps if requirements failed" do
568
+ assert_next_steps([4, 13])
569
+ assert_next_steps([], 4, false)
694
570
  end
695
571
 
696
- end
697
- end
698
572
 
699
- describe 'termination' do
700
- let(:normal_world) { WorldInstance.create_world }
701
- let(:remote_world) { WorldInstance.create_remote_world(normal_world).last }
573
+ it "is not done while other steps can be finished" do
574
+ assert_next_steps([4, 13])
575
+ assert_next_steps([], 4, false)
576
+ assert !manager.done?
577
+ assert_next_steps([], 13, false)
578
+ assert manager.done?
579
+ end
580
+ end
702
581
 
703
- [:normal_world, :remote_world].each do |which|
704
- describe which do
705
- let(:world) { self.send which }
582
+ end
706
583
 
707
- if which == :normal_world
708
- it 'executes until its done when terminating' do
709
- $slow_actions_done = 0
710
- world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
711
- world.terminate.wait
712
- $slow_actions_done.must_equal 1
713
- end
584
+ describe 'Pool::JobStorage' do
585
+ FakeStep ||= Struct.new(:execution_plan_id)
586
+
587
+ let(:storage) { Dynflow::Executors::Parallel::Pool::JobStorage.new }
588
+ it do
589
+ storage.must_be_empty
590
+ storage.pop.must_be_nil
591
+ storage.pop.must_be_nil
592
+
593
+ storage.add s = FakeStep.new(1)
594
+ storage.pop.must_equal s
595
+ storage.must_be_empty
596
+ storage.pop.must_be_nil
597
+
598
+ storage.add s11 = FakeStep.new(1)
599
+ storage.add s12 = FakeStep.new(1)
600
+ storage.add s13 = FakeStep.new(1)
601
+ storage.add s21 = FakeStep.new(2)
602
+ storage.add s22 = FakeStep.new(2)
603
+ storage.add s31 = FakeStep.new(3)
604
+
605
+ storage.pop.must_equal s21
606
+ storage.pop.must_equal s31
607
+ storage.pop.must_equal s11
608
+ storage.pop.must_equal s22
609
+ storage.pop.must_equal s12
610
+ storage.pop.must_equal s13
611
+
612
+ storage.must_be_empty
613
+ storage.pop.must_be_nil
614
+ end
615
+ end
714
616
 
715
- it 'executes until its done when terminating even suspended' do
716
- result = world.trigger(Support::CodeWorkflowExample::DummySuspended,
717
- external_task_id: '123',
718
- text: 'none')
719
- world.terminate.wait
720
- assert result.finished.ready?
721
- end
722
- end
617
+ end
723
618
 
724
- it 'does not accept new work' do
725
- assert world.terminate.wait
726
- result = world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
727
- result.must_be :planned?
728
- result.wont_be :triggered?
729
- result.error.must_be_kind_of Dynflow::Error
730
- end
619
+ describe 'termination' do
620
+ let(:world) { WorldFactory.create_world }
621
+
622
+ it 'waits for currently running actions' do
623
+ $slow_actions_done = 0
624
+ running = world.trigger(Support::DummyExample::Slow, 1)
625
+ suspended = world.trigger(Support::DummyExample::EventedAction, :timeout => 3 )
626
+ sleep 0.2
627
+ world.terminate.wait
628
+ $slow_actions_done.must_equal 1
629
+ [running, suspended].each do |triggered|
630
+ plan = world.persistence.load_execution_plan(triggered.id)
631
+ plan.state.must_equal :paused
632
+ plan.result.must_equal :pending
633
+ end
634
+ end
731
635
 
732
- it 'it terminates when no work' do
733
- skip 'blocks occasionally' if which == :remote_world # FIXME
734
- world.trigger(Support::CodeWorkflowExample::Slow, 0.02).finished.wait
735
- assert world.terminate.wait
736
- end
636
+ describe 'before_termination hooks' do
637
+ it 'runs before temination hooks' do
638
+ hook_run = false
639
+ world.before_termination { hook_run = true }
640
+ world.terminate.wait
641
+ assert hook_run
642
+ end
737
643
 
738
- it 'it terminates when no work right after initialization' do
739
- assert world.terminate.wait
740
- end
644
+ it 'continues when some hook fails' do
645
+ run_hooks, failed_hooks = [], []
646
+ world.before_termination { run_hooks << 1 }
647
+ world.before_termination { run_hooks << 2; failed_hooks << 2; raise 'error' }
648
+ world.before_termination { run_hooks << 3 }
649
+ world.terminate.wait
650
+ run_hooks.must_equal [1, 2, 3]
651
+ failed_hooks.must_equal [2]
652
+ end
653
+ end
741
654
 
742
- it 'second terminate works' do
743
- assert world.terminate.wait
744
- assert world.terminate.wait
745
- end
655
+ it 'does not accept new work' do
656
+ assert world.terminate.wait
657
+ result = world.trigger(Support::DummyExample::Slow, 0.02)
658
+ result.must_be :planned?
659
+ result.finished.wait
660
+ assert result.finished.failed?
661
+ result.finished.reason.must_be_kind_of Concurrent::Actor::ActorTerminated
662
+ end
746
663
 
747
- it 'second terminate works concurrently' do
748
- assert [world.terminate, world.terminate].map(&:value).all?
749
- end
664
+ it 'it terminates when no work right after initialization' do
665
+ assert world.terminate.wait
666
+ end
750
667
 
751
- end
752
- end
668
+ it 'second terminate works' do
669
+ assert world.terminate.wait
670
+ assert world.terminate.wait
671
+ end
753
672
 
673
+ it 'second terminate works concurrently' do
674
+ assert [world.terminate, world.terminate].map(&:value).all?
754
675
  end
755
676
  end
677
+
756
678
  end
757
679
  end