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
13 changes: 5 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ jobs:

unit_test_cloud:
name: Unit test with cloud
runs-on: ubuntu-latest
timeout-minutes: 60
runs-on: ubuntu-latest-16-cores
timeout-minutes: 35
steps:
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down Expand Up @@ -175,7 +175,7 @@ jobs:

- name: Run cloud test
if: ${{ steps.cloud-test-eligibility.outputs.enabled == 'true' }}
timeout-minutes: 15
timeout-minutes: 25
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
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ 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:listCloudExcludedTests` to inventory the tests excluded
from Cloud without executing them. The normal `test` task continues to run Cloud-excluded tests
locally.

Filter the inventory to one exclusion reason with, for example:

```bash
./gradlew :temporal-sdk:listCloudExcludedTests \
-PcloudTestExclusionReason=RequiresLocalServer
```

The accepted reasons are `RequiresLocalServer`, `RequiresCloudProvisioning`, and
`NeedsCloudAdaptation`.

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
56 changes: 56 additions & 0 deletions temporal-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,62 @@ test {
}
}

def cloudTestExclusionCategories = [
RequiresLocalServer: 'io.temporal.testing.CloudTestExclusion$RequiresLocalServer',
RequiresCloudProvisioning: 'io.temporal.testing.CloudTestExclusion$RequiresCloudProvisioning',
NeedsCloudAdaptation: 'io.temporal.testing.CloudTestExclusion$NeedsCloudAdaptation',
]

task listCloudExcludedTests(type: Test) {
group = 'verification'
description = 'Lists temporal-sdk tests that are excluded from Temporal Cloud.'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
dryRun = true
String exclusionReason = providers.gradleProperty('cloudTestExclusionReason').getOrNull()
String exclusionCategory = exclusionReason == null
? 'io.temporal.testing.CloudTestExclusion'
: cloudTestExclusionCategories.get(exclusionReason)
if (exclusionCategory == null) {
throw new GradleException(
"Unknown Cloud test exclusion reason '${exclusionReason}'. Expected one of: " +
cloudTestExclusionCategories.keySet().join(', ') + '.')
}
useJUnit {
includeCategories exclusionCategory
}
testLogging {
events 'skipped'
}
}

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'
}
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
@@ -1,6 +1,7 @@
package io.temporal.client.functional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import io.temporal.api.common.v1.WorkflowExecution;
import io.temporal.api.history.v1.HistoryEvent;
Expand Down Expand Up @@ -37,11 +38,12 @@ public void startWithDelay() {
testWorkflowRule
.getWorkflowClient()
.newWorkflowStub(TestNoArgsWorkflowFunc.class, workflowOptions);
long start = System.currentTimeMillis();
long startNanos = System.nanoTime();
stubF.func();
long end = System.currentTimeMillis();
// Assert that the workflow took at least 5 seconds to start
assertEquals(1000, end - start, 500);
Duration elapsed = Duration.ofNanos(System.nanoTime() - startNanos);
assertTrue(
"Workflow completed before its one-second start delay: " + elapsed,
elapsed.compareTo(Duration.ofSeconds(1)) >= 0);
WorkflowExecution workflowExecution = WorkflowStub.fromTyped(stubF).getExecution();
WorkflowExecutionHistory workflowExecutionHistory =
testWorkflowRule.getWorkflowClient().fetchHistory(workflowExecution.getWorkflowId());
Expand Down
Loading
Loading