|
11 | 11 |
|
12 | 12 | from __future__ import annotations |
13 | 13 |
|
| 14 | +from types import SimpleNamespace |
14 | 15 | from typing import Any, cast |
15 | 16 |
|
16 | 17 | import pytest |
17 | 18 | from opentelemetry import trace as otel_trace |
18 | 19 | from opentelemetry.trace import TraceFlags, SpanContext |
| 20 | +from temporalio import activity as temporal_activity |
19 | 21 |
|
20 | 22 | import agentex.lib.core.tracing.trace as trace_mod |
21 | 23 | import agentex.lib.core.tracing.obs_ids as obs_ids_mod |
22 | 24 | from agentex.lib.core.tracing.trace import _OBS_HANDLES, Trace |
23 | 25 | from agentex.lib.core.tracing.obs_ids import obs_correlation |
24 | 26 | from agentex.lib.core.tracing.obs_span import tag_ambient_obs_span |
| 27 | +from agentex.lib.core.temporal.activities.adk.tracing_activities import TracingActivityName |
25 | 28 |
|
26 | 29 | _TRACE_ID = 0x0123456789ABCDEF0123456789ABCDEF |
27 | 30 | _SPAN_ID = 0x0123456789ABCDEF |
@@ -188,3 +191,41 @@ def test_business_activity_dd_only_reads_otel_not_ddtrace(monkeypatch: pytest.Mo |
188 | 191 | # OTel ids, not ddtrace's ("d"*32) and not empty. |
189 | 192 | assert span.data["obs_trace_id"] == _TRACE_HEX |
190 | 193 | assert span.data["obs_span_id"] == _SPAN_HEX |
| 194 | + |
| 195 | + |
| 196 | +# --------------------------------------------------------------------------- # |
| 197 | +# The dispatch discriminator itself (trace.py:_in_tracing_dispatch_activity). |
| 198 | +# This is the one line preventing a cross-worker handle leak inside START_SPAN, |
| 199 | +# so it gets exercised directly with a faked activity.info() -- and against the |
| 200 | +# enum's own .value, so it also fails if TracingActivityName ever drifts from |
| 201 | +# the strings hardcoded in trace.py. |
| 202 | +# --------------------------------------------------------------------------- # |
| 203 | +def test_dispatch_discriminator_true_for_start_and_end_span(monkeypatch: pytest.MonkeyPatch) -> None: |
| 204 | + monkeypatch.setattr(temporal_activity, "in_activity", lambda: True) |
| 205 | + for name in (TracingActivityName.START_SPAN, TracingActivityName.END_SPAN): |
| 206 | + # activity_type round-trips as the plain string value through protobuf. |
| 207 | + monkeypatch.setattr(temporal_activity, "info", lambda n=name: SimpleNamespace(activity_type=n.value)) |
| 208 | + assert trace_mod._in_tracing_dispatch_activity() is True, name |
| 209 | + |
| 210 | + |
| 211 | +def test_dispatch_discriminator_false_for_business_activity(monkeypatch: pytest.MonkeyPatch) -> None: |
| 212 | + # A real agent-turn activity (e.g. process_mortgage_turn) is NOT a dispatch |
| 213 | + # activity -> it must take the per-step wrapper branch, not Option-A tagging. |
| 214 | + monkeypatch.setattr(temporal_activity, "in_activity", lambda: True) |
| 215 | + monkeypatch.setattr(temporal_activity, "info", lambda: SimpleNamespace(activity_type="process_mortgage_turn")) |
| 216 | + assert trace_mod._in_tracing_dispatch_activity() is False |
| 217 | + |
| 218 | + |
| 219 | +def test_dispatch_discriminator_false_when_not_in_activity(monkeypatch: pytest.MonkeyPatch) -> None: |
| 220 | + # Guard short-circuits on in_activity()==False; info() (here a dispatch value) |
| 221 | + # must never be consulted, else the sync path would be misclassified. |
| 222 | + monkeypatch.setattr(temporal_activity, "in_activity", lambda: False) |
| 223 | + monkeypatch.setattr(temporal_activity, "info", lambda: SimpleNamespace(activity_type="start-span")) |
| 224 | + assert trace_mod._in_tracing_dispatch_activity() is False |
| 225 | + |
| 226 | + |
| 227 | +def test_in_temporal_activity_tracks_in_activity(monkeypatch: pytest.MonkeyPatch) -> None: |
| 228 | + monkeypatch.setattr(temporal_activity, "in_activity", lambda: True) |
| 229 | + assert trace_mod._in_temporal_activity() is True |
| 230 | + monkeypatch.setattr(temporal_activity, "in_activity", lambda: False) |
| 231 | + assert trace_mod._in_temporal_activity() is False |
0 commit comments