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.
- checksums.yaml +15 -0
- data/aws-flow.gemspec +1 -0
- data/lib/aws/decider/activity.rb +8 -6
- data/lib/aws/decider/async_decider.rb +1 -0
- data/lib/aws/decider/async_retrying_executor.rb +3 -3
- data/lib/aws/decider/decider.rb +16 -14
- data/lib/aws/decider/executor.rb +35 -22
- data/lib/aws/decider/flow_defaults.rb +28 -14
- data/lib/aws/decider/generic_client.rb +3 -4
- data/lib/aws/decider/options.rb +91 -117
- data/lib/aws/decider/state_machines.rb +1 -0
- data/lib/aws/decider/utilities.rb +15 -0
- data/lib/aws/decider/version.rb +1 -1
- data/lib/aws/decider/worker.rb +14 -8
- data/lib/aws/decider/workflow_client.rb +16 -11
- data/lib/aws/runner.rb +43 -39
- data/spec/aws/decider/integration/activity_spec.rb +345 -0
- data/spec/aws/{integration → decider/integration}/integration_spec.rb +818 -1183
- data/spec/aws/decider/integration/setup.rb +3 -0
- data/spec/aws/decider/unit/activity_spec.rb +233 -0
- data/spec/aws/decider/unit/async_retrying_executor_spec.rb +131 -0
- data/spec/aws/{unit → decider/unit}/decider_spec.rb +171 -718
- data/spec/aws/decider/unit/executor_spec.rb +123 -0
- data/spec/aws/decider/unit/flow_defaults_spec.rb +62 -0
- data/spec/aws/decider/unit/misc_spec.rb +101 -0
- data/spec/aws/decider/unit/options_spec.rb +289 -0
- data/spec/aws/decider/unit/retry_spec.rb +217 -0
- data/spec/aws/{unit → decider/unit}/rubyflow.rb +0 -0
- data/spec/aws/decider/unit/setup.rb +3 -0
- data/spec/aws/decider/unit/worker_spec.rb +325 -0
- data/spec/aws/decider/unit/workflow_client_spec.rb +83 -0
- data/spec/aws/{unit → flow}/async_backtrace_spec.rb +0 -0
- data/spec/aws/{unit → flow}/async_scope_spec.rb +0 -0
- data/spec/aws/{unit → flow}/begin_rescue_ensure_spec.rb +1 -0
- data/spec/aws/{unit → flow}/external_task_spec.rb +0 -0
- data/spec/aws/{unit → flow}/factories.rb +0 -0
- data/spec/aws/{unit → flow}/fiber_condition_variable_spec.rb +0 -0
- data/spec/aws/{unit → flow}/fiber_spec.rb +0 -0
- data/spec/aws/{unit → flow}/flow_spec.rb +0 -0
- data/spec/aws/{unit → flow}/future_spec.rb +0 -0
- data/spec/aws/{unit → flow}/simple_dfa_spec.rb +0 -0
- data/spec/aws/{integration → runner/integration}/runner_integration_spec.rb +16 -43
- data/spec/aws/{unit → runner/unit}/runner_unit_spec.rb +18 -18
- data/spec/spec_helper.rb +264 -2
- metadata +37 -28
- data/spec/aws/unit/executor_spec.rb +0 -49
- data/spec/aws/unit/options_spec.rb +0 -293
- data/spec/aws/unit/preinclude_tests.rb +0 -149
@@ -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.
|
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
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
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
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
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
|
325
|
+
def start
|
562
326
|
activity.run_activity1
|
563
327
|
activity.run_activity2
|
564
328
|
end
|
565
329
|
end
|
566
|
-
workflow_type_object =
|
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
|
-
|
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
|
-
|
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 =
|
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)
|
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
|
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|
|
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
|