Skip to content
Draft
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
3 changes: 2 additions & 1 deletion temporal-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
}

testImplementation project(':temporal-testing')
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 +288,4 @@ testing {
tasks.named('check') {
dependsOn(testing.suites.jackson3Tests)
dependsOn(testing.suites.virtualThreadTests)
}
}
2 changes: 2 additions & 0 deletions temporal-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ java {
dependencies {
api project(':temporal-sdk')
api project(':temporal-test-server')
compileOnly project(':temporal-envconfig')

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

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

testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter'
testRuntimeOnly 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,12 @@ 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) {
if (!useExternalService || useEnvConfig) {
return;
}

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

import io.temporal.internal.common.env.EnvironmentVariableUtils;
import io.grpc.Metadata;
import io.temporal.client.ActivityClientOptions;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.envconfig.ClientConfigProfile;
import io.temporal.envconfig.LoadClientConfigProfileOptions;
import io.temporal.serviceclient.GrpcMetadataProvider;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.testing.TestEnvironmentOptions;
import io.temporal.testing.TestWorkflowRule;
import io.temporal.testing.internal.devserver.SdkJavaTestServerProfile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;

public class ExternalServiceTestConfigurator {
private static boolean USE_EXTERNAL_SERVICE =
EnvironmentVariableUtils.readBooleanFlag("USE_EXTERNAL_SERVICE");
private static String TEMPORAL_SERVICE_ADDRESS =
EnvironmentVariableUtils.readString("TEMPORAL_SERVICE_ADDRESS");
private static boolean USE_VIRTUAL_THREADS =
EnvironmentVariableUtils.readBooleanFlag("USE_VIRTUAL_THREADS");
static final String TEMPORAL_TEST_ENV_CONFIG_SERVER = "TEMPORAL_TEST_ENV_CONFIG_SERVER";
private static final String USE_EXTERNAL_SERVICE = "USE_EXTERNAL_SERVICE";
private static final String TEMPORAL_SERVICE_ADDRESS = "TEMPORAL_SERVICE_ADDRESS";
private static final String USE_VIRTUAL_THREADS = "USE_VIRTUAL_THREADS";

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

public static boolean isUseVirtualThreads() {
return USE_VIRTUAL_THREADS;
return readBooleanFlag(System.getenv(), USE_VIRTUAL_THREADS);
}

public static String getTemporalServiceAddress() {
if (SdkJavaTestServerProfile.isActive()) {
return SdkJavaTestServerProfile.getTarget();
Map<String, String> environment = System.getenv();
if (readBooleanFlag(environment, TEMPORAL_TEST_ENV_CONFIG_SERVER)) {
return getTemporalServiceAddress(environment);
}
return USE_EXTERNAL_SERVICE
? (TEMPORAL_SERVICE_ADDRESS != null ? TEMPORAL_SERVICE_ADDRESS : "127.0.0.1:7233")
: null;
String devServerTarget = SdkJavaTestServerProfile.getTarget();
if (devServerTarget != null) {
return devServerTarget;
}
return getTemporalServiceAddress(environment);
}

public static TestWorkflowRule.Builder configure(
@Nonnull TestWorkflowRule.Builder testWorkflowRule) {
if (isUseExternalService()) {
return configure(testWorkflowRule, System.getenv(), SdkJavaTestServerProfile.getTarget());
}

static TestWorkflowRule.Builder configure(
TestWorkflowRule.Builder testWorkflowRule, Map<String, String> environment) {
return configure(testWorkflowRule, environment, null);
}

static TestWorkflowRule.Builder configure(
TestWorkflowRule.Builder testWorkflowRule,
Map<String, String> environment,
String devServerTarget) {
return configure(testWorkflowRule, environment, devServerTarget, true);
}

static TestWorkflowRule.Builder configureConnection(
TestWorkflowRule.Builder testWorkflowRule,
Map<String, String> environment,
String devServerTarget) {
return configure(testWorkflowRule, environment, devServerTarget, false);
}

private static TestWorkflowRule.Builder configure(
TestWorkflowRule.Builder testWorkflowRule,
Map<String, String> environment,
String devServerTarget,
boolean configureOptions) {
ClientConfigProfile profile = loadEnvConfigProfile(environment);
if (profile != null) {
testWorkflowRule.setUseExternalService(true);
testWorkflowRule.setTarget(profile.getAddress());
testWorkflowRule.setNamespace(profile.getNamespace());
if (configureOptions) {
testWorkflowRule.setWorkflowServiceStubsOptions(profile.toWorkflowServiceStubsOptions());
testWorkflowRule.setWorkflowClientOptions(profile.toWorkflowClientOptions());
}
} else if (devServerTarget != null) {
testWorkflowRule.setUseExternalService(true);
testWorkflowRule.setTarget(devServerTarget);
} else if (readBooleanFlag(environment, USE_EXTERNAL_SERVICE)) {
testWorkflowRule.setUseExternalService(true);
String target = getTemporalServiceAddress();
if (target != null) {
testWorkflowRule.setTarget(target);
String serviceAddress = environment.get(TEMPORAL_SERVICE_ADDRESS);
if (serviceAddress != null) {
testWorkflowRule.setTarget(serviceAddress);
}
}
return testWorkflowRule;
}

public static TestEnvironmentOptions.Builder configure(
@Nonnull TestEnvironmentOptions.Builder testEnvironmentOptions) {
if (isUseExternalService()) {
return configure(testEnvironmentOptions, System.getenv(), SdkJavaTestServerProfile.getTarget());
}

static TestEnvironmentOptions.Builder configure(
TestEnvironmentOptions.Builder testEnvironmentOptions, Map<String, String> environment) {
return configure(testEnvironmentOptions, environment, null);
}

static TestEnvironmentOptions.Builder configure(
TestEnvironmentOptions.Builder testEnvironmentOptions,
Map<String, String> environment,
String devServerTarget) {
ClientConfigProfile profile = loadEnvConfigProfile(environment);
if (profile != null) {
testEnvironmentOptions.setUseExternalService(true);
testEnvironmentOptions.setTarget(profile.getAddress());
testEnvironmentOptions.setWorkflowServiceStubsOptions(
profile.toWorkflowServiceStubsOptions());
testEnvironmentOptions.setWorkflowClientOptions(profile.toWorkflowClientOptions());
} else if (devServerTarget != null) {
testEnvironmentOptions.setUseExternalService(true);
testEnvironmentOptions.setTarget(devServerTarget);
} else if (readBooleanFlag(environment, USE_EXTERNAL_SERVICE)) {
testEnvironmentOptions.setUseExternalService(true);
String target = getTemporalServiceAddress();
if (target != null) {
testEnvironmentOptions.setTarget(target);
String serviceAddress = environment.get(TEMPORAL_SERVICE_ADDRESS);
if (serviceAddress != null) {
testEnvironmentOptions.setTarget(serviceAddress);
}
}
return testEnvironmentOptions;
Expand All @@ -58,4 +131,132 @@ public static TestEnvironmentOptions.Builder configure(
public static TestEnvironmentOptions.Builder configuredTestEnvironmentOptions() {
return configure(TestEnvironmentOptions.newBuilder());
}

static WorkflowServiceStubsOptions configure(
WorkflowServiceStubsOptions workflowServiceStubsOptions, Map<String, String> environment) {
ClientConfigProfile profile = loadEnvConfigProfile(environment);
if (profile == null) {
return workflowServiceStubsOptions;
}

WorkflowServiceStubsOptions profileOptions = profile.toWorkflowServiceStubsOptions();
GrpcMetadataProvider metadataProvider =
mergeMetadata(
profileOptions.getHeaders(),
profileOptions.getGrpcMetadataProviders(),
workflowServiceStubsOptions.getHeaders(),
workflowServiceStubsOptions.getGrpcMetadataProviders());
return WorkflowServiceStubsOptions.newBuilder(workflowServiceStubsOptions)
.setChannel(null)
.setTarget(profileOptions.getTarget())
.setEnableHttps(profileOptions.getEnableHttps())
.setSslContext(profileOptions.getSslContext())
.setChannelInitializer(profileOptions.getChannelInitializer())
.setHeaders(new Metadata())
.setGrpcMetadataProviders(Collections.singletonList(metadataProvider))
.build();
}

private static GrpcMetadataProvider mergeMetadata(
Metadata profileHeaders,
Iterable<GrpcMetadataProvider> profileProviders,
Metadata testHeaders,
Iterable<GrpcMetadataProvider> testProviders) {
List<GrpcMetadataProvider> profileProviderList = new ArrayList<>();
if (profileProviders != null) {
profileProviders.forEach(profileProviderList::add);
}
return () -> {
Metadata metadata = new Metadata();
if (testHeaders != null) {
metadata.merge(testHeaders);
}
if (testProviders != null) {
testProviders.forEach(provider -> metadata.merge(provider.getMetadata()));
}
Metadata profileMetadata = new Metadata();
if (profileHeaders != null) {
profileMetadata.merge(profileHeaders);
}
profileProviderList.forEach(provider -> profileMetadata.merge(provider.getMetadata()));
for (String keyName : profileMetadata.keys()) {
if (keyName.endsWith("-bin")) {
metadata.discardAll(Metadata.Key.of(keyName, Metadata.BINARY_BYTE_MARSHALLER));
} else {
metadata.discardAll(Metadata.Key.of(keyName, Metadata.ASCII_STRING_MARSHALLER));
}
}
metadata.merge(profileMetadata);
return metadata;
};
}

static WorkflowClientOptions configure(
WorkflowClientOptions workflowClientOptions, Map<String, String> environment) {
ClientConfigProfile profile = loadEnvConfigProfile(environment);
return profile == null
? workflowClientOptions
: WorkflowClientOptions.newBuilder(workflowClientOptions)
.setNamespace(profile.getNamespace())
.build();
}

static ActivityClientOptions configure(
ActivityClientOptions activityClientOptions, Map<String, String> environment) {
ClientConfigProfile profile = loadEnvConfigProfile(environment);
return profile == null
? activityClientOptions
: ActivityClientOptions.newBuilder(activityClientOptions)
.setNamespace(profile.getNamespace())
.build();
}

static boolean isUseExternalService(Map<String, String> environment) {
return readBooleanFlag(environment, TEMPORAL_TEST_ENV_CONFIG_SERVER)
|| readBooleanFlag(environment, USE_EXTERNAL_SERVICE);
}

static String getTemporalServiceAddress(Map<String, String> environment) {
ClientConfigProfile profile = loadEnvConfigProfile(environment);
if (profile != null) {
return profile.getAddress();
}
return readBooleanFlag(environment, USE_EXTERNAL_SERVICE)
? (environment.get(TEMPORAL_SERVICE_ADDRESS) != null
? environment.get(TEMPORAL_SERVICE_ADDRESS)
: "127.0.0.1:7233")
: null;
}

private static ClientConfigProfile loadEnvConfigProfile(Map<String, String> environment) {
if (!readBooleanFlag(environment, TEMPORAL_TEST_ENV_CONFIG_SERVER)) {
return null;
}

ClientConfigProfile profile;
try {
profile =
ClientConfigProfile.load(
LoadClientConfigProfileOptions.newBuilder().setEnvOverrides(environment).build());
} catch (IOException e) {
throw new IllegalStateException(
"Unable to load client configuration for the Temporal test harness.", e);
}
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.");
}
return profile;
}

private static boolean readBooleanFlag(Map<String, String> environment, String variableName) {
String value = environment.get(variableName);
if (value == null) {
return false;
}
value = value.trim();
return !Boolean.FALSE.toString().equalsIgnoreCase(value) && !"0".equals(value);
}
}
Loading
Loading