dynflow 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.travis.yml +1 -0
  2. data/lib/dynflow.rb +1 -0
  3. data/lib/dynflow/action.rb +5 -3
  4. data/lib/dynflow/action/finalize_phase.rb +3 -1
  5. data/lib/dynflow/action/plan_phase.rb +4 -2
  6. data/lib/dynflow/action/run_phase.rb +3 -1
  7. data/lib/dynflow/daemon.rb +1 -0
  8. data/lib/dynflow/execution_plan.rb +6 -4
  9. data/lib/dynflow/executors/abstract.rb +12 -0
  10. data/lib/dynflow/executors/parallel.rb +16 -35
  11. data/lib/dynflow/executors/parallel/core.rb +13 -8
  12. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +21 -29
  13. data/lib/dynflow/executors/parallel/flow_manager.rb +0 -3
  14. data/lib/dynflow/executors/parallel/running_steps_manager.rb +5 -3
  15. data/lib/dynflow/executors/parallel/sequential_manager.rb +6 -2
  16. data/lib/dynflow/executors/parallel/worker.rb +5 -4
  17. data/lib/dynflow/executors/remote_via_socket.rb +7 -2
  18. data/lib/dynflow/executors/remote_via_socket/core.rb +66 -32
  19. data/lib/dynflow/future.rb +1 -1
  20. data/lib/dynflow/listeners/abstract.rb +4 -0
  21. data/lib/dynflow/listeners/serialization.rb +42 -8
  22. data/lib/dynflow/listeners/socket.rb +49 -19
  23. data/lib/dynflow/middleware.rb +46 -0
  24. data/lib/dynflow/middleware/action.rb +9 -0
  25. data/lib/dynflow/middleware/register.rb +32 -0
  26. data/lib/dynflow/middleware/resolver.rb +63 -0
  27. data/lib/dynflow/middleware/stack.rb +29 -0
  28. data/lib/dynflow/middleware/world.rb +58 -0
  29. data/lib/dynflow/simple_world.rb +1 -0
  30. data/lib/dynflow/testing/dummy_world.rb +3 -1
  31. data/lib/dynflow/version.rb +1 -1
  32. data/lib/dynflow/web_console.rb +7 -2
  33. data/lib/dynflow/world.rb +29 -9
  34. data/test/action_test.rb +5 -6
  35. data/test/execution_plan_test.rb +10 -11
  36. data/test/executor_test.rb +152 -89
  37. data/test/middleware_test.rb +109 -0
  38. data/test/remote_via_socket_test.rb +166 -0
  39. data/test/{code_workflow_example.rb → support/code_workflow_example.rb} +39 -30
  40. data/test/support/middleware_example.rb +132 -0
  41. data/test/test_helper.rb +18 -16
  42. data/test/testing_test.rb +4 -3
  43. data/test/web_console_test.rb +1 -2
  44. metadata +16 -4
@@ -6,6 +6,7 @@ module Dynflow
6
6
  # we can check consistency here because SimpleWorld doesn't expect
7
7
  # remote executor being in place.
8
8
  self.consistency_check
9
+ self.execute_planned_execution_plans
9
10
  end
10
11
 
11
12
  def default_options
@@ -4,13 +4,14 @@ module Dynflow
4
4
  extend Mimic
5
5
  mimic! World
6
6
 
7
- attr_reader :clock, :executor
7
+ attr_reader :clock, :executor, :middleware
8
8
  attr_accessor :action
9
9
 
10
10
  def initialize
11
11
  @logger_adapter = Testing.logger_adapter
12
12
  @clock = ManagedClock.new
13
13
  @executor = DummyExecutor.new(self)
14
+ @middleware = Middleware::World.new
14
15
  end
15
16
 
16
17
  def action_logger
@@ -28,6 +29,7 @@ module Dynflow
28
29
  def event(execution_plan_id, step_id, event, future = Future.new)
29
30
  executor.event execution_plan_id, step_id, event, future
30
31
  end
32
+
31
33
  end
32
34
  end
33
35
  end
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'dynflow'
2
2
  require 'pp'
3
3
  require 'sinatra'
4
+ require 'yaml'
4
5
 
5
6
  module Dynflow
6
7
  class WebConsole < Sinatra::Base
@@ -24,12 +25,16 @@ module Dynflow
24
25
  settings.world
25
26
  end
26
27
 
28
+ def prettify_value(value)
29
+ YAML.dump(value)
30
+ end
31
+
27
32
  def prettyprint(value)
28
33
  value = prettyprint_references(value)
29
34
  if value
30
- pretty_value = value.pretty_inspect
35
+ pretty_value = prettify_value(value)
31
36
  <<-HTML
32
- <pre class="prettyprint">#{h(pretty_value)}</pre>
37
+ <pre class="prettyprint lang-yaml">#{h(pretty_value)}</pre>
33
38
  HTML
34
39
  else
35
40
  ""
data/lib/dynflow/world.rb CHANGED
@@ -3,7 +3,7 @@ module Dynflow
3
3
  include Algebrick::TypeCheck
4
4
 
5
5
  attr_reader :executor, :persistence, :transaction_adapter, :action_classes, :subscription_index,
6
- :logger_adapter, :options
6
+ :logger_adapter, :options, :middleware
7
7
 
8
8
  def initialize(options_hash = {})
9
9
  @options = default_options.merge options_hash
@@ -13,6 +13,7 @@ module Dynflow
13
13
  @persistence = Persistence.new(self, persistence_adapter)
14
14
  @executor = Type! option_val(:executor), Executors::Abstract
15
15
  @action_classes = option_val(:action_classes)
16
+ @middleware = Middleware::World.new
16
17
  calculate_subscription_index
17
18
 
18
19
  executor.initialized.wait
@@ -47,6 +48,7 @@ module Dynflow
47
48
  # reload actions classes, intended only for devel
48
49
  def reload!
49
50
  @action_classes.map! { |klass| klass.to_s.constantize }
51
+ middleware.clear_cache!
50
52
  calculate_subscription_index
51
53
  end
52
54
 
@@ -74,7 +76,11 @@ module Dynflow
74
76
  execution_plan = plan(action_class, *args)
75
77
  planned = execution_plan.state == :planned
76
78
  finished = if planned
77
- execute(execution_plan.id)
79
+ begin
80
+ execute(execution_plan.id)
81
+ rescue => exception
82
+ Future.new.fail exception
83
+ end
78
84
  else
79
85
  Future.new.resolve(execution_plan)
80
86
  end
@@ -122,15 +128,19 @@ module Dynflow
122
128
  # After the executor is unregistered, the consistency check should be performed
123
129
  # to fix the orphaned plans as well.
124
130
  def consistency_check
125
- abnormal_execution_plans = self.persistence.find_execution_plans filters: { 'state' => %w(running planning) }
131
+ abnormal_execution_plans =
132
+ self.persistence.find_execution_plans filters: { 'state' => %w(running planning) }
126
133
  if abnormal_execution_plans.empty?
127
134
  logger.info 'Clean start.'
128
135
  else
129
136
  format_str = '%36s %10s %10s'
130
137
  message = ['Abnormal execution plans, process was probably killed.',
131
- 'Following ExecutionPlans will be set to paused, admin has to fix them manually.',
138
+ 'Following ExecutionPlans will be set to paused, ',
139
+ 'it should be fixed manually by administrator.',
132
140
  (format format_str, 'ExecutionPlan', 'state', 'result'),
133
- *(abnormal_execution_plans.map { |ep| format format_str, ep.id, ep.state, ep.result })]
141
+ *(abnormal_execution_plans.map do |ep|
142
+ format format_str, ep.id, ep.state, ep.result
143
+ end)]
134
144
 
135
145
  logger.error message.join("\n")
136
146
 
@@ -147,13 +157,23 @@ module Dynflow
147
157
  end
148
158
  end
149
159
 
160
+ # should be called after World is initialized, SimpleWorld does it automatically
161
+ def execute_planned_execution_plans
162
+ planned_execution_plans =
163
+ self.persistence.find_execution_plans filters: { 'state' => %w(planned) }
164
+ planned_execution_plans.each { |ep| execute ep.id }
165
+ end
166
+
150
167
  private
151
168
 
152
169
  def calculate_subscription_index
153
- @subscription_index = action_classes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |klass, index|
154
- next unless klass.subscribe
155
- Array(klass.subscribe).each { |subscribed_class| index[subscribed_class.to_s.constantize] << klass }
156
- end.tap { |o| o.freeze }
170
+ @subscription_index =
171
+ action_classes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |klass, index|
172
+ next unless klass.subscribe
173
+ Array(klass.subscribe).each do |subscribed_class|
174
+ index[subscribed_class.to_s.constantize] << klass
175
+ end
176
+ end.tap { |o| o.freeze }
157
177
  end
158
178
 
159
179
  def option_val(key)
data/test/action_test.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require_relative 'test_helper'
2
- require_relative 'code_workflow_example'
3
2
 
4
3
  module Dynflow
5
4
 
@@ -65,8 +64,8 @@ module Dynflow
65
64
  specify { smart_action_class.all_children.wont_include smarter_action_class.finalize_phase }
66
65
 
67
66
  describe 'World#subscribed_actions' do
68
- event_action_class = CodeWorkflowExample::Triage
69
- subscribed_action_class = CodeWorkflowExample::NotifyAssignee
67
+ event_action_class = Support::CodeWorkflowExample::Triage
68
+ subscribed_action_class = Support::CodeWorkflowExample::NotifyAssignee
70
69
 
71
70
  specify { subscribed_action_class.subscribe.must_equal event_action_class }
72
71
  specify { world.subscribed_actions(event_action_class).must_include subscribed_action_class }
@@ -78,7 +77,7 @@ module Dynflow
78
77
  include WorldInstance
79
78
 
80
79
  let :execution_plan do
81
- id, planned, finished = *world.trigger(CodeWorkflowExample::IncomingIssues, issues_data)
80
+ id, planned, finished = *world.trigger(Support::CodeWorkflowExample::IncomingIssues, issues_data)
82
81
  raise unless planned
83
82
  finished.value
84
83
  end
@@ -90,11 +89,11 @@ module Dynflow
90
89
 
91
90
  let :presenter do
92
91
  execution_plan.actions.find do |action|
93
- action.is_a? CodeWorkflowExample::IncomingIssues
92
+ action.is_a? Support::CodeWorkflowExample::IncomingIssues
94
93
  end
95
94
  end
96
95
 
97
- specify { presenter.action_class.must_equal CodeWorkflowExample::IncomingIssues }
96
+ specify { presenter.action_class.must_equal Support::CodeWorkflowExample::IncomingIssues }
98
97
 
99
98
  it 'allows aggregating data from other actions' do
100
99
  presenter.summary.must_equal(assignees: ["John Doe"])
@@ -1,5 +1,4 @@
1
1
  require_relative 'test_helper'
2
- require_relative 'code_workflow_example'
3
2
 
4
3
  module Dynflow
5
4
  module ExecutionPlanTest
@@ -16,7 +15,7 @@ module Dynflow
16
15
  describe 'serialization' do
17
16
 
18
17
  let :execution_plan do
19
- world.plan(CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
18
+ world.plan(Support::CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
20
19
  end
21
20
 
22
21
  let :deserialized_execution_plan do
@@ -47,7 +46,7 @@ module Dynflow
47
46
  describe '#result' do
48
47
 
49
48
  let :execution_plan do
50
- world.plan(CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
49
+ world.plan(Support::CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
51
50
  end
52
51
 
53
52
  describe 'for error in planning phase' do
@@ -107,7 +106,7 @@ module Dynflow
107
106
 
108
107
  describe 'plan steps' do
109
108
  let :execution_plan do
110
- world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
109
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
111
110
  end
112
111
 
113
112
  it 'stores the information about the sub actions' do
@@ -129,7 +128,7 @@ module Dynflow
129
128
  describe 'persisted action' do
130
129
 
131
130
  let :execution_plan do
132
- world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
131
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
133
132
  end
134
133
 
135
134
  let :action do
@@ -148,7 +147,7 @@ module Dynflow
148
147
 
149
148
  describe 'single dependencies' do
150
149
  let :execution_plan do
151
- world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
150
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
152
151
  end
153
152
 
154
153
  it 'constructs the plan of actions to be executed in run phase' do
@@ -169,7 +168,7 @@ module Dynflow
169
168
 
170
169
  describe 'multi dependencies' do
171
170
  let :execution_plan do
172
- world.plan(CodeWorkflowExample::Commit, 'sha' => 'abc123')
171
+ world.plan(Support::CodeWorkflowExample::Commit, 'sha' => 'abc123')
173
172
  end
174
173
 
175
174
  it 'constructs the plan of actions to be executed in run phase' do
@@ -186,7 +185,7 @@ module Dynflow
186
185
 
187
186
  describe 'sequence and concurrence keyword used' do
188
187
  let :execution_plan do
189
- world.plan(CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
188
+ world.plan(Support::CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
190
189
  end
191
190
 
192
191
  it 'constructs the plan of actions to be executed in run phase' do
@@ -202,7 +201,7 @@ module Dynflow
202
201
 
203
202
  describe 'subscribed action' do
204
203
  let :execution_plan do
205
- world.plan(CodeWorkflowExample::DummyTrigger, {})
204
+ world.plan(Support::CodeWorkflowExample::DummyTrigger, {})
206
205
  end
207
206
 
208
207
  it 'constructs the plan of actions to be executed in run phase' do
@@ -218,7 +217,7 @@ module Dynflow
218
217
  describe 'finalize flow' do
219
218
 
220
219
  let :execution_plan do
221
- world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
220
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
222
221
  end
223
222
 
224
223
  it 'plans the finalize steps in a sequence' do
@@ -237,7 +236,7 @@ module Dynflow
237
236
 
238
237
  describe 'accessing actions results' do
239
238
  let :execution_plan do
240
- world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
239
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
241
240
  end
242
241
 
243
242
  it 'provides the access to the actions data via Action::Presenter' do
@@ -1,5 +1,4 @@
1
1
  require_relative 'test_helper'
2
- require_relative 'code_workflow_example'
3
2
 
4
3
  module Dynflow
5
4
  module ExecutorTest
@@ -28,19 +27,15 @@ module Dynflow
28
27
  { 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
29
28
  end
30
29
 
31
- let :execution_plan do
32
- world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
33
- end
34
-
35
30
  let :failed_execution_plan do
36
- plan = world.plan(CodeWorkflowExample::IncomingIssues, failing_issues_data)
31
+ plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
37
32
  plan = world.execute(plan.id).value
38
33
  plan.state.must_equal :paused
39
34
  plan
40
35
  end
41
36
 
42
37
  let :finalize_failed_execution_plan do
43
- plan = world.plan(CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
38
+ plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
44
39
  plan = world.execute(plan.id).value
45
40
  plan.state.must_equal :paused
46
41
  plan
@@ -58,10 +53,29 @@ module Dynflow
58
53
 
59
54
  describe "after successful planning" do
60
55
 
56
+ let :execution_plan do
57
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
58
+ end
59
+
61
60
  it "is pending" do
62
61
  execution_plan.state.must_equal :planned
63
62
  end
64
63
 
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
71
+
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
65
79
  end
66
80
 
67
81
  describe "after error in planning" do
@@ -85,7 +99,7 @@ module Dynflow
85
99
  describe "when being executed" do
86
100
 
87
101
  let :execution_plan do
88
- world.plan(CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
102
+ world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
89
103
  end
90
104
 
91
105
  before do
@@ -103,7 +117,7 @@ module Dynflow
103
117
  plan.state.must_equal :running
104
118
  triage = plan.steps.values.find do |s|
105
119
  s.is_a?(Dynflow::ExecutionPlan::Steps::RunStep) &&
106
- s.action_class == Dynflow::CodeWorkflowExample::Triage
120
+ s.action_class == Support::CodeWorkflowExample::Triage
107
121
  end
108
122
  triage.state.must_equal :running
109
123
  world.persistence.
@@ -118,23 +132,6 @@ module Dynflow
118
132
  end
119
133
  end
120
134
  end
121
-
122
- describe "when finished successfully" do
123
-
124
- it "is stopped" do
125
- world.execute(execution_plan.id).value.tap do |plan|
126
- plan.state.must_equal :stopped
127
- end
128
- end
129
- end
130
-
131
- describe "when finished with error" do
132
- it "is paused" do
133
- world.execute(failed_execution_plan.id).value.tap do |plan|
134
- plan.state.must_equal :paused
135
- end
136
- end
137
- end
138
135
  end
139
136
 
140
137
  describe "execution of run flow" do
@@ -144,25 +141,51 @@ module Dynflow
144
141
  end
145
142
 
146
143
  let :result do
147
- world.execute(execution_plan.id).value.tap do |result|
148
- raise result if result.is_a? Exception
149
- end
144
+ world.execute(execution_plan.id).value!
150
145
  end
151
146
 
152
147
  after do
153
148
  TestExecutionLog.teardown
154
149
  end
155
150
 
156
- let :persisted_plan do
151
+ def persisted_plan
157
152
  result
158
- world.persistence.load_execution_plan(execution_plan.id)
153
+ super
159
154
  end
160
155
 
161
156
  describe 'cancellable action' do
162
157
 
158
+ describe event_class = Listeners::Serialization::Protocol::Event do
159
+ it 'de/serializes' do
160
+ Klass = Class.new do
161
+ def initialize(v)
162
+ @v = v
163
+ end
164
+
165
+ def to_s
166
+ @v.to_s
167
+ end
168
+
169
+ def ==(other)
170
+ @v == other.instance_variable_get(:@v)
171
+ end
172
+ end
173
+
174
+ object = Klass.new :value
175
+ event = event_class['uuid', 0, object]
176
+ hash = event.to_hash
177
+ json = MultiJson.dump(hash)
178
+ hash_loaded = MultiJson.load(json)
179
+ assert_equal event[:event], event_class.from_hash(hash_loaded)[:event]
180
+ assert_equal event, event_class.from_hash(hash_loaded)
181
+
182
+ ExecutorTest.send :remove_const, :Klass
183
+ end
184
+ end
185
+
163
186
  describe 'successful' do
164
187
  let :execution_plan do
165
- world.plan(CodeWorkflowExample::CancelableSuspended, {})
188
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
166
189
  end
167
190
 
168
191
  it "doesn't cause problems" do
@@ -173,7 +196,7 @@ module Dynflow
173
196
 
174
197
  describe 'canceled' do
175
198
  let :execution_plan do
176
- world.plan(CodeWorkflowExample::CancelableSuspended, { text: 'cancel' })
199
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
177
200
  end
178
201
 
179
202
  it 'cancels' do
@@ -187,7 +210,7 @@ module Dynflow
187
210
 
188
211
  describe 'canceled failed' do
189
212
  let :execution_plan do
190
- world.plan(CodeWorkflowExample::CancelableSuspended, { text: 'cancel fail' })
213
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
191
214
  end
192
215
 
193
216
  it 'fails' do
@@ -199,40 +222,37 @@ module Dynflow
199
222
  action.output[:progress].must_equal 30
200
223
  end
201
224
  end
202
- end
203
-
204
225
 
205
- describe "suspended action" do
206
- let :execution_plan do
207
- world.plan(CodeWorkflowExample::DummySuspended, { :external_task_id => '123' })
208
- end
226
+ if world_method == :remote_world
227
+ describe 'canceled externally' do
228
+ let :execution_plan do
229
+ world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-external' })
230
+ end
209
231
 
210
- it "doesn't cause problems" do
211
- result.result.must_equal :success
212
- result.state.must_equal :stopped
232
+ it 'cancels' do
233
+ finished = world.execute(execution_plan.id)
234
+ sleep 0.05
235
+ world.
236
+ event(execution_plan.id, 2,
237
+ Support::CodeWorkflowExample::CancelableSuspended::Cancel).
238
+ value!.must_equal true
239
+ result = finished.value!
240
+
241
+ result.result.must_equal :success
242
+ result.state.must_equal :stopped
243
+ action = world.persistence.load_action result.steps[2]
244
+ action.output[:progress].must_be :<=, 30
245
+ action.output[:cancelled].must_equal true
246
+ end
247
+ end
213
248
  end
214
249
 
215
- it 'does set times' do
216
- result.started_at.wont_be_nil
217
- result.ended_at.wont_be_nil
218
- result.execution_time.must_be :<, result.real_time
219
- result.execution_time.must_equal(
220
- result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
221
-
222
- plan_step = result.steps[1]
223
- plan_step.started_at.wont_be_nil
224
- plan_step.ended_at.wont_be_nil
225
- plan_step.execution_time.must_equal plan_step.real_time
226
-
227
- run_step = result.steps[2]
228
- run_step.started_at.wont_be_nil
229
- run_step.ended_at.wont_be_nil
230
- run_step.execution_time.must_be :<, run_step.real_time
231
- end
250
+ end
232
251
 
252
+ describe 'suspended action' do
233
253
  describe 'handling errors in setup' do
234
254
  let :execution_plan do
235
- world.plan(CodeWorkflowExample::DummySuspended,
255
+ world.plan(Support::CodeWorkflowExample::DummySuspended,
236
256
  external_task_id: '123',
237
257
  text: 'troll setup')
238
258
  end
@@ -247,6 +267,35 @@ module Dynflow
247
267
  end
248
268
  end
249
269
 
270
+ describe 'running' do
271
+ let :execution_plan do
272
+ world.plan(Support::CodeWorkflowExample::DummySuspended, { :external_task_id => '123' })
273
+ end
274
+
275
+ it "doesn't cause problems" do
276
+ result.result.must_equal :success
277
+ result.state.must_equal :stopped
278
+ end
279
+
280
+ it 'does set times' do
281
+ result.started_at.wont_be_nil
282
+ result.ended_at.wont_be_nil
283
+ result.execution_time.must_be :<, result.real_time
284
+ result.execution_time.must_equal(
285
+ result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
286
+
287
+ plan_step = result.steps[1]
288
+ plan_step.started_at.wont_be_nil
289
+ plan_step.ended_at.wont_be_nil
290
+ plan_step.execution_time.must_equal plan_step.real_time
291
+
292
+ run_step = result.steps[2]
293
+ run_step.started_at.wont_be_nil
294
+ run_step.ended_at.wont_be_nil
295
+ run_step.execution_time.must_be :<, run_step.real_time
296
+ end
297
+ end
298
+
250
299
  describe 'progress' do
251
300
  before do
252
301
  TestPause.setup
@@ -260,7 +309,7 @@ module Dynflow
260
309
 
261
310
  describe 'plan with one action' do
262
311
  let :execution_plan do
263
- world.plan(CodeWorkflowExample::DummySuspended,
312
+ world.plan(Support::CodeWorkflowExample::DummySuspended,
264
313
  { external_task_id: '123',
265
314
  text: 'pause in progress 20%' })
266
315
  end
@@ -275,7 +324,7 @@ module Dynflow
275
324
 
276
325
  describe 'plan with more action' do
277
326
  let :execution_plan do
278
- world.plan(CodeWorkflowExample::DummyHeavyProgress,
327
+ world.plan(Support::CodeWorkflowExample::DummyHeavyProgress,
279
328
  { external_task_id: '123',
280
329
  text: 'pause in progress 20%' })
281
330
  end
@@ -291,7 +340,7 @@ module Dynflow
291
340
 
292
341
  describe 'works when resumed after error' do
293
342
  let :execution_plan do
294
- world.plan(CodeWorkflowExample::DummySuspended,
343
+ world.plan(Support::CodeWorkflowExample::DummySuspended,
295
344
  { external_task_id: '123',
296
345
  text: 'troll progress' })
297
346
  end
@@ -315,7 +364,7 @@ module Dynflow
315
364
  describe "action with empty flows" do
316
365
 
317
366
  let :execution_plan do
318
- world.plan(CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
367
+ world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
319
368
  assert_equal plan.run_flow.size, 0
320
369
  assert_equal plan.finalize_flow.size, 0
321
370
  end.tap do |w|
@@ -338,7 +387,7 @@ module Dynflow
338
387
  describe 'action with empty run flow but some finalize flow' do
339
388
 
340
389
  let :execution_plan do
341
- world.plan(CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
390
+ world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
342
391
  assert_equal plan.run_flow.size, 0
343
392
  assert_equal plan.finalize_flow.size, 1
344
393
  end
@@ -351,24 +400,29 @@ module Dynflow
351
400
 
352
401
  end
353
402
 
354
- it "runs all the steps in the run flow" do
355
- assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
356
- Dynflow::Flows::Concurrence
357
- Dynflow::Flows::Sequence
358
- 4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
359
- 7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
360
- 9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
361
- Dynflow::Flows::Sequence
362
- 13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
363
- 16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
364
- 18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
365
- EXECUTED_RUN_FLOW
403
+ describe 'running' do
404
+ let :execution_plan do
405
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
406
+ end
407
+
408
+ it "runs all the steps in the run flow" do
409
+ assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
410
+ Dynflow::Flows::Concurrence
411
+ Dynflow::Flows::Sequence
412
+ 4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
413
+ 7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
414
+ 9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
415
+ Dynflow::Flows::Sequence
416
+ 13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
417
+ 16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
418
+ 18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
419
+ EXECUTED_RUN_FLOW
420
+ end
366
421
  end
367
422
 
368
423
  end
369
424
 
370
425
  describe "execution of finalize flow" do
371
-
372
426
  before do
373
427
  TestExecutionLog.setup
374
428
  result = world.execute(execution_plan.id).value
@@ -380,17 +434,19 @@ module Dynflow
380
434
  end
381
435
 
382
436
  describe "when run flow successful" do
437
+ let :execution_plan do
438
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
439
+ end
383
440
 
384
441
  it "runs all the steps in the finalize flow" do
385
- assert_finalized(Dynflow::CodeWorkflowExample::IncomingIssues,
442
+ assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
386
443
  { "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
387
- assert_finalized(Dynflow::CodeWorkflowExample::Triage,
444
+ assert_finalized(Support::CodeWorkflowExample::Triage,
388
445
  { "author" => "Peter Smith", "text" => "Failing test" })
389
446
  end
390
447
  end
391
448
 
392
449
  describe "when run flow failed" do
393
-
394
450
  let :execution_plan do
395
451
  failed_execution_plan
396
452
  end
@@ -425,7 +481,7 @@ module Dynflow
425
481
  resumed_execution_plan.result.must_equal :success
426
482
 
427
483
  run_triages = TestExecutionLog.run.find_all do |action_class, input|
428
- action_class == CodeWorkflowExample::Triage
484
+ action_class == Support::CodeWorkflowExample::Triage
429
485
  end
430
486
  run_triages.size.must_equal 1
431
487
 
@@ -443,6 +499,7 @@ module Dynflow
443
499
  end
444
500
 
445
501
  end
502
+
446
503
  describe "re-execution of run flow after fix in finalize phase" do
447
504
 
448
505
  after do
@@ -466,7 +523,7 @@ module Dynflow
466
523
  resumed_execution_plan.result.must_equal :success
467
524
 
468
525
  run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
469
- action_class == CodeWorkflowExample::Triage
526
+ action_class == Support::CodeWorkflowExample::Triage
470
527
  end
471
528
  run_triages.size.must_equal 2
472
529
 
@@ -502,7 +559,7 @@ module Dynflow
502
559
  resumed_execution_plan.result.must_equal :success
503
560
 
504
561
  run_triages = TestExecutionLog.run.find_all do |action_class, input|
505
- action_class == CodeWorkflowExample::Triage
562
+ action_class == Support::CodeWorkflowExample::Triage
506
563
  end
507
564
  run_triages.size.must_equal 0
508
565
 
@@ -531,6 +588,10 @@ module Dynflow
531
588
  end
532
589
 
533
590
  describe 'FlowManager' do
591
+ let :execution_plan do
592
+ world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
593
+ end
594
+
534
595
  let(:manager) { Executors::Parallel::FlowManager.new execution_plan, execution_plan.run_flow }
535
596
 
536
597
  def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
@@ -645,13 +706,13 @@ module Dynflow
645
706
  if which == :normal_world
646
707
  it 'executes until its done when terminating' do
647
708
  $slow_actions_done = 0
648
- world.trigger(CodeWorkflowExample::Slow, 0.02)
709
+ world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
649
710
  world.terminate.wait
650
711
  $slow_actions_done.must_equal 1
651
712
  end
652
713
 
653
714
  it 'executes until its done when terminating even suspended' do
654
- result = world.trigger(CodeWorkflowExample::DummySuspended,
715
+ result = world.trigger(Support::CodeWorkflowExample::DummySuspended,
655
716
  external_task_id: '123',
656
717
  text: 'none')
657
718
  world.terminate.wait
@@ -661,12 +722,14 @@ module Dynflow
661
722
 
662
723
  it 'does not accept new work' do
663
724
  assert world.terminate.wait
664
- -> { world.trigger(CodeWorkflowExample::Slow, 0.02) }.must_raise Dynflow::Error
725
+ result = world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
726
+ result.planned.must_equal true
727
+ -> { result.finished.value! }.must_raise Dynflow::Error
665
728
  end
666
729
 
667
730
  it 'it terminates when no work' do
668
731
  skip 'blocks occasionally' if which == :remote_world # FIXME
669
- world.trigger(CodeWorkflowExample::Slow, 0.02).finished.wait
732
+ world.trigger(Support::CodeWorkflowExample::Slow, 0.02).finished.wait
670
733
  assert world.terminate.wait
671
734
  end
672
735