aws-flow 1.0.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/Gemfile +8 -0
- data/LICENSE.TXT +15 -0
- data/NOTICE.TXT +14 -0
- data/Rakefile +39 -0
- data/aws-flow-core/Gemfile +9 -0
- data/aws-flow-core/LICENSE.TXT +15 -0
- data/aws-flow-core/NOTICE.TXT +14 -0
- data/aws-flow-core/Rakefile +27 -0
- data/aws-flow-core/aws-flow-core.gemspec +12 -0
- data/aws-flow-core/lib/aws/flow.rb +26 -0
- data/aws-flow-core/lib/aws/flow/async_backtrace.rb +134 -0
- data/aws-flow-core/lib/aws/flow/async_scope.rb +195 -0
- data/aws-flow-core/lib/aws/flow/begin_rescue_ensure.rb +386 -0
- data/aws-flow-core/lib/aws/flow/fiber.rb +77 -0
- data/aws-flow-core/lib/aws/flow/flow_utils.rb +50 -0
- data/aws-flow-core/lib/aws/flow/future.rb +109 -0
- data/aws-flow-core/lib/aws/flow/implementation.rb +151 -0
- data/aws-flow-core/lib/aws/flow/simple_dfa.rb +85 -0
- data/aws-flow-core/lib/aws/flow/tasks.rb +405 -0
- data/aws-flow-core/test/aws/async_backtrace_spec.rb +41 -0
- data/aws-flow-core/test/aws/async_scope_spec.rb +118 -0
- data/aws-flow-core/test/aws/begin_rescue_ensure_spec.rb +665 -0
- data/aws-flow-core/test/aws/external_task_spec.rb +197 -0
- data/aws-flow-core/test/aws/factories.rb +52 -0
- data/aws-flow-core/test/aws/fiber_condition_variable_spec.rb +163 -0
- data/aws-flow-core/test/aws/fiber_spec.rb +78 -0
- data/aws-flow-core/test/aws/flow_spec.rb +255 -0
- data/aws-flow-core/test/aws/future_spec.rb +210 -0
- data/aws-flow-core/test/aws/rubyflow.rb +22 -0
- data/aws-flow-core/test/aws/simple_dfa_spec.rb +63 -0
- data/aws-flow-core/test/aws/spec_helper.rb +36 -0
- data/aws-flow.gemspec +13 -0
- data/lib/aws/decider.rb +67 -0
- data/lib/aws/decider/activity.rb +408 -0
- data/lib/aws/decider/activity_definition.rb +111 -0
- data/lib/aws/decider/async_decider.rb +673 -0
- data/lib/aws/decider/async_retrying_executor.rb +153 -0
- data/lib/aws/decider/data_converter.rb +40 -0
- data/lib/aws/decider/decider.rb +511 -0
- data/lib/aws/decider/decision_context.rb +60 -0
- data/lib/aws/decider/exceptions.rb +178 -0
- data/lib/aws/decider/executor.rb +149 -0
- data/lib/aws/decider/flow_defaults.rb +70 -0
- data/lib/aws/decider/generic_client.rb +178 -0
- data/lib/aws/decider/history_helper.rb +173 -0
- data/lib/aws/decider/implementation.rb +82 -0
- data/lib/aws/decider/options.rb +607 -0
- data/lib/aws/decider/state_machines.rb +373 -0
- data/lib/aws/decider/task_handler.rb +76 -0
- data/lib/aws/decider/task_poller.rb +207 -0
- data/lib/aws/decider/utilities.rb +187 -0
- data/lib/aws/decider/worker.rb +324 -0
- data/lib/aws/decider/workflow_client.rb +374 -0
- data/lib/aws/decider/workflow_clock.rb +104 -0
- data/lib/aws/decider/workflow_definition.rb +101 -0
- data/lib/aws/decider/workflow_definition_factory.rb +53 -0
- data/lib/aws/decider/workflow_enabled.rb +26 -0
- data/test/aws/decider_spec.rb +1299 -0
- data/test/aws/factories.rb +45 -0
- data/test/aws/integration_spec.rb +3108 -0
- data/test/aws/spec_helper.rb +23 -0
- metadata +138 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
##
|
15
|
+
|
16
|
+
require 'rubygems'
|
17
|
+
|
18
|
+
class WorkflowGenerator
|
19
|
+
class << self
|
20
|
+
def generate_workflow(domain, options = {})
|
21
|
+
|
22
|
+
name = options[:name] || "default_name"
|
23
|
+
version = options[:version] || "1"
|
24
|
+
task_list = options[:task_list] || "default_task_list"
|
25
|
+
child_policy = options[:child_policy] || :request_cancel
|
26
|
+
task_start_to_close = options[:task_start_to_close] || 3600
|
27
|
+
default_execution_timeout = options[:default_execution_timeout] || 24 * 3600
|
28
|
+
|
29
|
+
|
30
|
+
target_workflow = domain.workflow_types.page.select { |x| x.name == name}
|
31
|
+
if target_workflow.length == 0
|
32
|
+
workflow_type = domain.workflow_types.create(name, version,
|
33
|
+
:default_task_list => task_list,
|
34
|
+
:default_child_policy => child_policy,
|
35
|
+
:default_task_start_to_close_timeout => task_start_to_close,
|
36
|
+
:default_execution_start_to_close_timeout => default_execution_timeout)
|
37
|
+
else
|
38
|
+
workflow_type = target_workflow.first
|
39
|
+
end
|
40
|
+
|
41
|
+
return workflow_type
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,3108 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
##
|
15
|
+
|
16
|
+
require 'yaml'
|
17
|
+
require 'aws-sdk'
|
18
|
+
require 'logger'
|
19
|
+
|
20
|
+
require 'aws/decider'
|
21
|
+
include AWS::Flow
|
22
|
+
|
23
|
+
$RUBYFLOW_DECIDER_TASK_LIST = 'test_task_list'
|
24
|
+
|
25
|
+
def kill_executors
|
26
|
+
return if ForkingExecutor.executors.nil?
|
27
|
+
ForkingExecutor.executors.each do |executor|
|
28
|
+
executor.shutdown(0) unless executor.is_shutdown rescue StandardError
|
29
|
+
end
|
30
|
+
#TODO Reinstate this, but it's useful to keep them around for debugging
|
31
|
+
#ForkingExecutor.executors = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_swf
|
35
|
+
current_date = Time.now.strftime("%d-%m-%Y")
|
36
|
+
file_name = "/tmp/" + current_date
|
37
|
+
if File.exists?(file_name)
|
38
|
+
last_run = File.open(file_name, 'r').read.to_i
|
39
|
+
else
|
40
|
+
last_run = 0
|
41
|
+
end
|
42
|
+
last_run += 1
|
43
|
+
File.open(file_name, 'w+') {|f| f.write(last_run)}
|
44
|
+
current_date = Time.now.strftime("%d-%m-%Y")
|
45
|
+
config_file = File.open('credentials.cfg') { |f| f.read }
|
46
|
+
config = YAML.load(config_file).first
|
47
|
+
AWS.config(config)
|
48
|
+
swf = AWS::SimpleWorkflow.new
|
49
|
+
$RUBYFLOW_DECIDER_DOMAIN = "rubyflow_decider_domain_#{current_date}-#{last_run}"
|
50
|
+
begin
|
51
|
+
domain = swf.domains.create($RUBYFLOW_DECIDER_DOMAIN, "10")
|
52
|
+
rescue AWS::SimpleWorkflow::Errors::DomainAlreadyExistsFault => e
|
53
|
+
domain = swf.domains[$RUBYFLOW_DECIDER_DOMAIN]
|
54
|
+
end
|
55
|
+
|
56
|
+
return swf, domain, $RUBYFLOW_DECIDER_DOMAIN
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
class SimpleTestHistoryEvent
|
62
|
+
def initialize(id); @id = id; end
|
63
|
+
def attributes; TestHistoryAttributes.new(@id); end
|
64
|
+
end
|
65
|
+
class TestHistoryAttributes
|
66
|
+
def initialize(id); @id = id; end
|
67
|
+
[:activity_id, :workflow_id, :timer_id].each do |method|
|
68
|
+
define_method(method) { @id }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "RubyFlowDecider" do
|
73
|
+
before(:all) do
|
74
|
+
class MyWorkflow
|
75
|
+
extend Decider
|
76
|
+
version "1"
|
77
|
+
# TODO more of the stuff from the proposal
|
78
|
+
end
|
79
|
+
@swf, @domain, $RUBYFLOW_DECIDER_DOMAIN = setup_swf
|
80
|
+
$swf, $domain = @swf, @domain
|
81
|
+
# If there are any outstanding decision tasks before we start the test, that
|
82
|
+
# could really mess things up, and make the tests non-idempotent. So lets
|
83
|
+
# clear those out
|
84
|
+
|
85
|
+
while @domain.decision_tasks.count($RUBYFLOW_DECIDER_TASK_LIST).count > 0
|
86
|
+
@domain.decision_tasks.poll_for_single_task($RUBYFLOW_DECIDER_TASK_LIST) do |task|
|
87
|
+
task.complete workflow_execution
|
88
|
+
end
|
89
|
+
end
|
90
|
+
if @domain.workflow_executions.with_status(:open).count.count > 0
|
91
|
+
@domain.workflow_executions.with_status(:open).each { |wf| wf.terminate }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
before(:each) do
|
95
|
+
kill_executors
|
96
|
+
kill_executors
|
97
|
+
end
|
98
|
+
after(:each) do
|
99
|
+
kill_executors
|
100
|
+
kill_executors
|
101
|
+
end
|
102
|
+
|
103
|
+
it "runs an empty workflow, making sure base configuration stuff is correct" do
|
104
|
+
target_workflow = @domain.workflow_types.page(:per_page => 1000).select { |x| x.name == "blank_workflow_test"}
|
105
|
+
if target_workflow.length == 0
|
106
|
+
workflow_type = @domain.workflow_types.create("blank_workflow_test", '1',
|
107
|
+
:default_task_list => $RUBYFLOW_DECIDER_TASK_LIST,
|
108
|
+
:default_child_policy => :request_cancel,
|
109
|
+
:default_task_start_to_close_timeout => 3600,
|
110
|
+
:default_execution_start_to_close_timeout => 24 * 3600)
|
111
|
+
else
|
112
|
+
workflow_type = target_workflow.first
|
113
|
+
end
|
114
|
+
workflow_execution = workflow_type.start_execution :input => "yay"
|
115
|
+
workflow_execution.terminate
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
describe WorkflowTaskPoller do
|
120
|
+
describe "Integration Tests" do
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "Unit Tests" do
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe WorkflowWorker do
|
130
|
+
describe "Unit Tests" do
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
describe "Integration Tests" do
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe DecisionTaskHandler do
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "interface" do
|
144
|
+
end
|
145
|
+
|
146
|
+
describe Activities do
|
147
|
+
it "ensures that a real activity will get scheduled" do
|
148
|
+
task_list = "activity_task_list"
|
149
|
+
class Blah
|
150
|
+
extend Activity
|
151
|
+
end
|
152
|
+
class BasicActivity
|
153
|
+
extend Activity
|
154
|
+
|
155
|
+
activity :run_activity1 do |o|
|
156
|
+
o.default_task_heartbeat_timeout = "3600"
|
157
|
+
o.default_task_list = "activity_task_list"
|
158
|
+
o.default_task_schedule_to_close_timeout = "3600"
|
159
|
+
o.default_task_schedule_to_start_timeout = "3600"
|
160
|
+
o.default_task_start_to_close_timeout = "3600"
|
161
|
+
o.version = "1"
|
162
|
+
end
|
163
|
+
def run_activity1
|
164
|
+
"first regular activity"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
class BasicWorkflow
|
168
|
+
extend Decider
|
169
|
+
|
170
|
+
entry_point :entry_point
|
171
|
+
version "1"
|
172
|
+
activity_client :activity do |options|
|
173
|
+
options.prefix_name = "BasicActivity"
|
174
|
+
end
|
175
|
+
def entry_point
|
176
|
+
activity.run_activity1
|
177
|
+
end
|
178
|
+
end
|
179
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
180
|
+
worker.add_workflow_implementation(BasicWorkflow)
|
181
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
182
|
+
activity_worker.add_activities_implementation(BasicActivity)
|
183
|
+
workflow_type_name = "BasicWorkflow.entry_point"
|
184
|
+
worker.register
|
185
|
+
activity_worker.register
|
186
|
+
sleep 3
|
187
|
+
workflow_type, _ = @domain.workflow_types.page(:per_page => 1000).select {|x| x.name == workflow_type_name}
|
188
|
+
|
189
|
+
workflow_execution = workflow_type.start_execution(:execution_start_to_close_timeout => 3600, :task_list => task_list, :task_start_to_close_timeout => 3600, :child_policy => :request_cancel)
|
190
|
+
worker.run_once
|
191
|
+
activity_worker.run_once
|
192
|
+
worker.run_once
|
193
|
+
workflow_execution.events.map(&:event_type).should ==
|
194
|
+
["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "ActivityTaskScheduled", "ActivityTaskStarted", "ActivityTaskCompleted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "WorkflowExecutionCompleted"]
|
195
|
+
end
|
196
|
+
|
197
|
+
it "tests to see what two activities look like" do
|
198
|
+
task_list = "double_activity_task_list"
|
199
|
+
class DoubleActivity
|
200
|
+
extend Activity
|
201
|
+
activity :run_activity1, :run_activity2 do |o|
|
202
|
+
o.version = "1"
|
203
|
+
o.default_task_heartbeat_timeout = "3600"
|
204
|
+
o.default_task_list = "double_activity_task_list"
|
205
|
+
o.default_task_schedule_to_close_timeout = "3600"
|
206
|
+
o.default_task_schedule_to_start_timeout = "10"
|
207
|
+
o.default_task_start_to_close_timeout = "10"
|
208
|
+
o.exponential_retry do |retry_options|
|
209
|
+
retry_options.retries_per_exception = {
|
210
|
+
ActivityTaskTimedOutException => Float::INFINITY,
|
211
|
+
}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
def run_activity1
|
215
|
+
"first parallel activity"
|
216
|
+
end
|
217
|
+
def run_activity2
|
218
|
+
"second parallel activity"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
class DoubleWorkflow
|
222
|
+
extend Decider
|
223
|
+
version "1"
|
224
|
+
activity_client :activity do |options|
|
225
|
+
options.prefix_name = "DoubleActivity"
|
226
|
+
end
|
227
|
+
entry_point :entry_point
|
228
|
+
def entry_point
|
229
|
+
activity.send_async(:run_activity1)
|
230
|
+
activity.run_activity2
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
235
|
+
worker.add_workflow_implementation(DoubleWorkflow)
|
236
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
237
|
+
activity_worker.add_activities_implementation(DoubleActivity)
|
238
|
+
workflow_type_name = "DoubleWorkflow.entry_point"
|
239
|
+
worker.register
|
240
|
+
activity_worker.register
|
241
|
+
sleep 3
|
242
|
+
workflow_id = "basic_activity_workflow"
|
243
|
+
run_id = @swf.client.start_workflow_execution(:execution_start_to_close_timeout => "3600", :task_list => {:name => task_list}, :task_start_to_close_timeout => "3600", :child_policy => "REQUEST_CANCEL", :workflow_type => {:name => workflow_type_name, :version => "1"}, :workflow_id => workflow_id, :domain => @domain.name.to_s)
|
244
|
+
workflow_execution = AWS::SimpleWorkflow::WorkflowExecution.new(@domain, workflow_id, run_id["runId"])
|
245
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
246
|
+
@forking_executor.execute { worker.start }
|
247
|
+
sleep 5
|
248
|
+
@forking_executor.execute { activity_worker.start }
|
249
|
+
sleep 20
|
250
|
+
@forking_executor.shutdown(1)
|
251
|
+
|
252
|
+
workflow_history = workflow_execution.events.map(&:event_type)
|
253
|
+
workflow_history.count("ActivityTaskCompleted").should == 2
|
254
|
+
workflow_history.count("WorkflowExecutionCompleted").should == 1
|
255
|
+
end
|
256
|
+
|
257
|
+
it "tests to see that two subsequent activities are supported" do
|
258
|
+
task_list = "subsequent_activity_task_list"
|
259
|
+
class SubsequentActivity
|
260
|
+
extend Activity
|
261
|
+
activity :run_activity1, :run_activity2 do |o|
|
262
|
+
o.default_task_heartbeat_timeout = "3600"
|
263
|
+
o.version = "1"
|
264
|
+
o.default_task_list = "subsequent_activity_task_list"
|
265
|
+
o.default_task_schedule_to_close_timeout = "3600"
|
266
|
+
o.default_task_schedule_to_start_timeout = "20"
|
267
|
+
o.default_task_start_to_close_timeout = "20"
|
268
|
+
end
|
269
|
+
def run_activity1
|
270
|
+
"First subsequent activity"
|
271
|
+
end
|
272
|
+
def run_activity2
|
273
|
+
"Second subsequent activity"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
class SubsequentWorkflow
|
277
|
+
extend Decider
|
278
|
+
version "1"
|
279
|
+
activity_client :activity do |options|
|
280
|
+
options.prefix_name = "SubsequentActivity"
|
281
|
+
end
|
282
|
+
entry_point :entry_point
|
283
|
+
def entry_point
|
284
|
+
activity.run_activity1
|
285
|
+
activity.run_activity2
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
290
|
+
worker.add_workflow_implementation(SubsequentWorkflow)
|
291
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
292
|
+
activity_worker.add_activities_implementation(SubsequentActivity)
|
293
|
+
workflow_type_name = "SubsequentWorkflow.entry_point"
|
294
|
+
worker.register
|
295
|
+
activity_worker.register
|
296
|
+
sleep 3
|
297
|
+
workflow_id = "basic_subsequent_activities"
|
298
|
+
run_id = @swf.client.start_workflow_execution(:execution_start_to_close_timeout => "3600", :task_list => {:name => task_list}, :task_start_to_close_timeout => "3600", :child_policy => "REQUEST_CANCEL", :workflow_type => {:name => workflow_type_name, :version => "1"}, :workflow_id => workflow_id, :domain => @domain.name.to_s)
|
299
|
+
workflow_execution = AWS::SimpleWorkflow::WorkflowExecution.new(@domain, workflow_id, run_id["runId"])
|
300
|
+
worker.run_once
|
301
|
+
activity_worker.run_once
|
302
|
+
worker.run_once
|
303
|
+
activity_worker.run_once
|
304
|
+
worker.run_once
|
305
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
306
|
+
end
|
307
|
+
|
308
|
+
it "tests a much larger workflow" do
|
309
|
+
task_list = "large_activity_task_list"
|
310
|
+
class LargeActivity
|
311
|
+
extend Activity
|
312
|
+
activity :run_activity1, :run_activity2, :run_activity3, :run_activity4 do |o|
|
313
|
+
o.default_task_heartbeat_timeout = "3600"
|
314
|
+
o.default_task_list = "large_activity_task_list"
|
315
|
+
o.default_task_schedule_to_close_timeout = "3600"
|
316
|
+
o.default_task_schedule_to_start_timeout = "5"
|
317
|
+
o.default_task_start_to_close_timeout = "5"
|
318
|
+
o.version = "1"
|
319
|
+
o.exponential_retry do |retry_options|
|
320
|
+
retry_options.retries_per_exception = {
|
321
|
+
ActivityTaskTimedOutException => Float::INFINITY,
|
322
|
+
}
|
323
|
+
end
|
324
|
+
end
|
325
|
+
def run_activity1
|
326
|
+
"My name is Ozymandias - 1"
|
327
|
+
end
|
328
|
+
def run_activity2
|
329
|
+
"King of Kings! - 2 "
|
330
|
+
end
|
331
|
+
def run_activity3
|
332
|
+
"Look on my works, ye mighty - 3"
|
333
|
+
end
|
334
|
+
def run_activity4
|
335
|
+
"And Despair! - 4"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
class LargeWorkflow
|
339
|
+
extend Decider
|
340
|
+
version "1"
|
341
|
+
activity_client :activity do |options|
|
342
|
+
options.prefix_name = "LargeActivity"
|
343
|
+
end
|
344
|
+
entry_point :entry_point
|
345
|
+
def entry_point
|
346
|
+
activity.send_async(:run_activity1)
|
347
|
+
activity.send_async(:run_activity2)
|
348
|
+
activity.send_async(:run_activity3)
|
349
|
+
activity.run_activity4()
|
350
|
+
end
|
351
|
+
end
|
352
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
353
|
+
worker.add_workflow_implementation(LargeWorkflow)
|
354
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
355
|
+
activity_worker.add_activities_implementation(LargeActivity)
|
356
|
+
worker.register
|
357
|
+
activity_worker.register
|
358
|
+
sleep 3
|
359
|
+
|
360
|
+
workflow_type_name = "LargeWorkflow.entry_point"
|
361
|
+
workflow_type, _ = @domain.workflow_types.page(:per_page => 1000).select {|x| x.name == workflow_type_name}
|
362
|
+
|
363
|
+
workflow_execution = workflow_type.start_execution(:execution_start_to_close_timeout => 3600, :task_list => task_list, :task_start_to_close_timeout => 15, :child_policy => :request_cancel)
|
364
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 5)
|
365
|
+
@forking_executor.execute { activity_worker.start }
|
366
|
+
|
367
|
+
|
368
|
+
@forking_executor.execute { worker.start }
|
369
|
+
|
370
|
+
|
371
|
+
sleep 50
|
372
|
+
|
373
|
+
@forking_executor.shutdown(1)
|
374
|
+
workflow_history = workflow_execution.events.map(&:event_type)
|
375
|
+
workflow_execution.events.each {|x| p x}
|
376
|
+
workflow_history.count("WorkflowExecutionCompleted").should == 1
|
377
|
+
workflow_history.count("ActivityTaskCompleted").should == 4
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
describe WorkflowFactory do
|
382
|
+
it "makes sure that you can use the basic workflow_factory" do
|
383
|
+
task_list = "workflow_factory_task_list"
|
384
|
+
class WorkflowFactoryActivity
|
385
|
+
extend Activity
|
386
|
+
activity :run_activity1 do |options|
|
387
|
+
options.version = "1"
|
388
|
+
options.default_task_heartbeat_timeout = "3600"
|
389
|
+
options.default_task_list = "workflow_factory_task_list"
|
390
|
+
options.default_task_schedule_to_close_timeout = "3600"
|
391
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
392
|
+
options.default_task_start_to_close_timeout = "3600"
|
393
|
+
end
|
394
|
+
def run_activity1(arg)
|
395
|
+
"#{arg} is what the activity recieved"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
class WorkflowFactoryWorkflow
|
400
|
+
|
401
|
+
extend Decider
|
402
|
+
version "1"
|
403
|
+
entry_point :entry_point
|
404
|
+
activity_client :activity do |options|
|
405
|
+
options.prefix_name = "WorkflowFactoryActivity"
|
406
|
+
options.default_task_heartbeat_timeout = "3600"
|
407
|
+
options.default_task_list = "workflow_factory_task_list"
|
408
|
+
options.default_task_schedule_to_close_timeout = "3600"
|
409
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
410
|
+
options.default_task_start_to_close_timeout = "3600"
|
411
|
+
end
|
412
|
+
def entry_point(arg)
|
413
|
+
activity.run_activity1("#{arg} recieved as input")
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
418
|
+
worker.add_workflow_implementation(WorkflowFactoryWorkflow)
|
419
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
420
|
+
activity_worker.add_activities_implementation(WorkflowFactoryActivity)
|
421
|
+
worker.register
|
422
|
+
activity_worker.register
|
423
|
+
|
424
|
+
my_workflow_factory = workflow_factory(@swf.client, @domain) do |options|
|
425
|
+
options.workflow_name = "WorkflowFactoryWorkflow"
|
426
|
+
options.execution_start_to_close_timeout = 3600
|
427
|
+
options.task_list = "workflow_factory_task_list"
|
428
|
+
options.task_start_to_close_timeout = 3600
|
429
|
+
options.task_list
|
430
|
+
options.child_policy = :request_cancel
|
431
|
+
end
|
432
|
+
my_workflow = my_workflow_factory.get_client
|
433
|
+
|
434
|
+
workflow_execution = my_workflow.start_execution("some input")
|
435
|
+
|
436
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
437
|
+
|
438
|
+
@forking_executor.execute { worker.start }
|
439
|
+
|
440
|
+
sleep 3
|
441
|
+
|
442
|
+
@forking_executor.execute { activity_worker.start }
|
443
|
+
|
444
|
+
sleep 5
|
445
|
+
|
446
|
+
workflow_execution.events.map(&:event_type).should == ["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "ActivityTaskScheduled", "ActivityTaskStarted", "ActivityTaskCompleted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "WorkflowExecutionCompleted"]
|
447
|
+
@forking_executor.shutdown(1)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
|
452
|
+
class ParentActivity
|
453
|
+
class << self
|
454
|
+
attr_accessor :task_list
|
455
|
+
end
|
456
|
+
end
|
457
|
+
class ParentWorkflow
|
458
|
+
class << self
|
459
|
+
attr_accessor :task_list, :activity_class
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
class GeneralActivity
|
464
|
+
class << self; attr_accessor :task_list; end
|
465
|
+
end
|
466
|
+
class MyWorkflow
|
467
|
+
class << self; attr_accessor :task_list; end
|
468
|
+
end
|
469
|
+
|
470
|
+
def general_test(attributes, &block)
|
471
|
+
task_list = attributes[:task_list] || "general_task_list"
|
472
|
+
class_name = attributes[:class_name] || "General"
|
473
|
+
|
474
|
+
new_activity_class = Class.new(ParentActivity) do
|
475
|
+
extend Activities
|
476
|
+
activity :run_activity1, :run_activity2 do |options|
|
477
|
+
options.default_task_heartbeat_timeout = "3600"
|
478
|
+
options.default_task_list = task_list
|
479
|
+
# options.default_task_schedule_to_close_timeout = "20"
|
480
|
+
options.default_task_schedule_to_start_timeout = "20"
|
481
|
+
options.default_task_start_to_close_timeout = "20"
|
482
|
+
options.version = "1"
|
483
|
+
options.prefix_name = "#{class_name}Activity"
|
484
|
+
end
|
485
|
+
def run_activity1
|
486
|
+
end
|
487
|
+
def run_activity2
|
488
|
+
end
|
489
|
+
end
|
490
|
+
@activity_class = Object.const_set("#{class_name}Activity", new_activity_class)
|
491
|
+
new_workflow_class = Class.new(ParentWorkflow) do
|
492
|
+
extend Workflows
|
493
|
+
workflow(:entry_point) { {:version => 1, :execution_start_to_close_timeout => 3600, :task_list => task_list , :prefix_name => "#{class_name}Workflow"} }
|
494
|
+
def entry_point
|
495
|
+
activity.run_activity1
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
@workflow_class = Object.const_set("#{class_name}Workflow", new_workflow_class)
|
500
|
+
@workflow_class.activity_class = @activity_class
|
501
|
+
@workflow_class.task_list = task_list
|
502
|
+
@activity_class.task_list = task_list
|
503
|
+
@workflow_class.class_eval do
|
504
|
+
activity_client(:activity) { {:from_class => self.activity_class} }
|
505
|
+
end
|
506
|
+
@worker = WorkflowWorker.new(@swf.client, @domain, task_list, @workflow_class)
|
507
|
+
@activity_worker = ActivityWorker.new(@swf.client, @domain, task_list, @activity_class)
|
508
|
+
|
509
|
+
@worker.register
|
510
|
+
@activity_worker.register
|
511
|
+
@my_workflow_client = workflow_client(@swf.client, @domain) { {:from_class => @workflow_class} }
|
512
|
+
end
|
513
|
+
|
514
|
+
it "ensures that backtraces are set correctly with yaml" do
|
515
|
+
general_test(:task_list => "Backtrace_test", :class_name => "BacktraceTest")
|
516
|
+
@workflow_class.class_eval do
|
517
|
+
def entry_point
|
518
|
+
begin
|
519
|
+
activity.run_activity1
|
520
|
+
rescue ActivityTaskFailedException => e
|
521
|
+
error = e
|
522
|
+
e.backtrace.nil?.should == false
|
523
|
+
end
|
524
|
+
return error.backtrace
|
525
|
+
end
|
526
|
+
end
|
527
|
+
@activity_class.class_eval do
|
528
|
+
def run_activity1
|
529
|
+
raise "Error!"
|
530
|
+
end
|
531
|
+
end
|
532
|
+
workflow_execution = @my_workflow_client.start_execution
|
533
|
+
@worker.run_once
|
534
|
+
@activity_worker.run_once
|
535
|
+
@worker.run_once
|
536
|
+
workflow_execution.events.to_a[-1].attributes.result.should =~ /Error!/
|
537
|
+
end
|
538
|
+
describe "Handle_ tests" do
|
539
|
+
# This also effectively tests "RequestCancelExternalWorkflowExecutionInitiated"
|
540
|
+
it "ensures that handle_child_workflow_execution_canceled is correct" do
|
541
|
+
class OtherCancellationChildWorkflow
|
542
|
+
extend Workflows
|
543
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_child_workflow", :execution_start_to_close_timeout => 3600} }
|
544
|
+
def entry_point(arg)
|
545
|
+
create_timer(5)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
class BadCancellationChildWorkflow
|
549
|
+
extend Workflows
|
550
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_parent_workflow", :execution_start_to_close_timeout => 3600} }
|
551
|
+
def other_entry_point
|
552
|
+
end
|
553
|
+
|
554
|
+
def entry_point(arg)
|
555
|
+
client = workflow_client($swf.client, $domain) { {:from_class => "OtherCancellationChildWorkflow"} }
|
556
|
+
workflow_future = client.send_async(:start_execution, 5)
|
557
|
+
client.request_cancel_workflow_execution(workflow_future)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "new_child_workflow", OtherCancellationChildWorkflow)
|
561
|
+
worker2.register
|
562
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "new_parent_workflow", BadCancellationChildWorkflow)
|
563
|
+
worker.register
|
564
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "BadCancellationChildWorkflow"} }
|
565
|
+
workflow_execution = client.entry_point(5)
|
566
|
+
|
567
|
+
worker.run_once
|
568
|
+
worker2.run_once
|
569
|
+
worker.run_once
|
570
|
+
workflow_execution.events.map(&:event_type).should include "ExternalWorkflowExecutionCancelRequested"
|
571
|
+
worker2.run_once
|
572
|
+
workflow_execution.events.map(&:event_type).should include "ChildWorkflowExecutionCanceled"
|
573
|
+
worker.run_once
|
574
|
+
workflow_execution.events.to_a.last.attributes.details.should =~ /AWS::Flow::Core::Cancellation/
|
575
|
+
end
|
576
|
+
|
577
|
+
it "ensures that handle_child_workflow_terminated is handled correctly" do
|
578
|
+
class OtherTerminationChildWorkflow
|
579
|
+
extend Workflows
|
580
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_child_workflow", :execution_start_to_close_timeout => 3600} }
|
581
|
+
|
582
|
+
def entry_point(arg)
|
583
|
+
create_timer(5)
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|
587
|
+
$workflow_id = nil
|
588
|
+
class BadTerminationChildWorkflow
|
589
|
+
extend Workflows
|
590
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_parent_workflow", :execution_start_to_close_timeout => 3600} }
|
591
|
+
def other_entry_point
|
592
|
+
end
|
593
|
+
|
594
|
+
def entry_point(arg)
|
595
|
+
client = workflow_client($swf.client, $domain) { {:from_class => "OtherTerminationChildWorkflow"} }
|
596
|
+
workflow_future = client.send_async(:start_execution, 5)
|
597
|
+
$workflow_id = workflow_future.workflow_execution.workflow_id.get
|
598
|
+
end
|
599
|
+
end
|
600
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "new_child_workflow", OtherTerminationChildWorkflow)
|
601
|
+
worker2.register
|
602
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "new_parent_workflow", BadTerminationChildWorkflow)
|
603
|
+
worker.register
|
604
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "BadTerminationChildWorkflow"} }
|
605
|
+
workflow_execution = client.entry_point(5)
|
606
|
+
|
607
|
+
worker.run_once
|
608
|
+
worker2.run_once
|
609
|
+
$swf.client.terminate_workflow_execution({:workflow_id => $workflow_id, :domain => $domain.name})
|
610
|
+
worker.run_once
|
611
|
+
workflow_execution.events.to_a.last.attributes.details.should =~ /AWS::Flow::ChildWorkflowTerminatedException/
|
612
|
+
end
|
613
|
+
|
614
|
+
it "ensures that handle_child_workflow_timed_out is handled correctly" do
|
615
|
+
class OtherTimedOutChildWorkflow
|
616
|
+
extend Workflows
|
617
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_child_workflow", :execution_start_to_close_timeout => 5} }
|
618
|
+
|
619
|
+
def entry_point(arg)
|
620
|
+
create_timer(5)
|
621
|
+
end
|
622
|
+
|
623
|
+
end
|
624
|
+
$workflow_id = nil
|
625
|
+
class BadTimedOutChildWorkflow
|
626
|
+
extend Workflows
|
627
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_parent_workflow", :execution_start_to_close_timeout => 3600} }
|
628
|
+
def other_entry_point
|
629
|
+
end
|
630
|
+
|
631
|
+
def entry_point(arg)
|
632
|
+
client = workflow_client($swf.client, $domain) { {:from_class => "OtherTimedOutChildWorkflow"} }
|
633
|
+
workflow_future = client.send_async(:start_execution, 5)
|
634
|
+
$workflow_id = workflow_future.workflow_execution.workflow_id.get
|
635
|
+
end
|
636
|
+
end
|
637
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "new_child_workflow", OtherTimedOutChildWorkflow)
|
638
|
+
worker2.register
|
639
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "new_parent_workflow", BadTimedOutChildWorkflow)
|
640
|
+
worker.register
|
641
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "BadTimedOutChildWorkflow"} }
|
642
|
+
workflow_execution = client.entry_point(5)
|
643
|
+
worker.run_once
|
644
|
+
sleep 8
|
645
|
+
worker.run_once
|
646
|
+
workflow_execution.events.to_a.last.attributes.details.should =~ /AWS::Flow::ChildWorkflowTimedOutException/
|
647
|
+
end
|
648
|
+
|
649
|
+
it "ensures that handle_timer_canceled is fine" do
|
650
|
+
general_test(:task_list => "handle_timer_canceled", :class_name => "HandleTimerCanceled")
|
651
|
+
@workflow_class.class_eval do
|
652
|
+
def entry_point
|
653
|
+
bre = error_handler do |t|
|
654
|
+
t.begin do
|
655
|
+
create_timer(100)
|
656
|
+
end
|
657
|
+
t.rescue(CancellationException) {}
|
658
|
+
end
|
659
|
+
create_timer(1)
|
660
|
+
bre.cancel(CancellationException.new)
|
661
|
+
end
|
662
|
+
end
|
663
|
+
workflow_execution = @my_workflow_client.start_execution
|
664
|
+
@worker.run_once
|
665
|
+
@worker.run_once
|
666
|
+
workflow_history = workflow_execution.events.map(&:event_type)
|
667
|
+
workflow_history.count("TimerCanceled").should == 1
|
668
|
+
workflow_history.count("WorkflowExecutionCompleted").should == 1
|
669
|
+
end
|
670
|
+
|
671
|
+
it "ensures that activities under a bre get cancelled" do
|
672
|
+
general_test(:task_list => "activite under bre", :class_name => "ActivitiesUnderBRE")
|
673
|
+
@workflow_class.class_eval do
|
674
|
+
def entry_point
|
675
|
+
bre = error_handler do |t|
|
676
|
+
t.begin { activity.send_async(:run_activity1) }
|
677
|
+
end
|
678
|
+
create_timer(1)
|
679
|
+
bre.cancel(CancellationException.new)
|
680
|
+
end
|
681
|
+
end
|
682
|
+
workflow_execution = @my_workflow_client.start_execution
|
683
|
+
@worker.run_once
|
684
|
+
@worker.run_once
|
685
|
+
workflow_execution.events.map(&:event_type).count("ActivityTaskCancelRequested").should == 1
|
686
|
+
@worker.run_once
|
687
|
+
workflow_execution.events.to_a.last.attributes.reason.should == "AWS::Flow::Core::CancellationException"
|
688
|
+
end
|
689
|
+
|
690
|
+
it "ensures that start_timer_failed is handled correctly" do
|
691
|
+
general_test(:task_list => "start_timer_failed", :class_name => "StartTimerFailed")
|
692
|
+
end
|
693
|
+
|
694
|
+
it "ensures that get_state_method works fine" do
|
695
|
+
general_test(:task_list => "get_state_method", :class_name => "GetStateTest")
|
696
|
+
@workflow_class.class_eval do
|
697
|
+
get_state_method :get_state_test
|
698
|
+
def get_state_test
|
699
|
+
"This is the workflow state!"
|
700
|
+
end
|
701
|
+
end
|
702
|
+
workflow_execution = @my_workflow_client.start_execution
|
703
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "get_state_method", @workflow_class)
|
704
|
+
worker.run_once
|
705
|
+
workflow_execution.events.to_a[3].attributes.execution_context.should =~ /This is the workflow state!/
|
706
|
+
end
|
707
|
+
|
708
|
+
it "ensures that handle_request_cancel_activity_task_failed works" do
|
709
|
+
general_test(:task_list => "handle_request_cancel_activity_task_failed", :class_name => "HandleRCActivityTaskFailed")
|
710
|
+
class AsyncDecider
|
711
|
+
alias_method :old_handle_request_cancel_activity_task_failed, :handle_request_cancel_activity_task_failed
|
712
|
+
# We have to replace this method, otherwise we'd fail on handling the
|
713
|
+
# error because we can't find the decision in the decision_map. There
|
714
|
+
# is similar behavior in javaflow
|
715
|
+
def handle_request_cancel_activity_task_failed(event)
|
716
|
+
event_double = SimpleTestHistoryEvent.new("Activity1")
|
717
|
+
self.send(:old_handle_request_cancel_activity_task_failed, event_double)
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
class ActivityDecisionStateMachine
|
722
|
+
alias_method :old_create_request_cancel_activity_task_decision, :create_request_cancel_activity_task_decision
|
723
|
+
def create_request_cancel_activity_task_decision
|
724
|
+
{ :decision_type => "RequestCancelActivityTask",
|
725
|
+
:request_cancel_activity_task_decision_attributes => {:activity_id => "bad_id"} }
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
@workflow_class.class_eval do
|
730
|
+
def entry_point
|
731
|
+
future = activity.send_async(:run_activity1)
|
732
|
+
create_timer(1)
|
733
|
+
activity.request_cancel_activity_task(future)
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
|
738
|
+
workflow_execution = @my_workflow_client.start_execution
|
739
|
+
@worker.run_once
|
740
|
+
@worker.run_once
|
741
|
+
@worker.run_once
|
742
|
+
|
743
|
+
# In the future, we might want to verify that it transitions the state
|
744
|
+
# machine properly, but at a base, it should not fail the workflow.
|
745
|
+
workflow_execution.events.map(&:event_type).last.should == "DecisionTaskCompleted"
|
746
|
+
class AsyncDecider
|
747
|
+
alias_method :handle_request_cancel_activity_task_failed, :old_handle_request_cancel_activity_task_failed
|
748
|
+
end
|
749
|
+
class ActivityDecisionStateMachine
|
750
|
+
alias_method :create_request_cancel_activity_task_decision,:old_create_request_cancel_activity_task_decision
|
751
|
+
end
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
|
756
|
+
describe "General Testing" do
|
757
|
+
it "makes sure that you can register a workflow with defaults" do
|
758
|
+
general_test(:task_list => "workflow registration", :class_name => "WFRegister")
|
759
|
+
@workflow_class.class_eval do
|
760
|
+
workflow(:test_method) do
|
761
|
+
{
|
762
|
+
:version => 1,
|
763
|
+
:default_task_list => "foo",
|
764
|
+
:default_execution_start_to_close_timeout => 30,
|
765
|
+
:default_child_policy => "request_cancel"
|
766
|
+
}
|
767
|
+
end
|
768
|
+
end
|
769
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "test", @workflow_class)
|
770
|
+
|
771
|
+
worker.register
|
772
|
+
sleep 5
|
773
|
+
@domain.workflow_types.to_a.find{|x| x.name == "#{@workflow_class}.test_method"}.should_not be_nil
|
774
|
+
end
|
775
|
+
|
776
|
+
it "tests that workflow clock gives the same value over multiple replays" do
|
777
|
+
general_test(:task_list => "replaying_test", :class_name => "Replaying_clock")
|
778
|
+
@workflow_class.class_eval do
|
779
|
+
def entry_point
|
780
|
+
|
781
|
+
end
|
782
|
+
end
|
783
|
+
end
|
784
|
+
it "tests to make sure we set replaying correctly" do
|
785
|
+
general_test(:task_list => "is_replaying", :class_name => "Replaying")
|
786
|
+
@workflow_class.class_eval do
|
787
|
+
def entry_point
|
788
|
+
activity.run_activity1
|
789
|
+
decision_context.workflow_clock.replaying
|
790
|
+
end
|
791
|
+
end
|
792
|
+
workflow_execution = @my_workflow_client.start_execution
|
793
|
+
@worker.run_once
|
794
|
+
@activity_worker.run_once
|
795
|
+
@worker.run_once
|
796
|
+
# TODO Kinda hacky, we should be using the workflow_class's data_converter
|
797
|
+
workflow_execution.events.to_a.last.attributes[:result].include? "false"
|
798
|
+
end
|
799
|
+
|
800
|
+
it "makes sure that having a workflow with outstanding activities will close if one fails" do
|
801
|
+
general_test(:task_list => "outstanding_activity_failure", :class_name => "OutstandingActivityFailure")
|
802
|
+
@workflow_class.class_eval do
|
803
|
+
def entry_point
|
804
|
+
activity.send_async(:run_activity1)
|
805
|
+
task do
|
806
|
+
activity.run_activity2 {{:task_list => "foo"}}
|
807
|
+
end
|
808
|
+
end
|
809
|
+
end
|
810
|
+
@activity_class.class_eval do
|
811
|
+
def run_activity1
|
812
|
+
raise "simulated error"
|
813
|
+
end
|
814
|
+
def run_activity2
|
815
|
+
end
|
816
|
+
end
|
817
|
+
workflow_execution = @my_workflow_client.start_execution
|
818
|
+
@worker.run_once
|
819
|
+
@activity_worker.run_once
|
820
|
+
@worker.run_once
|
821
|
+
@worker.run_once
|
822
|
+
workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionFailed"
|
823
|
+
workflow_execution.events.to_a.length.should == 17
|
824
|
+
end
|
825
|
+
|
826
|
+
it "makes sure that you can use the :exponential_retry key" do
|
827
|
+
general_test(:task_list => "exponential_retry_key", :class_name => "ExponentialRetryKey")
|
828
|
+
@workflow_class.class_eval do
|
829
|
+
def entry_point
|
830
|
+
activity.reconfigure(:run_activity1) { {:exponential_retry => {:maximum_attempts => 1}, :task_schedule_to_start_timeout => 5} }
|
831
|
+
activity.run_activity1
|
832
|
+
end
|
833
|
+
end
|
834
|
+
workflow_execution = @my_workflow_client.start_execution
|
835
|
+
4.times { @worker.run_once }
|
836
|
+
workflow_execution.events.to_a.last.event_type.should == "WorkflowExecutionFailed"
|
837
|
+
end
|
838
|
+
|
839
|
+
it "ensures that you can use an arbitrary logger" do
|
840
|
+
testing_file = "/tmp/testing"
|
841
|
+
general_test(:task_list => "arbitrary logger", :class_name => "ArbitraryLogger")
|
842
|
+
File.delete(testing_file) if File.exists? testing_file
|
843
|
+
logger = Logger.new(testing_file)
|
844
|
+
logger.level = Logger::DEBUG
|
845
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "arbitrary logger", @workflow_class) { {:logger => logger} }
|
846
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "arbitrary logger", @activity_class) { { :logger => logger, :execution_workers => 20, :use_forking => false} }
|
847
|
+
execution = @my_workflow_client.start_execution
|
848
|
+
worker.run_once
|
849
|
+
file = File.open(testing_file)
|
850
|
+
# The file should have something in it(i.e., not blank)
|
851
|
+
file.read.should_not =~ /""/
|
852
|
+
# Clear the file so we can be sure the activity worker works too
|
853
|
+
File.open(testing_file, 'w') {}
|
854
|
+
file = File.open(testing_file).read.should_not =~ /""/
|
855
|
+
activity_worker.run_once
|
856
|
+
end
|
857
|
+
it "makes sure that raising an exception in the wf definition is fine" do
|
858
|
+
general_test(:task_list => "exception in wf", :class_name => "WFException")
|
859
|
+
@workflow_class.class_eval do
|
860
|
+
def entry_point
|
861
|
+
raise Exception
|
862
|
+
end
|
863
|
+
end
|
864
|
+
execution = @my_workflow_client.start_execution
|
865
|
+
@worker.run_once
|
866
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionFailed"
|
867
|
+
end
|
868
|
+
it "makes sure that the return value of an activity is directly useable" do
|
869
|
+
general_test(:task_list => "return value activity", :class_name => "ActivityReturn")
|
870
|
+
@activity_class.class_eval do
|
871
|
+
def run_activity1
|
872
|
+
return 5
|
873
|
+
end
|
874
|
+
end
|
875
|
+
@workflow_class.class_eval do
|
876
|
+
def entry_point
|
877
|
+
x = activity.run_activity1
|
878
|
+
x.should == 5
|
879
|
+
end
|
880
|
+
end
|
881
|
+
execution = @my_workflow_client.start_execution
|
882
|
+
@worker.run_once
|
883
|
+
@activity_worker.run_once
|
884
|
+
@worker.run_once
|
885
|
+
end
|
886
|
+
it "makes sure that there is an easy way to get workflow_id" do
|
887
|
+
general_test(:task_list => "workflow_id method", :class_name => "WFID")
|
888
|
+
@workflow_class.class_eval do
|
889
|
+
def entry_point
|
890
|
+
workflow_id
|
891
|
+
end
|
892
|
+
end
|
893
|
+
execution = @my_workflow_client.start_execution
|
894
|
+
@worker.run_once
|
895
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
896
|
+
end
|
897
|
+
it "makes sure that arguments get passed correctly" do
|
898
|
+
task_list = "argument_task_list"
|
899
|
+
class ArgumentActivity
|
900
|
+
class << self; attr_accessor :task_list; end
|
901
|
+
end
|
902
|
+
class ArgumentWorkflow
|
903
|
+
class << self; attr_accessor :task_list; end
|
904
|
+
end
|
905
|
+
|
906
|
+
ArgumentActivity.task_list = task_list
|
907
|
+
ArgumentWorkflow.task_list = task_list
|
908
|
+
class ArgumentActivity
|
909
|
+
class << self
|
910
|
+
attr_accessor :task_list
|
911
|
+
end
|
912
|
+
extend Activity
|
913
|
+
activity :run_activity1 do |options|
|
914
|
+
options.default_task_heartbeat_timeout = "3600"
|
915
|
+
options.default_task_list = ArgumentActivity.task_list
|
916
|
+
options.default_task_schedule_to_close_timeout = "3600"
|
917
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
918
|
+
options.default_task_start_to_close_timeout = "3600"
|
919
|
+
options.version = "1"
|
920
|
+
end
|
921
|
+
def run_activity1(arg)
|
922
|
+
arg.should == 5
|
923
|
+
arg + 1
|
924
|
+
end
|
925
|
+
end
|
926
|
+
class ArgumentWorkflow
|
927
|
+
class << self
|
928
|
+
attr_accessor :task_list, :entry_point_to_call
|
929
|
+
end
|
930
|
+
extend Decider
|
931
|
+
version "1"
|
932
|
+
entry_point :entry_point
|
933
|
+
activity_client :activity do |options|
|
934
|
+
options.prefix_name = "ArgumentActivity"
|
935
|
+
options.default_task_heartbeat_timeout = "3600"
|
936
|
+
options.default_task_list = ArgumentWorkflow.task_list
|
937
|
+
options.default_task_schedule_to_close_timeout = "3600"
|
938
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
939
|
+
options.default_task_start_to_close_timeout = "3600"
|
940
|
+
|
941
|
+
end
|
942
|
+
def entry_point(arg)
|
943
|
+
arg.should == 5
|
944
|
+
activity.run_activity1(arg)
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
949
|
+
worker.add_workflow_implementation(ArgumentWorkflow)
|
950
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
951
|
+
activity_worker.add_activities_implementation(ArgumentActivity)
|
952
|
+
worker.register
|
953
|
+
activity_worker.register
|
954
|
+
my_workflow_factory = workflow_factory(@swf.client, @domain) do |options|
|
955
|
+
options.workflow_name = "ArgumentWorkflow"
|
956
|
+
options.execution_start_to_close_timeout = 3600
|
957
|
+
options.task_list = "argument_task_list"
|
958
|
+
options.task_start_to_close_timeout = 10
|
959
|
+
options.child_policy = :request_cancel
|
960
|
+
end
|
961
|
+
my_workflow = my_workflow_factory.get_client
|
962
|
+
workflow_execution = my_workflow.start_execution(5)
|
963
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
964
|
+
@forking_executor.execute { worker.start }
|
965
|
+
sleep 4
|
966
|
+
@forking_executor.execute { activity_worker.start }
|
967
|
+
|
968
|
+
sleep 9
|
969
|
+
workflow_execution.events.map(&:event_type).should ==
|
970
|
+
["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "ActivityTaskScheduled", "ActivityTaskStarted", "ActivityTaskCompleted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "WorkflowExecutionCompleted"]
|
971
|
+
workflow_execution.events.to_a.last.attributes[:result].should =~ /6/
|
972
|
+
@forking_executor.shutdown(1)
|
973
|
+
end
|
974
|
+
it "makes sure that a standard error works" do
|
975
|
+
general_test(:task_list => "regular error raise", :class_name => "StandardError")
|
976
|
+
@workflow_class.class_eval do
|
977
|
+
def entry_point
|
978
|
+
activity.run_activity1
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
@activity_class.class_eval do
|
983
|
+
def run_activity1
|
984
|
+
raise "This is a simulated error"
|
985
|
+
end
|
986
|
+
end
|
987
|
+
workflow_execution = @my_workflow_client.start_execution
|
988
|
+
|
989
|
+
@worker.run_once
|
990
|
+
@activity_worker.run_once
|
991
|
+
@worker.run_once
|
992
|
+
|
993
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionFailed").should == 1
|
994
|
+
end
|
995
|
+
|
996
|
+
|
997
|
+
it "ensures that exceptions to include functions properly" do
|
998
|
+
general_test(:task_list => "exceptions_to_include", :class_name => "ExceptionsToInclude")
|
999
|
+
@workflow_class.class_eval do
|
1000
|
+
def entry_point
|
1001
|
+
activity.exponential_retry(:run_activity1) { {:exceptions_to_exclude => [SecurityError] } }
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
@activity_class.class_eval do
|
1005
|
+
def run_activity1
|
1006
|
+
raise SecurityError
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
workflow_execution = @my_workflow_client.start_execution
|
1010
|
+
@worker.run_once
|
1011
|
+
@activity_worker.run_once
|
1012
|
+
@worker.run_once
|
1013
|
+
workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionFailed"
|
1014
|
+
end
|
1015
|
+
class YAMLPlusOne
|
1016
|
+
def dump(obj)
|
1017
|
+
obj.to_yaml + "1"
|
1018
|
+
end
|
1019
|
+
def load(source)
|
1020
|
+
source = source[0..-2]
|
1021
|
+
YAML.load source
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
it "makes sure you can set a different converter for activities" do
|
1025
|
+
class DifferentActivityConverterActivity
|
1026
|
+
extend Activities
|
1027
|
+
activity :test_converter do
|
1028
|
+
{
|
1029
|
+
:data_converter => YAMLPlusOne.new,
|
1030
|
+
:default_task_list => "different converter activity",
|
1031
|
+
:version => "1",
|
1032
|
+
:default_task_heartbeat_timeout => "3600",
|
1033
|
+
:default_task_schedule_to_close_timeout => "60",
|
1034
|
+
:default_task_schedule_to_start_timeout => "60",
|
1035
|
+
:default_task_start_to_close_timeout => "60",
|
1036
|
+
}
|
1037
|
+
end
|
1038
|
+
def test_converter
|
1039
|
+
"this"
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain,"different converter activity", DifferentActivityConverterActivity)
|
1043
|
+
class DifferentActivityConverterWorkflow
|
1044
|
+
extend Workflows
|
1045
|
+
workflow(:entry_point) { {:version => "1", :execution_start_to_close_timeout => 3600, :task_list => "different converter activity"} }
|
1046
|
+
activity_client(:activity) { { :from_class => DifferentActivityConverterActivity } }
|
1047
|
+
def entry_point
|
1048
|
+
activity.test_converter
|
1049
|
+
end
|
1050
|
+
end
|
1051
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "different converter activity", DifferentActivityConverterWorkflow)
|
1052
|
+
my_workflow_client = workflow_client(@swf.client, @domain) { { :from_class => DifferentActivityConverterWorkflow } }
|
1053
|
+
worker.register
|
1054
|
+
activity_worker.register
|
1055
|
+
workflow_execution = my_workflow_client.start_execution
|
1056
|
+
worker.run_once
|
1057
|
+
activity_worker.run_once
|
1058
|
+
worker.run_once
|
1059
|
+
workflow_execution.events.to_a[6].attributes[:result].should =~ /1/
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
it "makes sure that timers work" do
|
1063
|
+
general_test(:task_list => "Timer_task_list", :class_name => "Timer")
|
1064
|
+
@workflow_class.class_eval do
|
1065
|
+
def entry_point
|
1066
|
+
create_timer(5)
|
1067
|
+
activity.run_activity1
|
1068
|
+
end
|
1069
|
+
end
|
1070
|
+
workflow_execution = @my_workflow_client.start_execution
|
1071
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
1072
|
+
@forking_executor.execute { @worker.start }
|
1073
|
+
sleep 10
|
1074
|
+
@forking_executor.execute { @activity_worker.start }
|
1075
|
+
sleep 30
|
1076
|
+
workflow_execution.events.map(&:event_type).should ==
|
1077
|
+
["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "TimerStarted", "TimerFired", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "ActivityTaskScheduled", "ActivityTaskStarted", "ActivityTaskCompleted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "WorkflowExecutionCompleted"]
|
1078
|
+
@forking_executor.shutdown(1)
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
|
1082
|
+
it "makes sure that timers can have a block passed in" do
|
1083
|
+
general_test(:task_list => "timer_with_block", :class_name => "TimerBlock")
|
1084
|
+
@workflow_class.class_eval do
|
1085
|
+
def entry_point
|
1086
|
+
create_timer(5) { activity.run_activity1 }
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
workflow_execution = @my_workflow_client.start_execution
|
1090
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
1091
|
+
@forking_executor.execute { @worker.start }
|
1092
|
+
sleep 10
|
1093
|
+
@forking_executor.execute { @activity_worker.start }
|
1094
|
+
sleep 30
|
1095
|
+
@forking_executor.shutdown(1)
|
1096
|
+
workflow_execution.events.map(&:event_type).should ==
|
1097
|
+
["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "TimerStarted", "TimerFired", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "ActivityTaskScheduled", "ActivityTaskStarted", "ActivityTaskCompleted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "WorkflowExecutionCompleted"]
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
it "makes sure that you can have an asynchronous timer" do
|
1101
|
+
general_test(:task_list => "async_timer", :class_name => "Async")
|
1102
|
+
@workflow_class.class_eval do
|
1103
|
+
def entry_point
|
1104
|
+
create_timer_async(5)
|
1105
|
+
activity.run_activity1
|
1106
|
+
end
|
1107
|
+
end
|
1108
|
+
workflow_execution = @my_workflow_client.start_execution
|
1109
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
1110
|
+
@forking_executor.execute { @worker.start }
|
1111
|
+
sleep 5
|
1112
|
+
@forking_executor.execute { @activity_worker.start }
|
1113
|
+
|
1114
|
+
sleep 15
|
1115
|
+
@forking_executor.shutdown(1)
|
1116
|
+
after_first_decision = workflow_execution.events.to_a.slice(4, 2).map(&:event_type)
|
1117
|
+
after_first_decision.should include "TimerStarted"
|
1118
|
+
after_first_decision.should include "ActivityTaskScheduled"
|
1119
|
+
end
|
1120
|
+
it "makes sure that you can have an asynchronous timer with a block" do
|
1121
|
+
general_test(:task_list => "async_timer_with_block", :class_name => "AsyncBlock")
|
1122
|
+
@activity_class.class_eval do
|
1123
|
+
def run_activity2
|
1124
|
+
end
|
1125
|
+
activity :run_activity2 do |options|
|
1126
|
+
options.default_task_heartbeat_timeout = "3600"
|
1127
|
+
options.default_task_list = self.task_list
|
1128
|
+
options.version = "1"
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
@workflow_class.class_eval do
|
1132
|
+
def entry_point
|
1133
|
+
create_timer_async(5) { activity.run_activity1 }
|
1134
|
+
activity.run_activity2
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
@activity_worker = ActivityWorker.new(@swf.client, @domain, "async timer with block", AsyncBlockActivity)
|
1138
|
+
@activity_worker.register
|
1139
|
+
@workflow_execution = @my_workflow_client.start_execution
|
1140
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
1141
|
+
@forking_executor.execute { @worker.start }
|
1142
|
+
sleep 5
|
1143
|
+
@forking_executor.execute { @activity_worker.start }
|
1144
|
+
sleep 15
|
1145
|
+
@forking_executor.shutdown(1)
|
1146
|
+
activity_scheduled = @workflow_execution.events.to_a.each_with_index.map{|x, i| i if x.event_type == "ActivityTaskScheduled"}.compact
|
1147
|
+
history_events = @workflow_execution.events.to_a
|
1148
|
+
history_events[activity_scheduled.first - 1].event_type == "TimerStarted" ||
|
1149
|
+
history_events[activity_scheduled.first + 1].event_type == "TimerStarted"
|
1150
|
+
history_events[activity_scheduled.first].attributes[:activity_type].name.should == "AsyncBlockActivity.run_activity2"
|
1151
|
+
history_events[activity_scheduled.last].attributes[:activity_type].name.should == "AsyncBlockActivity.run_activity1"
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
describe "Child Workflows" do
|
1155
|
+
|
1156
|
+
it "is a basic child workflow test" do
|
1157
|
+
class OtherChildWorkflow
|
1158
|
+
extend Decider
|
1159
|
+
version "1"
|
1160
|
+
entry_point :entry_point
|
1161
|
+
def entry_point(arg)
|
1162
|
+
sleep 1
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
end
|
1166
|
+
class BadChildWorkflow
|
1167
|
+
extend Decider
|
1168
|
+
version "1"
|
1169
|
+
def other_entry_point
|
1170
|
+
end
|
1171
|
+
entry_point :entry_point
|
1172
|
+
def entry_point(arg)
|
1173
|
+
my_workflow_factory = workflow_factory($swf.client, $domain) do |options|
|
1174
|
+
options.workflow_name = "OtherChildWorkflow"
|
1175
|
+
options.execution_method = "entry_point"
|
1176
|
+
options.execution_start_to_close_timeout = 3600
|
1177
|
+
options.task_start_to_close_timeout = 10
|
1178
|
+
options.version = "1"
|
1179
|
+
options.task_list = "test2"
|
1180
|
+
end
|
1181
|
+
client = my_workflow_factory.get_client
|
1182
|
+
client.send_async(:start_execution, 5)
|
1183
|
+
client.send_async(:start_execution, 5)
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
1187
|
+
options.workflow_name = "BadChildWorkflow"
|
1188
|
+
options.execution_start_to_close_timeout = 3600
|
1189
|
+
options.task_list = "test"
|
1190
|
+
options.version = "1"
|
1191
|
+
end
|
1192
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "test2")
|
1193
|
+
worker2.add_workflow_implementation(OtherChildWorkflow)
|
1194
|
+
worker2.register
|
1195
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "test")
|
1196
|
+
worker.add_workflow_implementation(BadChildWorkflow)
|
1197
|
+
worker.register
|
1198
|
+
sleep 5
|
1199
|
+
my_workflow_client = my_workflow_factory.get_client
|
1200
|
+
workflow_execution = my_workflow_client.entry_point(5)
|
1201
|
+
# sleep 10
|
1202
|
+
|
1203
|
+
# Start, start off the child workflow
|
1204
|
+
worker.run_once
|
1205
|
+
# Run Both child workflows
|
1206
|
+
worker2.run_once
|
1207
|
+
worker2.run_once
|
1208
|
+
worker.run_once
|
1209
|
+
# Appears to a case that happens sometimes where the history looks like
|
1210
|
+
# ["WorkflowExecutionStarted", "DecisionTaskScheduled", "DecisionTaskStarted", "DecisionTaskCompleted", "StartChildWorkflowExecutionInitiated", "StartChildWorkflowExecutionInitiated", "ChildWorkflowExecutionStarted", "DecisionTaskScheduled", "ChildWorkflowExecutionStarted", "ChildWorkflowExecutionCompleted", "DecisionTaskStarted", "ChildWorkflowExecutionCompleted", "DecisionTaskScheduled", "DecisionTaskCompleted"]
|
1211
|
+
# In order to deal with this, we have the following line below
|
1212
|
+
worker.run_once if workflow_execution.events.map(&:event_type).last == "DecisionTaskCompleted"
|
1213
|
+
events = workflow_execution.events.map(&:event_type)
|
1214
|
+
events.should include "ChildWorkflowExecutionStarted"
|
1215
|
+
events.should include "ChildWorkflowExecutionCompleted"
|
1216
|
+
events.should include "WorkflowExecutionCompleted"
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
it "ensures that workflow clock provides at least basic support for current_time_millis" do
|
1220
|
+
general_test(:task_list => "workflow_clock_basic", :class_name => "WorkflowClockBasic")
|
1221
|
+
|
1222
|
+
@workflow_class.class_eval do
|
1223
|
+
class << self
|
1224
|
+
attr_accessor :time_hash, :replaying_hash
|
1225
|
+
end
|
1226
|
+
def entry_point
|
1227
|
+
def record_point(name)
|
1228
|
+
self.class.replaying_hash[name] << decision_context.workflow_clock.replaying
|
1229
|
+
self.class.time_hash[name] << decision_context.workflow_clock.current_time
|
1230
|
+
end
|
1231
|
+
record_point(:first)
|
1232
|
+
create_timer(5)
|
1233
|
+
record_point(:second)
|
1234
|
+
create_timer(3)
|
1235
|
+
record_point(:third)
|
1236
|
+
end
|
1237
|
+
end
|
1238
|
+
@workflow_class.time_hash = Hash.new {|hash, key| hash[key] = []}
|
1239
|
+
@workflow_class.replaying_hash = Hash.new {|hash, key| hash[key] = []}
|
1240
|
+
workflow_execution = @my_workflow_client.start_execution
|
1241
|
+
3.times { @worker.run_once }
|
1242
|
+
# Maintain the invariant that you should *not* be replaying only once
|
1243
|
+
@workflow_class.replaying_hash.values.each {|x| x.count(false).should be 1}
|
1244
|
+
# Maintain the invariant that at the same point in the code,
|
1245
|
+
# replay_current_time_millis will return the same value
|
1246
|
+
@workflow_class.time_hash.values.each do |array|
|
1247
|
+
array.reduce {|first, second| first if first.should == second}
|
1248
|
+
end
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
it "ensures that a child workflow failing raises a ChildWorkflowExecutionFailed" do
|
1252
|
+
class FailingChildChildWorkflow
|
1253
|
+
extend Workflows
|
1254
|
+
workflow(:entry_point) { {:version => 1, :task_list => "failing_child_workflow", :execution_start_to_close_timeout => 3600} }
|
1255
|
+
def entry_point(arg)
|
1256
|
+
raise "simulated error"
|
1257
|
+
end
|
1258
|
+
end
|
1259
|
+
class FailingHostChildWorkflow
|
1260
|
+
extend Workflows
|
1261
|
+
workflow(:entry_point) { {:version => 1, :task_list => "failing_parent_workflow", :execution_start_to_close_timeout => 3600} }
|
1262
|
+
def other_entry_point
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
def entry_point(arg)
|
1266
|
+
client = workflow_client($swf.client, $domain) { {:from_class => "FailingChildChildWorkflow"} }
|
1267
|
+
begin
|
1268
|
+
client.start_execution(5)
|
1269
|
+
rescue Exception => e
|
1270
|
+
#pass
|
1271
|
+
end
|
1272
|
+
end
|
1273
|
+
end
|
1274
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "failing_child_workflow", FailingChildChildWorkflow)
|
1275
|
+
worker2.register
|
1276
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "failing_parent_workflow", FailingHostChildWorkflow)
|
1277
|
+
worker.register
|
1278
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "FailingHostChildWorkflow"} }
|
1279
|
+
workflow_execution = client.entry_point(5)
|
1280
|
+
worker.run_once
|
1281
|
+
worker2.run_once
|
1282
|
+
worker2.run_once
|
1283
|
+
worker.run_once
|
1284
|
+
events = workflow_execution.events.map(&:event_type)
|
1285
|
+
events.should include "ChildWorkflowExecutionFailed"
|
1286
|
+
events.should include "WorkflowExecutionCompleted"
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
it "ensures that a child workflow can use data_converter correctly" do
|
1290
|
+
class DataConverterChildChildWorkflow
|
1291
|
+
extend Workflows
|
1292
|
+
workflow(:entry_point) { {:version => 1, :task_list => "data_converter_child_workflow", :execution_start_to_close_timeout => 3600, :data_converter => YAMLPlusOne.new} }
|
1293
|
+
def entry_point(arg)
|
1294
|
+
return arg + 1
|
1295
|
+
end
|
1296
|
+
end
|
1297
|
+
class DataConverterHostChildWorkflow
|
1298
|
+
extend Workflows
|
1299
|
+
workflow(:entry_point) { {:version => 1, :task_list => "data_converter_parent_workflow", :execution_start_to_close_timeout => 3600} }
|
1300
|
+
def other_entry_point
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
def entry_point(arg)
|
1304
|
+
client = workflow_client($swf.client, $domain) { {:from_class => "DataConverterChildChildWorkflow"} }
|
1305
|
+
task { client.start_execution(5) }
|
1306
|
+
end
|
1307
|
+
end
|
1308
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "data_converter_child_workflow", DataConverterChildChildWorkflow)
|
1309
|
+
worker2.register
|
1310
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "data_converter_parent_workflow", DataConverterHostChildWorkflow)
|
1311
|
+
worker.register
|
1312
|
+
|
1313
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "DataConverterHostChildWorkflow"} }
|
1314
|
+
workflow_execution = client.entry_point(5)
|
1315
|
+
worker.run_once
|
1316
|
+
worker2.run_once
|
1317
|
+
worker.run_once
|
1318
|
+
|
1319
|
+
workflow_execution.events.to_a[7].attributes.result.should =~ /1/
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
it "makes sure that the new way of doing child workflows works" do
|
1323
|
+
class OtherNewChildWorkflow
|
1324
|
+
extend Workflows
|
1325
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_child_workflow", :execution_start_to_close_timeout => 3600} }
|
1326
|
+
def entry_point(arg)
|
1327
|
+
sleep 2
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
end
|
1331
|
+
class BadNewChildWorkflow
|
1332
|
+
extend Workflows
|
1333
|
+
workflow(:entry_point) { {:version => 1, :task_list => "new_parent_workflow", :execution_start_to_close_timeout => 3600} }
|
1334
|
+
def other_entry_point
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
def entry_point(arg)
|
1338
|
+
client = workflow_client($swf.client, $domain) { {:from_class => "OtherNewChildWorkflow"} }
|
1339
|
+
task { client.start_execution(5) }
|
1340
|
+
task { client.start_execution(5) }
|
1341
|
+
end
|
1342
|
+
end
|
1343
|
+
worker2 = WorkflowWorker.new(@swf.client, @domain, "new_child_workflow", OtherNewChildWorkflow)
|
1344
|
+
worker2.register
|
1345
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "new_parent_workflow", BadNewChildWorkflow)
|
1346
|
+
worker.register
|
1347
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "BadNewChildWorkflow"} }
|
1348
|
+
workflow_execution = client.entry_point(5)
|
1349
|
+
worker.run_once
|
1350
|
+
worker2.run_once
|
1351
|
+
worker2.run_once
|
1352
|
+
worker.run_once
|
1353
|
+
worker.run_once if workflow_execution.events.map(&:event_type).last == "DecisionTaskCompleted"
|
1354
|
+
events = workflow_execution.events.map(&:event_type)
|
1355
|
+
events.should include "ChildWorkflowExecutionStarted"
|
1356
|
+
events.should include "ChildWorkflowExecutionCompleted"
|
1357
|
+
events.should include "WorkflowExecutionCompleted"
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
it "makes sure that you can use retries_per_exception" do
|
1361
|
+
general_test(:task_list => "retries_per_exception", :class_name => "RetriesPerException")
|
1362
|
+
@activity_class.class_eval do
|
1363
|
+
def run_activity1
|
1364
|
+
raise StandardError
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
@workflow_class.class_eval do
|
1368
|
+
activity_client :activity do |options|
|
1369
|
+
options.default_task_heartbeat_timeout = "3600"
|
1370
|
+
options.default_task_list = self.task_list
|
1371
|
+
options.default_task_schedule_to_close_timeout = "5"
|
1372
|
+
options.default_task_schedule_to_start_timeout = "5"
|
1373
|
+
options.default_task_start_to_close_timeout = "5"
|
1374
|
+
options.version = "1"
|
1375
|
+
options.prefix_name = self.activity_class.to_s
|
1376
|
+
end
|
1377
|
+
def entry_point
|
1378
|
+
activity.exponential_retry(:run_activity1) do |o|
|
1379
|
+
o.retries_per_exception = {
|
1380
|
+
ActivityTaskTimedOutException => Float::INFINITY,
|
1381
|
+
ActivityTaskFailedException => 3
|
1382
|
+
}
|
1383
|
+
end
|
1384
|
+
end
|
1385
|
+
end
|
1386
|
+
|
1387
|
+
workflow_execution = @my_workflow_client.start_execution
|
1388
|
+
@worker.run_once
|
1389
|
+
@activity_worker.run_once
|
1390
|
+
@worker.run_once
|
1391
|
+
@worker.run_once
|
1392
|
+
@activity_worker.run_once
|
1393
|
+
|
1394
|
+
@worker.run_once
|
1395
|
+
@worker.run_once
|
1396
|
+
@activity_worker.run_once
|
1397
|
+
|
1398
|
+
@worker.run_once
|
1399
|
+
|
1400
|
+
workflow_history = workflow_execution.events.map(&:event_type)
|
1401
|
+
workflow_history.count("ActivityTaskFailed").should == 3
|
1402
|
+
|
1403
|
+
workflow_history.count("WorkflowExecutionFailed").should == 1
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
it "makes sure that continueAsNew within a timer works" do
|
1407
|
+
general_test(:task_list => "continue_as_new_timer", :class_name => "ContinueAsNewTimer")
|
1408
|
+
@workflow_class.class_eval do
|
1409
|
+
def entry_point
|
1410
|
+
create_timer(5) do
|
1411
|
+
continue_as_new do |options|
|
1412
|
+
options.execution_start_to_close_timeout = 3600
|
1413
|
+
options.task_list = "continue_as_new_timer"
|
1414
|
+
options.tag_list = []
|
1415
|
+
options.version = "1"
|
1416
|
+
end
|
1417
|
+
end
|
1418
|
+
end
|
1419
|
+
end
|
1420
|
+
@workflow_execution = @my_workflow_client.entry_point
|
1421
|
+
@worker.run_once
|
1422
|
+
@worker.run_once
|
1423
|
+
@workflow_execution.events.map(&:event_type).last.should ==
|
1424
|
+
"WorkflowExecutionContinuedAsNew"
|
1425
|
+
@workflow_execution.status.should ==
|
1426
|
+
:continued_as_new
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
it "ensures that you can write a continue_as_new with less configuration" do
|
1430
|
+
general_test(:task_list => "continue_as_new_config", :class_name => "ContinueAsNewConfiguration")
|
1431
|
+
@workflow_class.class_eval do
|
1432
|
+
def entry_point
|
1433
|
+
continue_as_new
|
1434
|
+
end
|
1435
|
+
end
|
1436
|
+
@workflow_execution = @my_workflow_client.entry_point
|
1437
|
+
@worker.run_once
|
1438
|
+
@workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionContinuedAsNew"
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
it "makes sure that basic continueAsNew works" do
|
1442
|
+
general_test(:task_list => "continue_as_new", :class_name => "ContinueAsNew")
|
1443
|
+
@workflow_class.class_eval do
|
1444
|
+
def entry_point
|
1445
|
+
continue_as_new do |options|
|
1446
|
+
options.workflow_name = @workflow_class.to_s
|
1447
|
+
options.execution_method = :entry_point
|
1448
|
+
options.execution_start_to_close_timeout = 3600
|
1449
|
+
options.task_list = "continue_as_new"
|
1450
|
+
options.tag_list = []
|
1451
|
+
options.task_start_to_close_timeout = 30
|
1452
|
+
options.child_policy = "REQUEST_CANCEL"
|
1453
|
+
options.version = "1"
|
1454
|
+
end
|
1455
|
+
end
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
@workflow_execution = @my_workflow_client.entry_point
|
1459
|
+
@worker.run_once
|
1460
|
+
@workflow_execution.events.map(&:event_type).last.should ==
|
1461
|
+
"WorkflowExecutionContinuedAsNew"
|
1462
|
+
@workflow_execution.status.should ==
|
1463
|
+
:continued_as_new
|
1464
|
+
end
|
1465
|
+
|
1466
|
+
it "makes sure that exponential retry returns values correctly" do
|
1467
|
+
class ExponentialActivity
|
1468
|
+
extend Activity
|
1469
|
+
activity :run_activity1 do |options|
|
1470
|
+
options.version = "1"
|
1471
|
+
options.default_task_list = "exponential_test_return_task_list"
|
1472
|
+
options.default_task_schedule_to_close_timeout = "5"
|
1473
|
+
options.default_task_schedule_to_start_timeout = "5"
|
1474
|
+
options.default_task_start_to_close_timeout = "5"
|
1475
|
+
options.default_task_heartbeat_timeout = "3600"
|
1476
|
+
end
|
1477
|
+
def run_activity1
|
1478
|
+
return 5
|
1479
|
+
end
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
class ExponentialWorkflow
|
1483
|
+
extend Decider
|
1484
|
+
version "1"
|
1485
|
+
|
1486
|
+
activity_client :activity do |options|
|
1487
|
+
options.prefix_name = "ExponentialActivity"
|
1488
|
+
|
1489
|
+
options.default_task_list = "exponential_test_return_task_list"
|
1490
|
+
|
1491
|
+
options.version = "1"
|
1492
|
+
end
|
1493
|
+
entry_point :entry_point
|
1494
|
+
def entry_point
|
1495
|
+
x = activity.exponential_retry(:run_activity1) do |o|
|
1496
|
+
o.retries_per_exception = {
|
1497
|
+
ActivityTaskTimedOutException => Float::INFINITY,
|
1498
|
+
ActivityTaskFailedException => 3
|
1499
|
+
}
|
1500
|
+
end
|
1501
|
+
x.should == 5
|
1502
|
+
end
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
task_list = "exponential_test_return_task_list"
|
1506
|
+
# @swf and @domain are set beforehand with the aws ruby sdk
|
1507
|
+
|
1508
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list, ExponentialWorkflow)
|
1509
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list, ExponentialActivity)
|
1510
|
+
worker.register
|
1511
|
+
|
1512
|
+
activity_worker.register
|
1513
|
+
my_workflow_factory = workflow_factory(@swf.client, @domain) do |options|
|
1514
|
+
options.workflow_name = "ExponentialWorkflow"
|
1515
|
+
options.execution_start_to_close_timeout = 3600
|
1516
|
+
options.task_list = task_list
|
1517
|
+
options.task_start_to_close_timeout = 120
|
1518
|
+
options.child_policy = :request_cancel
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
sleep 5
|
1522
|
+
client = my_workflow_factory.get_client
|
1523
|
+
workflow_execution = client.start_execution
|
1524
|
+
worker.run_once
|
1525
|
+
activity_worker.run_once
|
1526
|
+
|
1527
|
+
worker.run_once
|
1528
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
1529
|
+
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
it "makes sure that signals work correctly" do
|
1533
|
+
class SignalWorkflow
|
1534
|
+
class << self
|
1535
|
+
attr_accessor :task_list, :trace
|
1536
|
+
end
|
1537
|
+
@trace = []
|
1538
|
+
extend Decider
|
1539
|
+
version "1"
|
1540
|
+
def this_signal
|
1541
|
+
@wait.broadcast
|
1542
|
+
end
|
1543
|
+
signal :this_signal
|
1544
|
+
entry_point :entry_point
|
1545
|
+
def entry_point
|
1546
|
+
@wait ||= FiberConditionVariable.new
|
1547
|
+
@wait.wait
|
1548
|
+
end
|
1549
|
+
end
|
1550
|
+
task_list = "SignalWorkflow_tasklist"
|
1551
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
1552
|
+
worker.add_workflow_implementation(SignalWorkflow)
|
1553
|
+
worker.register
|
1554
|
+
my_workflow_factory = workflow_factory(@swf.client, @domain) do |options|
|
1555
|
+
options.workflow_name = "SignalWorkflow"
|
1556
|
+
options.execution_start_to_close_timeout = 3600
|
1557
|
+
options.task_list = task_list
|
1558
|
+
options.task_start_to_close_timeout = 10
|
1559
|
+
options.child_policy = :request_cancel
|
1560
|
+
end
|
1561
|
+
my_workflow = my_workflow_factory.get_client
|
1562
|
+
sleep 3
|
1563
|
+
workflow_execution = my_workflow.start_execution
|
1564
|
+
forking_executor = ForkingExecutor.new(:max_workers => 2)
|
1565
|
+
worker.run_once
|
1566
|
+
my_workflow.signal_workflow_execution("this_signal", workflow_execution)
|
1567
|
+
worker.run_once
|
1568
|
+
forking_executor.shutdown(1)
|
1569
|
+
|
1570
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
it "makes sure that internal signalling works" do
|
1574
|
+
class SignallingActivity
|
1575
|
+
extend Activity
|
1576
|
+
activity :run_activity1 do |options|
|
1577
|
+
options.default_task_heartbeat_timeout = "3600"
|
1578
|
+
options.default_task_list = "SignalWorker_activity_task_task"
|
1579
|
+
options.default_task_schedule_to_close_timeout = "10"
|
1580
|
+
options.default_task_schedule_to_start_timeout = "10"
|
1581
|
+
options.default_task_start_to_close_timeout = "8"
|
1582
|
+
options.version = "1"
|
1583
|
+
end
|
1584
|
+
def run_activity1
|
1585
|
+
return 5
|
1586
|
+
end
|
1587
|
+
end
|
1588
|
+
|
1589
|
+
class SignalInternalWorkflow
|
1590
|
+
extend Decider
|
1591
|
+
version "1"
|
1592
|
+
activity_client :activity do |options|
|
1593
|
+
options.prefix_name = "SignallingActivity"
|
1594
|
+
options.version = "1"
|
1595
|
+
options.default_task_list = "SignalWorker_activity_task_task"
|
1596
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
1597
|
+
options.default_task_start_to_close_timeout = "3600"
|
1598
|
+
end
|
1599
|
+
entry_point :entry_point
|
1600
|
+
def entry_point
|
1601
|
+
my_workflow_factory = workflow_factory($swf, $domain) do |options|
|
1602
|
+
options.workflow_name = "SignalWorkflow"
|
1603
|
+
options.execution_method = "entry_point"
|
1604
|
+
options.execution_start_to_close_timeout = 3600
|
1605
|
+
options.task_start_to_close_timeout = 3600
|
1606
|
+
options.child_policy = :request_cancel
|
1607
|
+
options.version = "1"
|
1608
|
+
options.task_list = "WorkflowSignalee_tasklist"
|
1609
|
+
end
|
1610
|
+
client = my_workflow_factory.get_client
|
1611
|
+
workflow_future = client.send_async(:start_execution)
|
1612
|
+
activity.run_activity1
|
1613
|
+
client.signal_workflow_execution(:this_signal, workflow_future)
|
1614
|
+
end
|
1615
|
+
end
|
1616
|
+
class SignalWorkflow
|
1617
|
+
class << self
|
1618
|
+
attr_accessor :task_list, :trace
|
1619
|
+
end
|
1620
|
+
@trace = []
|
1621
|
+
extend Decider
|
1622
|
+
version "1"
|
1623
|
+
def this_signal
|
1624
|
+
@wait.broadcast
|
1625
|
+
end
|
1626
|
+
signal :this_signal
|
1627
|
+
entry_point :entry_point
|
1628
|
+
def entry_point
|
1629
|
+
@wait ||= FiberConditionVariable.new
|
1630
|
+
@wait.wait
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
task_list = "SignalWorkflow_tasklist"
|
1634
|
+
worker_signalee = WorkflowWorker.new(@swf.client, @domain, "WorkflowSignalee_tasklist")
|
1635
|
+
worker_signalee.add_workflow_implementation(SignalWorkflow)
|
1636
|
+
worker_signaler = WorkflowWorker.new(@swf.client, @domain, task_list)
|
1637
|
+
worker_signaler.add_workflow_implementation(SignalInternalWorkflow)
|
1638
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "SignalWorker_activity_task_task", SignallingActivity)
|
1639
|
+
worker_signaler.register
|
1640
|
+
worker_signalee.register
|
1641
|
+
activity_worker.register
|
1642
|
+
my_workflow_factory = workflow_factory(@swf.client, @domain) do |options|
|
1643
|
+
options.workflow_name = "SignalInternalWorkflow"
|
1644
|
+
options.execution_start_to_close_timeout = 3600
|
1645
|
+
options.task_list = task_list
|
1646
|
+
options.task_start_to_close_timeout = 600
|
1647
|
+
options.child_policy = :request_cancel
|
1648
|
+
end
|
1649
|
+
my_workflow = my_workflow_factory.get_client
|
1650
|
+
workflow_execution = my_workflow.start_execution
|
1651
|
+
worker_signaler.run_once
|
1652
|
+
worker_signalee.run_once
|
1653
|
+
activity_worker.run_once
|
1654
|
+
# Sleep a bit so that the activity execution completes before we decide, so we don't decide on the ChildWorkflowExecutionInitiated before the ActivityTaskCompleted schedules anothe DecisionTaskScheduled
|
1655
|
+
sleep 10
|
1656
|
+
worker_signaler.run_once
|
1657
|
+
worker_signalee.run_once
|
1658
|
+
worker_signaler.run_once
|
1659
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
1660
|
+
end
|
1661
|
+
end
|
1662
|
+
|
1663
|
+
it "makes sure that an error fails an activity" do
|
1664
|
+
task_list = "exponential_retry_test"
|
1665
|
+
GeneralActivity.task_list = task_list
|
1666
|
+
MyWorkflow.task_list = task_list
|
1667
|
+
class GeneralActivity
|
1668
|
+
class << self
|
1669
|
+
attr_accessor :task_list
|
1670
|
+
end
|
1671
|
+
extend Activity
|
1672
|
+
activity :run_activity1 do |options|
|
1673
|
+
options.default_task_list = GeneralActivity.task_list
|
1674
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
1675
|
+
options.default_task_start_to_close_timeout = "3600"
|
1676
|
+
options.version = "1"
|
1677
|
+
end
|
1678
|
+
def run_activity1
|
1679
|
+
raise "error"
|
1680
|
+
end
|
1681
|
+
end
|
1682
|
+
class MyWorkflow
|
1683
|
+
class << self
|
1684
|
+
attr_accessor :task_list
|
1685
|
+
end
|
1686
|
+
extend Decider
|
1687
|
+
version "1"
|
1688
|
+
activity_client :activity do |options|
|
1689
|
+
options.prefix_name = "GeneralActivity"
|
1690
|
+
options.version = "1"
|
1691
|
+
options.default_task_list = MyWorkflow.task_list
|
1692
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
1693
|
+
options.default_task_start_to_close_timeout = "3600"
|
1694
|
+
end
|
1695
|
+
entry_point :entry_point
|
1696
|
+
def entry_point(arg)
|
1697
|
+
activity.run_activity1
|
1698
|
+
end
|
1699
|
+
end
|
1700
|
+
|
1701
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
1702
|
+
worker.add_workflow_implementation(MyWorkflow)
|
1703
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
1704
|
+
activity_worker.add_activities_implementation(GeneralActivity)
|
1705
|
+
worker.register
|
1706
|
+
activity_worker.register
|
1707
|
+
my_workflow_factory = workflow_factory(@swf.client, @domain) do |options|
|
1708
|
+
options.workflow_name = "MyWorkflow"
|
1709
|
+
options.execution_start_to_close_timeout = 3600
|
1710
|
+
options.task_list = task_list
|
1711
|
+
options.task_start_to_close_timeout = 3600
|
1712
|
+
options.child_policy = :request_cancel
|
1713
|
+
end
|
1714
|
+
my_workflow = my_workflow_factory.get_client
|
1715
|
+
|
1716
|
+
workflow_execution = my_workflow.start_execution(5)
|
1717
|
+
|
1718
|
+
sleep 10
|
1719
|
+
@forking_executor = ForkingExecutor.new(:max_workers => 3)
|
1720
|
+
@forking_executor.execute { worker.start }
|
1721
|
+
sleep 5
|
1722
|
+
@forking_executor.execute { activity_worker.start }
|
1723
|
+
|
1724
|
+
sleep 20
|
1725
|
+
@forking_executor.shutdown(1)
|
1726
|
+
workflow_execution.events.map(&:event_type)
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
it "is a good example of the service" do
|
1730
|
+
# Definition of the activity
|
1731
|
+
class AddOneActivity
|
1732
|
+
extend Activity
|
1733
|
+
activity :run_activity1 do |options|
|
1734
|
+
options.default_task_list = "add_one_task_list"
|
1735
|
+
options.version = "1"
|
1736
|
+
options.default_task_heartbeat_timeout = "3600"
|
1737
|
+
options.default_task_schedule_to_close_timeout = "30"
|
1738
|
+
options.default_task_schedule_to_start_timeout = "30"
|
1739
|
+
options.default_task_start_to_close_timeout = "30"
|
1740
|
+
end
|
1741
|
+
def run_activity1(arg)
|
1742
|
+
arg.should == 5
|
1743
|
+
arg + 1
|
1744
|
+
end
|
1745
|
+
end
|
1746
|
+
# Definition of the workflow logic
|
1747
|
+
class MyWorkflow
|
1748
|
+
extend Decider
|
1749
|
+
version "1"
|
1750
|
+
activity_client :activity do |options|
|
1751
|
+
options.prefix_name = "AddOneActivity"
|
1752
|
+
# If we had the activity somewhere we couldn't reach it, we would have
|
1753
|
+
# to have the lines below, but since the have access to the activity, we
|
1754
|
+
# can simply "peek" at its configuration, and use those
|
1755
|
+
|
1756
|
+
# options.default_task_heartbeat_timeout = "3600"
|
1757
|
+
# options.default_task_list = "add_one_task_list"
|
1758
|
+
# options.default_task_schedule_to_close_timeout = "3600"
|
1759
|
+
# options.default_task_schedule_to_start_timeout = "3600"
|
1760
|
+
# options.default_task_start_to_close_timeout = "3600"
|
1761
|
+
end
|
1762
|
+
|
1763
|
+
# The default place to start the execution of a workflow is "entry_point",
|
1764
|
+
# but you can specify any entry point you want with the entry_point method
|
1765
|
+
entry_point :start_my_workflow
|
1766
|
+
def start_my_workflow(arg)
|
1767
|
+
# Should is a Rspec assert statement. E.g. "assert that the variable arg
|
1768
|
+
# is equal to 5"
|
1769
|
+
arg.should == 5
|
1770
|
+
# This makes sure that if there is an error, such a time out, then the
|
1771
|
+
# activity will be rescheduled
|
1772
|
+
activity.exponential_retry(:run_activity1, arg) do |o|
|
1773
|
+
o.maximum_attempts = 3
|
1774
|
+
end
|
1775
|
+
end
|
1776
|
+
end
|
1777
|
+
|
1778
|
+
# Set up the workflow/activity worker
|
1779
|
+
task_list = "add_one_task_list"
|
1780
|
+
# @swf and @domain are set beforehand with the aws ruby sdk
|
1781
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
1782
|
+
worker.add_workflow_implementation(MyWorkflow)
|
1783
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
1784
|
+
activity_worker.add_activities_implementation(AddOneActivity)
|
1785
|
+
worker.register
|
1786
|
+
activity_worker.register
|
1787
|
+
|
1788
|
+
# Get a workflow client to start the workflow
|
1789
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
1790
|
+
options.workflow_name = "MyWorkflow"
|
1791
|
+
options.execution_start_to_close_timeout = 3600
|
1792
|
+
options.task_list = task_list
|
1793
|
+
options.task_start_to_close_timeout = 3600
|
1794
|
+
options.child_policy = :request_cancel
|
1795
|
+
end
|
1796
|
+
# Forking executors have some possibility of race conditions, so we will
|
1797
|
+
# avoid them by putting in a small sleep. There is no plan to fix at current, as
|
1798
|
+
# we don't expect forking executor to be used by most customers.
|
1799
|
+
my_workflow_client = my_workflow_factory.get_client
|
1800
|
+
sleep 5
|
1801
|
+
workflow_execution = my_workflow_client.start_execution(5)
|
1802
|
+
# We use an executor here so as to be able to test this feature within one
|
1803
|
+
# working process, as activity_worker.start and worker.start will block
|
1804
|
+
# otherwise
|
1805
|
+
forking_executor = ForkingExecutor.new
|
1806
|
+
forking_executor.execute { activity_worker.start }
|
1807
|
+
forking_executor.execute { worker.start }
|
1808
|
+
|
1809
|
+
# Sleep to give the threads some time to compute, as we'll run right out of
|
1810
|
+
# the test before they can run otherwise
|
1811
|
+
sleep 40
|
1812
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
it "is an example of joining a parallel split" do
|
1816
|
+
# Definition of the activity
|
1817
|
+
class ParallelSplitActivity
|
1818
|
+
extend Activity
|
1819
|
+
activity :run_activity1, :run_activity2, :run_activity3 do |options|
|
1820
|
+
options.default_task_list = "parallel_split_task_list"
|
1821
|
+
options.version = "1"
|
1822
|
+
options.default_task_heartbeat_timeout = "3600"
|
1823
|
+
options.default_task_schedule_to_close_timeout = "3600"
|
1824
|
+
options.default_task_schedule_to_start_timeout = "3600"
|
1825
|
+
options.default_task_start_to_close_timeout = "3600"
|
1826
|
+
end
|
1827
|
+
def run_activity1(arg)
|
1828
|
+
arg.should == 5
|
1829
|
+
arg + 1
|
1830
|
+
end
|
1831
|
+
def run_activity2(arg)
|
1832
|
+
arg + 2
|
1833
|
+
end
|
1834
|
+
def run_activity3(arg)
|
1835
|
+
arg + 3
|
1836
|
+
end
|
1837
|
+
end
|
1838
|
+
# Definition of the workflow logic
|
1839
|
+
class MyWorkflow
|
1840
|
+
extend Decider
|
1841
|
+
version "1"
|
1842
|
+
activity_client :activity do |options|
|
1843
|
+
options.prefix_name = "ParallelSplitActivity"
|
1844
|
+
# If we had the activity somewhere we couldn't reach it, we would have
|
1845
|
+
# to have the lines below, but since the have access to the activity, we
|
1846
|
+
# can simply "peek" at its configuration, and use those
|
1847
|
+
|
1848
|
+
# options.default_task_heartbeat_timeout = "3600"
|
1849
|
+
# options.default_task_list = "parallel_split_task_list"
|
1850
|
+
# options.default_task_schedule_to_close_timeout = "3600"
|
1851
|
+
# options.default_task_schedule_to_start_timeout = "3600"
|
1852
|
+
# options.default_task_start_to_close_timeout = "3600"
|
1853
|
+
end
|
1854
|
+
|
1855
|
+
# The default place to start the execution of a workflow is "entry_point",
|
1856
|
+
# but you can specify any entry point you want with the entry_point method
|
1857
|
+
entry_point :start_my_workflow
|
1858
|
+
def start_my_workflow(arg)
|
1859
|
+
future_array = []
|
1860
|
+
[:run_activity1, :run_activity2, :run_activity3].each do |this_activity|
|
1861
|
+
# send_async will not block here, but will instead return a
|
1862
|
+
# future. So, at the end of this each loop, future_array will contain
|
1863
|
+
# 3 promises corresponding to the values that will eventually be
|
1864
|
+
# returned from calling the activities
|
1865
|
+
future_array << activity.send_async(this_activity, arg)
|
1866
|
+
|
1867
|
+
# wait_for_all will block until all the promises in the enumerable
|
1868
|
+
# collection that it is given are ready. There is also wait_for_any,
|
1869
|
+
# which will return when any of the promises are ready. In this way,
|
1870
|
+
# you can join on a parallel split.
|
1871
|
+
end
|
1872
|
+
wait_for_all(future_array)
|
1873
|
+
end
|
1874
|
+
end
|
1875
|
+
|
1876
|
+
# Set up the workflow/activity worker
|
1877
|
+
task_list = "parallel_split_task_list"
|
1878
|
+
# @swf and @domain are set beforehand with the aws ruby sdk
|
1879
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
1880
|
+
worker.add_workflow_implementation(MyWorkflow)
|
1881
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
1882
|
+
activity_worker.add_activities_implementation(ParallelSplitActivity)
|
1883
|
+
worker.register
|
1884
|
+
activity_worker.register
|
1885
|
+
|
1886
|
+
# Get a workflow client to start the workflow
|
1887
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
1888
|
+
options.workflow_name = "MyWorkflow"
|
1889
|
+
options.execution_start_to_close_timeout = 3600
|
1890
|
+
options.task_list = task_list
|
1891
|
+
options.task_start_to_close_timeout = 10
|
1892
|
+
options.child_policy = :request_cancel
|
1893
|
+
end
|
1894
|
+
|
1895
|
+
my_workflow_client = my_workflow_factory.get_client
|
1896
|
+
workflow_execution = my_workflow_client.start_execution(5)
|
1897
|
+
|
1898
|
+
# We use an executor here so as to be able to test this feature within one
|
1899
|
+
# working process, as activity_worker.start and worker.start will block
|
1900
|
+
# otherwise
|
1901
|
+
|
1902
|
+
# Forking executors have some possibility of race conditions, so we will
|
1903
|
+
# avoid them by putting in a small sleep. There is no plan to fix at current, as
|
1904
|
+
# we don't expect forking executor to be used by most customers.
|
1905
|
+
sleep 5
|
1906
|
+
forking_executor = ForkingExecutor.new
|
1907
|
+
|
1908
|
+
forking_executor.execute { activity_worker.start }
|
1909
|
+
sleep 5
|
1910
|
+
forking_executor.execute { worker.start }
|
1911
|
+
|
1912
|
+
|
1913
|
+
# Sleep to give the threads some time to compute, as we'll run right out of
|
1914
|
+
# the test before they can run otherwise
|
1915
|
+
sleep 50
|
1916
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
1917
|
+
end
|
1918
|
+
|
1919
|
+
it "is an example of error handling in rubyflow" do
|
1920
|
+
class ErrorHandlingActivity
|
1921
|
+
extend Activity
|
1922
|
+
activity :run_activity1, :run_activity2 do |options|
|
1923
|
+
options.default_task_list = "error_handling_task_list"
|
1924
|
+
options.version = "1"
|
1925
|
+
options.default_task_heartbeat_timeout = "3600"
|
1926
|
+
options.default_task_schedule_to_close_timeout = "10"
|
1927
|
+
options.default_task_schedule_to_start_timeout = "10"
|
1928
|
+
options.default_task_start_to_close_timeout = "10"
|
1929
|
+
end
|
1930
|
+
def run_activity1(arg)
|
1931
|
+
raise StandardError, "run_activity1 failed"
|
1932
|
+
end
|
1933
|
+
def run_activity2(arg)
|
1934
|
+
raise StandardError, "run_activity2 failed"
|
1935
|
+
end
|
1936
|
+
end
|
1937
|
+
# Definition of the workflow logic
|
1938
|
+
class MyWorkflow
|
1939
|
+
extend Decider
|
1940
|
+
version "1"
|
1941
|
+
activity_client :activity do |options|
|
1942
|
+
options.prefix_name = "ErrorHandlingActivity"
|
1943
|
+
# If we had the activity somewhere we couldn't reach it, we would have
|
1944
|
+
# to have the lines below, but since the have access to the activity, we
|
1945
|
+
# can simply "peek" at its configuration, and use those
|
1946
|
+
|
1947
|
+
# options.default_task_heartbeat_timeout = "3600"
|
1948
|
+
# options.default_task_list = "error_handling_task_list"
|
1949
|
+
# options.default_task_schedule_to_close_timeout = "3600"
|
1950
|
+
# options.default_task_schedule_to_start_timeout = "3600"
|
1951
|
+
# options.default_task_start_to_close_timeout = "3600"
|
1952
|
+
end
|
1953
|
+
|
1954
|
+
# The default place to start the execution of a workflow is "entry_point",
|
1955
|
+
# but you can specify any entry point you want with the entry_point method
|
1956
|
+
entry_point :start_my_workflow
|
1957
|
+
def start_my_workflow(arg)
|
1958
|
+
# activity.run_activity1(arg) will "block", and so we can use the normal
|
1959
|
+
# ruby error handler semantics, and if there is a failure, it will
|
1960
|
+
# propagate here
|
1961
|
+
error_seen = nil
|
1962
|
+
begin
|
1963
|
+
activity.run_activity1(arg)
|
1964
|
+
rescue Exception => e
|
1965
|
+
error_seen = e.class
|
1966
|
+
# Do something with the error
|
1967
|
+
ensure
|
1968
|
+
# Should is a Rspec assert statement. E.g. "assert that the variable error_seen
|
1969
|
+
# is equal to StandardError
|
1970
|
+
error_seen.should == ActivityTaskFailedException
|
1971
|
+
# Do something to clean up after
|
1972
|
+
end
|
1973
|
+
# Since send_async won't "block" here, but will schedule a task for
|
1974
|
+
# processing later and evaluate any expressions after the send_async, we
|
1975
|
+
# should use the asynchronous error handler so as to make sure that
|
1976
|
+
# exceptions raised by the send_async will be caught hereerror_seen = nil
|
1977
|
+
error_handler do |t|
|
1978
|
+
t.begin { activity.send_async(:run_activity2, arg) }
|
1979
|
+
t.rescue(Exception) do |error|
|
1980
|
+
error_seen = error.class
|
1981
|
+
# Do something with the error
|
1982
|
+
end
|
1983
|
+
t.ensure do
|
1984
|
+
error_seen.should == ActivityTaskFailedException
|
1985
|
+
# Do something to clean up after
|
1986
|
+
end
|
1987
|
+
end
|
1988
|
+
5
|
1989
|
+
end
|
1990
|
+
end
|
1991
|
+
|
1992
|
+
# Set up the workflow/activity worker
|
1993
|
+
task_list = "error_handling_task_list"
|
1994
|
+
# @swf and @domain are set beforehand with the aws ruby sdk
|
1995
|
+
worker = WorkflowWorker.new(@swf.client, @domain, task_list)
|
1996
|
+
worker.add_workflow_implementation(MyWorkflow)
|
1997
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, task_list)
|
1998
|
+
activity_worker.add_activities_implementation(ErrorHandlingActivity)
|
1999
|
+
worker.register
|
2000
|
+
activity_worker.register
|
2001
|
+
|
2002
|
+
# Get a workflow client to start the workflow
|
2003
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2004
|
+
options.workflow_name = "MyWorkflow"
|
2005
|
+
options.execution_start_to_close_timeout = 3600
|
2006
|
+
options.task_list = task_list
|
2007
|
+
options.task_start_to_close_timeout = 20
|
2008
|
+
options.child_policy = :request_cancel
|
2009
|
+
end
|
2010
|
+
|
2011
|
+
my_workflow_client = my_workflow_factory.get_client
|
2012
|
+
workflow_execution = my_workflow_client.start_execution(5)
|
2013
|
+
|
2014
|
+
# We use an executor here so as to be able to test this feature within one
|
2015
|
+
# working process, as activity_worker.start and worker.start will block
|
2016
|
+
# otherwise
|
2017
|
+
# forking_executor = ForkingExecutor.new
|
2018
|
+
|
2019
|
+
# forking_executor.execute { activity_worker.start }
|
2020
|
+
# class WorkflowWorker
|
2021
|
+
# def start
|
2022
|
+
# poller = WorkflowTaskPoller.new(@service, @domain, DecisionTaskHandler.new(@workflow_definition_map), @ptask_list)
|
2023
|
+
# loop do
|
2024
|
+
# poller.poll_and_process_single_task
|
2025
|
+
# end
|
2026
|
+
# end
|
2027
|
+
# end
|
2028
|
+
# debugger
|
2029
|
+
|
2030
|
+
worker.run_once
|
2031
|
+
activity_worker.run_once
|
2032
|
+
worker.run_once
|
2033
|
+
activity_worker.run_once
|
2034
|
+
worker.run_once
|
2035
|
+
# worker.start
|
2036
|
+
|
2037
|
+
|
2038
|
+
workflow_execution.events.map(&:event_type).count("WorkflowExecutionCompleted").should == 1
|
2039
|
+
end
|
2040
|
+
|
2041
|
+
it "ensures that you can use an internal workflow_client without domain/client" do
|
2042
|
+
general_test(:task_list => "internal_without_domain", :class_name => "InternalWithoutDomain")
|
2043
|
+
@workflow_class.class_eval do
|
2044
|
+
def entry_point
|
2045
|
+
my_workflow_client = workflow_client
|
2046
|
+
my_workflow_client.class.should == WorkflowClient
|
2047
|
+
end
|
2048
|
+
end
|
2049
|
+
|
2050
|
+
workflow_execution = @my_workflow_client.entry_point
|
2051
|
+
@worker.run_once
|
2052
|
+
end
|
2053
|
+
|
2054
|
+
it "ensures you cannot schedule more than 99 things in one decision" do
|
2055
|
+
general_test(:task_list => "schedule_more_than_100", :class_name => "Above100TasksScheduled")
|
2056
|
+
@workflow_class.class_eval do
|
2057
|
+
def entry_point
|
2058
|
+
101.times do
|
2059
|
+
activity.send_async(:run_activity1)
|
2060
|
+
end
|
2061
|
+
end
|
2062
|
+
end
|
2063
|
+
workflow_execution = @my_workflow_client.entry_point
|
2064
|
+
@worker.run_once
|
2065
|
+
workflow_execution.events.map(&:event_type).count("ActivityTaskScheduled").should be 99
|
2066
|
+
@worker.run_once
|
2067
|
+
workflow_execution.events.map(&:event_type).count("ActivityTaskScheduled").should be 101
|
2068
|
+
end
|
2069
|
+
|
2070
|
+
|
2071
|
+
describe "ensures that you can specify the {workflow_id,execution_method} to be used for an external client" do
|
2072
|
+
{:workflow_id => ["blah", "workflow_id"] ,
|
2073
|
+
:execution_method => ["entry_point", "workflow_type.name.split('.').last" ]
|
2074
|
+
}.each_pair do |method, value_and_method_to_check|
|
2075
|
+
value, method_to_check = value_and_method_to_check
|
2076
|
+
swf, domain, _ = setup_swf
|
2077
|
+
it "makes sure that #{method} can be specified correctly" do
|
2078
|
+
class WorkflowIDWorkflow
|
2079
|
+
extend Decider
|
2080
|
+
version "1"
|
2081
|
+
entry_point :entry_point
|
2082
|
+
def entry_point
|
2083
|
+
"yay"
|
2084
|
+
end
|
2085
|
+
end
|
2086
|
+
worker = WorkflowWorker.new(swf.client, domain, "timeout_test", WorkflowIDWorkflow)
|
2087
|
+
worker.register
|
2088
|
+
my_workflow_factory = workflow_factory swf.client, domain do |options|
|
2089
|
+
options.workflow_name = "WorkflowIDWorkflow"
|
2090
|
+
options.execution_start_to_close_timeout = 3600
|
2091
|
+
options.task_list = "timeout_test"
|
2092
|
+
end
|
2093
|
+
my_workflow_client = my_workflow_factory.get_client
|
2094
|
+
execution = my_workflow_client.entry_point do |opt|
|
2095
|
+
opt.send("#{method}=", value)
|
2096
|
+
opt.tag_list = ["stuff"]
|
2097
|
+
end
|
2098
|
+
return_value = eval "execution.#{method_to_check}"
|
2099
|
+
return_value.should == value
|
2100
|
+
execution.tags.should == ["stuff"]
|
2101
|
+
end
|
2102
|
+
end
|
2103
|
+
end
|
2104
|
+
describe "making sure that timeouts are infrequent" do
|
2105
|
+
it "is a basic repro case" do
|
2106
|
+
class TimeoutActivity
|
2107
|
+
extend Activity
|
2108
|
+
activity :run_activity1 do |options|
|
2109
|
+
options.default_task_list = "timeout_test"
|
2110
|
+
options.version = "1"
|
2111
|
+
options.default_task_heartbeat_timeout = "3600"
|
2112
|
+
options.default_task_schedule_to_close_timeout = "60"
|
2113
|
+
options.default_task_schedule_to_start_timeout = "60"
|
2114
|
+
options.default_task_start_to_close_timeout = "60"
|
2115
|
+
end
|
2116
|
+
def run_activity1
|
2117
|
+
"did some work in run_activity1"
|
2118
|
+
end
|
2119
|
+
end
|
2120
|
+
class MyWorkflow
|
2121
|
+
extend Decider
|
2122
|
+
version "1"
|
2123
|
+
activity_client :activity do |options|
|
2124
|
+
options.prefix_name = "TimeoutActivity"
|
2125
|
+
end
|
2126
|
+
entry_point :entry_point
|
2127
|
+
def entry_point
|
2128
|
+
activity.run_activity1
|
2129
|
+
end
|
2130
|
+
end
|
2131
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "timeout_test", MyWorkflow)
|
2132
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "timeout_test", TimeoutActivity)
|
2133
|
+
worker.register
|
2134
|
+
activity_worker.register
|
2135
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2136
|
+
options.workflow_name = "MyWorkflow"
|
2137
|
+
options.execution_start_to_close_timeout = 3600
|
2138
|
+
options.task_list = "timeout_test"
|
2139
|
+
end
|
2140
|
+
my_workflow_client = my_workflow_factory.get_client
|
2141
|
+
num_tests = 50
|
2142
|
+
workflow_executions = []
|
2143
|
+
1.upto(num_tests) { |i| workflow_executions << my_workflow_client.entry_point }
|
2144
|
+
forking_executor = ForkingExecutor.new(:max_workers => 3)
|
2145
|
+
forking_executor.execute { worker.start }
|
2146
|
+
forking_executor.execute { activity_worker.start }
|
2147
|
+
sleep 60
|
2148
|
+
failed_executions = workflow_executions.each{|x| x.events.to_a.last.event_type.should == "WorkflowExecutionCompleted" }
|
2149
|
+
end
|
2150
|
+
end
|
2151
|
+
|
2152
|
+
describe "makes sure that workflow clients expose the same client api and do the right thing" do
|
2153
|
+
it "makes sure that send_async works" do
|
2154
|
+
class SendAsyncWorkflow
|
2155
|
+
extend Decider
|
2156
|
+
version "1"
|
2157
|
+
entry_point :entry_point
|
2158
|
+
def entry_point(arg)
|
2159
|
+
end
|
2160
|
+
end
|
2161
|
+
class SendAsyncBadWorkflow
|
2162
|
+
class << self
|
2163
|
+
attr_accessor :task_list, :trace
|
2164
|
+
end
|
2165
|
+
@trace = []
|
2166
|
+
extend Decider
|
2167
|
+
version "1"
|
2168
|
+
entry_point :entry_point
|
2169
|
+
def entry_point(arg)
|
2170
|
+
my_workflow_factory = workflow_factory($swf_client, $domain) do |options|
|
2171
|
+
options.workflow_name = "SendAsyncWorkflow"
|
2172
|
+
options.execution_method = "entry_point"
|
2173
|
+
options.execution_start_to_close_timeout = 3600
|
2174
|
+
options.task_start_to_close_timeout = 10
|
2175
|
+
options.version = "1"
|
2176
|
+
options.task_list = "client_test_async2"
|
2177
|
+
end
|
2178
|
+
client = my_workflow_factory.get_client
|
2179
|
+
client.send_async(:start_execution, arg) { {:task_start_to_close_timeout => 35 } }
|
2180
|
+
client.send_async(:start_execution, arg)
|
2181
|
+
end
|
2182
|
+
end
|
2183
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "client_test_async", SendAsyncBadWorkflow)
|
2184
|
+
internal_worker = WorkflowWorker.new(@swf.client, @domain, "client_test_async2", SendAsyncWorkflow)
|
2185
|
+
worker.register
|
2186
|
+
internal_worker.register
|
2187
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2188
|
+
options.workflow_name = "SendAsyncBadWorkflow"
|
2189
|
+
options.execution_start_to_close_timeout = 3600
|
2190
|
+
options.task_list = "client_test_async"
|
2191
|
+
end
|
2192
|
+
my_workflow_client = my_workflow_factory.get_client
|
2193
|
+
workflow_execution = my_workflow_client.entry_point(5)
|
2194
|
+
|
2195
|
+
worker.run_once
|
2196
|
+
internal_worker.run_once
|
2197
|
+
internal_worker.run_once
|
2198
|
+
worker.run_once
|
2199
|
+
worker.run_once if workflow_execution.events.map(&:event_type).last == "DecisionTaskCompleted"
|
2200
|
+
history_events = workflow_execution.events.map(&:event_type)
|
2201
|
+
history_events.count("ChildWorkflowExecutionCompleted").should == 2
|
2202
|
+
history_events.count("WorkflowExecutionCompleted").should == 1
|
2203
|
+
end
|
2204
|
+
|
2205
|
+
it "makes sure that retry works" do
|
2206
|
+
class OtherWorkflow
|
2207
|
+
extend Decider
|
2208
|
+
version "1"
|
2209
|
+
entry_point :entry_point
|
2210
|
+
def entry_point(arg)
|
2211
|
+
raise "Simulated error"
|
2212
|
+
end
|
2213
|
+
end
|
2214
|
+
class BadWorkflow
|
2215
|
+
class << self
|
2216
|
+
attr_accessor :task_list, :trace
|
2217
|
+
end
|
2218
|
+
@trace = []
|
2219
|
+
extend Decider
|
2220
|
+
version "1"
|
2221
|
+
entry_point :entry_point
|
2222
|
+
def entry_point(arg)
|
2223
|
+
my_workflow_factory = workflow_factory($swf_client, $domain) do |options|
|
2224
|
+
options.workflow_name = "OtherWorkflow"
|
2225
|
+
options.execution_method = "entry_point"
|
2226
|
+
options.execution_start_to_close_timeout = 3600
|
2227
|
+
options.task_start_to_close_timeout = 10
|
2228
|
+
options.version = "1"
|
2229
|
+
options.task_list = "client_test_retry2"
|
2230
|
+
end
|
2231
|
+
client = my_workflow_factory.get_client
|
2232
|
+
client.exponential_retry(:start_execution, arg) do |opt|
|
2233
|
+
opt.maximum_attempts = 1
|
2234
|
+
end
|
2235
|
+
end
|
2236
|
+
end
|
2237
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "client_test_retry", BadWorkflow)
|
2238
|
+
internal_worker = WorkflowWorker.new(@swf.client, @domain, "client_test_retry2", OtherWorkflow)
|
2239
|
+
worker.register
|
2240
|
+
internal_worker.register
|
2241
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2242
|
+
options.workflow_name = "BadWorkflow"
|
2243
|
+
options.execution_start_to_close_timeout = 3600
|
2244
|
+
options.task_list = "client_test_retry"
|
2245
|
+
end
|
2246
|
+
my_workflow_client = my_workflow_factory.get_client
|
2247
|
+
workflow_execution = my_workflow_client.entry_point(5)
|
2248
|
+
|
2249
|
+
worker.run_once
|
2250
|
+
internal_worker.run_once
|
2251
|
+
# Make sure that we finish the execution and fail before reporting ack
|
2252
|
+
sleep 10
|
2253
|
+
worker.run_once
|
2254
|
+
worker.run_once
|
2255
|
+
internal_worker.run_once
|
2256
|
+
sleep 10
|
2257
|
+
worker.run_once
|
2258
|
+
history_events = workflow_execution.events.map(&:event_type)
|
2259
|
+
history_events.count("ChildWorkflowExecutionFailed").should == 2
|
2260
|
+
history_events.count("WorkflowExecutionFailed").should == 1
|
2261
|
+
end
|
2262
|
+
|
2263
|
+
it "ensures that activity task timed out is not a terminal exception, and that it can use the new option style" do
|
2264
|
+
general_test(:task_list => "activity_task_timed_out", :class_name => "ActivityTaskTimedOut")
|
2265
|
+
@workflow_class.class_eval do
|
2266
|
+
def entry_point
|
2267
|
+
activity.exponential_retry(:run_activity1) do
|
2268
|
+
{
|
2269
|
+
:retries_per_exception => {
|
2270
|
+
ActivityTaskTimedOutException => Float::INFINITY,
|
2271
|
+
ActivityTaskFailedException => 3
|
2272
|
+
}
|
2273
|
+
}
|
2274
|
+
end
|
2275
|
+
end
|
2276
|
+
end
|
2277
|
+
|
2278
|
+
@workflow_execution = @my_workflow_client.entry_point
|
2279
|
+
@worker.run_once
|
2280
|
+
sleep 20
|
2281
|
+
@worker.run_once
|
2282
|
+
@worker.run_once
|
2283
|
+
@workflow_execution.events.map(&:event_type).last.should == "ActivityTaskScheduled"
|
2284
|
+
end
|
2285
|
+
|
2286
|
+
it "ensures that with_retry does synchronous blocking by default" do
|
2287
|
+
general_test(:task_list => "with_retry_synch", :class_name => "WithRetrySynchronous")
|
2288
|
+
@workflow_class.class_eval do
|
2289
|
+
def entry_point
|
2290
|
+
foo = with_retry do
|
2291
|
+
activity.run_activity1
|
2292
|
+
end
|
2293
|
+
activity.run_activity2
|
2294
|
+
end
|
2295
|
+
end
|
2296
|
+
workflow_execution = @my_workflow_client.entry_point
|
2297
|
+
@worker.run_once
|
2298
|
+
# WFExecutionStarted, DecisionTaskScheduled, DecisionTaskStarted, DecisionTaskCompleted, ActivityTaskScheduled(only 1!)
|
2299
|
+
workflow_execution.events.to_a.length.should be 5
|
2300
|
+
end
|
2301
|
+
|
2302
|
+
it "ensures that with_retry does asynchronous blocking correctly" do
|
2303
|
+
general_test(:task_list => "with_retry_synch", :class_name => "WithRetrySynchronous")
|
2304
|
+
@workflow_class.class_eval do
|
2305
|
+
def entry_point
|
2306
|
+
with_retry do
|
2307
|
+
activity.send_async(:run_activity1)
|
2308
|
+
activity.send_async(:run_activity2)
|
2309
|
+
end
|
2310
|
+
end
|
2311
|
+
end
|
2312
|
+
workflow_execution = @my_workflow_client.entry_point
|
2313
|
+
@worker.run_once
|
2314
|
+
# WFExecutionStarted, DecisionTaskScheduled, DecisionTaskStarted, DecisionTaskCompleted, ActivityTaskScheduled(only 1!)
|
2315
|
+
workflow_execution.events.to_a.length.should be 6
|
2316
|
+
end
|
2317
|
+
|
2318
|
+
|
2319
|
+
it "makes sure that option inheritance doesn't override set values" do
|
2320
|
+
class OptionsWorkflow
|
2321
|
+
extend Workflows
|
2322
|
+
version "1"
|
2323
|
+
entry_point :entry_point
|
2324
|
+
def entry_point
|
2325
|
+
"yay"
|
2326
|
+
end
|
2327
|
+
end
|
2328
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "client_test_inheritance", OptionsWorkflow)
|
2329
|
+
worker.register
|
2330
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2331
|
+
options.workflow_name = "OptionsWorkflow"
|
2332
|
+
options.execution_start_to_close_timeout = 3600
|
2333
|
+
options.task_start_to_close_timeout = 10
|
2334
|
+
options.child_policy = :REQUEST_CANCEL
|
2335
|
+
options.task_list = "client_test_inheritance"
|
2336
|
+
end
|
2337
|
+
workflow_execution = my_workflow_factory.get_client.entry_point
|
2338
|
+
workflow_execution.terminate
|
2339
|
+
workflow_execution.child_policy.should == :request_cancel
|
2340
|
+
end
|
2341
|
+
|
2342
|
+
it "makes sure that option inheritance gives you defaults" do
|
2343
|
+
class OptionsWorkflow
|
2344
|
+
extend Workflows
|
2345
|
+
version "1"
|
2346
|
+
entry_point :entry_point
|
2347
|
+
def entry_point
|
2348
|
+
"yay"
|
2349
|
+
end
|
2350
|
+
end
|
2351
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "client_test_inheritance", OptionsWorkflow)
|
2352
|
+
worker.register
|
2353
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2354
|
+
options.workflow_name = "OptionsWorkflow"
|
2355
|
+
options.execution_start_to_close_timeout = 3600
|
2356
|
+
options.child_policy = :REQUEST_CANCEL
|
2357
|
+
options.task_list = "client_test_inheritance"
|
2358
|
+
end
|
2359
|
+
|
2360
|
+
workflow_execution = my_workflow_factory.get_client.entry_point
|
2361
|
+
workflow_execution.terminate
|
2362
|
+
|
2363
|
+
workflow_execution.child_policy.should == :request_cancel
|
2364
|
+
end
|
2365
|
+
|
2366
|
+
it "makes sure that the new option style is supported" do
|
2367
|
+
class NewOptionsActivity
|
2368
|
+
extend Activity
|
2369
|
+
activity :run_activity1 do
|
2370
|
+
{
|
2371
|
+
:default_task_list => "options_test", :version => "1",
|
2372
|
+
:default_task_heartbeat_timeout => "3600",
|
2373
|
+
:default_task_schedule_to_close_timeout => "60",
|
2374
|
+
:default_task_schedule_to_start_timeout => "60",
|
2375
|
+
:default_task_start_to_close_timeout => "60",
|
2376
|
+
}
|
2377
|
+
end
|
2378
|
+
def run_activity1
|
2379
|
+
"did some work in run_activity1"
|
2380
|
+
end
|
2381
|
+
end
|
2382
|
+
class NewOptionsWorkflow
|
2383
|
+
extend Workflows
|
2384
|
+
version "1"
|
2385
|
+
entry_point :entry_point
|
2386
|
+
activity_client :activity do
|
2387
|
+
{
|
2388
|
+
:prefix_name => "NewOptionsActivity", :version => "1"
|
2389
|
+
}
|
2390
|
+
end
|
2391
|
+
def entry_point
|
2392
|
+
activity.run_activity1
|
2393
|
+
end
|
2394
|
+
end
|
2395
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "options_test", NewOptionsWorkflow)
|
2396
|
+
worker.register
|
2397
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "options_test", NewOptionsActivity)
|
2398
|
+
activity_worker.register
|
2399
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2400
|
+
options.workflow_name = "NewOptionsWorkflow"
|
2401
|
+
options.execution_start_to_close_timeout = 3600
|
2402
|
+
options.task_start_to_close_timeout = 10
|
2403
|
+
options.child_policy = :REQUEST_CANCEL
|
2404
|
+
options.task_list = "options_test"
|
2405
|
+
end
|
2406
|
+
workflow_execution = my_workflow_factory.get_client.entry_point
|
2407
|
+
worker.run_once
|
2408
|
+
activity_worker.run_once
|
2409
|
+
worker.run_once
|
2410
|
+
workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2411
|
+
end
|
2412
|
+
|
2413
|
+
|
2414
|
+
|
2415
|
+
it "makes sure that the with_retry is supported" do
|
2416
|
+
class WithRetryActivity
|
2417
|
+
extend Activity
|
2418
|
+
activity :run_activity1 do
|
2419
|
+
{
|
2420
|
+
:default_task_list => "options_test", :version => "1",
|
2421
|
+
:default_task_heartbeat_timeout => "3600",
|
2422
|
+
:default_task_schedule_to_close_timeout => "60",
|
2423
|
+
:default_task_schedule_to_start_timeout => "60",
|
2424
|
+
:default_task_start_to_close_timeout => "60",
|
2425
|
+
}
|
2426
|
+
end
|
2427
|
+
def run_activity1
|
2428
|
+
raise "simulated error"
|
2429
|
+
end
|
2430
|
+
end
|
2431
|
+
class WithRetryWorkflow
|
2432
|
+
extend Workflows
|
2433
|
+
version "1"
|
2434
|
+
entry_point :entry_point
|
2435
|
+
activity_client :activity do
|
2436
|
+
{
|
2437
|
+
:prefix_name => "WithRetryActivity", :version => "1"
|
2438
|
+
}
|
2439
|
+
end
|
2440
|
+
def entry_point
|
2441
|
+
with_retry(:maximum_attempts => 1) { activity.run_activity1 }
|
2442
|
+
end
|
2443
|
+
end
|
2444
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "options_test", WithRetryWorkflow)
|
2445
|
+
worker.register
|
2446
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "options_test", WithRetryActivity)
|
2447
|
+
activity_worker.register
|
2448
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2449
|
+
options.workflow_name = "WithRetryWorkflow"
|
2450
|
+
options.execution_start_to_close_timeout = 3600
|
2451
|
+
options.task_start_to_close_timeout = 10
|
2452
|
+
options.child_policy = :REQUEST_CANCEL
|
2453
|
+
options.task_list = "options_test"
|
2454
|
+
end
|
2455
|
+
workflow_execution = my_workflow_factory.get_client.entry_point
|
2456
|
+
worker.run_once
|
2457
|
+
activity_worker.run_once
|
2458
|
+
worker.run_once # Sets a timer
|
2459
|
+
|
2460
|
+
worker.run_once
|
2461
|
+
activity_worker.run_once
|
2462
|
+
worker.run_once # Sets a timer
|
2463
|
+
|
2464
|
+
events = workflow_execution.events.map(&:event_type)
|
2465
|
+
events.count("ActivityTaskScheduled").should == 2
|
2466
|
+
events.last.should == "WorkflowExecutionFailed"
|
2467
|
+
end
|
2468
|
+
|
2469
|
+
it "makes sure that inheritance of workflows works" do
|
2470
|
+
class InheritWorkflow
|
2471
|
+
extend Workflows
|
2472
|
+
workflow(:test) {{:version => "1"}}
|
2473
|
+
end
|
2474
|
+
class ChildWorkflow < InheritWorkflow; end
|
2475
|
+
ChildWorkflow.workflows.empty?.should == false
|
2476
|
+
end
|
2477
|
+
|
2478
|
+
it "makes sure that inheritance of activities works" do
|
2479
|
+
class InheritActivity
|
2480
|
+
extend Activities
|
2481
|
+
activity :test
|
2482
|
+
end
|
2483
|
+
class ChildActivity < InheritActivity; end
|
2484
|
+
ChildActivity.activities.empty?.should == false
|
2485
|
+
end
|
2486
|
+
|
2487
|
+
it "makes sure that you can set the activity_name" do
|
2488
|
+
|
2489
|
+
class OptionsActivity
|
2490
|
+
extend Activity
|
2491
|
+
activity :run_activity1 do |options|
|
2492
|
+
options.default_task_list = "options_test"
|
2493
|
+
options.version = "1"
|
2494
|
+
options.default_task_heartbeat_timeout = "3600"
|
2495
|
+
options.default_task_schedule_to_close_timeout = "60"
|
2496
|
+
options.default_task_schedule_to_start_timeout = "60"
|
2497
|
+
options.default_task_start_to_close_timeout = "60"
|
2498
|
+
end
|
2499
|
+
def run_activity1
|
2500
|
+
"did some work in run_activity1"
|
2501
|
+
end
|
2502
|
+
end
|
2503
|
+
class OptionsWorkflow
|
2504
|
+
extend Workflows
|
2505
|
+
version "1"
|
2506
|
+
entry_point :entry_point
|
2507
|
+
activity_client :activity do
|
2508
|
+
{
|
2509
|
+
:prefix_name => "OptionsActivity", :version => "1"
|
2510
|
+
}
|
2511
|
+
end
|
2512
|
+
def entry_point
|
2513
|
+
activity.run_activity1
|
2514
|
+
end
|
2515
|
+
end
|
2516
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "options_test", OptionsWorkflow)
|
2517
|
+
worker.register
|
2518
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "options_test", OptionsActivity)
|
2519
|
+
activity_worker.register
|
2520
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2521
|
+
options.workflow_name = "OptionsWorkflow"
|
2522
|
+
options.execution_start_to_close_timeout = 3600
|
2523
|
+
options.task_start_to_close_timeout = 10
|
2524
|
+
options.child_policy = :REQUEST_CANCEL
|
2525
|
+
options.task_list = "options_test"
|
2526
|
+
end
|
2527
|
+
workflow_execution = my_workflow_factory.get_client.entry_point
|
2528
|
+
worker.run_once
|
2529
|
+
activity_worker.run_once
|
2530
|
+
worker.run_once
|
2531
|
+
end
|
2532
|
+
end
|
2533
|
+
|
2534
|
+
it "makes sure that you can create a workflow in the new way" do
|
2535
|
+
class WorkflowWorkflow
|
2536
|
+
extend Workflows
|
2537
|
+
workflow(:entry_point) { {:version => "1", :execution_start_to_close_timeout => 3600, :task_list => "test"} }
|
2538
|
+
def entry_point; "yay";end
|
2539
|
+
end
|
2540
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "test", WorkflowWorkflow)
|
2541
|
+
worker.register
|
2542
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "WorkflowWorkflow"} }
|
2543
|
+
execution = client.start_execution
|
2544
|
+
worker.run_once
|
2545
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2546
|
+
end
|
2547
|
+
it "makes sure that you can use with_opts with workflow_client" do
|
2548
|
+
class WorkflowWorkflow
|
2549
|
+
extend Workflows
|
2550
|
+
workflow(:entry_point) { {:version => "1", :execution_start_to_close_timeout => 3600, :task_list => "test"} }
|
2551
|
+
def entry_point; "yay";end
|
2552
|
+
end
|
2553
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "Foobarbaz", WorkflowWorkflow)
|
2554
|
+
worker.register
|
2555
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "WorkflowWorkflow"} }
|
2556
|
+
execution = client.with_opts(:task_list => "Foobarbaz").start_execution
|
2557
|
+
worker.run_once
|
2558
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2559
|
+
end
|
2560
|
+
|
2561
|
+
it "makes sure you can use with_opts with activity_client" do
|
2562
|
+
class ActivityActivity
|
2563
|
+
extend Activity
|
2564
|
+
activity(:run_activity1) do
|
2565
|
+
{
|
2566
|
+
:version => 1,
|
2567
|
+
:default_task_list => "options_test",
|
2568
|
+
:default_task_heartbeat_timeout => "3600",
|
2569
|
+
:default_task_schedule_to_close_timeout => "60",
|
2570
|
+
:default_task_schedule_to_start_timeout => "60",
|
2571
|
+
:default_task_start_to_close_timeout => "60",
|
2572
|
+
}
|
2573
|
+
end
|
2574
|
+
end
|
2575
|
+
class WorkflowWorkflow
|
2576
|
+
extend Workflows
|
2577
|
+
workflow(:entry_point) { {:version => "1", :execution_start_to_close_timeout => 3600, :task_list => "test"} }
|
2578
|
+
|
2579
|
+
def entry_point; "yay";end
|
2580
|
+
end
|
2581
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "Foobarbaz", WorkflowWorkflow)
|
2582
|
+
worker.register
|
2583
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "WorkflowWorkflow"} }
|
2584
|
+
execution = client.with_opts(:task_list => "Foobarbaz").start_execution
|
2585
|
+
worker.run_once
|
2586
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2587
|
+
end
|
2588
|
+
|
2589
|
+
it "makes sure that workers don't error out on schedule_activity_task_failed" do
|
2590
|
+
class BadActivityActivity
|
2591
|
+
extend Activity
|
2592
|
+
activity(:run_activity1) do
|
2593
|
+
{
|
2594
|
+
:version => 1
|
2595
|
+
}
|
2596
|
+
end
|
2597
|
+
end
|
2598
|
+
class WorkflowWorkflow
|
2599
|
+
extend Workflows
|
2600
|
+
workflow(:entry_point) { {:version => "1", :execution_start_to_close_timeout => 3600, :task_list => "test"} }
|
2601
|
+
activity_client(:client) { {:version => "1", :from_class => "BadActivityActivity"} }
|
2602
|
+
def entry_point; client.run_activity1; end
|
2603
|
+
end
|
2604
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "Foobarbaz", WorkflowWorkflow)
|
2605
|
+
worker.register
|
2606
|
+
client = workflow_client(@swf.client, @domain) { {:from_class => "WorkflowWorkflow"} }
|
2607
|
+
execution = client.with_opts(:task_list => "Foobarbaz").start_execution
|
2608
|
+
worker.run_once
|
2609
|
+
worker.run_once
|
2610
|
+
execution.events.map(&:event_type).last.should == "DecisionTaskCompleted"
|
2611
|
+
end
|
2612
|
+
|
2613
|
+
it "makes sure that you can have arbitrary activity names with from_class" do
|
2614
|
+
general_test(:task_list => "arbitrary_with_from_class", :class_name => "ArbitraryWithFromClass")
|
2615
|
+
@activity_class.class_eval do
|
2616
|
+
activity :test do
|
2617
|
+
{
|
2618
|
+
:default_task_heartbeat_timeout => "3600",
|
2619
|
+
:default_task_list => task_list,
|
2620
|
+
:default_task_schedule_to_close_timeout => "20",
|
2621
|
+
:default_task_schedule_to_start_timeout => "20",
|
2622
|
+
:default_task_start_to_close_timeout => "20",
|
2623
|
+
:version => "1",
|
2624
|
+
:prefix_name => "ArbitraryName",
|
2625
|
+
}
|
2626
|
+
end
|
2627
|
+
def test; end
|
2628
|
+
end
|
2629
|
+
$activity_class = @activity_class
|
2630
|
+
execution = @my_workflow_client.start_execution
|
2631
|
+
@activity_worker = ActivityWorker.new(@swf.client, @domain, "arbitrary_with_from_class", @activity_class)
|
2632
|
+
@activity_worker.register
|
2633
|
+
@workflow_class.class_eval do
|
2634
|
+
activity_client(:test) { {:from_class => $activity_class} }
|
2635
|
+
def entry_point
|
2636
|
+
test.test
|
2637
|
+
end
|
2638
|
+
end
|
2639
|
+
@worker.run_once
|
2640
|
+
@activity_worker.run_once
|
2641
|
+
@worker.run_once
|
2642
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2643
|
+
end
|
2644
|
+
|
2645
|
+
it "makes sure that you can have arbitrary activity names" do
|
2646
|
+
class ArbitraryActivity
|
2647
|
+
extend Activity
|
2648
|
+
def foo
|
2649
|
+
end
|
2650
|
+
activity :foo do
|
2651
|
+
{
|
2652
|
+
:default_task_list => "arbitrary_test",
|
2653
|
+
:version => "1",
|
2654
|
+
:default_task_heartbeat_timeout => "3600",
|
2655
|
+
:default_task_schedule_to_close_timeout => "60",
|
2656
|
+
:default_task_schedule_to_start_timeout => "60",
|
2657
|
+
:default_task_start_to_close_timeout => "60",
|
2658
|
+
:prefix_name => "Foo"
|
2659
|
+
}
|
2660
|
+
end
|
2661
|
+
end
|
2662
|
+
class ArbitraryWorkflow
|
2663
|
+
extend Workflows
|
2664
|
+
workflow(:entry_point) { {:version => "1" }}
|
2665
|
+
activity_client(:client) { {:version => "1", :prefix_name => "Foo"} }
|
2666
|
+
def entry_point
|
2667
|
+
client.foo
|
2668
|
+
end
|
2669
|
+
end
|
2670
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "arbitrary_test", ArbitraryWorkflow)
|
2671
|
+
worker.register
|
2672
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "arbitrary_test", ArbitraryActivity)
|
2673
|
+
activity_worker.register
|
2674
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2675
|
+
options.workflow_name = "ArbitraryWorkflow"
|
2676
|
+
options.execution_start_to_close_timeout = 3600
|
2677
|
+
options.task_start_to_close_timeout = 10
|
2678
|
+
options.child_policy = :REQUEST_CANCEL
|
2679
|
+
options.task_list = "arbitrary_test"
|
2680
|
+
end
|
2681
|
+
execution = my_workflow_factory.get_client.start_execution
|
2682
|
+
worker.run_once
|
2683
|
+
activity_worker.run_once
|
2684
|
+
worker.run_once
|
2685
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2686
|
+
end
|
2687
|
+
it "makes sure that exponential_retry's max_attempts works correctly" do
|
2688
|
+
general_test(:task_list => "exponential_retry_test_max_attempts", :class_name => "ExponentialRetryMaxAttempts")
|
2689
|
+
@activity_class.class_eval do
|
2690
|
+
def run_activity1
|
2691
|
+
raise "error"
|
2692
|
+
end
|
2693
|
+
end
|
2694
|
+
@workflow_class.class_eval do
|
2695
|
+
def entry_point
|
2696
|
+
activity.exponential_retry(:run_activity1) do |o|
|
2697
|
+
o.maximum_attempts = 2
|
2698
|
+
end
|
2699
|
+
end
|
2700
|
+
end
|
2701
|
+
workflow_execution = @my_workflow_client.start_execution
|
2702
|
+
@worker.run_once
|
2703
|
+
@activity_worker.run_once
|
2704
|
+
|
2705
|
+
# first failure
|
2706
|
+
@worker.run_once
|
2707
|
+
@worker.run_once
|
2708
|
+
@activity_worker.run_once
|
2709
|
+
|
2710
|
+
#second failure
|
2711
|
+
@worker.run_once
|
2712
|
+
@worker.run_once
|
2713
|
+
@activity_worker.run_once
|
2714
|
+
|
2715
|
+
# Finally, fail
|
2716
|
+
@worker.run_once
|
2717
|
+
|
2718
|
+
events = workflow_execution.events.map(&:event_type)
|
2719
|
+
events.count("WorkflowExecutionFailed").should == 1
|
2720
|
+
(events.count("ActivityTaskFailed") + events.count("ActivityTaskTimedOut")).should >= 3
|
2721
|
+
end
|
2722
|
+
|
2723
|
+
it "makes sure that exponential_retry's max_attempts works correctly from a configured client" do
|
2724
|
+
general_test(:task_list => "exponential_retry_test_with_configure", :class_name => "ExponentialRetryMaxAttemptsConfigure")
|
2725
|
+
@activity_class.class_eval do
|
2726
|
+
def run_activity1
|
2727
|
+
raise "error"
|
2728
|
+
end
|
2729
|
+
end
|
2730
|
+
@workflow_class.class_eval do
|
2731
|
+
def entry_point
|
2732
|
+
activity.reconfigure(:run_activity1) { {:exponential_retry => {:maximum_attempts => 2}} }
|
2733
|
+
|
2734
|
+
activity.run_activity1
|
2735
|
+
end
|
2736
|
+
end
|
2737
|
+
workflow_execution = @my_workflow_client.start_execution
|
2738
|
+
|
2739
|
+
@worker.run_once
|
2740
|
+
@activity_worker.run_once
|
2741
|
+
|
2742
|
+
# first failure
|
2743
|
+
@worker.run_once
|
2744
|
+
@worker.run_once
|
2745
|
+
@activity_worker.run_once
|
2746
|
+
|
2747
|
+
#second failure
|
2748
|
+
@worker.run_once
|
2749
|
+
@worker.run_once
|
2750
|
+
@activity_worker.run_once
|
2751
|
+
|
2752
|
+
# Finally, fail, catch, and succeed
|
2753
|
+
|
2754
|
+
@worker.run_once
|
2755
|
+
|
2756
|
+
events = workflow_execution.events.map(&:event_type)
|
2757
|
+
events.count("WorkflowExecutionFailed").should == 1
|
2758
|
+
(events.count("ActivityTaskFailed") + events.count("ActivityTaskTimedOut")).should >= 3
|
2759
|
+
end
|
2760
|
+
|
2761
|
+
it "makes sure that exponential_retry allows you to capture the error with configure" do
|
2762
|
+
general_test(:task_list => "exponential_retry_test_capture_with_configure", :class_name => "ExponentialRetryMaxAttemptsCaptureConfigure")
|
2763
|
+
@activity_class.class_eval do
|
2764
|
+
def run_activity1
|
2765
|
+
raise "error"
|
2766
|
+
end
|
2767
|
+
end
|
2768
|
+
@workflow_class.class_eval do
|
2769
|
+
def entry_point
|
2770
|
+
activity.reconfigure(:run_activity1) { {:exponential_retry => {:maximum_attempts => 2}} }
|
2771
|
+
begin
|
2772
|
+
activity.run_activity1
|
2773
|
+
rescue Exception => e
|
2774
|
+
# just making sure I can rescue
|
2775
|
+
end
|
2776
|
+
end
|
2777
|
+
end
|
2778
|
+
workflow_execution = @my_workflow_client.start_execution
|
2779
|
+
@worker.run_once
|
2780
|
+
@activity_worker.run_once
|
2781
|
+
|
2782
|
+
# first failure
|
2783
|
+
@worker.run_once
|
2784
|
+
@worker.run_once
|
2785
|
+
@activity_worker.run_once
|
2786
|
+
|
2787
|
+
#second failure
|
2788
|
+
@worker.run_once
|
2789
|
+
@worker.run_once
|
2790
|
+
@activity_worker.run_once
|
2791
|
+
|
2792
|
+
# Finally, fail, catch, and succeed
|
2793
|
+
@worker.run_once
|
2794
|
+
|
2795
|
+
events = workflow_execution.events.map(&:event_type)
|
2796
|
+
events.count("WorkflowExecutionCompleted").should == 1
|
2797
|
+
(events.count("ActivityTaskFailed") + events.count("ActivityTaskTimedOut")).should >= 3
|
2798
|
+
end
|
2799
|
+
|
2800
|
+
it "ensures that you can change options at the call site" do
|
2801
|
+
general_test(:task_list => "basic_options", :class_name => "BasicOptions")
|
2802
|
+
@workflow_class.class_eval do
|
2803
|
+
def entry_point
|
2804
|
+
activity.run_activity1 { {:start_to_close_timeout => 120 } }
|
2805
|
+
end
|
2806
|
+
end
|
2807
|
+
workflow_execution = @my_workflow_client.start_execution
|
2808
|
+
@worker.run_once
|
2809
|
+
# The default registered is 20, we want to make sure we overrode it
|
2810
|
+
workflow_execution.events.to_a[4].attributes[:start_to_close_timeout].should == 120
|
2811
|
+
end
|
2812
|
+
|
2813
|
+
|
2814
|
+
|
2815
|
+
it "ensures that heartbeats work" do
|
2816
|
+
general_test(:task_list => "basic_heartbeat", :class_name => "BasicHeartbeat")
|
2817
|
+
|
2818
|
+
@activity_class.class_eval do
|
2819
|
+
def run_activity1
|
2820
|
+
6.times do
|
2821
|
+
sleep 5
|
2822
|
+
record_activity_heartbeat("test!")
|
2823
|
+
end
|
2824
|
+
end
|
2825
|
+
end
|
2826
|
+
@workflow_class.class_eval do
|
2827
|
+
def entry_point
|
2828
|
+
activity.run_activity1 { {:heartbeat_timeout => 10, :start_to_close_timeout => 120, :schedule_to_close_timeout => 120} }
|
2829
|
+
end
|
2830
|
+
end
|
2831
|
+
workflow_execution = @my_workflow_client.start_execution
|
2832
|
+
@worker.run_once
|
2833
|
+
@activity_worker.run_once
|
2834
|
+
@worker.run_once
|
2835
|
+
workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2836
|
+
end
|
2837
|
+
|
2838
|
+
it "ensures that you can use heartbeats to request cancel" do
|
2839
|
+
general_test(:task_list => "heartbeat_request_cancel", :class_name => "HeartbeatRequestCancel")
|
2840
|
+
@activity_class.class_eval do
|
2841
|
+
def run_activity1
|
2842
|
+
6.times do
|
2843
|
+
sleep 5
|
2844
|
+
record_activity_heartbeat("test!")
|
2845
|
+
end
|
2846
|
+
raise "If we got here, the test failed, as we should have cancelled the activity"
|
2847
|
+
end
|
2848
|
+
end
|
2849
|
+
@workflow_class.class_eval do
|
2850
|
+
def entry_point
|
2851
|
+
error_handler do |t|
|
2852
|
+
t.begin do
|
2853
|
+
future = activity.run_activity1 { {:heartbeat_timeout => 10, :start_to_close_timeout => 120, :schedule_to_close_timeout => 120, :return_on_start => true} }
|
2854
|
+
create_timer(5)
|
2855
|
+
activity.request_cancel_activity_task(future)
|
2856
|
+
end
|
2857
|
+
t.rescue(CancellationException) { |e| }
|
2858
|
+
end
|
2859
|
+
end
|
2860
|
+
end
|
2861
|
+
|
2862
|
+
workflow_execution = @my_workflow_client.start_execution
|
2863
|
+
forking_executor = ForkingExecutor.new(:max_workers => 1)
|
2864
|
+
@worker.run_once
|
2865
|
+
forking_executor.execute { @activity_worker.start }
|
2866
|
+
sleep 10
|
2867
|
+
@worker.run_once
|
2868
|
+
sleep 10
|
2869
|
+
@worker.run_once
|
2870
|
+
# If we didn't cancel, the activity would fail
|
2871
|
+
workflow_execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2872
|
+
end
|
2873
|
+
|
2874
|
+
it "ensures you can use manual completion" do
|
2875
|
+
general_test(:task_list => "manual_completion", :class_name => "ManualCompletion")
|
2876
|
+
@activity_class.class_eval do
|
2877
|
+
activity :run_activity1 do
|
2878
|
+
{
|
2879
|
+
:default_task_heartbeat_timeout => "3600",
|
2880
|
+
:default_task_list => task_list,
|
2881
|
+
:task_schedule_to_start_timeout => 120,
|
2882
|
+
:task_start_to_close_timeout => 120,
|
2883
|
+
:version => "1",
|
2884
|
+
:manual_completion => true
|
2885
|
+
}
|
2886
|
+
end
|
2887
|
+
end
|
2888
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "manual_completion", @activity_class)
|
2889
|
+
activity_worker.register
|
2890
|
+
@workflow_class.class_eval do
|
2891
|
+
def entry_point
|
2892
|
+
activity.run_activity1
|
2893
|
+
end
|
2894
|
+
end
|
2895
|
+
workflow_execution = @my_workflow_client.start_execution
|
2896
|
+
@worker.run_once
|
2897
|
+
activity_worker.run_once
|
2898
|
+
workflow_execution.events.map(&:event_type).last.should == "ActivityTaskStarted"
|
2899
|
+
end
|
2900
|
+
|
2901
|
+
it "makes sure that exponential_retry allows you to capture the error" do
|
2902
|
+
general_test(:task_list => "exponential_retry_test_capture", :class_name => "ExponentialRetryMaxAttemptsCapture")
|
2903
|
+
@activity_class.class_eval do
|
2904
|
+
def run_activity1
|
2905
|
+
raise "error"
|
2906
|
+
end
|
2907
|
+
end
|
2908
|
+
@workflow_class.class_eval do
|
2909
|
+
def entry_point
|
2910
|
+
begin
|
2911
|
+
activity.exponential_retry(:run_activity1) do |o|
|
2912
|
+
o.maximum_attempts = 2
|
2913
|
+
end
|
2914
|
+
rescue Exception => e
|
2915
|
+
# Just making sure I can rescue
|
2916
|
+
end
|
2917
|
+
end
|
2918
|
+
end
|
2919
|
+
workflow_execution = @my_workflow_client.start_execution
|
2920
|
+
|
2921
|
+
@worker.run_once
|
2922
|
+
@activity_worker.run_once
|
2923
|
+
|
2924
|
+
# first failure
|
2925
|
+
@worker.run_once
|
2926
|
+
@worker.run_once
|
2927
|
+
@activity_worker.run_once
|
2928
|
+
|
2929
|
+
#second failure
|
2930
|
+
@worker.run_once
|
2931
|
+
@worker.run_once
|
2932
|
+
@activity_worker.run_once
|
2933
|
+
|
2934
|
+
|
2935
|
+
# Finally, fail
|
2936
|
+
@worker.run_once
|
2937
|
+
|
2938
|
+
events = workflow_execution.events.map(&:event_type)
|
2939
|
+
events.count("WorkflowExecutionCompleted").should == 1
|
2940
|
+
(events.count("ActivityTaskFailed") + events.count("ActivityTaskTimedOut")).should >= 3
|
2941
|
+
end
|
2942
|
+
|
2943
|
+
it "makes sure that you can use extend Activities" do
|
2944
|
+
class ActivitiesActivity
|
2945
|
+
extend Activities
|
2946
|
+
def foo
|
2947
|
+
end
|
2948
|
+
activity :foo do
|
2949
|
+
{
|
2950
|
+
:default_task_list => "arbitrary_test",
|
2951
|
+
:version => "1",
|
2952
|
+
:default_task_heartbeat_timeout => "3600",
|
2953
|
+
:default_task_schedule_to_close_timeout => "60",
|
2954
|
+
:default_task_schedule_to_start_timeout => "60",
|
2955
|
+
:default_task_start_to_close_timeout => "60",
|
2956
|
+
:prefix_name => "Foo"
|
2957
|
+
}
|
2958
|
+
end
|
2959
|
+
end
|
2960
|
+
class ActivitiesWorkflow
|
2961
|
+
extend Workflows
|
2962
|
+
workflow(:entry_point) { {:version => "1" }}
|
2963
|
+
activity_client(:client) { {:version => "1", :prefix_name => "Foo"} }
|
2964
|
+
def entry_point
|
2965
|
+
client.foo
|
2966
|
+
end
|
2967
|
+
end
|
2968
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "arbitrary_test", ActivitiesWorkflow)
|
2969
|
+
worker.register
|
2970
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "arbitrary_test", ActivitiesActivity)
|
2971
|
+
activity_worker.register
|
2972
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2973
|
+
options.workflow_name = "ActivitiesWorkflow"
|
2974
|
+
options.execution_start_to_close_timeout = 3600
|
2975
|
+
options.task_start_to_close_timeout = 10
|
2976
|
+
options.child_policy = :REQUEST_CANCEL
|
2977
|
+
options.task_list = "arbitrary_test"
|
2978
|
+
end
|
2979
|
+
execution = my_workflow_factory.get_client.start_execution
|
2980
|
+
worker.run_once
|
2981
|
+
activity_worker.run_once
|
2982
|
+
worker.run_once
|
2983
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
2984
|
+
end
|
2985
|
+
|
2986
|
+
it "makes sure that you can't have a '.' in prefix name" do
|
2987
|
+
class ArbitraryWorkflow
|
2988
|
+
extend Workflows
|
2989
|
+
workflow(:entry_point) { {:version => "1" }}
|
2990
|
+
activity_client(:client) { {:version => "1", :prefix_name => "Foo.this"} }
|
2991
|
+
def entry_point
|
2992
|
+
client.foo
|
2993
|
+
end
|
2994
|
+
end
|
2995
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "arbitrary_test", ArbitraryWorkflow)
|
2996
|
+
worker.register
|
2997
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
2998
|
+
options.workflow_name = "ArbitraryWorkflow"
|
2999
|
+
options.execution_start_to_close_timeout = 3600
|
3000
|
+
options.task_start_to_close_timeout = 10
|
3001
|
+
options.child_policy = :REQUEST_CANCEL
|
3002
|
+
options.task_list = "arbitrary_test"
|
3003
|
+
end
|
3004
|
+
execution = my_workflow_factory.get_client.start_execution
|
3005
|
+
worker.run_once
|
3006
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionFailed"
|
3007
|
+
end
|
3008
|
+
|
3009
|
+
it "ensures that reregistering with different values without changing the version will alert you" do
|
3010
|
+
class RegisterActivity
|
3011
|
+
extend Activity
|
3012
|
+
activity :foo do |opt|
|
3013
|
+
opt.version = "1"
|
3014
|
+
opt.default_task_start_to_close_timeout = "60"
|
3015
|
+
end
|
3016
|
+
end
|
3017
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "arbitrary_test", RegisterActivity)
|
3018
|
+
activity_worker.register
|
3019
|
+
class RegisterBadActivity
|
3020
|
+
extend Activity
|
3021
|
+
activity :foo do |opt|
|
3022
|
+
opt.version = "1"
|
3023
|
+
opt.default_task_start_to_close_timeout = 30
|
3024
|
+
opt.prefix_name = "RegisterActivity"
|
3025
|
+
end
|
3026
|
+
end
|
3027
|
+
activity_worker2 = ActivityWorker.new(@swf.client, @domain, "arbitrary_test", RegisterBadActivity)
|
3028
|
+
expect { activity_worker2.register }.to raise_error RuntimeError
|
3029
|
+
end
|
3030
|
+
|
3031
|
+
it "makes sure that you can have arbitrary activity names with the old style options" do
|
3032
|
+
class ArbitraryActivity
|
3033
|
+
extend Activity
|
3034
|
+
def foo
|
3035
|
+
end
|
3036
|
+
activity :foo do |opt|
|
3037
|
+
opt.default_task_list = "arbitrary_test"
|
3038
|
+
opt.version = "1"
|
3039
|
+
opt.default_task_heartbeat_timeout = "3600"
|
3040
|
+
opt.default_task_schedule_to_close_timeout = "60"
|
3041
|
+
opt.default_task_schedule_to_start_timeout = "60"
|
3042
|
+
opt.default_task_start_to_close_timeout = "60"
|
3043
|
+
opt.prefix_name = "Foo"
|
3044
|
+
end
|
3045
|
+
end
|
3046
|
+
class ArbitraryWorkflow
|
3047
|
+
extend Workflows
|
3048
|
+
workflow(:entry_point) { {:version => "1" }}
|
3049
|
+
activity_client(:client) { {:version => "1", :prefix_name => "Foo"} }
|
3050
|
+
def entry_point
|
3051
|
+
client.foo
|
3052
|
+
end
|
3053
|
+
end
|
3054
|
+
worker = WorkflowWorker.new(@swf.client, @domain, "arbitrary_test", ArbitraryWorkflow)
|
3055
|
+
worker.register
|
3056
|
+
activity_worker = ActivityWorker.new(@swf.client, @domain, "arbitrary_test", ArbitraryActivity)
|
3057
|
+
activity_worker.register
|
3058
|
+
my_workflow_factory = workflow_factory @swf.client, @domain do |options|
|
3059
|
+
options.workflow_name = "ArbitraryWorkflow"
|
3060
|
+
options.execution_start_to_close_timeout = 3600
|
3061
|
+
options.task_start_to_close_timeout = 10
|
3062
|
+
options.child_policy = :REQUEST_CANCEL
|
3063
|
+
options.task_list = "arbitrary_test"
|
3064
|
+
end
|
3065
|
+
execution = my_workflow_factory.get_client.start_execution
|
3066
|
+
worker.run_once
|
3067
|
+
activity_worker.run_once
|
3068
|
+
worker.run_once
|
3069
|
+
execution.events.map(&:event_type).last.should == "WorkflowExecutionCompleted"
|
3070
|
+
end
|
3071
|
+
|
3072
|
+
describe "Miscellaneous tests" do
|
3073
|
+
it "will test whether the service client uses the correct user-agent-prefix" do
|
3074
|
+
|
3075
|
+
swf, domain, _ = setup_swf
|
3076
|
+
swf.client.config.user_agent_prefix.should == "ruby-flow"
|
3077
|
+
|
3078
|
+
response = swf.client.list_domains({:registration_status => "REGISTERED"})
|
3079
|
+
result = response.http_request.headers["user-agent"]
|
3080
|
+
|
3081
|
+
result.should match(/^ruby-flow/)
|
3082
|
+
end
|
3083
|
+
|
3084
|
+
it "will test whether from_class can take in non-strings" do
|
3085
|
+
swf, domain, _ = setup_swf
|
3086
|
+
|
3087
|
+
class ActivityActivity
|
3088
|
+
extend Activity
|
3089
|
+
activity(:activity1) do
|
3090
|
+
{
|
3091
|
+
:version => 1
|
3092
|
+
}
|
3093
|
+
end
|
3094
|
+
end
|
3095
|
+
class WorkflowWorkflow
|
3096
|
+
extend Workflows
|
3097
|
+
workflow(:entry_point) { {:version => "1", :execution_start_to_close_timeout => 3600, :task_list => "test"} }
|
3098
|
+
activity_client(:activity) { {:version => "1", :from_class => ActivityActivity} }
|
3099
|
+
def entry_point
|
3100
|
+
activity.activity1
|
3101
|
+
end
|
3102
|
+
end
|
3103
|
+
|
3104
|
+
client = workflow_client(swf.client, domain) { {:from_class => WorkflowWorkflow} }
|
3105
|
+
client.is_execution_method(:entry_point).should == true
|
3106
|
+
end
|
3107
|
+
end
|
3108
|
+
end
|