Skip to content

Commit 5c06f86

Browse files
committed
Tests
1 parent 1b1c46c commit 5c06f86

File tree

3 files changed

+133
-19
lines changed

3 files changed

+133
-19
lines changed

test/future_execution_test.rb

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,135 @@ module FutureExecutionTest
190190
_(serializer.args).must_equal args
191191
end
192192
end
193+
194+
describe 'execution plan chaining' do
195+
let(:world) do
196+
WorldFactory.create_world { |config| config.auto_rescue = true }
197+
end
198+
199+
before do
200+
@preexisting = world.persistence.find_ready_delayed_plans(Time.now).map(&:execution_plan_uuid)
201+
end
202+
203+
it 'chains two execution plans' do
204+
plan1 = world.plan(Support::DummyExample::Dummy)
205+
plan2 = world.chain(plan1.id, Support::DummyExample::Dummy)
206+
207+
Concurrent::Promises.resolvable_future.tap do |promise|
208+
world.execute(plan1.id, promise)
209+
end.wait
210+
211+
plan1 = world.persistence.load_execution_plan(plan1.id)
212+
_(plan1.state).must_equal :stopped
213+
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| @preexisting.include? p.execution_plan_uuid }
214+
_(ready.count).must_equal 1
215+
_(ready.first.execution_plan_uuid).must_equal plan2.execution_plan_id
216+
end
217+
218+
it 'chains onto multiple execution plans and waits for all to finish' do
219+
plan1 = world.plan(Support::DummyExample::Dummy)
220+
plan2 = world.plan(Support::DummyExample::Dummy)
221+
plan3 = world.chain([plan1.id, plan2.id], Support::DummyExample::Dummy)
222+
223+
# Execute and complete plan1
224+
Concurrent::Promises.resolvable_future.tap do |promise|
225+
world.execute(plan1.id, promise)
226+
end.wait
227+
228+
plan1 = world.persistence.load_execution_plan(plan1.id)
229+
_(plan1.state).must_equal :stopped
230+
231+
# plan3 should still not be ready because plan2 hasn't finished yet
232+
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| @preexisting.include? p.execution_plan_uuid }
233+
_(ready.count).must_equal 0
234+
235+
# Execute and complete plan2
236+
Concurrent::Promises.resolvable_future.tap do |promise|
237+
world.execute(plan2.id, promise)
238+
end.wait
239+
240+
plan2 = world.persistence.load_execution_plan(plan2.id)
241+
_(plan2.state).must_equal :stopped
242+
243+
# Now plan3 should be ready since both plan1 and plan2 are complete
244+
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| @preexisting.include? p.execution_plan_uuid }
245+
_(ready.count).must_equal 1
246+
_(ready.first.execution_plan_uuid).must_equal plan3.execution_plan_id
247+
end
248+
249+
it 'cancels the chained plan if the prerequisite fails' do
250+
plan1 = world.plan(Support::DummyExample::FailingDummy)
251+
plan2 = world.chain(plan1.id, Support::DummyExample::Dummy)
252+
253+
Concurrent::Promises.resolvable_future.tap do |promise|
254+
world.execute(plan1.id, promise)
255+
end.wait
256+
257+
plan1 = world.persistence.load_execution_plan(plan1.id)
258+
_(plan1.state).must_equal :stopped
259+
_(plan1.result).must_equal :error
260+
261+
# plan2 will appear in ready delayed plans
262+
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| @preexisting.include? p.execution_plan_uuid }
263+
_(ready.map(&:execution_plan_uuid)).must_equal [plan2.execution_plan_id]
264+
265+
# Process the delayed plan through the director
266+
work_item = Dynflow::Director::PlanningWorkItem.new(plan2.execution_plan_id, :default, world.id)
267+
work_item.world = world
268+
work_item.execute
269+
270+
# Now plan2 should be stopped with error due to failed dependency
271+
plan2 = world.persistence.load_execution_plan(plan2.execution_plan_id)
272+
_(plan2.state).must_equal :stopped
273+
_(plan2.result).must_equal :error
274+
_(plan2.errors.first.message).must_match(/preqrequisite execution plans failed/)
275+
_(plan2.errors.first.message).must_match(/#{plan1.id}/)
276+
end
277+
278+
it 'cancels the chained plan if at least one prerequisite fails' do
279+
plan1 = world.plan(Support::DummyExample::Dummy)
280+
plan2 = world.plan(Support::DummyExample::FailingDummy)
281+
plan3 = world.chain([plan1.id, plan2.id], Support::DummyExample::Dummy)
282+
283+
# Execute and complete plan1 successfully
284+
Concurrent::Promises.resolvable_future.tap do |promise|
285+
world.execute(plan1.id, promise)
286+
end.wait
287+
288+
plan1 = world.persistence.load_execution_plan(plan1.id)
289+
_(plan1.state).must_equal :stopped
290+
_(plan1.result).must_equal :success
291+
292+
# plan3 should still not be ready because plan2 hasn't finished yet
293+
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| @preexisting.include? p.execution_plan_uuid }
294+
_(ready).must_equal []
295+
296+
# Execute and complete plan2 with failure
297+
Concurrent::Promises.resolvable_future.tap do |promise|
298+
world.execute(plan2.id, promise)
299+
end.wait
300+
301+
plan2 = world.persistence.load_execution_plan(plan2.id)
302+
_(plan2.state).must_equal :stopped
303+
_(plan2.result).must_equal :error
304+
305+
# plan3 will now appear in ready delayed plans even though one prerequisite failed
306+
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| @preexisting.include? p.execution_plan_uuid }
307+
_(ready.map(&:execution_plan_uuid)).must_equal [plan3.execution_plan_id]
308+
309+
# Process the delayed plan through the director
310+
work_item = Dynflow::Director::PlanningWorkItem.new(plan3.execution_plan_id, :default, world.id)
311+
work_item.world = world
312+
work_item.execute
313+
314+
# Now plan3 should be stopped with error due to failed dependency
315+
plan3 = world.persistence.load_execution_plan(plan3.execution_plan_id)
316+
_(plan3.state).must_equal :stopped
317+
_(plan3.result).must_equal :error
318+
_(plan3.errors.first.message).must_match(/preqrequisite execution plans failed/)
319+
_(plan3.errors.first.message).must_match(/#{plan2.id}/)
320+
end
321+
end
193322
end
194323
end
195324
end

test/support/dummy_example.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ def run; end
3131

3232
class FailingDummy < Dynflow::Action
3333
def run; raise 'error'; end
34+
35+
def rescue_strategy
36+
Dynflow::Action::Rescue::Fail
37+
end
3438
end
3539

3640
class Slow < Dynflow::Action

test/world_test.rb

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,6 @@ module WorldTest
5151
_(terminated_event.resolved?).must_equal true
5252
end
5353
end
54-
55-
describe '#chain' do
56-
it 'chains two execution plans' do
57-
plan1 = world.plan(Support::DummyExample::Dummy)
58-
plan2 = world.chain(plan1.id, Support::DummyExample::Dummy)
59-
60-
preexisting = world.persistence.find_ready_delayed_plans(Time.now).map(&:execution_plan_uuid)
61-
62-
done = Concurrent::Promises.resolvable_future
63-
world.execute(plan1.id, done)
64-
done.wait
65-
66-
plan1 = world.persistence.load_execution_plan(plan1.id)
67-
_(plan1.state).must_equal :stopped
68-
ready = world.persistence.find_ready_delayed_plans(Time.now).reject { |p| preexisting.include? p.execution_plan_uuid }
69-
_(ready.count).must_equal 1
70-
_(ready.first.execution_plan_uuid).must_equal plan2.execution_plan_id
71-
end
72-
end
7354
end
7455
end
7556
end

0 commit comments

Comments
 (0)