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