dynflow 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/dynflow.rb +2 -1
- data/lib/dynflow/action.rb +271 -101
- data/lib/dynflow/action/format.rb +4 -4
- data/lib/dynflow/action/progress.rb +8 -8
- data/lib/dynflow/execution_plan.rb +14 -15
- data/lib/dynflow/execution_plan/output_reference.rb +56 -23
- data/lib/dynflow/execution_plan/steps/abstract.rb +1 -3
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +0 -17
- data/lib/dynflow/execution_plan/steps/error.rb +35 -11
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -1
- data/lib/dynflow/execution_plan/steps/plan_step.rb +8 -3
- data/lib/dynflow/execution_plan/steps/run_step.rb +1 -1
- data/lib/dynflow/listeners/socket.rb +0 -1
- data/lib/dynflow/middleware.rb +0 -1
- data/lib/dynflow/middleware/world.rb +1 -1
- data/lib/dynflow/persistence.rb +2 -5
- data/lib/dynflow/serializable.rb +24 -11
- data/lib/dynflow/testing/assertions.rb +5 -5
- data/lib/dynflow/testing/dummy_execution_plan.rb +1 -1
- data/lib/dynflow/testing/dummy_planned_action.rb +2 -1
- data/lib/dynflow/testing/dummy_world.rb +4 -0
- data/lib/dynflow/testing/factories.rb +18 -10
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world.rb +35 -21
- data/test/action_test.rb +50 -76
- data/test/clock_test.rb +4 -4
- data/test/execution_plan_test.rb +1 -1
- data/test/executor_test.rb +3 -2
- data/test/remote_via_socket_test.rb +11 -9
- data/test/support/code_workflow_example.rb +2 -3
- data/test/test_helper.rb +1 -1
- data/test/testing_test.rb +3 -3
- metadata +2 -8
- data/lib/dynflow/action/finalize_phase.rb +0 -20
- data/lib/dynflow/action/flow_phase.rb +0 -44
- data/lib/dynflow/action/plan_phase.rb +0 -87
- data/lib/dynflow/action/presenter.rb +0 -51
- data/lib/dynflow/action/run_phase.rb +0 -45
- 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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -6,7 +6,8 @@ module Dynflow
|
|
6
6
|
|
7
7
|
def initialize(klass)
|
8
8
|
mimic! klass
|
9
|
-
@output = ExecutionPlan::OutputReference.new(
|
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)
|
@@ -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.
|
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
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
38
|
+
Match! plan_action.phase, Action::Plan, Action::Run
|
34
39
|
step = DummyStep.new
|
35
|
-
run_action = if
|
36
|
-
plan_action.
|
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
|
-
|
66
|
+
Match! run_action.phase, Action::Run
|
60
67
|
step = DummyStep.new
|
61
|
-
finalize_action = run_action.
|
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
|
-
|
85
|
+
Match! action.phase, Action::Run
|
78
86
|
action.world.clock.progress
|
79
87
|
action.world.executor.progress
|
80
88
|
end
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/world.rb
CHANGED
@@ -52,21 +52,35 @@ module Dynflow
|
|
52
52
|
calculate_subscription_index
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
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
|
63
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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)
|
data/test/action_test.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
77
|
-
|
49
|
+
describe Action::Present do
|
50
|
+
include WorldInstance
|
78
51
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
69
|
+
specify { presenter.class.must_equal Support::CodeWorkflowExample::IncomingIssues }
|
97
70
|
|
98
|
-
|
99
|
-
|
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
|
data/test/clock_test.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
42
|
+
assert_in_delta 0.3, Time.now - start, 0.08
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'works under stress' do
|
data/test/execution_plan_test.rb
CHANGED
@@ -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.
|
245
|
+
action.phase.must_equal Action::Present
|
246
246
|
end
|
247
247
|
end
|
248
248
|
end
|
data/test/executor_test.rb
CHANGED
@@ -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
|
727
|
-
|
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
|
47
|
-
|
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
|
57
|
-
|
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
|
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
|
-
|
105
|
-
|
106
|
-
|
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
|
119
|
+
result.must_be :planned?
|
118
120
|
|
119
121
|
terminate listener
|
120
122
|
|