dynflow 0.2.0 → 0.3.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 (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