azure-functions-durable 1.2.9__py3-none-any.whl → 1.3.0__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 +260 -249
- azure/durable_functions/decorators/metadata.py +109 -109
- azure/durable_functions/entity.py +129 -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 +812 -781
- azure/durable_functions/models/DurableOrchestrationContext.py +761 -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 -32
- azure/durable_functions/models/OrchestratorState.py +117 -116
- azure/durable_functions/models/PurgeHistoryResult.py +33 -33
- azure/durable_functions/models/ReplaySchema.py +9 -8
- azure/durable_functions/models/RetryOptions.py +69 -69
- azure/durable_functions/models/RpcManagementOptions.py +86 -86
- azure/durable_functions/models/Task.py +540 -426
- azure/durable_functions/models/TaskOrchestrationExecutor.py +352 -336
- azure/durable_functions/models/TokenSource.py +56 -56
- azure/durable_functions/models/__init__.py +26 -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 +94 -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 -27
- 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 +80 -69
- azure/durable_functions/models/utils/json_utils.py +96 -56
- azure/durable_functions/orchestrator.py +73 -71
- azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +42 -0
- azure/durable_functions/testing/__init__.py +6 -0
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/LICENSE +21 -21
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/METADATA +59 -58
- azure_functions_durable-1.3.0.dist-info/RECORD +103 -0
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.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 -68
- tests/models/test_DurableOrchestrationClient.py +730 -730
- 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 +397 -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 -841
- 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_long_timers.py +70 -0
- 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.9.data/data/_manifest/bsi.json +0 -1
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.cat +0 -0
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json +0 -11985
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
- azure_functions_durable-1.2.9.dist-info/RECORD +0 -102
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,466 +1,466 @@
|
|
|
1
|
-
from typing import List, Union
|
|
2
|
-
from azure.durable_functions.models.ReplaySchema import ReplaySchema
|
|
3
|
-
from .orchestrator_test_utils \
|
|
4
|
-
import get_orchestration_state_result, assert_orchestration_state_equals, assert_valid_schema
|
|
5
|
-
from tests.test_utils.ContextBuilder import ContextBuilder
|
|
6
|
-
from azure.durable_functions.models.OrchestratorState import OrchestratorState
|
|
7
|
-
from azure.durable_functions.models.RetryOptions import RetryOptions
|
|
8
|
-
from azure.durable_functions.models.actions.CallActivityWithRetryAction \
|
|
9
|
-
import CallActivityWithRetryAction
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
RETRY_OPTIONS = RetryOptions(5000, 3)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def generator_function(context):
|
|
16
|
-
outputs = []
|
|
17
|
-
|
|
18
|
-
retry_options = RETRY_OPTIONS
|
|
19
|
-
task1 = yield context.call_activity_with_retry(
|
|
20
|
-
"Hello", retry_options, "Tokyo")
|
|
21
|
-
task2 = yield context.call_activity_with_retry(
|
|
22
|
-
"Hello", retry_options, "Seattle")
|
|
23
|
-
task3 = yield context.call_activity_with_retry(
|
|
24
|
-
"Hello", retry_options, "London")
|
|
25
|
-
|
|
26
|
-
outputs.append(task1)
|
|
27
|
-
outputs.append(task2)
|
|
28
|
-
outputs.append(task3)
|
|
29
|
-
|
|
30
|
-
return outputs
|
|
31
|
-
|
|
32
|
-
def generator_function_try_catch(context):
|
|
33
|
-
outputs = []
|
|
34
|
-
|
|
35
|
-
retry_options = RETRY_OPTIONS
|
|
36
|
-
result = None
|
|
37
|
-
try:
|
|
38
|
-
result = yield context.call_activity_with_retry(
|
|
39
|
-
"Hello", retry_options, "Tokyo")
|
|
40
|
-
except:
|
|
41
|
-
result = yield context.call_activity_with_retry(
|
|
42
|
-
"Hello", retry_options, "Seattle")
|
|
43
|
-
return result
|
|
44
|
-
|
|
45
|
-
def generator_function_concurrent_retries(context):
|
|
46
|
-
outputs = []
|
|
47
|
-
|
|
48
|
-
retry_options = RETRY_OPTIONS
|
|
49
|
-
task1 = context.call_activity_with_retry(
|
|
50
|
-
"Hello", retry_options, "Tokyo")
|
|
51
|
-
task2 = context.call_activity_with_retry(
|
|
52
|
-
"Hello", retry_options, "Seattle")
|
|
53
|
-
task3 = context.call_activity_with_retry(
|
|
54
|
-
"Hello", retry_options, "London")
|
|
55
|
-
|
|
56
|
-
outputs = yield context.task_all([task1, task2, task3])
|
|
57
|
-
|
|
58
|
-
return outputs
|
|
59
|
-
|
|
60
|
-
def generator_function_two_concurrent_retries_when_all(context):
|
|
61
|
-
outputs = []
|
|
62
|
-
|
|
63
|
-
retry_options = RETRY_OPTIONS
|
|
64
|
-
task1 = context.call_activity_with_retry(
|
|
65
|
-
"Hello", retry_options, "Tokyo")
|
|
66
|
-
task2 = context.call_activity_with_retry(
|
|
67
|
-
"Hello", retry_options, "Seattle")
|
|
68
|
-
|
|
69
|
-
outputs = yield context.task_all([task1, task2])
|
|
70
|
-
|
|
71
|
-
return outputs
|
|
72
|
-
|
|
73
|
-
def generator_function_two_concurrent_retries_when_any(context):
|
|
74
|
-
outputs = []
|
|
75
|
-
|
|
76
|
-
retry_options = RETRY_OPTIONS
|
|
77
|
-
task1 = context.call_activity_with_retry(
|
|
78
|
-
"Hello", retry_options, "Tokyo")
|
|
79
|
-
task2 = context.call_activity_with_retry(
|
|
80
|
-
"Hello", retry_options, "Seattle")
|
|
81
|
-
|
|
82
|
-
outputs = yield context.task_any([task1, task2])
|
|
83
|
-
|
|
84
|
-
return outputs.result
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
|
|
88
|
-
return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema.value)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def add_hello_action(state: OrchestratorState, input_: Union[List[str], str]):
|
|
92
|
-
retry_options = RETRY_OPTIONS
|
|
93
|
-
actions = []
|
|
94
|
-
inputs = input_
|
|
95
|
-
if not isinstance(input_, list):
|
|
96
|
-
inputs = [input_]
|
|
97
|
-
for input_ in inputs:
|
|
98
|
-
action = CallActivityWithRetryAction(
|
|
99
|
-
function_name='Hello', retry_options=retry_options, input_=input_)
|
|
100
|
-
actions.append(action)
|
|
101
|
-
state._actions.append(actions)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def add_hello_failed_events(
|
|
105
|
-
context_builder: ContextBuilder, id_: int, reason: str, details: str):
|
|
106
|
-
context_builder.add_task_scheduled_event(name='Hello', id_=id_)
|
|
107
|
-
context_builder.add_orchestrator_completed_event()
|
|
108
|
-
context_builder.add_orchestrator_started_event()
|
|
109
|
-
context_builder.add_task_failed_event(
|
|
110
|
-
id_=id_, reason=reason, details=details)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def add_hello_completed_events(
|
|
114
|
-
context_builder: ContextBuilder, id_: int, result: str):
|
|
115
|
-
context_builder.add_task_scheduled_event(name='Hello', id_=id_)
|
|
116
|
-
context_builder.add_orchestrator_completed_event()
|
|
117
|
-
context_builder.add_orchestrator_started_event()
|
|
118
|
-
context_builder.add_task_completed_event(id_=id_, result=result)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def add_retry_timer_events(context_builder: ContextBuilder, id_: int):
|
|
122
|
-
fire_at = context_builder.add_timer_created_event(id_)
|
|
123
|
-
context_builder.add_orchestrator_completed_event()
|
|
124
|
-
context_builder.add_orchestrator_started_event()
|
|
125
|
-
context_builder.add_timer_fired_event(id_=id_, fire_at=fire_at)
|
|
126
|
-
|
|
127
|
-
def add_two_retriable_events_completing_out_of_order(context_builder: ContextBuilder,
|
|
128
|
-
failed_reason, failed_details):
|
|
129
|
-
## Schedule tasks
|
|
130
|
-
context_builder.add_task_scheduled_event(name='Hello', id_=0) # Tokyo task
|
|
131
|
-
context_builder.add_task_scheduled_event(name='Hello', id_=1) # Seattle task
|
|
132
|
-
|
|
133
|
-
context_builder.add_orchestrator_completed_event()
|
|
134
|
-
context_builder.add_orchestrator_started_event()
|
|
135
|
-
|
|
136
|
-
## Task failures and timer-scheduling
|
|
137
|
-
|
|
138
|
-
# tasks fail "out of order"
|
|
139
|
-
context_builder.add_task_failed_event(
|
|
140
|
-
id_=1, reason=failed_reason, details=failed_details) # Seattle task
|
|
141
|
-
fire_at_1 = context_builder.add_timer_created_event(2) # Seattle timer
|
|
142
|
-
|
|
143
|
-
context_builder.add_orchestrator_completed_event()
|
|
144
|
-
context_builder.add_orchestrator_started_event()
|
|
145
|
-
|
|
146
|
-
context_builder.add_task_failed_event(
|
|
147
|
-
id_=0, reason=failed_reason, details=failed_details) # Tokyo task
|
|
148
|
-
fire_at_2 = context_builder.add_timer_created_event(3) # Tokyo timer
|
|
149
|
-
|
|
150
|
-
context_builder.add_orchestrator_completed_event()
|
|
151
|
-
context_builder.add_orchestrator_started_event()
|
|
152
|
-
|
|
153
|
-
## fire timers
|
|
154
|
-
context_builder.add_timer_fired_event(id_=2, fire_at=fire_at_1) # Seattle timer
|
|
155
|
-
context_builder.add_timer_fired_event(id_=3, fire_at=fire_at_2) # Tokyo timer
|
|
156
|
-
|
|
157
|
-
## Complete events
|
|
158
|
-
context_builder.add_task_scheduled_event(name='Hello', id_=4) # Seattle task
|
|
159
|
-
context_builder.add_task_scheduled_event(name='Hello', id_=5) # Tokyo task
|
|
160
|
-
|
|
161
|
-
context_builder.add_orchestrator_completed_event()
|
|
162
|
-
context_builder.add_orchestrator_started_event()
|
|
163
|
-
context_builder.add_task_completed_event(id_=4, result="\"Hello Seattle!\"")
|
|
164
|
-
context_builder.add_task_completed_event(id_=5, result="\"Hello Tokyo!\"")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def test_initial_orchestration_state():
|
|
168
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
169
|
-
|
|
170
|
-
result = get_orchestration_state_result(
|
|
171
|
-
context_builder, generator_function)
|
|
172
|
-
|
|
173
|
-
expected_state = base_expected_state()
|
|
174
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
175
|
-
expected = expected_state.to_json()
|
|
176
|
-
|
|
177
|
-
assert_valid_schema(result)
|
|
178
|
-
assert_orchestration_state_equals(expected, result)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def test_tokyo_state():
|
|
182
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
183
|
-
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
184
|
-
|
|
185
|
-
result = get_orchestration_state_result(
|
|
186
|
-
context_builder, generator_function)
|
|
187
|
-
|
|
188
|
-
expected_state = base_expected_state()
|
|
189
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
190
|
-
add_hello_action(expected_state, 'Seattle')
|
|
191
|
-
expected = expected_state.to_json()
|
|
192
|
-
|
|
193
|
-
assert_valid_schema(result)
|
|
194
|
-
assert_orchestration_state_equals(expected, result)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def test_failed_tokyo_with_retry():
|
|
198
|
-
failed_reason = 'Reasons'
|
|
199
|
-
failed_details = 'Stuff and Things'
|
|
200
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
201
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
202
|
-
|
|
203
|
-
result = get_orchestration_state_result(
|
|
204
|
-
context_builder, generator_function)
|
|
205
|
-
|
|
206
|
-
expected_state = base_expected_state()
|
|
207
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
208
|
-
expected = expected_state.to_json()
|
|
209
|
-
|
|
210
|
-
assert_valid_schema(result)
|
|
211
|
-
assert_orchestration_state_equals(expected, result)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def test_failed_tokyo_with_timer_entry():
|
|
215
|
-
failed_reason = 'Reasons'
|
|
216
|
-
failed_details = 'Stuff and Things'
|
|
217
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
218
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
219
|
-
add_retry_timer_events(context_builder, 1)
|
|
220
|
-
|
|
221
|
-
result = get_orchestration_state_result(
|
|
222
|
-
context_builder, generator_function)
|
|
223
|
-
|
|
224
|
-
expected_state = base_expected_state()
|
|
225
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
226
|
-
expected = expected_state.to_json()
|
|
227
|
-
|
|
228
|
-
assert_valid_schema(result)
|
|
229
|
-
assert_orchestration_state_equals(expected, result)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def test_failed_tokyo_with_failed_retry():
|
|
233
|
-
failed_reason = 'Reasons'
|
|
234
|
-
failed_details = 'Stuff and Things'
|
|
235
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
236
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
237
|
-
add_retry_timer_events(context_builder, 1)
|
|
238
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
239
|
-
|
|
240
|
-
result = get_orchestration_state_result(
|
|
241
|
-
context_builder, generator_function)
|
|
242
|
-
|
|
243
|
-
expected_state = base_expected_state()
|
|
244
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
245
|
-
expected = expected_state.to_json()
|
|
246
|
-
|
|
247
|
-
assert_valid_schema(result)
|
|
248
|
-
assert_orchestration_state_equals(expected, result)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def test_failed_tokyo_with_failed_retry_timer_added():
|
|
252
|
-
failed_reason = 'Reasons'
|
|
253
|
-
failed_details = 'Stuff and Things'
|
|
254
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
255
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
256
|
-
add_retry_timer_events(context_builder, 1)
|
|
257
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
258
|
-
add_retry_timer_events(context_builder, 3)
|
|
259
|
-
|
|
260
|
-
result = get_orchestration_state_result(
|
|
261
|
-
context_builder, generator_function)
|
|
262
|
-
|
|
263
|
-
expected_state = base_expected_state()
|
|
264
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
265
|
-
expected = expected_state.to_json()
|
|
266
|
-
|
|
267
|
-
assert_valid_schema(result)
|
|
268
|
-
assert_orchestration_state_equals(expected, result)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def test_successful_tokyo_with_failed_retry_timer_added():
|
|
272
|
-
failed_reason = 'Reasons'
|
|
273
|
-
failed_details = 'Stuff and Things'
|
|
274
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
275
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
276
|
-
add_retry_timer_events(context_builder, 1)
|
|
277
|
-
add_hello_completed_events(context_builder, 2, "\"Hello Tokyo!\"")
|
|
278
|
-
|
|
279
|
-
result = get_orchestration_state_result(
|
|
280
|
-
context_builder, generator_function)
|
|
281
|
-
|
|
282
|
-
expected_state = base_expected_state()
|
|
283
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
284
|
-
add_hello_action(expected_state, 'Seattle')
|
|
285
|
-
expected = expected_state.to_json()
|
|
286
|
-
|
|
287
|
-
assert_valid_schema(result)
|
|
288
|
-
assert_orchestration_state_equals(expected, result)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def test_failed_tokyo_hit_max_attempts():
|
|
292
|
-
failed_reason = 'Reasons'
|
|
293
|
-
failed_details = 'Stuff and Things'
|
|
294
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
295
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
296
|
-
add_retry_timer_events(context_builder, 1)
|
|
297
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
298
|
-
add_retry_timer_events(context_builder, 3)
|
|
299
|
-
add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
|
|
300
|
-
add_retry_timer_events(context_builder, 5)
|
|
301
|
-
|
|
302
|
-
try:
|
|
303
|
-
result = get_orchestration_state_result(
|
|
304
|
-
context_builder, generator_function)
|
|
305
|
-
# expected an exception
|
|
306
|
-
assert False
|
|
307
|
-
except Exception as e:
|
|
308
|
-
error_label = "\n\n$OutOfProcData$:"
|
|
309
|
-
error_str = str(e)
|
|
310
|
-
|
|
311
|
-
expected_state = base_expected_state()
|
|
312
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
313
|
-
|
|
314
|
-
error_msg = f'{failed_reason} \n {failed_details}'
|
|
315
|
-
expected_state._error = error_msg
|
|
316
|
-
state_str = expected_state.to_json_string()
|
|
317
|
-
|
|
318
|
-
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
319
|
-
assert expected_error_str == error_str
|
|
320
|
-
|
|
321
|
-
def test_failed_tokyo_hit_max_attempts_in_try_catch():
|
|
322
|
-
# This test ensures that APIs can still be invoked after a failed CallActivityWithRetry invocation
|
|
323
|
-
failed_reason = 'Reasons'
|
|
324
|
-
failed_details = 'Stuff and Things'
|
|
325
|
-
context_builder = ContextBuilder('test_simple_function')
|
|
326
|
-
|
|
327
|
-
# events for first task: "Hello Tokyo"
|
|
328
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
329
|
-
add_retry_timer_events(context_builder, 1)
|
|
330
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
331
|
-
add_retry_timer_events(context_builder, 3)
|
|
332
|
-
add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
|
|
333
|
-
# we have an "extra" timer to wait for, due to legacy behavior in DTFx.
|
|
334
|
-
add_retry_timer_events(context_builder, 5)
|
|
335
|
-
|
|
336
|
-
# events to task in except block
|
|
337
|
-
add_hello_completed_events(context_builder, 6, "\"Hello Seattle!\"")
|
|
338
|
-
|
|
339
|
-
result = get_orchestration_state_result(
|
|
340
|
-
context_builder, generator_function_try_catch)
|
|
341
|
-
|
|
342
|
-
expected_state = base_expected_state()
|
|
343
|
-
add_hello_action(expected_state, 'Tokyo')
|
|
344
|
-
add_hello_action(expected_state, 'Seattle')
|
|
345
|
-
expected_state._output = "Hello Seattle!"
|
|
346
|
-
expected_state._is_done = True
|
|
347
|
-
expected = expected_state.to_json()
|
|
348
|
-
|
|
349
|
-
assert_valid_schema(result)
|
|
350
|
-
assert_orchestration_state_equals(expected, result)
|
|
351
|
-
|
|
352
|
-
def test_concurrent_retriable_results():
|
|
353
|
-
failed_reason = 'Reasons'
|
|
354
|
-
failed_details = 'Stuff and Things'
|
|
355
|
-
context_builder = ContextBuilder('test_concurrent_retriable')
|
|
356
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
357
|
-
add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
|
|
358
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
359
|
-
add_retry_timer_events(context_builder, 3)
|
|
360
|
-
add_retry_timer_events(context_builder, 4)
|
|
361
|
-
add_retry_timer_events(context_builder, 5)
|
|
362
|
-
add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
|
|
363
|
-
add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
|
|
364
|
-
add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
|
|
365
|
-
|
|
366
|
-
result = get_orchestration_state_result(
|
|
367
|
-
context_builder, generator_function_concurrent_retries)
|
|
368
|
-
|
|
369
|
-
expected_state = base_expected_state()
|
|
370
|
-
add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
|
|
371
|
-
expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
|
372
|
-
expected_state._is_done = True
|
|
373
|
-
expected = expected_state.to_json()
|
|
374
|
-
|
|
375
|
-
assert_valid_schema(result)
|
|
376
|
-
assert_orchestration_state_equals(expected, result)
|
|
377
|
-
|
|
378
|
-
def test_concurrent_retriable_results_unordered_arrival():
|
|
379
|
-
failed_reason = 'Reasons'
|
|
380
|
-
failed_details = 'Stuff and Things'
|
|
381
|
-
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
382
|
-
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
383
|
-
add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
|
|
384
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
385
|
-
add_retry_timer_events(context_builder, 3)
|
|
386
|
-
add_retry_timer_events(context_builder, 4)
|
|
387
|
-
add_retry_timer_events(context_builder, 5)
|
|
388
|
-
# events arrive in non-sequential different order
|
|
389
|
-
add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
|
|
390
|
-
add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
|
|
391
|
-
add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
|
|
392
|
-
|
|
393
|
-
result = get_orchestration_state_result(
|
|
394
|
-
context_builder, generator_function_concurrent_retries)
|
|
395
|
-
|
|
396
|
-
expected_state = base_expected_state()
|
|
397
|
-
add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
|
|
398
|
-
expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
|
399
|
-
expected_state._is_done = True
|
|
400
|
-
expected = expected_state.to_json()
|
|
401
|
-
|
|
402
|
-
assert_valid_schema(result)
|
|
403
|
-
assert_orchestration_state_equals(expected, result)
|
|
404
|
-
|
|
405
|
-
def test_concurrent_retriable_results_mixed_arrival():
|
|
406
|
-
failed_reason = 'Reasons'
|
|
407
|
-
failed_details = 'Stuff and Things'
|
|
408
|
-
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
409
|
-
# one task succeeds, the other two fail at first, and succeed on retry
|
|
410
|
-
add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
|
|
411
|
-
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
412
|
-
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
413
|
-
add_retry_timer_events(context_builder, 3)
|
|
414
|
-
add_retry_timer_events(context_builder, 4)
|
|
415
|
-
add_hello_completed_events(context_builder, 6, "\"Hello London!\"")
|
|
416
|
-
add_hello_completed_events(context_builder, 5, "\"Hello Seattle!\"")
|
|
417
|
-
|
|
418
|
-
result = get_orchestration_state_result(
|
|
419
|
-
context_builder, generator_function_concurrent_retries)
|
|
420
|
-
|
|
421
|
-
expected_state = base_expected_state()
|
|
422
|
-
add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
|
|
423
|
-
expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
|
424
|
-
expected_state._is_done = True
|
|
425
|
-
expected = expected_state.to_json()
|
|
426
|
-
|
|
427
|
-
assert_valid_schema(result)
|
|
428
|
-
assert_orchestration_state_equals(expected, result)
|
|
429
|
-
|
|
430
|
-
def test_concurrent_retriable_results_alternating_taskIDs_when_all():
|
|
431
|
-
failed_reason = 'Reasons'
|
|
432
|
-
failed_details = 'Stuff and Things'
|
|
433
|
-
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
434
|
-
|
|
435
|
-
add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
|
|
436
|
-
|
|
437
|
-
result = get_orchestration_state_result(
|
|
438
|
-
context_builder, generator_function_two_concurrent_retries_when_all)
|
|
439
|
-
|
|
440
|
-
expected_state = base_expected_state()
|
|
441
|
-
add_hello_action(expected_state, ['Tokyo', 'Seattle'])
|
|
442
|
-
expected_state._output = ["Hello Tokyo!", "Hello Seattle!"]
|
|
443
|
-
expected_state._is_done = True
|
|
444
|
-
expected = expected_state.to_json()
|
|
445
|
-
|
|
446
|
-
assert_valid_schema(result)
|
|
447
|
-
assert_orchestration_state_equals(expected, result)
|
|
448
|
-
|
|
449
|
-
def test_concurrent_retriable_results_alternating_taskIDs_when_any():
|
|
450
|
-
failed_reason = 'Reasons'
|
|
451
|
-
failed_details = 'Stuff and Things'
|
|
452
|
-
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
453
|
-
|
|
454
|
-
add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
|
|
455
|
-
|
|
456
|
-
result = get_orchestration_state_result(
|
|
457
|
-
context_builder, generator_function_two_concurrent_retries_when_any)
|
|
458
|
-
|
|
459
|
-
expected_state = base_expected_state()
|
|
460
|
-
add_hello_action(expected_state, ['Tokyo', 'Seattle'])
|
|
461
|
-
expected_state._output = "Hello Seattle!"
|
|
462
|
-
expected_state._is_done = True
|
|
463
|
-
expected = expected_state.to_json()
|
|
464
|
-
|
|
465
|
-
assert_valid_schema(result)
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
from azure.durable_functions.models.ReplaySchema import ReplaySchema
|
|
3
|
+
from .orchestrator_test_utils \
|
|
4
|
+
import get_orchestration_state_result, assert_orchestration_state_equals, assert_valid_schema
|
|
5
|
+
from tests.test_utils.ContextBuilder import ContextBuilder
|
|
6
|
+
from azure.durable_functions.models.OrchestratorState import OrchestratorState
|
|
7
|
+
from azure.durable_functions.models.RetryOptions import RetryOptions
|
|
8
|
+
from azure.durable_functions.models.actions.CallActivityWithRetryAction \
|
|
9
|
+
import CallActivityWithRetryAction
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
RETRY_OPTIONS = RetryOptions(5000, 3)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def generator_function(context):
|
|
16
|
+
outputs = []
|
|
17
|
+
|
|
18
|
+
retry_options = RETRY_OPTIONS
|
|
19
|
+
task1 = yield context.call_activity_with_retry(
|
|
20
|
+
"Hello", retry_options, "Tokyo")
|
|
21
|
+
task2 = yield context.call_activity_with_retry(
|
|
22
|
+
"Hello", retry_options, "Seattle")
|
|
23
|
+
task3 = yield context.call_activity_with_retry(
|
|
24
|
+
"Hello", retry_options, "London")
|
|
25
|
+
|
|
26
|
+
outputs.append(task1)
|
|
27
|
+
outputs.append(task2)
|
|
28
|
+
outputs.append(task3)
|
|
29
|
+
|
|
30
|
+
return outputs
|
|
31
|
+
|
|
32
|
+
def generator_function_try_catch(context):
|
|
33
|
+
outputs = []
|
|
34
|
+
|
|
35
|
+
retry_options = RETRY_OPTIONS
|
|
36
|
+
result = None
|
|
37
|
+
try:
|
|
38
|
+
result = yield context.call_activity_with_retry(
|
|
39
|
+
"Hello", retry_options, "Tokyo")
|
|
40
|
+
except:
|
|
41
|
+
result = yield context.call_activity_with_retry(
|
|
42
|
+
"Hello", retry_options, "Seattle")
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
def generator_function_concurrent_retries(context):
|
|
46
|
+
outputs = []
|
|
47
|
+
|
|
48
|
+
retry_options = RETRY_OPTIONS
|
|
49
|
+
task1 = context.call_activity_with_retry(
|
|
50
|
+
"Hello", retry_options, "Tokyo")
|
|
51
|
+
task2 = context.call_activity_with_retry(
|
|
52
|
+
"Hello", retry_options, "Seattle")
|
|
53
|
+
task3 = context.call_activity_with_retry(
|
|
54
|
+
"Hello", retry_options, "London")
|
|
55
|
+
|
|
56
|
+
outputs = yield context.task_all([task1, task2, task3])
|
|
57
|
+
|
|
58
|
+
return outputs
|
|
59
|
+
|
|
60
|
+
def generator_function_two_concurrent_retries_when_all(context):
|
|
61
|
+
outputs = []
|
|
62
|
+
|
|
63
|
+
retry_options = RETRY_OPTIONS
|
|
64
|
+
task1 = context.call_activity_with_retry(
|
|
65
|
+
"Hello", retry_options, "Tokyo")
|
|
66
|
+
task2 = context.call_activity_with_retry(
|
|
67
|
+
"Hello", retry_options, "Seattle")
|
|
68
|
+
|
|
69
|
+
outputs = yield context.task_all([task1, task2])
|
|
70
|
+
|
|
71
|
+
return outputs
|
|
72
|
+
|
|
73
|
+
def generator_function_two_concurrent_retries_when_any(context):
|
|
74
|
+
outputs = []
|
|
75
|
+
|
|
76
|
+
retry_options = RETRY_OPTIONS
|
|
77
|
+
task1 = context.call_activity_with_retry(
|
|
78
|
+
"Hello", retry_options, "Tokyo")
|
|
79
|
+
task2 = context.call_activity_with_retry(
|
|
80
|
+
"Hello", retry_options, "Seattle")
|
|
81
|
+
|
|
82
|
+
outputs = yield context.task_any([task1, task2])
|
|
83
|
+
|
|
84
|
+
return outputs.result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
|
|
88
|
+
return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema.value)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def add_hello_action(state: OrchestratorState, input_: Union[List[str], str]):
|
|
92
|
+
retry_options = RETRY_OPTIONS
|
|
93
|
+
actions = []
|
|
94
|
+
inputs = input_
|
|
95
|
+
if not isinstance(input_, list):
|
|
96
|
+
inputs = [input_]
|
|
97
|
+
for input_ in inputs:
|
|
98
|
+
action = CallActivityWithRetryAction(
|
|
99
|
+
function_name='Hello', retry_options=retry_options, input_=input_)
|
|
100
|
+
actions.append(action)
|
|
101
|
+
state._actions.append(actions)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def add_hello_failed_events(
|
|
105
|
+
context_builder: ContextBuilder, id_: int, reason: str, details: str):
|
|
106
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=id_)
|
|
107
|
+
context_builder.add_orchestrator_completed_event()
|
|
108
|
+
context_builder.add_orchestrator_started_event()
|
|
109
|
+
context_builder.add_task_failed_event(
|
|
110
|
+
id_=id_, reason=reason, details=details)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def add_hello_completed_events(
|
|
114
|
+
context_builder: ContextBuilder, id_: int, result: str):
|
|
115
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=id_)
|
|
116
|
+
context_builder.add_orchestrator_completed_event()
|
|
117
|
+
context_builder.add_orchestrator_started_event()
|
|
118
|
+
context_builder.add_task_completed_event(id_=id_, result=result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def add_retry_timer_events(context_builder: ContextBuilder, id_: int):
|
|
122
|
+
fire_at = context_builder.add_timer_created_event(id_)
|
|
123
|
+
context_builder.add_orchestrator_completed_event()
|
|
124
|
+
context_builder.add_orchestrator_started_event()
|
|
125
|
+
context_builder.add_timer_fired_event(id_=id_, fire_at=fire_at)
|
|
126
|
+
|
|
127
|
+
def add_two_retriable_events_completing_out_of_order(context_builder: ContextBuilder,
|
|
128
|
+
failed_reason, failed_details):
|
|
129
|
+
## Schedule tasks
|
|
130
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=0) # Tokyo task
|
|
131
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=1) # Seattle task
|
|
132
|
+
|
|
133
|
+
context_builder.add_orchestrator_completed_event()
|
|
134
|
+
context_builder.add_orchestrator_started_event()
|
|
135
|
+
|
|
136
|
+
## Task failures and timer-scheduling
|
|
137
|
+
|
|
138
|
+
# tasks fail "out of order"
|
|
139
|
+
context_builder.add_task_failed_event(
|
|
140
|
+
id_=1, reason=failed_reason, details=failed_details) # Seattle task
|
|
141
|
+
fire_at_1 = context_builder.add_timer_created_event(2) # Seattle timer
|
|
142
|
+
|
|
143
|
+
context_builder.add_orchestrator_completed_event()
|
|
144
|
+
context_builder.add_orchestrator_started_event()
|
|
145
|
+
|
|
146
|
+
context_builder.add_task_failed_event(
|
|
147
|
+
id_=0, reason=failed_reason, details=failed_details) # Tokyo task
|
|
148
|
+
fire_at_2 = context_builder.add_timer_created_event(3) # Tokyo timer
|
|
149
|
+
|
|
150
|
+
context_builder.add_orchestrator_completed_event()
|
|
151
|
+
context_builder.add_orchestrator_started_event()
|
|
152
|
+
|
|
153
|
+
## fire timers
|
|
154
|
+
context_builder.add_timer_fired_event(id_=2, fire_at=fire_at_1) # Seattle timer
|
|
155
|
+
context_builder.add_timer_fired_event(id_=3, fire_at=fire_at_2) # Tokyo timer
|
|
156
|
+
|
|
157
|
+
## Complete events
|
|
158
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=4) # Seattle task
|
|
159
|
+
context_builder.add_task_scheduled_event(name='Hello', id_=5) # Tokyo task
|
|
160
|
+
|
|
161
|
+
context_builder.add_orchestrator_completed_event()
|
|
162
|
+
context_builder.add_orchestrator_started_event()
|
|
163
|
+
context_builder.add_task_completed_event(id_=4, result="\"Hello Seattle!\"")
|
|
164
|
+
context_builder.add_task_completed_event(id_=5, result="\"Hello Tokyo!\"")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_initial_orchestration_state():
|
|
168
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
169
|
+
|
|
170
|
+
result = get_orchestration_state_result(
|
|
171
|
+
context_builder, generator_function)
|
|
172
|
+
|
|
173
|
+
expected_state = base_expected_state()
|
|
174
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
175
|
+
expected = expected_state.to_json()
|
|
176
|
+
|
|
177
|
+
assert_valid_schema(result)
|
|
178
|
+
assert_orchestration_state_equals(expected, result)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_tokyo_state():
|
|
182
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
183
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
184
|
+
|
|
185
|
+
result = get_orchestration_state_result(
|
|
186
|
+
context_builder, generator_function)
|
|
187
|
+
|
|
188
|
+
expected_state = base_expected_state()
|
|
189
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
190
|
+
add_hello_action(expected_state, 'Seattle')
|
|
191
|
+
expected = expected_state.to_json()
|
|
192
|
+
|
|
193
|
+
assert_valid_schema(result)
|
|
194
|
+
assert_orchestration_state_equals(expected, result)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_failed_tokyo_with_retry():
|
|
198
|
+
failed_reason = 'Reasons'
|
|
199
|
+
failed_details = 'Stuff and Things'
|
|
200
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
201
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
202
|
+
|
|
203
|
+
result = get_orchestration_state_result(
|
|
204
|
+
context_builder, generator_function)
|
|
205
|
+
|
|
206
|
+
expected_state = base_expected_state()
|
|
207
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
208
|
+
expected = expected_state.to_json()
|
|
209
|
+
|
|
210
|
+
assert_valid_schema(result)
|
|
211
|
+
assert_orchestration_state_equals(expected, result)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_failed_tokyo_with_timer_entry():
|
|
215
|
+
failed_reason = 'Reasons'
|
|
216
|
+
failed_details = 'Stuff and Things'
|
|
217
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
218
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
219
|
+
add_retry_timer_events(context_builder, 1)
|
|
220
|
+
|
|
221
|
+
result = get_orchestration_state_result(
|
|
222
|
+
context_builder, generator_function)
|
|
223
|
+
|
|
224
|
+
expected_state = base_expected_state()
|
|
225
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
226
|
+
expected = expected_state.to_json()
|
|
227
|
+
|
|
228
|
+
assert_valid_schema(result)
|
|
229
|
+
assert_orchestration_state_equals(expected, result)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_failed_tokyo_with_failed_retry():
|
|
233
|
+
failed_reason = 'Reasons'
|
|
234
|
+
failed_details = 'Stuff and Things'
|
|
235
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
236
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
237
|
+
add_retry_timer_events(context_builder, 1)
|
|
238
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
239
|
+
|
|
240
|
+
result = get_orchestration_state_result(
|
|
241
|
+
context_builder, generator_function)
|
|
242
|
+
|
|
243
|
+
expected_state = base_expected_state()
|
|
244
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
245
|
+
expected = expected_state.to_json()
|
|
246
|
+
|
|
247
|
+
assert_valid_schema(result)
|
|
248
|
+
assert_orchestration_state_equals(expected, result)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_failed_tokyo_with_failed_retry_timer_added():
|
|
252
|
+
failed_reason = 'Reasons'
|
|
253
|
+
failed_details = 'Stuff and Things'
|
|
254
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
255
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
256
|
+
add_retry_timer_events(context_builder, 1)
|
|
257
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
258
|
+
add_retry_timer_events(context_builder, 3)
|
|
259
|
+
|
|
260
|
+
result = get_orchestration_state_result(
|
|
261
|
+
context_builder, generator_function)
|
|
262
|
+
|
|
263
|
+
expected_state = base_expected_state()
|
|
264
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
265
|
+
expected = expected_state.to_json()
|
|
266
|
+
|
|
267
|
+
assert_valid_schema(result)
|
|
268
|
+
assert_orchestration_state_equals(expected, result)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def test_successful_tokyo_with_failed_retry_timer_added():
|
|
272
|
+
failed_reason = 'Reasons'
|
|
273
|
+
failed_details = 'Stuff and Things'
|
|
274
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
275
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
276
|
+
add_retry_timer_events(context_builder, 1)
|
|
277
|
+
add_hello_completed_events(context_builder, 2, "\"Hello Tokyo!\"")
|
|
278
|
+
|
|
279
|
+
result = get_orchestration_state_result(
|
|
280
|
+
context_builder, generator_function)
|
|
281
|
+
|
|
282
|
+
expected_state = base_expected_state()
|
|
283
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
284
|
+
add_hello_action(expected_state, 'Seattle')
|
|
285
|
+
expected = expected_state.to_json()
|
|
286
|
+
|
|
287
|
+
assert_valid_schema(result)
|
|
288
|
+
assert_orchestration_state_equals(expected, result)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def test_failed_tokyo_hit_max_attempts():
|
|
292
|
+
failed_reason = 'Reasons'
|
|
293
|
+
failed_details = 'Stuff and Things'
|
|
294
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
295
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
296
|
+
add_retry_timer_events(context_builder, 1)
|
|
297
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
298
|
+
add_retry_timer_events(context_builder, 3)
|
|
299
|
+
add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
|
|
300
|
+
add_retry_timer_events(context_builder, 5)
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
result = get_orchestration_state_result(
|
|
304
|
+
context_builder, generator_function)
|
|
305
|
+
# expected an exception
|
|
306
|
+
assert False
|
|
307
|
+
except Exception as e:
|
|
308
|
+
error_label = "\n\n$OutOfProcData$:"
|
|
309
|
+
error_str = str(e)
|
|
310
|
+
|
|
311
|
+
expected_state = base_expected_state()
|
|
312
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
313
|
+
|
|
314
|
+
error_msg = f'{failed_reason} \n {failed_details}'
|
|
315
|
+
expected_state._error = error_msg
|
|
316
|
+
state_str = expected_state.to_json_string()
|
|
317
|
+
|
|
318
|
+
expected_error_str = f"{error_msg}{error_label}{state_str}"
|
|
319
|
+
assert expected_error_str == error_str
|
|
320
|
+
|
|
321
|
+
def test_failed_tokyo_hit_max_attempts_in_try_catch():
|
|
322
|
+
# This test ensures that APIs can still be invoked after a failed CallActivityWithRetry invocation
|
|
323
|
+
failed_reason = 'Reasons'
|
|
324
|
+
failed_details = 'Stuff and Things'
|
|
325
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
326
|
+
|
|
327
|
+
# events for first task: "Hello Tokyo"
|
|
328
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
329
|
+
add_retry_timer_events(context_builder, 1)
|
|
330
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
331
|
+
add_retry_timer_events(context_builder, 3)
|
|
332
|
+
add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
|
|
333
|
+
# we have an "extra" timer to wait for, due to legacy behavior in DTFx.
|
|
334
|
+
add_retry_timer_events(context_builder, 5)
|
|
335
|
+
|
|
336
|
+
# events to task in except block
|
|
337
|
+
add_hello_completed_events(context_builder, 6, "\"Hello Seattle!\"")
|
|
338
|
+
|
|
339
|
+
result = get_orchestration_state_result(
|
|
340
|
+
context_builder, generator_function_try_catch)
|
|
341
|
+
|
|
342
|
+
expected_state = base_expected_state()
|
|
343
|
+
add_hello_action(expected_state, 'Tokyo')
|
|
344
|
+
add_hello_action(expected_state, 'Seattle')
|
|
345
|
+
expected_state._output = "Hello Seattle!"
|
|
346
|
+
expected_state._is_done = True
|
|
347
|
+
expected = expected_state.to_json()
|
|
348
|
+
|
|
349
|
+
assert_valid_schema(result)
|
|
350
|
+
assert_orchestration_state_equals(expected, result)
|
|
351
|
+
|
|
352
|
+
def test_concurrent_retriable_results():
|
|
353
|
+
failed_reason = 'Reasons'
|
|
354
|
+
failed_details = 'Stuff and Things'
|
|
355
|
+
context_builder = ContextBuilder('test_concurrent_retriable')
|
|
356
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
357
|
+
add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
|
|
358
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
359
|
+
add_retry_timer_events(context_builder, 3)
|
|
360
|
+
add_retry_timer_events(context_builder, 4)
|
|
361
|
+
add_retry_timer_events(context_builder, 5)
|
|
362
|
+
add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
|
|
363
|
+
add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
|
|
364
|
+
add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
|
|
365
|
+
|
|
366
|
+
result = get_orchestration_state_result(
|
|
367
|
+
context_builder, generator_function_concurrent_retries)
|
|
368
|
+
|
|
369
|
+
expected_state = base_expected_state()
|
|
370
|
+
add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
|
|
371
|
+
expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
|
372
|
+
expected_state._is_done = True
|
|
373
|
+
expected = expected_state.to_json()
|
|
374
|
+
|
|
375
|
+
assert_valid_schema(result)
|
|
376
|
+
assert_orchestration_state_equals(expected, result)
|
|
377
|
+
|
|
378
|
+
def test_concurrent_retriable_results_unordered_arrival():
|
|
379
|
+
failed_reason = 'Reasons'
|
|
380
|
+
failed_details = 'Stuff and Things'
|
|
381
|
+
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
382
|
+
add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
|
|
383
|
+
add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
|
|
384
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
385
|
+
add_retry_timer_events(context_builder, 3)
|
|
386
|
+
add_retry_timer_events(context_builder, 4)
|
|
387
|
+
add_retry_timer_events(context_builder, 5)
|
|
388
|
+
# events arrive in non-sequential different order
|
|
389
|
+
add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
|
|
390
|
+
add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
|
|
391
|
+
add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
|
|
392
|
+
|
|
393
|
+
result = get_orchestration_state_result(
|
|
394
|
+
context_builder, generator_function_concurrent_retries)
|
|
395
|
+
|
|
396
|
+
expected_state = base_expected_state()
|
|
397
|
+
add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
|
|
398
|
+
expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
|
399
|
+
expected_state._is_done = True
|
|
400
|
+
expected = expected_state.to_json()
|
|
401
|
+
|
|
402
|
+
assert_valid_schema(result)
|
|
403
|
+
assert_orchestration_state_equals(expected, result)
|
|
404
|
+
|
|
405
|
+
def test_concurrent_retriable_results_mixed_arrival():
|
|
406
|
+
failed_reason = 'Reasons'
|
|
407
|
+
failed_details = 'Stuff and Things'
|
|
408
|
+
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
409
|
+
# one task succeeds, the other two fail at first, and succeed on retry
|
|
410
|
+
add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
|
|
411
|
+
add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
|
|
412
|
+
add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
|
|
413
|
+
add_retry_timer_events(context_builder, 3)
|
|
414
|
+
add_retry_timer_events(context_builder, 4)
|
|
415
|
+
add_hello_completed_events(context_builder, 6, "\"Hello London!\"")
|
|
416
|
+
add_hello_completed_events(context_builder, 5, "\"Hello Seattle!\"")
|
|
417
|
+
|
|
418
|
+
result = get_orchestration_state_result(
|
|
419
|
+
context_builder, generator_function_concurrent_retries)
|
|
420
|
+
|
|
421
|
+
expected_state = base_expected_state()
|
|
422
|
+
add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
|
|
423
|
+
expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
|
|
424
|
+
expected_state._is_done = True
|
|
425
|
+
expected = expected_state.to_json()
|
|
426
|
+
|
|
427
|
+
assert_valid_schema(result)
|
|
428
|
+
assert_orchestration_state_equals(expected, result)
|
|
429
|
+
|
|
430
|
+
def test_concurrent_retriable_results_alternating_taskIDs_when_all():
|
|
431
|
+
failed_reason = 'Reasons'
|
|
432
|
+
failed_details = 'Stuff and Things'
|
|
433
|
+
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
434
|
+
|
|
435
|
+
add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
|
|
436
|
+
|
|
437
|
+
result = get_orchestration_state_result(
|
|
438
|
+
context_builder, generator_function_two_concurrent_retries_when_all)
|
|
439
|
+
|
|
440
|
+
expected_state = base_expected_state()
|
|
441
|
+
add_hello_action(expected_state, ['Tokyo', 'Seattle'])
|
|
442
|
+
expected_state._output = ["Hello Tokyo!", "Hello Seattle!"]
|
|
443
|
+
expected_state._is_done = True
|
|
444
|
+
expected = expected_state.to_json()
|
|
445
|
+
|
|
446
|
+
assert_valid_schema(result)
|
|
447
|
+
assert_orchestration_state_equals(expected, result)
|
|
448
|
+
|
|
449
|
+
def test_concurrent_retriable_results_alternating_taskIDs_when_any():
|
|
450
|
+
failed_reason = 'Reasons'
|
|
451
|
+
failed_details = 'Stuff and Things'
|
|
452
|
+
context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
|
|
453
|
+
|
|
454
|
+
add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
|
|
455
|
+
|
|
456
|
+
result = get_orchestration_state_result(
|
|
457
|
+
context_builder, generator_function_two_concurrent_retries_when_any)
|
|
458
|
+
|
|
459
|
+
expected_state = base_expected_state()
|
|
460
|
+
add_hello_action(expected_state, ['Tokyo', 'Seattle'])
|
|
461
|
+
expected_state._output = "Hello Seattle!"
|
|
462
|
+
expected_state._is_done = True
|
|
463
|
+
expected = expected_state.to_json()
|
|
464
|
+
|
|
465
|
+
assert_valid_schema(result)
|
|
466
466
|
assert_orchestration_state_equals(expected, result)
|