Skip to content

Commit 53b769b

Browse files
authored
test for updatewithstart and cancellation (#750)
Signed-off-by: Tihomir Surdilovic <[email protected]>
1 parent c126e31 commit 53b769b

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package io.temporal.samples.hello;
2+
3+
import io.temporal.activity.*;
4+
import io.temporal.api.enums.v1.WorkflowIdConflictPolicy;
5+
import io.temporal.client.*;
6+
import io.temporal.failure.ActivityFailure;
7+
import io.temporal.failure.CanceledFailure;
8+
import io.temporal.testing.TestWorkflowRule;
9+
import io.temporal.workflow.*;
10+
import java.time.Duration;
11+
import java.util.concurrent.TimeUnit;
12+
import org.junit.Assert;
13+
import org.junit.Rule;
14+
import org.junit.Test;
15+
16+
public class HelloUpdateAndCancellationTest {
17+
@Rule
18+
public TestWorkflowRule testWorkflowRule =
19+
TestWorkflowRule.newBuilder()
20+
.setWorkflowTypes(TestWorkflowImpl.class)
21+
.setActivityImplementations(new TestActivitiesImpl())
22+
.build();
23+
24+
@Test
25+
public void testUpdateAndWorkflowCancellation() {
26+
// Start workflow with UpdateWithStart then cancel workflow before activity completes
27+
TestWorkflow workflow =
28+
testWorkflowRule
29+
.getWorkflowClient()
30+
.newWorkflowStub(
31+
TestWorkflow.class,
32+
WorkflowOptions.newBuilder()
33+
.setWorkflowId("test-workflow-cancel")
34+
.setTaskQueue(testWorkflowRule.getTaskQueue())
35+
.setWorkflowIdConflictPolicy(
36+
WorkflowIdConflictPolicy.WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING)
37+
.build());
38+
39+
WorkflowUpdateHandle<String> updateHandle =
40+
WorkflowClient.startUpdateWithStart(
41+
workflow::mileStoneCompleted,
42+
UpdateOptions.<String>newBuilder()
43+
.setWaitForStage(WorkflowUpdateStage.ACCEPTED)
44+
.build(),
45+
new WithStartWorkflowOperation<>(workflow::execute));
46+
47+
testWorkflowRule
48+
.getTestEnvironment()
49+
.registerDelayedCallback(
50+
Duration.ofSeconds(3),
51+
() -> {
52+
WorkflowStub.fromTyped(workflow).cancel("canceled by test");
53+
});
54+
55+
String updateResult = updateHandle.getResult();
56+
Assert.assertEquals("milestone canceled", updateResult);
57+
58+
try {
59+
WorkflowStub.fromTyped(workflow).getResult(String.class);
60+
Assert.fail("Workflow Execution should have been canceled");
61+
} catch (WorkflowFailedException e) {
62+
// Our workflow should have been canceled
63+
Assert.assertEquals(CanceledFailure.class, e.getCause().getClass());
64+
}
65+
}
66+
67+
@Test
68+
public void testUpdateAndActivityCancellation() {
69+
// Start workflow with UpdateWithStart then cancel the activity only by sending signal to
70+
// execution
71+
TestWorkflow workflow =
72+
testWorkflowRule
73+
.getWorkflowClient()
74+
.newWorkflowStub(
75+
TestWorkflow.class,
76+
WorkflowOptions.newBuilder()
77+
.setWorkflowId("test-activity-cancel")
78+
.setTaskQueue(testWorkflowRule.getTaskQueue())
79+
.setWorkflowIdConflictPolicy(
80+
WorkflowIdConflictPolicy.WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING)
81+
.build());
82+
83+
WorkflowUpdateHandle<String> updateHandle =
84+
WorkflowClient.startUpdateWithStart(
85+
workflow::mileStoneCompleted,
86+
UpdateOptions.<String>newBuilder()
87+
.setWaitForStage(WorkflowUpdateStage.ACCEPTED)
88+
.build(),
89+
new WithStartWorkflowOperation<>(workflow::execute));
90+
91+
testWorkflowRule
92+
.getTestEnvironment()
93+
.registerDelayedCallback(
94+
Duration.ofSeconds(3),
95+
() -> {
96+
WorkflowStub.fromTyped(workflow).signal("cancelActivity");
97+
});
98+
99+
String updateResult = updateHandle.getResult();
100+
Assert.assertEquals("milestone canceled", updateResult);
101+
102+
try {
103+
WorkflowStub.fromTyped(workflow).getResult(String.class);
104+
Assert.fail("Workflow Execution should have failed");
105+
} catch (WorkflowFailedException e) {
106+
// In this case we did not cancel workflow execution but we failed it by throwing
107+
// ActivityFailure
108+
Assert.assertEquals(ActivityFailure.class, e.getCause().getClass());
109+
ActivityFailure af = (ActivityFailure) e.getCause();
110+
// Since we canceled the activity still, the cause of ActivityFailure should be
111+
// CanceledFailure
112+
Assert.assertEquals(CanceledFailure.class, af.getCause().getClass());
113+
}
114+
}
115+
116+
@WorkflowInterface
117+
public interface TestWorkflow {
118+
@WorkflowMethod
119+
String execute();
120+
121+
@UpdateMethod
122+
String mileStoneCompleted();
123+
124+
@SignalMethod
125+
void cancelActivity();
126+
}
127+
128+
public static class TestWorkflowImpl implements TestWorkflow {
129+
boolean milestoneDone, mileStoneCanceled;
130+
CancellationScope scope;
131+
TestActivities activities =
132+
Workflow.newActivityStub(
133+
TestActivities.class,
134+
ActivityOptions.newBuilder()
135+
.setHeartbeatTimeout(Duration.ofSeconds(3))
136+
.setStartToCloseTimeout(Duration.ofSeconds(10))
137+
.setCancellationType(ActivityCancellationType.WAIT_CANCELLATION_COMPLETED)
138+
.build());
139+
140+
@Override
141+
public String execute() {
142+
scope =
143+
Workflow.newCancellationScope(
144+
() -> {
145+
activities.runActivity();
146+
});
147+
148+
try {
149+
scope.run();
150+
milestoneDone = true;
151+
Workflow.await(Workflow::isEveryHandlerFinished);
152+
return "workflow completed";
153+
} catch (ActivityFailure e) {
154+
if (e.getCause() instanceof CanceledFailure) {
155+
CancellationScope detached =
156+
Workflow.newDetachedCancellationScope(
157+
() -> {
158+
mileStoneCanceled = true;
159+
Workflow.await(Workflow::isEveryHandlerFinished);
160+
});
161+
detached.run();
162+
}
163+
throw e;
164+
}
165+
}
166+
167+
@Override
168+
public String mileStoneCompleted() {
169+
Workflow.await(() -> milestoneDone || mileStoneCanceled);
170+
// For sake of testing isEveryHandlerFinished block here for 2 seconds
171+
Workflow.sleep(Duration.ofSeconds(2));
172+
return milestoneDone ? "milestone completed" : "milestone canceled";
173+
}
174+
175+
@Override
176+
public void cancelActivity() {
177+
if (scope != null) {
178+
scope.cancel("test reason");
179+
}
180+
}
181+
}
182+
183+
@ActivityInterface
184+
public interface TestActivities {
185+
void runActivity();
186+
}
187+
188+
public static class TestActivitiesImpl implements TestActivities {
189+
190+
@Override
191+
public void runActivity() {
192+
ActivityExecutionContext context = Activity.getExecutionContext();
193+
for (int i = 0; i < 9; i++) {
194+
sleep(1);
195+
try {
196+
context.heartbeat(i);
197+
} catch (ActivityCompletionException e) {
198+
throw e;
199+
}
200+
}
201+
}
202+
}
203+
204+
private static void sleep(int seconds) {
205+
try {
206+
Thread.sleep(TimeUnit.SECONDS.toMillis(seconds));
207+
} catch (InterruptedException e) {
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)