aws-flow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +8 -0
- data/LICENSE.TXT +15 -0
- data/NOTICE.TXT +14 -0
- data/Rakefile +39 -0
- data/aws-flow-core/Gemfile +9 -0
- data/aws-flow-core/LICENSE.TXT +15 -0
- data/aws-flow-core/NOTICE.TXT +14 -0
- data/aws-flow-core/Rakefile +27 -0
- data/aws-flow-core/aws-flow-core.gemspec +12 -0
- data/aws-flow-core/lib/aws/flow.rb +26 -0
- data/aws-flow-core/lib/aws/flow/async_backtrace.rb +134 -0
- data/aws-flow-core/lib/aws/flow/async_scope.rb +195 -0
- data/aws-flow-core/lib/aws/flow/begin_rescue_ensure.rb +386 -0
- data/aws-flow-core/lib/aws/flow/fiber.rb +77 -0
- data/aws-flow-core/lib/aws/flow/flow_utils.rb +50 -0
- data/aws-flow-core/lib/aws/flow/future.rb +109 -0
- data/aws-flow-core/lib/aws/flow/implementation.rb +151 -0
- data/aws-flow-core/lib/aws/flow/simple_dfa.rb +85 -0
- data/aws-flow-core/lib/aws/flow/tasks.rb +405 -0
- data/aws-flow-core/test/aws/async_backtrace_spec.rb +41 -0
- data/aws-flow-core/test/aws/async_scope_spec.rb +118 -0
- data/aws-flow-core/test/aws/begin_rescue_ensure_spec.rb +665 -0
- data/aws-flow-core/test/aws/external_task_spec.rb +197 -0
- data/aws-flow-core/test/aws/factories.rb +52 -0
- data/aws-flow-core/test/aws/fiber_condition_variable_spec.rb +163 -0
- data/aws-flow-core/test/aws/fiber_spec.rb +78 -0
- data/aws-flow-core/test/aws/flow_spec.rb +255 -0
- data/aws-flow-core/test/aws/future_spec.rb +210 -0
- data/aws-flow-core/test/aws/rubyflow.rb +22 -0
- data/aws-flow-core/test/aws/simple_dfa_spec.rb +63 -0
- data/aws-flow-core/test/aws/spec_helper.rb +36 -0
- data/aws-flow.gemspec +13 -0
- data/lib/aws/decider.rb +67 -0
- data/lib/aws/decider/activity.rb +408 -0
- data/lib/aws/decider/activity_definition.rb +111 -0
- data/lib/aws/decider/async_decider.rb +673 -0
- data/lib/aws/decider/async_retrying_executor.rb +153 -0
- data/lib/aws/decider/data_converter.rb +40 -0
- data/lib/aws/decider/decider.rb +511 -0
- data/lib/aws/decider/decision_context.rb +60 -0
- data/lib/aws/decider/exceptions.rb +178 -0
- data/lib/aws/decider/executor.rb +149 -0
- data/lib/aws/decider/flow_defaults.rb +70 -0
- data/lib/aws/decider/generic_client.rb +178 -0
- data/lib/aws/decider/history_helper.rb +173 -0
- data/lib/aws/decider/implementation.rb +82 -0
- data/lib/aws/decider/options.rb +607 -0
- data/lib/aws/decider/state_machines.rb +373 -0
- data/lib/aws/decider/task_handler.rb +76 -0
- data/lib/aws/decider/task_poller.rb +207 -0
- data/lib/aws/decider/utilities.rb +187 -0
- data/lib/aws/decider/worker.rb +324 -0
- data/lib/aws/decider/workflow_client.rb +374 -0
- data/lib/aws/decider/workflow_clock.rb +104 -0
- data/lib/aws/decider/workflow_definition.rb +101 -0
- data/lib/aws/decider/workflow_definition_factory.rb +53 -0
- data/lib/aws/decider/workflow_enabled.rb +26 -0
- data/test/aws/decider_spec.rb +1299 -0
- data/test/aws/factories.rb +45 -0
- data/test/aws/integration_spec.rb +3108 -0
- data/test/aws/spec_helper.rb +23 -0
- metadata +138 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
module AWS
|
17
|
+
module Flow
|
18
|
+
|
19
|
+
# Every workflow implementation needs to be a subclass of this class.
|
20
|
+
#
|
21
|
+
# Usually there should be no need to instantiate the class manually, as instead, the @execute method is called to
|
22
|
+
# start the workflow (you can think of ths as having factory class methods).
|
23
|
+
class WorkflowDefinition
|
24
|
+
|
25
|
+
attr_reader :decision_helper
|
26
|
+
|
27
|
+
attr_reader :converter
|
28
|
+
|
29
|
+
def initialize(instance, workflow_method, signals, get_state_method, converter)
|
30
|
+
@instance = instance
|
31
|
+
@workflow_method = workflow_method
|
32
|
+
@get_state_method = get_state_method
|
33
|
+
@signals = signals
|
34
|
+
@converter = converter
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute(input = nil)
|
38
|
+
#TODO Set up all the converter stuff
|
39
|
+
result = Future.new
|
40
|
+
method_output = Future.new
|
41
|
+
error_handler do |t|
|
42
|
+
t.begin do
|
43
|
+
if input.nil?
|
44
|
+
method_output.set(@instance.send(@workflow_method))
|
45
|
+
else
|
46
|
+
ruby_input = @converter.load input
|
47
|
+
# Have to have *ruby_input in order to be able to handle sending
|
48
|
+
# arbitrary arguments correctly, as otherwise it will seem as if
|
49
|
+
# @workflow_method will always have an arity of 1
|
50
|
+
method_output.set(@instance.send(@workflow_method, *ruby_input))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
t.rescue(Exception) do |error|
|
54
|
+
@failure = WorkflowException.new(error.message, @converter.dump(error))
|
55
|
+
#TODO error handling stuff
|
56
|
+
end
|
57
|
+
t.ensure do
|
58
|
+
raise @failure if @failure
|
59
|
+
result.set(@converter.dump method_output.get)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
return result
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_workflow_state
|
66
|
+
return nil if @get_state_method.nil?
|
67
|
+
converter = @get_state_method.data_converter || @converter
|
68
|
+
method = @get_state_method.method_name
|
69
|
+
begin
|
70
|
+
result = @instance.send(method)
|
71
|
+
return converter.dump(result)
|
72
|
+
rescue Exception => e
|
73
|
+
raise WorkflowException.new(e.message, converter.dump(e))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
def signal_received(signal_name, input)
|
80
|
+
method_pair = @signals[signal_name]
|
81
|
+
raise "No such signal for #{signal_name}" unless method_pair
|
82
|
+
converter = method_pair.data_converter
|
83
|
+
method_name = method_pair.method_name
|
84
|
+
error_handler do |t|
|
85
|
+
t.begin do
|
86
|
+
if input.class <= NoInput
|
87
|
+
@instance.send(method_name)
|
88
|
+
else
|
89
|
+
parameters = converter.load input
|
90
|
+
@instance.send(method_name, *parameters)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
t.rescue(Exception) do |e|
|
94
|
+
WorkflowException.new("Got an error while sending #{method_name} with parameters #{parameters}", converter.dump(e))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
module AWS
|
17
|
+
module Flow
|
18
|
+
|
19
|
+
class WorkflowDefinitionFactory
|
20
|
+
attr_reader :converter
|
21
|
+
def initialize(klass, workflow_type, registration_options, implementation_options, workflow_method, signals, get_state_method)
|
22
|
+
@klass = klass
|
23
|
+
@workflow_type = workflow_type
|
24
|
+
@registration_options = registration_options
|
25
|
+
@implementation_options = implementation_options
|
26
|
+
@workflow_method = workflow_method
|
27
|
+
@signals = signals
|
28
|
+
@get_state_method = get_state_method
|
29
|
+
if ! implementation_options.nil?
|
30
|
+
@converter = implementation_options.data_converter
|
31
|
+
end
|
32
|
+
@converter ||= FlowConstants.default_data_converter
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_workflow_definition(decision_context)
|
37
|
+
FlowFiber.current[:decision_context] = decision_context
|
38
|
+
this_instance = @klass.new
|
39
|
+
WorkflowDefinition.new(this_instance, @workflow_method, @signals, @get_state_method, @converter)
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete_workflow_definition(definition)
|
43
|
+
FlowFiber.unset(FlowFiber.current, :decision_context)
|
44
|
+
# Indicating to GC that these values are no longer needed
|
45
|
+
FlowFiber.local_variables.each_pair do |key, value|
|
46
|
+
value = nil
|
47
|
+
FlowFiber.local_variables.delete(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
#++
|
15
|
+
|
16
|
+
module AWS
|
17
|
+
module Flow
|
18
|
+
# This method is for internal use only and may be changed or removed
|
19
|
+
# without prior notice. Use {#workflow_client} instead.
|
20
|
+
# @!visibility private
|
21
|
+
def workflow_factory(client, domain, &options)
|
22
|
+
WorkflowFactory.new(client, domain, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,1299 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
5
|
+
# You may not use this file except in compliance with the License.
|
6
|
+
# A copy of the License is located at
|
7
|
+
#
|
8
|
+
# http://aws.amazon.com/apache2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is distributed
|
11
|
+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
12
|
+
# express or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
##
|
15
|
+
|
16
|
+
require 'yaml'
|
17
|
+
|
18
|
+
require 'aws/decider'
|
19
|
+
include AWS::Flow
|
20
|
+
|
21
|
+
|
22
|
+
$RUBYFLOW_DECIDER_DOMAIN = "rubyflow_decider_domain_06-12-2012"
|
23
|
+
$RUBYFLOW_DECIDER_TASK_LIST = 'test_task_list'
|
24
|
+
class TrivialConverter
|
25
|
+
def dump(x)
|
26
|
+
x
|
27
|
+
end
|
28
|
+
def load(x)
|
29
|
+
x
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class FakePage
|
34
|
+
def initialize(object); @object = object; end
|
35
|
+
def page; @object; end
|
36
|
+
end
|
37
|
+
|
38
|
+
class FakeWorkflowExecution
|
39
|
+
def run_id
|
40
|
+
"1"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FakeWorkflowExecutionCollecton
|
45
|
+
def at(workflow_id, run_id); "Workflow_execution"; end
|
46
|
+
end
|
47
|
+
|
48
|
+
class FakeDomain
|
49
|
+
def initialize(workflow_type_object)
|
50
|
+
@workflow_type_object = workflow_type_object
|
51
|
+
end
|
52
|
+
def page; FakePage.new(@workflow_type_object); end
|
53
|
+
def workflow_executions; FakeWorkflowExecutionCollecton.new; end
|
54
|
+
def name; "fake_domain"; end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
describe Activity do
|
59
|
+
let(:trace) { [] }
|
60
|
+
let(:client) { client = GenericActivityClient.new(DecisionHelper.new, ActivityOptions.new) }
|
61
|
+
|
62
|
+
it "ensures that schedule_activity gets set up from the activity_client" do
|
63
|
+
client.reconfigure(:test) { |o| o.version = "blah" }
|
64
|
+
class GenericActivityClient
|
65
|
+
alias :old_schedule_activity :schedule_activity
|
66
|
+
def schedule_activity(name, activity_type, input, options)
|
67
|
+
:scheduled_activity
|
68
|
+
end
|
69
|
+
end
|
70
|
+
trace << client.test
|
71
|
+
trace.should == [:scheduled_activity]
|
72
|
+
class GenericActivityClient
|
73
|
+
alias :schedule_activity :old_schedule_activity
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "multiple activities" do
|
78
|
+
it "ensures that you can schedule multiple activities with the same activity call" do
|
79
|
+
class GenericActivityClient
|
80
|
+
alias :old_schedule_activity :schedule_activity
|
81
|
+
def schedule_activity(name, activity_type, input, options)
|
82
|
+
"scheduled_activity_#{name}".to_sym
|
83
|
+
end
|
84
|
+
end
|
85
|
+
client = GenericActivityClient.new(DecisionHelper.new, ActivityOptions.new)
|
86
|
+
client.reconfigure(:test, :test2) { |o| o.version = "blah" }
|
87
|
+
trace << client.test
|
88
|
+
trace << client.test2
|
89
|
+
class GenericActivityClient
|
90
|
+
alias :schedule_activity :old_schedule_activity
|
91
|
+
end
|
92
|
+
trace.should == [:"scheduled_activity_.test", :"scheduled_activity_.test2"]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
describe GenericActivityClient do
|
96
|
+
|
97
|
+
before(:each) do
|
98
|
+
@options = ActivityOptions.new
|
99
|
+
@decision_helper = DecisionHelper.new
|
100
|
+
@client = GenericActivityClient.new(@decision_helper, @options)
|
101
|
+
[:test, :test_nil].each do |method_name|
|
102
|
+
@client.reconfigure(method_name) { |o| o.version = 1 }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
it "ensures that activities get generated correctly" do
|
106
|
+
scope = AsyncScope.new { @client.test }
|
107
|
+
scope.eventLoop
|
108
|
+
@decision_helper.decision_map.values.first.
|
109
|
+
get_decision[:schedule_activity_task_decision_attributes][:activity_type][:name].should =~
|
110
|
+
/test/
|
111
|
+
end
|
112
|
+
|
113
|
+
it "ensures that an activity that has no arguments is scheduled with no input value" do
|
114
|
+
scope = AsyncScope.new do
|
115
|
+
|
116
|
+
@client.test_nil
|
117
|
+
end
|
118
|
+
scope.eventLoop
|
119
|
+
@decision_helper.decision_map.values.first.
|
120
|
+
get_decision[:schedule_activity_task_decision_attributes].keys.should_not include :input
|
121
|
+
end
|
122
|
+
|
123
|
+
it "ensures that activities pass multiple activities fine" do
|
124
|
+
scope = AsyncScope.new do
|
125
|
+
@client.test(1, 2, 3)
|
126
|
+
end
|
127
|
+
scope.eventLoop
|
128
|
+
input = @decision_helper.decision_map.values.first.
|
129
|
+
get_decision[:schedule_activity_task_decision_attributes][:input]
|
130
|
+
@client.data_converter.load(input).should == [1, 2, 3]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe WorkflowClient do
|
136
|
+
class FakeServiceClient
|
137
|
+
attr_accessor :trace
|
138
|
+
def respond_decision_task_completed(task_completed_request)
|
139
|
+
@trace ||= []
|
140
|
+
@trace << task_completed_request
|
141
|
+
end
|
142
|
+
def start_workflow_execution(options)
|
143
|
+
@trace ||= []
|
144
|
+
@trace << options
|
145
|
+
{"runId" => "blah"}
|
146
|
+
end
|
147
|
+
def register_workflow_type(options)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
class TestWorkflow
|
151
|
+
extend Decider
|
152
|
+
|
153
|
+
entry_point :entry_point
|
154
|
+
def entry_point
|
155
|
+
return "This is the entry point"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
before(:each) do
|
159
|
+
workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
|
160
|
+
@client = WorkflowClient.new(FakeServiceClient.new, FakeDomain.new(workflow_type_object), TestWorkflow, StartWorkflowOptions.new)
|
161
|
+
end
|
162
|
+
it "makes sure that configure works correctly" do
|
163
|
+
@client.reconfigure(:entry_point) {{ :task_list => "This nonsense" }}
|
164
|
+
@client.entry_point
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe ActivityDefinition do
|
170
|
+
class MyActivity
|
171
|
+
extend Activity
|
172
|
+
def test_three_arguments(a, b, c)
|
173
|
+
a + b + c
|
174
|
+
end
|
175
|
+
def test_no_arguments()
|
176
|
+
:no_arguments
|
177
|
+
end
|
178
|
+
def test_one_argument(arg)
|
179
|
+
arg
|
180
|
+
end
|
181
|
+
def test_getting_context
|
182
|
+
self.activity_execution_context
|
183
|
+
end
|
184
|
+
activity :test_three_arguments, :test_no_arguments, :test_one_argument
|
185
|
+
end
|
186
|
+
it "ensures that an activity definition can handle one argument" do
|
187
|
+
activity_definition = ActivityDefinition.new(MyActivity.new, :test_one_argument, nil , nil, TrivialConverter.new)
|
188
|
+
activity_definition.execute(5, nil).should == 5
|
189
|
+
end
|
190
|
+
it "ensures that you can get the activity context " do
|
191
|
+
activity_definition = ActivityDefinition.new(MyActivity.new, :test_getting_context, nil , nil, TrivialConverter.new)
|
192
|
+
(activity_definition.execute(nil, ActivityExecutionContext.new(nil, nil, nil)).is_a? ActivityExecutionContext).should == true
|
193
|
+
end
|
194
|
+
it "ensures that the activity context gets unset after the execute" do
|
195
|
+
activity_definition = ActivityDefinition.new(MyActivity.new, :test_getting_context, nil , nil, TrivialConverter.new)
|
196
|
+
activity_definition.execute(nil, ActivityExecutionContext.new(nil, nil, nil))
|
197
|
+
begin
|
198
|
+
activity_definition.execute(nil, nil)
|
199
|
+
rescue Exception => e
|
200
|
+
e.backtrace.should include "No activity execution context"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
it "ensures that an activity definition can handle multiple arguments" do
|
204
|
+
activity_definition = ActivityDefinition.new(MyActivity.new, :test_three_arguments, nil , nil, TrivialConverter.new)
|
205
|
+
activity_definition.execute([1,2,3], nil).should == 6
|
206
|
+
end
|
207
|
+
it "ensures that an activity definition can handle no arguments" do
|
208
|
+
activity_definition = ActivityDefinition.new(MyActivity.new, :test_no_arguments, nil , nil, TrivialConverter.new)
|
209
|
+
activity_definition.execute(nil, nil).should == :no_arguments
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe WorkflowDefinitionFactory do
|
214
|
+
before(:each) do
|
215
|
+
class MyWorkflow
|
216
|
+
extend Decider
|
217
|
+
version "1"
|
218
|
+
def no_arguments
|
219
|
+
:no_arguments
|
220
|
+
end
|
221
|
+
def one_argument(arg)
|
222
|
+
arg
|
223
|
+
end
|
224
|
+
def multiple_arguments(arg1, arg2, arg3)
|
225
|
+
arg3
|
226
|
+
end
|
227
|
+
end
|
228
|
+
class WorkflowDefinition
|
229
|
+
attr_accessor :decision_helper, :workflow_method, :converter
|
230
|
+
end
|
231
|
+
end
|
232
|
+
let(:fake_decision_context) { stub(:decision_helper => nil) }
|
233
|
+
let(:workflow_definition) do
|
234
|
+
FlowFiber.stub(:current) { Hash.new(Hash.new) }
|
235
|
+
WorkflowDefinitionFactory.new(MyWorkflow, nil, nil, nil, nil, nil, nil).get_workflow_definition(fake_decision_context) end
|
236
|
+
it "makes sure that workflowDefinitionFactory#get_workflow_definition returns different instances" do
|
237
|
+
FlowFiber.stub(:current) { Hash.new(Hash.new) }
|
238
|
+
workflow_factory = WorkflowDefinitionFactory.new(MyWorkflow, nil, nil, nil, nil, nil ,nil)
|
239
|
+
first_definition = workflow_factory.get_workflow_definition(fake_decision_context)
|
240
|
+
second_definition = workflow_factory.get_workflow_definition(fake_decision_context)
|
241
|
+
(first_definition.object_id == second_definition.object_id).should == false
|
242
|
+
end
|
243
|
+
describe "Testing the input/output" do
|
244
|
+
before(:each) do
|
245
|
+
workflow_definition.converter = TrivialConverter.new
|
246
|
+
end
|
247
|
+
it "ensures that a workflow definition can handle multiple arguments" do
|
248
|
+
workflow_definition.workflow_method = :multiple_arguments
|
249
|
+
AsyncScope.new do
|
250
|
+
workflow_definition.execute([1, 2, 3]).get
|
251
|
+
end.eventLoop
|
252
|
+
end
|
253
|
+
it "ensures that a workflow definition can handle no arguments" do
|
254
|
+
workflow_definition.workflow_method = :no_arguments
|
255
|
+
AsyncScope.new do
|
256
|
+
workflow_definition.execute(nil).get.should == :no_arguments
|
257
|
+
end.eventLoop
|
258
|
+
end
|
259
|
+
it "ensures that a workflow definition can handle one argument" do
|
260
|
+
workflow_definition.workflow_method = :one_argument
|
261
|
+
AsyncScope.new do
|
262
|
+
workflow_definition.execute(5).get.should == 5
|
263
|
+
end.eventLoop
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
p
|
268
|
+
describe ForkingExecutor do
|
269
|
+
it "makes sure that forking executors basic execute works" do
|
270
|
+
test_file_name = "ForkingExecutorTestFile"
|
271
|
+
begin
|
272
|
+
forking_executor = ForkingExecutor.new
|
273
|
+
File.exists?(test_file_name).should == false
|
274
|
+
forking_executor.execute do
|
275
|
+
File.new(test_file_name, 'w')
|
276
|
+
end
|
277
|
+
sleep 3
|
278
|
+
File.exists?(test_file_name).should == true
|
279
|
+
ensure
|
280
|
+
|
281
|
+
File.unlink(test_file_name)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
it "ensures that you cannot execute more tasks on a shutdown executor" do
|
286
|
+
forking_executor = ForkingExecutor.new
|
287
|
+
forking_executor.execute do
|
288
|
+
end
|
289
|
+
forking_executor.execute do
|
290
|
+
end
|
291
|
+
forking_executor.shutdown(1)
|
292
|
+
expect { forking_executor.execute { "yay" } }.to raise_error
|
293
|
+
RejectedExecutionException
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
describe AsyncDecider do
|
299
|
+
before(:each) do
|
300
|
+
@decision_helper = DecisionHelper.new
|
301
|
+
@history_helper = double(HistoryHelper)
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
describe YAMLDataConverter do
|
306
|
+
let(:converter) {YAMLDataConverter.new}
|
307
|
+
describe "ensures that x == load(dump(x)) is true" do
|
308
|
+
{
|
309
|
+
Fixnum => 5,
|
310
|
+
String => "Hello World",
|
311
|
+
Hash => {:test => "good"},
|
312
|
+
Array => ["Hello", "World", 5],
|
313
|
+
Symbol => :test,
|
314
|
+
NilClass => nil
|
315
|
+
}.each_pair do |klass, exemplar|
|
316
|
+
it "tests #{klass}" do
|
317
|
+
1.upto(10).each do |i|
|
318
|
+
converted_exemplar = exemplar
|
319
|
+
i.times {converted_exemplar = converter.dump converted_exemplar}
|
320
|
+
i.times {converted_exemplar = converter.load converted_exemplar}
|
321
|
+
converted_exemplar.should == exemplar
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe WorkflowFactory do
|
329
|
+
it "ensures that you can create a workflow_client without access to the Workflow definition" do
|
330
|
+
workflow_type_object = double("workflow_type", :name => "NonExistantWorkflow.some_entry_method", :start_execution => "" )
|
331
|
+
class FakeServiceClient
|
332
|
+
attr_accessor :trace
|
333
|
+
def respond_decision_task_completed(task_completed_request)
|
334
|
+
@trace ||= []
|
335
|
+
@trace << task_completed_request
|
336
|
+
end
|
337
|
+
def start_workflow_execution(options)
|
338
|
+
@trace ||= []
|
339
|
+
@trace << options
|
340
|
+
{"runId" => "blah"}
|
341
|
+
end
|
342
|
+
def register_workflow_type(options)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
domain = FakeDomain.new(workflow_type_object)
|
346
|
+
swf_client = FakeServiceClient.new
|
347
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
348
|
+
options.workflow_name = "NonExistantWorkflow"
|
349
|
+
options.execution_method = "some_entry_method"
|
350
|
+
end
|
351
|
+
# We want to make sure that we get to trying to start the execution on the
|
352
|
+
# workflow_type. The workflow_type will be nil, since we return an empty
|
353
|
+
# array in the domain.
|
354
|
+
my_workflow_factory.get_client.start_execution
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
describe "FakeHistory" do
|
359
|
+
before(:all) do
|
360
|
+
class WorkflowClock
|
361
|
+
alias_method :old_current_time, :current_time
|
362
|
+
def current_time
|
363
|
+
Time.now
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
class SynchronousWorkflowWorker < WorkflowWorker
|
368
|
+
def start
|
369
|
+
poller = SynchronousWorkflowTaskPoller.new(@service, nil, DecisionTaskHandler.new(@workflow_definition_map), @task_list)
|
370
|
+
poller.poll_and_process_single_task
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class FakeServiceClient
|
375
|
+
attr_accessor :trace
|
376
|
+
def respond_decision_task_completed(task_completed_request)
|
377
|
+
@trace ||= []
|
378
|
+
@trace << task_completed_request
|
379
|
+
end
|
380
|
+
def start_workflow_execution(options)
|
381
|
+
{"runId" => "blah"}
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class Hash
|
386
|
+
def to_h; self; end
|
387
|
+
end
|
388
|
+
|
389
|
+
class TestHistoryEvent < AWS::SimpleWorkflow::HistoryEvent
|
390
|
+
def initialize(event_type, event_id, attributes)
|
391
|
+
@event_type = event_type
|
392
|
+
@attributes = attributes
|
393
|
+
@event_id = event_id
|
394
|
+
@created_at = Time.now
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class FakeWorkflowType < WorkflowType
|
399
|
+
attr_accessor :domain, :name, :version
|
400
|
+
def initialize(domain, name, version)
|
401
|
+
@domain = domain
|
402
|
+
@name = name
|
403
|
+
@version = version
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class TestHistoryWrapper
|
408
|
+
def initialize(workflow_type, events)
|
409
|
+
@workflow_type = workflow_type
|
410
|
+
@events = events
|
411
|
+
end
|
412
|
+
def workflow_execution
|
413
|
+
FakeWorkflowExecution.new
|
414
|
+
end
|
415
|
+
def task_token
|
416
|
+
"1"
|
417
|
+
end
|
418
|
+
def previous_started_event_id
|
419
|
+
1
|
420
|
+
end
|
421
|
+
attr_reader :events, :workflow_type
|
422
|
+
end
|
423
|
+
end
|
424
|
+
after(:all) do
|
425
|
+
class WorkflowClock
|
426
|
+
alias_method :current_time, :old_current_time
|
427
|
+
end
|
428
|
+
|
429
|
+
end
|
430
|
+
|
431
|
+
|
432
|
+
it "reproduces a bug found by a customer" do
|
433
|
+
class BadWorkflow
|
434
|
+
class << self
|
435
|
+
attr_accessor :task_list
|
436
|
+
end
|
437
|
+
extend Decider
|
438
|
+
|
439
|
+
version "1"
|
440
|
+
entry_point :entry_point
|
441
|
+
def entry_point
|
442
|
+
# pass
|
443
|
+
end
|
444
|
+
end
|
445
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
446
|
+
domain = FakeDomain.new(workflow_type_object)
|
447
|
+
|
448
|
+
|
449
|
+
swf_client = FakeServiceClient.new
|
450
|
+
task_list = "BadWorkflow_tasklist"
|
451
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
452
|
+
worker.add_workflow_implementation(BadWorkflow)
|
453
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
454
|
+
options.workflow_name = "BadWorkflow"
|
455
|
+
options.execution_start_to_close_timeout = 3600
|
456
|
+
options.task_list = task_list
|
457
|
+
options.task_start_to_close_timeout = 10
|
458
|
+
options.child_policy = :request_cancel
|
459
|
+
end
|
460
|
+
my_workflow = my_workflow_factory.get_client
|
461
|
+
workflow_execution = my_workflow.start_execution(5)
|
462
|
+
|
463
|
+
|
464
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
465
|
+
def get_decision_tasks
|
466
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
467
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
468
|
+
[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"}),
|
469
|
+
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"}),
|
470
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {:scheduled_event_id=>2, :identity=>"some_identity"}),
|
471
|
+
TestHistoryEvent.new("DecisionTaskTimedOut", 4, {:scheduled_event_id=>2, :timeout_type=>"START_TO_CLOSE", :started_event_id=>3})
|
472
|
+
])
|
473
|
+
|
474
|
+
end
|
475
|
+
end
|
476
|
+
worker.start
|
477
|
+
# @forking_executor.execute { activity_worker.start }
|
478
|
+
|
479
|
+
# debugger
|
480
|
+
# worker.start
|
481
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
482
|
+
"CompleteWorkflowExecution"
|
483
|
+
end
|
484
|
+
|
485
|
+
it "reproduces the ActivityTaskTimedOut problem" do
|
486
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
487
|
+
def get_decision_tasks
|
488
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
489
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
490
|
+
[
|
491
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
492
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
493
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
494
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
|
495
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
|
496
|
+
TestHistoryEvent.new("ActivityTaskStarted", 6, {}),
|
497
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 7, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
|
498
|
+
])
|
499
|
+
end
|
500
|
+
end
|
501
|
+
class BadWorkflow
|
502
|
+
class << self
|
503
|
+
attr_accessor :task_list
|
504
|
+
end
|
505
|
+
extend Decider
|
506
|
+
version "1"
|
507
|
+
entry_point :entry_point
|
508
|
+
activity_client :activity do |options|
|
509
|
+
options.prefix_name = "BadActivity"
|
510
|
+
options.version = "1"
|
511
|
+
options.default_task_heartbeat_timeout = "3600"
|
512
|
+
options.default_task_list = "BadWorkflow"
|
513
|
+
options.default_task_schedule_to_close_timeout = "30"
|
514
|
+
options.default_task_schedule_to_start_timeout = "30"
|
515
|
+
options.default_task_start_to_close_timeout = "10"
|
516
|
+
end
|
517
|
+
def entry_point
|
518
|
+
activity.run_activity1
|
519
|
+
activity.run_activity2
|
520
|
+
end
|
521
|
+
end
|
522
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
523
|
+
domain = FakeDomain.new(workflow_type_object)
|
524
|
+
|
525
|
+
|
526
|
+
swf_client = FakeServiceClient.new
|
527
|
+
task_list = "BadWorkflow_tasklist"
|
528
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
529
|
+
worker.add_workflow_implementation(BadWorkflow)
|
530
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
531
|
+
options.workflow_name = "BadWorkflow"
|
532
|
+
options.execution_start_to_close_timeout = 3600
|
533
|
+
options.task_list = task_list
|
534
|
+
options.task_start_to_close_timeout = 10
|
535
|
+
options.child_policy = :request_cancel
|
536
|
+
end
|
537
|
+
|
538
|
+
my_workflow = my_workflow_factory.get_client
|
539
|
+
workflow_execution = my_workflow.start_execution(5)
|
540
|
+
worker.start
|
541
|
+
|
542
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
543
|
+
"FailWorkflowExecution"
|
544
|
+
swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details].should =~
|
545
|
+
/AWS::Flow::ActivityTaskTimedOutException/
|
546
|
+
end
|
547
|
+
|
548
|
+
it "makes sure that exponential retry can take arguments" do
|
549
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
550
|
+
def get_decision_tasks
|
551
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
552
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
553
|
+
[
|
554
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
555
|
+
])
|
556
|
+
end
|
557
|
+
end
|
558
|
+
class BadWorkflow
|
559
|
+
class << self
|
560
|
+
attr_accessor :task_list
|
561
|
+
end
|
562
|
+
extend Decider
|
563
|
+
version "1"
|
564
|
+
entry_point :entry_point
|
565
|
+
|
566
|
+
activity_client :activity do |options|
|
567
|
+
options.prefix_name = "BadActivity"
|
568
|
+
options.version = "1"
|
569
|
+
options.default_task_heartbeat_timeout = "3600"
|
570
|
+
options.default_task_list = "BadWorkflow"
|
571
|
+
options.default_task_schedule_to_close_timeout = "30"
|
572
|
+
options.default_task_schedule_to_start_timeout = "30"
|
573
|
+
options.default_task_start_to_close_timeout = "10"
|
574
|
+
end
|
575
|
+
def entry_point
|
576
|
+
activity.exponential_retry(:run_activity1, 5) do |o|
|
577
|
+
o.maximum_attempts = 3
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
582
|
+
domain = FakeDomain.new(workflow_type_object)
|
583
|
+
|
584
|
+
swf_client = FakeServiceClient.new
|
585
|
+
task_list = "BadWorkflow_tasklist"
|
586
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
587
|
+
worker.add_workflow_implementation(BadWorkflow)
|
588
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
589
|
+
options.workflow_name = "BadWorkflow"
|
590
|
+
options.execution_start_to_close_timeout = 3600
|
591
|
+
options.task_list = task_list
|
592
|
+
options.task_start_to_close_timeout = 10
|
593
|
+
options.child_policy = :request_cancel
|
594
|
+
end
|
595
|
+
my_workflow = my_workflow_factory.get_client
|
596
|
+
workflow_execution = my_workflow.start_execution
|
597
|
+
worker.start
|
598
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
599
|
+
"ScheduleActivityTask"
|
600
|
+
end
|
601
|
+
|
602
|
+
it "makes sure that overriding works correctly" do
|
603
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
604
|
+
def get_decision_tasks
|
605
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
606
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
607
|
+
[
|
608
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
609
|
+
])
|
610
|
+
end
|
611
|
+
end
|
612
|
+
class BadWorkflow
|
613
|
+
class << self
|
614
|
+
attr_accessor :task_list
|
615
|
+
end
|
616
|
+
extend Decider
|
617
|
+
version "1"
|
618
|
+
entry_point :entry_point
|
619
|
+
activity_client :activity do |options|
|
620
|
+
options.prefix_name = "BadActivity"
|
621
|
+
options.version = "1"
|
622
|
+
options.default_task_heartbeat_timeout = "3600"
|
623
|
+
options.default_task_list = "BadWorkflow"
|
624
|
+
options.default_task_schedule_to_close_timeout = "30"
|
625
|
+
options.default_task_schedule_to_start_timeout = "30"
|
626
|
+
options.default_task_start_to_close_timeout = "10"
|
627
|
+
end
|
628
|
+
def entry_point
|
629
|
+
activity.exponential_retry(:run_activity1, 5) do |o|
|
630
|
+
o.maximum_attempts = 3
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
634
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
635
|
+
domain = FakeDomain.new(workflow_type_object)
|
636
|
+
|
637
|
+
swf_client = FakeServiceClient.new
|
638
|
+
task_list = "BadWorkflow_tasklist"
|
639
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
640
|
+
worker.add_workflow_implementation(BadWorkflow)
|
641
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
642
|
+
options.workflow_name = "BadWorkflow"
|
643
|
+
options.execution_start_to_close_timeout = 3600
|
644
|
+
options.task_list = task_list
|
645
|
+
options.task_start_to_close_timeout = 10
|
646
|
+
options.child_policy = :request_cancel
|
647
|
+
end
|
648
|
+
my_workflow = my_workflow_factory.get_client
|
649
|
+
workflow_execution = my_workflow.start_execution
|
650
|
+
worker.start
|
651
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
652
|
+
"ScheduleActivityTask"
|
653
|
+
end
|
654
|
+
|
655
|
+
it "makes sure that exponential_retry blocks correctly" do
|
656
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
657
|
+
def get_decision_tasks
|
658
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
659
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
660
|
+
[
|
661
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
662
|
+
])
|
663
|
+
end
|
664
|
+
end
|
665
|
+
class BadWorkflow
|
666
|
+
class << self
|
667
|
+
attr_accessor :task_list, :trace
|
668
|
+
end
|
669
|
+
@trace = []
|
670
|
+
extend Decider
|
671
|
+
version "1"
|
672
|
+
entry_point :entry_point
|
673
|
+
activity_client :activity do |options|
|
674
|
+
options.prefix_name = "BadActivity"
|
675
|
+
options.version = "1"
|
676
|
+
options.default_task_heartbeat_timeout = "3600"
|
677
|
+
options.default_task_list = "BadWorkflow"
|
678
|
+
options.default_task_schedule_to_close_timeout = "30"
|
679
|
+
options.default_task_schedule_to_start_timeout = "30"
|
680
|
+
options.default_task_start_to_close_timeout = "10"
|
681
|
+
end
|
682
|
+
def entry_point
|
683
|
+
BadWorkflow.trace << :start
|
684
|
+
activity.exponential_retry(:run_activity1, 5) do |o|
|
685
|
+
o.maximum_attempts = 3
|
686
|
+
end
|
687
|
+
BadWorkflow.trace << :middle
|
688
|
+
activity.exponential_retry(:run_activity2, 5) do |o|
|
689
|
+
o.maximum_attempts = 3
|
690
|
+
end
|
691
|
+
activity.run_activity1
|
692
|
+
BadWorkflow.trace << :end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
696
|
+
domain = FakeDomain.new(workflow_type_object)
|
697
|
+
|
698
|
+
swf_client = FakeServiceClient.new
|
699
|
+
task_list = "BadWorkflow_tasklist"
|
700
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
701
|
+
worker.add_workflow_implementation(BadWorkflow)
|
702
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
703
|
+
options.workflow_name = "BadWorkflow"
|
704
|
+
options.execution_start_to_close_timeout = 3600
|
705
|
+
options.task_list = task_list
|
706
|
+
options.task_start_to_close_timeout = 10
|
707
|
+
options.child_policy = :request_cancel
|
708
|
+
end
|
709
|
+
my_workflow = my_workflow_factory.get_client
|
710
|
+
workflow_execution = my_workflow.start_execution
|
711
|
+
worker.start
|
712
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
713
|
+
"ScheduleActivityTask"
|
714
|
+
BadWorkflow.trace.should == [:start]
|
715
|
+
end
|
716
|
+
|
717
|
+
it "makes sure that exponential_retry blocks correctly when done through configure" do
|
718
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
719
|
+
def get_decision_tasks
|
720
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
721
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
722
|
+
[
|
723
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
724
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
725
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
726
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
|
727
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
|
728
|
+
TestHistoryEvent.new("ActivityTaskStarted", 6, {}),
|
729
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 7, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
|
730
|
+
])
|
731
|
+
end
|
732
|
+
end
|
733
|
+
class BadWorkflow
|
734
|
+
class << self
|
735
|
+
attr_accessor :task_list, :trace
|
736
|
+
end
|
737
|
+
@trace = []
|
738
|
+
extend Decider
|
739
|
+
version "1"
|
740
|
+
entry_point :entry_point
|
741
|
+
activity_client :activity do |options|
|
742
|
+
options.prefix_name = "BadActivity"
|
743
|
+
options.version = "1"
|
744
|
+
options.default_task_heartbeat_timeout = "3600"
|
745
|
+
options.default_task_list = "BadWorkflow"
|
746
|
+
options.default_task_schedule_to_close_timeout = "90"
|
747
|
+
options.default_task_schedule_to_start_timeout = "90"
|
748
|
+
options.default_task_start_to_close_timeout = "90"
|
749
|
+
end
|
750
|
+
def entry_point
|
751
|
+
BadWorkflow.trace << :start
|
752
|
+
|
753
|
+
activity.reconfigure(:run_activity1) do |o|
|
754
|
+
o.exponential_retry do |retry_options|
|
755
|
+
retry_options.maximum_attempts = 3
|
756
|
+
end
|
757
|
+
end
|
758
|
+
activity.run_activity1
|
759
|
+
BadWorkflow.trace << :middle
|
760
|
+
activity.run_activity1
|
761
|
+
end
|
762
|
+
end
|
763
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
764
|
+
domain = FakeDomain.new(workflow_type_object)
|
765
|
+
|
766
|
+
swf_client = FakeServiceClient.new
|
767
|
+
task_list = "BadWorkflow_tasklist"
|
768
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
769
|
+
worker.add_workflow_implementation(BadWorkflow)
|
770
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
771
|
+
options.workflow_name = "BadWorkflow"
|
772
|
+
options.execution_start_to_close_timeout = 3600
|
773
|
+
options.task_list = task_list
|
774
|
+
options.task_start_to_close_timeout = 30
|
775
|
+
options.child_policy = :request_cancel
|
776
|
+
end
|
777
|
+
my_workflow = my_workflow_factory.get_client
|
778
|
+
workflow_execution = my_workflow.start_execution
|
779
|
+
worker.start
|
780
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
781
|
+
"StartTimer"
|
782
|
+
BadWorkflow.trace.should == [:start]
|
783
|
+
end
|
784
|
+
|
785
|
+
it "makes sure that exponential_retry blocks correctly when done through the activity_client" do
|
786
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
787
|
+
def get_decision_tasks
|
788
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
789
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
790
|
+
[
|
791
|
+
|
792
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {:created_at => Time.now}),
|
793
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {:created_at => Time.now}),
|
794
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {:created_at => Time.now}),
|
795
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {:created_at => Time.now}),
|
796
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1", :created_at => Time.now}),
|
797
|
+
TestHistoryEvent.new("ActivityTaskStarted", 6, {:created_at => Time.now}),
|
798
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 7, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE", :created_at => Time.now}),
|
799
|
+
])
|
800
|
+
end
|
801
|
+
end
|
802
|
+
class BadWorkflow
|
803
|
+
class << self
|
804
|
+
attr_accessor :task_list, :trace
|
805
|
+
end
|
806
|
+
@trace = []
|
807
|
+
extend Decider
|
808
|
+
version "1"
|
809
|
+
entry_point :entry_point
|
810
|
+
activity_client :activity do |options|
|
811
|
+
options.prefix_name = "BadActivity"
|
812
|
+
options.version = "1"
|
813
|
+
options.default_task_heartbeat_timeout = "3600"
|
814
|
+
options.default_task_list = "BadWorkflow"
|
815
|
+
options.default_task_schedule_to_close_timeout = "30"
|
816
|
+
options.default_task_schedule_to_start_timeout = "30"
|
817
|
+
options.default_task_start_to_close_timeout = "30"
|
818
|
+
options.exponential_retry do |retry_options|
|
819
|
+
retry_options.maximum_attempts = 3
|
820
|
+
end
|
821
|
+
end
|
822
|
+
def entry_point
|
823
|
+
BadWorkflow.trace << :start
|
824
|
+
activity.run_activity1
|
825
|
+
BadWorkflow.trace << :middle
|
826
|
+
activity.run_activity1
|
827
|
+
|
828
|
+
end
|
829
|
+
end
|
830
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
831
|
+
domain = FakeDomain.new(workflow_type_object)
|
832
|
+
|
833
|
+
swf_client = FakeServiceClient.new
|
834
|
+
task_list = "BadWorkflow_tasklist"
|
835
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
836
|
+
worker.add_workflow_implementation(BadWorkflow)
|
837
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
838
|
+
options.workflow_name = "BadWorkflow"
|
839
|
+
options.execution_start_to_close_timeout = 3600
|
840
|
+
options.task_list = task_list
|
841
|
+
options.task_start_to_close_timeout = 30
|
842
|
+
options.child_policy = :request_cancel
|
843
|
+
end
|
844
|
+
my_workflow = my_workflow_factory.get_client
|
845
|
+
workflow_execution = my_workflow.start_execution
|
846
|
+
worker.start
|
847
|
+
|
848
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
849
|
+
"StartTimer"
|
850
|
+
BadWorkflow.trace.should == [:start]
|
851
|
+
end
|
852
|
+
it "makes sure that multiple schedules followed by a timeout work" do
|
853
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
854
|
+
def get_decision_tasks
|
855
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
856
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
857
|
+
[
|
858
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
859
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
860
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
861
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
|
862
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
|
863
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 6, {:activity_id => "Activity2"}),
|
864
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 7, {:activity_id => "Activity3"}),
|
865
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 8, {:activity_id => "Activity4"}),
|
866
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 9, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
|
867
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 10, {:scheduled_event_id => 6, :timeout_type => "START_TO_CLOSE"}),
|
868
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 11, {:scheduled_event_id => 7, :timeout_type => "START_TO_CLOSE"}),
|
869
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 12, {:scheduled_event_id => 8, :timeout_type => "START_TO_CLOSE"}),
|
870
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 13, {}),
|
871
|
+
TestHistoryEvent.new("DecisionTaskStarted", 14, {}),
|
872
|
+
|
873
|
+
])
|
874
|
+
end
|
875
|
+
end
|
876
|
+
class BadWorkflow
|
877
|
+
class << self
|
878
|
+
attr_accessor :task_list, :trace
|
879
|
+
end
|
880
|
+
@trace = []
|
881
|
+
extend Decider
|
882
|
+
version "1"
|
883
|
+
entry_point :entry_point
|
884
|
+
activity_client :activity do |options|
|
885
|
+
options.prefix_name = "BadActivity"
|
886
|
+
options.version = "1"
|
887
|
+
options.default_task_heartbeat_timeout = "3600"
|
888
|
+
options.default_task_list = "BadWorkflow"
|
889
|
+
options.default_task_schedule_to_close_timeout = "30"
|
890
|
+
options.default_task_schedule_to_start_timeout = "30"
|
891
|
+
options.default_task_start_to_close_timeout = "30"
|
892
|
+
options.exponential_retry do |retry_options|
|
893
|
+
retry_options.maximum_attempts = 3
|
894
|
+
end
|
895
|
+
end
|
896
|
+
def entry_point
|
897
|
+
BadWorkflow.trace << :start
|
898
|
+
[:run_activity1, :run_activity2, :run_activity3, :run_activity4].each do |act|
|
899
|
+
activity.send_async(act)
|
900
|
+
end
|
901
|
+
BadWorkflow.trace << :middle
|
902
|
+
activity.run_activity3
|
903
|
+
BadWorkflow.trace << :end
|
904
|
+
end
|
905
|
+
end
|
906
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
907
|
+
domain = FakeDomain.new(workflow_type_object)
|
908
|
+
|
909
|
+
swf_client = FakeServiceClient.new
|
910
|
+
task_list = "BadWorkflow_tasklist"
|
911
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
912
|
+
worker.add_workflow_implementation(BadWorkflow)
|
913
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
914
|
+
options.workflow_name = "BadWorkflow"
|
915
|
+
options.execution_start_to_close_timeout = 3600
|
916
|
+
options.task_list = task_list
|
917
|
+
options.task_start_to_close_timeout = 30
|
918
|
+
options.child_policy = :request_cancel
|
919
|
+
end
|
920
|
+
my_workflow = my_workflow_factory.get_client
|
921
|
+
workflow_execution = my_workflow.start_execution
|
922
|
+
worker.start
|
923
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
924
|
+
"StartTimer"
|
925
|
+
swf_client.trace.first[:decisions].length.should == 4
|
926
|
+
BadWorkflow.trace.should == [:start, :middle]
|
927
|
+
end
|
928
|
+
|
929
|
+
it "makes sure that timeout followed by success is handled correctly" do
|
930
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
931
|
+
def get_decision_tasks
|
932
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
933
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
934
|
+
[
|
935
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
936
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
937
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
938
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
|
939
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
|
940
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 6, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
|
941
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 7, {}),
|
942
|
+
TestHistoryEvent.new("DecisionTaskStarted", 8, {}),
|
943
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 10, {}),
|
944
|
+
TestHistoryEvent.new("TimerStarted", 11, {:decision_task_completed_event_id => 10, :timer_id => "Timer1", :start_to_fire_timeout => 1}),
|
945
|
+
TestHistoryEvent.new("TimerFired", 12, {:timer_id => "Timer1", :started_event_id => 11}),
|
946
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 13, {}),
|
947
|
+
TestHistoryEvent.new("DecisionTaskStarted", 14, {}),
|
948
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 15, {}),
|
949
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 16, {:activity_id => "Activity2"}),
|
950
|
+
TestHistoryEvent.new("ActivityTaskCompleted", 17, {:scheduled_event_id => 16 }),
|
951
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 18, {}),
|
952
|
+
TestHistoryEvent.new("DecisionTaskStarted", 19, {}),
|
953
|
+
|
954
|
+
])
|
955
|
+
end
|
956
|
+
end
|
957
|
+
class BadWorkflow
|
958
|
+
class << self
|
959
|
+
attr_accessor :task_list, :trace
|
960
|
+
end
|
961
|
+
@trace = []
|
962
|
+
extend Decider
|
963
|
+
version "1"
|
964
|
+
entry_point :entry_point
|
965
|
+
activity_client :activity do |options|
|
966
|
+
options.prefix_name = "BadActivity"
|
967
|
+
options.version = "1"
|
968
|
+
options.default_task_heartbeat_timeout = "3600"
|
969
|
+
options.default_task_list = "BadWorkflow"
|
970
|
+
options.default_task_schedule_to_close_timeout = "30"
|
971
|
+
options.default_task_schedule_to_start_timeout = "30"
|
972
|
+
options.default_task_start_to_close_timeout = "30"
|
973
|
+
options.exponential_retry do |retry_options|
|
974
|
+
retry_options.maximum_attempts = 3
|
975
|
+
end
|
976
|
+
end
|
977
|
+
def entry_point
|
978
|
+
BadWorkflow.trace << :start
|
979
|
+
activity.run_activity1
|
980
|
+
BadWorkflow.trace << :middle
|
981
|
+
|
982
|
+
BadWorkflow.trace << :end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
986
|
+
domain = FakeDomain.new(workflow_type_object)
|
987
|
+
|
988
|
+
swf_client = FakeServiceClient.new
|
989
|
+
task_list = "BadWorkflow_tasklist"
|
990
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
991
|
+
worker.add_workflow_implementation(BadWorkflow)
|
992
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
993
|
+
options.workflow_name = "BadWorkflow"
|
994
|
+
options.execution_start_to_close_timeout = 3600
|
995
|
+
options.task_list = task_list
|
996
|
+
options.task_start_to_close_timeout = 30
|
997
|
+
options.child_policy = :request_cancel
|
998
|
+
end
|
999
|
+
my_workflow = my_workflow_factory.get_client
|
1000
|
+
workflow_execution = my_workflow.start_execution
|
1001
|
+
worker.start
|
1002
|
+
|
1003
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
1004
|
+
"CompleteWorkflowExecution"
|
1005
|
+
BadWorkflow.trace.should == [:start, :middle, :end]
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
it "makes sure that signal works correctly" do
|
1009
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
1010
|
+
def get_decision_tasks
|
1011
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
1012
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
1013
|
+
[
|
1014
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
1015
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
1016
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
1017
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
|
1018
|
+
TestHistoryEvent.new("WorkflowExecutionSignaled", 5, {:signal_name => "this_signal"}),
|
1019
|
+
])
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
class BadWorkflow
|
1023
|
+
class << self
|
1024
|
+
attr_accessor :task_list, :trace
|
1025
|
+
end
|
1026
|
+
@trace = []
|
1027
|
+
extend Decider
|
1028
|
+
version "1"
|
1029
|
+
entry_point :entry_point
|
1030
|
+
activity_client :activity do |options|
|
1031
|
+
options.prefix_name = "BadActivity"
|
1032
|
+
options.version = "1"
|
1033
|
+
options.default_task_heartbeat_timeout = "3600"
|
1034
|
+
options.default_task_list = "BadWorkflow"
|
1035
|
+
options.default_task_schedule_to_close_timeout = "30"
|
1036
|
+
options.default_task_schedule_to_start_timeout = "30"
|
1037
|
+
options.default_task_start_to_close_timeout = "30"
|
1038
|
+
options.exponential_retry do |retry_options|
|
1039
|
+
retry_options.maximum_attempts = 3
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
def this_signal
|
1043
|
+
@wait.broadcast
|
1044
|
+
end
|
1045
|
+
signal :this_signal
|
1046
|
+
def entry_point
|
1047
|
+
BadWorkflow.trace << :start
|
1048
|
+
@wait ||= FiberConditionVariable.new
|
1049
|
+
@wait.wait
|
1050
|
+
BadWorkflow.trace << :middle
|
1051
|
+
BadWorkflow.trace << :end
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
1055
|
+
domain = FakeDomain.new(workflow_type_object)
|
1056
|
+
|
1057
|
+
swf_client = FakeServiceClient.new
|
1058
|
+
task_list = "BadWorkflow_tasklist"
|
1059
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
1060
|
+
worker.add_workflow_implementation(BadWorkflow)
|
1061
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
1062
|
+
options.workflow_name = "BadWorkflow"
|
1063
|
+
options.execution_start_to_close_timeout = 3600
|
1064
|
+
options.task_list = task_list
|
1065
|
+
end
|
1066
|
+
my_workflow = my_workflow_factory.get_client
|
1067
|
+
workflow_execution = my_workflow.start_execution
|
1068
|
+
worker.start
|
1069
|
+
swf_client.trace.first[:decisions].first[:decision_type].should ==
|
1070
|
+
"CompleteWorkflowExecution"
|
1071
|
+
BadWorkflow.trace.should == [:start, :middle, :end]
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
it "makes sure that signal works correctly" do
|
1075
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
1076
|
+
def get_decision_tasks
|
1077
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
|
1078
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
1079
|
+
[
|
1080
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
1081
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
1082
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
1083
|
+
])
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
class BadWorkflow
|
1087
|
+
class << self
|
1088
|
+
attr_accessor :task_list, :trace
|
1089
|
+
end
|
1090
|
+
@trace = []
|
1091
|
+
extend Decider
|
1092
|
+
version "1"
|
1093
|
+
entry_point :entry_point
|
1094
|
+
def entry_point
|
1095
|
+
raise "This is an expected error"
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
workflow_type_object = double("workflow_type", :name => "BadWorkflow.entry_point", :start_execution => "" )
|
1099
|
+
domain = FakeDomain.new(workflow_type_object)
|
1100
|
+
|
1101
|
+
swf_client = FakeServiceClient.new
|
1102
|
+
|
1103
|
+
task_list = "BadWorkflow_tasklist"
|
1104
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list, BadWorkflow)
|
1105
|
+
|
1106
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
1107
|
+
options.workflow_name = "BadWorkflow"
|
1108
|
+
options.execution_start_to_close_timeout = 3600
|
1109
|
+
options.task_list = task_list
|
1110
|
+
end
|
1111
|
+
my_workflow = my_workflow_factory.get_client
|
1112
|
+
workflow_execution = my_workflow.start_execution
|
1113
|
+
worker.start
|
1114
|
+
swf_client.trace.first[:decisions].first[:fail_workflow_execution_decision_attributes][:details].should =~ /This is an expected error/
|
1115
|
+
end
|
1116
|
+
it "makes sure that you can do retry with the easier Fixnum semantic"do
|
1117
|
+
|
1118
|
+
class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
|
1119
|
+
def get_decision_tasks
|
1120
|
+
fake_workflow_type = FakeWorkflowType.new(nil, "FixnumWorkflow.entry_point", "1")
|
1121
|
+
TestHistoryWrapper.new(fake_workflow_type,
|
1122
|
+
[
|
1123
|
+
TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
|
1124
|
+
TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
|
1125
|
+
TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
|
1126
|
+
TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
|
1127
|
+
TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
|
1128
|
+
TestHistoryEvent.new("ActivityTaskStarted", 6, {}),
|
1129
|
+
TestHistoryEvent.new("ActivityTaskTimedOut", 7, {:scheduled_event_id => 5, :timeout_type => "START_TO_CLOSE"}),
|
1130
|
+
])
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
workflow_type_object = double("workflow_type", :name => "FixnumWorkflow.entry_point", :start_execution => "" )
|
1134
|
+
domain = FakeDomain.new(workflow_type_object)
|
1135
|
+
|
1136
|
+
class FixnumActivity
|
1137
|
+
extend Activity
|
1138
|
+
activity :run_activity1
|
1139
|
+
def run_activity1; raise StandardError; end
|
1140
|
+
end
|
1141
|
+
class FixnumWorkflow
|
1142
|
+
extend Workflows
|
1143
|
+
workflow(:entry_point) { {:version => "1"} }
|
1144
|
+
activity_client(:activity) { {:version => "1", :prefix_name => "FixnumActivity" } }
|
1145
|
+
def entry_point
|
1146
|
+
|
1147
|
+
activity.retry(:run_activity1, 5) {{:maximum_attempts => 5, :should_jitter => false}}
|
1148
|
+
end
|
1149
|
+
end
|
1150
|
+
swf_client = FakeServiceClient.new
|
1151
|
+
task_list = "FixnumWorkflow_tasklist"
|
1152
|
+
my_workflow_factory = workflow_factory(swf_client, domain) do |options|
|
1153
|
+
options.workflow_name = "FixnumWorkflow"
|
1154
|
+
options.execution_start_to_close_timeout = 3600
|
1155
|
+
options.task_list = task_list
|
1156
|
+
end
|
1157
|
+
worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
|
1158
|
+
worker.add_workflow_implementation(FixnumWorkflow)
|
1159
|
+
my_workflow = my_workflow_factory.get_client
|
1160
|
+
workflow_execution = my_workflow.start_execution
|
1161
|
+
worker.start
|
1162
|
+
swf_client.trace.first[:decisions].first[:start_timer_decision_attributes][:start_to_fire_timeout].should == "5"
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
end
|
1166
|
+
describe "Misc tests" do
|
1167
|
+
it "makes sure that Workflows is equivalent to Decider" do
|
1168
|
+
class TestDecider
|
1169
|
+
extend Workflows
|
1170
|
+
end
|
1171
|
+
TestDecider.methods.map(&:to_sym).should include :signal
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
describe FlowConstants do
|
1176
|
+
|
1177
|
+
it "will test the default retry function with regular cases" do
|
1178
|
+
test_first = [Time.now, Time.now, Time.now]
|
1179
|
+
test_time_of_failure = [0, 10, 100]
|
1180
|
+
test_attempts = [{}, {Exception=>1}, {ActivityTaskTimedOutException=>5, Exception=>2}]
|
1181
|
+
test_output = [0, 1, 64]
|
1182
|
+
arr = test_first.zip(test_time_of_failure, test_attempts, test_output)
|
1183
|
+
arr.each do |first, time_of_failure, attempts, output|
|
1184
|
+
result = FlowConstants.exponential_retry_function.call(first, time_of_failure, attempts)
|
1185
|
+
(result == output).should == true
|
1186
|
+
end
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
it "will test for exceptions" do
|
1190
|
+
expect { FlowConstants.exponential_retry_function.call(-1, 1, {}) }.to raise_error(ArgumentError, "first is not an instance of Time")
|
1191
|
+
expect { FlowConstants.exponential_retry_function.call(Time.now, -1, {}) }.to raise_error(ArgumentError, "time_of_failure can't be negative")
|
1192
|
+
expect { FlowConstants.exponential_retry_function.call(Time.now, 1, {Exception=>-1}) }.to raise_error(ArgumentError, "number of attempts can't be negative")
|
1193
|
+
expect { FlowConstants.exponential_retry_function.call(Time.now, 1, {Exception=>-1, ActivityTaskTimedOutException=>-10}) }.to raise_error(ArgumentError, "number of attempts can't be negative")
|
1194
|
+
expect { FlowConstants.exponential_retry_function.call(Time.now, 1, {Exception=>2, ActivityTaskTimedOutException=>-10}) }.to raise_error(ArgumentError, "number of attempts can't be negative")
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
|
1200
|
+
|
1201
|
+
describe "testing changing default values in RetryOptions and RetryPolicy" do
|
1202
|
+
|
1203
|
+
it "will test exponential retry with a new retry function" do
|
1204
|
+
my_retry_func = lambda do |first, time_of_failure, attempts|
|
1205
|
+
10
|
1206
|
+
end
|
1207
|
+
options = {
|
1208
|
+
:should_jitter => false
|
1209
|
+
}
|
1210
|
+
retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
|
1211
|
+
result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
|
1212
|
+
result.should == 10
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
it "will test the jitter function" do
|
1216
|
+
my_retry_func = lambda do |first, time_of_failure, attempts|
|
1217
|
+
10
|
1218
|
+
end
|
1219
|
+
options = {
|
1220
|
+
:should_jitter => true
|
1221
|
+
}
|
1222
|
+
retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options, true))
|
1223
|
+
result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception, 1)
|
1224
|
+
result.should >= 10 && result.should < 15
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
|
1228
|
+
it "will test whether we get the same jitter for a particular execution id" do
|
1229
|
+
|
1230
|
+
(FlowConstants.jitter_function.call(1, 100)).should equal(FlowConstants.jitter_function.call(1, 100))
|
1231
|
+
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
it "will test the default exceptions included for retry" do
|
1235
|
+
options = RetryOptions.new
|
1236
|
+
options.exceptions_to_include.should include Exception
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
it "will test the default exceptions included for retry" do
|
1240
|
+
my_retry_func = lambda do |first, time_of_failure, attempts|
|
1241
|
+
10
|
1242
|
+
end
|
1243
|
+
options = {
|
1244
|
+
:exceptions_to_include => [ActivityTaskTimedOutException],
|
1245
|
+
:exceptions_to_exclude => [ActivityTaskFailedException]
|
1246
|
+
}
|
1247
|
+
retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
|
1248
|
+
result = retry_policy.isRetryable(ActivityTaskTimedOutException.new("a", "b", "c", "d"))
|
1249
|
+
result.should == true
|
1250
|
+
|
1251
|
+
result = retry_policy.isRetryable(ActivityTaskFailedException.new("a", "b", "c", RuntimeError.new))
|
1252
|
+
result.should == false
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
|
1256
|
+
it "will make sure exception is raised if the called exception is there in both included and excluded exceptions" do
|
1257
|
+
my_retry_func = lambda do |first, time_of_failure, attempts|
|
1258
|
+
10
|
1259
|
+
end
|
1260
|
+
options = {
|
1261
|
+
:exceptions_to_include => [ActivityTaskFailedException],
|
1262
|
+
:exceptions_to_exclude => [ActivityTaskFailedException]
|
1263
|
+
}
|
1264
|
+
retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
|
1265
|
+
expect {retry_policy.isRetryable(ActivityTaskFailedException.new("a", "b", "c", ActivityTaskFailedException))}.to raise_error
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
it "will test max_attempts" do
|
1269
|
+
my_retry_func = lambda do |first, time_of_failure, attempts|
|
1270
|
+
10
|
1271
|
+
end
|
1272
|
+
options = {
|
1273
|
+
:maximum_attempts => 5,
|
1274
|
+
:should_jitter => false
|
1275
|
+
}
|
1276
|
+
retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
|
1277
|
+
result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception.new, 1)
|
1278
|
+
result.should == -1
|
1279
|
+
|
1280
|
+
result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>4}, Exception.new, 1)
|
1281
|
+
result.should == 10
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
it "will test retries_per_exception" do
|
1285
|
+
my_retry_func = lambda do |first, time_of_failure, attempts|
|
1286
|
+
10
|
1287
|
+
end
|
1288
|
+
options = {
|
1289
|
+
:retries_per_exception => {Exception => 5},
|
1290
|
+
:should_jitter => false
|
1291
|
+
}
|
1292
|
+
retry_policy = RetryPolicy.new(my_retry_func, RetryOptions.new(options))
|
1293
|
+
result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>10}, Exception.new, 1)
|
1294
|
+
result.should == -1
|
1295
|
+
|
1296
|
+
result = retry_policy.next_retry_delay_seconds(Time.now, 0, {Exception=>4}, Exception.new, 1)
|
1297
|
+
result.should == 10
|
1298
|
+
end
|
1299
|
+
end
|