|
| 1 | +from datetime import timedelta |
1 | 2 | import os |
2 | 3 | import time |
3 | 4 |
|
4 | 5 | import pytest |
| 6 | +from azure.identity import DefaultAzureCredential |
5 | 7 |
|
6 | 8 | from durabletask import client, entities, task |
7 | 9 | from durabletask.azuremanaged.client import DurableTaskSchedulerClient |
|
14 | 16 | # Read the environment variables |
15 | 17 | taskhub_name = os.getenv("TASKHUB", "default") |
16 | 18 | endpoint = os.getenv("ENDPOINT", "http://localhost:8080") |
| 19 | +# endpoint = os.getenv("ENDPOINT", "https://andy-dts-testin-byaje2c8.northcentralus.durabletask.io") |
| 20 | +credential = None if endpoint == "http://localhost:8080" else DefaultAzureCredential() |
17 | 21 |
|
18 | 22 |
|
19 | 23 | def test_client_signal_entity(): |
@@ -256,3 +260,99 @@ def empty_orchestrator(ctx: task.OrchestrationContext, _): |
256 | 260 | assert state.serialized_input is None |
257 | 261 | assert state.serialized_output is None |
258 | 262 | assert state.serialized_custom_status is None |
| 263 | + |
| 264 | + |
| 265 | +def test_entity_unlocks_when_user_code_throws(): |
| 266 | + invoke_count = 0 |
| 267 | + |
| 268 | + def empty_entity(ctx: entities.EntityContext, _): |
| 269 | + nonlocal invoke_count # don't do this in a real app! |
| 270 | + invoke_count += 1 |
| 271 | + |
| 272 | + def empty_orchestrator(ctx: task.OrchestrationContext, _): |
| 273 | + entity_id = entities.EntityInstanceId("empty_entity", "testEntity3") |
| 274 | + with (yield ctx.lock_entities([entity_id])): |
| 275 | + yield ctx.call_entity(entity_id, "do_nothing") |
| 276 | + raise Exception("Simulated exception") |
| 277 | + |
| 278 | + # Start a worker, which will connect to the sidecar in a background thread |
| 279 | + with DurableTaskSchedulerWorker(host_address=endpoint, secure_channel=True, |
| 280 | + taskhub=taskhub_name, token_credential=None) as w: |
| 281 | + w.add_orchestrator(empty_orchestrator) |
| 282 | + w.add_entity(empty_entity) |
| 283 | + w.start() |
| 284 | + |
| 285 | + c = DurableTaskSchedulerClient(host_address=endpoint, secure_channel=True, |
| 286 | + taskhub=taskhub_name, token_credential=None) |
| 287 | + time.sleep(2) # wait for the signal and orchestration to be processed |
| 288 | + id = c.schedule_new_orchestration(empty_orchestrator) |
| 289 | + c.wait_for_orchestration_completion(id, timeout=30) |
| 290 | + id = c.schedule_new_orchestration(empty_orchestrator) |
| 291 | + c.wait_for_orchestration_completion(id, timeout=30) |
| 292 | + |
| 293 | + assert invoke_count == 2 |
| 294 | + |
| 295 | + |
| 296 | +def test_entity_unlocks_when_user_mishandles_lock(): |
| 297 | + invoke_count = 0 |
| 298 | + |
| 299 | + def empty_entity(ctx: entities.EntityContext, _): |
| 300 | + nonlocal invoke_count # don't do this in a real app! |
| 301 | + invoke_count += 1 |
| 302 | + |
| 303 | + def empty_orchestrator(ctx: task.OrchestrationContext, _): |
| 304 | + entity_id = entities.EntityInstanceId("empty_entity", "testEntity3") |
| 305 | + yield ctx.lock_entities([entity_id]) |
| 306 | + yield ctx.call_entity(entity_id, "do_nothing") |
| 307 | + |
| 308 | + # Start a worker, which will connect to the sidecar in a background thread |
| 309 | + with DurableTaskSchedulerWorker(host_address=endpoint, secure_channel=True, |
| 310 | + taskhub=taskhub_name, token_credential=None) as w: |
| 311 | + w.add_orchestrator(empty_orchestrator) |
| 312 | + w.add_entity(empty_entity) |
| 313 | + w.start() |
| 314 | + |
| 315 | + c = DurableTaskSchedulerClient(host_address=endpoint, secure_channel=True, |
| 316 | + taskhub=taskhub_name, token_credential=None) |
| 317 | + time.sleep(2) # wait for the signal and orchestration to be processed |
| 318 | + id = c.schedule_new_orchestration(empty_orchestrator) |
| 319 | + c.wait_for_orchestration_completion(id, timeout=30) |
| 320 | + id = c.schedule_new_orchestration(empty_orchestrator) |
| 321 | + c.wait_for_orchestration_completion(id, timeout=30) |
| 322 | + |
| 323 | + assert invoke_count == 2 |
| 324 | + |
| 325 | + |
| 326 | +# TODO: Uncomment this test |
| 327 | +# Will not pass until https://msazure.visualstudio.com/One/_git/AAPT-DTMB/pullrequest/13610881 is merged and deployed to the docker image |
| 328 | +# def test_entity_unlocks_when_user_calls_continue_as_new(): |
| 329 | +# invoke_count = 0 |
| 330 | + |
| 331 | +# def empty_entity(ctx: entities.EntityContext, _): |
| 332 | +# nonlocal invoke_count # don't do this in a real app! |
| 333 | +# invoke_count += 1 |
| 334 | + |
| 335 | +# def empty_orchestrator(ctx: task.OrchestrationContext, entity_call_count: int): |
| 336 | +# entity_id = entities.EntityInstanceId("empty_entity", "testEntity6") |
| 337 | +# nonlocal invoke_count |
| 338 | +# if not ctx.is_replaying: |
| 339 | +# invoke_count += 1 |
| 340 | +# with (yield ctx.lock_entities([entity_id])): |
| 341 | +# yield ctx.call_entity(entity_id, "do_nothing") |
| 342 | +# if entity_call_count > 0: |
| 343 | +# ctx.continue_as_new(entity_call_count - 1, save_events=True) |
| 344 | + |
| 345 | +# # Start a worker, which will connect to the sidecar in a background thread |
| 346 | +# with DurableTaskSchedulerWorker(host_address=endpoint, secure_channel=True, |
| 347 | +# taskhub=taskhub_name, token_credential=credential) as w: |
| 348 | +# w.add_orchestrator(empty_orchestrator) |
| 349 | +# w.add_entity(empty_entity) |
| 350 | +# w.start() |
| 351 | + |
| 352 | +# c = DurableTaskSchedulerClient(host_address=endpoint, secure_channel=True, |
| 353 | +# taskhub=taskhub_name, token_credential=credential) |
| 354 | +# time.sleep(2) # wait for the signal and orchestration to be processed |
| 355 | +# id = c.schedule_new_orchestration(empty_orchestrator, input=2) |
| 356 | +# c.wait_for_orchestration_completion(id, timeout=500) |
| 357 | + |
| 358 | +# assert invoke_count == 6 |
0 commit comments