dynflow 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/lib/dynflow.rb +2 -1
  2. data/lib/dynflow/action.rb +271 -101
  3. data/lib/dynflow/action/format.rb +4 -4
  4. data/lib/dynflow/action/progress.rb +8 -8
  5. data/lib/dynflow/execution_plan.rb +14 -15
  6. data/lib/dynflow/execution_plan/output_reference.rb +56 -23
  7. data/lib/dynflow/execution_plan/steps/abstract.rb +1 -3
  8. data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +0 -17
  9. data/lib/dynflow/execution_plan/steps/error.rb +35 -11
  10. data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -1
  11. data/lib/dynflow/execution_plan/steps/plan_step.rb +8 -3
  12. data/lib/dynflow/execution_plan/steps/run_step.rb +1 -1
  13. data/lib/dynflow/listeners/socket.rb +0 -1
  14. data/lib/dynflow/middleware.rb +0 -1
  15. data/lib/dynflow/middleware/world.rb +1 -1
  16. data/lib/dynflow/persistence.rb +2 -5
  17. data/lib/dynflow/serializable.rb +24 -11
  18. data/lib/dynflow/testing/assertions.rb +5 -5
  19. data/lib/dynflow/testing/dummy_execution_plan.rb +1 -1
  20. data/lib/dynflow/testing/dummy_planned_action.rb +2 -1
  21. data/lib/dynflow/testing/dummy_world.rb +4 -0
  22. data/lib/dynflow/testing/factories.rb +18 -10
  23. data/lib/dynflow/version.rb +1 -1
  24. data/lib/dynflow/world.rb +35 -21
  25. data/test/action_test.rb +50 -76
  26. data/test/clock_test.rb +4 -4
  27. data/test/execution_plan_test.rb +1 -1
  28. data/test/executor_test.rb +3 -2
  29. data/test/remote_via_socket_test.rb +11 -9
  30. data/test/support/code_workflow_example.rb +2 -3
  31. data/test/test_helper.rb +1 -1
  32. data/test/testing_test.rb +3 -3
  33. metadata +2 -8
  34. data/lib/dynflow/action/finalize_phase.rb +0 -20
  35. data/lib/dynflow/action/flow_phase.rb +0 -44
  36. data/lib/dynflow/action/plan_phase.rb +0 -87
  37. data/lib/dynflow/action/presenter.rb +0 -51
  38. data/lib/dynflow/action/run_phase.rb +0 -45
  39. data/lib/dynflow/middleware/action.rb +0 -9
@@ -20,7 +20,7 @@ module Dynflow
20
20
 
21
21
  # assert that +assert_actioned_plan+ was planned by +action+
22
22
  def assert_action_planed(action, planned_action_class)
23
- Type! action, Dynflow::Action::PlanPhase
23
+ Match! action.phase, Action::Plan
24
24
  Match! action.state, :success
25
25
  found = action.execution_plan.planned_plan_steps.
26
26
  select { |a| a.is_a?(planned_action_class) }
@@ -31,7 +31,7 @@ module Dynflow
31
31
 
32
32
  # assert that +action+ has run-phase planned
33
33
  def assert_run_phase(action, input = nil, &block)
34
- Type! action, Dynflow::Action::PlanPhase
34
+ Match! action.phase, Action::Plan
35
35
  Match! action.state, :success
36
36
  action.execution_plan.planned_run_steps.must_include action
37
37
  action.input.must_equal input.stringify_keys if input
@@ -40,21 +40,21 @@ module Dynflow
40
40
 
41
41
  # refute that +action+ has run-phase planned
42
42
  def refute_run_phase(action)
43
- Type! action, Dynflow::Action::PlanPhase
43
+ Match! action.phase, Action::Plan
44
44
  Match! action.state, :success
45
45
  action.execution_plan.planned_run_steps.wont_include action
46
46
  end
47
47
 
48
48
  # assert that +action+ has finalize-phase planned
49
49
  def assert_finalize_phase(action)
50
- Type! action, Dynflow::Action::PlanPhase
50
+ Match! action.phase, Action::Plan
51
51
  Match! action.state, :success
52
52
  action.execution_plan.planned_finalize_steps.must_include action
53
53
  end
54
54
 
55
55
  # refute that +action+ has finalize-phase planned
56
56
  def refute_finalize_phase(action)
57
- Type! action, Dynflow::Action::PlanPhase
57
+ Match! action.phase, Action::Plan
58
58
  Match! action.state, :success
59
59
  action.execution_plan.planned_finalize_steps.wont_include action
60
60
  end
@@ -7,7 +7,7 @@ module Dynflow
7
7
  attr_reader :id, :planned_plan_steps, :planned_run_steps, :planned_finalize_steps
8
8
 
9
9
  def initialize
10
- @id = Testing.get_id
10
+ @id = Testing.get_id.to_s
11
11
  @planned_plan_steps = []
12
12
  @planned_run_steps = []
13
13
  @planned_finalize_steps = []
@@ -6,7 +6,8 @@ module Dynflow
6
6
 
7
7
  def initialize(klass)
8
8
  mimic! klass
9
- @output = ExecutionPlan::OutputReference.new(Testing.get_id, Testing.get_id)
9
+ @output = ExecutionPlan::OutputReference.new(
10
+ Testing.get_id.to_s, Testing.get_id, Testing.get_id)
10
11
  end
11
12
 
12
13
  def execute(execution_plan, event, *args)
@@ -30,6 +30,10 @@ module Dynflow
30
30
  executor.event execution_plan_id, step_id, event, future
31
31
  end
32
32
 
33
+ def persistence
34
+ nil
35
+ end
36
+
33
37
  end
34
38
  end
35
39
  end
@@ -7,17 +7,22 @@ module Dynflow
7
7
  def create_action(action_class, trigger = nil)
8
8
  execution_plan = DummyExecutionPlan.new
9
9
  step = DummyStep.new
10
- action_class.plan_phase.new(
10
+ action_class.new(
11
11
  { step: DummyStep.new,
12
+ execution_plan: execution_plan,
13
+ trigger: trigger,
12
14
  execution_plan_id: execution_plan.id,
13
15
  id: Testing.get_id,
14
- plan_step_id: step.id },
15
- execution_plan, trigger)
16
+ phase: Action::Plan,
17
+ plan_step_id: step.id,
18
+ run_step_id: nil,
19
+ finalize_step_id: nil },
20
+ execution_plan.world)
16
21
  end
17
22
 
18
23
  # @return [Action::PlanPhase]
19
24
  def plan_action(plan_action, *args, &block)
20
- Type! plan_action, Dynflow::Action::PlanPhase
25
+ Match! plan_action.phase, Action::Plan
21
26
 
22
27
  plan_action.execute *args, &block
23
28
  raise plan_action.error if plan_action.error
@@ -30,15 +35,17 @@ module Dynflow
30
35
 
31
36
  # @return [Action::RunPhase]
32
37
  def run_action(plan_action, event = nil, &stubbing)
33
- Type! plan_action, Dynflow::Action::PlanPhase, Dynflow::Action::RunPhase
38
+ Match! plan_action.phase, Action::Plan, Action::Run
34
39
  step = DummyStep.new
35
- run_action = if Dynflow::Action::PlanPhase === plan_action
36
- plan_action.action_class.run_phase.new(
40
+ run_action = if plan_action.phase == Action::Plan
41
+ plan_action.class.new(
37
42
  { step: step,
38
43
  execution_plan_id: plan_action.execution_plan_id,
39
44
  id: plan_action.id,
40
45
  plan_step_id: plan_action.plan_step_id,
41
46
  run_step_id: step.id,
47
+ finalize_step_id: nil,
48
+ phase: Action::Run,
42
49
  input: plan_action.input },
43
50
  plan_action.world)
44
51
 
@@ -56,15 +63,16 @@ module Dynflow
56
63
 
57
64
  # @return [Action::FinalizePhase]
58
65
  def finalize_action(run_action, &stubbing)
59
- Type! run_action, Dynflow::Action::RunPhase
66
+ Match! run_action.phase, Action::Run
60
67
  step = DummyStep.new
61
- finalize_action = run_action.action_class.finalize_phase.new(
68
+ finalize_action = run_action.class.new(
62
69
  { step: step,
63
70
  execution_plan_id: run_action.execution_plan_id,
64
71
  id: run_action.id,
65
72
  plan_step_id: run_action.plan_step_id,
66
73
  run_step_id: run_action.run_step_id,
67
74
  finalize_step_id: step.id,
75
+ phase: Action::Finalize,
68
76
  input: run_action.input },
69
77
  run_action.world)
70
78
 
@@ -74,7 +82,7 @@ module Dynflow
74
82
  end
75
83
 
76
84
  def progress_action_time action
77
- Type! action, Dynflow::Action::RunPhase
85
+ Match! action.phase, Action::Run
78
86
  action.world.clock.progress
79
87
  action.world.executor.progress
80
88
  end
@@ -1,3 +1,3 @@
1
1
  module Dynflow
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -52,21 +52,35 @@ module Dynflow
52
52
  calculate_subscription_index
53
53
  end
54
54
 
55
- class TriggerResult
56
- include Algebrick::TypeCheck
55
+ TriggerResult = Algebrick.type do
56
+ # Returned by #trigger when planning fails.
57
+ PlaningFailed = type { fields! execution_plan_id: String, error: Exception }
58
+ # Returned by #trigger when planning is successful but execution fails to start.
59
+ ExecutionFailed = type { fields! execution_plan_id: String, error: Exception }
60
+ # Returned by #trigger when planning is successful, #future will resolve after
61
+ # ExecutionPlan is executed.
62
+ Triggered = type { fields! execution_plan_id: String, future: Future }
57
63
 
58
- attr_reader :execution_plan_id, :planned, :finished
59
- alias_method :id, :execution_plan_id
60
- alias_method :planned?, :planned
64
+ variants PlaningFailed, ExecutionFailed, Triggered
65
+ end
66
+
67
+ module TriggerResult
68
+ def planned?
69
+ match self, PlaningFailed => false, ExecutionFailed => true, Triggered => true
70
+ end
71
+
72
+ def triggered?
73
+ match self, PlaningFailed => false, ExecutionFailed => false, Triggered => true
74
+ end
61
75
 
62
- def initialize(execution_plan_id, planned, finished)
63
- @execution_plan_id = Type! execution_plan_id, String
64
- @planned = Type! planned, TrueClass, FalseClass
65
- @finished = Type! finished, Future
76
+ def id
77
+ execution_plan_id
66
78
  end
79
+ end
67
80
 
68
- def to_a
69
- [execution_plan_id, planned, finished]
81
+ module Triggered
82
+ def finished
83
+ future
70
84
  end
71
85
  end
72
86
 
@@ -75,16 +89,16 @@ module Dynflow
75
89
  def trigger(action_class, *args)
76
90
  execution_plan = plan(action_class, *args)
77
91
  planned = execution_plan.state == :planned
78
- finished = if planned
79
- begin
80
- execute(execution_plan.id)
81
- rescue => exception
82
- Future.new.fail exception
83
- end
84
- else
85
- Future.new.resolve(execution_plan)
86
- end
87
- return TriggerResult.new(execution_plan.id, planned, finished)
92
+
93
+ if planned
94
+ begin
95
+ Triggered[execution_plan.id, execute(execution_plan.id)]
96
+ rescue => exception
97
+ ExecutionFailed[execution_plan.id, exception]
98
+ end
99
+ else
100
+ PlaningFailed[execution_plan.id, execution_plan.errors.first.exception]
101
+ end
88
102
  end
89
103
 
90
104
  def event(execution_plan_id, step_id, event, future = Future.new)
@@ -1,102 +1,76 @@
1
1
  require_relative 'test_helper'
2
2
 
3
3
  module Dynflow
4
-
5
-
6
- describe Action::Missing do
4
+ describe 'action' do
7
5
  include WorldInstance
8
6
 
9
- let :action_data do
10
- { class: 'RenamedAction',
11
- id: 123,
12
- input: {},
13
- execution_plan_id: 123 }
14
- end
15
-
16
- subject do
17
- state_holder = ExecutionPlan::Steps::Abstract.allocate
18
- state_holder.set_state :success, true
19
- Action.from_hash(action_data, :run_phase, state_holder, world)
20
- end
21
-
22
- specify { subject.action_class.name.must_equal 'RenamedAction' }
23
- specify { assert subject.is_a? Action }
24
- end
25
-
26
- describe "extending action phase" do
27
-
28
- module TestExtending
29
-
30
- module Extension
31
- def new_method
32
- end
7
+ describe Action::Missing do
8
+
9
+ let :action_data do
10
+ { class: 'RenamedAction',
11
+ id: 1,
12
+ input: {},
13
+ output: {},
14
+ execution_plan_id: '123',
15
+ plan_step_id: 2,
16
+ run_step_id: 3,
17
+ finalize_step_id: nil,
18
+ phase: Action::Run }
33
19
  end
34
20
 
35
- class ExtendedAction < Dynflow::Action
36
- def self.phase_modules
37
- super.merge(run_phase: [Extension]) { |key, old, new| old + new }.freeze
38
- end
21
+ subject do
22
+ step = ExecutionPlan::Steps::Abstract.allocate
23
+ step.set_state :success, true
24
+ Action.from_hash(action_data.merge(step: step), world)
39
25
  end
40
- end
41
26
 
42
- it "is possible to extend the action just for some phase" do
43
- refute TestExtending::ExtendedAction.plan_phase.instance_methods.include?(:new_method)
44
- refute Dynflow::Action.run_phase.instance_methods.include?(:new_method)
45
- assert TestExtending::ExtendedAction.run_phase.instance_methods.include?(:new_method)
27
+ specify { subject.class.name.must_equal 'RenamedAction' }
28
+ specify { assert subject.is_a? Action }
46
29
  end
47
- end
48
30
 
31
+ describe 'children' do
49
32
 
50
- describe 'children' do
51
- include WorldInstance
52
-
53
- smart_action_class = Class.new(Dynflow::Action)
54
- smarter_action_class = Class.new(smart_action_class)
33
+ smart_action_class = Class.new(Dynflow::Action)
34
+ smarter_action_class = Class.new(smart_action_class)
55
35
 
56
- specify { refute smart_action_class.phase? }
57
- specify { refute smarter_action_class.phase? }
58
- specify { assert smarter_action_class.plan_phase.phase? }
36
+ specify { smart_action_class.all_children.must_include smarter_action_class }
37
+ specify { smart_action_class.all_children.size.must_equal 1 }
59
38
 
60
- specify { smart_action_class.all_children.must_include smarter_action_class }
61
- specify { smart_action_class.all_children.size.must_equal 1 }
62
- specify { smart_action_class.all_children.wont_include smarter_action_class.plan_phase }
63
- specify { smart_action_class.all_children.wont_include smarter_action_class.run_phase }
64
- specify { smart_action_class.all_children.wont_include smarter_action_class.finalize_phase }
39
+ describe 'World#subscribed_actions' do
40
+ event_action_class = Support::CodeWorkflowExample::Triage
41
+ subscribed_action_class = Support::CodeWorkflowExample::NotifyAssignee
65
42
 
66
- describe 'World#subscribed_actions' do
67
- event_action_class = Support::CodeWorkflowExample::Triage
68
- subscribed_action_class = Support::CodeWorkflowExample::NotifyAssignee
69
-
70
- specify { subscribed_action_class.subscribe.must_equal event_action_class }
71
- specify { world.subscribed_actions(event_action_class).must_include subscribed_action_class }
72
- specify { world.subscribed_actions(event_action_class).size.must_equal 1 }
43
+ specify { subscribed_action_class.subscribe.must_equal event_action_class }
44
+ specify { world.subscribed_actions(event_action_class).must_include subscribed_action_class }
45
+ specify { world.subscribed_actions(event_action_class).size.must_equal 1 }
46
+ end
73
47
  end
74
- end
75
48
 
76
- describe Action::Presenter do
77
- include WorldInstance
49
+ describe Action::Present do
50
+ include WorldInstance
78
51
 
79
- let :execution_plan do
80
- id, planned, finished = *world.trigger(Support::CodeWorkflowExample::IncomingIssues, issues_data)
81
- raise unless planned
82
- finished.value
83
- end
52
+ let :execution_plan do
53
+ result = world.trigger(Support::CodeWorkflowExample::IncomingIssues, issues_data)
54
+ result.must_be :planned?
55
+ result.finished.value
56
+ end
84
57
 
85
- let :issues_data do
86
- [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
87
- { 'author' => 'John Doe', 'text' => 'Internal server error' }]
88
- end
58
+ let :issues_data do
59
+ [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
60
+ { 'author' => 'John Doe', 'text' => 'Internal server error' }]
61
+ end
89
62
 
90
- let :presenter do
91
- execution_plan.actions.find do |action|
92
- action.is_a? Support::CodeWorkflowExample::IncomingIssues
63
+ let :presenter do
64
+ execution_plan.actions.find do |action|
65
+ action.is_a? Support::CodeWorkflowExample::IncomingIssues
66
+ end
93
67
  end
94
- end
95
68
 
96
- specify { presenter.action_class.must_equal Support::CodeWorkflowExample::IncomingIssues }
69
+ specify { presenter.class.must_equal Support::CodeWorkflowExample::IncomingIssues }
97
70
 
98
- it 'allows aggregating data from other actions' do
99
- presenter.summary.must_equal(assignees: ["John Doe"])
71
+ it 'allows aggregating data from other actions' do
72
+ presenter.summary.must_equal(assignees: ["John Doe"])
73
+ end
100
74
  end
101
75
  end
102
76
  end
@@ -22,7 +22,7 @@ describe clock_class do
22
22
  clock.ping q, 0.1, o = Object.new
23
23
  assert_equal o, q.pop
24
24
  finish = Time.now
25
- assert_in_delta 0.1, finish - start, 0.02
25
+ assert_in_delta 0.1, finish - start, 0.08
26
26
  end
27
27
 
28
28
  it 'pongs on expected times' do
@@ -35,11 +35,11 @@ describe clock_class do
35
35
  clock.ping q, 0.2, :c
36
36
 
37
37
  assert_equal :b, q.pop
38
- assert_in_delta 0.1, Time.now - start, 0.02
38
+ assert_in_delta 0.1, Time.now - start, 0.08
39
39
  assert_equal :c, q.pop
40
- assert_in_delta 0.2, Time.now - start, 0.02
40
+ assert_in_delta 0.2, Time.now - start, 0.08
41
41
  assert_equal :a, q.pop
42
- assert_in_delta 0.3, Time.now - start, 0.02
42
+ assert_in_delta 0.3, Time.now - start, 0.08
43
43
  end
44
44
 
45
45
  it 'works under stress' do
@@ -242,7 +242,7 @@ module Dynflow
242
242
  it 'provides the access to the actions data via Action::Presenter' do
243
243
  execution_plan.actions.size.must_equal 9
244
244
  execution_plan.actions.each do |action|
245
- action.must_be_kind_of Action::Presenter
245
+ action.phase.must_equal Action::Present
246
246
  end
247
247
  end
248
248
  end
@@ -723,8 +723,9 @@ module Dynflow
723
723
  it 'does not accept new work' do
724
724
  assert world.terminate.wait
725
725
  result = world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
726
- result.planned.must_equal true
727
- -> { result.finished.value! }.must_raise Dynflow::Error
726
+ result.must_be :planned?
727
+ result.wont_be :triggered?
728
+ result.error.must_be_kind_of Dynflow::Error
728
729
  end
729
730
 
730
731
  it 'it terminates when no work' do
@@ -43,8 +43,9 @@ describe 'remote communication' do
43
43
  it 'raises when not connected' do
44
44
  remote_world = create_remote_world
45
45
  result = remote_world.trigger Support::CodeWorkflowExample::Commit, 'sha'
46
- result.planned.must_equal true
47
- -> { result.finished.value! }.must_raise Dynflow::Error
46
+ result.must_be :planned?
47
+ result.wont_be :triggered?
48
+ result.error.must_be_kind_of Dynflow::Error
48
49
 
49
50
  terminate remote_world
50
51
  end
@@ -53,8 +54,9 @@ describe 'remote communication' do
53
54
  specify do
54
55
  remote_world = create_remote_world
55
56
  result = remote_world.trigger Support::CodeWorkflowExample::Commit, 'sha'
56
- result.planned.must_equal true
57
- -> { result.finished.value! }.must_raise Dynflow::Error
57
+ result.must_be :planned?
58
+ result.wont_be :triggered?
59
+ result.error.must_be_kind_of Dynflow::Error
58
60
 
59
61
  remote_world.persistence.load_execution_plan(result.id).state.must_equal :planned
60
62
 
@@ -81,7 +83,7 @@ describe 'remote communication' do
81
83
  remote_world: remote_world = create_remote_world }
82
84
 
83
85
  result = remote_world.trigger Support::CodeWorkflowExample::Commit, 'sha'
84
- result.planned.must_equal true
86
+ result.must_be :planned?
85
87
  result.finished.value!.must_be_kind_of Dynflow::ExecutionPlan
86
88
 
87
89
  terminate *objects.values_at(*order)
@@ -101,9 +103,9 @@ describe 'remote communication' do
101
103
 
102
104
  terminate rmw1
103
105
 
104
- -> { rmw1.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value! }.
105
- must_raise Dynflow::Error
106
- rmw2.trigger(Support::CodeWorkflowExample::Commit, 'sha').finished.value!
106
+ refute rmw1.trigger(Support::CodeWorkflowExample::Commit, 'sha').triggered?
107
+ rmw2.trigger(Support::CodeWorkflowExample::Commit, 'sha').
108
+ finished.value!.must_be_kind_of Dynflow::ExecutionPlan
107
109
 
108
110
  terminate rmw2, listener, world
109
111
  end
@@ -114,7 +116,7 @@ describe 'remote communication' do
114
116
  remote_world = create_remote_world
115
117
 
116
118
  result = remote_world.trigger(Support::CodeWorkflowExample::Slow, 2)
117
- result.planned.must_equal true
119
+ result.must_be :planned?
118
120
 
119
121
  terminate listener
120
122