dynflow 1.2.1 → 1.2.2

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.
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