From fb859c50f7bb89a8f26c5a93b9a70db83bf45f23 Mon Sep 17 00:00:00 2001 From: kaibocai <89094811+kaibocai@users.noreply.github.com> Date: Thu, 18 Jan 2024 09:34:00 -0600 Subject: [PATCH] add tests --- .github/workflows/build-validation.yml | 9 + client/build.gradle | 11 ++ .../durabletask/IntegrationGoTests.java | 185 ++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 client/src/test/java/com/microsoft/durabletask/IntegrationGoTests.java diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 9262f86..88d1716 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -57,6 +57,15 @@ jobs: - name: Integration Tests with Gradle run: ./gradlew integrationTest + - name: Stop Durable Task Sidecar + run: docker stop durabletask-sidecar + + - name: Initialize Durable Task GO + run: docker run --name durabletask-go -p 4001:4001 -d kaibocai/durabletask-go:latest + + - name: Integration GO Tests with Gradle + run: ./gradlew integrationGoTest + - name: Archive test report uses: actions/upload-artifact@v2 with: diff --git a/client/build.gradle b/client/build.gradle index 2da2165..47460cd 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -80,6 +80,7 @@ test { // Skip tests tagged as "integration" since those are slower // and require external dependencies. excludeTags "integration" + excludeTags "integration-go" } } @@ -96,6 +97,16 @@ task integrationTest(type: Test) { testLogging.showStandardStreams = true } +// integration-go runs against sidecar durabletask-go +task integrationGoTest(type: Test) { + useJUnitPlatform { + includeTags 'integration-go' + } + dependsOn build + shouldRunAfter test + testLogging.showStandardStreams = true +} + publishing { repositories { maven { diff --git a/client/src/test/java/com/microsoft/durabletask/IntegrationGoTests.java b/client/src/test/java/com/microsoft/durabletask/IntegrationGoTests.java new file mode 100644 index 0000000..7c49287 --- /dev/null +++ b/client/src/test/java/com/microsoft/durabletask/IntegrationGoTests.java @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.microsoft.durabletask; + +import com.microsoft.durabletask.client.InstanceIdReuseAction; +import io.grpc.StatusRuntimeException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * These integration tests are designed to exercise the core, high-level features of + * the Durable Task programming model. + *
+ * These tests currently require a sidecar process to be + * running on the local machine (the sidecar is what accepts the client operations and + * sends invocation instructions to the DurableTaskWorker). + */ +@Tag("integration-go") +public class IntegrationGoTests 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; + + @AfterEach + private void shutdown() throws InterruptedException { + if (this.server != null) { + this.server.stop(); + } + } + + @Test + void singleActivityIgnore() throws TimeoutException { + final String orchestratorName = "SingleActivity"; + final String activityName = "Echo"; + DurableTaskGrpcWorker worker = this.createWorkerBuilder() + .addOrchestrator(orchestratorName, ctx -> { + String activityInput = ctx.getInput(String.class); + ctx.createTimer(Duration.ofSeconds(2)); + String output = ctx.callActivity(activityName, activityInput, String.class).await(); + ctx.complete(output); + }) + .addActivity(activityName, ctx -> { + return String.format("Hello, %s!", ctx.getInput(String.class)); + }) + .buildAndStart(); + + DurableTaskClient client = new DurableTaskGrpcClientBuilder().build(); + try (worker; client) { + final String instanceID = "SKIP_IF_RUNNING_OR_COMPLETED"; + NewOrchestrationInstanceOptions instanceOptions = new NewOrchestrationInstanceOptions(); + instanceOptions + .setInstanceId(instanceID) + .setInput("World") + .addTargetStatus(OrchestrationRuntimeStatus.RUNNING, OrchestrationRuntimeStatus.COMPLETED, OrchestrationRuntimeStatus.PENDING) + .setInstanceIdReuseAction(InstanceIdReuseAction.IGNORE); + + client.scheduleNewOrchestrationInstance(orchestratorName, "GO", instanceID); + client.waitForInstanceStart(instanceID, defaultTimeout); + long pivotTime = Instant.now().getEpochSecond(); + client.scheduleNewOrchestrationInstance(orchestratorName, instanceOptions); + OrchestrationMetadata instance = client.waitForInstanceCompletion( + instanceID, + defaultTimeout, + true); + + assertNotNull(instance); + assertEquals(OrchestrationRuntimeStatus.COMPLETED, instance.getRuntimeStatus()); + String output = instance.readOutputAs(String.class); + String expected = "Hello, GO!"; + assertEquals(expected, output); + + // Verify that the delay actually happened + long expectedCompletionSecond = instance.getCreatedAt().getEpochSecond(); + assertTrue(expectedCompletionSecond <= pivotTime); + } + } + + @Test + void singleActivityTerminate() throws TimeoutException { + final String orchestratorName = "SingleActivity"; + final String activityName = "Echo"; + DurableTaskGrpcWorker worker = this.createWorkerBuilder() + .addOrchestrator(orchestratorName, ctx -> { + String activityInput = ctx.getInput(String.class); + ctx.createTimer(Duration.ofSeconds(2)); + String output = ctx.callActivity(activityName, activityInput, String.class).await(); + ctx.complete(output); + }) + .addActivity(activityName, ctx -> { + return String.format("Hello, %s!", ctx.getInput(String.class)); + }) + .buildAndStart(); + + DurableTaskClient client = new DurableTaskGrpcClientBuilder().build(); + try (worker; client) { + final String instanceID = "TERMINATE_IF_RUNNING_OR_COMPLETED"; + NewOrchestrationInstanceOptions instanceOptions = new NewOrchestrationInstanceOptions(); + instanceOptions + .setInstanceId(instanceID) + .setInput("World") + .addTargetStatus(OrchestrationRuntimeStatus.RUNNING, OrchestrationRuntimeStatus.COMPLETED, OrchestrationRuntimeStatus.PENDING) + .setInstanceIdReuseAction(InstanceIdReuseAction.TERMINATE); + + client.scheduleNewOrchestrationInstance(orchestratorName, "GO", instanceID); + client.waitForInstanceStart(instanceID, defaultTimeout); + long pivotTime = Instant.now().getEpochSecond(); + client.scheduleNewOrchestrationInstance(orchestratorName, instanceOptions); + OrchestrationMetadata instance = client.waitForInstanceCompletion( + instanceID, + defaultTimeout, + true); + + assertNotNull(instance); + assertEquals(OrchestrationRuntimeStatus.COMPLETED, instance.getRuntimeStatus()); + String output = instance.readOutputAs(String.class); + String expected = "Hello, World!"; + assertEquals(expected, output); + + // Verify that the delay actually happened + long expectedCompletionSecond = instance.getCreatedAt().getEpochSecond(); + assertTrue(pivotTime <= expectedCompletionSecond); + } + } + + @Test + void singleActivityError() throws TimeoutException { + final String orchestratorName = "SingleActivity"; + final String activityName = "Echo"; + DurableTaskGrpcWorker worker = this.createWorkerBuilder() + .addOrchestrator(orchestratorName, ctx -> { + String activityInput = ctx.getInput(String.class); + ctx.createTimer(Duration.ofSeconds(2)); + String output = ctx.callActivity(activityName, activityInput, String.class).await(); + ctx.complete(output); + }) + .addActivity(activityName, ctx -> { + return String.format("Hello, %s!", ctx.getInput(String.class)); + }) + .buildAndStart(); + + DurableTaskClient client = new DurableTaskGrpcClientBuilder().build(); + try (worker; client) { + final String instanceID = "ERROR_IF_RUNNING_OR_COMPLETED"; + + client.scheduleNewOrchestrationInstance(orchestratorName, "GO", instanceID); + assertThrows( + StatusRuntimeException.class, + () -> client.scheduleNewOrchestrationInstance(orchestratorName, "World", instanceID) + ); + } + } +} \ No newline at end of file