aws-flow 1.0.5 → 1.0.6

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.
@@ -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