Skip to content

Commit 0f9813c

Browse files
Handle async completion in TestActivityEnvironment (#2487)
1 parent 08d6089 commit 0f9813c

File tree

4 files changed

+94
-33
lines changed

4 files changed

+94
-33
lines changed

temporal-sdk/src/test/java/io/temporal/internal/testing/ActivityTestingTest.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import io.temporal.client.ActivityCanceledException;
3232
import io.temporal.failure.ActivityFailure;
3333
import io.temporal.failure.ApplicationFailure;
34+
import io.temporal.testing.ActivityRequestedAsyncCompletion;
3435
import io.temporal.testing.TestActivityEnvironment;
3536
import java.io.IOException;
3637
import java.util.ArrayList;
@@ -39,10 +40,7 @@
3940
import java.util.concurrent.ConcurrentHashMap;
4041
import java.util.concurrent.atomic.AtomicInteger;
4142
import java.util.concurrent.atomic.AtomicReference;
42-
import org.junit.After;
43-
import org.junit.Before;
44-
import org.junit.Rule;
45-
import org.junit.Test;
43+
import org.junit.*;
4644
import org.junit.rules.Timeout;
4745

4846
public class ActivityTestingTest {
@@ -111,6 +109,21 @@ public void testFailure() {
111109
}
112110
}
113111

112+
private static class AsyncActivityImpl implements TestActivity {
113+
@Override
114+
public String activity1(String input) {
115+
Activity.getExecutionContext().doNotCompleteOnReturn();
116+
return "";
117+
}
118+
}
119+
120+
@Test
121+
public void testAsyncActivity() {
122+
testEnvironment.registerActivitiesImplementations(new AsyncActivityImpl());
123+
TestActivity activity = testEnvironment.newActivityStub(TestActivity.class);
124+
Assert.assertThrows(ActivityRequestedAsyncCompletion.class, () -> activity.activity1("input1"));
125+
}
126+
114127
private static class HeartbeatActivityImpl implements TestActivity {
115128

116129
@Override
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3+
*
4+
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this material except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package io.temporal.testing;
22+
23+
/**
24+
* Exception thrown when an activity request to complete asynchronously in the {@link
25+
* TestActivityEnvironment}. Intended to be used in unit tests to assert an activity requested async
26+
* completion.
27+
*/
28+
public final class ActivityRequestedAsyncCompletion extends RuntimeException {
29+
private final String activityId;
30+
private final boolean manualCompletion;
31+
32+
public ActivityRequestedAsyncCompletion(String activityId, boolean manualCompletion) {
33+
super("activity requested async completion");
34+
this.activityId = activityId;
35+
this.manualCompletion = manualCompletion;
36+
}
37+
38+
public String getActivityId() {
39+
return activityId;
40+
}
41+
42+
public boolean isManualCompletion() {
43+
return manualCompletion;
44+
}
45+
}

temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironment.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ static TestActivityEnvironment newInstance(TestEnvironmentOptions options) {
8282
* Creates a stub that can be used to invoke activities registered through {@link
8383
* #registerActivitiesImplementations(Object...)}.
8484
*
85+
* <p>Activity methods may throw {@link ActivityRequestedAsyncCompletion} if the activity
86+
* requested async completion.
87+
*
8588
* @param activityInterface activity interface class that the object under test implements.
8689
* @param <T> Type of the activity interface.
8790
* @return The stub that implements the activity interface.
@@ -92,6 +95,9 @@ static TestActivityEnvironment newInstance(TestEnvironmentOptions options) {
9295
* Creates a stub that can be used to invoke activities registered through {@link
9396
* #registerActivitiesImplementations(Object...)}.
9497
*
98+
* <p>Activity methods may throw {@link ActivityRequestedAsyncCompletion} if the activity
99+
* requested async completion.
100+
*
95101
* @param <T> Type of the activity interface.
96102
* @param activityInterface activity interface class that the object under test implements
97103
* @param options options that specify the activity invocation parameters

temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
package io.temporal.testing;
2222

23-
import com.google.common.base.Defaults;
2423
import com.google.protobuf.ByteString;
2524
import com.uber.m3.tally.NoopScope;
2625
import com.uber.m3.tally.Scope;
@@ -511,40 +510,38 @@ private <T> T getReply(
511510
Type resultType) {
512511
DataConverter dataConverter =
513512
testEnvironmentOptions.getWorkflowClientOptions().getDataConverter();
514-
RespondActivityTaskCompletedRequest taskCompleted = response.getTaskCompleted();
515-
if (taskCompleted != null) {
513+
if (response.getTaskCompleted() != null) {
514+
RespondActivityTaskCompletedRequest taskCompleted = response.getTaskCompleted();
516515
Optional<Payloads> result =
517516
taskCompleted.hasResult() ? Optional.of(taskCompleted.getResult()) : Optional.empty();
518517
return dataConverter.fromPayloads(0, result, resultClass, resultType);
519-
} else {
518+
} else if (response.getTaskFailed() != null) {
520519
RespondActivityTaskFailedRequest taskFailed =
521520
response.getTaskFailed().getTaskFailedRequest();
522-
if (taskFailed != null) {
523-
Exception cause = dataConverter.failureToException(taskFailed.getFailure());
524-
throw new ActivityFailure(
525-
taskFailed.getFailure().getMessage(),
526-
0,
527-
0,
528-
task.getActivityType().getName(),
529-
task.getActivityId(),
530-
RetryState.RETRY_STATE_NON_RETRYABLE_FAILURE,
531-
"TestActivityEnvironment",
532-
cause);
533-
} else {
534-
RespondActivityTaskCanceledRequest taskCanceled = response.getTaskCanceled();
535-
if (taskCanceled != null) {
536-
throw new CanceledFailure(
537-
"canceled",
538-
new EncodedValues(
539-
taskCanceled.hasDetails()
540-
? Optional.of(taskCanceled.getDetails())
541-
: Optional.empty(),
542-
dataConverter),
543-
null);
544-
}
545-
}
521+
Exception cause = dataConverter.failureToException(taskFailed.getFailure());
522+
throw new ActivityFailure(
523+
taskFailed.getFailure().getMessage(),
524+
0,
525+
0,
526+
task.getActivityType().getName(),
527+
task.getActivityId(),
528+
RetryState.RETRY_STATE_NON_RETRYABLE_FAILURE,
529+
"TestActivityEnvironment",
530+
cause);
531+
} else if (response.getTaskCanceled() != null) {
532+
RespondActivityTaskCanceledRequest taskCanceled = response.getTaskCanceled();
533+
throw new CanceledFailure(
534+
"canceled",
535+
new EncodedValues(
536+
taskCanceled.hasDetails()
537+
? Optional.of(taskCanceled.getDetails())
538+
: Optional.empty(),
539+
dataConverter),
540+
null);
541+
} else {
542+
throw new ActivityRequestedAsyncCompletion(
543+
task.getActivityId(), response.isManualCompletion());
546544
}
547-
return Defaults.defaultValue(resultClass);
548545
}
549546

550547
@Override

0 commit comments

Comments
 (0)