diff --git a/src/main/kotlin/dev/restate/sdktesting/contracts/Failing.kt b/src/main/kotlin/dev/restate/sdktesting/contracts/Failing.kt index 7fe82b5..ffe071f 100644 --- a/src/main/kotlin/dev/restate/sdktesting/contracts/Failing.kt +++ b/src/main/kotlin/dev/restate/sdktesting/contracts/Failing.kt @@ -21,7 +21,30 @@ interface Failing { @Handler suspend fun failingCallWithEventualSuccess(context: ObjectContext): Int - @Handler suspend fun failingSideEffectWithEventualSuccess(context: ObjectContext): Int - @Handler suspend fun terminallyFailingSideEffect(context: ObjectContext, errorMessage: String) + + /** + * `minimumAttempts` should be used to check when to succeed. The retry policy should be + * configured to be infinite. + * + * @return the number of executed attempts. In order to implement this count, an atomic counter in + * the service should be used. + */ + @Handler + suspend fun sideEffectSucceedsAfterGivenAttempts( + context: ObjectContext, + minimumAttempts: Int + ): Int + + /** + * `retryPolicyMaxRetryCount` should be used to configure the retry policy. + * + * @return the number of executed attempts. In order to implement this count, an atomic counter in + * the service should be used. + */ + @Handler + suspend fun sideEffectFailsAfterGivenAttempts( + context: ObjectContext, + retryPolicyMaxRetryCount: Int + ): Int } diff --git a/src/main/kotlin/dev/restate/sdktesting/tests/SideEffect.kt b/src/main/kotlin/dev/restate/sdktesting/tests/RunFlush.kt similarity index 90% rename from src/main/kotlin/dev/restate/sdktesting/tests/SideEffect.kt rename to src/main/kotlin/dev/restate/sdktesting/tests/RunFlush.kt index 55c263a..54938a9 100644 --- a/src/main/kotlin/dev/restate/sdktesting/tests/SideEffect.kt +++ b/src/main/kotlin/dev/restate/sdktesting/tests/RunFlush.kt @@ -14,6 +14,7 @@ import dev.restate.sdktesting.contracts.TestUtilsServiceDefinitions import dev.restate.sdktesting.infra.InjectClient import dev.restate.sdktesting.infra.RestateDeployerExtension import dev.restate.sdktesting.infra.ServiceSpec +import java.util.* import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.DisplayName @@ -24,7 +25,7 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode @Tag("only-always-suspending") -class SideEffect { +class RunFlush { companion object { @RegisterExtension val deployerExt: RestateDeployerExtension = RestateDeployerExtension { @@ -33,10 +34,10 @@ class SideEffect { } } - @DisplayName("Side effect should wait on acknowledgements") + @DisplayName("Run should wait on acknowledgements") @Test @Execution(ExecutionMode.CONCURRENT) - fun sideEffectFlush(@InjectClient ingressClient: Client) = runTest { + fun flush(@InjectClient ingressClient: Client) = runTest { assertThat(TestUtilsServiceClient.fromClient(ingressClient).countExecutedSideEffects(3)) .isEqualTo(0) } diff --git a/src/main/kotlin/dev/restate/sdktesting/tests/RunRetry.kt b/src/main/kotlin/dev/restate/sdktesting/tests/RunRetry.kt new file mode 100644 index 0000000..f4a25be --- /dev/null +++ b/src/main/kotlin/dev/restate/sdktesting/tests/RunRetry.kt @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +package dev.restate.sdktesting.tests + +import dev.restate.sdk.client.Client +import dev.restate.sdktesting.contracts.FailingClient +import dev.restate.sdktesting.contracts.FailingDefinitions +import dev.restate.sdktesting.infra.InjectClient +import dev.restate.sdktesting.infra.RestateDeployerExtension +import dev.restate.sdktesting.infra.ServiceSpec +import java.util.* +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode + +@Tag("always-suspending") +class RunRetry { + companion object { + @RegisterExtension + val deployerExt: RestateDeployerExtension = RestateDeployerExtension { + withServiceSpec(ServiceSpec.defaultBuilder().withServices(FailingDefinitions.SERVICE_NAME)) + } + } + + @DisplayName("Run is retried until it succeeds") + @Test + @Execution(ExecutionMode.CONCURRENT) + fun withSuccess(@InjectClient ingressClient: Client) = runTest { + val attempts = 3 + + assertThat( + FailingClient.fromClient(ingressClient, UUID.randomUUID().toString()) + .sideEffectSucceedsAfterGivenAttempts(attempts)) + .isGreaterThanOrEqualTo(attempts) + } + + @DisplayName("Run is retried until it exhausts the retry attempts") + @Test + @Execution(ExecutionMode.CONCURRENT) + fun withExhaustedAttempts(@InjectClient ingressClient: Client) = runTest { + val attempts = 3 + + assertThat( + FailingClient.fromClient(ingressClient, UUID.randomUUID().toString()) + .sideEffectFailsAfterGivenAttempts(attempts)) + .isGreaterThanOrEqualTo(attempts) + } +} diff --git a/src/main/kotlin/dev/restate/sdktesting/tests/UserErrors.kt b/src/main/kotlin/dev/restate/sdktesting/tests/UserErrors.kt index 1359678..f9192d8 100644 --- a/src/main/kotlin/dev/restate/sdktesting/tests/UserErrors.kt +++ b/src/main/kotlin/dev/restate/sdktesting/tests/UserErrors.kt @@ -40,39 +40,6 @@ class UserErrors { } } - // @DisplayName("Test terminal error of failing side effect with finite retry policy is - // propagated") - // @Test - // @Execution(ExecutionMode.CONCURRENT) - // fun failingSideEffectWithFiniteRetryPolicy(@InjectBlockingStub stub: - // FailingServiceBlockingStub) { - // val errorMessage = "some error message" - // - // assertThatThrownBy { - // stub.failingSideEffectWithFiniteRetryPolicy( - // ErrorMessage.newBuilder() - // .setKey(UUID.randomUUID().toString()) - // .setErrorMessage(errorMessage) - // .build()) - // } - // .asInstanceOf(type(StatusRuntimeException::class.java)) - // .extracting(StatusRuntimeException::getStatus) - // .extracting(Status::getDescription, InstanceOfAssertFactories.STRING) - // .contains("failing side effect action") - // } - - // @DisplayName("Test propagate failure from sideEffect and internal invoke") - // @Test - // @Execution(ExecutionMode.CONCURRENT) - // fun sideEffectFailurePropagation(@InjectClient ingressClient: Client) { - // assertThat( - // FailingClient.fromClient(ingressClient, UUID.randomUUID().toString()) - // .invokeExternalAndHandleFailure()) - // // We match on this regex because there might be additional parts of the string injected - // // by runtime/sdk in the error message strings - // .matches("begin.*external_call.*internal_call") - // } - @DisplayName("Test calling method that fails terminally") @Test @Execution(ExecutionMode.CONCURRENT) @@ -122,16 +89,6 @@ class UserErrors { .hasMessageContaining(errorMessage) } - @DisplayName("Test side effects are retried until they succeed") - @Test - @Execution(ExecutionMode.CONCURRENT) - fun sideEffectWithEventualSuccess(@InjectClient ingressClient: Client) = runTest { - assertThat( - FailingClient.fromClient(ingressClient, UUID.randomUUID().toString()) - .failingCallWithEventualSuccess()) - .isEqualTo(SUCCESS_ATTEMPT) - } - @DisplayName("Test invocations are retried until they succeed") @Test @Execution(ExecutionMode.CONCURRENT)