diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 193c2380..20635b43 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -46,14 +46,27 @@ jobs: # TODO: Move the sidecar into a central image repository - name: Initialize Durable Task Sidecar - run: docker run --name durabletask-sidecar -p 4001:4001 --env 'DURABLETASK_SIDECAR_LOGLEVEL=Debug' -d kaibocai/durabletask-sidecar:latest start --backend Emulator + run: docker run --name durabletask-sidecar -p 4001:4001 --env 'DURABLETASK_SIDECAR_LOGLEVEL=Debug' -d peterstone2019/durabletask-sidecar:latest start --backend Emulator + + - name: Display Durable Task Sidecar Logs + run: nohup docker logs --since=0 durabletask-sidecar > durabletask-sidecar.log 2>&1 & # wait for 10 seconds, so sidecar container can be fully up, this will avoid intermittent failing issues for integration tests causing by failed to connect to sidecar - name: Wait for 10 seconds run: sleep 10 - name: Integration Tests with Gradle - run: ./gradlew integrationTest + run: ./gradlew integrationTest || echo "TEST_FAILED=true" >> $GITHUB_ENV + continue-on-error: true + + - name: Kill Durable Task Sidecar + run: docker kill durabletask-sidecar + + - name: Upload Durable Task Sidecar Logs + uses: actions/upload-artifact@v4 + with: + name: Durable Task Sidecar Logs + path: durabletask-sidecar.log - name: Archive test report uses: actions/upload-artifact@v4 @@ -67,6 +80,10 @@ jobs: name: Package path: client/build/libs + - name: Fail the job if tests failed + if: env.TEST_FAILED == 'true' + run: exit 1 + functions-e2e-tests: needs: build @@ -124,7 +141,7 @@ jobs: uses: gradle/gradle-build-action@v2 - name: Publish to local - run: ./gradlew publishToMavenLocal -x sign + run: ./gradlew publishToMavenLocal - name: Build azure functions sample run: ./gradlew azureFunctionsPackage diff --git a/client/build.gradle b/client/build.gradle index e23fdc53..3f5ea75f 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -118,6 +118,8 @@ task integrationTest(type: Test) { dependsOn build shouldRunAfter test testLogging.showStandardStreams = true + + ignoreFailures = false } publishing { diff --git a/client/src/test/java/com/microsoft/durabletask/ErrorHandlingIntegrationTests.java b/client/src/test/java/com/microsoft/durabletask/ErrorHandlingIntegrationTests.java index 8920d67b..e2d4afa3 100644 --- a/client/src/test/java/com/microsoft/durabletask/ErrorHandlingIntegrationTests.java +++ b/client/src/test/java/com/microsoft/durabletask/ErrorHandlingIntegrationTests.java @@ -7,12 +7,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.extension.ExtendWith; import java.time.Duration; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.*; /** @@ -23,8 +25,15 @@ * client operations and sends invocation instructions to the DurableTaskWorker). */ @Tag("integration") +@ExtendWith(TestRetryExtension.class) public class ErrorHandlingIntegrationTests extends IntegrationTestBase { - @Test + @BeforeEach + private void startUp() { + DurableTaskClient client = new DurableTaskGrpcClientBuilder().build(); + client.deleteTaskHub(); + } + + @RetryingTest void orchestratorException() throws TimeoutException { final String orchestratorName = "OrchestratorWithException"; final String errorMessage = "Kah-BOOOOOM!!!"; @@ -50,7 +59,7 @@ void orchestratorException() throws TimeoutException { } } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(booleans = {true, false}) void activityException(boolean handleException) throws TimeoutException { final String orchestratorName = "OrchestratorWithActivityException"; @@ -102,7 +111,7 @@ void activityException(boolean handleException) throws TimeoutException { } } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(ints = {1, 2, 10}) public void retryActivityFailures(int maxNumberOfAttempts) throws TimeoutException { // There is one task for each activity call and one task between each retry @@ -116,7 +125,7 @@ public void retryActivityFailures(int maxNumberOfAttempts) throws TimeoutExcepti }); } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(ints = {1, 2, 10}) public void retryActivityFailuresWithCustomLogic(int maxNumberOfAttempts) throws TimeoutException { // This gets incremented every time the retry handler is invoked @@ -133,7 +142,7 @@ public void retryActivityFailuresWithCustomLogic(int maxNumberOfAttempts) throws assertEquals(maxNumberOfAttempts, retryHandlerCalls.get()); } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(booleans = {true, false}) void subOrchestrationException(boolean handleException) throws TimeoutException { final String orchestratorName = "OrchestrationWithBustedSubOrchestrator"; @@ -183,7 +192,7 @@ void subOrchestrationException(boolean handleException) throws TimeoutException } } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(ints = {1, 2, 10}) public void retrySubOrchestratorFailures(int maxNumberOfAttempts) throws TimeoutException { // There is one task for each sub-orchestrator call and one task between each retry @@ -198,7 +207,7 @@ public void retrySubOrchestratorFailures(int maxNumberOfAttempts) throws Timeout }); } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(ints = {1, 2, 10}) public void retrySubOrchestrationFailuresWithCustomLogic(int maxNumberOfAttempts) throws TimeoutException { // This gets incremented every time the retry handler is invoked diff --git a/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java b/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java index cc7a790a..5e9ac5d9 100644 --- a/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java +++ b/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java @@ -15,8 +15,10 @@ import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -31,11 +33,19 @@ * sends invocation instructions to the DurableTaskWorker). */ @Tag("integration") +@ExtendWith(TestRetryExtension.class) public class IntegrationTests extends IntegrationTestBase { static final Duration defaultTimeout = Duration.ofSeconds(100); // All tests that create a server should save it to this variable for proper shutdown private DurableTaskGrpcWorker server; + // Before whole test suite, delete the task hub + @BeforeEach + private void startUp() { + DurableTaskClient client = new DurableTaskGrpcClientBuilder().build(); + client.deleteTaskHub(); + } + @AfterEach private void shutdown() throws InterruptedException { if (this.server != null) { @@ -43,7 +53,7 @@ private void shutdown() throws InterruptedException { } } - @Test + @RetryingTest void emptyOrchestration() throws TimeoutException { final String orchestratorName = "EmptyOrchestration"; final String input = "Hello " + Instant.now(); @@ -66,7 +76,7 @@ void emptyOrchestration() throws TimeoutException { } } - @Test + @RetryingTest void singleTimer() throws IOException, TimeoutException { final String orchestratorName = "SingleTimer"; final Duration delay = Duration.ofSeconds(3); @@ -89,7 +99,7 @@ void singleTimer() throws IOException, TimeoutException { } } - @Test + @RetryingTest void longTimer() throws TimeoutException { final String orchestratorName = "LongTimer"; final Duration delay = Duration.ofSeconds(7); @@ -106,6 +116,7 @@ void longTimer() throws TimeoutException { DurableTaskClient client = new DurableTaskGrpcClientBuilder().build(); try (worker; client) { + client.createTaskHub(true); String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName); Duration timeout = delay.plus(defaultTimeout); OrchestrationMetadata instance = client.waitForInstanceCompletion(instanceId, timeout, false); @@ -132,7 +143,7 @@ void longTimer() throws TimeoutException { } } - @Test + @RetryingTest void longTimerNonblocking() throws TimeoutException { final String orchestratorName = "ActivityAnyOf"; final String externalEventActivityName = "externalEvent"; @@ -170,7 +181,7 @@ void longTimerNonblocking() throws TimeoutException { } } - @Test + @RetryingTest void longTimerNonblockingNoExternal() throws TimeoutException { final String orchestratorName = "ActivityAnyOf"; final String externalEventActivityName = "externalEvent"; @@ -207,7 +218,7 @@ void longTimerNonblockingNoExternal() throws TimeoutException { } - @Test + @RetryingTest void longTimeStampTimer() throws TimeoutException { final String orchestratorName = "LongTimeStampTimer"; final Duration delay = Duration.ofSeconds(7); @@ -241,7 +252,7 @@ void longTimeStampTimer() throws TimeoutException { } } - @Test + @RetryingTest void singleTimeStampTimer() throws IOException, TimeoutException { final String orchestratorName = "SingleTimeStampTimer"; final Duration delay = Duration.ofSeconds(3); @@ -265,7 +276,7 @@ void singleTimeStampTimer() throws IOException, TimeoutException { } } - @Test + @RetryingTest void isReplaying() throws IOException, InterruptedException, TimeoutException { final String orchestratorName = "SingleTimer"; DurableTaskGrpcWorker worker = this.createWorkerBuilder() @@ -301,7 +312,7 @@ void isReplaying() throws IOException, InterruptedException, TimeoutException { } } - @Test + @RetryingTest void singleActivity() throws IOException, InterruptedException, TimeoutException { final String orchestratorName = "SingleActivity"; final String activityName = "Echo"; @@ -333,7 +344,7 @@ void singleActivity() throws IOException, InterruptedException, TimeoutException } } - @Test + @RetryingTest void currentDateTimeUtc() throws IOException, TimeoutException { final String orchestratorName = "CurrentDateTimeUtc"; final String echoActivityName = "Echo"; @@ -372,7 +383,7 @@ void currentDateTimeUtc() throws IOException, TimeoutException { } } - @Test + @RetryingTest void activityChain() throws IOException, TimeoutException { final String orchestratorName = "ActivityChain"; final String plusOneActivityName = "PlusOne"; @@ -399,7 +410,7 @@ void activityChain() throws IOException, TimeoutException { } } - @Test + @RetryingTest void subOrchestration() throws TimeoutException { final String orchestratorName = "SubOrchestration"; DurableTaskGrpcWorker worker = this.createWorkerBuilder().addOrchestrator(orchestratorName, ctx -> { @@ -420,7 +431,7 @@ void subOrchestration() throws TimeoutException { } } - @Test + @RetryingTest void continueAsNew() throws TimeoutException { final String orchestratorName = "continueAsNew"; final Duration delay = Duration.ofSeconds(0); @@ -443,7 +454,7 @@ void continueAsNew() throws TimeoutException { } } - @Test + @RetryingTest void continueAsNewWithExternalEvents() throws TimeoutException, InterruptedException{ final String orchestratorName = "continueAsNewWithExternalEvents"; final String eventName = "MyEvent"; @@ -474,7 +485,7 @@ void continueAsNewWithExternalEvents() throws TimeoutException, InterruptedExcep } } - @Test + @RetryingTest void termination() throws TimeoutException { final String orchestratorName = "Termination"; final Duration delay = Duration.ofSeconds(3); @@ -496,7 +507,7 @@ void termination() throws TimeoutException { } } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(booleans = {true, false}) void restartOrchestrationWithNewInstanceId(boolean restartWithNewInstanceId) throws TimeoutException { final String orchestratorName = "restart"; @@ -523,7 +534,7 @@ void restartOrchestrationWithNewInstanceId(boolean restartWithNewInstanceId) thr } } - @Test + @RetryingTest void restartOrchestrationThrowsException() { final String orchestratorName = "restart"; final Duration delay = Duration.ofSeconds(3); @@ -585,7 +596,7 @@ void suspendResumeOrchestration() throws TimeoutException, InterruptedException } } - @Test + @RetryingTest void terminateSuspendOrchestration() throws TimeoutException, InterruptedException { final String orchestratorName = "suspendResume"; final String eventName = "MyEvent"; @@ -611,7 +622,7 @@ void terminateSuspendOrchestration() throws TimeoutException, InterruptedExcepti } } - @Test + @RetryingTest void activityFanOut() throws IOException, TimeoutException { final String orchestratorName = "ActivityFanOut"; final String activityName = "ToString"; @@ -653,7 +664,7 @@ void activityFanOut() throws IOException, TimeoutException { } } - @Test + @RetryingTest void externalEvents() throws IOException, TimeoutException { final String orchestratorName = "ExternalEvents"; final String eventName = "MyEvent"; @@ -692,7 +703,7 @@ void externalEvents() throws IOException, TimeoutException { } } - @ParameterizedTest + @RetryingParameterizedTest @ValueSource(booleans = {true, false}) void externalEventsWithTimeouts(boolean raiseEvent) throws IOException, TimeoutException { final String orchestratorName = "ExternalEventsWithTimeouts"; @@ -731,7 +742,7 @@ void externalEventsWithTimeouts(boolean raiseEvent) throws IOException, TimeoutE } } - @Test + @RetryingTest void setCustomStatus() throws TimeoutException { final String orchestratorName = "SetCustomStatus"; @@ -764,7 +775,7 @@ void setCustomStatus() throws TimeoutException { } } - @Test + @RetryingTest void clearCustomStatus() throws TimeoutException { final String orchestratorName = "ClearCustomStatus"; @@ -793,7 +804,8 @@ void clearCustomStatus() throws TimeoutException { } } - @Test + // due to clock drift, client/worker and sidecar time are not exactly synchronized, this test needs to accommodate for client vs backend timestamps difference + @RetryingTest void multiInstanceQuery() throws TimeoutException{ final String plusOne = "plusOne"; final String waitForEvent = "waitForEvent"; @@ -830,6 +842,11 @@ void multiInstanceQuery() throws TimeoutException{ } }); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + Instant sequencesFinishedTime = Instant.now(); IntStream.range(0, 5).mapToObj(i -> { @@ -853,28 +870,43 @@ void multiInstanceQuery() throws TimeoutException{ assertEquals(10, result.getOrchestrationState().size()); // Test CreatedTimeTo filter - query.setCreatedTimeTo(startTime); + query.setCreatedTimeTo(startTime.minus(Duration.ofSeconds(1))); result = client.queryInstances(query); - assertTrue(result.getOrchestrationState().isEmpty()); + assertTrue(result.getOrchestrationState().isEmpty(), + "Result should be empty but found " + result.getOrchestrationState().size() + " instances: " + + "Start time: " + startTime + ", " + + result.getOrchestrationState().stream() + .map(state -> String.format("\nID: %s, Status: %s, Created: %s", + state.getInstanceId(), + state.getRuntimeStatus(), + state.getCreatedAt())) + .collect(Collectors.joining(", "))); query.setCreatedTimeTo(sequencesFinishedTime); result = client.queryInstances(query); - assertEquals(5, result.getOrchestrationState().size()); + // Verify all returned instances contain "sequence" in their IDs + assertEquals(5, result.getOrchestrationState().stream() + .filter(state -> state.getInstanceId().contains("sequence")) + .count(), + "Expected exactly 5 instances with 'sequence' in their IDs"); - query.setCreatedTimeTo(Instant.now()); + query.setCreatedTimeTo(Instant.now().plus(Duration.ofSeconds(1))); result = client.queryInstances(query); assertEquals(10, result.getOrchestrationState().size()); // Test CreatedTimeFrom filter - query.setCreatedTimeFrom(Instant.now()); + query.setCreatedTimeFrom(Instant.now().plus(Duration.ofSeconds(1))); result = client.queryInstances(query); assertTrue(result.getOrchestrationState().isEmpty()); - query.setCreatedTimeFrom(sequencesFinishedTime); + query.setCreatedTimeFrom(sequencesFinishedTime.minus(Duration.ofSeconds(5))); result = client.queryInstances(query); - assertEquals(5, result.getOrchestrationState().size()); + assertEquals(5, result.getOrchestrationState().stream() + .filter(state -> state.getInstanceId().contains("sequence")) + .count(), + "Expected exactly 5 instances with 'sequence' in their IDs"); - query.setCreatedTimeFrom(startTime); + query.setCreatedTimeFrom(startTime.minus(Duration.ofSeconds(1))); result = client.queryInstances(query); assertEquals(10, result.getOrchestrationState().size()); @@ -954,7 +986,7 @@ void multiInstanceQuery() throws TimeoutException{ } } - @Test + @RetryingTest void purgeInstanceId() throws TimeoutException { final String orchestratorName = "PurgeInstance"; final String plusOneActivityName = "PlusOne"; @@ -985,7 +1017,7 @@ void purgeInstanceId() throws TimeoutException { } } - @Test + @RetryingTest void purgeInstanceFilter() throws TimeoutException { final String orchestratorName = "PurgeInstance"; final String plusOne = "PlusOne"; @@ -1028,7 +1060,7 @@ void purgeInstanceFilter() throws TimeoutException { // Test CreatedTimeFrom PurgeInstanceCriteria criteria = new PurgeInstanceCriteria(); - criteria.setCreatedTimeFrom(startTime); + criteria.setCreatedTimeFrom(startTime.minus(Duration.ofSeconds(1))); PurgeResult result = client.purgeInstances(criteria); assertEquals(1, result.getDeletedInstanceCount()); @@ -1082,7 +1114,7 @@ void purgeInstanceFilter() throws TimeoutException { } } - @Test + @RetryingTest void purgeInstanceFilterTimeout() throws TimeoutException { final String orchestratorName = "PurgeInstance"; final String plusOne = "PlusOne"; @@ -1139,7 +1171,7 @@ void purgeInstanceFilterTimeout() throws TimeoutException { } } - @Test() + @RetryingTest void waitForInstanceStartThrowsException() { final String orchestratorName = "orchestratorName"; @@ -1161,7 +1193,7 @@ void waitForInstanceStartThrowsException() { } } - @Test() + @RetryingTest void waitForInstanceCompletionThrowsException() { final String orchestratorName = "orchestratorName"; final String plusOneActivityName = "PlusOne"; @@ -1191,7 +1223,7 @@ void waitForInstanceCompletionThrowsException() { } } - @Test + @RetryingTest void activityFanOutWithException() throws TimeoutException { final String orchestratorName = "ActivityFanOut"; final String activityName = "Divide"; @@ -1248,7 +1280,7 @@ private static String getExceptionMessage(String taskName, int expectedTaskId, S expectedExceptionMessage); } - @Test + @RetryingTest void thenApply() throws IOException, InterruptedException, TimeoutException { final String orchestratorName = "thenApplyActivity"; final String activityName = "Echo"; @@ -1281,7 +1313,7 @@ void thenApply() throws IOException, InterruptedException, TimeoutException { } } - @Test + @RetryingTest void externalEventThenAccept() throws InterruptedException, TimeoutException { final String orchestratorName = "continueAsNewWithExternalEvents"; final String eventName = "MyEvent"; @@ -1315,7 +1347,7 @@ void externalEventThenAccept() throws InterruptedException, TimeoutException { } } - @Test + @RetryingTest void activityAllOf() throws IOException, TimeoutException { final String orchestratorName = "ActivityAllOf"; final String activityName = "ToString"; @@ -1374,7 +1406,7 @@ void activityAllOf() throws IOException, TimeoutException { } } - @Test + @RetryingTest void activityAllOfException() throws IOException, TimeoutException { final String orchestratorName = "ActivityAllOf"; final String activityName = "ToString"; @@ -1436,7 +1468,7 @@ void activityAllOfException() throws IOException, TimeoutException { } } - @Test + @RetryingTest void activityAnyOf() throws IOException, TimeoutException { final String orchestratorName = "ActivityAnyOf"; final String activityName = "ToString"; @@ -1485,7 +1517,7 @@ void activityAnyOf() throws IOException, TimeoutException { } } - @Test + @RetryingTest public void newUUIDTest() { String orchestratorName = "test-new-uuid"; String echoActivityName = "Echo"; diff --git a/client/src/test/java/com/microsoft/durabletask/RetryingParameterizedTest.java b/client/src/test/java/com/microsoft/durabletask/RetryingParameterizedTest.java new file mode 100644 index 00000000..aeb72ded --- /dev/null +++ b/client/src/test/java/com/microsoft/durabletask/RetryingParameterizedTest.java @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.durabletask; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for parameterized test methods that should be retried on failure. + * By default, tests will be retried up to 3 times. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ParameterizedTest +@ExtendWith(TestRetryExtension.class) +public @interface RetryingParameterizedTest { +} \ No newline at end of file diff --git a/client/src/test/java/com/microsoft/durabletask/RetryingTest.java b/client/src/test/java/com/microsoft/durabletask/RetryingTest.java new file mode 100644 index 00000000..da6a3ae5 --- /dev/null +++ b/client/src/test/java/com/microsoft/durabletask/RetryingTest.java @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.durabletask; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for test methods that should be retried on failure. + * By default, tests will be retried up to 3 times. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Test +@ExtendWith(TestRetryExtension.class) +public @interface RetryingTest { +} \ No newline at end of file diff --git a/client/src/test/java/com/microsoft/durabletask/TestRetryExtension.java b/client/src/test/java/com/microsoft/durabletask/TestRetryExtension.java new file mode 100644 index 00000000..405e0d3f --- /dev/null +++ b/client/src/test/java/com/microsoft/durabletask/TestRetryExtension.java @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.durabletask; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import java.util.logging.Logger; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * JUnit Jupiter extension that provides test retry capability. + * Tests will be retried up to 3 times before failing. + */ +public class TestRetryExtension implements TestExecutionExceptionHandler { + private static final Logger LOGGER = Logger.getLogger(TestRetryExtension.class.getName()); + private static final int MAX_RETRY_COUNT = 3; + private final ConcurrentHashMap retryCounters = new ConcurrentHashMap<>(); + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + String testMethod = getTestMethodName(context); + int retryCount = retryCounters.getOrDefault(testMethod, 0); + + if (retryCount < MAX_RETRY_COUNT - 1) { // -1 because the first attempt doesn't count as a retry + retryCounters.put(testMethod, retryCount + 1); + LOGGER.warning(String.format("Test '%s' failed (attempt %d). Retrying...", testMethod, retryCount + 1)); + // Return without rethrowing to allow retry + return; + } + + // Log final failure and rethrow the exception + LOGGER.severe(String.format("Test '%s' failed after %d retries", testMethod, MAX_RETRY_COUNT - 1)); + throw throwable; + } + + private String getTestMethodName(ExtensionContext context) { + String methodName = context.getRequiredTestMethod().getName(); + + // Include parameters for parameterized tests to ensure each parameter combination is retried separately + String params = context.getDisplayName(); + // If the display name contains parameters (e.g. "testMethod(param1, param2)"), extract them + if (params.contains("(") && params.endsWith(")")) { + params = params.substring(params.indexOf('(')); + } else { + params = ""; + } + + return context.getRequiredTestClass().getName() + "." + methodName + params; + } +} \ No newline at end of file diff --git a/endtoendtests/e2e-test-setup.ps1 b/endtoendtests/e2e-test-setup.ps1 index fe62ecfc..6e57d763 100644 --- a/endtoendtests/e2e-test-setup.ps1 +++ b/endtoendtests/e2e-test-setup.ps1 @@ -7,7 +7,7 @@ param( [string]$ContainerName="app", [switch]$NoSetup=$false, [switch]$NoValidation=$false, - [string]$AzuriteVersion="3.20.1", + [string]$AzuriteVersion="3.34.0", [int]$Sleep=30 ) diff --git a/endtoendtests/src/test/java/com/functions/EndToEndTests.java b/endtoendtests/src/test/java/com/functions/EndToEndTests.java index 53d40dcd..65b9fb70 100644 --- a/endtoendtests/src/test/java/com/functions/EndToEndTests.java +++ b/endtoendtests/src/test/java/com/functions/EndToEndTests.java @@ -168,7 +168,7 @@ public void thenChain() throws InterruptedException { Set continueStates = new HashSet<>(); continueStates.add("Pending"); continueStates.add("Running"); - final String expect = "AUSTIN-test"; + final String expect = "\"AUSTIN\"-test"; String startOrchestrationPath = "/api/StartOrchestrationThenChain"; Response response = post(startOrchestrationPath); JsonPath jsonPath = response.jsonPath(); diff --git a/samples-azure-functions/e2e-test-setup.ps1 b/samples-azure-functions/e2e-test-setup.ps1 index fe62ecfc..6e57d763 100644 --- a/samples-azure-functions/e2e-test-setup.ps1 +++ b/samples-azure-functions/e2e-test-setup.ps1 @@ -7,7 +7,7 @@ param( [string]$ContainerName="app", [switch]$NoSetup=$false, [switch]$NoValidation=$false, - [string]$AzuriteVersion="3.20.1", + [string]$AzuriteVersion="3.34.0", [int]$Sleep=30 ) diff --git a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java index 279854f2..a756b5db 100644 --- a/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java +++ b/samples-azure-functions/src/test/java/com/functions/EndToEndTests.java @@ -168,7 +168,7 @@ public void thenChain() throws InterruptedException { Set continueStates = new HashSet<>(); continueStates.add("Pending"); continueStates.add("Running"); - final String expect = "AUSTIN-test"; + final String expect = "\"AUSTIN\"-test"; String startOrchestrationPath = "/api/StartOrchestrationThenChain"; Response response = post(startOrchestrationPath); JsonPath jsonPath = response.jsonPath(); @@ -226,7 +226,7 @@ public void orchestrationPOJO() throws InterruptedException { assertTrue(pass); Response statusResponse = get(statusQueryGetUri); String outputName = statusResponse.jsonPath().get("output.name"); - assertEquals("TESTNAME", outputName); + assertEquals("\"TESTNAME\"", outputName); } private boolean pollingCheck(String statusQueryGetUri,