Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ Normal Gradle test filtering works, so a single dev-server-backed test can be ru

Java 11 must be available to Gradle for these commands.

To run an SDK test against an externally managed server using the standard Temporal client
environment configuration, set `TEMPORAL_TEST_ENV_CONFIG_SERVER`. For example, the following runs
one Cloud-safe workflow test:

```bash
TEMPORAL_TEST_ENV_CONFIG_SERVER=true \
TEMPORAL_ADDRESS=your-namespace.tmprl.cloud:7233 \
TEMPORAL_NAMESPACE=your-namespace \
TEMPORAL_API_KEY=your-api-key \
./gradlew :temporal-sdk:test \
--tests 'io.temporal.client.functional.SignalTest.signalCompletedWorkflow'
```

The harness also supports the standard `TEMPORAL_CONFIG_FILE` and `TEMPORAL_PROFILE` variables.
Values from `TEMPORAL_ADDRESS`, `TEMPORAL_NAMESPACE`, `TEMPORAL_API_KEY`, `TEMPORAL_TLS_*`, and
`TEMPORAL_GRPC_META_*` override the selected profile. Envconfig mode connects to an existing server
and namespace; it does not create or register either one.

## Things to Avoid

Avoid changes that make review harder without improving the contribution:
Expand Down
4 changes: 3 additions & 1 deletion temporal-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ dependencies {
}

testImplementation project(':temporal-testing')
// The optional envconfig-backed test harness is loaded only while SDK tests are running.
testRuntimeOnly project(':temporal-envconfig')
testImplementation "junit:junit:${junitVersion}"
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation 'pl.pragmatists:JUnitParams:1.1.1'
Expand Down Expand Up @@ -287,4 +289,4 @@ testing {
tasks.named('check') {
dependsOn(testing.suites.jackson3Tests)
dependsOn(testing.suites.virtualThreadTests)
}
}
4 changes: 4 additions & 0 deletions temporal-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ java {
dependencies {
api project(':temporal-sdk')
api project(':temporal-test-server')
// Envconfig is optional for consumers of temporal-testing.
compileOnly project(':temporal-envconfig')

implementation 'org.apache.commons:commons-compress:1.28.0'

Expand All @@ -32,6 +34,8 @@ dependencies {
junit5Api 'org.junit.jupiter:junit-jupiter-api'

testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter'
// Tests construct envconfig profiles directly.
testImplementation project(':temporal-envconfig')
testRuntimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.temporal.api.workflowservice.v1.ListNamespacesRequest;
import io.temporal.api.workflowservice.v1.ListNamespacesResponse;
import io.temporal.api.workflowservice.v1.RegisterNamespaceRequest;
import io.temporal.internal.common.env.EnvironmentVariableUtils;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;

Expand All @@ -15,10 +16,13 @@ public class RegisterTestNamespace {
public static final String NAMESPACE = "UnitTest";
private static final boolean useExternalService =
Boolean.parseBoolean(System.getenv("USE_EXTERNAL_SERVICE"));
private static final boolean useEnvConfig =
EnvironmentVariableUtils.readBooleanFlag("TEMPORAL_TEST_ENV_CONFIG_SERVER");
private static final String serviceAddress = System.getenv("TEMPORAL_SERVICE_ADDRESS");

public static void main(String[] args) throws InterruptedException {
if (!useExternalService) {
// Envconfig mode connects to an existing namespace and must not register UnitTest.
if (useEnvConfig || !useExternalService) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.temporal.testing.internal;

import io.temporal.envconfig.ClientConfigProfile;
import io.temporal.internal.common.env.EnvironmentVariableUtils;
import io.temporal.testing.TestEnvironmentOptions;
import io.temporal.testing.TestWorkflowRule;
import io.temporal.testing.internal.devserver.SdkJavaTestServerProfile;
import java.io.IOException;
import javax.annotation.Nonnull;

public class ExternalServiceTestConfigurator {
private static boolean USE_ENV_CONFIG =
EnvironmentVariableUtils.readBooleanFlag("TEMPORAL_TEST_ENV_CONFIG_SERVER");
private static boolean USE_EXTERNAL_SERVICE =
EnvironmentVariableUtils.readBooleanFlag("USE_EXTERNAL_SERVICE");
private static String TEMPORAL_SERVICE_ADDRESS =
Expand All @@ -15,14 +19,17 @@ public class ExternalServiceTestConfigurator {
EnvironmentVariableUtils.readBooleanFlag("USE_VIRTUAL_THREADS");

public static boolean isUseExternalService() {
return USE_EXTERNAL_SERVICE || SdkJavaTestServerProfile.isActive();
return USE_ENV_CONFIG || USE_EXTERNAL_SERVICE || SdkJavaTestServerProfile.isActive();
}

public static boolean isUseVirtualThreads() {
return USE_VIRTUAL_THREADS;
}

public static String getTemporalServiceAddress() {
if (USE_ENV_CONFIG) {
return loadEnvConfigProfile().getAddress();
}
if (SdkJavaTestServerProfile.isActive()) {
return SdkJavaTestServerProfile.getTarget();
}
Expand All @@ -33,6 +40,9 @@ public static String getTemporalServiceAddress() {

public static TestWorkflowRule.Builder configure(
@Nonnull TestWorkflowRule.Builder testWorkflowRule) {
if (USE_ENV_CONFIG) {
return configureFromEnvConfig(testWorkflowRule, loadEnvConfigProfile());
}
if (isUseExternalService()) {
testWorkflowRule.setUseExternalService(true);
String target = getTemporalServiceAddress();
Expand All @@ -45,6 +55,9 @@ public static TestWorkflowRule.Builder configure(

public static TestEnvironmentOptions.Builder configure(
@Nonnull TestEnvironmentOptions.Builder testEnvironmentOptions) {
if (USE_ENV_CONFIG) {
return configureFromEnvConfig(testEnvironmentOptions, loadEnvConfigProfile());
}
if (isUseExternalService()) {
testEnvironmentOptions.setUseExternalService(true);
String target = getTemporalServiceAddress();
Expand All @@ -58,4 +71,46 @@ public static TestEnvironmentOptions.Builder configure(
public static TestEnvironmentOptions.Builder configuredTestEnvironmentOptions() {
return configure(TestEnvironmentOptions.newBuilder());
}

static TestWorkflowRule.Builder configureFromEnvConfig(
TestWorkflowRule.Builder testWorkflowRule, ClientConfigProfile profile) {
validateEnvConfigProfile(profile);
testWorkflowRule.setUseExternalService(true);
testWorkflowRule.setTarget(profile.getAddress());
testWorkflowRule.setNamespace(profile.getNamespace());
testWorkflowRule.setWorkflowServiceStubsOptions(profile.toWorkflowServiceStubsOptions());
testWorkflowRule.setWorkflowClientOptions(profile.toWorkflowClientOptions());
return testWorkflowRule;
}

static TestEnvironmentOptions.Builder configureFromEnvConfig(
TestEnvironmentOptions.Builder testEnvironmentOptions, ClientConfigProfile profile) {
validateEnvConfigProfile(profile);
testEnvironmentOptions.setUseExternalService(true);
testEnvironmentOptions.setTarget(profile.getAddress());
testEnvironmentOptions.setWorkflowServiceStubsOptions(profile.toWorkflowServiceStubsOptions());
testEnvironmentOptions.setWorkflowClientOptions(profile.toWorkflowClientOptions());
return testEnvironmentOptions;
}

private static ClientConfigProfile loadEnvConfigProfile() {
ClientConfigProfile profile;
try {
profile = ClientConfigProfile.load();
} catch (IOException e) {
throw new IllegalStateException(
"Unable to load client configuration for the Temporal test harness.", e);
}
validateEnvConfigProfile(profile);
return profile;
}

private static void validateEnvConfigProfile(ClientConfigProfile profile) {
if (profile.getAddress() == null || profile.getAddress().isEmpty()) {
throw new IllegalStateException("Envconfig test harness requires a Temporal server address.");
}
if (profile.getNamespace() == null || profile.getNamespace().isEmpty()) {
throw new IllegalStateException("Envconfig test harness requires a Temporal namespace.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.temporal.testing.internal;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.grpc.Metadata;
import io.temporal.envconfig.ClientConfigProfile;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.testing.TestEnvironmentOptions;
import io.temporal.testing.TestWorkflowRule;
import org.junit.jupiter.api.Test;

public class ExternalServiceTestConfiguratorTest {

@Test
public void configureTestWorkflowRuleFromEnvConfig() {
TestWorkflowRule rule =
ExternalServiceTestConfigurator.configureFromEnvConfig(
TestWorkflowRule.newBuilder(), newProfile())
.build();
try {
assertEquals(
"envconfig-address:7233", rule.getWorkflowServiceStubs().getOptions().getTarget());
assertEquals("envconfig-namespace", rule.getWorkflowClient().getOptions().getNamespace());
} finally {
rule.getTestEnvironment().close();
}
}

@Test
public void configureTestEnvironmentFromEnvConfig() {
TestEnvironmentOptions options =
ExternalServiceTestConfigurator.configureFromEnvConfig(
TestEnvironmentOptions.newBuilder(), newProfile())
.build();

assertTrue(options.isUseExternalService());
assertEquals("envconfig-address:7233", options.getTarget());
assertEquals("envconfig-address:7233", options.getWorkflowServiceStubsOptions().getTarget());
assertEquals("envconfig-namespace", options.getWorkflowClientOptions().getNamespace());
assertTrue(options.getWorkflowServiceStubsOptions().getEnableHttps());

Metadata metadata = metadata(options.getWorkflowServiceStubsOptions());
assertEquals(
"metadata-value",
metadata.get(Metadata.Key.of("test-header", Metadata.ASCII_STRING_MARSHALLER)));
assertEquals(
"Bearer api-key",
metadata.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)));
}

@Test
public void requireAddressAndNamespaceInEnvConfigMode() {
ClientConfigProfile missingAddress =
ClientConfigProfile.newBuilder().setNamespace("envconfig-namespace").build();
IllegalStateException missingAddressException =
assertThrows(
IllegalStateException.class,
() ->
ExternalServiceTestConfigurator.configureFromEnvConfig(
TestEnvironmentOptions.newBuilder(), missingAddress));
assertEquals(
"Envconfig test harness requires a Temporal server address.",
missingAddressException.getMessage());

ClientConfigProfile missingNamespace =
ClientConfigProfile.newBuilder().setAddress("envconfig-address:7233").build();
IllegalStateException missingNamespaceException =
assertThrows(
IllegalStateException.class,
() ->
ExternalServiceTestConfigurator.configureFromEnvConfig(
TestEnvironmentOptions.newBuilder(), missingNamespace));
assertEquals(
"Envconfig test harness requires a Temporal namespace.",
missingNamespaceException.getMessage());
}

private static ClientConfigProfile newProfile() {
Metadata metadata = new Metadata();
metadata.put(
Metadata.Key.of("test-header", Metadata.ASCII_STRING_MARSHALLER), "metadata-value");
return ClientConfigProfile.newBuilder()
.setAddress("envconfig-address:7233")
.setNamespace("envconfig-namespace")
.setApiKey("api-key")
.setMetadata(metadata)
.build();
}

private static Metadata metadata(WorkflowServiceStubsOptions options) {
Metadata metadata = new Metadata();
options.getGrpcMetadataProviders().forEach(provider -> metadata.merge(provider.getMetadata()));
return metadata;
}
}
Loading