azure-functions-durable 1.2.8__py3-none-any.whl → 1.2.10__py3-none-any.whl
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.
- azure/durable_functions/__init__.py +81 -81
- azure/durable_functions/constants.py +9 -9
- azure/durable_functions/decorators/__init__.py +3 -3
- azure/durable_functions/decorators/durable_app.py +249 -249
- azure/durable_functions/decorators/metadata.py +109 -109
- azure/durable_functions/entity.py +125 -125
- azure/durable_functions/models/DurableEntityContext.py +201 -201
- azure/durable_functions/models/DurableHttpRequest.py +58 -58
- azure/durable_functions/models/DurableOrchestrationBindings.py +66 -66
- azure/durable_functions/models/DurableOrchestrationClient.py +781 -711
- azure/durable_functions/models/DurableOrchestrationContext.py +722 -707
- azure/durable_functions/models/DurableOrchestrationStatus.py +156 -156
- azure/durable_functions/models/EntityStateResponse.py +23 -23
- azure/durable_functions/models/FunctionContext.py +7 -7
- azure/durable_functions/models/OrchestrationRuntimeStatus.py +32 -29
- azure/durable_functions/models/OrchestratorState.py +117 -116
- azure/durable_functions/models/PurgeHistoryResult.py +33 -33
- azure/durable_functions/models/ReplaySchema.py +8 -8
- azure/durable_functions/models/RetryOptions.py +69 -69
- azure/durable_functions/models/RpcManagementOptions.py +86 -86
- azure/durable_functions/models/Task.py +426 -426
- azure/durable_functions/models/TaskOrchestrationExecutor.py +346 -333
- azure/durable_functions/models/TokenSource.py +56 -56
- azure/durable_functions/models/__init__.py +24 -24
- azure/durable_functions/models/actions/Action.py +23 -23
- azure/durable_functions/models/actions/ActionType.py +18 -18
- azure/durable_functions/models/actions/CallActivityAction.py +41 -41
- azure/durable_functions/models/actions/CallActivityWithRetryAction.py +45 -45
- azure/durable_functions/models/actions/CallEntityAction.py +46 -46
- azure/durable_functions/models/actions/CallHttpAction.py +35 -35
- azure/durable_functions/models/actions/CallSubOrchestratorAction.py +40 -40
- azure/durable_functions/models/actions/CallSubOrchestratorWithRetryAction.py +44 -44
- azure/durable_functions/models/actions/CompoundAction.py +35 -35
- azure/durable_functions/models/actions/ContinueAsNewAction.py +36 -36
- azure/durable_functions/models/actions/CreateTimerAction.py +48 -48
- azure/durable_functions/models/actions/NoOpAction.py +35 -35
- azure/durable_functions/models/actions/SignalEntityAction.py +47 -47
- azure/durable_functions/models/actions/WaitForExternalEventAction.py +63 -63
- azure/durable_functions/models/actions/WhenAllAction.py +14 -14
- azure/durable_functions/models/actions/WhenAnyAction.py +14 -14
- azure/durable_functions/models/actions/__init__.py +24 -24
- azure/durable_functions/models/entities/EntityState.py +74 -74
- azure/durable_functions/models/entities/OperationResult.py +76 -76
- azure/durable_functions/models/entities/RequestMessage.py +53 -53
- azure/durable_functions/models/entities/ResponseMessage.py +48 -48
- azure/durable_functions/models/entities/Signal.py +62 -62
- azure/durable_functions/models/entities/__init__.py +17 -17
- azure/durable_functions/models/history/HistoryEvent.py +92 -92
- azure/durable_functions/models/history/HistoryEventType.py +27 -25
- azure/durable_functions/models/history/__init__.py +8 -8
- azure/durable_functions/models/utils/__init__.py +7 -7
- azure/durable_functions/models/utils/entity_utils.py +103 -91
- azure/durable_functions/models/utils/http_utils.py +69 -69
- azure/durable_functions/models/utils/json_utils.py +56 -56
- azure/durable_functions/orchestrator.py +71 -71
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/LICENSE +21 -21
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/METADATA +58 -58
- azure_functions_durable-1.2.10.dist-info/RECORD +100 -0
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/WHEEL +1 -1
- tests/models/test_DecoratorMetadata.py +135 -135
- tests/models/test_Decorators.py +107 -107
- tests/models/test_DurableOrchestrationBindings.py +68 -56
- tests/models/test_DurableOrchestrationClient.py +730 -612
- tests/models/test_DurableOrchestrationContext.py +102 -102
- tests/models/test_DurableOrchestrationStatus.py +59 -59
- tests/models/test_OrchestrationState.py +28 -28
- tests/models/test_RpcManagementOptions.py +79 -79
- tests/models/test_TokenSource.py +10 -10
- tests/orchestrator/models/OrchestrationInstance.py +18 -18
- tests/orchestrator/orchestrator_test_utils.py +130 -130
- tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
- tests/orchestrator/test_call_http.py +235 -176
- tests/orchestrator/test_continue_as_new.py +67 -67
- tests/orchestrator/test_create_timer.py +126 -126
- tests/orchestrator/test_entity.py +395 -395
- tests/orchestrator/test_external_event.py +53 -53
- tests/orchestrator/test_fan_out_fan_in.py +175 -175
- tests/orchestrator/test_is_replaying_flag.py +101 -101
- tests/orchestrator/test_retries.py +308 -308
- tests/orchestrator/test_sequential_orchestrator.py +841 -801
- tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
- tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
- tests/orchestrator/test_serialization.py +30 -30
- tests/orchestrator/test_sub_orchestrator.py +95 -95
- tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
- tests/orchestrator/test_task_any.py +60 -60
- tests/tasks/tasks_test_utils.py +17 -17
- tests/tasks/test_new_uuid.py +34 -34
- tests/test_utils/ContextBuilder.py +174 -174
- tests/test_utils/EntityContextBuilder.py +56 -56
- tests/test_utils/constants.py +1 -1
- tests/test_utils/json_utils.py +30 -30
- tests/test_utils/testClasses.py +56 -56
- tests/utils/__init__.py +1 -0
- tests/utils/test_entity_utils.py +24 -0
- azure_functions_durable-1.2.8.data/data/_manifest/bsi.json +0 -1
- azure_functions_durable-1.2.8.data/data/_manifest/manifest.cat +0 -0
- azure_functions_durable-1.2.8.data/data/_manifest/manifest.spdx.json +0 -12845
- azure_functions_durable-1.2.8.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
- azure_functions_durable-1.2.8.dist-info/RECORD +0 -102
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/top_level.txt +0 -0
|
@@ -1,801 +1,841 @@
|
|
|
1
|
-
from azure.durable_functions.models.actions.WhenAnyAction import WhenAnyAction
|
|
2
|
-
from azure.durable_functions.models.actions.WhenAllAction import WhenAllAction
|
|
3
|
-
from azure.durable_functions.models.ReplaySchema import ReplaySchema
|
|
4
|
-
from datetime import datetime, timedelta
|
|
5
|
-
from .orchestrator_test_utils \
|
|
6
|
-
import assert_orchestration_state_equals, get_orchestration_state_result, assert_valid_schema
|
|
7
|
-
from tests.test_utils.ContextBuilder import ContextBuilder
|
|
8
|
-
from azure.durable_functions.models.OrchestratorState import OrchestratorState
|
|
9
|
-
from azure.durable_functions.models.actions.CallActivityAction \
|
|
10
|
-
import CallActivityAction
|
|
11
|
-
from tests.test_utils.testClasses import SerializableClass
|
|
12
|
-
import azure.durable_functions as df
|
|
13
|
-
import azure.functions as func
|
|
14
|
-
|
|
15
|
-
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
|
|
16
|
-
|
|
17
|
-
def generator_function(context):
|
|
18
|
-
outputs = []
|
|
19
|
-
|
|
20
|
-
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
21
|
-
task2 = yield context.call_activity("Hello", "Seattle")
|
|
22
|
-
task3 = yield context.call_activity("Hello", "London")
|
|
23
|
-
|
|
24
|
-
outputs.append(task1)
|
|
25
|
-
outputs.append(task2)
|
|
26
|
-
outputs.append(task3)
|
|
27
|
-
|
|
28
|
-
return outputs
|
|
29
|
-
|
|
30
|
-
@app.function_name("generator_function_with_pystein")
|
|
31
|
-
@app.orchestration_trigger(context_name="context")
|
|
32
|
-
def generator_function_with_pystein(context):
|
|
33
|
-
outputs = []
|
|
34
|
-
|
|
35
|
-
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
36
|
-
task2 = yield context.call_activity("Hello", "Seattle")
|
|
37
|
-
task3 = yield context.call_activity("Hello", "London")
|
|
38
|
-
|
|
39
|
-
outputs.append(task1)
|
|
40
|
-
outputs.append(task2)
|
|
41
|
-
outputs.append(task3)
|
|
42
|
-
|
|
43
|
-
return outputs
|
|
44
|
-
|
|
45
|
-
def generator_function_multi_yield_when_all(context):
|
|
46
|
-
outputs = []
|
|
47
|
-
|
|
48
|
-
task1 = context.call_activity("Hello", "Tokyo")
|
|
49
|
-
yield context.task_all([task1])
|
|
50
|
-
result = yield context.task_all([task1])
|
|
51
|
-
|
|
52
|
-
return result
|
|
53
|
-
|
|
54
|
-
def generator_function_is_replaying(context):
|
|
55
|
-
outputs = []
|
|
56
|
-
|
|
57
|
-
outputs.append(context.is_replaying)
|
|
58
|
-
yield context.call_activity("Hello", "Tokyo")
|
|
59
|
-
outputs.append(context.is_replaying)
|
|
60
|
-
yield context.call_activity("Hello", "Seattle")
|
|
61
|
-
outputs.append(context.is_replaying)
|
|
62
|
-
yield context.call_activity("Hello", "London")
|
|
63
|
-
return outputs
|
|
64
|
-
|
|
65
|
-
def generator_function_no_yield(context):
|
|
66
|
-
outputs = []
|
|
67
|
-
|
|
68
|
-
task1 = context.call_activity("Hello", "Tokyo")
|
|
69
|
-
task2 = context.call_activity("Hello", "Seattle")
|
|
70
|
-
task3 = yield context.call_activity("Hello", "London")
|
|
71
|
-
|
|
72
|
-
return task3
|
|
73
|
-
|
|
74
|
-
def generator_function_duplicate_yield(context):
|
|
75
|
-
task1 = context.call_activity("Hello", "Tokyo")
|
|
76
|
-
yield task1
|
|
77
|
-
yield task1
|
|
78
|
-
|
|
79
|
-
return ""
|
|
80
|
-
|
|
81
|
-
def generator_function_reducing_when_all(context):
|
|
82
|
-
task1 = context.call_activity("Hello", "Tokyo")
|
|
83
|
-
task2 = context.call_activity("Hello", "Seattle")
|
|
84
|
-
pending_tasks = [task1, task2]
|
|
85
|
-
|
|
86
|
-
# Yield until first task is completed
|
|
87
|
-
finished_task1 = yield context.task_any(pending_tasks)
|
|
88
|
-
|
|
89
|
-
# Remove completed task from pending tasks
|
|
90
|
-
pending_tasks.remove(finished_task1)
|
|
91
|
-
|
|
92
|
-
# Yield remaining task
|
|
93
|
-
yield context.task_any(pending_tasks)
|
|
94
|
-
|
|
95
|
-
# Ensure we can still schedule new tasks
|
|
96
|
-
yield context.call_activity("Hello", "London")
|
|
97
|
-
return ""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def generator_function_reuse_task_in_whenany(context):
|
|
101
|
-
task1 = context.call_activity("Hello", "Tokyo")
|
|
102
|
-
task2 = context.call_activity("Hello", "Seattle")
|
|
103
|
-
pending_tasks = [task1, task2]
|
|
104
|
-
|
|
105
|
-
# Yield until first task is completed
|
|
106
|
-
finished_task1 = yield context.task_any(pending_tasks)
|
|
107
|
-
|
|
108
|
-
# Remove completed task from pending tasks
|
|
109
|
-
pending_tasks.remove(finished_task1)
|
|
110
|
-
|
|
111
|
-
task3 = context.call_activity("Hello", "London")
|
|
112
|
-
tasks = pending_tasks + [task3]
|
|
113
|
-
|
|
114
|
-
# Yield remaining tasks
|
|
115
|
-
yield context.task_any(tasks)
|
|
116
|
-
return ""
|
|
117
|
-
|
|
118
|
-
def generator_function_compound_tasks(context):
|
|
119
|
-
yield context.call_activity("Hello", "Tokyo")
|
|
120
|
-
|
|
121
|
-
task1 = context.call_activity("Hello", "Tokyo")
|
|
122
|
-
task2 = context.call_activity("Hello", "Tokyo")
|
|
123
|
-
task3 = context.call_activity("Hello", "Tokyo")
|
|
124
|
-
task4 = context.task_any([task3])
|
|
125
|
-
task5 = context.task_all([task1, task2, task4])
|
|
126
|
-
task6 = context.task_any([task5])
|
|
127
|
-
yield task6
|
|
128
|
-
|
|
129
|
-
return ""
|
|
130
|
-
|
|
131
|
-
def generator_function_time_is_not_none(context):
|
|
132
|
-
outputs = []
|
|
133
|
-
|
|
134
|
-
now = context.current_utc_datetime
|
|
135
|
-
if not now:
|
|
136
|
-
raise Exception("No time! 1st attempt")
|
|
137
|
-
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
138
|
-
|
|
139
|
-
now = context.current_utc_datetime
|
|
140
|
-
if not now:
|
|
141
|
-
raise Exception("No time! 2nd attempt")
|
|
142
|
-
task2 = yield context.call_activity("Hello", "Seattle")
|
|
143
|
-
|
|
144
|
-
now = context.current_utc_datetime
|
|
145
|
-
if not now:
|
|
146
|
-
raise Exception("No time! 3rd attempt")
|
|
147
|
-
task3 = yield context.call_activity("Hello", "London")
|
|
148
|
-
|
|
149
|
-
now = context.current_utc_datetime
|
|
150
|
-
if not now:
|
|
151
|
-
raise Exception("No time! 4th attempt")
|
|
152
|
-
|
|
153
|
-
outputs.append(task1)
|
|
154
|
-
outputs.append(task2)
|
|
155
|
-
outputs.append(task3)
|
|
156
|
-
|
|
157
|
-
return outputs
|
|
158
|
-
|
|
159
|
-
def generator_function_time_gather(context):
|
|
160
|
-
outputs = []
|
|
161
|
-
|
|
162
|
-
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
163
|
-
yield context.call_activity("Hello", "Tokyo")
|
|
164
|
-
|
|
165
|
-
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
166
|
-
yield context.call_activity("Hello", "Seattle")
|
|
167
|
-
|
|
168
|
-
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
169
|
-
yield context.call_activity("Hello", "London")
|
|
170
|
-
|
|
171
|
-
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
172
|
-
return outputs
|
|
173
|
-
|
|
174
|
-
def generator_function_rasing_ex(context):
|
|
175
|
-
outputs = []
|
|
176
|
-
|
|
177
|
-
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
178
|
-
task2 = yield context.call_activity("Hello", "Seattle")
|
|
179
|
-
task3 = yield context.call_activity("Hello", "London")
|
|
180
|
-
|
|
181
|
-
outputs.append(task1)
|
|
182
|
-
outputs.append(task2)
|
|
183
|
-
outputs.append(task3)
|
|
184
|
-
|
|
185
|
-
raise ValueError("Oops!")
|
|
186
|
-
|
|
187
|
-
@app.orchestration_trigger(context_name="context")
|
|
188
|
-
def generator_function_rasing_ex_with_pystein(context):
|
|
189
|
-
outputs = []
|
|
190
|
-
|
|
191
|
-
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
192
|
-
task2 = yield context.call_activity("Hello", "Seattle")
|
|
193
|
-
task3 = yield context.call_activity("Hello", "London")
|
|
194
|
-
|
|
195
|
-
outputs.append(task1)
|
|
196
|
-
outputs.append(task2)
|
|
197
|
-
outputs.append(task3)
|
|
198
|
-
|
|
199
|
-
raise ValueError("Oops!")
|
|
200
|
-
|
|
201
|
-
def generator_function_with_serialization(context):
|
|
202
|
-
"""Ochestrator to test sequential activity calls with a serializable input arguments."""
|
|
203
|
-
outputs = []
|
|
204
|
-
|
|
205
|
-
task1 = yield context.call_activity("Hello", SerializableClass("Tokyo"))
|
|
206
|
-
task2 = yield context.call_activity("Hello", SerializableClass("Seattle"))
|
|
207
|
-
task3 = yield context.call_activity("Hello", SerializableClass("London"))
|
|
208
|
-
|
|
209
|
-
outputs.append(task1)
|
|
210
|
-
outputs.append(task2)
|
|
211
|
-
outputs.append(task3)
|
|
212
|
-
|
|
213
|
-
return outputs
|
|
214
|
-
|
|
215
|
-
def generator_function_new_guid(context):
|
|
216
|
-
"""Simple orchestrator that generates 3 GUIDs"""
|
|
217
|
-
outputs = []
|
|
218
|
-
|
|
219
|
-
output1 = context.new_guid()
|
|
220
|
-
output2 = context.new_guid()
|
|
221
|
-
output3 = context.new_guid()
|
|
222
|
-
|
|
223
|
-
outputs.append(str(output1))
|
|
224
|
-
outputs.append(str(output2))
|
|
225
|
-
outputs.append(str(output3))
|
|
226
|
-
return outputs
|
|
227
|
-
|
|
228
|
-
def generator_function_call_activity_with_name(context):
|
|
229
|
-
"""Simple orchestrator that call activity function with function name"""
|
|
230
|
-
outputs = []
|
|
231
|
-
|
|
232
|
-
task1 = yield context.call_activity(Hello, "Tokyo")
|
|
233
|
-
task2 = yield context.call_activity(Hello, "Seattle")
|
|
234
|
-
task3 = yield context.call_activity(Hello, "London")
|
|
235
|
-
|
|
236
|
-
outputs.append(task1)
|
|
237
|
-
outputs.append(task2)
|
|
238
|
-
outputs.append(task3)
|
|
239
|
-
|
|
240
|
-
return outputs
|
|
241
|
-
|
|
242
|
-
def generator_function_call_activity_with_callable(context):
|
|
243
|
-
outputs = []
|
|
244
|
-
|
|
245
|
-
task1 = yield context.call_activity(generator_function, "Tokyo")
|
|
246
|
-
|
|
247
|
-
outputs.append(task1)
|
|
248
|
-
|
|
249
|
-
return outputs
|
|
250
|
-
|
|
251
|
-
def generator_function_call_activity_with_orchestrator(context):
|
|
252
|
-
outputs = []
|
|
253
|
-
|
|
254
|
-
task1 = yield context.call_activity(generator_function_rasing_ex_with_pystein, "Tokyo")
|
|
255
|
-
|
|
256
|
-
outputs.append(task1)
|
|
257
|
-
|
|
258
|
-
return outputs
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
def
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
context_builder.
|
|
290
|
-
context_builder.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
context_builder
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
context_builder
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
assert
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
add_hello_completed_events(context_builder,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
expected_state = base_expected_state(
|
|
519
|
-
|
|
520
|
-
add_hello_action(expected_state, '
|
|
521
|
-
add_hello_action(expected_state, '
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
context_builder
|
|
531
|
-
add_hello_completed_events(context_builder,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
expected_state
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
"""
|
|
588
|
-
context_builder
|
|
589
|
-
add_hello_completed_events(context_builder,
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
expected_state
|
|
597
|
-
|
|
598
|
-
add_hello_action(expected_state,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
result
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
#
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
]
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
def
|
|
755
|
-
"""Tests that a
|
|
756
|
-
context_builder = ContextBuilder('
|
|
757
|
-
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1
|
+
from azure.durable_functions.models.actions.WhenAnyAction import WhenAnyAction
|
|
2
|
+
from azure.durable_functions.models.actions.WhenAllAction import WhenAllAction
|
|
3
|
+
from azure.durable_functions.models.ReplaySchema import ReplaySchema
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from .orchestrator_test_utils \
|
|
6
|
+
import assert_orchestration_state_equals, get_orchestration_state_result, assert_valid_schema
|
|
7
|
+
from tests.test_utils.ContextBuilder import ContextBuilder
|
|
8
|
+
from azure.durable_functions.models.OrchestratorState import OrchestratorState
|
|
9
|
+
from azure.durable_functions.models.actions.CallActivityAction \
|
|
10
|
+
import CallActivityAction
|
|
11
|
+
from tests.test_utils.testClasses import SerializableClass
|
|
12
|
+
import azure.durable_functions as df
|
|
13
|
+
import azure.functions as func
|
|
14
|
+
|
|
15
|
+
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
|
|
16
|
+
|
|
17
|
+
def generator_function(context):
|
|
18
|
+
outputs = []
|
|
19
|
+
|
|
20
|
+
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
21
|
+
task2 = yield context.call_activity("Hello", "Seattle")
|
|
22
|
+
task3 = yield context.call_activity("Hello", "London")
|
|
23
|
+
|
|
24
|
+
outputs.append(task1)
|
|
25
|
+
outputs.append(task2)
|
|
26
|
+
outputs.append(task3)
|
|
27
|
+
|
|
28
|
+
return outputs
|
|
29
|
+
|
|
30
|
+
@app.function_name("generator_function_with_pystein")
|
|
31
|
+
@app.orchestration_trigger(context_name="context")
|
|
32
|
+
def generator_function_with_pystein(context):
|
|
33
|
+
outputs = []
|
|
34
|
+
|
|
35
|
+
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
36
|
+
task2 = yield context.call_activity("Hello", "Seattle")
|
|
37
|
+
task3 = yield context.call_activity("Hello", "London")
|
|
38
|
+
|
|
39
|
+
outputs.append(task1)
|
|
40
|
+
outputs.append(task2)
|
|
41
|
+
outputs.append(task3)
|
|
42
|
+
|
|
43
|
+
return outputs
|
|
44
|
+
|
|
45
|
+
def generator_function_multi_yield_when_all(context):
|
|
46
|
+
outputs = []
|
|
47
|
+
|
|
48
|
+
task1 = context.call_activity("Hello", "Tokyo")
|
|
49
|
+
yield context.task_all([task1])
|
|
50
|
+
result = yield context.task_all([task1])
|
|
51
|
+
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
def generator_function_is_replaying(context):
|
|
55
|
+
outputs = []
|
|
56
|
+
|
|
57
|
+
outputs.append(context.is_replaying)
|
|
58
|
+
yield context.call_activity("Hello", "Tokyo")
|
|
59
|
+
outputs.append(context.is_replaying)
|
|
60
|
+
yield context.call_activity("Hello", "Seattle")
|
|
61
|
+
outputs.append(context.is_replaying)
|
|
62
|
+
yield context.call_activity("Hello", "London")
|
|
63
|
+
return outputs
|
|
64
|
+
|
|
65
|
+
def generator_function_no_yield(context):
|
|
66
|
+
outputs = []
|
|
67
|
+
|
|
68
|
+
task1 = context.call_activity("Hello", "Tokyo")
|
|
69
|
+
task2 = context.call_activity("Hello", "Seattle")
|
|
70
|
+
task3 = yield context.call_activity("Hello", "London")
|
|
71
|
+
|
|
72
|
+
return task3
|
|
73
|
+
|
|
74
|
+
def generator_function_duplicate_yield(context):
|
|
75
|
+
task1 = context.call_activity("Hello", "Tokyo")
|
|
76
|
+
yield task1
|
|
77
|
+
yield task1
|
|
78
|
+
|
|
79
|
+
return ""
|
|
80
|
+
|
|
81
|
+
def generator_function_reducing_when_all(context):
|
|
82
|
+
task1 = context.call_activity("Hello", "Tokyo")
|
|
83
|
+
task2 = context.call_activity("Hello", "Seattle")
|
|
84
|
+
pending_tasks = [task1, task2]
|
|
85
|
+
|
|
86
|
+
# Yield until first task is completed
|
|
87
|
+
finished_task1 = yield context.task_any(pending_tasks)
|
|
88
|
+
|
|
89
|
+
# Remove completed task from pending tasks
|
|
90
|
+
pending_tasks.remove(finished_task1)
|
|
91
|
+
|
|
92
|
+
# Yield remaining task
|
|
93
|
+
yield context.task_any(pending_tasks)
|
|
94
|
+
|
|
95
|
+
# Ensure we can still schedule new tasks
|
|
96
|
+
yield context.call_activity("Hello", "London")
|
|
97
|
+
return ""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def generator_function_reuse_task_in_whenany(context):
|
|
101
|
+
task1 = context.call_activity("Hello", "Tokyo")
|
|
102
|
+
task2 = context.call_activity("Hello", "Seattle")
|
|
103
|
+
pending_tasks = [task1, task2]
|
|
104
|
+
|
|
105
|
+
# Yield until first task is completed
|
|
106
|
+
finished_task1 = yield context.task_any(pending_tasks)
|
|
107
|
+
|
|
108
|
+
# Remove completed task from pending tasks
|
|
109
|
+
pending_tasks.remove(finished_task1)
|
|
110
|
+
|
|
111
|
+
task3 = context.call_activity("Hello", "London")
|
|
112
|
+
tasks = pending_tasks + [task3]
|
|
113
|
+
|
|
114
|
+
# Yield remaining tasks
|
|
115
|
+
yield context.task_any(tasks)
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
def generator_function_compound_tasks(context):
|
|
119
|
+
yield context.call_activity("Hello", "Tokyo")
|
|
120
|
+
|
|
121
|
+
task1 = context.call_activity("Hello", "Tokyo")
|
|
122
|
+
task2 = context.call_activity("Hello", "Tokyo")
|
|
123
|
+
task3 = context.call_activity("Hello", "Tokyo")
|
|
124
|
+
task4 = context.task_any([task3])
|
|
125
|
+
task5 = context.task_all([task1, task2, task4])
|
|
126
|
+
task6 = context.task_any([task5])
|
|
127
|
+
yield task6
|
|
128
|
+
|
|
129
|
+
return ""
|
|
130
|
+
|
|
131
|
+
def generator_function_time_is_not_none(context):
|
|
132
|
+
outputs = []
|
|
133
|
+
|
|
134
|
+
now = context.current_utc_datetime
|
|
135
|
+
if not now:
|
|
136
|
+
raise Exception("No time! 1st attempt")
|
|
137
|
+
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
138
|
+
|
|
139
|
+
now = context.current_utc_datetime
|
|
140
|
+
if not now:
|
|
141
|
+
raise Exception("No time! 2nd attempt")
|
|
142
|
+
task2 = yield context.call_activity("Hello", "Seattle")
|
|
143
|
+
|
|
144
|
+
now = context.current_utc_datetime
|
|
145
|
+
if not now:
|
|
146
|
+
raise Exception("No time! 3rd attempt")
|
|
147
|
+
task3 = yield context.call_activity("Hello", "London")
|
|
148
|
+
|
|
149
|
+
now = context.current_utc_datetime
|
|
150
|
+
if not now:
|
|
151
|
+
raise Exception("No time! 4th attempt")
|
|
152
|
+
|
|
153
|
+
outputs.append(task1)
|
|
154
|
+
outputs.append(task2)
|
|
155
|
+
outputs.append(task3)
|
|
156
|
+
|
|
157
|
+
return outputs
|
|
158
|
+
|
|
159
|
+
def generator_function_time_gather(context):
|
|
160
|
+
outputs = []
|
|
161
|
+
|
|
162
|
+
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
163
|
+
yield context.call_activity("Hello", "Tokyo")
|
|
164
|
+
|
|
165
|
+
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
166
|
+
yield context.call_activity("Hello", "Seattle")
|
|
167
|
+
|
|
168
|
+
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
169
|
+
yield context.call_activity("Hello", "London")
|
|
170
|
+
|
|
171
|
+
outputs.append(context.current_utc_datetime.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
172
|
+
return outputs
|
|
173
|
+
|
|
174
|
+
def generator_function_rasing_ex(context):
|
|
175
|
+
outputs = []
|
|
176
|
+
|
|
177
|
+
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
178
|
+
task2 = yield context.call_activity("Hello", "Seattle")
|
|
179
|
+
task3 = yield context.call_activity("Hello", "London")
|
|
180
|
+
|
|
181
|
+
outputs.append(task1)
|
|
182
|
+
outputs.append(task2)
|
|
183
|
+
outputs.append(task3)
|
|
184
|
+
|
|
185
|
+
raise ValueError("Oops!")
|
|
186
|
+
|
|
187
|
+
@app.orchestration_trigger(context_name="context")
|
|
188
|
+
def generator_function_rasing_ex_with_pystein(context):
|
|
189
|
+
outputs = []
|
|
190
|
+
|
|
191
|
+
task1 = yield context.call_activity("Hello", "Tokyo")
|
|
192
|
+
task2 = yield context.call_activity("Hello", "Seattle")
|
|
193
|
+
task3 = yield context.call_activity("Hello", "London")
|
|
194
|
+
|
|
195
|
+
outputs.append(task1)
|
|
196
|
+
outputs.append(task2)
|
|
197
|
+
outputs.append(task3)
|
|
198
|
+
|
|
199
|
+
raise ValueError("Oops!")
|
|
200
|
+
|
|
201
|
+
def generator_function_with_serialization(context):
|
|
202
|
+
"""Ochestrator to test sequential activity calls with a serializable input arguments."""
|
|
203
|
+
outputs = []
|
|
204
|
+
|
|
205
|
+
task1 = yield context.call_activity("Hello", SerializableClass("Tokyo"))
|
|
206
|
+
task2 = yield context.call_activity("Hello", SerializableClass("Seattle"))
|
|
207
|
+
task3 = yield context.call_activity("Hello", SerializableClass("London"))
|
|
208
|
+
|
|
209
|
+
outputs.append(task1)
|
|
210
|
+
outputs.append(task2)
|
|
211
|
+
outputs.append(task3)
|
|
212
|
+
|
|
213
|
+
return outputs
|
|
214
|
+
|
|
215
|
+
def generator_function_new_guid(context):
|
|
216
|
+
"""Simple orchestrator that generates 3 GUIDs"""
|
|
217
|
+
outputs = []
|
|
218
|
+
|
|
219
|
+
output1 = context.new_guid()
|
|
220
|
+
output2 = context.new_guid()
|
|
221
|
+
output3 = context.new_guid()
|
|
222
|
+
|
|
223
|
+
outputs.append(str(output1))
|
|
224
|
+
outputs.append(str(output2))
|
|
225
|
+
outputs.append(str(output3))
|
|
226
|
+
return outputs
|
|
227
|
+
|
|
228
|
+
def generator_function_call_activity_with_name(context):
|
|
229
|
+
"""Simple orchestrator that call activity function with function name"""
|
|
230
|
+
outputs = []
|
|
231
|
+
|
|
232
|
+
task1 = yield context.call_activity(Hello, "Tokyo")
|
|
233
|
+
task2 = yield context.call_activity(Hello, "Seattle")
|
|
234
|
+
task3 = yield context.call_activity(Hello, "London")
|
|
235
|
+
|
|
236
|
+
outputs.append(task1)
|
|
237
|
+
outputs.append(task2)
|
|
238
|
+
outputs.append(task3)
|
|
239
|
+
|
|
240
|
+
return outputs
|
|
241
|
+
|
|
242
|
+
def generator_function_call_activity_with_callable(context):
|
|
243
|
+
outputs = []
|
|
244
|
+
|
|
245
|
+
task1 = yield context.call_activity(generator_function, "Tokyo")
|
|
246
|
+
|
|
247
|
+
outputs.append(task1)
|
|
248
|
+
|
|
249
|
+
return outputs
|
|
250
|
+
|
|
251
|
+
def generator_function_call_activity_with_orchestrator(context):
|
|
252
|
+
outputs = []
|
|
253
|
+
|
|
254
|
+
task1 = yield context.call_activity(generator_function_rasing_ex_with_pystein, "Tokyo")
|
|
255
|
+
|
|
256
|
+
outputs.append(task1)
|
|
257
|
+
|
|
258
|
+
return outputs
|
|
259
|
+
|
|
260
|
+
def generator_function_call_activity_with_none_return(context):
|
|
261
|
+
"""Simple orchestrator that call activity function which can return None"""
|
|
262
|
+
outputs = []
|
|
263
|
+
|
|
264
|
+
task1 = yield context.call_activity(hello_return_none, "Tokyo")
|
|
265
|
+
task2 = yield context.call_activity(hello_return_none, "Seattle")
|
|
266
|
+
task3 = yield context.call_activity(hello_return_none, "London")
|
|
267
|
+
|
|
268
|
+
outputs.append(task1)
|
|
269
|
+
outputs.append(task2)
|
|
270
|
+
outputs.append(task3)
|
|
271
|
+
|
|
272
|
+
return outputs
|
|
273
|
+
|
|
274
|
+
@app.activity_trigger(input_name = "myArg")
|
|
275
|
+
def Hello(myArg: str):
|
|
276
|
+
return "Hello" + myArg
|
|
277
|
+
|
|
278
|
+
@app.activity_trigger(input_name = "myArg")
|
|
279
|
+
def hello_return_none(myArg: str):
|
|
280
|
+
if myArg == "London":
|
|
281
|
+
return None
|
|
282
|
+
else:
|
|
283
|
+
return "Hello" + myArg
|
|
284
|
+
|
|
285
|
+
def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
|
|
286
|
+
return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema)
|
|
287
|
+
|
|
288
|
+
def add_timer_fired_events(context_builder: ContextBuilder, id_: int, timestamp: str):
|
|
289
|
+
fire_at: str = context_builder.add_timer_created_event(id_, timestamp)
|
|
290
|
+
context_builder.add_orchestrator_completed_event()
|
|
291
|
+
context_builder.add_orchestrator_started_event()
|
|
292
|
+
context_builder.add_timer_fired_event(id_=id_, fire_at=fire_at)
|
|
293
|
+
|
|
294
|
+
def add_hello_action(state: OrchestratorState, input_: str, activity_name="Hello"):
|
|
295
|
+
action = CallActivityAction(function_name=activity_name, input_=input_)
|
|
296
|
+
state.actions.append([action])
|
|
297
|
+
|
|
298
|
+
def add_hello_completed_events(
|
|
299
|
+
context_builder: ContextBuilder, id_: int, result: str, is_played=False, activity_name="Hello"):
|
|
300
|
+
context_builder.add_task_scheduled_event(name=activity_name, id_=id_)
|
|
301
|
+
context_builder.add_orchestrator_completed_event()
|
|
302
|
+
context_builder.add_orchestrator_started_event()
|
|
303
|
+
context_builder.add_task_completed_event(id_=id_, result=result, is_played=is_played)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def add_hello_failed_events(
|
|
307
|
+
context_builder: ContextBuilder, id_: int, reason: str, details: str):
|
|
308
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=id_)
|
|
309
|
+
context_builder.add_orchestrator_completed_event()
|
|
310
|
+
context_builder.add_orchestrator_started_event()
|
|
311
|
+
context_builder.add_task_failed_event(
|
|
312
|
+
id_=id_, reason=reason, details=details)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_initial_orchestration_state():
|
|
316
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
317
|
+
|
|
318
|
+
result = get_orchestration_state_result(
|
|
319
|
+
context_builder, generator_function)
|
|
320
|
+
|
|
321
|
+
expected_state = base_expected_state()
|
|
322
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
323
|
+
expected = expected_state.to_json()
|
|
324
|
+
|
|
325
|
+
assert_valid_schema(result)
|
|
326
|
+
assert_orchestration_state_equals(expected, result)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def test_tokyo_state():
|
|
330
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
331
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
332
|
+
|
|
333
|
+
result = get_orchestration_state_result(
|
|
334
|
+
context_builder, generator_function)
|
|
335
|
+
|
|
336
|
+
expected_state = base_expected_state()
|
|
337
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
338
|
+
add_hello_action(expected_state, 'Seattle')
|
|
339
|
+
expected = expected_state.to_json()
|
|
340
|
+
|
|
341
|
+
assert_valid_schema(result)
|
|
342
|
+
assert_orchestration_state_equals(expected, result)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_failed_tokyo_state():
|
|
346
|
+
failed_reason = 'Reasons'
|
|
347
|
+
failed_details = 'Stuff and Things'
|
|
348
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
349
|
+
add_hello_failed_events(
|
|
350
|
+
context_builder, 0, failed_reason, failed_details)
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
result = get_orchestration_state_result(
|
|
354
|
+
context_builder, generator_function)
|
|
355
|
+
# expected an exception
|
|
356
|
+
assert False
|
|
357
|
+
except Exception as e:
|
|
358
|
+
error_label = "\n\n$OutOfProcData$:"
|
|
359
|
+
error_str = str(e)
|
|
360
|
+
|
|
361
|
+
expected_state = base_expected_state()
|
|
362
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
363
|
+
error_msg = f'{failed_reason} \n {failed_details}'
|
|
364
|
+
expected_state._error = error_msg
|
|
365
|
+
state_str = expected_state.to_json_string()
|
|
366
|
+
|
|
367
|
+
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
368
|
+
assert expected_error_str == error_str
|
|
369
|
+
|
|
370
|
+
def test_call_activity_with_name():
|
|
371
|
+
context_builder = ContextBuilder('test_call_activity_with_name')
|
|
372
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
373
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
374
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
375
|
+
result = get_orchestration_state_result(
|
|
376
|
+
context_builder, generator_function_call_activity_with_name)
|
|
377
|
+
|
|
378
|
+
expected_state = base_expected_state(
|
|
379
|
+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
|
|
380
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
381
|
+
add_hello_action(expected_state, 'Seattle')
|
|
382
|
+
add_hello_action(expected_state, 'London')
|
|
383
|
+
expected_state._is_done = True
|
|
384
|
+
expected = expected_state.to_json()
|
|
385
|
+
|
|
386
|
+
assert_valid_schema(result)
|
|
387
|
+
assert_orchestration_state_equals(expected, result)
|
|
388
|
+
|
|
389
|
+
def test_call_activity_with_none_return():
|
|
390
|
+
context_builder = ContextBuilder('test_call_activity_with_none_return')
|
|
391
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"", "hello_return_none")
|
|
392
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"", "hello_return_none")
|
|
393
|
+
add_hello_completed_events(context_builder, 2, None, "hello_return_none")
|
|
394
|
+
result = get_orchestration_state_result(
|
|
395
|
+
context_builder, generator_function_call_activity_with_none_return)
|
|
396
|
+
|
|
397
|
+
expected_state = base_expected_state(
|
|
398
|
+
['Hello Tokyo!', 'Hello Seattle!', None])
|
|
399
|
+
add_hello_action(expected_state, 'Tokyo', "hello_return_none")
|
|
400
|
+
add_hello_action(expected_state, 'Seattle', "hello_return_none")
|
|
401
|
+
add_hello_action(expected_state, 'London', "hello_return_none")
|
|
402
|
+
expected_state._is_done = True
|
|
403
|
+
expected = expected_state.to_json()
|
|
404
|
+
|
|
405
|
+
assert_valid_schema(result)
|
|
406
|
+
assert_orchestration_state_equals(expected, result)
|
|
407
|
+
|
|
408
|
+
def test_call_activity_function_callable_exception():
|
|
409
|
+
context_builder = ContextBuilder('test_call_activity_by_name_exception')
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
result = get_orchestration_state_result(
|
|
413
|
+
context_builder, generator_function_call_activity_with_callable)
|
|
414
|
+
# expected an exception
|
|
415
|
+
assert False
|
|
416
|
+
except Exception as e:
|
|
417
|
+
error_label = "\n\n$OutOfProcData$:"
|
|
418
|
+
error_str = str(e)
|
|
419
|
+
|
|
420
|
+
expected_state = base_expected_state()
|
|
421
|
+
error_msg = "The `call_activity` API received a `Callable` without an "\
|
|
422
|
+
"associated Azure Functions trigger-type. "\
|
|
423
|
+
"Please ensure you're using the Python programming model V2 "\
|
|
424
|
+
"and that your activity function is annotated with the `activity_trigger`"\
|
|
425
|
+
"decorator. Otherwise, provide in the name of the activity as a string."
|
|
426
|
+
expected_state._error = error_msg
|
|
427
|
+
state_str = expected_state.to_json_string()
|
|
428
|
+
|
|
429
|
+
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
430
|
+
assert expected_error_str == error_str
|
|
431
|
+
|
|
432
|
+
def test_call_activity_function_with_orchestrator_exception():
|
|
433
|
+
context_builder = ContextBuilder('test_call_activity_by_name_exception')
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
result = get_orchestration_state_result(
|
|
437
|
+
context_builder, generator_function_call_activity_with_orchestrator)
|
|
438
|
+
# expected an exception
|
|
439
|
+
assert False
|
|
440
|
+
except Exception as e:
|
|
441
|
+
error_label = "\n\n$OutOfProcData$:"
|
|
442
|
+
error_str = str(e)
|
|
443
|
+
|
|
444
|
+
expected_state = base_expected_state()
|
|
445
|
+
error_msg = "Received function with Trigger-type `"\
|
|
446
|
+
+ generator_function_rasing_ex_with_pystein._function._trigger.type\
|
|
447
|
+
+ "` but expected `ActivityTrigger`. Ensure your "\
|
|
448
|
+
"function is annotated with the `ActivityTrigger`" \
|
|
449
|
+
" decorator or directly pass in the name of the "\
|
|
450
|
+
"function as a string."
|
|
451
|
+
expected_state._error = error_msg
|
|
452
|
+
state_str = expected_state.to_json_string()
|
|
453
|
+
|
|
454
|
+
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
455
|
+
assert expected_error_str == error_str
|
|
456
|
+
|
|
457
|
+
def test_user_code_raises_exception():
|
|
458
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
459
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
460
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
461
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
result = get_orchestration_state_result(
|
|
465
|
+
context_builder, generator_function_rasing_ex)
|
|
466
|
+
# expected an exception
|
|
467
|
+
assert False
|
|
468
|
+
except Exception as e:
|
|
469
|
+
error_label = "\n\n$OutOfProcData$:"
|
|
470
|
+
error_str = str(e)
|
|
471
|
+
|
|
472
|
+
expected_state = base_expected_state()
|
|
473
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
474
|
+
add_hello_action(expected_state, 'Seattle')
|
|
475
|
+
add_hello_action(expected_state, 'London')
|
|
476
|
+
error_msg = 'Oops!'
|
|
477
|
+
expected_state._error = error_msg
|
|
478
|
+
state_str = expected_state.to_json_string()
|
|
479
|
+
|
|
480
|
+
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
481
|
+
assert expected_error_str == error_str
|
|
482
|
+
|
|
483
|
+
def test_user_code_raises_exception_with_pystein():
|
|
484
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
485
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
486
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
487
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
488
|
+
|
|
489
|
+
try:
|
|
490
|
+
result = get_orchestration_state_result(
|
|
491
|
+
context_builder, generator_function_rasing_ex_with_pystein,
|
|
492
|
+
uses_pystein=True)
|
|
493
|
+
# expected an exception
|
|
494
|
+
assert False
|
|
495
|
+
except Exception as e:
|
|
496
|
+
error_label = "\n\n$OutOfProcData$:"
|
|
497
|
+
error_str = str(e)
|
|
498
|
+
|
|
499
|
+
expected_state = base_expected_state()
|
|
500
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
501
|
+
add_hello_action(expected_state, 'Seattle')
|
|
502
|
+
add_hello_action(expected_state, 'London')
|
|
503
|
+
error_msg = 'Oops!'
|
|
504
|
+
expected_state._error = error_msg
|
|
505
|
+
state_str = expected_state.to_json_string()
|
|
506
|
+
|
|
507
|
+
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
508
|
+
assert expected_error_str == error_str
|
|
509
|
+
|
|
510
|
+
def test_tokyo_and_seattle_state():
|
|
511
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
512
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
513
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
514
|
+
|
|
515
|
+
result = get_orchestration_state_result(
|
|
516
|
+
context_builder, generator_function)
|
|
517
|
+
|
|
518
|
+
expected_state = base_expected_state()
|
|
519
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
520
|
+
add_hello_action(expected_state, 'Seattle')
|
|
521
|
+
add_hello_action(expected_state, 'London')
|
|
522
|
+
expected = expected_state.to_json()
|
|
523
|
+
|
|
524
|
+
assert_valid_schema(result)
|
|
525
|
+
assert_orchestration_state_equals(expected, result)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def test_tokyo_and_seattle_and_london_state():
|
|
529
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
530
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
531
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
532
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
533
|
+
|
|
534
|
+
result = get_orchestration_state_result(
|
|
535
|
+
context_builder, generator_function)
|
|
536
|
+
|
|
537
|
+
expected_state = base_expected_state(
|
|
538
|
+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
|
|
539
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
540
|
+
add_hello_action(expected_state, 'Seattle')
|
|
541
|
+
add_hello_action(expected_state, 'London')
|
|
542
|
+
expected_state._is_done = True
|
|
543
|
+
expected = expected_state.to_json()
|
|
544
|
+
|
|
545
|
+
assert_valid_schema(result)
|
|
546
|
+
assert_orchestration_state_equals(expected, result)
|
|
547
|
+
|
|
548
|
+
def test_tokyo_and_seattle_and_london_state_pystein():
|
|
549
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
550
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
551
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
552
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
553
|
+
|
|
554
|
+
result = get_orchestration_state_result(
|
|
555
|
+
context_builder, generator_function_with_pystein,
|
|
556
|
+
uses_pystein=True)
|
|
557
|
+
|
|
558
|
+
expected_state = base_expected_state(
|
|
559
|
+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
|
|
560
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
561
|
+
add_hello_action(expected_state, 'Seattle')
|
|
562
|
+
add_hello_action(expected_state, 'London')
|
|
563
|
+
expected_state._is_done = True
|
|
564
|
+
expected = expected_state.to_json()
|
|
565
|
+
|
|
566
|
+
assert_valid_schema(result)
|
|
567
|
+
assert_orchestration_state_equals(expected, result)
|
|
568
|
+
|
|
569
|
+
def test_multi_when_all_yield():
|
|
570
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
571
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
572
|
+
|
|
573
|
+
result = get_orchestration_state_result(
|
|
574
|
+
context_builder, generator_function_multi_yield_when_all)
|
|
575
|
+
|
|
576
|
+
expected_state = base_expected_state(
|
|
577
|
+
['Hello Tokyo!'])
|
|
578
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
579
|
+
expected_state._is_done = True
|
|
580
|
+
expected = expected_state.to_json()
|
|
581
|
+
|
|
582
|
+
assert_valid_schema(result)
|
|
583
|
+
assert_orchestration_state_equals(expected, result)
|
|
584
|
+
|
|
585
|
+
def test_sequential_is_replaying():
|
|
586
|
+
context_builder = ContextBuilder('test_simple_function', is_replaying=True)
|
|
587
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"", True)
|
|
588
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"", True)
|
|
589
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"", True)
|
|
590
|
+
|
|
591
|
+
result = get_orchestration_state_result(
|
|
592
|
+
context_builder, generator_function_is_replaying)
|
|
593
|
+
|
|
594
|
+
expected_state = base_expected_state(
|
|
595
|
+
[True, True, True])
|
|
596
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
597
|
+
add_hello_action(expected_state, 'Seattle')
|
|
598
|
+
add_hello_action(expected_state, 'London')
|
|
599
|
+
expected_state._is_done = True
|
|
600
|
+
expected = expected_state.to_json()
|
|
601
|
+
|
|
602
|
+
assert_valid_schema(result)
|
|
603
|
+
assert_orchestration_state_equals(expected, result)
|
|
604
|
+
|
|
605
|
+
def test_sequential_orchestration_no_yield():
|
|
606
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
607
|
+
add_hello_completed_events(context_builder, 0, "\"Hello London!\"")
|
|
608
|
+
|
|
609
|
+
result = get_orchestration_state_result(
|
|
610
|
+
context_builder, generator_function_no_yield)
|
|
611
|
+
|
|
612
|
+
expected_state = base_expected_state('Hello London!')
|
|
613
|
+
add_hello_action(expected_state, 'London')
|
|
614
|
+
expected_state._is_done = True
|
|
615
|
+
expected = expected_state.to_json()
|
|
616
|
+
|
|
617
|
+
assert_valid_schema(result)
|
|
618
|
+
assert_orchestration_state_equals(expected, result)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def test_tokyo_and_seattle_and_london_with_serialization_state():
|
|
622
|
+
"""Tests the sequential function pattern with custom object serialization.
|
|
623
|
+
|
|
624
|
+
This simple test validates that a sequential function pattern returns
|
|
625
|
+
the expected state when the input to activities is a user-provided
|
|
626
|
+
serializable class.
|
|
627
|
+
"""
|
|
628
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
629
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
630
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
631
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
632
|
+
|
|
633
|
+
result = get_orchestration_state_result(
|
|
634
|
+
context_builder, generator_function_with_serialization)
|
|
635
|
+
|
|
636
|
+
expected_state = base_expected_state(
|
|
637
|
+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
|
|
638
|
+
add_hello_action(expected_state, SerializableClass("Tokyo"))
|
|
639
|
+
add_hello_action(expected_state, SerializableClass("Seattle"))
|
|
640
|
+
add_hello_action(expected_state, SerializableClass("London"))
|
|
641
|
+
expected_state._is_done = True
|
|
642
|
+
expected = expected_state.to_json()
|
|
643
|
+
|
|
644
|
+
assert_valid_schema(result)
|
|
645
|
+
assert_orchestration_state_equals(expected, result)
|
|
646
|
+
|
|
647
|
+
def test_utc_time_is_never_none():
|
|
648
|
+
"""Tests an orchestrator that errors out if its current_utc_datetime is ever None.
|
|
649
|
+
|
|
650
|
+
If we receive all activity results, it means we never error'ed out. Our test has
|
|
651
|
+
a history events array with identical timestamps, simulating events arriving
|
|
652
|
+
very close to one another."""
|
|
653
|
+
|
|
654
|
+
# we set `increase_time` to False to make sure the changes are resilient
|
|
655
|
+
# to undistinguishable timestamps (events arrive very close to each other)
|
|
656
|
+
context_builder = ContextBuilder('test_simple_function', increase_time=False)
|
|
657
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
658
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
659
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
660
|
+
|
|
661
|
+
result = get_orchestration_state_result(
|
|
662
|
+
context_builder, generator_function_deterministic_utc_time)
|
|
663
|
+
|
|
664
|
+
expected_state = base_expected_state(
|
|
665
|
+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
|
|
666
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
667
|
+
add_hello_action(expected_state, 'Seattle')
|
|
668
|
+
add_hello_action(expected_state, 'London')
|
|
669
|
+
expected_state._is_done = True
|
|
670
|
+
expected = expected_state.to_json()
|
|
671
|
+
|
|
672
|
+
assert_valid_schema(result)
|
|
673
|
+
assert_orchestration_state_equals(expected, result)
|
|
674
|
+
|
|
675
|
+
def test_utc_time_is_never_none():
|
|
676
|
+
"""Tests an orchestrator that errors out if its current_utc_datetime is ever None.
|
|
677
|
+
|
|
678
|
+
If we receive all activity results, it means we never error'ed out. Our test has
|
|
679
|
+
a history events array with identical timestamps, simulating events arriving
|
|
680
|
+
very close to one another."""
|
|
681
|
+
|
|
682
|
+
# we set `increase_time` to False to make sure the changes are resilient
|
|
683
|
+
# to undistinguishable timestamps (events arrive very close to each other)
|
|
684
|
+
context_builder = ContextBuilder('test_simple_function', increase_time=False)
|
|
685
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
686
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
687
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
688
|
+
|
|
689
|
+
result = get_orchestration_state_result(
|
|
690
|
+
context_builder, generator_function_time_is_not_none)
|
|
691
|
+
|
|
692
|
+
expected_state = base_expected_state(
|
|
693
|
+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
|
|
694
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
695
|
+
add_hello_action(expected_state, 'Seattle')
|
|
696
|
+
add_hello_action(expected_state, 'London')
|
|
697
|
+
expected_state._is_done = True
|
|
698
|
+
expected = expected_state.to_json()
|
|
699
|
+
|
|
700
|
+
assert_valid_schema(result)
|
|
701
|
+
assert_orchestration_state_equals(expected, result)
|
|
702
|
+
|
|
703
|
+
def test_utc_time_updates_correctly():
|
|
704
|
+
"""Tests that current_utc_datetime updates correctly"""
|
|
705
|
+
|
|
706
|
+
now = datetime.utcnow()
|
|
707
|
+
# the first orchestrator-started event starts 1 second after `now`
|
|
708
|
+
context_builder = ContextBuilder('test_simple_function', starting_time=now)
|
|
709
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
710
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
711
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
712
|
+
|
|
713
|
+
result = get_orchestration_state_result(
|
|
714
|
+
context_builder, generator_function_time_gather)
|
|
715
|
+
|
|
716
|
+
# In the expected history, the orchestrator starts again every 4 seconds
|
|
717
|
+
# The current_utc_datetime should update to the orchestrator start event timestamp
|
|
718
|
+
num_restarts = 3
|
|
719
|
+
expected_utc_time = now + timedelta(seconds=1)
|
|
720
|
+
outputs = [expected_utc_time.strftime("%m/%d/%Y, %H:%M:%S")]
|
|
721
|
+
for _ in range(num_restarts):
|
|
722
|
+
expected_utc_time += timedelta(seconds=4)
|
|
723
|
+
outputs.append(expected_utc_time.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
724
|
+
|
|
725
|
+
expected_state = base_expected_state(outputs)
|
|
726
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
727
|
+
add_hello_action(expected_state, 'Seattle')
|
|
728
|
+
add_hello_action(expected_state, 'London')
|
|
729
|
+
expected_state._is_done = True
|
|
730
|
+
expected = expected_state.to_json()
|
|
731
|
+
|
|
732
|
+
assert_valid_schema(result)
|
|
733
|
+
assert_orchestration_state_equals(expected, result)
|
|
734
|
+
|
|
735
|
+
def test_new_guid_orchestrator():
|
|
736
|
+
"""Tests that the new_guid API is replay-safe and produces new GUIDs every time"""
|
|
737
|
+
context_builder = ContextBuilder('test_guid_orchestrator')
|
|
738
|
+
|
|
739
|
+
# To test that the API is replay-safe, we generate two orchestrators
|
|
740
|
+
# with the same starting context
|
|
741
|
+
result1 = get_orchestration_state_result(
|
|
742
|
+
context_builder, generator_function_new_guid)
|
|
743
|
+
outputs1 = result1["output"]
|
|
744
|
+
|
|
745
|
+
result2 = get_orchestration_state_result(
|
|
746
|
+
context_builder, generator_function_new_guid)
|
|
747
|
+
outputs2 = result2["output"]
|
|
748
|
+
|
|
749
|
+
# All GUIDs should be unique
|
|
750
|
+
assert len(outputs1) == len(set(outputs1))
|
|
751
|
+
# The two GUID lists should be the same
|
|
752
|
+
assert outputs1 == outputs2
|
|
753
|
+
|
|
754
|
+
def test_duplicate_yields_do_not_add_duplicate_actions():
|
|
755
|
+
"""Tests that yield'ing a Task twice does not double the task's actions"""
|
|
756
|
+
context_builder = ContextBuilder('test_guid_orchestrator')
|
|
757
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
758
|
+
|
|
759
|
+
result = get_orchestration_state_result(
|
|
760
|
+
context_builder, generator_function_duplicate_yield)
|
|
761
|
+
|
|
762
|
+
expected_state = base_expected_state("")
|
|
763
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
764
|
+
expected_state._is_done = True
|
|
765
|
+
expected = expected_state.to_json()
|
|
766
|
+
|
|
767
|
+
assert_valid_schema(result)
|
|
768
|
+
assert_orchestration_state_equals(expected, result)
|
|
769
|
+
|
|
770
|
+
def test_reducing_when_any_pattern():
|
|
771
|
+
"""Tests that a user can call when_any on a progressively smaller list of already scheduled tasks"""
|
|
772
|
+
context_builder = ContextBuilder('test_reducing_when_any', replay_schema=ReplaySchema.V2)
|
|
773
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
774
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
775
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
776
|
+
|
|
777
|
+
result = get_orchestration_state_result(
|
|
778
|
+
context_builder, generator_function_reducing_when_all)
|
|
779
|
+
|
|
780
|
+
# this scenario is only supported for V2 replay
|
|
781
|
+
expected_state = base_expected_state("",replay_schema=ReplaySchema.V2)
|
|
782
|
+
expected_state._actions = [
|
|
783
|
+
[WhenAnyAction(
|
|
784
|
+
[CallActivityAction("Hello", "Seattle"), CallActivityAction("Hello", "Tokyo")]),
|
|
785
|
+
CallActivityAction("Hello", "London")
|
|
786
|
+
]
|
|
787
|
+
]
|
|
788
|
+
|
|
789
|
+
expected_state._is_done = True
|
|
790
|
+
expected = expected_state.to_json()
|
|
791
|
+
|
|
792
|
+
assert_orchestration_state_equals(expected, result)
|
|
793
|
+
|
|
794
|
+
def test_reducing_when_any_pattern():
|
|
795
|
+
"""Tests that a user can call when_any on a progressively smaller list of already scheduled tasks"""
|
|
796
|
+
context_builder = ContextBuilder('generator_function_reuse_task_in_whenany', replay_schema=ReplaySchema.V2)
|
|
797
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
798
|
+
add_hello_completed_events(context_builder, 1, "\"Hello Seattle!\"")
|
|
799
|
+
add_hello_completed_events(context_builder, 2, "\"Hello London!\"")
|
|
800
|
+
|
|
801
|
+
result = get_orchestration_state_result(
|
|
802
|
+
context_builder, generator_function_reuse_task_in_whenany)
|
|
803
|
+
|
|
804
|
+
# this scenario is only supported for V2 replay
|
|
805
|
+
expected_state = base_expected_state("",replay_schema=ReplaySchema.V2)
|
|
806
|
+
expected_state._actions = [
|
|
807
|
+
[WhenAnyAction(
|
|
808
|
+
[CallActivityAction("Hello", "Seattle"), CallActivityAction("Hello", "Tokyo")]),
|
|
809
|
+
WhenAnyAction(
|
|
810
|
+
[CallActivityAction("Hello", "London")])
|
|
811
|
+
]
|
|
812
|
+
]
|
|
813
|
+
|
|
814
|
+
expected_state._is_done = True
|
|
815
|
+
expected = expected_state.to_json()
|
|
816
|
+
|
|
817
|
+
assert_orchestration_state_equals(expected, result)
|
|
818
|
+
|
|
819
|
+
def test_compound_tasks_return_single_action_in_V2():
|
|
820
|
+
"""Tests that compound tasks, in the v2 replay schema, are represented as a single "deep" action"""
|
|
821
|
+
context_builder = ContextBuilder('test_v2_replay_schema', replay_schema=ReplaySchema.V2)
|
|
822
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
823
|
+
|
|
824
|
+
result = get_orchestration_state_result(
|
|
825
|
+
context_builder, generator_function_compound_tasks)
|
|
826
|
+
|
|
827
|
+
expected_state = base_expected_state(replay_schema=ReplaySchema.V2)
|
|
828
|
+
expected_state._actions = [
|
|
829
|
+
[CallActivityAction("Hello", "Tokyo"), WhenAnyAction(
|
|
830
|
+
[WhenAllAction(
|
|
831
|
+
[CallActivityAction("Hello", "Tokyo"), CallActivityAction("Hello", "Tokyo"), WhenAnyAction(
|
|
832
|
+
[CallActivityAction("Hello", "Tokyo")])
|
|
833
|
+
])
|
|
834
|
+
])
|
|
835
|
+
]
|
|
836
|
+
]
|
|
837
|
+
expected_state._is_done = False
|
|
838
|
+
expected = expected_state.to_json()
|
|
839
|
+
|
|
840
|
+
#assert_valid_schema(result)
|
|
841
|
+
assert_orchestration_state_equals(expected, result)
|