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.
Files changed (62) hide show
  1. data/Gemfile +8 -0
  2. data/LICENSE.TXT +15 -0
  3. data/NOTICE.TXT +14 -0
  4. data/Rakefile +39 -0
  5. data/aws-flow-core/Gemfile +9 -0
  6. data/aws-flow-core/LICENSE.TXT +15 -0
  7. data/aws-flow-core/NOTICE.TXT +14 -0
  8. data/aws-flow-core/Rakefile +27 -0
  9. data/aws-flow-core/aws-flow-core.gemspec +12 -0
  10. data/aws-flow-core/lib/aws/flow.rb +26 -0
  11. data/aws-flow-core/lib/aws/flow/async_backtrace.rb +134 -0
  12. data/aws-flow-core/lib/aws/flow/async_scope.rb +195 -0
  13. data/aws-flow-core/lib/aws/flow/begin_rescue_ensure.rb +386 -0
  14. data/aws-flow-core/lib/aws/flow/fiber.rb +77 -0
  15. data/aws-flow-core/lib/aws/flow/flow_utils.rb +50 -0
  16. data/aws-flow-core/lib/aws/flow/future.rb +109 -0
  17. data/aws-flow-core/lib/aws/flow/implementation.rb +151 -0
  18. data/aws-flow-core/lib/aws/flow/simple_dfa.rb +85 -0
  19. data/aws-flow-core/lib/aws/flow/tasks.rb +405 -0
  20. data/aws-flow-core/test/aws/async_backtrace_spec.rb +41 -0
  21. data/aws-flow-core/test/aws/async_scope_spec.rb +118 -0
  22. data/aws-flow-core/test/aws/begin_rescue_ensure_spec.rb +665 -0
  23. data/aws-flow-core/test/aws/external_task_spec.rb +197 -0
  24. data/aws-flow-core/test/aws/factories.rb +52 -0
  25. data/aws-flow-core/test/aws/fiber_condition_variable_spec.rb +163 -0
  26. data/aws-flow-core/test/aws/fiber_spec.rb +78 -0
  27. data/aws-flow-core/test/aws/flow_spec.rb +255 -0
  28. data/aws-flow-core/test/aws/future_spec.rb +210 -0
  29. data/aws-flow-core/test/aws/rubyflow.rb +22 -0
  30. data/aws-flow-core/test/aws/simple_dfa_spec.rb +63 -0
  31. data/aws-flow-core/test/aws/spec_helper.rb +36 -0
  32. data/aws-flow.gemspec +13 -0
  33. data/lib/aws/decider.rb +67 -0
  34. data/lib/aws/decider/activity.rb +408 -0
  35. data/lib/aws/decider/activity_definition.rb +111 -0
  36. data/lib/aws/decider/async_decider.rb +673 -0
  37. data/lib/aws/decider/async_retrying_executor.rb +153 -0
  38. data/lib/aws/decider/data_converter.rb +40 -0
  39. data/lib/aws/decider/decider.rb +511 -0
  40. data/lib/aws/decider/decision_context.rb +60 -0
  41. data/lib/aws/decider/exceptions.rb +178 -0
  42. data/lib/aws/decider/executor.rb +149 -0
  43. data/lib/aws/decider/flow_defaults.rb +70 -0
  44. data/lib/aws/decider/generic_client.rb +178 -0
  45. data/lib/aws/decider/history_helper.rb +173 -0
  46. data/lib/aws/decider/implementation.rb +82 -0
  47. data/lib/aws/decider/options.rb +607 -0
  48. data/lib/aws/decider/state_machines.rb +373 -0
  49. data/lib/aws/decider/task_handler.rb +76 -0
  50. data/lib/aws/decider/task_poller.rb +207 -0
  51. data/lib/aws/decider/utilities.rb +187 -0
  52. data/lib/aws/decider/worker.rb +324 -0
  53. data/lib/aws/decider/workflow_client.rb +374 -0
  54. data/lib/aws/decider/workflow_clock.rb +104 -0
  55. data/lib/aws/decider/workflow_definition.rb +101 -0
  56. data/lib/aws/decider/workflow_definition_factory.rb +53 -0
  57. data/lib/aws/decider/workflow_enabled.rb +26 -0
  58. data/test/aws/decider_spec.rb +1299 -0
  59. data/test/aws/factories.rb +45 -0
  60. data/test/aws/integration_spec.rb +3108 -0
  61. data/test/aws/spec_helper.rb +23 -0
  62. 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