|
7 | 7 |
|
8 | 8 | import java.util.List; |
9 | 9 | import java.util.concurrent.Executors; |
| 10 | +import java.util.concurrent.TimeUnit; |
10 | 11 | import java.util.concurrent.atomic.AtomicBoolean; |
11 | 12 | import java.util.function.Function; |
12 | 13 | import org.junit.jupiter.api.BeforeEach; |
|
20 | 21 | import software.amazon.lambda.durable.DurableConfig; |
21 | 22 | import software.amazon.lambda.durable.DurableContext; |
22 | 23 | import software.amazon.lambda.durable.TypeToken; |
| 24 | +import software.amazon.lambda.durable.config.ParallelConfig; |
23 | 25 | import software.amazon.lambda.durable.config.RunInChildContextConfig; |
24 | 26 | import software.amazon.lambda.durable.context.DurableContextImpl; |
25 | 27 | import software.amazon.lambda.durable.exception.ChildContextFailedException; |
@@ -126,6 +128,17 @@ private ChildContextOperation<String> createOperationWithParent( |
126 | 128 | parent); |
127 | 129 | } |
128 | 130 |
|
| 131 | + private ParallelOperation createCompletedParentOperation() { |
| 132 | + // Child completion synchronizes on the parent's future, which a constructor-free mock leaves null. |
| 133 | + var parent = new ParallelOperation( |
| 134 | + OperationIdentifier.of("parent", "parent-parallel", OperationSubType.PARALLEL), |
| 135 | + SERDES, |
| 136 | + durableContext, |
| 137 | + ParallelConfig.builder().build()); |
| 138 | + parent.markAlreadyCompleted(); |
| 139 | + return parent; |
| 140 | + } |
| 141 | + |
129 | 142 | // ===== SUCCEEDED replay ===== |
130 | 143 |
|
131 | 144 | /** SUCCEEDED replay returns cached result without re-executing the function. */ |
@@ -348,12 +361,13 @@ void replayWithNameMismatchTerminatesExecution() { |
348 | 361 | void childSkipsSuccessCheckpointWhenParentAlreadyCompleted() throws Exception { |
349 | 362 | when(executionManager.getOperationAndUpdateReplayState("1")).thenReturn(null); |
350 | 363 |
|
351 | | - var parent = mock(ConcurrencyOperation.class); |
352 | | - when(parent.isOperationCompleted()).thenReturn(true); |
| 364 | + var parent = createCompletedParentOperation(); |
353 | 365 |
|
354 | 366 | var operation = createOperationWithParent(ctx -> "result", parent); |
355 | 367 | operation.execute(); |
356 | | - Thread.sleep(200); |
| 368 | + operation.getRunningUserHandler().get(5, TimeUnit.SECONDS); |
| 369 | + |
| 370 | + assertTrue(operation.isOperationCompleted(), "Child should complete even when its parent has completed"); |
357 | 371 |
|
358 | 372 | // sendOperationUpdate should only be called once for START, not for SUCCEED |
359 | 373 | verify(executionManager, never()) |
@@ -399,16 +413,17 @@ void virtualChildSucceedsWhenResultValidationDisabled() throws Exception { |
399 | 413 | void childSkipsFailureCheckpointWhenParentAlreadyCompleted() throws Exception { |
400 | 414 | when(executionManager.getOperationAndUpdateReplayState("1")).thenReturn(null); |
401 | 415 |
|
402 | | - var parent = mock(ConcurrencyOperation.class); |
403 | | - when(parent.isOperationCompleted()).thenReturn(true); |
| 416 | + var parent = createCompletedParentOperation(); |
404 | 417 |
|
405 | 418 | var operation = createOperationWithParent( |
406 | 419 | ctx -> { |
407 | 420 | throw new RuntimeException("branch failed"); |
408 | 421 | }, |
409 | 422 | parent); |
410 | 423 | operation.execute(); |
411 | | - Thread.sleep(200); |
| 424 | + operation.getRunningUserHandler().get(5, TimeUnit.SECONDS); |
| 425 | + |
| 426 | + assertTrue(operation.isOperationCompleted(), "Failed child should complete even when its parent has completed"); |
412 | 427 |
|
413 | 428 | // sendOperationUpdate should not be called with FAIL action |
414 | 429 | verify(executionManager, never()) |
|
0 commit comments