dynflow 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/lib/dynflow.rb +1 -0
- data/lib/dynflow/action.rb +5 -3
- data/lib/dynflow/action/finalize_phase.rb +3 -1
- data/lib/dynflow/action/plan_phase.rb +4 -2
- data/lib/dynflow/action/run_phase.rb +3 -1
- data/lib/dynflow/daemon.rb +1 -0
- data/lib/dynflow/execution_plan.rb +6 -4
- data/lib/dynflow/executors/abstract.rb +12 -0
- data/lib/dynflow/executors/parallel.rb +16 -35
- data/lib/dynflow/executors/parallel/core.rb +13 -8
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +21 -29
- data/lib/dynflow/executors/parallel/flow_manager.rb +0 -3
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +5 -3
- data/lib/dynflow/executors/parallel/sequential_manager.rb +6 -2
- data/lib/dynflow/executors/parallel/worker.rb +5 -4
- data/lib/dynflow/executors/remote_via_socket.rb +7 -2
- data/lib/dynflow/executors/remote_via_socket/core.rb +66 -32
- data/lib/dynflow/future.rb +1 -1
- data/lib/dynflow/listeners/abstract.rb +4 -0
- data/lib/dynflow/listeners/serialization.rb +42 -8
- data/lib/dynflow/listeners/socket.rb +49 -19
- data/lib/dynflow/middleware.rb +46 -0
- data/lib/dynflow/middleware/action.rb +9 -0
- data/lib/dynflow/middleware/register.rb +32 -0
- data/lib/dynflow/middleware/resolver.rb +63 -0
- data/lib/dynflow/middleware/stack.rb +29 -0
- data/lib/dynflow/middleware/world.rb +58 -0
- data/lib/dynflow/simple_world.rb +1 -0
- data/lib/dynflow/testing/dummy_world.rb +3 -1
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +7 -2
- data/lib/dynflow/world.rb +29 -9
- data/test/action_test.rb +5 -6
- data/test/execution_plan_test.rb +10 -11
- data/test/executor_test.rb +152 -89
- data/test/middleware_test.rb +109 -0
- data/test/remote_via_socket_test.rb +166 -0
- data/test/{code_workflow_example.rb → support/code_workflow_example.rb} +39 -30
- data/test/support/middleware_example.rb +132 -0
- data/test/test_helper.rb +18 -16
- data/test/testing_test.rb +4 -3
- data/test/web_console_test.rb +1 -2
- metadata +16 -4
data/lib/dynflow/simple_world.rb
CHANGED
@@ -4,13 +4,14 @@ module Dynflow
|
|
4
4
|
extend Mimic
|
5
5
|
mimic! World
|
6
6
|
|
7
|
-
attr_reader :clock, :executor
|
7
|
+
attr_reader :clock, :executor, :middleware
|
8
8
|
attr_accessor :action
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@logger_adapter = Testing.logger_adapter
|
12
12
|
@clock = ManagedClock.new
|
13
13
|
@executor = DummyExecutor.new(self)
|
14
|
+
@middleware = Middleware::World.new
|
14
15
|
end
|
15
16
|
|
16
17
|
def action_logger
|
@@ -28,6 +29,7 @@ module Dynflow
|
|
28
29
|
def event(execution_plan_id, step_id, event, future = Future.new)
|
29
30
|
executor.event execution_plan_id, step_id, event, future
|
30
31
|
end
|
32
|
+
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/web_console.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'dynflow'
|
2
2
|
require 'pp'
|
3
3
|
require 'sinatra'
|
4
|
+
require 'yaml'
|
4
5
|
|
5
6
|
module Dynflow
|
6
7
|
class WebConsole < Sinatra::Base
|
@@ -24,12 +25,16 @@ module Dynflow
|
|
24
25
|
settings.world
|
25
26
|
end
|
26
27
|
|
28
|
+
def prettify_value(value)
|
29
|
+
YAML.dump(value)
|
30
|
+
end
|
31
|
+
|
27
32
|
def prettyprint(value)
|
28
33
|
value = prettyprint_references(value)
|
29
34
|
if value
|
30
|
-
pretty_value = value
|
35
|
+
pretty_value = prettify_value(value)
|
31
36
|
<<-HTML
|
32
|
-
<pre class="prettyprint">#{h(pretty_value)}</pre>
|
37
|
+
<pre class="prettyprint lang-yaml">#{h(pretty_value)}</pre>
|
33
38
|
HTML
|
34
39
|
else
|
35
40
|
""
|
data/lib/dynflow/world.rb
CHANGED
@@ -3,7 +3,7 @@ module Dynflow
|
|
3
3
|
include Algebrick::TypeCheck
|
4
4
|
|
5
5
|
attr_reader :executor, :persistence, :transaction_adapter, :action_classes, :subscription_index,
|
6
|
-
:logger_adapter, :options
|
6
|
+
:logger_adapter, :options, :middleware
|
7
7
|
|
8
8
|
def initialize(options_hash = {})
|
9
9
|
@options = default_options.merge options_hash
|
@@ -13,6 +13,7 @@ module Dynflow
|
|
13
13
|
@persistence = Persistence.new(self, persistence_adapter)
|
14
14
|
@executor = Type! option_val(:executor), Executors::Abstract
|
15
15
|
@action_classes = option_val(:action_classes)
|
16
|
+
@middleware = Middleware::World.new
|
16
17
|
calculate_subscription_index
|
17
18
|
|
18
19
|
executor.initialized.wait
|
@@ -47,6 +48,7 @@ module Dynflow
|
|
47
48
|
# reload actions classes, intended only for devel
|
48
49
|
def reload!
|
49
50
|
@action_classes.map! { |klass| klass.to_s.constantize }
|
51
|
+
middleware.clear_cache!
|
50
52
|
calculate_subscription_index
|
51
53
|
end
|
52
54
|
|
@@ -74,7 +76,11 @@ module Dynflow
|
|
74
76
|
execution_plan = plan(action_class, *args)
|
75
77
|
planned = execution_plan.state == :planned
|
76
78
|
finished = if planned
|
77
|
-
|
79
|
+
begin
|
80
|
+
execute(execution_plan.id)
|
81
|
+
rescue => exception
|
82
|
+
Future.new.fail exception
|
83
|
+
end
|
78
84
|
else
|
79
85
|
Future.new.resolve(execution_plan)
|
80
86
|
end
|
@@ -122,15 +128,19 @@ module Dynflow
|
|
122
128
|
# After the executor is unregistered, the consistency check should be performed
|
123
129
|
# to fix the orphaned plans as well.
|
124
130
|
def consistency_check
|
125
|
-
abnormal_execution_plans =
|
131
|
+
abnormal_execution_plans =
|
132
|
+
self.persistence.find_execution_plans filters: { 'state' => %w(running planning) }
|
126
133
|
if abnormal_execution_plans.empty?
|
127
134
|
logger.info 'Clean start.'
|
128
135
|
else
|
129
136
|
format_str = '%36s %10s %10s'
|
130
137
|
message = ['Abnormal execution plans, process was probably killed.',
|
131
|
-
'Following ExecutionPlans will be set to paused,
|
138
|
+
'Following ExecutionPlans will be set to paused, ',
|
139
|
+
'it should be fixed manually by administrator.',
|
132
140
|
(format format_str, 'ExecutionPlan', 'state', 'result'),
|
133
|
-
*(abnormal_execution_plans.map
|
141
|
+
*(abnormal_execution_plans.map do |ep|
|
142
|
+
format format_str, ep.id, ep.state, ep.result
|
143
|
+
end)]
|
134
144
|
|
135
145
|
logger.error message.join("\n")
|
136
146
|
|
@@ -147,13 +157,23 @@ module Dynflow
|
|
147
157
|
end
|
148
158
|
end
|
149
159
|
|
160
|
+
# should be called after World is initialized, SimpleWorld does it automatically
|
161
|
+
def execute_planned_execution_plans
|
162
|
+
planned_execution_plans =
|
163
|
+
self.persistence.find_execution_plans filters: { 'state' => %w(planned) }
|
164
|
+
planned_execution_plans.each { |ep| execute ep.id }
|
165
|
+
end
|
166
|
+
|
150
167
|
private
|
151
168
|
|
152
169
|
def calculate_subscription_index
|
153
|
-
@subscription_index =
|
154
|
-
|
155
|
-
|
156
|
-
|
170
|
+
@subscription_index =
|
171
|
+
action_classes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |klass, index|
|
172
|
+
next unless klass.subscribe
|
173
|
+
Array(klass.subscribe).each do |subscribed_class|
|
174
|
+
index[subscribed_class.to_s.constantize] << klass
|
175
|
+
end
|
176
|
+
end.tap { |o| o.freeze }
|
157
177
|
end
|
158
178
|
|
159
179
|
def option_val(key)
|
data/test/action_test.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
|
-
require_relative 'code_workflow_example'
|
3
2
|
|
4
3
|
module Dynflow
|
5
4
|
|
@@ -65,8 +64,8 @@ module Dynflow
|
|
65
64
|
specify { smart_action_class.all_children.wont_include smarter_action_class.finalize_phase }
|
66
65
|
|
67
66
|
describe 'World#subscribed_actions' do
|
68
|
-
event_action_class = CodeWorkflowExample::Triage
|
69
|
-
subscribed_action_class = CodeWorkflowExample::NotifyAssignee
|
67
|
+
event_action_class = Support::CodeWorkflowExample::Triage
|
68
|
+
subscribed_action_class = Support::CodeWorkflowExample::NotifyAssignee
|
70
69
|
|
71
70
|
specify { subscribed_action_class.subscribe.must_equal event_action_class }
|
72
71
|
specify { world.subscribed_actions(event_action_class).must_include subscribed_action_class }
|
@@ -78,7 +77,7 @@ module Dynflow
|
|
78
77
|
include WorldInstance
|
79
78
|
|
80
79
|
let :execution_plan do
|
81
|
-
id, planned, finished = *world.trigger(CodeWorkflowExample::IncomingIssues, issues_data)
|
80
|
+
id, planned, finished = *world.trigger(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
82
81
|
raise unless planned
|
83
82
|
finished.value
|
84
83
|
end
|
@@ -90,11 +89,11 @@ module Dynflow
|
|
90
89
|
|
91
90
|
let :presenter do
|
92
91
|
execution_plan.actions.find do |action|
|
93
|
-
action.is_a? CodeWorkflowExample::IncomingIssues
|
92
|
+
action.is_a? Support::CodeWorkflowExample::IncomingIssues
|
94
93
|
end
|
95
94
|
end
|
96
95
|
|
97
|
-
specify { presenter.action_class.must_equal CodeWorkflowExample::IncomingIssues }
|
96
|
+
specify { presenter.action_class.must_equal Support::CodeWorkflowExample::IncomingIssues }
|
98
97
|
|
99
98
|
it 'allows aggregating data from other actions' do
|
100
99
|
presenter.summary.must_equal(assignees: ["John Doe"])
|
data/test/execution_plan_test.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
|
-
require_relative 'code_workflow_example'
|
3
2
|
|
4
3
|
module Dynflow
|
5
4
|
module ExecutionPlanTest
|
@@ -16,7 +15,7 @@ module Dynflow
|
|
16
15
|
describe 'serialization' do
|
17
16
|
|
18
17
|
let :execution_plan do
|
19
|
-
world.plan(CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
|
18
|
+
world.plan(Support::CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
|
20
19
|
end
|
21
20
|
|
22
21
|
let :deserialized_execution_plan do
|
@@ -47,7 +46,7 @@ module Dynflow
|
|
47
46
|
describe '#result' do
|
48
47
|
|
49
48
|
let :execution_plan do
|
50
|
-
world.plan(CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
|
49
|
+
world.plan(Support::CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
|
51
50
|
end
|
52
51
|
|
53
52
|
describe 'for error in planning phase' do
|
@@ -107,7 +106,7 @@ module Dynflow
|
|
107
106
|
|
108
107
|
describe 'plan steps' do
|
109
108
|
let :execution_plan do
|
110
|
-
world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
|
109
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
111
110
|
end
|
112
111
|
|
113
112
|
it 'stores the information about the sub actions' do
|
@@ -129,7 +128,7 @@ module Dynflow
|
|
129
128
|
describe 'persisted action' do
|
130
129
|
|
131
130
|
let :execution_plan do
|
132
|
-
world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
|
131
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
133
132
|
end
|
134
133
|
|
135
134
|
let :action do
|
@@ -148,7 +147,7 @@ module Dynflow
|
|
148
147
|
|
149
148
|
describe 'single dependencies' do
|
150
149
|
let :execution_plan do
|
151
|
-
world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
|
150
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
152
151
|
end
|
153
152
|
|
154
153
|
it 'constructs the plan of actions to be executed in run phase' do
|
@@ -169,7 +168,7 @@ module Dynflow
|
|
169
168
|
|
170
169
|
describe 'multi dependencies' do
|
171
170
|
let :execution_plan do
|
172
|
-
world.plan(CodeWorkflowExample::Commit, 'sha' => 'abc123')
|
171
|
+
world.plan(Support::CodeWorkflowExample::Commit, 'sha' => 'abc123')
|
173
172
|
end
|
174
173
|
|
175
174
|
it 'constructs the plan of actions to be executed in run phase' do
|
@@ -186,7 +185,7 @@ module Dynflow
|
|
186
185
|
|
187
186
|
describe 'sequence and concurrence keyword used' do
|
188
187
|
let :execution_plan do
|
189
|
-
world.plan(CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
|
188
|
+
world.plan(Support::CodeWorkflowExample::FastCommit, 'sha' => 'abc123')
|
190
189
|
end
|
191
190
|
|
192
191
|
it 'constructs the plan of actions to be executed in run phase' do
|
@@ -202,7 +201,7 @@ module Dynflow
|
|
202
201
|
|
203
202
|
describe 'subscribed action' do
|
204
203
|
let :execution_plan do
|
205
|
-
world.plan(CodeWorkflowExample::DummyTrigger, {})
|
204
|
+
world.plan(Support::CodeWorkflowExample::DummyTrigger, {})
|
206
205
|
end
|
207
206
|
|
208
207
|
it 'constructs the plan of actions to be executed in run phase' do
|
@@ -218,7 +217,7 @@ module Dynflow
|
|
218
217
|
describe 'finalize flow' do
|
219
218
|
|
220
219
|
let :execution_plan do
|
221
|
-
world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
|
220
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
222
221
|
end
|
223
222
|
|
224
223
|
it 'plans the finalize steps in a sequence' do
|
@@ -237,7 +236,7 @@ module Dynflow
|
|
237
236
|
|
238
237
|
describe 'accessing actions results' do
|
239
238
|
let :execution_plan do
|
240
|
-
world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
|
239
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
241
240
|
end
|
242
241
|
|
243
242
|
it 'provides the access to the actions data via Action::Presenter' do
|
data/test/executor_test.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
|
-
require_relative 'code_workflow_example'
|
3
2
|
|
4
3
|
module Dynflow
|
5
4
|
module ExecutorTest
|
@@ -28,19 +27,15 @@ module Dynflow
|
|
28
27
|
{ 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
|
29
28
|
end
|
30
29
|
|
31
|
-
let :execution_plan do
|
32
|
-
world.plan(CodeWorkflowExample::IncomingIssues, issues_data)
|
33
|
-
end
|
34
|
-
|
35
30
|
let :failed_execution_plan do
|
36
|
-
plan = world.plan(CodeWorkflowExample::IncomingIssues, failing_issues_data)
|
31
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
|
37
32
|
plan = world.execute(plan.id).value
|
38
33
|
plan.state.must_equal :paused
|
39
34
|
plan
|
40
35
|
end
|
41
36
|
|
42
37
|
let :finalize_failed_execution_plan do
|
43
|
-
plan = world.plan(CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
|
38
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
|
44
39
|
plan = world.execute(plan.id).value
|
45
40
|
plan.state.must_equal :paused
|
46
41
|
plan
|
@@ -58,10 +53,29 @@ module Dynflow
|
|
58
53
|
|
59
54
|
describe "after successful planning" do
|
60
55
|
|
56
|
+
let :execution_plan do
|
57
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
58
|
+
end
|
59
|
+
|
61
60
|
it "is pending" do
|
62
61
|
execution_plan.state.must_equal :planned
|
63
62
|
end
|
64
63
|
|
64
|
+
describe "when finished successfully" do
|
65
|
+
it "is stopped" do
|
66
|
+
world.execute(execution_plan.id).value.tap do |plan|
|
67
|
+
plan.state.must_equal :stopped
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "when finished with error" do
|
73
|
+
it "is paused" do
|
74
|
+
world.execute(failed_execution_plan.id).value.tap do |plan|
|
75
|
+
plan.state.must_equal :paused
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
65
79
|
end
|
66
80
|
|
67
81
|
describe "after error in planning" do
|
@@ -85,7 +99,7 @@ module Dynflow
|
|
85
99
|
describe "when being executed" do
|
86
100
|
|
87
101
|
let :execution_plan do
|
88
|
-
world.plan(CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
|
102
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
|
89
103
|
end
|
90
104
|
|
91
105
|
before do
|
@@ -103,7 +117,7 @@ module Dynflow
|
|
103
117
|
plan.state.must_equal :running
|
104
118
|
triage = plan.steps.values.find do |s|
|
105
119
|
s.is_a?(Dynflow::ExecutionPlan::Steps::RunStep) &&
|
106
|
-
s.action_class ==
|
120
|
+
s.action_class == Support::CodeWorkflowExample::Triage
|
107
121
|
end
|
108
122
|
triage.state.must_equal :running
|
109
123
|
world.persistence.
|
@@ -118,23 +132,6 @@ module Dynflow
|
|
118
132
|
end
|
119
133
|
end
|
120
134
|
end
|
121
|
-
|
122
|
-
describe "when finished successfully" do
|
123
|
-
|
124
|
-
it "is stopped" do
|
125
|
-
world.execute(execution_plan.id).value.tap do |plan|
|
126
|
-
plan.state.must_equal :stopped
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
describe "when finished with error" do
|
132
|
-
it "is paused" do
|
133
|
-
world.execute(failed_execution_plan.id).value.tap do |plan|
|
134
|
-
plan.state.must_equal :paused
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
135
|
end
|
139
136
|
|
140
137
|
describe "execution of run flow" do
|
@@ -144,25 +141,51 @@ module Dynflow
|
|
144
141
|
end
|
145
142
|
|
146
143
|
let :result do
|
147
|
-
world.execute(execution_plan.id).value
|
148
|
-
raise result if result.is_a? Exception
|
149
|
-
end
|
144
|
+
world.execute(execution_plan.id).value!
|
150
145
|
end
|
151
146
|
|
152
147
|
after do
|
153
148
|
TestExecutionLog.teardown
|
154
149
|
end
|
155
150
|
|
156
|
-
|
151
|
+
def persisted_plan
|
157
152
|
result
|
158
|
-
|
153
|
+
super
|
159
154
|
end
|
160
155
|
|
161
156
|
describe 'cancellable action' do
|
162
157
|
|
158
|
+
describe event_class = Listeners::Serialization::Protocol::Event do
|
159
|
+
it 'de/serializes' do
|
160
|
+
Klass = Class.new do
|
161
|
+
def initialize(v)
|
162
|
+
@v = v
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_s
|
166
|
+
@v.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
def ==(other)
|
170
|
+
@v == other.instance_variable_get(:@v)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
object = Klass.new :value
|
175
|
+
event = event_class['uuid', 0, object]
|
176
|
+
hash = event.to_hash
|
177
|
+
json = MultiJson.dump(hash)
|
178
|
+
hash_loaded = MultiJson.load(json)
|
179
|
+
assert_equal event[:event], event_class.from_hash(hash_loaded)[:event]
|
180
|
+
assert_equal event, event_class.from_hash(hash_loaded)
|
181
|
+
|
182
|
+
ExecutorTest.send :remove_const, :Klass
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
163
186
|
describe 'successful' do
|
164
187
|
let :execution_plan do
|
165
|
-
world.plan(CodeWorkflowExample::CancelableSuspended, {})
|
188
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
|
166
189
|
end
|
167
190
|
|
168
191
|
it "doesn't cause problems" do
|
@@ -173,7 +196,7 @@ module Dynflow
|
|
173
196
|
|
174
197
|
describe 'canceled' do
|
175
198
|
let :execution_plan do
|
176
|
-
world.plan(CodeWorkflowExample::CancelableSuspended, { text: 'cancel' })
|
199
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
|
177
200
|
end
|
178
201
|
|
179
202
|
it 'cancels' do
|
@@ -187,7 +210,7 @@ module Dynflow
|
|
187
210
|
|
188
211
|
describe 'canceled failed' do
|
189
212
|
let :execution_plan do
|
190
|
-
world.plan(CodeWorkflowExample::CancelableSuspended, { text: 'cancel
|
213
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
|
191
214
|
end
|
192
215
|
|
193
216
|
it 'fails' do
|
@@ -199,40 +222,37 @@ module Dynflow
|
|
199
222
|
action.output[:progress].must_equal 30
|
200
223
|
end
|
201
224
|
end
|
202
|
-
end
|
203
|
-
|
204
225
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
226
|
+
if world_method == :remote_world
|
227
|
+
describe 'canceled externally' do
|
228
|
+
let :execution_plan do
|
229
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-external' })
|
230
|
+
end
|
209
231
|
|
210
|
-
|
211
|
-
|
212
|
-
|
232
|
+
it 'cancels' do
|
233
|
+
finished = world.execute(execution_plan.id)
|
234
|
+
sleep 0.05
|
235
|
+
world.
|
236
|
+
event(execution_plan.id, 2,
|
237
|
+
Support::CodeWorkflowExample::CancelableSuspended::Cancel).
|
238
|
+
value!.must_equal true
|
239
|
+
result = finished.value!
|
240
|
+
|
241
|
+
result.result.must_equal :success
|
242
|
+
result.state.must_equal :stopped
|
243
|
+
action = world.persistence.load_action result.steps[2]
|
244
|
+
action.output[:progress].must_be :<=, 30
|
245
|
+
action.output[:cancelled].must_equal true
|
246
|
+
end
|
247
|
+
end
|
213
248
|
end
|
214
249
|
|
215
|
-
|
216
|
-
result.started_at.wont_be_nil
|
217
|
-
result.ended_at.wont_be_nil
|
218
|
-
result.execution_time.must_be :<, result.real_time
|
219
|
-
result.execution_time.must_equal(
|
220
|
-
result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
|
221
|
-
|
222
|
-
plan_step = result.steps[1]
|
223
|
-
plan_step.started_at.wont_be_nil
|
224
|
-
plan_step.ended_at.wont_be_nil
|
225
|
-
plan_step.execution_time.must_equal plan_step.real_time
|
226
|
-
|
227
|
-
run_step = result.steps[2]
|
228
|
-
run_step.started_at.wont_be_nil
|
229
|
-
run_step.ended_at.wont_be_nil
|
230
|
-
run_step.execution_time.must_be :<, run_step.real_time
|
231
|
-
end
|
250
|
+
end
|
232
251
|
|
252
|
+
describe 'suspended action' do
|
233
253
|
describe 'handling errors in setup' do
|
234
254
|
let :execution_plan do
|
235
|
-
world.plan(CodeWorkflowExample::DummySuspended,
|
255
|
+
world.plan(Support::CodeWorkflowExample::DummySuspended,
|
236
256
|
external_task_id: '123',
|
237
257
|
text: 'troll setup')
|
238
258
|
end
|
@@ -247,6 +267,35 @@ module Dynflow
|
|
247
267
|
end
|
248
268
|
end
|
249
269
|
|
270
|
+
describe 'running' do
|
271
|
+
let :execution_plan do
|
272
|
+
world.plan(Support::CodeWorkflowExample::DummySuspended, { :external_task_id => '123' })
|
273
|
+
end
|
274
|
+
|
275
|
+
it "doesn't cause problems" do
|
276
|
+
result.result.must_equal :success
|
277
|
+
result.state.must_equal :stopped
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'does set times' do
|
281
|
+
result.started_at.wont_be_nil
|
282
|
+
result.ended_at.wont_be_nil
|
283
|
+
result.execution_time.must_be :<, result.real_time
|
284
|
+
result.execution_time.must_equal(
|
285
|
+
result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
|
286
|
+
|
287
|
+
plan_step = result.steps[1]
|
288
|
+
plan_step.started_at.wont_be_nil
|
289
|
+
plan_step.ended_at.wont_be_nil
|
290
|
+
plan_step.execution_time.must_equal plan_step.real_time
|
291
|
+
|
292
|
+
run_step = result.steps[2]
|
293
|
+
run_step.started_at.wont_be_nil
|
294
|
+
run_step.ended_at.wont_be_nil
|
295
|
+
run_step.execution_time.must_be :<, run_step.real_time
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
250
299
|
describe 'progress' do
|
251
300
|
before do
|
252
301
|
TestPause.setup
|
@@ -260,7 +309,7 @@ module Dynflow
|
|
260
309
|
|
261
310
|
describe 'plan with one action' do
|
262
311
|
let :execution_plan do
|
263
|
-
world.plan(CodeWorkflowExample::DummySuspended,
|
312
|
+
world.plan(Support::CodeWorkflowExample::DummySuspended,
|
264
313
|
{ external_task_id: '123',
|
265
314
|
text: 'pause in progress 20%' })
|
266
315
|
end
|
@@ -275,7 +324,7 @@ module Dynflow
|
|
275
324
|
|
276
325
|
describe 'plan with more action' do
|
277
326
|
let :execution_plan do
|
278
|
-
world.plan(CodeWorkflowExample::DummyHeavyProgress,
|
327
|
+
world.plan(Support::CodeWorkflowExample::DummyHeavyProgress,
|
279
328
|
{ external_task_id: '123',
|
280
329
|
text: 'pause in progress 20%' })
|
281
330
|
end
|
@@ -291,7 +340,7 @@ module Dynflow
|
|
291
340
|
|
292
341
|
describe 'works when resumed after error' do
|
293
342
|
let :execution_plan do
|
294
|
-
world.plan(CodeWorkflowExample::DummySuspended,
|
343
|
+
world.plan(Support::CodeWorkflowExample::DummySuspended,
|
295
344
|
{ external_task_id: '123',
|
296
345
|
text: 'troll progress' })
|
297
346
|
end
|
@@ -315,7 +364,7 @@ module Dynflow
|
|
315
364
|
describe "action with empty flows" do
|
316
365
|
|
317
366
|
let :execution_plan do
|
318
|
-
world.plan(CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
|
367
|
+
world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
|
319
368
|
assert_equal plan.run_flow.size, 0
|
320
369
|
assert_equal plan.finalize_flow.size, 0
|
321
370
|
end.tap do |w|
|
@@ -338,7 +387,7 @@ module Dynflow
|
|
338
387
|
describe 'action with empty run flow but some finalize flow' do
|
339
388
|
|
340
389
|
let :execution_plan do
|
341
|
-
world.plan(CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
|
390
|
+
world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
|
342
391
|
assert_equal plan.run_flow.size, 0
|
343
392
|
assert_equal plan.finalize_flow.size, 1
|
344
393
|
end
|
@@ -351,24 +400,29 @@ module Dynflow
|
|
351
400
|
|
352
401
|
end
|
353
402
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
403
|
+
describe 'running' do
|
404
|
+
let :execution_plan do
|
405
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
406
|
+
end
|
407
|
+
|
408
|
+
it "runs all the steps in the run flow" do
|
409
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
|
410
|
+
Dynflow::Flows::Concurrence
|
411
|
+
Dynflow::Flows::Sequence
|
412
|
+
4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
413
|
+
7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
414
|
+
9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
415
|
+
Dynflow::Flows::Sequence
|
416
|
+
13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
417
|
+
16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
418
|
+
18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
419
|
+
EXECUTED_RUN_FLOW
|
420
|
+
end
|
366
421
|
end
|
367
422
|
|
368
423
|
end
|
369
424
|
|
370
425
|
describe "execution of finalize flow" do
|
371
|
-
|
372
426
|
before do
|
373
427
|
TestExecutionLog.setup
|
374
428
|
result = world.execute(execution_plan.id).value
|
@@ -380,17 +434,19 @@ module Dynflow
|
|
380
434
|
end
|
381
435
|
|
382
436
|
describe "when run flow successful" do
|
437
|
+
let :execution_plan do
|
438
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
439
|
+
end
|
383
440
|
|
384
441
|
it "runs all the steps in the finalize flow" do
|
385
|
-
assert_finalized(
|
442
|
+
assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
|
386
443
|
{ "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
|
387
|
-
assert_finalized(
|
444
|
+
assert_finalized(Support::CodeWorkflowExample::Triage,
|
388
445
|
{ "author" => "Peter Smith", "text" => "Failing test" })
|
389
446
|
end
|
390
447
|
end
|
391
448
|
|
392
449
|
describe "when run flow failed" do
|
393
|
-
|
394
450
|
let :execution_plan do
|
395
451
|
failed_execution_plan
|
396
452
|
end
|
@@ -425,7 +481,7 @@ module Dynflow
|
|
425
481
|
resumed_execution_plan.result.must_equal :success
|
426
482
|
|
427
483
|
run_triages = TestExecutionLog.run.find_all do |action_class, input|
|
428
|
-
action_class == CodeWorkflowExample::Triage
|
484
|
+
action_class == Support::CodeWorkflowExample::Triage
|
429
485
|
end
|
430
486
|
run_triages.size.must_equal 1
|
431
487
|
|
@@ -443,6 +499,7 @@ module Dynflow
|
|
443
499
|
end
|
444
500
|
|
445
501
|
end
|
502
|
+
|
446
503
|
describe "re-execution of run flow after fix in finalize phase" do
|
447
504
|
|
448
505
|
after do
|
@@ -466,7 +523,7 @@ module Dynflow
|
|
466
523
|
resumed_execution_plan.result.must_equal :success
|
467
524
|
|
468
525
|
run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
|
469
|
-
action_class == CodeWorkflowExample::Triage
|
526
|
+
action_class == Support::CodeWorkflowExample::Triage
|
470
527
|
end
|
471
528
|
run_triages.size.must_equal 2
|
472
529
|
|
@@ -502,7 +559,7 @@ module Dynflow
|
|
502
559
|
resumed_execution_plan.result.must_equal :success
|
503
560
|
|
504
561
|
run_triages = TestExecutionLog.run.find_all do |action_class, input|
|
505
|
-
action_class == CodeWorkflowExample::Triage
|
562
|
+
action_class == Support::CodeWorkflowExample::Triage
|
506
563
|
end
|
507
564
|
run_triages.size.must_equal 0
|
508
565
|
|
@@ -531,6 +588,10 @@ module Dynflow
|
|
531
588
|
end
|
532
589
|
|
533
590
|
describe 'FlowManager' do
|
591
|
+
let :execution_plan do
|
592
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
593
|
+
end
|
594
|
+
|
534
595
|
let(:manager) { Executors::Parallel::FlowManager.new execution_plan, execution_plan.run_flow }
|
535
596
|
|
536
597
|
def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
|
@@ -645,13 +706,13 @@ module Dynflow
|
|
645
706
|
if which == :normal_world
|
646
707
|
it 'executes until its done when terminating' do
|
647
708
|
$slow_actions_done = 0
|
648
|
-
world.trigger(CodeWorkflowExample::Slow, 0.02)
|
709
|
+
world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
|
649
710
|
world.terminate.wait
|
650
711
|
$slow_actions_done.must_equal 1
|
651
712
|
end
|
652
713
|
|
653
714
|
it 'executes until its done when terminating even suspended' do
|
654
|
-
result = world.trigger(CodeWorkflowExample::DummySuspended,
|
715
|
+
result = world.trigger(Support::CodeWorkflowExample::DummySuspended,
|
655
716
|
external_task_id: '123',
|
656
717
|
text: 'none')
|
657
718
|
world.terminate.wait
|
@@ -661,12 +722,14 @@ module Dynflow
|
|
661
722
|
|
662
723
|
it 'does not accept new work' do
|
663
724
|
assert world.terminate.wait
|
664
|
-
|
725
|
+
result = world.trigger(Support::CodeWorkflowExample::Slow, 0.02)
|
726
|
+
result.planned.must_equal true
|
727
|
+
-> { result.finished.value! }.must_raise Dynflow::Error
|
665
728
|
end
|
666
729
|
|
667
730
|
it 'it terminates when no work' do
|
668
731
|
skip 'blocks occasionally' if which == :remote_world # FIXME
|
669
|
-
world.trigger(CodeWorkflowExample::Slow, 0.02).finished.wait
|
732
|
+
world.trigger(Support::CodeWorkflowExample::Slow, 0.02).finished.wait
|
670
733
|
assert world.terminate.wait
|
671
734
|
end
|
672
735
|
|