dynflow 0.7.9 → 0.8.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/.gitignore +2 -0
- data/.travis.yml +16 -1
- data/Gemfile +13 -1
- data/doc/pages/source/_drafts/2015-03-01-new-documentation.markdown +10 -0
- data/doc/pages/source/_includes/menu.html +1 -0
- data/doc/pages/source/_includes/menu_right.html +1 -1
- data/doc/pages/source/_sass/_bootstrap-variables.sass +1 -0
- data/doc/pages/source/_sass/_style.scss +4 -0
- data/doc/pages/source/blog/index.html +12 -0
- data/doc/pages/source/documentation/index.md +330 -5
- data/dynflow.gemspec +3 -1
- data/examples/example_helper.rb +18 -11
- data/examples/orchestrate_evented.rb +2 -1
- data/examples/remote_executor.rb +53 -20
- data/lib/dynflow.rb +16 -6
- data/lib/dynflow/action/suspended.rb +1 -1
- data/lib/dynflow/action/with_sub_plans.rb +3 -6
- data/lib/dynflow/actor.rb +56 -0
- data/lib/dynflow/clock.rb +43 -38
- data/lib/dynflow/config.rb +107 -0
- data/lib/dynflow/connectors.rb +7 -0
- data/lib/dynflow/connectors/abstract.rb +41 -0
- data/lib/dynflow/connectors/database.rb +175 -0
- data/lib/dynflow/connectors/direct.rb +71 -0
- data/lib/dynflow/coordinator.rb +280 -0
- data/lib/dynflow/coordinator_adapters.rb +8 -0
- data/lib/dynflow/coordinator_adapters/abstract.rb +28 -0
- data/lib/dynflow/coordinator_adapters/sequel.rb +29 -0
- data/lib/dynflow/dispatcher.rb +58 -0
- data/lib/dynflow/dispatcher/abstract.rb +14 -0
- data/lib/dynflow/dispatcher/client_dispatcher.rb +139 -0
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +86 -0
- data/lib/dynflow/errors.rb +7 -1
- data/lib/dynflow/execution_history.rb +46 -0
- data/lib/dynflow/execution_plan.rb +19 -15
- data/lib/dynflow/executors.rb +0 -1
- data/lib/dynflow/executors/abstract.rb +5 -10
- data/lib/dynflow/executors/parallel.rb +16 -13
- data/lib/dynflow/executors/parallel/core.rb +76 -78
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +4 -5
- data/lib/dynflow/executors/parallel/pool.rb +22 -52
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +9 -2
- data/lib/dynflow/executors/parallel/worker.rb +5 -10
- data/lib/dynflow/persistence.rb +14 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +14 -3
- data/lib/dynflow/persistence_adapters/sequel.rb +142 -38
- data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +14 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +14 -0
- data/lib/dynflow/round_robin.rb +37 -0
- data/lib/dynflow/serializable.rb +1 -2
- data/lib/dynflow/serializer.rb +46 -0
- data/lib/dynflow/testing/dummy_executor.rb +2 -2
- data/lib/dynflow/testing/dummy_world.rb +1 -1
- data/lib/dynflow/transaction_adapters/abstract.rb +0 -5
- data/lib/dynflow/transaction_adapters/active_record.rb +0 -10
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web.rb +26 -0
- data/lib/dynflow/web/console.rb +108 -0
- data/lib/dynflow/web/console_helpers.rb +158 -0
- data/lib/dynflow/web/filtering_helpers.rb +85 -0
- data/lib/dynflow/web/world_helpers.rb +9 -0
- data/lib/dynflow/web_console.rb +3 -310
- data/lib/dynflow/world.rb +188 -119
- data/test/abnormal_states_recovery_test.rb +152 -0
- data/test/action_test.rb +2 -3
- data/test/clock_test.rb +1 -5
- data/test/coordinator_test.rb +152 -0
- data/test/dispatcher_test.rb +146 -0
- data/test/execution_plan_test.rb +2 -1
- data/test/executor_test.rb +534 -612
- data/test/middleware_test.rb +4 -4
- data/test/persistence_test.rb +17 -0
- data/test/prepare_travis_env.sh +35 -0
- data/test/rescue_test.rb +5 -3
- data/test/round_robin_test.rb +28 -0
- data/test/support/code_workflow_example.rb +0 -73
- data/test/support/dummy_example.rb +130 -0
- data/test/support/test_execution_log.rb +41 -0
- data/test/test_helper.rb +222 -116
- data/test/testing_test.rb +10 -10
- data/test/web_console_test.rb +3 -3
- data/test/world_test.rb +23 -0
- data/web/assets/images/logo-square.png +0 -0
- data/web/assets/stylesheets/application.css +9 -0
- data/web/assets/vendor/bootstrap/config.json +429 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-theme.css +479 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-theme.min.css +10 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +5377 -4980
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -8
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +1674 -1645
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +11 -5
- data/web/views/execution_history.erb +17 -0
- data/web/views/index.erb +4 -6
- data/web/views/layout.erb +44 -8
- data/web/views/show.erb +4 -5
- data/web/views/worlds.erb +26 -0
- metadata +116 -23
- checksums.yaml +0 -15
- data/lib/dynflow/daemon.rb +0 -30
- data/lib/dynflow/executors/remote_via_socket.rb +0 -43
- data/lib/dynflow/executors/remote_via_socket/core.rb +0 -184
- data/lib/dynflow/future.rb +0 -173
- data/lib/dynflow/listeners.rb +0 -7
- data/lib/dynflow/listeners/abstract.rb +0 -17
- data/lib/dynflow/listeners/serialization.rb +0 -77
- data/lib/dynflow/listeners/socket.rb +0 -117
- data/lib/dynflow/micro_actor.rb +0 -102
- data/lib/dynflow/simple_world.rb +0 -19
- data/test/remote_via_socket_test.rb +0 -170
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +0 -1109
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +0 -9
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
data/test/execution_plan_test.rb
CHANGED
data/test/executor_test.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
require_relative 'test_helper'
|
|
2
3
|
|
|
3
4
|
module Dynflow
|
|
@@ -6,752 +7,673 @@ module Dynflow
|
|
|
6
7
|
|
|
7
8
|
include PlanAssertions
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
let(:world) { WorldFactory.create_world }
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
let :issues_data do
|
|
13
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
|
14
|
+
{ 'author' => 'John Doe', 'text' => 'Internal server error' }]
|
|
15
|
+
end
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
let :failing_issues_data do
|
|
18
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
|
19
|
+
{ 'author' => 'John Doe', 'text' => 'trolling' }]
|
|
20
|
+
end
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
let :finalize_failing_issues_data do
|
|
23
|
+
[{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
|
|
24
|
+
{ 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
|
|
25
|
+
end
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
let :failed_execution_plan do
|
|
28
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
|
|
29
|
+
plan = world.execute(plan.id).value
|
|
30
|
+
plan.state.must_equal :paused
|
|
31
|
+
plan
|
|
32
|
+
end
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
let :finalize_failed_execution_plan do
|
|
35
|
+
plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, finalize_failing_issues_data)
|
|
36
|
+
plan = world.execute(plan.id).value
|
|
37
|
+
plan.state.must_equal :paused
|
|
38
|
+
plan
|
|
39
|
+
end
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
let :persisted_plan do
|
|
42
|
+
world.persistence.load_execution_plan(execution_plan.id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let :executor_class do
|
|
46
|
+
Executors::Parallel
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe "execution plan state" do
|
|
50
|
+
|
|
51
|
+
describe "after successful planning" do
|
|
52
|
+
|
|
53
|
+
let :execution_plan do
|
|
54
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
|
35
55
|
end
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
plan = world.execute(plan.id).value
|
|
40
|
-
plan.state.must_equal :paused
|
|
41
|
-
plan
|
|
57
|
+
it "is pending" do
|
|
58
|
+
execution_plan.state.must_equal :planned
|
|
42
59
|
end
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
describe "when finished successfully" do
|
|
62
|
+
it "is stopped" do
|
|
63
|
+
world.execute(execution_plan.id).value.tap do |plan|
|
|
64
|
+
plan.state.must_equal :stopped
|
|
65
|
+
end
|
|
66
|
+
end
|
|
46
67
|
end
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
|
|
69
|
+
describe "when finished with error" do
|
|
70
|
+
it "is paused" do
|
|
71
|
+
world.execute(failed_execution_plan.id).value.tap do |plan|
|
|
72
|
+
plan.state.must_equal :paused
|
|
73
|
+
end
|
|
74
|
+
end
|
|
50
75
|
end
|
|
76
|
+
end
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
describe "after error in planning" do
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
class FailingAction < Dynflow::Action
|
|
81
|
+
def plan
|
|
82
|
+
raise "I failed"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
55
85
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
let :execution_plan do
|
|
87
|
+
world.plan(FailingAction)
|
|
88
|
+
end
|
|
59
89
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
it "is stopped" do
|
|
91
|
+
execution_plan.state.must_equal :stopped
|
|
92
|
+
end
|
|
63
93
|
|
|
64
|
-
|
|
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
|
|
94
|
+
end
|
|
71
95
|
|
|
72
|
-
|
|
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
|
|
79
|
-
end
|
|
96
|
+
describe "when being executed" do
|
|
80
97
|
|
|
81
|
-
|
|
98
|
+
let :execution_plan do
|
|
99
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssue, { 'text' => 'get a break' })
|
|
100
|
+
end
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
end
|
|
102
|
+
before do
|
|
103
|
+
TestPause.setup
|
|
104
|
+
@execution = world.execute(execution_plan.id)
|
|
105
|
+
end
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
after do
|
|
108
|
+
@execution.wait
|
|
109
|
+
TestPause.teardown
|
|
110
|
+
end
|
|
92
111
|
|
|
93
|
-
|
|
94
|
-
|
|
112
|
+
it "is running" do
|
|
113
|
+
TestPause.when_paused do
|
|
114
|
+
plan = world.persistence.load_execution_plan(execution_plan.id)
|
|
115
|
+
plan.state.must_equal :running
|
|
116
|
+
triage = plan.steps.values.find do |s|
|
|
117
|
+
s.is_a?(Dynflow::ExecutionPlan::Steps::RunStep) &&
|
|
118
|
+
s.action_class == Support::CodeWorkflowExample::Triage
|
|
95
119
|
end
|
|
120
|
+
triage.state.must_equal :running
|
|
121
|
+
world.persistence.
|
|
122
|
+
load_step(triage.execution_plan_id, triage.id, world).
|
|
123
|
+
state.must_equal :running
|
|
124
|
+
end
|
|
125
|
+
end
|
|
96
126
|
|
|
127
|
+
it "fails when trying to execute again" do
|
|
128
|
+
TestPause.when_paused do
|
|
129
|
+
assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
|
|
97
130
|
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
98
134
|
|
|
99
|
-
|
|
135
|
+
describe "execution of run flow" do
|
|
100
136
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
137
|
+
before do
|
|
138
|
+
TestExecutionLog.setup
|
|
139
|
+
end
|
|
104
140
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
141
|
+
let :result do
|
|
142
|
+
world.execute(execution_plan.id).value!
|
|
143
|
+
end
|
|
109
144
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
end
|
|
145
|
+
after do
|
|
146
|
+
TestExecutionLog.teardown
|
|
147
|
+
end
|
|
114
148
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
triage = plan.steps.values.find do |s|
|
|
120
|
-
s.is_a?(Dynflow::ExecutionPlan::Steps::RunStep) &&
|
|
121
|
-
s.action_class == Support::CodeWorkflowExample::Triage
|
|
122
|
-
end
|
|
123
|
-
triage.state.must_equal :running
|
|
124
|
-
world.persistence.
|
|
125
|
-
load_step(triage.execution_plan_id, triage.id, world).
|
|
126
|
-
state.must_equal :running
|
|
127
|
-
end
|
|
128
|
-
end
|
|
149
|
+
def persisted_plan
|
|
150
|
+
result
|
|
151
|
+
super
|
|
152
|
+
end
|
|
129
153
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
154
|
+
describe 'cancellable action' do
|
|
155
|
+
describe 'successful' do
|
|
156
|
+
let :execution_plan do
|
|
157
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, {})
|
|
135
158
|
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
describe "execution of run flow" do
|
|
139
159
|
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
it "doesn't cause problems" do
|
|
161
|
+
result.result.must_equal :success
|
|
162
|
+
result.state.must_equal :stopped
|
|
142
163
|
end
|
|
164
|
+
end
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
describe 'canceled' do
|
|
167
|
+
let :execution_plan do
|
|
168
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-self' })
|
|
146
169
|
end
|
|
147
170
|
|
|
148
|
-
|
|
149
|
-
|
|
171
|
+
it 'cancels' do
|
|
172
|
+
result.result.must_equal :success
|
|
173
|
+
result.state.must_equal :stopped
|
|
174
|
+
action = world.persistence.load_action result.steps[2]
|
|
175
|
+
action.output[:task][:progress].must_equal 30
|
|
176
|
+
action.output[:task][:cancelled].must_equal true
|
|
150
177
|
end
|
|
178
|
+
end
|
|
151
179
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
180
|
+
describe 'canceled failed' do
|
|
181
|
+
let :execution_plan do
|
|
182
|
+
world.plan(Support::CodeWorkflowExample::CancelableSuspended, { text: 'cancel-fail cancel-self' })
|
|
155
183
|
end
|
|
156
184
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
@v.to_s
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def ==(other)
|
|
171
|
-
@v == other.instance_variable_get(:@v)
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
object = Klass.new :value
|
|
176
|
-
event = event_class['uuid', 0, object]
|
|
177
|
-
hash = event.to_hash
|
|
178
|
-
json = MultiJson.dump(hash)
|
|
179
|
-
hash_loaded = MultiJson.load(json)
|
|
180
|
-
assert_equal event[:event], event_class.from_hash(hash_loaded)[:event]
|
|
181
|
-
assert_equal event, event_class.from_hash(hash_loaded)
|
|
182
|
-
|
|
183
|
-
ExecutorTest.send :remove_const, :Klass
|
|
184
|
-
end
|
|
185
|
-
end
|
|
185
|
+
it 'fails' do
|
|
186
|
+
result.result.must_equal :error
|
|
187
|
+
result.state.must_equal :paused
|
|
188
|
+
step = result.steps[2]
|
|
189
|
+
step.error.message.must_equal 'action cancelled'
|
|
190
|
+
action = world.persistence.load_action step
|
|
191
|
+
action.output[:task][:progress].must_equal 30
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
186
195
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
describe 'suspended action' do
|
|
197
|
+
describe 'handling errors in setup' do
|
|
198
|
+
let :execution_plan do
|
|
199
|
+
world.plan(Support::DummyExample::Polling,
|
|
200
|
+
external_task_id: '123',
|
|
201
|
+
text: 'troll setup')
|
|
202
|
+
end
|
|
191
203
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
204
|
+
it 'fails' do
|
|
205
|
+
assert_equal :error, result.result
|
|
206
|
+
assert_equal :paused, result.state
|
|
207
|
+
assert_equal :error,
|
|
208
|
+
result.steps.values.
|
|
209
|
+
find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.
|
|
210
|
+
state
|
|
211
|
+
end
|
|
212
|
+
end
|
|
197
213
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
214
|
+
describe 'running' do
|
|
215
|
+
let :execution_plan do
|
|
216
|
+
world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
|
|
217
|
+
end
|
|
202
218
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
action.output[:task][:progress].must_equal 30
|
|
208
|
-
action.output[:task][:cancelled].must_equal true
|
|
209
|
-
end
|
|
210
|
-
end
|
|
219
|
+
it "doesn't cause problems" do
|
|
220
|
+
result.result.must_equal :success
|
|
221
|
+
result.state.must_equal :stopped
|
|
222
|
+
end
|
|
211
223
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
it 'does set times' do
|
|
225
|
+
result.started_at.wont_be_nil
|
|
226
|
+
result.ended_at.wont_be_nil
|
|
227
|
+
result.execution_time.must_be :<, result.real_time
|
|
228
|
+
result.execution_time.must_equal(
|
|
229
|
+
result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
|
|
216
230
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
step.error.message.must_equal 'action cancelled'
|
|
222
|
-
action = world.persistence.load_action step
|
|
223
|
-
action.output[:task][:progress].must_equal 30
|
|
224
|
-
end
|
|
225
|
-
end
|
|
231
|
+
plan_step = result.steps[1]
|
|
232
|
+
plan_step.started_at.wont_be_nil
|
|
233
|
+
plan_step.ended_at.wont_be_nil
|
|
234
|
+
plan_step.execution_time.must_equal plan_step.real_time
|
|
226
235
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
it 'cancels' do
|
|
234
|
-
finished = world.execute(execution_plan.id)
|
|
235
|
-
sleep 0.05
|
|
236
|
-
world.
|
|
237
|
-
event(execution_plan.id, 2,
|
|
238
|
-
Support::CodeWorkflowExample::CancelableSuspended::Cancel).
|
|
239
|
-
value!.must_equal true
|
|
240
|
-
result = finished.value!
|
|
241
|
-
|
|
242
|
-
result.result.must_equal :success
|
|
243
|
-
result.state.must_equal :stopped
|
|
244
|
-
action = world.persistence.load_action result.steps[2]
|
|
245
|
-
action.output[:task][:progress].must_be :<=, 30
|
|
246
|
-
action.output[:task][:cancelled].must_equal true
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
236
|
+
run_step = result.steps[2]
|
|
237
|
+
run_step.started_at.wont_be_nil
|
|
238
|
+
run_step.ended_at.wont_be_nil
|
|
239
|
+
run_step.execution_time.must_be :<, run_step.real_time
|
|
240
|
+
end
|
|
241
|
+
end
|
|
250
242
|
|
|
243
|
+
describe 'progress' do
|
|
244
|
+
before do
|
|
245
|
+
TestPause.setup
|
|
246
|
+
@running_plan = world.execute(execution_plan.id)
|
|
251
247
|
end
|
|
252
248
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
external_task_id: '123',
|
|
258
|
-
text: 'troll setup')
|
|
259
|
-
end
|
|
249
|
+
after do
|
|
250
|
+
@running_plan.wait
|
|
251
|
+
TestPause.teardown
|
|
252
|
+
end
|
|
260
253
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.
|
|
267
|
-
state
|
|
268
|
-
end
|
|
254
|
+
describe 'plan with one action' do
|
|
255
|
+
let :execution_plan do
|
|
256
|
+
world.plan(Support::DummyExample::Polling,
|
|
257
|
+
{ external_task_id: '123',
|
|
258
|
+
text: 'pause in progress 20%' })
|
|
269
259
|
end
|
|
270
260
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
world.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
it "doesn't cause problems" do
|
|
277
|
-
result.result.must_equal :success
|
|
278
|
-
result.state.must_equal :stopped
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
it 'does set times' do
|
|
282
|
-
result.started_at.wont_be_nil
|
|
283
|
-
result.ended_at.wont_be_nil
|
|
284
|
-
result.execution_time.must_be :<, result.real_time
|
|
285
|
-
result.execution_time.must_equal(
|
|
286
|
-
result.steps.inject(0) { |sum, (_, step)| sum + step.execution_time })
|
|
287
|
-
|
|
288
|
-
plan_step = result.steps[1]
|
|
289
|
-
plan_step.started_at.wont_be_nil
|
|
290
|
-
plan_step.ended_at.wont_be_nil
|
|
291
|
-
plan_step.execution_time.must_equal plan_step.real_time
|
|
292
|
-
|
|
293
|
-
run_step = result.steps[2]
|
|
294
|
-
run_step.started_at.wont_be_nil
|
|
295
|
-
run_step.ended_at.wont_be_nil
|
|
296
|
-
run_step.execution_time.must_be :<, run_step.real_time
|
|
261
|
+
it 'determines the progress of the execution plan in percents' do
|
|
262
|
+
TestPause.when_paused do
|
|
263
|
+
plan = world.persistence.load_execution_plan(execution_plan.id)
|
|
264
|
+
plan.progress.round(2).must_equal 0.2
|
|
297
265
|
end
|
|
298
266
|
end
|
|
267
|
+
end
|
|
299
268
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
after do
|
|
307
|
-
@running_plan.wait
|
|
308
|
-
TestPause.teardown
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
describe 'plan with one action' do
|
|
312
|
-
let :execution_plan do
|
|
313
|
-
world.plan(Support::CodeWorkflowExample::DummySuspended,
|
|
314
|
-
{ external_task_id: '123',
|
|
315
|
-
text: 'pause in progress 20%' })
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
it 'determines the progress of the execution plan in percents' do
|
|
319
|
-
TestPause.when_paused do
|
|
320
|
-
plan = world.persistence.load_execution_plan(execution_plan.id)
|
|
321
|
-
plan.progress.round(2).must_equal 0.2
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
describe 'plan with more action' do
|
|
327
|
-
let :execution_plan do
|
|
328
|
-
world.plan(Support::CodeWorkflowExample::DummyHeavyProgress,
|
|
329
|
-
{ external_task_id: '123',
|
|
330
|
-
text: 'pause in progress 20%' })
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
it 'takes the steps weight in account' do
|
|
334
|
-
TestPause.when_paused do
|
|
335
|
-
plan = world.persistence.load_execution_plan(execution_plan.id)
|
|
336
|
-
plan.progress.round(2).must_equal 0.42
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
end
|
|
269
|
+
describe 'plan with more action' do
|
|
270
|
+
let :execution_plan do
|
|
271
|
+
world.plan(Support::DummyExample::WeightedPolling,
|
|
272
|
+
{ external_task_id: '123',
|
|
273
|
+
text: 'pause in progress 20%' })
|
|
340
274
|
end
|
|
341
275
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
world.
|
|
345
|
-
|
|
346
|
-
text: 'troll progress' })
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
specify do
|
|
350
|
-
assert_equal :paused, result.state
|
|
351
|
-
assert_equal :error, result.result
|
|
352
|
-
assert_equal :error, result.steps.values.
|
|
353
|
-
find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
|
|
354
|
-
|
|
355
|
-
ep = world.execute(result.id).value
|
|
356
|
-
assert_equal :stopped, ep.state
|
|
357
|
-
assert_equal :success, ep.result
|
|
358
|
-
assert_equal :success, ep.steps.values.
|
|
359
|
-
find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
|
|
276
|
+
it 'takes the steps weight in account' do
|
|
277
|
+
TestPause.when_paused do
|
|
278
|
+
plan = world.persistence.load_execution_plan(execution_plan.id)
|
|
279
|
+
plan.progress.round(2).must_equal 0.42
|
|
360
280
|
end
|
|
361
281
|
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
362
284
|
|
|
285
|
+
describe 'works when resumed after error' do
|
|
286
|
+
let :execution_plan do
|
|
287
|
+
world.plan(Support::DummyExample::Polling,
|
|
288
|
+
{ external_task_id: '123',
|
|
289
|
+
text: 'troll progress' })
|
|
363
290
|
end
|
|
364
291
|
|
|
365
|
-
|
|
292
|
+
specify do
|
|
293
|
+
assert_equal :paused, result.state
|
|
294
|
+
assert_equal :error, result.result
|
|
295
|
+
assert_equal :error, result.steps.values.
|
|
296
|
+
find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
|
|
366
297
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
end
|
|
298
|
+
ep = world.execute(result.id).value
|
|
299
|
+
assert_equal :stopped, ep.state
|
|
300
|
+
assert_equal :success, ep.result
|
|
301
|
+
assert_equal :success, ep.steps.values.
|
|
302
|
+
find { |s| s.is_a? Dynflow::ExecutionPlan::Steps::RunStep }.state
|
|
303
|
+
end
|
|
304
|
+
end
|
|
375
305
|
|
|
376
|
-
|
|
377
|
-
result.result.must_equal :success
|
|
378
|
-
result.state.must_equal :stopped
|
|
379
|
-
end
|
|
306
|
+
end
|
|
380
307
|
|
|
381
|
-
|
|
382
|
-
world.execute(execution_plan.id)
|
|
383
|
-
assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
|
|
384
|
-
end
|
|
308
|
+
describe "action with empty flows" do
|
|
385
309
|
|
|
310
|
+
let :execution_plan do
|
|
311
|
+
world.plan(Support::CodeWorkflowExample::Dummy, { :text => "dummy" }).tap do |plan|
|
|
312
|
+
assert_equal plan.run_flow.size, 0
|
|
313
|
+
assert_equal plan.finalize_flow.size, 0
|
|
314
|
+
end.tap do |w|
|
|
315
|
+
w
|
|
386
316
|
end
|
|
317
|
+
end
|
|
387
318
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
assert_equal plan.run_flow.size, 0
|
|
393
|
-
assert_equal plan.finalize_flow.size, 1
|
|
394
|
-
end
|
|
395
|
-
end
|
|
319
|
+
it "doesn't cause problems" do
|
|
320
|
+
result.result.must_equal :success
|
|
321
|
+
result.state.must_equal :stopped
|
|
322
|
+
end
|
|
396
323
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
324
|
+
it 'will not run again' do
|
|
325
|
+
world.execute(execution_plan.id)
|
|
326
|
+
assert_raises(Dynflow::Error) { world.execute(execution_plan.id).value! }
|
|
327
|
+
end
|
|
401
328
|
|
|
402
|
-
|
|
329
|
+
end
|
|
403
330
|
|
|
404
|
-
|
|
405
|
-
let :execution_plan do
|
|
406
|
-
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
|
407
|
-
end
|
|
331
|
+
describe 'action with empty run flow but some finalize flow' do
|
|
408
332
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
|
414
|
-
7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
|
415
|
-
9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
|
416
|
-
Dynflow::Flows::Sequence
|
|
417
|
-
13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
|
418
|
-
16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
|
419
|
-
18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
|
420
|
-
EXECUTED_RUN_FLOW
|
|
421
|
-
end
|
|
333
|
+
let :execution_plan do
|
|
334
|
+
world.plan(Support::CodeWorkflowExample::DummyWithFinalize, { :text => "dummy" }).tap do |plan|
|
|
335
|
+
assert_equal plan.run_flow.size, 0
|
|
336
|
+
assert_equal plan.finalize_flow.size, 1
|
|
422
337
|
end
|
|
338
|
+
end
|
|
423
339
|
|
|
340
|
+
it "doesn't cause problems" do
|
|
341
|
+
result.result.must_equal :success
|
|
342
|
+
result.state.must_equal :stopped
|
|
424
343
|
end
|
|
425
344
|
|
|
426
|
-
|
|
427
|
-
before do
|
|
428
|
-
TestExecutionLog.setup
|
|
429
|
-
result = world.execute(execution_plan.id).value
|
|
430
|
-
raise result if result.is_a? Exception
|
|
431
|
-
end
|
|
345
|
+
end
|
|
432
346
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
347
|
+
describe 'running' do
|
|
348
|
+
let :execution_plan do
|
|
349
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
|
350
|
+
end
|
|
436
351
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
352
|
+
it "runs all the steps in the run flow" do
|
|
353
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, persisted_plan
|
|
354
|
+
Dynflow::Flows::Concurrence
|
|
355
|
+
Dynflow::Flows::Sequence
|
|
356
|
+
4: Triage(success) {"author"=>"Peter Smith", "text"=>"Failing test"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
|
357
|
+
7: UpdateIssue(success) {"author"=>"Peter Smith", "text"=>"Failing test", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
|
358
|
+
9: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
|
359
|
+
Dynflow::Flows::Sequence
|
|
360
|
+
13: Triage(success) {"author"=>"John Doe", "text"=>"Internal server error"} --> {"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}
|
|
361
|
+
16: UpdateIssue(success) {"author"=>"John Doe", "text"=>"Internal server error", "assignee"=>"John Doe", "severity"=>"medium"} --> {}
|
|
362
|
+
18: NotifyAssignee(success) {"triage"=>{"classification"=>{"assignee"=>"John Doe", "severity"=>"medium"}}} --> {}
|
|
363
|
+
EXECUTED_RUN_FLOW
|
|
364
|
+
end
|
|
365
|
+
end
|
|
441
366
|
|
|
442
|
-
|
|
443
|
-
assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
|
|
444
|
-
{ "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
|
|
445
|
-
assert_finalized(Support::CodeWorkflowExample::Triage,
|
|
446
|
-
{ "author" => "Peter Smith", "text" => "Failing test" })
|
|
447
|
-
end
|
|
448
|
-
end
|
|
367
|
+
end
|
|
449
368
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
369
|
+
describe "execution of finalize flow" do
|
|
370
|
+
before do
|
|
371
|
+
TestExecutionLog.setup
|
|
372
|
+
result = world.execute(execution_plan.id).value
|
|
373
|
+
raise result if result.is_a? Exception
|
|
374
|
+
end
|
|
454
375
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
end
|
|
376
|
+
after do
|
|
377
|
+
TestExecutionLog.teardown
|
|
378
|
+
end
|
|
459
379
|
|
|
380
|
+
describe "when run flow successful" do
|
|
381
|
+
let :execution_plan do
|
|
382
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
|
460
383
|
end
|
|
461
384
|
|
|
462
|
-
|
|
385
|
+
it "runs all the steps in the finalize flow" do
|
|
386
|
+
assert_finalized(Support::CodeWorkflowExample::IncomingIssues,
|
|
387
|
+
{ "issues" => [{ "author" => "Peter Smith", "text" => "Failing test" }, { "author" => "John Doe", "text" => "Internal server error" }] })
|
|
388
|
+
assert_finalized(Support::CodeWorkflowExample::Triage,
|
|
389
|
+
{ "author" => "Peter Smith", "text" => "Failing test" })
|
|
390
|
+
end
|
|
391
|
+
end
|
|
463
392
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
393
|
+
describe "when run flow failed" do
|
|
394
|
+
let :execution_plan do
|
|
395
|
+
failed_execution_plan
|
|
396
|
+
end
|
|
467
397
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
world.persistence.load_action(failed_step).tap do |action|
|
|
473
|
-
action.input[:text] = "ok"
|
|
474
|
-
world.persistence.save_action(failed_step.execution_plan_id, action)
|
|
475
|
-
end
|
|
476
|
-
TestExecutionLog.setup
|
|
477
|
-
world.execute(failed_execution_plan.id).value
|
|
478
|
-
end
|
|
398
|
+
it "doesn't run the steps in the finalize flow" do
|
|
399
|
+
TestExecutionLog.finalize.size.must_equal 0
|
|
400
|
+
end
|
|
401
|
+
end
|
|
479
402
|
|
|
480
|
-
|
|
481
|
-
resumed_execution_plan.state.must_equal :stopped
|
|
482
|
-
resumed_execution_plan.result.must_equal :success
|
|
403
|
+
end
|
|
483
404
|
|
|
484
|
-
|
|
485
|
-
action_class == Support::CodeWorkflowExample::Triage
|
|
486
|
-
end
|
|
487
|
-
run_triages.size.must_equal 1
|
|
488
|
-
|
|
489
|
-
assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
|
490
|
-
Dynflow::Flows::Concurrence
|
|
491
|
-
Dynflow::Flows::Sequence
|
|
492
|
-
4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
493
|
-
7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
|
494
|
-
9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
495
|
-
Dynflow::Flows::Sequence
|
|
496
|
-
13: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
497
|
-
16: UpdateIssue(success) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
|
498
|
-
18: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
499
|
-
EXECUTED_RUN_FLOW
|
|
500
|
-
end
|
|
405
|
+
describe "re-execution of run flow after fix in run phase" do
|
|
501
406
|
|
|
502
|
-
|
|
407
|
+
after do
|
|
408
|
+
TestExecutionLog.teardown
|
|
409
|
+
end
|
|
503
410
|
|
|
504
|
-
|
|
411
|
+
let :resumed_execution_plan do
|
|
412
|
+
failed_step = failed_execution_plan.steps.values.find do |step|
|
|
413
|
+
step.state == :error
|
|
414
|
+
end
|
|
415
|
+
world.persistence.load_action(failed_step).tap do |action|
|
|
416
|
+
action.input[:text] = "ok"
|
|
417
|
+
world.persistence.save_action(failed_step.execution_plan_id, action)
|
|
418
|
+
end
|
|
419
|
+
TestExecutionLog.setup
|
|
420
|
+
world.execute(failed_execution_plan.id).value
|
|
421
|
+
end
|
|
505
422
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
423
|
+
it "runs all the steps in the run flow" do
|
|
424
|
+
resumed_execution_plan.state.must_equal :stopped
|
|
425
|
+
resumed_execution_plan.result.must_equal :success
|
|
509
426
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
427
|
+
run_triages = TestExecutionLog.run.find_all do |action_class, input|
|
|
428
|
+
action_class == Support::CodeWorkflowExample::Triage
|
|
429
|
+
end
|
|
430
|
+
run_triages.size.must_equal 1
|
|
431
|
+
|
|
432
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
|
433
|
+
Dynflow::Flows::Concurrence
|
|
434
|
+
Dynflow::Flows::Sequence
|
|
435
|
+
4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
436
|
+
7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
|
437
|
+
9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
438
|
+
Dynflow::Flows::Sequence
|
|
439
|
+
13: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
440
|
+
16: UpdateIssue(success) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
|
441
|
+
18: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
442
|
+
EXECUTED_RUN_FLOW
|
|
443
|
+
end
|
|
521
444
|
|
|
522
|
-
|
|
523
|
-
resumed_execution_plan.state.must_equal :stopped
|
|
524
|
-
resumed_execution_plan.result.must_equal :success
|
|
445
|
+
end
|
|
525
446
|
|
|
526
|
-
|
|
527
|
-
action_class == Support::CodeWorkflowExample::Triage
|
|
528
|
-
end
|
|
529
|
-
run_triages.size.must_equal 2
|
|
447
|
+
describe "re-execution of run flow after fix in finalize phase" do
|
|
530
448
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
535
|
-
14: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
536
|
-
19: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
537
|
-
20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling in finalize\"}]} --> {}
|
|
538
|
-
EXECUTED_RUN_FLOW
|
|
539
|
-
end
|
|
449
|
+
after do
|
|
450
|
+
TestExecutionLog.teardown
|
|
451
|
+
end
|
|
540
452
|
|
|
453
|
+
let :resumed_execution_plan do
|
|
454
|
+
failed_step = finalize_failed_execution_plan.steps.values.find do |step|
|
|
455
|
+
step.state == :error
|
|
541
456
|
end
|
|
457
|
+
world.persistence.load_action(failed_step).tap do |action|
|
|
458
|
+
action.input[:text] = "ok"
|
|
459
|
+
world.persistence.save_action(failed_step.execution_plan_id, action)
|
|
460
|
+
end
|
|
461
|
+
TestExecutionLog.setup
|
|
462
|
+
world.execute(finalize_failed_execution_plan.id).value
|
|
463
|
+
end
|
|
542
464
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
TestExecutionLog.teardown
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
let :resumed_execution_plan do
|
|
550
|
-
failed_step = failed_execution_plan.steps.values.find do |step|
|
|
551
|
-
step.state == :error
|
|
552
|
-
end
|
|
553
|
-
failed_execution_plan.skip(failed_step)
|
|
554
|
-
TestExecutionLog.setup
|
|
555
|
-
world.execute(failed_execution_plan.id).value
|
|
556
|
-
end
|
|
465
|
+
it "runs all the steps in the finalize flow" do
|
|
466
|
+
resumed_execution_plan.state.must_equal :stopped
|
|
467
|
+
resumed_execution_plan.result.must_equal :success
|
|
557
468
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
469
|
+
run_triages = TestExecutionLog.finalize.find_all do |action_class, input|
|
|
470
|
+
action_class == Support::CodeWorkflowExample::Triage
|
|
471
|
+
end
|
|
472
|
+
run_triages.size.must_equal 2
|
|
561
473
|
|
|
562
|
-
|
|
563
|
-
action_class == Support::CodeWorkflowExample::Triage
|
|
564
|
-
end
|
|
565
|
-
run_triages.size.must_equal 0
|
|
566
|
-
|
|
567
|
-
assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
|
568
|
-
Dynflow::Flows::Concurrence
|
|
569
|
-
Dynflow::Flows::Sequence
|
|
570
|
-
4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
571
|
-
7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
|
572
|
-
9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
573
|
-
Dynflow::Flows::Sequence
|
|
574
|
-
13: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
|
|
575
|
-
16: UpdateIssue(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>Step(13).output[:classification][:assignee], \"severity\"=>Step(13).output[:classification][:severity]} --> {}
|
|
576
|
-
18: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
|
|
577
|
-
EXECUTED_RUN_FLOW
|
|
578
|
-
|
|
579
|
-
assert_finalize_flow <<-FINALIZE_FLOW, resumed_execution_plan
|
|
474
|
+
assert_finalize_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
|
580
475
|
Dynflow::Flows::Sequence
|
|
581
476
|
5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
582
477
|
10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
583
|
-
14: Triage(
|
|
584
|
-
19: NotifyAssignee(
|
|
585
|
-
20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"}]} --> {}
|
|
586
|
-
|
|
478
|
+
14: Triage(success) {\"author\"=>\"John Doe\", \"text\"=>\"ok\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
479
|
+
19: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
480
|
+
20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling in finalize\"}]} --> {}
|
|
481
|
+
EXECUTED_RUN_FLOW
|
|
482
|
+
end
|
|
587
483
|
|
|
588
|
-
|
|
589
|
-
end
|
|
484
|
+
end
|
|
590
485
|
|
|
591
|
-
|
|
592
|
-
let :execution_plan do
|
|
593
|
-
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
|
594
|
-
end
|
|
486
|
+
describe "re-execution of run flow after skipping" do
|
|
595
487
|
|
|
596
|
-
|
|
488
|
+
after do
|
|
489
|
+
TestExecutionLog.teardown
|
|
490
|
+
end
|
|
597
491
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
assert_equal Set.new(expected_next_step_ids), Set.new(next_step_ids)
|
|
607
|
-
end
|
|
492
|
+
let :resumed_execution_plan do
|
|
493
|
+
failed_step = failed_execution_plan.steps.values.find do |step|
|
|
494
|
+
step.state == :error
|
|
495
|
+
end
|
|
496
|
+
failed_execution_plan.skip(failed_step)
|
|
497
|
+
TestExecutionLog.setup
|
|
498
|
+
world.execute(failed_execution_plan.id).value
|
|
499
|
+
end
|
|
608
500
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
assert_next_steps([7], 4)
|
|
613
|
-
assert_next_steps([9], 7)
|
|
614
|
-
assert_next_steps([], 9)
|
|
615
|
-
assert_next_steps([16], 13)
|
|
616
|
-
assert_next_steps([18], 16)
|
|
617
|
-
assert_next_steps([], 18)
|
|
618
|
-
assert manager.done?
|
|
619
|
-
end
|
|
620
|
-
end
|
|
501
|
+
it "runs all pending steps except skipped" do
|
|
502
|
+
resumed_execution_plan.state.must_equal :stopped
|
|
503
|
+
resumed_execution_plan.result.must_equal :warning
|
|
621
504
|
|
|
622
|
-
|
|
505
|
+
run_triages = TestExecutionLog.run.find_all do |action_class, input|
|
|
506
|
+
action_class == Support::CodeWorkflowExample::Triage
|
|
507
|
+
end
|
|
508
|
+
run_triages.size.must_equal 0
|
|
509
|
+
|
|
510
|
+
assert_run_flow <<-EXECUTED_RUN_FLOW, resumed_execution_plan
|
|
511
|
+
Dynflow::Flows::Concurrence
|
|
512
|
+
Dynflow::Flows::Sequence
|
|
513
|
+
4: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
514
|
+
7: UpdateIssue(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\", \"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"} --> {}
|
|
515
|
+
9: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
516
|
+
Dynflow::Flows::Sequence
|
|
517
|
+
13: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
|
|
518
|
+
16: UpdateIssue(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\", \"assignee\"=>Step(13).output[:classification][:assignee], \"severity\"=>Step(13).output[:classification][:severity]} --> {}
|
|
519
|
+
18: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
|
|
520
|
+
EXECUTED_RUN_FLOW
|
|
521
|
+
|
|
522
|
+
assert_finalize_flow <<-FINALIZE_FLOW, resumed_execution_plan
|
|
523
|
+
Dynflow::Flows::Sequence
|
|
524
|
+
5: Triage(success) {\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"} --> {\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}
|
|
525
|
+
10: NotifyAssignee(success) {\"triage\"=>{\"classification\"=>{\"assignee\"=>\"John Doe\", \"severity\"=>\"medium\"}}} --> {}
|
|
526
|
+
14: Triage(skipped) {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"} --> {}
|
|
527
|
+
19: NotifyAssignee(skipped) {\"triage\"=>Step(13).output} --> {}
|
|
528
|
+
20: IncomingIssues(success) {\"issues\"=>[{\"author\"=>\"Peter Smith\", \"text\"=>\"Failing test\"}, {\"author\"=>\"John Doe\", \"text\"=>\"trolling\"}]} --> {}
|
|
529
|
+
FINALIZE_FLOW
|
|
623
530
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
assert_next_steps([], 4, false)
|
|
627
|
-
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
628
533
|
|
|
534
|
+
describe 'FlowManager' do
|
|
535
|
+
let :execution_plan do
|
|
536
|
+
world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
|
|
537
|
+
end
|
|
629
538
|
|
|
630
|
-
|
|
631
|
-
assert_next_steps([4, 13])
|
|
632
|
-
assert_next_steps([], 4, false)
|
|
633
|
-
assert !manager.done?
|
|
634
|
-
assert_next_steps([], 13, false)
|
|
635
|
-
assert manager.done?
|
|
636
|
-
end
|
|
637
|
-
end
|
|
539
|
+
let(:manager) { Executors::Parallel::FlowManager.new execution_plan, execution_plan.run_flow }
|
|
638
540
|
|
|
541
|
+
def assert_next_steps(expected_next_step_ids, finished_step_id = nil, success = true)
|
|
542
|
+
if finished_step_id
|
|
543
|
+
step = manager.execution_plan.steps[finished_step_id]
|
|
544
|
+
next_steps = manager.cursor_index[step.id].what_is_next(step, success)
|
|
545
|
+
else
|
|
546
|
+
next_steps = manager.start
|
|
639
547
|
end
|
|
548
|
+
next_step_ids = next_steps.map(&:id)
|
|
549
|
+
assert_equal Set.new(expected_next_step_ids), Set.new(next_step_ids)
|
|
550
|
+
end
|
|
640
551
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
rr.next.must_equal 2
|
|
652
|
-
rr.next.must_equal 1
|
|
653
|
-
rr.next.must_equal 2
|
|
654
|
-
rr.delete 1
|
|
655
|
-
rr.next.must_equal 2
|
|
656
|
-
rr.next.must_equal 2
|
|
657
|
-
rr.delete 2
|
|
658
|
-
rr.next.must_be_nil
|
|
659
|
-
rr.must_be_empty
|
|
660
|
-
end
|
|
552
|
+
describe 'what_is_next' do
|
|
553
|
+
it 'returns next steps after required steps were finished' do
|
|
554
|
+
assert_next_steps([4, 13])
|
|
555
|
+
assert_next_steps([7], 4)
|
|
556
|
+
assert_next_steps([9], 7)
|
|
557
|
+
assert_next_steps([], 9)
|
|
558
|
+
assert_next_steps([16], 13)
|
|
559
|
+
assert_next_steps([18], 16)
|
|
560
|
+
assert_next_steps([], 18)
|
|
561
|
+
assert manager.done?
|
|
661
562
|
end
|
|
563
|
+
end
|
|
662
564
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
storage.must_be_empty
|
|
669
|
-
storage.pop.must_be_nil
|
|
670
|
-
storage.pop.must_be_nil
|
|
671
|
-
|
|
672
|
-
storage.add s = FakeStep.new(1)
|
|
673
|
-
storage.pop.must_equal s
|
|
674
|
-
storage.must_be_empty
|
|
675
|
-
storage.pop.must_be_nil
|
|
676
|
-
|
|
677
|
-
storage.add s11 = FakeStep.new(1)
|
|
678
|
-
storage.add s12 = FakeStep.new(1)
|
|
679
|
-
storage.add s13 = FakeStep.new(1)
|
|
680
|
-
storage.add s21 = FakeStep.new(2)
|
|
681
|
-
storage.add s22 = FakeStep.new(2)
|
|
682
|
-
storage.add s31 = FakeStep.new(3)
|
|
683
|
-
|
|
684
|
-
storage.pop.must_equal s21
|
|
685
|
-
storage.pop.must_equal s31
|
|
686
|
-
storage.pop.must_equal s11
|
|
687
|
-
storage.pop.must_equal s22
|
|
688
|
-
storage.pop.must_equal s12
|
|
689
|
-
storage.pop.must_equal s13
|
|
690
|
-
|
|
691
|
-
storage.must_be_empty
|
|
692
|
-
storage.pop.must_be_nil
|
|
693
|
-
end
|
|
565
|
+
describe 'what_is_next with errors' do
|
|
566
|
+
|
|
567
|
+
it "doesn't return next steps if requirements failed" do
|
|
568
|
+
assert_next_steps([4, 13])
|
|
569
|
+
assert_next_steps([], 4, false)
|
|
694
570
|
end
|
|
695
571
|
|
|
696
|
-
end
|
|
697
|
-
end
|
|
698
572
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
573
|
+
it "is not done while other steps can be finished" do
|
|
574
|
+
assert_next_steps([4, 13])
|
|
575
|
+
assert_next_steps([], 4, false)
|
|
576
|
+
assert !manager.done?
|
|
577
|
+
assert_next_steps([], 13, false)
|
|
578
|
+
assert manager.done?
|
|
579
|
+
end
|
|
580
|
+
end
|
|
702
581
|
|
|
703
|
-
|
|
704
|
-
describe which do
|
|
705
|
-
let(:world) { self.send which }
|
|
582
|
+
end
|
|
706
583
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
584
|
+
describe 'Pool::JobStorage' do
|
|
585
|
+
FakeStep ||= Struct.new(:execution_plan_id)
|
|
586
|
+
|
|
587
|
+
let(:storage) { Dynflow::Executors::Parallel::Pool::JobStorage.new }
|
|
588
|
+
it do
|
|
589
|
+
storage.must_be_empty
|
|
590
|
+
storage.pop.must_be_nil
|
|
591
|
+
storage.pop.must_be_nil
|
|
592
|
+
|
|
593
|
+
storage.add s = FakeStep.new(1)
|
|
594
|
+
storage.pop.must_equal s
|
|
595
|
+
storage.must_be_empty
|
|
596
|
+
storage.pop.must_be_nil
|
|
597
|
+
|
|
598
|
+
storage.add s11 = FakeStep.new(1)
|
|
599
|
+
storage.add s12 = FakeStep.new(1)
|
|
600
|
+
storage.add s13 = FakeStep.new(1)
|
|
601
|
+
storage.add s21 = FakeStep.new(2)
|
|
602
|
+
storage.add s22 = FakeStep.new(2)
|
|
603
|
+
storage.add s31 = FakeStep.new(3)
|
|
604
|
+
|
|
605
|
+
storage.pop.must_equal s21
|
|
606
|
+
storage.pop.must_equal s31
|
|
607
|
+
storage.pop.must_equal s11
|
|
608
|
+
storage.pop.must_equal s22
|
|
609
|
+
storage.pop.must_equal s12
|
|
610
|
+
storage.pop.must_equal s13
|
|
611
|
+
|
|
612
|
+
storage.must_be_empty
|
|
613
|
+
storage.pop.must_be_nil
|
|
614
|
+
end
|
|
615
|
+
end
|
|
714
616
|
|
|
715
|
-
|
|
716
|
-
result = world.trigger(Support::CodeWorkflowExample::DummySuspended,
|
|
717
|
-
external_task_id: '123',
|
|
718
|
-
text: 'none')
|
|
719
|
-
world.terminate.wait
|
|
720
|
-
assert result.finished.ready?
|
|
721
|
-
end
|
|
722
|
-
end
|
|
617
|
+
end
|
|
723
618
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
619
|
+
describe 'termination' do
|
|
620
|
+
let(:world) { WorldFactory.create_world }
|
|
621
|
+
|
|
622
|
+
it 'waits for currently running actions' do
|
|
623
|
+
$slow_actions_done = 0
|
|
624
|
+
running = world.trigger(Support::DummyExample::Slow, 1)
|
|
625
|
+
suspended = world.trigger(Support::DummyExample::EventedAction, :timeout => 3 )
|
|
626
|
+
sleep 0.2
|
|
627
|
+
world.terminate.wait
|
|
628
|
+
$slow_actions_done.must_equal 1
|
|
629
|
+
[running, suspended].each do |triggered|
|
|
630
|
+
plan = world.persistence.load_execution_plan(triggered.id)
|
|
631
|
+
plan.state.must_equal :paused
|
|
632
|
+
plan.result.must_equal :pending
|
|
633
|
+
end
|
|
634
|
+
end
|
|
731
635
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
636
|
+
describe 'before_termination hooks' do
|
|
637
|
+
it 'runs before temination hooks' do
|
|
638
|
+
hook_run = false
|
|
639
|
+
world.before_termination { hook_run = true }
|
|
640
|
+
world.terminate.wait
|
|
641
|
+
assert hook_run
|
|
642
|
+
end
|
|
737
643
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
644
|
+
it 'continues when some hook fails' do
|
|
645
|
+
run_hooks, failed_hooks = [], []
|
|
646
|
+
world.before_termination { run_hooks << 1 }
|
|
647
|
+
world.before_termination { run_hooks << 2; failed_hooks << 2; raise 'error' }
|
|
648
|
+
world.before_termination { run_hooks << 3 }
|
|
649
|
+
world.terminate.wait
|
|
650
|
+
run_hooks.must_equal [1, 2, 3]
|
|
651
|
+
failed_hooks.must_equal [2]
|
|
652
|
+
end
|
|
653
|
+
end
|
|
741
654
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
655
|
+
it 'does not accept new work' do
|
|
656
|
+
assert world.terminate.wait
|
|
657
|
+
result = world.trigger(Support::DummyExample::Slow, 0.02)
|
|
658
|
+
result.must_be :planned?
|
|
659
|
+
result.finished.wait
|
|
660
|
+
assert result.finished.failed?
|
|
661
|
+
result.finished.reason.must_be_kind_of Concurrent::Actor::ActorTerminated
|
|
662
|
+
end
|
|
746
663
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
664
|
+
it 'it terminates when no work right after initialization' do
|
|
665
|
+
assert world.terminate.wait
|
|
666
|
+
end
|
|
750
667
|
|
|
751
|
-
|
|
752
|
-
|
|
668
|
+
it 'second terminate works' do
|
|
669
|
+
assert world.terminate.wait
|
|
670
|
+
assert world.terminate.wait
|
|
671
|
+
end
|
|
753
672
|
|
|
673
|
+
it 'second terminate works concurrently' do
|
|
674
|
+
assert [world.terminate, world.terminate].map(&:value).all?
|
|
754
675
|
end
|
|
755
676
|
end
|
|
677
|
+
|
|
756
678
|
end
|
|
757
679
|
end
|