aws-flow 1.3.0 → 2.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 (48) hide show
  1. checksums.yaml +15 -0
  2. data/aws-flow.gemspec +1 -0
  3. data/lib/aws/decider/activity.rb +8 -6
  4. data/lib/aws/decider/async_decider.rb +1 -0
  5. data/lib/aws/decider/async_retrying_executor.rb +3 -3
  6. data/lib/aws/decider/decider.rb +16 -14
  7. data/lib/aws/decider/executor.rb +35 -22
  8. data/lib/aws/decider/flow_defaults.rb +28 -14
  9. data/lib/aws/decider/generic_client.rb +3 -4
  10. data/lib/aws/decider/options.rb +91 -117
  11. data/lib/aws/decider/state_machines.rb +1 -0
  12. data/lib/aws/decider/utilities.rb +15 -0
  13. data/lib/aws/decider/version.rb +1 -1
  14. data/lib/aws/decider/worker.rb +14 -8
  15. data/lib/aws/decider/workflow_client.rb +16 -11
  16. data/lib/aws/runner.rb +43 -39
  17. data/spec/aws/decider/integration/activity_spec.rb +345 -0
  18. data/spec/aws/{integration → decider/integration}/integration_spec.rb +818 -1183
  19. data/spec/aws/decider/integration/setup.rb +3 -0
  20. data/spec/aws/decider/unit/activity_spec.rb +233 -0
  21. data/spec/aws/decider/unit/async_retrying_executor_spec.rb +131 -0
  22. data/spec/aws/{unit → decider/unit}/decider_spec.rb +171 -718
  23. data/spec/aws/decider/unit/executor_spec.rb +123 -0
  24. data/spec/aws/decider/unit/flow_defaults_spec.rb +62 -0
  25. data/spec/aws/decider/unit/misc_spec.rb +101 -0
  26. data/spec/aws/decider/unit/options_spec.rb +289 -0
  27. data/spec/aws/decider/unit/retry_spec.rb +217 -0
  28. data/spec/aws/{unit → decider/unit}/rubyflow.rb +0 -0
  29. data/spec/aws/decider/unit/setup.rb +3 -0
  30. data/spec/aws/decider/unit/worker_spec.rb +325 -0
  31. data/spec/aws/decider/unit/workflow_client_spec.rb +83 -0
  32. data/spec/aws/{unit → flow}/async_backtrace_spec.rb +0 -0
  33. data/spec/aws/{unit → flow}/async_scope_spec.rb +0 -0
  34. data/spec/aws/{unit → flow}/begin_rescue_ensure_spec.rb +1 -0
  35. data/spec/aws/{unit → flow}/external_task_spec.rb +0 -0
  36. data/spec/aws/{unit → flow}/factories.rb +0 -0
  37. data/spec/aws/{unit → flow}/fiber_condition_variable_spec.rb +0 -0
  38. data/spec/aws/{unit → flow}/fiber_spec.rb +0 -0
  39. data/spec/aws/{unit → flow}/flow_spec.rb +0 -0
  40. data/spec/aws/{unit → flow}/future_spec.rb +0 -0
  41. data/spec/aws/{unit → flow}/simple_dfa_spec.rb +0 -0
  42. data/spec/aws/{integration → runner/integration}/runner_integration_spec.rb +16 -43
  43. data/spec/aws/{unit → runner/unit}/runner_unit_spec.rb +18 -18
  44. data/spec/spec_helper.rb +264 -2
  45. metadata +37 -28
  46. data/spec/aws/unit/executor_spec.rb +0 -49
  47. data/spec/aws/unit/options_spec.rb +0 -293
  48. data/spec/aws/unit/preinclude_tests.rb +0 -149
@@ -0,0 +1,3 @@
1
+ require 'spec_helper'
2
+
3
+ include Test::Integ
@@ -0,0 +1,233 @@
1
+ require_relative 'setup'
2
+
3
+ describe Activity do
4
+ let(:trace) { [] }
5
+ let(:client) { client = GenericActivityClient.new(DecisionHelper.new, ActivityOptions.new) }
6
+
7
+ it "ensures that schedule_activity gets set up from the activity_client" do
8
+ client.reconfigure(:test) { |o| o.version = "blah" }
9
+ class GenericActivityClient
10
+ alias :old_schedule_activity :schedule_activity
11
+ def schedule_activity(name, activity_type, input, options)
12
+ :scheduled_activity
13
+ end
14
+ end
15
+ trace << client.test
16
+ trace.should == [:scheduled_activity]
17
+ class GenericActivityClient
18
+ alias :schedule_activity :old_schedule_activity
19
+ end
20
+ end
21
+
22
+ describe "multiple activities" do
23
+ it "ensures that you can schedule multiple activities with the same activity call" do
24
+ class GenericActivityClient
25
+ alias :old_schedule_activity :schedule_activity
26
+ def schedule_activity(name, activity_type, input, options)
27
+ "scheduled_activity_#{name}".to_sym
28
+ end
29
+ end
30
+ client = GenericActivityClient.new(DecisionHelper.new, ActivityOptions.new)
31
+ client.reconfigure(:test, :test2) { |o| o.version = "blah" }
32
+ trace << client.test
33
+ trace << client.test2
34
+ class GenericActivityClient
35
+ alias :schedule_activity :old_schedule_activity
36
+ end
37
+ trace.should == [:"scheduled_activity_.test", :"scheduled_activity_.test2"]
38
+ end
39
+ end
40
+ describe GenericActivityClient do
41
+
42
+ before(:each) do
43
+ @options = ActivityOptions.new
44
+ @decision_helper = DecisionHelper.new
45
+ @client = GenericActivityClient.new(@decision_helper, @options)
46
+ [:test, :test_nil].each do |method_name|
47
+ @client.reconfigure(method_name) { |o| o.version = 1 }
48
+ end
49
+ end
50
+ it "ensures that activities get generated correctly" do
51
+ scope = AsyncScope.new { @client.test }
52
+ scope.eventLoop
53
+ @decision_helper.decision_map.values.first.
54
+ get_decision[:schedule_activity_task_decision_attributes][:activity_type][:name].should =~
55
+ /test/
56
+ end
57
+
58
+ it "ensures that an activity that has no arguments is scheduled with no input value" do
59
+ scope = AsyncScope.new do
60
+
61
+ @client.test_nil
62
+ end
63
+ scope.eventLoop
64
+ @decision_helper.decision_map.values.first.
65
+ get_decision[:schedule_activity_task_decision_attributes].keys.should_not include :input
66
+ end
67
+
68
+ it "ensures that activities pass multiple activities fine" do
69
+ scope = AsyncScope.new do
70
+ @client.test(1, 2, 3)
71
+ end
72
+ scope.eventLoop
73
+ input = @decision_helper.decision_map.values.first.
74
+ get_decision[:schedule_activity_task_decision_attributes][:input]
75
+ @client.data_converter.load(input).should == [1, 2, 3]
76
+ end
77
+
78
+ context "github issue # 57" do
79
+ # The following tests for github issue # 57
80
+ before(:all) do
81
+ class GithubIssue57Activity
82
+ extend AWS::Flow::Activities
83
+ activity :not_retryable do
84
+ {
85
+ :version => "1.0",
86
+ :task_list => 'not_retryable_activity',
87
+ :schedule_to_start_timeout => 10,
88
+ :start_to_close_timeout => 10,
89
+ :heartbeat_timeout => 2
90
+ }
91
+ end
92
+
93
+ activity :retryable do
94
+ {
95
+ :version => "2.0",
96
+ :task_list => 'retryable_activity',
97
+ :schedule_to_start_timeout => 20,
98
+ :start_to_close_timeout => 20,
99
+ :exponential_retry => {
100
+ :maximum_attempts => 3,
101
+ },
102
+ }
103
+ end
104
+ end
105
+
106
+ class GithubIssue57Workflow
107
+ extend AWS::Flow::Workflows
108
+ workflow :start do
109
+ {
110
+ version: "1.0",
111
+ default_execution_start_to_close_timeout: 120,
112
+ default_task_list: "default",
113
+ detault_task_start_to_close_timeout: 30
114
+ }
115
+ end
116
+ activity_client(:client) { { from_class: "GithubIssue57Activity" } }
117
+ def start
118
+ client.not_retryable
119
+ client.retryable
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ it "tests first activity invocation options" do
126
+ $workflow_type = FakeWorkflowType.new(nil, "GithubIssue57Workflow.start", "1.0")
127
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
128
+ def get_decision_task
129
+ TestHistoryWrapper.new($workflow_type, FakeWorkflowExecution.new(nil, nil),
130
+ [
131
+ TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
132
+ TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
133
+ TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
134
+ ])
135
+ end
136
+ end
137
+
138
+ workflow_type_object = FakeWorkflowType.new(nil, "GithubIssue57Workflow.start", "1.0")
139
+ domain = FakeDomain.new($workflow_type)
140
+
141
+ swf_client = FakeServiceClient.new
142
+ task_list = "default"
143
+
144
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, GithubIssue57Workflow)
145
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: GithubIssue57Workflow } }
146
+ expect_any_instance_of(AWS::Flow::GenericClient).to_not receive(:_retry_with_options)
147
+ workflow_execution = client.start_execution
148
+ worker.start
149
+
150
+
151
+ swf_client.trace.first[:decisions].first[:decision_type].should == "ScheduleActivityTask"
152
+ attributes = swf_client.trace.first[:decisions].first[:schedule_activity_task_decision_attributes]
153
+ attributes[:activity_type].should == { name: "GithubIssue57Activity.not_retryable", version: "1.0" }
154
+ attributes[:heartbeat_timeout].should == "2"
155
+ attributes[:schedule_to_start_timeout].should == "10"
156
+ attributes[:start_to_close_timeout].should == "10"
157
+ attributes[:task_list].should == { name: "not_retryable_activity" }
158
+ end
159
+
160
+ it "tests second activity invocation options" do
161
+
162
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
163
+ def get_decision_task
164
+ fake_workflow_type = FakeWorkflowType.new(nil, "GithubIssue57Workflow.start", "1.0")
165
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
166
+ FakeEvents.new(["WorkflowExecutionStarted",
167
+ "DecisionTaskScheduled",
168
+ "DecisionTaskStarted",
169
+ "DecisionTaskCompleted",
170
+ ["ActivityTaskScheduled", {activity_id: "Activity1"}],
171
+ "ActivityTaskStarted",
172
+ ["ActivityTaskCompleted", {scheduled_event_id: 5}],
173
+ "DecisionTaskScheduled",
174
+ "DecisionTaskStarted"
175
+ ]))
176
+ end
177
+ end
178
+
179
+ workflow_type_object = double("workflow_type", :name => "GithubIssue57Workflow.start", :start_execution => "" )
180
+ domain = FakeDomain.new(workflow_type_object)
181
+
182
+ swf_client = FakeServiceClient.new
183
+ task_list = "default"
184
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "GithubIssue57Workflow" } }
185
+
186
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, GithubIssue57Workflow)
187
+
188
+ workflow_execution = client.start_execution
189
+ worker.start
190
+
191
+ swf_client.trace.first[:decisions].first[:decision_type].should == "ScheduleActivityTask"
192
+ attributes = swf_client.trace.first[:decisions].first[:schedule_activity_task_decision_attributes]
193
+ attributes[:activity_type].should == { name: "GithubIssue57Activity.retryable", version: "2.0" }
194
+ attributes[:heartbeat_timeout].nil?.should == true
195
+ attributes[:schedule_to_start_timeout].should == "20"
196
+ attributes[:start_to_close_timeout].should == "20"
197
+ attributes[:task_list].should == { name: "retryable_activity" }
198
+
199
+ end
200
+ it "tests second activity invocation for exponential retry option" do
201
+
202
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
203
+ def get_decision_task
204
+ fake_workflow_type = FakeWorkflowType.new(nil, "GithubIssue57Workflow.start", "1.0")
205
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
206
+ FakeEvents.new(["WorkflowExecutionStarted",
207
+ "DecisionTaskScheduled",
208
+ "DecisionTaskStarted",
209
+ "DecisionTaskCompleted",
210
+ ["ActivityTaskScheduled", {activity_id: "Activity1"}],
211
+ "ActivityTaskStarted",
212
+ ["ActivityTaskCompleted", {scheduled_event_id: 5}],
213
+ "DecisionTaskScheduled",
214
+ "DecisionTaskStarted"
215
+ ]))
216
+ end
217
+ end
218
+
219
+ workflow_type_object = double("workflow_type", :name => "GithubIssue57Workflow.start", :start_execution => "" )
220
+ domain = FakeDomain.new(workflow_type_object)
221
+
222
+ swf_client = FakeServiceClient.new
223
+ task_list = "default"
224
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "GithubIssue57Workflow" } }
225
+
226
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, GithubIssue57Workflow)
227
+ expect_any_instance_of(AWS::Flow::GenericClient).to receive(:_retry_with_options)
228
+ workflow_execution = client.start_execution
229
+ worker.start
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,131 @@
1
+ require_relative 'setup'
2
+
3
+ describe RetryPolicy do
4
+ context "#isRetryable" do
5
+ it "tests the default exceptions included for retry" do
6
+ my_retry_func = lambda do |first, time_of_failure, attempts|
7
+ 10
8
+ end
9
+ options = {
10
+ :exceptions_to_include => [ActivityTaskTimedOutException],
11
+ :exceptions_to_exclude => [ActivityTaskFailedException]
12
+ }
13
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
14
+ result = retry_policy.isRetryable(ActivityTaskTimedOutException.new("a", "b", "c", "d"))
15
+ result.should == true
16
+
17
+ result = retry_policy.isRetryable(ActivityTaskFailedException.new("a", "b", "c", RuntimeError.new))
18
+ result.should == false
19
+ end
20
+
21
+
22
+ it "ensures that an exception is raised if the called exception is present in both included and excluded exceptions" do
23
+ my_retry_func = lambda do |first, time_of_failure, attempts|
24
+ 10
25
+ end
26
+ options = {
27
+ :exceptions_to_include => [ActivityTaskFailedException],
28
+ :exceptions_to_exclude => [ActivityTaskFailedException]
29
+ }
30
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
31
+ expect {retry_policy.isRetryable(ActivityTaskFailedException.new("a", "b", "c", ActivityTaskFailedException))}.to raise_error
32
+ end
33
+ end
34
+
35
+ context "#next_retry_delay_seconds" do
36
+ it "tests exponential retry with a new retry function" do
37
+ my_retry_func = lambda do |first, time_of_failure, attempts|
38
+ 10
39
+ end
40
+ options = {
41
+ :should_jitter => false
42
+ }
43
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
44
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
45
+ result.should == 10
46
+ end
47
+
48
+ it "tests the jitter function" do
49
+ my_retry_func = lambda do |first, time_of_failure, attempts|
50
+ 10
51
+ end
52
+ options = {
53
+ :should_jitter => true
54
+ }
55
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options, true))
56
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
57
+ result.should >= 10 && result.should < 15
58
+ end
59
+
60
+ it "tests default exceptions included for retry" do
61
+ options = RetryOptions.new
62
+ options.exceptions_to_include.should include Exception
63
+ end
64
+
65
+ it "tests max_attempts" do
66
+ my_retry_func = lambda do |first, time_of_failure, attempts|
67
+ 10
68
+ end
69
+ options = {
70
+ :maximum_attempts => 5,
71
+ :should_jitter => false
72
+ }
73
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
74
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception.new, 1)
75
+ result.should == -1
76
+
77
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>4, ArgumentError=>3}, Exception.new, 1)
78
+ result.should == -1
79
+
80
+ # this should be retried because Exceptions=>5 includes the original exception, followed by 4 retries.
81
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>5}, Exception.new, 1)
82
+ result.should == 10
83
+
84
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>4}, Exception.new, 1)
85
+ result.should == 10
86
+ end
87
+
88
+ it "tests retries_per_exception" do
89
+ my_retry_func = lambda do |first, time_of_failure, attempts|
90
+ 10
91
+ end
92
+ options = {
93
+ :retries_per_exception => {Exception => 5, ArgumentError => 2},
94
+ :should_jitter => false
95
+ }
96
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
97
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception.new, 1)
98
+ result.should == -1
99
+
100
+ # this should be retried because Exceptions=>5 includes the original exception, followed by 4 retries.
101
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>5}, Exception.new, 1)
102
+ result.should == 10
103
+
104
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>5, ArgumentError=>3}, ArgumentError.new, 1)
105
+ result.should == -1
106
+ end
107
+
108
+ it "ensures that next_retry_delay_seconds honors -1 returned by the retry function" do
109
+ my_retry_func = lambda do |first, time_of_failure, attempts|
110
+ -1
111
+ end
112
+ options = {
113
+ :should_jitter => true
114
+ }
115
+ retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
116
+ result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
117
+ result.should == -1
118
+ end
119
+
120
+ it "ensures that retry_expiration_interval_seconds works correctly" do
121
+ options = {
122
+ should_jitter: false,
123
+ retry_expiration_interval_seconds: 10,
124
+ }
125
+ retry_policy = RetryPolicy.new(FlowConstants.exponential_retry_function, ExponentialRetryOptions.new(options))
126
+ first = Time.now
127
+ result = retry_policy.next_retry_delay_seconds(first, first+11, {Exception=>10}, Exception, 1)
128
+ result.should == -1
129
+ end
130
+ end
131
+ end
@@ -14,212 +14,7 @@
14
14
  ##
15
15
 
16
16
  require 'yaml'
17
-
18
- require 'aws/decider'
19
- include AWS::Flow
20
- class FakeConfig
21
- def to_h
22
-
23
- end
24
- end
25
- class FakeServiceClient
26
- attr_accessor :trace
27
- def respond_decision_task_completed(task_completed_request)
28
- @trace ||= []
29
- @trace << task_completed_request
30
- end
31
- def start_workflow_execution(options)
32
- @trace ||= []
33
- @trace << options
34
- {"runId" => "blah"}
35
- end
36
- def register_activity_type(options)
37
- end
38
- def register_workflow_type(options)
39
- end
40
- def respond_activity_task_completed(task_token, result)
41
- end
42
- def start_workflow_execution(options)
43
- {"runId" => "blah"}
44
- end
45
- def config
46
- FakeConfig.new
47
- end
48
- end
49
-
50
- $RUBYFLOW_DECIDER_DOMAIN = "rubyflow_decider_domain_06-12-2012"
51
- $RUBYFLOW_DECIDER_TASK_LIST = 'test_task_list'
52
-
53
- class FakeAttribute
54
- def initialize(data); @data = data; end
55
- def method_missing(method_name, *args, &block)
56
- if @data.keys.include? method_name
57
- return @data[method_name]
58
- end
59
- super
60
- end
61
- def keys; @data.keys; end
62
- def [](key); @data[key]; end
63
- def to_h; @data; end
64
- def []=(key, val); @data[key] = val; end
65
- end
66
-
67
- class FakeEvents
68
- def initialize(args)
69
- @events = []
70
- args.each_with_index do |event, index|
71
- event, attr = event if event.is_a? Array
72
- attr ||= {}
73
- @events << TestHistoryEvent.new(event, index + 1, FakeAttribute.new(attr))
74
- end
75
- @events
76
- end
77
- def to_a
78
- @events
79
- end
80
- end
81
- class TrivialConverter
82
- def dump(x)
83
- x
84
- end
85
- def load(x)
86
- x
87
- end
88
- end
89
-
90
- class FakeLogger
91
- attr_accessor :level
92
- def info(s); end
93
- def debug(s); end
94
- def warn(s); end
95
- def error(s); end
96
- end
97
-
98
- class FakePage
99
- def initialize(object); @object = object; end
100
- def page; @object; end
101
- end
102
-
103
- class FakeWorkflowExecution
104
- def initialize(run_id = "1", workflow_id = "1")
105
- @run_id = run_id
106
- @workflow_id = workflow_id
107
- end
108
- attr_accessor :run_id, :workflow_id, :task_list
109
- end
110
-
111
- class FakeWorkflowExecutionCollecton
112
- def at(workflow_id, run_id); "Workflow_execution"; end
113
- end
114
-
115
- class FakeDomain
116
- def initialize(workflow_type_object)
117
- @workflow_type_object = workflow_type_object
118
- end
119
- def page; FakePage.new(@workflow_type_object); end
120
- def workflow_executions; FakeWorkflowExecutionCollecton.new; end
121
- def name; "fake_domain"; end
122
-
123
- end
124
-
125
-
126
- describe Activity do
127
- let(:trace) { [] }
128
- let(:client) { client = GenericActivityClient.new(DecisionHelper.new, ActivityOptions.new) }
129
-
130
- it "ensures that schedule_activity gets set up from the activity_client" do
131
- client.reconfigure(:test) { |o| o.version = "blah" }
132
- class GenericActivityClient
133
- alias :old_schedule_activity :schedule_activity
134
- def schedule_activity(name, activity_type, input, options)
135
- :scheduled_activity
136
- end
137
- end
138
- trace << client.test
139
- trace.should == [:scheduled_activity]
140
- class GenericActivityClient
141
- alias :schedule_activity :old_schedule_activity
142
- end
143
- end
144
-
145
- describe "multiple activities" do
146
- it "ensures that you can schedule multiple activities with the same activity call" do
147
- class GenericActivityClient
148
- alias :old_schedule_activity :schedule_activity
149
- def schedule_activity(name, activity_type, input, options)
150
- "scheduled_activity_#{name}".to_sym
151
- end
152
- end
153
- client = GenericActivityClient.new(DecisionHelper.new, ActivityOptions.new)
154
- client.reconfigure(:test, :test2) { |o| o.version = "blah" }
155
- trace << client.test
156
- trace << client.test2
157
- class GenericActivityClient
158
- alias :schedule_activity :old_schedule_activity
159
- end
160
- trace.should == [:"scheduled_activity_.test", :"scheduled_activity_.test2"]
161
- end
162
- end
163
- describe GenericActivityClient do
164
-
165
- before(:each) do
166
- @options = ActivityOptions.new
167
- @decision_helper = DecisionHelper.new
168
- @client = GenericActivityClient.new(@decision_helper, @options)
169
- [:test, :test_nil].each do |method_name|
170
- @client.reconfigure(method_name) { |o| o.version = 1 }
171
- end
172
- end
173
- it "ensures that activities get generated correctly" do
174
- scope = AsyncScope.new { @client.test }
175
- scope.eventLoop
176
- @decision_helper.decision_map.values.first.
177
- get_decision[:schedule_activity_task_decision_attributes][:activity_type][:name].should =~
178
- /test/
179
- end
180
-
181
- it "ensures that an activity that has no arguments is scheduled with no input value" do
182
- scope = AsyncScope.new do
183
-
184
- @client.test_nil
185
- end
186
- scope.eventLoop
187
- @decision_helper.decision_map.values.first.
188
- get_decision[:schedule_activity_task_decision_attributes].keys.should_not include :input
189
- end
190
-
191
- it "ensures that activities pass multiple activities fine" do
192
- scope = AsyncScope.new do
193
- @client.test(1, 2, 3)
194
- end
195
- scope.eventLoop
196
- input = @decision_helper.decision_map.values.first.
197
- get_decision[:schedule_activity_task_decision_attributes][:input]
198
- @client.data_converter.load(input).should == [1, 2, 3]
199
- end
200
- end
201
- end
202
-
203
- describe WorkflowClient do
204
-
205
- class TestWorkflow
206
- extend Decider
207
-
208
- entry_point :entry_point
209
- def entry_point
210
- return "This is the entry point"
211
- end
212
- end
213
- before(:each) do
214
- workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
215
- @client = WorkflowClient.new(FakeServiceClient.new, FakeDomain.new(workflow_type_object), TestWorkflow, StartWorkflowOptions.new)
216
- end
217
- it "makes sure that configure works correctly" do
218
- @client.reconfigure(:entry_point) {{ :task_list => "This nonsense" }}
219
- @client.entry_point
220
-
221
- end
222
- end
17
+ require_relative 'setup'
223
18
 
224
19
  describe ActivityDefinition do
225
20
  class MyActivity
@@ -322,42 +117,6 @@ describe WorkflowDefinitionFactory do
322
117
  end
323
118
  end
324
119
 
325
- describe ForkingExecutor do
326
- it "makes sure that forking executors basic execute works" do
327
- test_file_name = "ForkingExecutorTestFile"
328
- begin
329
- forking_executor = ForkingExecutor.new
330
- File.exists?(test_file_name).should == false
331
- forking_executor.execute do
332
- File.new(test_file_name, 'w')
333
- end
334
- sleep 3
335
- File.exists?(test_file_name).should == true
336
- ensure
337
- File.unlink(test_file_name)
338
- end
339
- end
340
-
341
- it "ensures that you cannot execute more tasks on a shutdown executor" do
342
- forking_executor = ForkingExecutor.new
343
- forking_executor.execute do
344
- end
345
- forking_executor.execute do
346
- end
347
- forking_executor.shutdown(1)
348
- expect { forking_executor.execute { "yay" } }.to raise_error
349
- RejectedExecutionException
350
- end
351
-
352
- end
353
-
354
- describe AsyncDecider do
355
- before(:each) do
356
- @decision_helper = DecisionHelper.new
357
- @history_helper = double(HistoryHelper)
358
- end
359
-
360
- end
361
120
  describe YAMLDataConverter do
362
121
  let(:converter) {YAMLDataConverter.new}
363
122
  %w{syck psych}.each do |engine|
@@ -394,6 +153,51 @@ describe YAMLDataConverter do
394
153
  end
395
154
  end
396
155
 
156
+ describe Workflows do
157
+
158
+ context "#workflow" do
159
+
160
+ it "makes sure we can specify multiple workflows" do
161
+ class MultipleWorkflowsTest1_Workflow
162
+ extend AWS::Flow::Workflows
163
+ workflow :workflow_a do
164
+ {
165
+ version: "1.0",
166
+ default_execution_start_to_close_timeout: 600,
167
+ default_task_list: "tasklist_a"
168
+ }
169
+ end
170
+ workflow :workflow_b do
171
+ {
172
+ version: "1.0",
173
+ default_execution_start_to_close_timeout: 300,
174
+ default_task_list: "tasklist_b"
175
+ }
176
+ end
177
+ end
178
+ MultipleWorkflowsTest1_Workflow.workflows.count.should == 2
179
+ MultipleWorkflowsTest1_Workflow.workflows.map(&:name).should == ["MultipleWorkflowsTest1_Workflow.workflow_a", "MultipleWorkflowsTest1_Workflow.workflow_b"]
180
+ MultipleWorkflowsTest1_Workflow.workflows.map(&:options).map(&:default_task_list).should == ["tasklist_a", "tasklist_b"]
181
+ end
182
+
183
+ it "makes sure we can pass multiple workflow names with same options" do
184
+ class MultipleWorkflowsTest2_Workflow
185
+ extend AWS::Flow::Workflows
186
+ workflow :workflow_a, :workflow_b do
187
+ {
188
+ version: "1.0",
189
+ default_task_list: "tasklist_a"
190
+ }
191
+ end
192
+ end
193
+ MultipleWorkflowsTest2_Workflow.workflows.count.should == 2
194
+ MultipleWorkflowsTest2_Workflow.workflows.map(&:name).should == ["MultipleWorkflowsTest2_Workflow.workflow_a", "MultipleWorkflowsTest2_Workflow.workflow_b"]
195
+ MultipleWorkflowsTest2_Workflow.workflows.map(&:options).map(&:default_task_list).should == ["tasklist_a", "tasklist_a"]
196
+
197
+ end
198
+ end
199
+ end
200
+
397
201
  describe WorkflowFactory do
398
202
  it "ensures that you can create a workflow_client without access to the Workflow definition" do
399
203
  workflow_type_object = double("workflow_type", :name => "NonExistantWorkflow.some_entry_method", :start_execution => "" )
@@ -418,53 +222,6 @@ describe "FakeHistory" do
418
222
  Time.now
419
223
  end
420
224
  end
421
-
422
- class SynchronousWorkflowWorker < WorkflowWorker
423
- def start
424
- poller = SynchronousWorkflowTaskPoller.new(@service, nil, DecisionTaskHandler.new(@workflow_definition_map), @task_list)
425
- poller.poll_and_process_single_task
426
- end
427
- end
428
-
429
-
430
- class Hash
431
- def to_h; self; end
432
- end
433
-
434
- class TestHistoryEvent < AWS::SimpleWorkflow::HistoryEvent
435
- def initialize(event_type, event_id, attributes)
436
- @event_type = event_type
437
- @attributes = attributes
438
- @event_id = event_id
439
- @created_at = Time.now
440
- end
441
- end
442
-
443
- class FakeWorkflowType < WorkflowType
444
- attr_accessor :domain, :name, :version
445
- def initialize(domain, name, version)
446
- @domain = domain
447
- @name = name
448
- @version = version
449
- end
450
- end
451
-
452
- class TestHistoryWrapper
453
- def initialize(workflow_type, events)
454
- @workflow_type = workflow_type
455
- @events = events
456
- end
457
- def workflow_execution
458
- FakeWorkflowExecution.new
459
- end
460
- def task_token
461
- "1"
462
- end
463
- def previous_started_event_id
464
- 1
465
- end
466
- attr_reader :events, :workflow_type
467
- end
468
225
  end
469
226
  after(:all) do
470
227
  class WorkflowClock
@@ -509,7 +266,7 @@ describe "FakeHistory" do
509
266
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
510
267
  def get_decision_task
511
268
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
512
- TestHistoryWrapper.new(fake_workflow_type,
269
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
513
270
  [TestHistoryEvent.new("WorkflowExecutionStarted", 1, {:parent_initiated_event_id=>0, :child_policy=>:request_cancel, :execution_start_to_close_timeout=>3600, :task_start_to_close_timeout=>5, :workflow_type=> fake_workflow_type, :task_list=>"BadWorkflow"}),
514
271
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {:parent_initiated_event_id=>0, :child_policy=>:request_cancel, :execution_start_to_close_timeout=>3600, :task_start_to_close_timeout=>5, :workflow_type=> fake_workflow_type, :task_list=>"BadWorkflow"}),
515
272
  TestHistoryEvent.new("DecisionTaskStarted", 3, {:scheduled_event_id=>2, :identity=>"some_identity"}),
@@ -529,57 +286,57 @@ describe "FakeHistory" do
529
286
  it "reproduces the ActivityTaskTimedOut problem" do
530
287
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
531
288
  def get_decision_task
532
- fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
533
- TestHistoryWrapper.new(fake_workflow_type,
289
+ fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.start", "1")
290
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
534
291
  [
535
- TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
536
- TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
537
- TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
538
- TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
539
- TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
540
- TestHistoryEvent.new("ActivityTaskStarted", 6, {}),
541
- TestHistoryEvent.new("ActivityTaskTimedOut", 7, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
542
- ])
292
+ TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
293
+ TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
294
+ TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
295
+ TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
296
+ TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
297
+ TestHistoryEvent.new("ActivityTaskStarted", 6, {}),
298
+ TestHistoryEvent.new("ActivityTaskTimedOut", 7, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
299
+ ])
543
300
  end
544
301
  end
302
+
545
303
  class BadWorkflow
546
- class << self
547
- attr_accessor :task_list
548
- end
549
- extend Decider
550
- version "1"
551
- entry_point :entry_point
552
- activity_client :activity do |options|
553
- options.prefix_name = "BadActivity"
554
- options.version = "1"
555
- options.default_task_heartbeat_timeout = "3600"
556
- options.default_task_list = "BadWorkflow"
557
- options.default_task_schedule_to_close_timeout = "30"
558
- options.default_task_schedule_to_start_timeout = "30"
559
- options.default_task_start_to_close_timeout = "10"
304
+ extend AWS::Flow::Workflows
305
+ workflow :start do
306
+ {
307
+ version: "1",
308
+ default_execution_start_to_close_timeout: 3600,
309
+ default_task_list: "BadWorkflow_tasklist",
310
+ default_task_start_to_close_timeout: 10,
311
+ default_child_policy: :request_cancel
312
+ }
313
+ end
314
+ activity_client(:activity) do
315
+ {
316
+ prefix_name: "BadActivity",
317
+ version: "1",
318
+ default_task_heartbeat_timeout: "3600",
319
+ default_task_list: "BadWorkflow",
320
+ default_task_schedule_to_close_timeout: "30",
321
+ default_task_schedule_to_start_timeout: "30",
322
+ default_task_start_to_close_timeout: "10",
323
+ }
560
324
  end
561
- def entry_point
325
+ def start
562
326
  activity.run_activity1
563
327
  activity.run_activity2
564
328
  end
565
329
  end
566
- workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
330
+ workflow_type_object = FakeWorkflowType.new(nil, "BadWorkflow.start", "1.0")
567
331
  domain = FakeDomain.new(workflow_type_object)
568
332
 
569
333
  swf_client = FakeServiceClient.new
570
334
  task_list = "BadWorkflow_tasklist"
571
335
  worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
572
336
  worker.add_workflow_implementation(BadWorkflow)
573
- my_workflow_factory = workflow_factory(swf_client, domain) do |options|
574
- options.workflow_name = "BadWorkflow"
575
- options.execution_start_to_close_timeout = 3600
576
- options.task_list = task_list
577
- options.task_start_to_close_timeout = 10
578
- options.child_policy = :request_cancel
579
- end
337
+ client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "BadWorkflow" } }
580
338
 
581
- my_workflow = my_workflow_factory.get_client
582
- workflow_execution = my_workflow.start_execution(5)
339
+ workflow_execution = client.start_execution(5)
583
340
  worker.start
584
341
 
585
342
  swf_client.trace.first[:decisions].first[:decision_type].should ==
@@ -592,7 +349,7 @@ describe "FakeHistory" do
592
349
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
593
350
  def get_decision_task
594
351
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
595
- TestHistoryWrapper.new(fake_workflow_type,
352
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
596
353
  [
597
354
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
598
355
  ])
@@ -646,7 +403,7 @@ describe "FakeHistory" do
646
403
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
647
404
  def get_decision_task
648
405
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
649
- TestHistoryWrapper.new(fake_workflow_type,
406
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
650
407
  [
651
408
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
652
409
  ])
@@ -699,7 +456,7 @@ describe "FakeHistory" do
699
456
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
700
457
  def get_decision_task
701
458
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
702
- TestHistoryWrapper.new(fake_workflow_type,
459
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
703
460
  [
704
461
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
705
462
  ])
@@ -761,7 +518,7 @@ describe "FakeHistory" do
761
518
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
762
519
  def get_decision_task
763
520
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
764
- TestHistoryWrapper.new(fake_workflow_type,
521
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
765
522
  [
766
523
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
767
524
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -829,7 +586,7 @@ describe "FakeHistory" do
829
586
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
830
587
  def get_decision_task
831
588
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
832
- TestHistoryWrapper.new(fake_workflow_type,
589
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
833
590
  [
834
591
 
835
592
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {:created_at => Time.now}),
@@ -897,7 +654,7 @@ describe "FakeHistory" do
897
654
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
898
655
  def get_decision_task
899
656
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
900
- TestHistoryWrapper.new(fake_workflow_type,
657
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
901
658
  [
902
659
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
903
660
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -974,7 +731,7 @@ describe "FakeHistory" do
974
731
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
975
732
  def get_decision_task
976
733
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
977
- TestHistoryWrapper.new(fake_workflow_type,
734
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
978
735
  [
979
736
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
980
737
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -1053,7 +810,7 @@ describe "FakeHistory" do
1053
810
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1054
811
  def get_decision_task
1055
812
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
1056
- TestHistoryWrapper.new(fake_workflow_type,
813
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1057
814
  [
1058
815
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
1059
816
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -1119,7 +876,7 @@ describe "FakeHistory" do
1119
876
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1120
877
  def get_decision_task
1121
878
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
1122
- TestHistoryWrapper.new(fake_workflow_type,
879
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1123
880
  [
1124
881
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
1125
882
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -1162,7 +919,7 @@ describe "FakeHistory" do
1162
919
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1163
920
  def get_decision_task
1164
921
  fake_workflow_type = FakeWorkflowType.new(nil, "FixnumWorkflow.entry_point", "1")
1165
- TestHistoryWrapper.new(fake_workflow_type,
922
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1166
923
  [
1167
924
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
1168
925
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -1211,7 +968,7 @@ describe "FakeHistory" do
1211
968
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1212
969
  def get_decision_task
1213
970
  fake_workflow_type = FakeWorkflowType.new(nil, "CompleteWorkflowExecutionFailedWorkflow.entry_point", "1")
1214
- TestHistoryWrapper.new(fake_workflow_type,
971
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1215
972
  [
1216
973
  TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
1217
974
  TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
@@ -1278,7 +1035,7 @@ describe "FakeHistory" do
1278
1035
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1279
1036
  def get_decision_task
1280
1037
  fake_workflow_type = FakeWorkflowType.new(nil, "TimeOutWorkflow.entry_point", "1")
1281
- TestHistoryWrapper.new(fake_workflow_type,
1038
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1282
1039
  FakeEvents.new(["WorkflowExecutionStarted",
1283
1040
  "DecisionTaskScheduled",
1284
1041
  "DecisionTaskStarted",
@@ -1300,12 +1057,12 @@ describe "FakeHistory" do
1300
1057
  ]))
1301
1058
  end
1302
1059
  end
1303
- workflow_type_object = double("workflow_type", :name => "TimeOutWorkflow.entry_point", :start_execution => "" )
1304
-
1060
+ workflow_type_object = FakeWorkflowType.new(nil, "TimeOutWorkflow.entry_point", "1")
1305
1061
 
1306
1062
  domain = FakeDomain.new(workflow_type_object)
1307
1063
  swf_client = FakeServiceClient.new
1308
- $my_workflow_client = workflow_client(swf_client, domain) {{:prefix_name => "TimeOutWorkflow", :execution_method => "entry_point", :version => "1"}}
1064
+ $my_workflow_client = workflow_client(swf_client, domain){{:prefix_name => "TimeOutWorkflow", :execution_method => "entry_point", :version => "1"}}
1065
+
1309
1066
  class TimeOutActivity
1310
1067
  extend Activity
1311
1068
  activity :run_activity1
@@ -1317,13 +1074,12 @@ describe "FakeHistory" do
1317
1074
  activity_client(:activity) { {:version => "1", :prefix_name => "TimeOutActivity" } }
1318
1075
 
1319
1076
  def entry_point
1320
- $my_workflow_client.start_execution() { {:task_list => "nonsense_task_list", :workflow_id => "child_workflow_test"}}
1077
+ $my_workflow_client.start_execution { { task_list: "nonsense_tasklist", workflow_id: "child_workflow_test" } }
1321
1078
  activity_client.run_activity1
1322
- p "yay"
1323
1079
  end
1324
-
1325
1080
  end
1326
1081
 
1082
+
1327
1083
  task_list = "TimeOutWorkflow_tasklist"
1328
1084
  my_workflow_factory = workflow_factory(swf_client, domain) do |options|
1329
1085
  options.workflow_name = "TimeOutWorkflow"
@@ -1343,7 +1099,7 @@ describe "FakeHistory" do
1343
1099
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1344
1100
  def get_decision_task
1345
1101
  fake_workflow_type = FakeWorkflowType.new(nil, "OtherTimeOutWorkflow.entry_point", "1")
1346
- TestHistoryWrapper.new(fake_workflow_type,
1102
+ TestHistoryWrapper.new(fake_workflow_type, FakeWorkflowExecution.new(nil, nil),
1347
1103
  FakeEvents.new(["WorkflowExecutionStarted",
1348
1104
  "DecisionTaskScheduled",
1349
1105
  "DecisionTaskStarted",
@@ -1406,6 +1162,76 @@ describe "FakeHistory" do
1406
1162
  worker.start
1407
1163
  swf_client.trace.first[:decisions].first[:decision_type].should == "CompleteWorkflowExecution"
1408
1164
  end
1165
+
1166
+ it "replicates the error seen in github issue #37" do
1167
+
1168
+ class TimeOutFailuresWorkflow
1169
+ extend Workflows
1170
+ workflow(:entry_point) do
1171
+ {
1172
+ execution_start_to_close_timeout: 600,
1173
+ task_list: "TimeOutFailuresWorkflow_tasklist",
1174
+ version: "1.0",
1175
+ }
1176
+ end
1177
+
1178
+ def entry_point
1179
+ error_handler do |t|
1180
+ t.begin do
1181
+ $client.send_async(:start_execution) { {task_list: "nonsense_task_list", workflow_id: "child_workflow_test"}}
1182
+ create_timer(5)
1183
+ end
1184
+ t.rescue AWS::Flow::ChildWorkflowException do |ex|
1185
+ # noop because we don't want the parent execution to die
1186
+ end
1187
+ end
1188
+ end
1189
+ end
1190
+
1191
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1192
+ def get_decision_task
1193
+ TestHistoryWrapper.new($workflow_type, FakeWorkflowExecution.new(nil, nil),
1194
+ FakeEvents.new(["WorkflowExecutionStarted",
1195
+ "DecisionTaskScheduled",
1196
+ "DecisionTaskStarted",
1197
+ "DecisionTaskCompleted",
1198
+ ["TimerStarted", {:decision_task_completed_event_id => 4, :timer_id => "Timer1", :start_to_fire_timeout => 10}],
1199
+ ["StartChildWorkflowExecutionInitiated", {:workflow_id => "child_workflow_test"}],
1200
+ ["ChildWorkflowExecutionStarted", {:workflow_execution => FakeWorkflowExecution.new("1", "child_workflow_test"), :workflow_id => "child_workflow_test"}],
1201
+
1202
+ "DecisionTaskScheduled",
1203
+ "DecisionTaskStarted",
1204
+ "DecisionTaskCompleted",
1205
+ ["ChildWorkflowExecutionTerminated", :workflow_execution => FakeWorkflowExecution.new("1", "child_workflow_test")],
1206
+ "DecisionTaskScheduled",
1207
+ "DecisionTaskStarted",
1208
+ ["TimerFired", {:timer_id => "Timer1"}],
1209
+ "DecisionTaskScheduled",
1210
+ "DecisionTaskCompleted",
1211
+ ["CancelTimerFailed", {:timer_id => "Timer1"}],
1212
+ "CompleteWorkflowExecutionFailed",
1213
+ "DecisionTaskScheduled",
1214
+ "DecisionTaskStarted"
1215
+ ]))
1216
+ end
1217
+ end
1218
+
1219
+ $workflow_type = FakeWorkflowType.new(nil, "TimeOutFailuresWorkflow.entry_point", "1.0")
1220
+ swf_client = FakeServiceClient.new
1221
+ domain = FakeDomain.new($workflow_type)
1222
+ $client = AWS::Flow::workflow_client(swf_client, domain) { { from_class: "TimeOutFailuresWorkflow" } }
1223
+
1224
+ task_list = "TimeOutFailuresWorkflow_tasklist"
1225
+ my_workflow_factory = workflow_factory(swf_client, domain) do |options|
1226
+ end
1227
+
1228
+ $client.start_execution
1229
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, TimeOutFailuresWorkflow)
1230
+ worker.start
1231
+ #my_workflow = my_workflow_factory.get_client
1232
+ #workflow_execution = my_workflow.start_execution
1233
+ swf_client.trace.first[:decisions].first[:decision_type].should == "CompleteWorkflowExecution"
1234
+ end
1409
1235
  end
1410
1236
 
1411
1237
  describe "Misc tests" do
@@ -1422,28 +1248,6 @@ describe "Misc tests" do
1422
1248
  AWS.eager_autoload!
1423
1249
  end
1424
1250
 
1425
- it "ensures that one worker for forking executor will only allow one thing to be processed at a time" do
1426
- executor = ForkingExecutor.new(:max_workers => 1)
1427
-
1428
- test_file_name = "ForkingExecutorRunOne"
1429
- File.new(test_file_name, "w")
1430
- start_time = Time.now
1431
- executor.execute do
1432
- File.open(test_file_name, "a+") { |f| f.write("First Execution\n")}
1433
- sleep 4
1434
- end
1435
- # Because execute will block if the worker queue is full, we will wait here
1436
- # if we have reached the max number of workers
1437
- executor.execute { 2 + 2 }
1438
- finish_time = Time.now
1439
- # If we waited for the first task to finish, then we will have waited at
1440
- # least 4 seconds; if we didn't, we should not have waited. Thus, if we have
1441
- # waited > 3 seconds, we have likely waited for the first task to finish
1442
- # before doing the second one
1443
- (finish_time - start_time).should > 3
1444
- File.unlink(test_file_name)
1445
- end
1446
-
1447
1251
  it "ensures that using send_async doesn't mutate the original hash" do
1448
1252
  class GenericClientTest < GenericClient
1449
1253
  def call_options(*args, &options)
@@ -1472,361 +1276,10 @@ describe "Misc tests" do
1472
1276
  previous_hash.should == previous_hash_copy
1473
1277
  end
1474
1278
 
1475
- it "makes sure we can remove depedency on UUIDTools" do
1476
- require "securerandom"
1477
- # first check if SecureRandom.uuid returns uuid in the right format
1478
- regex = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/
1479
- SecureRandom.uuid.should match(regex)
1480
-
1481
- class FakeWorkflowExecutionCollecton
1482
- def at(workflow_id, run_id); FakeWorkflowExecution.new(run_id, workflow_id); end
1483
- end
1484
-
1485
- # Now check if the uuid is correctly set in start_external_workflow method
1486
- workflow_type = WorkflowType.new(nil, "TestWorkflow.entry_point", "1")
1487
- client = AWS::Flow::WorkflowClient.new(FakeServiceClient.new, FakeDomain.new(workflow_type), TestWorkflow, WorkflowOptions.new)
1488
- workflow = client.start_external_workflow
1489
- workflow.workflow_id.should match(regex)
1490
- end
1491
1279
  it "makes sure complete method is present on the completion handle and not open request" do
1492
1280
  ( OpenRequestInfo.new.respond_to? :complete ).should == false
1493
- task = ExternalTask.new({}) { |t| puts t }
1281
+ task = ExternalTask.new({}) { |t| }
1494
1282
  ( ExternalTaskCompletionHandle.new(task).respond_to? :complete ).should == true
1495
1283
  end
1496
1284
  end
1497
1285
 
1498
-
1499
- describe FlowConstants do
1500
- options = {
1501
- :initial_retry_interval => 1,
1502
- :backoff_coefficient => 2,
1503
- :should_jitter => false,
1504
- :maximum_retry_interval_seconds => 100
1505
- }
1506
- options = ExponentialRetryOptions.new(options)
1507
-
1508
- it "will test the default retry function with regular cases" do
1509
- test_first = [Time.now, Time.now, Time.now]
1510
- test_time_of_failure = [0, 10, 100]
1511
- test_attempts = [{Exception=>2}, {Exception=>4}, {ActivityTaskTimedOutException=>5, Exception=>2}]
1512
- test_output = [1, 4, 32]
1513
- arr = test_first.zip(test_time_of_failure, test_attempts, test_output)
1514
- arr.each do |first, time_of_failure, attempts, output|
1515
- result = FlowConstants.exponential_retry_function.call(first, time_of_failure, attempts, options)
1516
- (result == output).should == true
1517
- end
1518
- end
1519
-
1520
- it "will test for exceptions" do
1521
- expect { FlowConstants.exponential_retry_function.call(-1, 1, {}, options) }.to raise_error(ArgumentError, "first is not an instance of Time")
1522
- expect { FlowConstants.exponential_retry_function.call(Time.now, -1, {}, options) }.to raise_error(ArgumentError, "time_of_failure can't be negative")
1523
- expect { FlowConstants.exponential_retry_function.call(Time.now, 1, {Exception=>-1}, options) }.to raise_error(ArgumentError, "number of attempts can't be negative")
1524
- expect { FlowConstants.exponential_retry_function.call(Time.now, 1, {Exception=>-1, ActivityTaskTimedOutException=>-10}, options) }.to raise_error(ArgumentError, "number of attempts can't be negative")
1525
- expect { FlowConstants.exponential_retry_function.call(Time.now, 1, {Exception=>2, ActivityTaskTimedOutException=>-10}, options) }.to raise_error(ArgumentError, "number of attempts can't be negative")
1526
- end
1527
-
1528
- end
1529
- class TestWorkflow
1530
- extend Workflows
1531
- workflow :entry_point do
1532
- {
1533
- :execution_start_to_close_timeout => 30, :version => "1"
1534
- }
1535
- end
1536
- def entry_point
1537
-
1538
- end
1539
-
1540
- end
1541
- class TestActivity
1542
- extend Activity
1543
-
1544
- activity :run_activity1 do |o|
1545
- o.default_task_heartbeat_timeout = "3600"
1546
- o.default_task_list = "activity_task_list"
1547
- o.default_task_schedule_to_close_timeout = "3600"
1548
- o.default_task_schedule_to_start_timeout = "3600"
1549
- o.default_task_start_to_close_timeout = "3600"
1550
- o.version = "1"
1551
- end
1552
- def run_activity1
1553
- "first regular activity"
1554
- end
1555
- def run_activity2
1556
- "second regular activity"
1557
- end
1558
- end
1559
-
1560
- class TestActivityWorker < ActivityWorker
1561
-
1562
- attr_accessor :executor
1563
- def initialize(service, domain, task_list, forking_executor, *args, &block)
1564
- super(service, domain, task_list, *args, &block)
1565
- @executor = forking_executor
1566
- end
1567
- end
1568
-
1569
- class FakeTaskPoller < WorkflowTaskPoller
1570
- def get_decision_task
1571
- nil
1572
- end
1573
- end
1574
- def dumb_fib(n)
1575
- n < 1 ? 1 : dumb_fib(n - 1) + dumb_fib(n - 2)
1576
- end
1577
- describe WorkflowWorker do
1578
- it "will test whether WorkflowWorker shuts down cleanly when an interrupt is received" do
1579
- task_list = "TestWorkflow_tasklist"
1580
- service = FakeServiceClient.new
1581
- workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1582
- domain = FakeDomain.new(workflow_type_object)
1583
- workflow_worker = WorkflowWorker.new(service, domain, task_list)
1584
- workflow_worker.add_workflow_implementation(TestWorkflow)
1585
- pid = fork do
1586
- loop do
1587
- workflow_worker.run_once(true, FakeTaskPoller.new(service, domain, nil, task_list, nil))
1588
- end
1589
- end
1590
- # Send an interrupt to the child process
1591
- Process.kill("INT", pid)
1592
- # Adding a sleep to let things get setup correctly (not ideal but going with
1593
- # this for now)
1594
- sleep 5
1595
- return_pid, status = Process.wait2(pid, Process::WNOHANG)
1596
- Process.kill("KILL", pid) if return_pid.nil?
1597
- return_pid.should_not be nil
1598
- status.success?.should be_true
1599
- end
1600
-
1601
- it "will test whether WorkflowWorker dies cleanly when two interrupts are received" do
1602
- class FakeTaskPoller
1603
- def poll_and_process_single_task
1604
- dumb_fib(5000)
1605
- end
1606
- end
1607
- task_list = "TestWorkflow_tasklist"
1608
- service = FakeServiceClient.new
1609
- workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1610
- domain = FakeDomain.new(workflow_type_object)
1611
- workflow_worker = WorkflowWorker.new(service, domain, task_list)
1612
- workflow_worker.add_workflow_implementation(TestWorkflow)
1613
- pid = fork do
1614
- loop do
1615
- workflow_worker.run_once(true, FakeTaskPoller.new(service, domain, nil, task_list, nil))
1616
- end
1617
- end
1618
- # Send an interrupt to the child process
1619
- sleep 3
1620
- 2.times { Process.kill("INT", pid); sleep 2 }
1621
- return_pid, status = Process.wait2(pid, Process::WNOHANG)
1622
-
1623
- Process.kill("KILL", pid) if return_pid.nil?
1624
- return_pid.should_not be nil
1625
- status.success?.should be_false
1626
- end
1627
-
1628
- end
1629
- describe ActivityWorker do
1630
-
1631
- class FakeDomain
1632
- def activity_tasks
1633
- sleep 30
1634
- end
1635
- end
1636
- it "will test whether the ActivityWorker shuts down cleanly when an interrupt is received" do
1637
-
1638
- task_list = "TestWorkflow_tasklist"
1639
- service = FakeServiceClient.new
1640
- workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1641
- domain = FakeDomain.new(workflow_type_object)
1642
- forking_executor = ForkingExecutor.new
1643
- activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor) { {:logger => FakeLogger.new} }
1644
- activity_worker.add_activities_implementation(TestActivity)
1645
- # Starts the activity worker in a forked process. Also, attaches an at_exit
1646
- # handler to the process. When the process exits, the handler checks whether
1647
- # the executor's internal is_shutdown variable is set correctly or not.
1648
- pid = fork do
1649
- at_exit {
1650
- activity_worker.executor.is_shutdown.should == true
1651
- }
1652
- activity_worker.start true
1653
- end
1654
- # Send an interrupt to the child process
1655
- Process.kill("INT", pid)
1656
- # Adding a sleep to let things get setup correctly (not ideal but going with
1657
- # this for now)
1658
- sleep 5
1659
- return_pid, status = Process.wait2(pid, Process::WNOHANG)
1660
- Process.kill("KILL", pid) if return_pid.nil?
1661
- return_pid.should_not be nil
1662
-
1663
- status.success?.should be_true
1664
- end
1665
-
1666
- # This method will take a long time to run, allowing us to test our shutdown
1667
- # scenarios
1668
-
1669
-
1670
- it "will test whether the ActivityWorker shuts down immediately if two or more interrupts are received" do
1671
- task_list = "TestWorkflow_tasklist"
1672
- service = FakeServiceClient.new
1673
- workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1674
- domain = FakeDomain.new(workflow_type_object)
1675
- forking_executor = ForkingExecutor.new
1676
- activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor) { {:logger => FakeLogger.new} }
1677
-
1678
- activity_worker.add_activities_implementation(TestActivity)
1679
- # Starts the activity worker in a forked process. Also, executes a task
1680
- # using the forking executor of the activity worker. The executor will
1681
- # create a child process to run that task. The task (dumb_fib) is
1682
- # purposefully designed to be long running so that we can test our shutdown
1683
- # scenario.
1684
- pid = fork do
1685
- activity_worker.executor.execute {
1686
- dumb_fib(1000)
1687
- }
1688
- activity_worker.start true
1689
- end
1690
- # Adding a sleep to let things get setup correctly (not idea but going with
1691
- # this for now)
1692
- sleep 3
1693
- # Send 2 interrupts to the child process
1694
- 2.times { Process.kill("INT", pid); sleep 3 }
1695
- status = Process.waitall
1696
- status[0][1].success?.should be_false
1697
- end
1698
-
1699
- end
1700
-
1701
- describe "testing changing default values in RetryOptions and RetryPolicy" do
1702
-
1703
- it "will test exponential retry with a new retry function" do
1704
- my_retry_func = lambda do |first, time_of_failure, attempts|
1705
- 10
1706
- end
1707
- options = {
1708
- :should_jitter => false
1709
- }
1710
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
1711
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
1712
- result.should == 10
1713
- end
1714
-
1715
- it "will test the jitter function" do
1716
- my_retry_func = lambda do |first, time_of_failure, attempts|
1717
- 10
1718
- end
1719
- options = {
1720
- :should_jitter => true
1721
- }
1722
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options, true))
1723
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
1724
- result.should >= 10 && result.should < 15
1725
- end
1726
-
1727
-
1728
- it "will test whether we get the same jitter for a particular execution id" do
1729
-
1730
- (FlowConstants.jitter_function.call(1, 100)).should equal(FlowConstants.jitter_function.call(1, 100))
1731
-
1732
- end
1733
-
1734
- it "will test the default exceptions included for retry" do
1735
- options = RetryOptions.new
1736
- options.exceptions_to_include.should include Exception
1737
- end
1738
-
1739
- it "will test the default exceptions included for retry" do
1740
- my_retry_func = lambda do |first, time_of_failure, attempts|
1741
- 10
1742
- end
1743
- options = {
1744
- :exceptions_to_include => [ActivityTaskTimedOutException],
1745
- :exceptions_to_exclude => [ActivityTaskFailedException]
1746
- }
1747
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
1748
- result = retry_policy.isRetryable(ActivityTaskTimedOutException.new("a", "b", "c", "d"))
1749
- result.should == true
1750
-
1751
- result = retry_policy.isRetryable(ActivityTaskFailedException.new("a", "b", "c", RuntimeError.new))
1752
- result.should == false
1753
- end
1754
-
1755
-
1756
- it "will make sure exception is raised if the called exception is there in both included and excluded exceptions" do
1757
- my_retry_func = lambda do |first, time_of_failure, attempts|
1758
- 10
1759
- end
1760
- options = {
1761
- :exceptions_to_include => [ActivityTaskFailedException],
1762
- :exceptions_to_exclude => [ActivityTaskFailedException]
1763
- }
1764
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
1765
- expect {retry_policy.isRetryable(ActivityTaskFailedException.new("a", "b", "c", ActivityTaskFailedException))}.to raise_error
1766
- end
1767
-
1768
- it "will test max_attempts" do
1769
- my_retry_func = lambda do |first, time_of_failure, attempts|
1770
- 10
1771
- end
1772
- options = {
1773
- :maximum_attempts => 5,
1774
- :should_jitter => false
1775
- }
1776
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
1777
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception.new, 1)
1778
- result.should == -1
1779
-
1780
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>4}, Exception.new, 1)
1781
- result.should == 10
1782
- end
1783
-
1784
- it "will test retries_per_exception" do
1785
- my_retry_func = lambda do |first, time_of_failure, attempts|
1786
- 10
1787
- end
1788
- options = {
1789
- :retries_per_exception => {Exception => 5},
1790
- :should_jitter => false
1791
- }
1792
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
1793
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception.new, 1)
1794
- result.should == -1
1795
-
1796
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>4}, Exception.new, 1)
1797
- result.should == 10
1798
- end
1799
-
1800
- it "makes sure that the default retry function will use the user provided options" do
1801
-
1802
- first = Time.now
1803
- time_of_failure = 0
1804
- attempts = {Exception=>2}
1805
- options = {
1806
- :initial_retry_interval => 10,
1807
- :backoff_coefficient => 2,
1808
- :should_jitter => false,
1809
- :maximum_retry_interval_seconds => 5
1810
- }
1811
- options = ExponentialRetryOptions.new(options)
1812
- result = FlowConstants.exponential_retry_function.call(first, time_of_failure, attempts, options)
1813
- result.should == 5
1814
- end
1815
-
1816
- it "ensures that the next_retry_delay_seconds honors -1 returned by the retry function" do
1817
- my_retry_func = lambda do |first, time_of_failure, attempts|
1818
- -1
1819
- end
1820
- options = {
1821
- :should_jitter => true
1822
- }
1823
- retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
1824
- result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
1825
- result.should == -1
1826
- end
1827
-
1828
- it "ensures that the jitter function checks arguments passed to it" do
1829
- expect { FlowConstants.jitter_function.call(1, -1) }.to raise_error(
1830
- ArgumentError, "max_value should be greater than 0")
1831
- end
1832
- end