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
9 changes: 3 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ jobs:

- name: Run cloud test
if: ${{ steps.cloud-test-eligibility.outputs.enabled == 'true' }}
timeout-minutes: 15
timeout-minutes: 40
env:
USER: unittest
TEMPORAL_TEST_ENV_CONFIG_SERVER: "true"
Expand All @@ -184,10 +184,7 @@ jobs:
TEMPORAL_CLIENT_CLOUD_NAMESPACE: ${{ steps.create-cloud-namespace.outputs.namespace }}
TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }}
TEMPORAL_CLIENT_CLOUD_API_VERSION: v0.19.1
run: |
./gradlew --no-daemon :temporal-sdk:test \
--tests '*CloudOperationsClientTest' \
--tests 'io.temporal.client.functional.SignalTest.signalCompletedWorkflow'
run: ./gradlew --no-daemon :temporal-sdk:testCloud

- name: Delete Cloud namespace
if: ${{ always() && steps.create-cloud-namespace.outputs.namespace != '' }}
Expand All @@ -201,7 +198,7 @@ jobs:
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6
if: success() || failure() # always run even if the previous step fails
with:
report_paths: "**/build/test-results/test/TEST-*.xml"
report_paths: "**/build/test-results/testCloud/TEST-*.xml"

code_format:
name: Code format
Expand Down
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ Values from `TEMPORAL_ADDRESS`, `TEMPORAL_NAMESPACE`, `TEMPORAL_API_KEY`, `TEMPO
`TEMPORAL_GRPC_META_*` override the selected profile. Envconfig mode connects to an existing server
and namespace; it does not create or register either one.

The `:temporal-sdk:testCloud` task runs tests that are eligible for Temporal Cloud. It uses the same
envconfig variables and excludes tests annotated with a `CloudTestExclusion` JUnit category. Tests
are Cloud-eligible by default; use the narrowest applicable exclusion reason when a test requires a
local server, requires Cloud resources that CI does not provision, or still needs Cloud-specific
adaptation. Run `./gradlew :temporal-sdk:testCloud --test-dry-run` to inventory the selected tests
without executing them. The normal `test` task continues to run Cloud-excluded tests locally.

JUnit category marker interfaces are the Java equivalent of test-runner traits. Every Cloud
exclusion must pair its reason category with a complete explanatory note:

```java
@CloudTestExclusionNote("Starts an in-process time-skipping server.")
@Category(RequiresLocalServer.class)
```

## Things to Avoid

Avoid changes that make review harder without improving the contribution:
Expand Down
28 changes: 28 additions & 0 deletions temporal-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,34 @@ test {
}
}

task testCloud(type: Test) {
group = 'verification'
description = 'Runs temporal-sdk tests that are eligible for Temporal Cloud.'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
if (project.hasProperty('testJavaVersion')) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(project.property('testJavaVersion') as int)
}
}
useJUnit {
excludeCategories 'io.temporal.testing.CloudTestExclusion'
excludeCategories 'io.temporal.worker.IndependentResourceBasedTests'
}
testLogging {
events 'passed', 'skipped', 'failed'
exceptionFormat 'full'
showStandardStreams true
}
forkEvery = 1
maxParallelForks = Math.max(Runtime.runtime.availableProcessors().intdiv(2), 1) ?: 1
afterTest { TestDescriptor descriptor, TestResult result ->
if (result.resultType == org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE) {
failedTests << ["${descriptor.className}::${descriptor.name}"]
}
}
}

// On Java 17+, prepend java17 classes to all test classpaths so that Class.forName finds
// the real Jackson3JsonPayloadConverter instead of the Java 8 stub. This lets us test
// the present-java17-but-absent-jackson3 behavior (NoClassDefFoundError) in the same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public void getActivityInfo() {
Assert.assertEquals(ACTIVITY_OPTIONS.getStartToCloseTimeout(), info.startToCloseTimeout);
Assert.assertEquals(ACTIVITY_OPTIONS.getHeartbeatTimeout(), info.heartbeatTimeout);
Assert.assertEquals(ActivityInfoWorkflow.class.getSimpleName(), info.workflowType);
Assert.assertEquals(SDKTestWorkflowRule.NAMESPACE, info.namespace);
Assert.assertEquals(
testWorkflowRule.getWorkflowClient().getOptions().getNamespace(), info.namespace);
Assert.assertEquals(testWorkflowRule.getTaskQueue(), info.activityTaskQueue);
Assert.assertFalse(info.isLocal);
Assert.assertEquals(0, info.priorityKey);
Expand All @@ -98,7 +99,8 @@ public void getLocalActivityInfo() {
Assert.assertTrue(info.startToCloseTimeout.isZero());
Assert.assertTrue(info.heartbeatTimeout.isZero());
Assert.assertEquals(ActivityInfoWorkflow.class.getSimpleName(), info.workflowType);
Assert.assertEquals(SDKTestWorkflowRule.NAMESPACE, info.namespace);
Assert.assertEquals(
testWorkflowRule.getWorkflowClient().getOptions().getNamespace(), info.namespace);
Assert.assertEquals(testWorkflowRule.getTaskQueue(), info.activityTaskQueue);
Assert.assertTrue(info.isLocal);
Assert.assertEquals(0, info.priorityKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.testing.CloudTestExclusion.RequiresLocalServer;
import io.temporal.testing.CloudTestExclusionNote;
import io.temporal.testing.TestEnvironmentOptions;
import io.temporal.testing.TestWorkflowEnvironment;
import io.temporal.worker.Worker;
Expand All @@ -20,9 +22,12 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

@CloudTestExclusionNote("This test directly creates and controls a local test service.")
@Category(RequiresLocalServer.class)
public class AuthorizationTokenTest {
private static Metadata.Key<String> TEMPORAL_NAMESPACE_HEADER_KEY =
Metadata.Key.of("temporal-namespace", Metadata.ASCII_STRING_MARSHALLER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.temporal.api.enums.v1.TaskReachability;
import io.temporal.client.*;
import io.temporal.internal.testing.WorkflowTestingTest;
import io.temporal.testing.CloudTestExclusion.RequiresCloudProvisioning;
import io.temporal.testing.CloudTestExclusionNote;
import io.temporal.testing.internal.SDKTestWorkflowRule;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
Expand All @@ -16,8 +18,12 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@SuppressWarnings({"OptionalGetWithoutIsPresent", "deprecation"})
@CloudTestExclusionNote(
"Cloud CI namespaces disable the deprecated version-set and rules-based versioning APIs.")
@Category(RequiresCloudProvisioning.class)
public class BuildIdVersionSetsTest {
@Rule
public SDKTestWorkflowRule testWorkflowRule =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public void run() {
private ActivityClient newActivityClient() {
return ActivityClient.newInstance(
testWorkflowRule.getWorkflowClient().getWorkflowServiceStubs(),
ActivityClientOptions.newBuilder().setNamespace(SDKTestWorkflowRule.NAMESPACE).build());
ActivityClientOptions.newBuilder()
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.build());
}

private StartActivityOptions slowOpts() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ public void setUp() {
activityClient =
ActivityClient.newInstance(
clientStubs,
ActivityClientOptions.newBuilder().setNamespace(SDKTestWorkflowRule.NAMESPACE).build());
ActivityClientOptions.newBuilder()
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.build());
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public void run() {
private ActivityClient newActivityClient() {
return ActivityClient.newInstance(
testWorkflowRule.getWorkflowClient().getWorkflowServiceStubs(),
ActivityClientOptions.newBuilder().setNamespace(SDKTestWorkflowRule.NAMESPACE).build());
ActivityClientOptions.newBuilder()
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.build());
}

private StartActivityOptions slowOpts() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.temporal.client.functional;

import static io.temporal.testUtils.Eventually.assertEventually;
import static io.temporal.testing.internal.SDKTestWorkflowRule.NAMESPACE;
import static junit.framework.TestCase.*;
import static org.junit.Assume.assumeTrue;

Expand Down Expand Up @@ -54,22 +53,26 @@ public class MetricsTest {
private final ActivityClient activityClient =
ActivityClient.newInstance(
testWorkflowRule.getWorkflowServiceStubs(),
ActivityClientOptions.newBuilder().setNamespace(SDKTestWorkflowRule.NAMESPACE).build());

private static final List<Tag> TAGS_NAMESPACE =
MetricsTag.defaultTags(NAMESPACE).entrySet().stream()
.map(
nameValueEntry ->
new ImmutableTag(nameValueEntry.getKey(), nameValueEntry.getValue()))
.collect(Collectors.toList());
ActivityClientOptions.newBuilder()
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.build());

private List<Tag> tagsNamespace;
private List<Tag> tagsNamespaceQueue;

@Before
public void setUp() {
registry.clear();
tagsNamespace =
MetricsTag.defaultTags(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.entrySet()
.stream()
.map(
nameValueEntry ->
new ImmutableTag(nameValueEntry.getKey(), nameValueEntry.getValue()))
.collect(Collectors.toList());
tagsNamespaceQueue =
replaceTags(TAGS_NAMESPACE, MetricsTag.TASK_QUEUE, testWorkflowRule.getTaskQueue());
replaceTags(tagsNamespace, MetricsTag.TASK_QUEUE, testWorkflowRule.getTaskQueue());
}

@After
Expand Down Expand Up @@ -97,7 +100,7 @@ public void testSynchronousStartAndGetResult() throws InterruptedException {
MetricsTag.WORKFLOW_TYPE,
"QuicklyCompletingWorkflow");
List<Tag> longPollRequestTags =
replaceTag(TAGS_NAMESPACE, MetricsTag.OPERATION_NAME, "GetWorkflowExecutionHistory");
replaceTag(tagsNamespace, MetricsTag.OPERATION_NAME, "GetWorkflowExecutionHistory");

assertEventually(
Duration.ofSeconds(2),
Expand Down Expand Up @@ -130,7 +133,7 @@ public void testAsynchronousStartAndGetResult() throws InterruptedException, Exe
MetricsTag.WORKFLOW_TYPE,
"QuicklyCompletingWorkflow");
List<Tag> longPollRequestTags =
replaceTag(TAGS_NAMESPACE, MetricsTag.OPERATION_NAME, "GetWorkflowExecutionHistory");
replaceTag(tagsNamespace, MetricsTag.OPERATION_NAME, "GetWorkflowExecutionHistory");

assertEventually(
Duration.ofSeconds(2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import io.temporal.common.interceptors.ActivityClientInterceptorBase;
import io.temporal.failure.ApplicationFailure;
import io.temporal.failure.CanceledFailure;
import io.temporal.testing.CloudTestExclusion.NeedsCloudAdaptation;
import io.temporal.testing.CloudTestExclusionNote;
import io.temporal.testing.internal.SDKTestWorkflowRule;
import java.time.Duration;
import java.util.*;
Expand All @@ -32,6 +34,7 @@
import java.util.stream.Collectors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

/**
* Integration tests for standalone activities started via {@link ActivityClient}.
Expand Down Expand Up @@ -257,7 +260,9 @@ private StartActivityOptions simpleOpts(String id) {
private ActivityClient newActivityClient() {
return ActivityClient.newInstance(
testWorkflowRule.getWorkflowServiceStubs(),
ActivityClientOptions.newBuilder().setNamespace(SDKTestWorkflowRule.NAMESPACE).build());
ActivityClientOptions.newBuilder()
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.build());
}

@Test
Expand Down Expand Up @@ -516,7 +521,7 @@ public void testStartActivityInterceptorsAreCalledProperly() throws InterruptedE
ActivityClient.newInstance(
testWorkflowRule.getWorkflowServiceStubs(),
ActivityClientOptions.newBuilder()
.setNamespace(SDKTestWorkflowRule.NAMESPACE)
.setNamespace(testWorkflowRule.getWorkflowClient().getOptions().getNamespace())
.setInterceptors(Collections.singletonList(interceptor))
.build());

Expand Down Expand Up @@ -570,7 +575,7 @@ public void testExecuteActivityWorkerActivityInfoIsAccurate() {

assertEquals(activityId, info.activityId);
assertEquals("InspectInfo", info.activityType);
assertEquals(SDKTestWorkflowRule.NAMESPACE, info.namespace);
assertEquals(testWorkflowRule.getWorkflowClient().getOptions().getNamespace(), info.namespace);
assertEquals(testWorkflowRule.getTaskQueue(), info.taskQueue);
assertFalse(info.isLocal);
assertFalse(info.isInWorkflow);
Expand Down Expand Up @@ -831,6 +836,9 @@ public void testDescribeRawInfoMatchesTypedAccessors() {
assertEquals(desc.getAttempt(), rawInfo.getAttempt());
}

@CloudTestExclusionNote(
"Cloud describe does not expose the last failure during retry backoff within the test window.")
@Category(NeedsCloudAdaptation.class)
@Test
public void testDescribeLastFailureIsPopulatedDuringRetryBackoff() {
assumeTrue(SDKTestWorkflowRule.useExternalService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.temporal.client.WorkflowTargetOptions;
import io.temporal.common.WorkflowExecutionHistory;
import io.temporal.internal.common.ProtobufTimeUtils;
import io.temporal.testing.CloudTestExclusion.RequiresLocalServer;
import io.temporal.testing.CloudTestExclusionNote;
import io.temporal.testing.internal.SDKTestOptions;
import io.temporal.testing.internal.SDKTestWorkflowRule;
import io.temporal.workflow.shared.TestMultiArgWorkflowFunctions.*;
Expand All @@ -21,6 +23,7 @@
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

public class StartTest {

Expand Down Expand Up @@ -70,6 +73,9 @@ public void startNoArgFuncWithRejectDuplicate() {
"func", stubF.func()); // Check that duplicated start just returns the result.
}

@CloudTestExclusionNote(
"This test exercises behavior that is only supported by the local test server.")
@Category(RequiresLocalServer.class)
@Test
public void startOneArgsFuncWithDefault() {
// TODO why it doesn't work with external service?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.temporal.client.UntypedNexusOperationHandle;
import io.temporal.client.UntypedNexusServiceClient;
import io.temporal.failure.ApplicationFailure;
import io.temporal.testing.CloudTestExclusion.RequiresCloudProvisioning;
import io.temporal.testing.CloudTestExclusionNote;
import io.temporal.testing.internal.SDKTestWorkflowRule;
import io.temporal.workflow.shared.EchoNexusServiceImpl;
import io.temporal.workflow.shared.TestNexusServices;
Expand All @@ -25,6 +27,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

/**
* Coverage tests for the {@link CompletableFuture}-returning surface on the standalone Nexus
Expand All @@ -33,6 +36,9 @@
* UntypedNexusOperationHandle}. Each overload is asserted against the existing sync echo handler so
* the Java async API is exercised without depending on server-side async completion.
*/
@CloudTestExclusionNote(
"Cloud CI does not provision the standalone Nexus endpoint required by this test.")
@Category(RequiresCloudProvisioning.class)
public class NexusAsyncApiTest {

@Rule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import io.temporal.client.UntypedNexusOperationHandle;
import io.temporal.client.UntypedNexusServiceClient;
import io.temporal.nexus.TemporalOperationHandler;
import io.temporal.testing.CloudTestExclusion.RequiresCloudProvisioning;
import io.temporal.testing.CloudTestExclusionNote;
import io.temporal.testing.internal.SDKTestWorkflowRule;
import io.temporal.workflow.shared.EchoNexusServiceImpl;
import io.temporal.workflow.shared.TestNexusServices;
Expand All @@ -41,7 +43,11 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@CloudTestExclusionNote(
"Cloud CI does not provision the standalone Nexus endpoint required by this test.")
@Category(RequiresCloudProvisioning.class)
public class NexusClientTest {

private final AtomicInteger activityInvocationCount = new AtomicInteger();
Expand Down
Loading
Loading