dynflow 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '00091e6971f6c6bbcc56f3a03564b2de6f437db1985375caa2171d3f9859a0f7'
4
- data.tar.gz: e48ae6152594e70147b362b1e7ae4b0923cc645d38005af56b69c9e6f1cd5a31
3
+ metadata.gz: ddba15f840b26c5148fcebf8a0b69ff22abc0bba1fe70d2fa02e32722003011b
4
+ data.tar.gz: 4c7c6a7c3d23e1e0684ef61fa44ccf1cd44ff76cfba9c0e8e3b0587b9674ffeb
5
5
  SHA512:
6
- metadata.gz: e00b1eb7cf2bd103fe79d8a0e3046143f77548c380b2504b4fc3093b5c4a4929160f704f781e0565eacefb7cf493bd7b5ec6a8f5c2a5b0490e3f521285513a73
7
- data.tar.gz: 1d3aef7335990a1ff72fecd0b36bb444227f0f216aaf44677d66d453a8d29b470a58e7d2ccee4349ba341a12a4ca14306d6d4c5167f5ac7e375c36fed19362cd
6
+ metadata.gz: a5b836d767ef81131f217d82467d96410c63140bb38e247489b33b9386df2ddb30aff56da3b42bff216fd6a419f714176d0e111415e6793c3c534bdbfcbd6938
7
+ data.tar.gz: 01a74517eb6500b5e986759d017f2886c1a5032d81e026d1e41450033935dfab9c342bf4ced411da56b2cf3daefd79a82a1137d97030567bbcbe517a4e9599cb
@@ -1,5 +1,8 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
+ AllCops:
4
+ TargetRubyVersion: 2.2
5
+
3
6
  # Cop supports --auto-correct.
4
7
  # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
5
8
  Style/ExtraSpacing:
@@ -76,16 +76,16 @@ module Dynflow
76
76
  end
77
77
 
78
78
  # Helper for creating sub plans
79
- def trigger(*args)
79
+ def trigger(action_class, *args)
80
80
  if uses_concurrency_control
81
- trigger_with_concurrency_control(*args)
81
+ trigger_with_concurrency_control(action_class, *args)
82
82
  else
83
- world.trigger { world.plan_with_caller(self, *args) }
83
+ world.trigger { world.plan_with_options(action_class: action_class, args: args, caller_action: self) }
84
84
  end
85
85
  end
86
86
 
87
- def trigger_with_concurrency_control(*args)
88
- record = world.plan_with_caller(self, *args)
87
+ def trigger_with_concurrency_control(action_class, *args)
88
+ record = world.plan_with_options(action_class: action_class, args: args, caller_action: self)
89
89
  records = [[record.id], []]
90
90
  records.reverse! unless record.state == :planned
91
91
  @world.throttle_limiter.handle_plans!(execution_plan_id, *records).first
@@ -3,15 +3,19 @@ module Dynflow
3
3
  module QueueAdapters
4
4
  module QueueMethods
5
5
  def enqueue(job)
6
- ::Rails.application.dynflow.world.trigger(JobWrapper, job.serialize).tap do |plan|
7
- job.provider_job_id = plan.id
6
+ ::Rails.application.dynflow.world.trigger do |world|
7
+ job.provider_job_id = job.job_id
8
+ world.plan_with_options(id: job.provider_job_id, action_class: JobWrapper, args: [job.serialize])
8
9
  end
9
10
  end
10
11
 
11
12
  def enqueue_at(job, timestamp)
12
- ::Rails.application.dynflow.world.delay(JobWrapper, { :start_at => Time.at(timestamp) }, job.serialize).tap do |plan|
13
- job.provider_job_id = plan.id
14
- end
13
+ job.provider_job_id = job.job_id
14
+ ::Rails.application.dynflow.world
15
+ .delay_with_options(id: job.provider_job_id,
16
+ action_class: JobWrapper,
17
+ delay_options: { :start_at => Time.at(timestamp) },
18
+ args: [job.serialize])
15
19
  end
16
20
  end
17
21
 
@@ -33,13 +37,13 @@ module Dynflow
33
37
 
34
38
  def plan(attributes)
35
39
  input[:job_class] = attributes['job_class']
36
- input[:job_arguments] = attributes['arguments']
37
40
  input[:queue] = attributes['queue_name']
41
+ input[:job_data] = attributes
38
42
  plan_self
39
43
  end
40
44
 
41
45
  def run
42
- input[:job_class].constantize.perform_now(*input[:job_arguments])
46
+ ::ActiveJob::Base.execute(input[:job_data])
43
47
  end
44
48
 
45
49
  def label
@@ -119,7 +119,9 @@ module Dynflow
119
119
  def work_failed(work)
120
120
  if (manager = @execution_plan_managers[work.execution_plan_id])
121
121
  manager.terminate
122
- finish_manager(manager)
122
+ # Don't try to store when the execution plan went missing
123
+ plan_missing = @world.persistence.find_execution_plans(:filters => { uuid: work.execution_plan_id }).empty?
124
+ finish_manager(manager, store: !plan_missing)
123
125
  end
124
126
  end
125
127
 
@@ -144,24 +146,26 @@ module Dynflow
144
146
  def unless_done(manager, work_items)
145
147
  return [] unless manager
146
148
  if manager.done?
147
- finish_manager(manager)
148
- return []
149
+ try_to_rescue(manager) || finish_manager(manager)
149
150
  else
150
151
  return work_items
151
152
  end
152
153
  end
153
154
 
154
- def finish_manager(manager)
155
+ def try_to_rescue(manager)
156
+ rescue!(manager) if rescue?(manager)
157
+ end
158
+
159
+ def finish_manager(manager, store: true)
160
+ update_execution_plan_state(manager) if store
161
+ return []
162
+ ensure
155
163
  @execution_plan_managers.delete(manager.execution_plan.id)
156
- if rescue?(manager)
157
- rescue!(manager)
158
- else
159
- set_future(manager)
160
- end
164
+ set_future(manager)
161
165
  end
162
166
 
163
167
  def rescue?(manager)
164
- if @world.terminating? || !(@world.auto_rescue && manager.execution_plan.state == :paused)
168
+ if @world.terminating? || !(@world.auto_rescue && manager.execution_plan.error?)
165
169
  false
166
170
  elsif !@rescued_steps.key?(manager.execution_plan.id)
167
171
  # we have not rescued this plan yet
@@ -179,11 +183,12 @@ module Dynflow
179
183
  # to put this logic of making sure we don't run rescues in endless loop
180
184
  @rescued_steps[manager.execution_plan.id] ||= Set.new
181
185
  @rescued_steps[manager.execution_plan.id].merge(manager.execution_plan.failed_steps.map(&:id))
182
- rescue_plan_id = manager.execution_plan.rescue_plan_id
183
- if rescue_plan_id
184
- @world.executor.execute(rescue_plan_id, manager.future, false)
186
+ new_state = manager.execution_plan.prepare_for_rescue
187
+ if new_state == :running
188
+ return manager.restart
185
189
  else
186
- set_future(manager)
190
+ manager.execution_plan.state = new_state
191
+ return false
187
192
  end
188
193
  end
189
194
 
@@ -207,6 +212,28 @@ module Dynflow
207
212
  nil
208
213
  end
209
214
 
215
+ def update_execution_plan_state(manager)
216
+ execution_plan = manager.execution_plan
217
+ case execution_plan.state
218
+ when :running
219
+ if execution_plan.error?
220
+ execution_plan.execution_history.add('pause execution', @world.id)
221
+ execution_plan.update_state(:paused)
222
+ elsif manager.done?
223
+ execution_plan.execution_history.add('finish execution', @world.id)
224
+ execution_plan.update_state(:stopped)
225
+ end
226
+ # If the state is marked as running without errors but manager is not done,
227
+ # we let the invalidation procedure to handle re-execution on other executor
228
+ when :paused
229
+ execution_plan.execution_history.add('pause execution', @world.id)
230
+ execution_plan.save
231
+ when :stopped
232
+ execution_plan.execution_history.add('finish execution', @world.id)
233
+ execution_plan.save
234
+ end
235
+ end
236
+
210
237
  def set_future(manager)
211
238
  @rescued_steps.delete(manager.execution_plan.id)
212
239
  manager.future.fulfill manager.execution_plan
@@ -24,6 +24,12 @@ module Dynflow
24
24
  start_run or start_finalize or finish
25
25
  end
26
26
 
27
+ def restart
28
+ @run_manager = nil
29
+ @finalize_manager = nil
30
+ start
31
+ end
32
+
27
33
  def prepare_next_step(step)
28
34
  StepWorkItem.new(execution_plan.id, step, step.queue).tap do |work|
29
35
  @running_steps_manager.add(step, work)
@@ -97,8 +103,6 @@ module Dynflow
97
103
  end
98
104
 
99
105
  def finish
100
- execution_plan.execution_history.add('finish execution', @world.id)
101
- @execution_plan.update_state(execution_plan.error? ? :paused : :stopped)
102
106
  return no_work
103
107
  end
104
108
 
@@ -69,7 +69,7 @@ module Dynflow
69
69
 
70
70
  # all params with default values are part of *private* api
71
71
  def initialize(world,
72
- id = SecureRandom.uuid,
72
+ id = nil,
73
73
  label = nil,
74
74
  state = :pending,
75
75
  root_plan_step = nil,
@@ -81,7 +81,7 @@ module Dynflow
81
81
  execution_time = nil,
82
82
  real_time = 0.0,
83
83
  execution_history = ExecutionHistory.new)
84
-
84
+ id ||= SecureRandom.uuid
85
85
  @id = Type! id, String
86
86
  @world = Type! world, World
87
87
  @label = Type! label, String, NilClass
@@ -199,16 +199,17 @@ module Dynflow
199
199
  persistence.find_execution_plan_counts(filters: { 'caller_execution_plan_id' => self.id })
200
200
  end
201
201
 
202
- def rescue_plan_id
202
+ def prepare_for_rescue
203
203
  case rescue_strategy
204
204
  when Action::Rescue::Pause
205
- nil
205
+ :paused
206
206
  when Action::Rescue::Fail
207
- update_state :stopped
208
- nil
207
+ :stopped
209
208
  when Action::Rescue::Skip
210
209
  failed_steps.each { |step| self.skip(step) }
211
- self.id
210
+ :running
211
+ else
212
+ :paused
212
213
  end
213
214
  end
214
215
 
@@ -232,14 +233,6 @@ module Dynflow
232
233
  self.steps.values.find_all {|step| states.include?(step.state) }
233
234
  end
234
235
 
235
- def rescue_from_error
236
- if rescue_plan_id = self.rescue_plan_id
237
- @world.execute(rescue_plan_id)
238
- else
239
- raise Errors::RescueError, 'Unable to rescue from the error'
240
- end
241
- end
242
-
243
236
  def generate_action_id
244
237
  @last_action_id ||= 0
245
238
  @last_action_id += 1
@@ -0,0 +1,4 @@
1
+ Sequel.migration do
2
+ # Placeholder for 011_add_uuid_column.rb - it was readded in
3
+ # 018_add_uuid_column.rb with fixed check for postgresql
4
+ end
@@ -38,7 +38,7 @@ end
38
38
 
39
39
  Sequel.migration do
40
40
  up do
41
- if database_type == :postgresql
41
+ if database_type.to_s.include?('postgres')
42
42
  with_foreign_key_recreation do
43
43
  to_uuid :dynflow_execution_plans, :uuid
44
44
  to_uuid :dynflow_actions, :execution_plan_uuid
@@ -49,7 +49,7 @@ Sequel.migration do
49
49
  end
50
50
 
51
51
  down do
52
- if database_type == :postgresql
52
+ if database_type.to_s.include?('postgres')
53
53
  with_foreign_key_recreation do
54
54
  from_uuid :dynflow_execution_plans, :uuid
55
55
  from_uuid :dynflow_actions, :execution_plan_uuid
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '1.2.1'.freeze
2
+ VERSION = '1.2.2'.freeze
3
3
  end
@@ -183,26 +183,23 @@ module Dynflow
183
183
  end
184
184
  end
185
185
 
186
- def delay(*args)
187
- delay_with_caller(nil, *args)
186
+ def delay(action_class, delay_options, *args)
187
+ delay_with_options(action_class: action_class, args: args, delay_options: delay_options)
188
188
  end
189
189
 
190
- def delay_with_caller(caller_action, action_class, delay_options, *args)
190
+ def delay_with_options(action_class:, args:, delay_options:, id: nil, caller_action: nil)
191
191
  raise 'No action_class given' if action_class.nil?
192
- execution_plan = ExecutionPlan.new(self)
192
+ execution_plan = ExecutionPlan.new(self, id)
193
193
  execution_plan.delay(caller_action, action_class, delay_options, *args)
194
194
  Scheduled[execution_plan.id]
195
195
  end
196
196
 
197
197
  def plan(action_class, *args)
198
- ExecutionPlan.new(self).tap do |execution_plan|
199
- execution_plan.prepare(action_class)
200
- execution_plan.plan(*args)
201
- end
198
+ plan_with_options(action_class: action_class, args: args)
202
199
  end
203
200
 
204
- def plan_with_caller(caller_action, action_class, *args)
205
- ExecutionPlan.new(self).tap do |execution_plan|
201
+ def plan_with_options(action_class:, args:, id: nil, caller_action: nil)
202
+ ExecutionPlan.new(self, id).tap do |execution_plan|
206
203
  execution_plan.prepare(action_class, caller_action: caller_action)
207
204
  execution_plan.plan(*args)
208
205
  end
@@ -9,6 +9,7 @@ module Dynflow
9
9
  # @return [void]
10
10
  def invalidate(world)
11
11
  Type! world, Coordinator::ClientWorld, Coordinator::ExecutorWorld
12
+
12
13
  coordinator.acquire(Coordinator::WorldInvalidationLock.new(self, world)) do
13
14
  if world.is_a? Coordinator::ExecutorWorld
14
15
  old_execution_locks = coordinator.find_locks(class: Coordinator::ExecutionLock.name,
@@ -47,8 +48,8 @@ module Dynflow
47
48
  coordinator.release(execution_lock)
48
49
 
49
50
  if plan.error?
50
- rescue_id = plan.rescue_plan_id
51
- execute(rescue_id) if rescue_id
51
+ new_state = plan.prepare_for_rescue
52
+ execute(plan.id) if new_state == :running
52
53
  else
53
54
  if coordinator.find_worlds(true).any? # Check if there are any executors
54
55
  client_dispatcher.tell([:dispatch_request,
@@ -142,13 +142,7 @@ module Dynflow
142
142
  describe 'auto execute' do
143
143
 
144
144
  before do
145
- client_world.persistence.find_execution_plans({}).each do |plan|
146
- # make sure we don't handle plans from previous tests
147
- # TODO: delete the plans instead, once we have
148
- # https://github.com/Dynflow/dynflow/pull/141 merged
149
- plan.set_state(:stopped, true)
150
- plan.save
151
- end
145
+ client_world.persistence.delete_execution_plans({})
152
146
  end
153
147
 
154
148
  it "prevents from running the auto-execution twice" do
@@ -198,7 +192,7 @@ module Dynflow
198
192
  plan = client_world.persistence.load_execution_plan(triggered.id)
199
193
  plan.state.must_equal :paused
200
194
  expected_history = [['start execution', executor_world.id],
201
- ['finish execution', executor_world.id]]
195
+ ['pause execution', executor_world.id]]
202
196
  plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
203
197
  end
204
198
  end
@@ -318,10 +312,11 @@ module Dynflow
318
312
  plan
319
313
  end
320
314
 
315
+ let(:sample_uuid) { '11111111-2222-3333-4444-555555555555' }
321
316
  let(:valid_plan) { plan_in_state :running }
322
317
  let(:invalid_plan) { plan_in_state :stopped }
323
318
  let(:valid_lock) { Coordinator::SingletonActionLock.new('MyClass1', valid_plan.id) }
324
- let(:invalid_lock) { Coordinator::SingletonActionLock.new('MyClass2', 'plan-id') }
319
+ let(:invalid_lock) { Coordinator::SingletonActionLock.new('MyClass2', sample_uuid) }
325
320
  let(:invalid_lock2) { Coordinator::SingletonActionLock.new('MyClass3', invalid_plan.id) }
326
321
 
327
322
  it 'unlocks orphaned singleton action locks' do
@@ -8,10 +8,13 @@ module Dynflow
8
8
 
9
9
  def perform(msg)
10
10
  puts "This job says #{msg}"
11
+ puts "provider_job_id is #{provider_job_id}"
11
12
  end
12
13
  end
13
14
 
14
15
  describe 'running jobs' do
16
+ include TestHelpers
17
+
15
18
  let :world do
16
19
  WorldFactory.create_world
17
20
  end
@@ -48,9 +51,13 @@ module Dynflow
48
51
  job = nil
49
52
  out, = capture_subprocess_io do
50
53
  job = SampleJob.perform_later 'hello'
54
+ wait_for do
55
+ plan = world.persistence.load_execution_plan(job.provider_job_id)
56
+ plan.state == :stopped
57
+ end
51
58
  end
52
- assert world.persistence.load_execution_plan(job.provider_job_id)
53
59
  assert_match(/Enqueued Dynflow::SampleJob/, out)
60
+ assert_match(/provider_job_id is #{job.provider_job_id}/, out)
54
61
  end
55
62
 
56
63
  it 'schedules job in the future' do
@@ -191,6 +191,19 @@ module Dynflow
191
191
  end
192
192
  end
193
193
 
194
+ describe 'custom plan id' do
195
+ let(:sample_uuid) { '60366107-9910-4815-a6c6-bc45ee2ea2b8' }
196
+ let :execution_plan do
197
+ world.plan_with_options(action_class: Support::CodeWorkflowExample::IncomingIssues,
198
+ args: [issues_data],
199
+ id: sample_uuid)
200
+ end
201
+
202
+ it 'allows setting custom id for the execution plan' do
203
+ execution_plan.id.must_equal sample_uuid
204
+ end
205
+ end
206
+
194
207
  describe 'planning algorithm' do
195
208
 
196
209
  describe 'single dependencies' do
@@ -138,6 +138,7 @@ module Dynflow
138
138
  wait_for('execution plan removed from executor') do
139
139
  !director.current_execution_plan_ids.include?(execution_plan.id)
140
140
  end
141
+ world.persistence.find_execution_plans(filters: { uuid: [execution_plan.id] }).must_be :empty?
141
142
  end
142
143
  end
143
144
  end
@@ -15,125 +15,99 @@ module Dynflow
15
15
  end
16
16
 
17
17
  let :rescued_plan do
18
- execution_plan.rescue_from_error.value
18
+ world.persistence.load_execution_plan(execution_plan.id)
19
19
  end
20
20
 
21
- describe 'of simple skippable action in run phase' do
21
+ describe 'no auto rescue' do
22
+ describe 'of simple skippable action in run phase' do
22
23
 
23
- let :execution_plan do
24
- execute(Example::ActionWithSkip, 1, :error_on_run)
25
- end
24
+ let :execution_plan do
25
+ execute(Example::ActionWithSkip, 1, :error_on_run)
26
+ end
26
27
 
27
- it 'suggests skipping the action' do
28
- execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
29
- end
28
+ it 'suggests skipping the action' do
29
+ execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
30
+ end
30
31
 
31
- it 'skips the action and continues' do
32
- rescued_plan.state.must_equal :stopped
33
- rescued_plan.result.must_equal :warning
34
- rescued_plan.entry_action.output[:message].
35
- must_equal "skipped because some error as you wish"
32
+ it "doesn't rescue" do
33
+ rescued_plan.state.must_equal :paused
34
+ end
36
35
  end
37
- end
38
36
 
39
- describe 'of simple skippable action in finalize phase' do
37
+ describe 'of simple skippable action in finalize phase' do
40
38
 
41
- let :execution_plan do
42
- execute(Example::ActionWithSkip, 1, :error_on_finalize)
43
- end
39
+ let :execution_plan do
40
+ execute(Example::ActionWithSkip, 1, :error_on_finalize)
41
+ end
44
42
 
45
- it 'suggests skipping the action' do
46
- execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
47
- end
43
+ it 'suggests skipping the action' do
44
+ execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
45
+ end
48
46
 
49
- it 'skips the action and continues' do
50
- rescued_plan.state.must_equal :stopped
51
- rescued_plan.result.must_equal :warning
52
- rescued_plan.entry_action.output[:message].must_equal "Been here"
47
+ it "doesn't rescue" do
48
+ rescued_plan.state.must_equal :paused
49
+ end
53
50
  end
54
51
 
55
- end
52
+ describe 'of complex action with skips in run phase' do
56
53
 
57
- describe 'of complex action with skips in run phase' do
58
-
59
- let :execution_plan do
60
- execute(Example::ComplexActionWithSkip, :error_on_run)
61
- end
54
+ let :execution_plan do
55
+ execute(Example::ComplexActionWithSkip, :error_on_run)
56
+ end
62
57
 
63
- it 'suggests skipping the action' do
64
- execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
65
- end
58
+ it 'suggests skipping the action' do
59
+ execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
60
+ end
66
61
 
67
- it 'skips the action and continues' do
68
- rescued_plan.state.must_equal :stopped
69
- rescued_plan.result.must_equal :warning
70
- skipped_action = rescued_plan.actions.find do |action|
71
- action.run_step && action.run_step.state == :skipped
62
+ it "doesn't rescue" do
63
+ rescued_plan.state.must_equal :paused
72
64
  end
73
- skipped_action.output[:message].must_equal "skipped because some error as you wish"
74
65
  end
75
66
 
76
- end
77
-
78
- describe 'of complex action with skips in finalize phase' do
67
+ describe 'of complex action with skips in finalize phase' do
79
68
 
80
- let :execution_plan do
81
- execute(Example::ComplexActionWithSkip, :error_on_finalize)
82
- end
69
+ let :execution_plan do
70
+ execute(Example::ComplexActionWithSkip, :error_on_finalize)
71
+ end
83
72
 
84
- it 'suggests skipping the action' do
85
- execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
86
- end
73
+ it 'suggests skipping the action' do
74
+ execution_plan.rescue_strategy.must_equal Action::Rescue::Skip
75
+ end
87
76
 
88
- it 'skips the action and continues' do
89
- # we need to rescue twice for two errors in sequence
90
- rescued_plan = execution_plan.rescue_from_error.value
91
- rescued_plan = rescued_plan.rescue_from_error.value
92
- rescued_plan.state.must_equal :stopped
93
- rescued_plan.result.must_equal :warning
94
- skipped_action = rescued_plan.actions.find do |action|
95
- action.steps.find { |step| step && step.state == :skipped }
77
+ it "doesn't rescue" do
78
+ rescued_plan.state.must_equal :paused
96
79
  end
97
- skipped_action.output[:message].must_equal "Been here"
98
80
  end
99
81
 
100
- end
101
-
102
- describe 'of complex action without skips' do
82
+ describe 'of complex action without skips' do
103
83
 
104
- let :execution_plan do
105
- execute(Example::ComplexActionWithoutSkip, :error_on_run)
106
- end
84
+ let :execution_plan do
85
+ execute(Example::ComplexActionWithoutSkip, :error_on_run)
86
+ end
107
87
 
108
- it 'suggests pausing the plan' do
109
- execution_plan.rescue_strategy.must_equal Action::Rescue::Pause
110
- end
88
+ it 'suggests pausing the plan' do
89
+ execution_plan.rescue_strategy.must_equal Action::Rescue::Pause
90
+ end
111
91
 
112
- it 'fails rescuing' do
113
- lambda { rescued_plan }.must_raise Errors::RescueError
92
+ it "doesn't rescue" do
93
+ rescued_plan.state.must_equal :paused
94
+ end
114
95
  end
115
96
 
116
- end
97
+ describe 'of complex action with fail' do
117
98
 
118
- describe 'of complex action with fail' do
119
-
120
- let :execution_plan do
121
- execute(Example::ComplexActionWithFail, :error_on_run)
122
- end
99
+ let :execution_plan do
100
+ execute(Example::ComplexActionWithFail, :error_on_run)
101
+ end
123
102
 
124
- it 'suggests failing the plan' do
125
- execution_plan.rescue_strategy.must_equal Action::Rescue::Fail
126
- end
103
+ it 'suggests failing the plan' do
104
+ execution_plan.rescue_strategy.must_equal Action::Rescue::Fail
105
+ end
127
106
 
128
- it 'fails rescuing' do
129
- proc { rescued_plan }.must_raise Errors::RescueError
130
- execution_plan.state.must_equal :stopped
131
- execution_plan.result.must_equal :error
132
- execution_plan.steps_in_state(:success).count.must_equal 6
133
- execution_plan.steps_in_state(:pending).count.must_equal 6
134
- execution_plan.steps_in_state(:error).count.must_equal 1
107
+ it "doesn't rescue" do
108
+ rescued_plan.state.must_equal :paused
109
+ end
135
110
  end
136
-
137
111
  end
138
112
 
139
113
  describe 'auto rescue' do
@@ -144,8 +118,32 @@ module Dynflow
144
118
  end
145
119
  end
146
120
 
147
- describe 'of plan with skips' do
121
+ describe 'of simple skippable action in run phase' do
122
+ let :execution_plan do
123
+ execute(Example::ActionWithSkip, 1, :error_on_run)
124
+ end
125
+
126
+ it 'skips the action and continues' do
127
+ rescued_plan.state.must_equal :stopped
128
+ rescued_plan.result.must_equal :warning
129
+ rescued_plan.entry_action.output[:message].
130
+ must_equal "skipped because some error as you wish"
131
+ end
132
+ end
148
133
 
134
+ describe 'of simple skippable action in finalize phase' do
135
+ let :execution_plan do
136
+ execute(Example::ActionWithSkip, 1, :error_on_finalize)
137
+ end
138
+
139
+ it 'skips the action and continues' do
140
+ rescued_plan.state.must_equal :stopped
141
+ rescued_plan.result.must_equal :warning
142
+ rescued_plan.entry_action.output[:message].must_equal "Been here"
143
+ end
144
+ end
145
+
146
+ describe 'of plan with skips' do
149
147
  let :execution_plan do
150
148
  execute(Example::ComplexActionWithSkip, :error_on_run)
151
149
  end
@@ -153,12 +151,29 @@ module Dynflow
153
151
  it 'skips the action and continues automatically' do
154
152
  execution_plan.state.must_equal :stopped
155
153
  execution_plan.result.must_equal :warning
154
+ skipped_action = rescued_plan.actions.find do |action|
155
+ action.run_step && action.run_step.state == :skipped
156
+ end
157
+ skipped_action.output[:message].must_equal "skipped because some error as you wish"
156
158
  end
159
+ end
157
160
 
161
+ describe 'of complex action with skips in finalize phase' do
162
+ let :execution_plan do
163
+ execute(Example::ComplexActionWithSkip, :error_on_finalize)
164
+ end
165
+
166
+ it 'skips the action and continues' do
167
+ rescued_plan.state.must_equal :stopped
168
+ rescued_plan.result.must_equal :warning
169
+ skipped_action = rescued_plan.actions.find do |action|
170
+ action.steps.find { |step| step && step.state == :skipped }
171
+ end
172
+ skipped_action.output[:message].must_equal "Been here"
173
+ end
158
174
  end
159
175
 
160
176
  describe 'of plan faild on auto-rescue' do
161
-
162
177
  let :execution_plan do
163
178
  execute(Example::ActionWithSkip, 1, :error_on_skip)
164
179
  end
@@ -167,11 +182,9 @@ module Dynflow
167
182
  execution_plan.state.must_equal :paused
168
183
  execution_plan.result.must_equal :error
169
184
  end
170
-
171
185
  end
172
186
 
173
187
  describe 'of plan without skips' do
174
-
175
188
  let :execution_plan do
176
189
  execute(Example::ComplexActionWithoutSkip, :error_on_run)
177
190
  end
@@ -179,12 +192,13 @@ module Dynflow
179
192
  it 'skips the action and continues automatically' do
180
193
  execution_plan.state.must_equal :paused
181
194
  execution_plan.result.must_equal :error
195
+ expected_history = [['start execution', world.id],
196
+ ['pause execution', world.id]]
197
+ execution_plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
182
198
  end
183
-
184
199
  end
185
200
 
186
201
  describe 'of plan with fail' do
187
-
188
202
  let :execution_plan do
189
203
  execute(Example::ComplexActionWithFail, :error_on_run)
190
204
  end
@@ -195,6 +209,9 @@ module Dynflow
195
209
  execution_plan.steps_in_state(:success).count.must_equal 6
196
210
  execution_plan.steps_in_state(:pending).count.must_equal 6
197
211
  execution_plan.steps_in_state(:error).count.must_equal 1
212
+ expected_history = [['start execution', world.id],
213
+ ['finish execution', world.id]]
214
+ execution_plan.execution_history.map { |h| [h.name, h.world_id] }.must_equal(expected_history)
198
215
  end
199
216
  end
200
217
 
@@ -225,11 +225,11 @@ module TestHelpers
225
225
  def assert_plan_reexecuted(plan)
226
226
  assert_equal :stopped, plan.state
227
227
  assert_equal :success, plan.result
228
- assert_equal plan.execution_history.map(&:name),
229
- ['start execution',
228
+ assert_equal ['start execution',
230
229
  'terminate execution',
231
230
  'start execution',
232
- 'finish execution']
231
+ 'finish execution'],
232
+ plan.execution_history.map(&:name)
233
233
  refute_equal plan.execution_history.first.world_id, plan.execution_history.to_a.last.world_id
234
234
  end
235
235
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Necas
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-01-24 00:00:00.000000000 Z
12
+ date: 2019-02-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -481,13 +481,14 @@ files:
481
481
  - lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb
482
482
  - lib/dynflow/persistence_adapters/sequel_migrations/009_fix_mysql_data_length.rb
483
483
  - lib/dynflow/persistence_adapters/sequel_migrations/010_add_execution_plans_label.rb
484
- - lib/dynflow/persistence_adapters/sequel_migrations/011_add_uuid_column.rb
484
+ - lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb
485
485
  - lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb
486
486
  - lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb
487
487
  - lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb
488
488
  - lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb
489
489
  - lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb
490
490
  - lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb
491
+ - lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb
491
492
  - lib/dynflow/rails.rb
492
493
  - lib/dynflow/rails/configuration.rb
493
494
  - lib/dynflow/rails/daemon.rb