|
7 | 7 | import io.opentracing.mock.MockTracer;
|
8 | 8 | import io.opentracing.tag.Tags;
|
9 | 9 | import io.opentracing.util.ThreadLocalScopeManager;
|
| 10 | +import io.temporal.activity.Activity; |
10 | 11 | import io.temporal.activity.ActivityInterface;
|
11 | 12 | import io.temporal.activity.ActivityMethod;
|
12 | 13 | import io.temporal.activity.ActivityOptions;
|
13 | 14 | import io.temporal.client.WorkflowClient;
|
14 | 15 | import io.temporal.client.WorkflowClientOptions;
|
15 | 16 | import io.temporal.client.WorkflowOptions;
|
16 | 17 | import io.temporal.common.RetryOptions;
|
| 18 | +import io.temporal.failure.ApplicationErrorCategory; |
17 | 19 | import io.temporal.failure.ApplicationFailure;
|
18 | 20 | import io.temporal.testing.internal.SDKTestWorkflowRule;
|
19 | 21 | import io.temporal.worker.WorkerFactoryOptions;
|
@@ -152,4 +154,108 @@ public void testActivityFailureSpanStructure() {
|
152 | 154 | assertEquals(activityStartSpan.context().spanId(), activitySuccessfulRunSpan.parentId());
|
153 | 155 | assertEquals("RunActivity:Activity", activitySuccessfulRunSpan.operationName());
|
154 | 156 | }
|
| 157 | + |
| 158 | + @Rule |
| 159 | + public SDKTestWorkflowRule benignTestRule = |
| 160 | + SDKTestWorkflowRule.newBuilder() |
| 161 | + .setWorkerFactoryOptions( |
| 162 | + WorkerFactoryOptions.newBuilder() |
| 163 | + .setWorkerInterceptors(new OpenTracingWorkerInterceptor(OT_OPTIONS)) |
| 164 | + .validateAndBuildWithDefaults()) |
| 165 | + .setWorkflowTypes(BenignWorkflowImpl.class) |
| 166 | + .setActivityImplementations(new BenignFailingActivityImpl()) |
| 167 | + .build(); |
| 168 | + |
| 169 | + @ActivityInterface |
| 170 | + public interface BenignTestActivity { |
| 171 | + @ActivityMethod |
| 172 | + String throwMaybeBenign(); |
| 173 | + } |
| 174 | + |
| 175 | + @WorkflowInterface |
| 176 | + public interface BenignTestWorkflow { |
| 177 | + @WorkflowMethod |
| 178 | + String workflow(); |
| 179 | + } |
| 180 | + |
| 181 | + public static class BenignFailingActivityImpl implements BenignTestActivity { |
| 182 | + @Override |
| 183 | + public String throwMaybeBenign() { |
| 184 | + int attempt = Activity.getExecutionContext().getInfo().getAttempt(); |
| 185 | + if (attempt == 1) { |
| 186 | + // First attempt: regular failure |
| 187 | + throw ApplicationFailure.newFailure("not benign", "TestFailure"); |
| 188 | + } else if (attempt == 2) { |
| 189 | + // Second attempt: benign failure |
| 190 | + throw ApplicationFailure.newBuilder() |
| 191 | + .setMessage("benign") |
| 192 | + .setType("TestFailure") |
| 193 | + .setCategory(ApplicationErrorCategory.BENIGN) |
| 194 | + .build(); |
| 195 | + } else { |
| 196 | + // Third attempt: success |
| 197 | + return "success"; |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + public static class BenignWorkflowImpl implements BenignTestWorkflow { |
| 203 | + private final BenignTestActivity activity = |
| 204 | + Workflow.newActivityStub( |
| 205 | + BenignTestActivity.class, |
| 206 | + ActivityOptions.newBuilder() |
| 207 | + .setStartToCloseTimeout(Duration.ofMinutes(1)) |
| 208 | + .setRetryOptions( |
| 209 | + RetryOptions.newBuilder() |
| 210 | + .setMaximumAttempts(3) |
| 211 | + .setBackoffCoefficient(1) |
| 212 | + .setInitialInterval(Duration.ofMillis(100)) |
| 213 | + .build()) |
| 214 | + .validateAndBuildWithDefaults()); |
| 215 | + |
| 216 | + @Override |
| 217 | + public String workflow() { |
| 218 | + return activity.throwMaybeBenign(); |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + @Test |
| 223 | + public void testBenignApplicationFailureSpanBehavior() { |
| 224 | + MockSpan span = mockTracer.buildSpan("BenignTestFunction").start(); |
| 225 | + |
| 226 | + WorkflowClient client = benignTestRule.getWorkflowClient(); |
| 227 | + try (Scope scope = mockTracer.scopeManager().activate(span)) { |
| 228 | + BenignTestWorkflow workflow = |
| 229 | + client.newWorkflowStub( |
| 230 | + BenignTestWorkflow.class, |
| 231 | + WorkflowOptions.newBuilder() |
| 232 | + .setTaskQueue(benignTestRule.getTaskQueue()) |
| 233 | + .validateBuildWithDefaults()); |
| 234 | + assertEquals("success", workflow.workflow()); |
| 235 | + } finally { |
| 236 | + span.finish(); |
| 237 | + } |
| 238 | + |
| 239 | + List<MockSpan> allSpans = mockTracer.finishedSpans(); |
| 240 | + |
| 241 | + // Filter to only activity execution spans (RunActivity spans created by worker interceptor) |
| 242 | + List<MockSpan> activityRunSpans = |
| 243 | + allSpans.stream() |
| 244 | + .filter(s -> s.operationName().startsWith("RunActivity:")) |
| 245 | + .collect(java.util.stream.Collectors.toList()); |
| 246 | + |
| 247 | + assertEquals(3, activityRunSpans.size()); |
| 248 | + |
| 249 | + // First attempt: regular failure - should have ERROR tag |
| 250 | + MockSpan firstAttemptSpan = activityRunSpans.get(0); |
| 251 | + assertEquals(true, firstAttemptSpan.tags().get(Tags.ERROR.getKey())); |
| 252 | + |
| 253 | + // Second attempt: benign failure - should NOT have ERROR tag |
| 254 | + MockSpan secondAttemptSpan = activityRunSpans.get(1); |
| 255 | + assertEquals(null, secondAttemptSpan.tags().get(Tags.ERROR.getKey())); |
| 256 | + |
| 257 | + // Third attempt: success - should not have ERROR tag |
| 258 | + MockSpan thirdAttemptSpan = activityRunSpans.get(2); |
| 259 | + assertEquals(null, thirdAttemptSpan.tags().get(Tags.ERROR.getKey())); |
| 260 | + } |
155 | 261 | }
|
0 commit comments