dynflow 0.7.9 → 0.8.0

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