dynflow 0.3.0 → 0.4.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 (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