aws-flow 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -30,6 +30,14 @@ class TrivialConverter
30
30
  end
31
31
  end
32
32
 
33
+ class FakeLogger
34
+ attr_accessor :level
35
+ def info(s); end
36
+ def debug(s); end
37
+ def warn(s); end
38
+ def error(s); end
39
+ end
40
+
33
41
  class FakePage
34
42
  def initialize(object); @object = object; end
35
43
  def page; @object; end
@@ -1095,7 +1103,7 @@ describe "FakeHistory" do
1095
1103
  BadWorkflow.trace.should == [:start, :middle, :end]
1096
1104
  end
1097
1105
 
1098
- it "makes sure that signal works correctly" do
1106
+ it "makes sure that raising an error properly fails a workflow" do
1099
1107
  class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1100
1108
  def get_decision_tasks
1101
1109
  fake_workflow_type = FakeWorkflowType.new(nil, "BadWorkflow.entry_point", "1")
@@ -1185,6 +1193,85 @@ describe "FakeHistory" do
1185
1193
  worker.start
1186
1194
  swf_client.trace.first[:decisions].first[:start_timer_decision_attributes][:start_to_fire_timeout].should == "5"
1187
1195
  end
1196
+
1197
+ it "ensures that CompleteWorkflowExecutionFailed is correctly handled" do
1198
+ class FakeAttribute
1199
+ def initialize(data)
1200
+ @data = data
1201
+ end
1202
+ def method_missing(method_name, *args, &block)
1203
+ if @data.keys.include? method_name
1204
+ return @data[method_name]
1205
+ end
1206
+ super
1207
+ end
1208
+ end
1209
+
1210
+ class SynchronousWorkflowTaskPoller < WorkflowTaskPoller
1211
+ def get_decision_tasks
1212
+ fake_workflow_type = FakeWorkflowType.new(nil, "CompleteWorkflowExecutionFailedWorkflow.entry_point", "1")
1213
+ TestHistoryWrapper.new(fake_workflow_type,
1214
+ [
1215
+ TestHistoryEvent.new("WorkflowExecutionStarted", 1, {}),
1216
+ TestHistoryEvent.new("DecisionTaskScheduled", 2, {}),
1217
+ TestHistoryEvent.new("DecisionTaskStarted", 3, {}),
1218
+ TestHistoryEvent.new("DecisionTaskCompleted", 4, {}),
1219
+ TestHistoryEvent.new("ActivityTaskScheduled", 5, {:activity_id => "Activity1"}),
1220
+ TestHistoryEvent.new("ActivityTaskScheduled", 6, {:activity_id => "Activity2"}),
1221
+ TestHistoryEvent.new("ActivityTaskStarted", 7, {}),
1222
+ TestHistoryEvent.new("ActivityTaskFailed", 8, {:scheduled_event_id => 5}),
1223
+ TestHistoryEvent.new("DecisionTaskScheduled", 9, {}),
1224
+ TestHistoryEvent.new("ActivityTaskStarted", 10, {}),
1225
+ TestHistoryEvent.new("ActivityTaskFailed", 11, {:scheduled_event_id => 6}),
1226
+ TestHistoryEvent.new("DecisionTaskStarted", 12, {}),
1227
+ TestHistoryEvent.new("DecisionTaskCompleted", 13, {}),
1228
+ TestHistoryEvent.new("RequestCancelActivityTaskFailed", 14, FakeAttribute.new({:activity_id => "Activity2"}) ) ,
1229
+ TestHistoryEvent.new("CompleteWorkflowExecutionFailed", 15, {}),
1230
+ TestHistoryEvent.new("DecisionTaskScheduled", 16, {}),
1231
+ ])
1232
+ end
1233
+ end
1234
+ workflow_type_object = double("workflow_type", :name => "CompleteWorkflowExecutionFailedWorkflow.entry_point", :start_execution => "" )
1235
+ domain = FakeDomain.new(workflow_type_object)
1236
+
1237
+ class CompleteWorkflowExecutionFailedActivity
1238
+ extend Activity
1239
+ activity :run_activity1
1240
+ def run_activity1; raise StandardError; end
1241
+ end
1242
+ class CompleteWorkflowExecutionFailedWorkflow
1243
+ extend Workflows
1244
+ workflow(:entry_point) { {:version => "1"} }
1245
+ activity_client(:activity) { {:version => "1", :prefix_name => "CompleteWorkflowExecutionFailedActivity" } }
1246
+ def entry_point
1247
+ child_futures = []
1248
+ error_handler do |t|
1249
+ t.begin do
1250
+ child_futures << activity.send_async(:run_activity1)
1251
+ child_futures << activity.send_async(:run_activity1)
1252
+ wait_for_all(child_futures)
1253
+ end
1254
+ t.rescue(Exception) do |error|
1255
+ end
1256
+ t.ensure do
1257
+ end
1258
+ end
1259
+ end
1260
+ end
1261
+ swf_client = FakeServiceClient.new
1262
+ task_list = "CompleteWorkflowExecutionFailedWorkflow_tasklist"
1263
+ my_workflow_factory = workflow_factory(swf_client, domain) do |options|
1264
+ options.workflow_name = "CompleteWorkflowExecutionFailedWorkflow"
1265
+ options.execution_start_to_close_timeout = 3600
1266
+ options.task_list = task_list
1267
+ end
1268
+ worker = SynchronousWorkflowWorker.new(swf_client, domain, task_list)
1269
+ worker.add_workflow_implementation(CompleteWorkflowExecutionFailedWorkflow)
1270
+ my_workflow = my_workflow_factory.get_client
1271
+ workflow_execution = my_workflow.start_execution
1272
+ worker.start
1273
+ swf_client.trace.first[:decisions].first[:decision_type].should == "CompleteWorkflowExecution"
1274
+ end
1188
1275
  end
1189
1276
 
1190
1277
  describe "Misc tests" do
@@ -1224,6 +1311,33 @@ describe "Misc tests" do
1224
1311
  end
1225
1312
 
1226
1313
 
1314
+ it "ensures that using send_async doesn't mutate the original hash" do
1315
+ class GenericClientTest < GenericClient
1316
+ def call_options(*args, &options)
1317
+ options.call
1318
+ end
1319
+ end
1320
+ # Instead of setting up the fiber, just pretend we're internal
1321
+ module Utilities
1322
+ class << self
1323
+ alias_method :old_is_external, :is_external
1324
+ def is_external
1325
+ return false
1326
+ end
1327
+ end
1328
+ end
1329
+ generic_client = GenericClientTest.new
1330
+ previous_hash = {:key => :value}
1331
+ previous_hash_copy = previous_hash.dup
1332
+ generic_client.send_async(:call_options) { previous_hash }
1333
+ # Put is_external back before we have a chance of failing
1334
+ module Utilities
1335
+ class << self
1336
+ alias_method :is_external, :old_is_external
1337
+ end
1338
+ end
1339
+ previous_hash.should == previous_hash_copy
1340
+ end
1227
1341
  end
1228
1342
 
1229
1343
  describe FlowConstants do
@@ -1280,40 +1394,43 @@ class TestActivityWorker < ActivityWorker
1280
1394
 
1281
1395
  attr_accessor :executor
1282
1396
  def initialize(service, domain, task_list, forking_executor, *args, &block)
1283
- super(service, domain, task_list, *args)
1397
+ super(service, domain, task_list, *args, &block)
1284
1398
  @executor = forking_executor
1285
1399
  end
1286
1400
  end
1287
1401
 
1288
1402
  describe ActivityWorker do
1289
1403
 
1290
- it "will test whether the ActivityWorker shuts down cleanly when an interrupt is received" do
1291
-
1292
- task_list = "TestWorkflow_tasklist"
1293
- service = FakeServiceClient.new
1294
- workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1295
- domain = FakeDomain.new(workflow_type_object)
1296
- forking_executor = ForkingExecutor.new
1297
- activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor)
1298
-
1299
- activity_worker.add_activities_implementation(TestActivity)
1300
- # Starts the activity worker in a forked process. Also, attaches an at_exit handler to the process. When the process
1301
- # exits, the handler checks whether the executor's internal is_shutdown variable is set correctly or not.
1302
- pid = fork do
1303
- at_exit {
1304
- activity_worker.executor.is_shutdown.should == true
1305
- }
1306
- activity_worker.start true
1307
- end
1308
- # Adding a sleep to let things get setup correctly (not ideal but going with this for now)
1309
- sleep 1
1310
- # Send an interrupt to the child process
1311
- Process.kill("INT", pid)
1312
- status = Process.waitall
1313
- status[0][1].success?.should be_true
1314
- end
1315
-
1316
- # This method will take a long time to run, allowing us to test our shutdown scenarios
1404
+ # it "will test whether the ActivityWorker shuts down cleanly when an interrupt is received" do
1405
+
1406
+ # task_list = "TestWorkflow_tasklist"
1407
+ # service = FakeServiceClient.new
1408
+ # workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1409
+ # domain = FakeDomain.new(workflow_type_object)
1410
+ # forking_executor = ForkingExecutor.new
1411
+ # activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor) { {:logger => FakeLogger.new} }
1412
+
1413
+ # activity_worker.add_activities_implementation(TestActivity)
1414
+ # # Starts the activity worker in a forked process. Also, attaches an at_exit
1415
+ # # handler to the process. When the process exits, the handler checks whether
1416
+ # # the executor's internal is_shutdown variable is set correctly or not.
1417
+ # pid = fork do
1418
+ # at_exit {
1419
+ # activity_worker.executor.is_shutdown.should == true
1420
+ # }
1421
+ # activity_worker.start true
1422
+ # end
1423
+ # # Adding a sleep to let things get setup correctly (not ideal but going with
1424
+ # # this for now)
1425
+ # #sleep 1
1426
+ # # Send an interrupt to the child process
1427
+ # Process.kill("INT", pid)
1428
+ # status = Process.waitall
1429
+ # status[0][1].success?.should be_true
1430
+ # end
1431
+
1432
+ # This method will take a long time to run, allowing us to test our shutdown
1433
+ # scenarios
1317
1434
  def dumb_fib(n)
1318
1435
  n < 1 ? 1 : dumb_fib(n - 1) + dumb_fib(n - 2)
1319
1436
  end
@@ -1324,20 +1441,23 @@ describe ActivityWorker do
1324
1441
  workflow_type_object = double("workflow_type", :name => "TestWorkflow.entry_point", :start_execution => "" )
1325
1442
  domain = FakeDomain.new(workflow_type_object)
1326
1443
  forking_executor = ForkingExecutor.new
1327
- activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor)
1444
+ activity_worker = TestActivityWorker.new(service, domain, task_list, forking_executor) { {:logger => FakeLogger.new} }
1328
1445
 
1329
1446
  activity_worker.add_activities_implementation(TestActivity)
1330
- # Starts the activity worker in a forked process. Also, executes a task using the forking executor of the activity
1331
- # worker. The executor will create a child process to run that task. The task (dumb_fib) is purposefully designed to
1332
- # be long running so that we can test our shutdown scenario.
1447
+ # Starts the activity worker in a forked process. Also, executes a task
1448
+ # using the forking executor of the activity worker. The executor will
1449
+ # create a child process to run that task. The task (dumb_fib) is
1450
+ # purposefully designed to be long running so that we can test our shutdown
1451
+ # scenario.
1333
1452
  pid = fork do
1334
1453
  activity_worker.executor.execute {
1335
1454
  dumb_fib(1000)
1336
1455
  }
1337
1456
  activity_worker.start true
1338
1457
  end
1339
- # Adding a sleep to let things get setup correctly (not idea but going with this for now)
1340
- sleep 2
1458
+ # Adding a sleep to let things get setup correctly (not idea but going with
1459
+ # this for now)
1460
+ sleep 3
1341
1461
  # Send 2 interrupts to the child process
1342
1462
  2.times { Process.kill("INT", pid); sleep 3 }
1343
1463
  status = Process.waitall
@@ -0,0 +1,197 @@
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
+ describe "ExternalTask#alive?" do
17
+ let(:scope) { AsyncScope.new }
18
+ let(:simple_unit) do
19
+ ExternalTask.new(:parent => scope ) do |t|
20
+ t.initiate_task { |h| 2 + 2; h.complete }
21
+ end
22
+ end
23
+ it "tells you that ExternalTask is alive before the ExternalTask starts" do
24
+ simple_unit.alive?.should == true
25
+ end
26
+ it "tells you that ExternalTask is not alive once ExternalTask has exited" do
27
+ task_context = TaskContext.new(:parent => scope.get_closest_containing_scope, :task => simple_unit)
28
+ simple_unit.resume
29
+ simple_unit.alive?.should == false
30
+ end
31
+ end
32
+
33
+ describe ExternalTask do
34
+ let(:trace) { [] }
35
+
36
+ before(:each) do
37
+
38
+ class ExternalTaskException < Exception; end
39
+ @this_scope = AsyncScope.new() do
40
+ end
41
+ @this_task = ExternalTask.new(:parent => @this_scope) do |t|
42
+ t.cancellation_handler { |h, cause| h.fail(cause) }
43
+ t.initiate_task { |h| trace << :task_started; h.complete }
44
+ end
45
+ task_context = TaskContext.new(:parent => @this_scope.get_closest_containing_scope, :task => @this_task)
46
+ @this_scope << @this_task
47
+ end
48
+
49
+ it "should complete okay" do
50
+
51
+ @this_scope.eventLoop
52
+ trace.should == [:task_started]
53
+ end
54
+
55
+ it "cancelling should cause an exception to be raised" do
56
+ @this_task.cancel(ExternalTaskException.new)
57
+ expect { @this_scope.eventLoop }.to raise_error ExternalTaskException
58
+ end
59
+
60
+ it "shouldn't cancel an already completed task" do
61
+ @this_scope.eventLoop
62
+ @this_task.cancel(Exception)
63
+ end
64
+
65
+ describe "cancelling an external task without a cancellation handler" do
66
+ before(:each) do
67
+ @this_scope = AsyncScope.new() do
68
+
69
+ end
70
+ @this_task = ExternalTask.new() do |t|
71
+ t.initiate_task { |h| trace << :task_started; h.complete }
72
+ end
73
+ @this_scope << @this_task
74
+ end
75
+ it "ensures that calling cancel results in no error" do
76
+ task_context = TaskContext.new(:parent => @this_scope.get_closest_containing_scope, :task => @this_task)
77
+ @this_task.cancel(ExternalTaskException)
78
+ end
79
+ it "ensures that attempting to run a cancelled task has no effect" do
80
+ task_context = TaskContext.new(:parent => @this_scope.get_closest_containing_scope, :task => @this_task)
81
+ @this_task.cancel(ExternalTaskException)
82
+ @this_scope.eventLoop
83
+ trace.should == []
84
+ end
85
+ end
86
+
87
+ it "ensures that raising in the cancellation handler is handled " do
88
+ scope = AsyncScope.new
89
+ external_task = ExternalTask.new() do |t|
90
+ t.initiate_task { |h| h.complete }
91
+ t.cancellation_handler { |h, cause| raise "Oh no!" }
92
+ end
93
+ scope << external_task
94
+ task_context = TaskContext.new(:parent => scope.get_closest_containing_scope, :task => external_task)
95
+ external_task.cancel(Exception)
96
+ expect { scope.eventLoop }.to raise_error /Oh no!/
97
+ end
98
+
99
+ it "ensures that cancelling a completed task is handled" do
100
+ @this_scope.eventLoop
101
+ @this_task.cancel(Exception)
102
+ end
103
+
104
+ it "ensures that cancelling a cancelled task is handled" do
105
+ @this_scope.eventLoop
106
+ @this_task.cancel(Exception)
107
+ @this_task.cancel(Exception)
108
+ end
109
+
110
+ it "ensures that failing a task after completion raises an error" do
111
+ @this_scope = AsyncScope.new() do
112
+ end
113
+ @this_task = ExternalTask.new() do |t|
114
+ t.cancellation_handler { |h| h.fail }
115
+ t.initiate_task { |h| trace << :task_started; h.complete; h.fail(Exception) }
116
+ end
117
+ task_context = TaskContext.new(:parent => @this_scope.get_closest_containing_scope, :task => @this_task)
118
+ @this_scope << @this_task
119
+ expect { @this_scope.eventLoop }.to raise_error /Already completed/
120
+ end
121
+
122
+ it "ensures that completing an external_task allows the asyncScope to move forward" do
123
+ @handle = nil
124
+ @this_scope = AsyncScope.new() do
125
+ external_task do |t|
126
+ t.cancellation_handler {|h| h.fail}
127
+ t.initiate_task {|h| @handle = h }
128
+ end
129
+ end
130
+ @this_scope.eventLoop
131
+ @this_scope.is_complete?.should == false
132
+ @handle.complete
133
+ @this_scope.eventLoop
134
+ @this_scope.is_complete?.should == true
135
+ end
136
+ end
137
+
138
+ describe "external_task function" do
139
+
140
+ it "ensures that cancelling will raise the externalTasks cancellation handler" do
141
+ trace = []
142
+ this_scope = AsyncScope.new() do
143
+ trace << :async_start
144
+ external_task do |t|
145
+ t.cancellation_handler {|h, cause| trace << :cancellation_handler; h.complete }
146
+ t.initiate_task { |h| trace << :external_task}
147
+ end
148
+ task { trace << :task; raise IllegalStateException }
149
+ trace << :async_done
150
+ end
151
+ expect { this_scope.eventLoop }.to raise_error IllegalStateException
152
+ trace.should == [:async_start, :async_done, :external_task, :task, :cancellation_handler]
153
+ end
154
+
155
+ it "tests explicit cancellation of BRE cancelling externalTask correctly" do
156
+ handle_future = Future.new
157
+ trace = []
158
+
159
+ scope = AsyncScope.new do
160
+ trace << :async_start
161
+ bre = error_handler do |t|
162
+ t.begin do
163
+ external_task do |t|
164
+ trace << :external_task;
165
+ t.cancellation_handler {|h, cause| trace << :cancellation_handler; h.complete}
166
+ t.initiate_task { |h| handle_future.set(h) }
167
+ end
168
+
169
+ task do
170
+ trace << :in_the_cancel_task
171
+ handle_future.get
172
+ bre.cancel(nil)
173
+ end
174
+ end
175
+ t.rescue(Exception) { |e| e.class.should <= CancellationException }
176
+ end
177
+
178
+ trace << :async_done
179
+ end
180
+ scope.eventLoop
181
+ trace.should == [:async_start, :async_done, :external_task, :in_the_cancel_task, :cancellation_handler]
182
+ end
183
+
184
+ it "ensures that having an external_task within an AsyncScope works" do
185
+ trace = []
186
+ this_scope = AsyncScope.new() do
187
+ external_task do |t|
188
+ t.cancellation_handler { |h| h.fail }
189
+ t.initiate_task { |h| trace << :task_started; h.complete; }
190
+ end
191
+ end
192
+ trace.should == []
193
+ this_scope.eventLoop
194
+ trace.should == [:task_started]
195
+ end
196
+
197
+ end
@@ -13,7 +13,36 @@
13
13
  # permissions and limitations under the License.
14
14
  ##
15
15
 
16
- require 'rubygems'
16
+ require 'aws/flow'
17
+ include AWS::Flow::Core
18
+
19
+
20
+ class FlowFactory
21
+
22
+ attr_accessor :async_scope
23
+ def generate_BRE(options = {})
24
+ scope = generate_scope
25
+ bre = BeginRescueEnsure.new(:parent => scope.root_context)
26
+ begin_statement = options[:begin] ? options[:begin] : lambda {}
27
+ rescue_statement = options[:rescue] ? options[:rescue] : lambda {|x| }
28
+ rescue_exception = options[:rescue_exceptions] ? options[:rescue_exceptions] : StandardError
29
+ ensure_statement = options[:ensure] ? options[:ensure] : lambda {}
30
+ bre.begin begin_statement
31
+ bre.rescue(rescue_exception, rescue_statement)
32
+ bre.ensure ensure_statement
33
+ scope << bre
34
+ bre
35
+ end
36
+
37
+ def generate_scope(options = {})
38
+ lambda = options[:lambda] || lambda {}
39
+ @async_scope ||= AsyncScope.new do
40
+ lambda.call
41
+ end
42
+ return @async_scope
43
+ end
44
+
45
+ end
17
46
 
18
47
  class WorkflowGenerator
19
48
  class << self