Skip to content
Merged
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
35 changes: 6 additions & 29 deletions tests/contrib/langgraph/test_summary_fn.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from temporalio.contrib.langgraph import LangGraphPlugin, graph
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Replayer, Worker
from tests.helpers import assert_eq_eventually

SummaryFn = Callable[[tuple[Any, ...], dict[str, Any]], "str | None"]

Expand Down Expand Up @@ -234,27 +233,10 @@ async def test_summary_fn_not_in_node_metadata(client: Client) -> None:
class WorkflowNodeSummaryWorkflow:
def __init__(self) -> None:
self.app = graph("wf-node-graph").compile()
self._done = False
self._invoked = False

@workflow.run
async def run(self, input: str) -> Any:
result = await self.app.ainvoke({"value": input})
self._invoked = True
await workflow.wait_condition(lambda: self._done)
return result

@workflow.signal
def finish(self) -> None:
self._done = True

@workflow.query
def ran(self) -> bool:
return workflow.get_current_details() != ""

@workflow.query
def invoked(self) -> bool:
return self._invoked
return await self.app.ainvoke({"value": input})


async def test_workflow_node_sets_current_details(
Expand Down Expand Up @@ -285,16 +267,15 @@ async def test_workflow_node_sets_current_details(
id=f"wf-node-{uuid.uuid4()}",
task_queue=task_queue,
)
await assert_eq_eventually(
True, lambda: handle.query(WorkflowNodeSummaryWorkflow.ran)
)
assert await handle.result() == {"value": "ready"}
# Details are set last-writer-wins by the workflow-side node and nothing
# clears them at graph end, so the completed workflow answers the same
# as a mid-run probe would, with no workflow task in flight.
md: temporalio.api.sdk.v1.WorkflowMetadata = await handle.query(
"__temporal_workflow_metadata",
result_type=temporalio.api.sdk.v1.WorkflowMetadata,
)
assert md.current_details == "wf:ready"
await handle.signal(WorkflowNodeSummaryWorkflow.finish)
assert await handle.result() == {"value": "ready"}


async def test_workflow_node_clears_current_details_on_empty(
Expand Down Expand Up @@ -328,16 +309,12 @@ async def test_workflow_node_clears_current_details_on_empty(
id=f"wf-node-clear-{uuid.uuid4()}",
task_queue=task_queue,
)
await assert_eq_eventually(
True, lambda: handle.query(WorkflowNodeSummaryWorkflow.invoked)
)
await handle.result()
md: temporalio.api.sdk.v1.WorkflowMetadata = await handle.query(
"__temporal_workflow_metadata",
result_type=temporalio.api.sdk.v1.WorkflowMetadata,
)
assert md.current_details == ""
await handle.signal(WorkflowNodeSummaryWorkflow.finish)
await handle.result()


async def test_replay_with_summary_fn(client: Client) -> None:
Expand Down
61 changes: 22 additions & 39 deletions tests/contrib/openai_agents/test_openai_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter

from temporalio import activity, workflow
from temporalio.api.enums.v1 import EventType
from temporalio.client import Client
from temporalio.contrib.openai_agents import _temporal_openai_agents
from temporalio.contrib.openai_agents.testing import (
Expand All @@ -24,7 +25,7 @@
ResearchWorkflow,
research_mock_model,
)
from tests.helpers import assert_eq_eventually, new_worker
from tests.helpers import assert_event_subsequence, new_worker


class MemoryTracingProcessor(TracingProcessor):
Expand Down Expand Up @@ -242,11 +243,26 @@ async def simple_no_context_activity() -> str:
return "success"


async def wait_for_activity_processed(client: Client, workflow_id: str) -> None:
"""Wait, via an untraced history poll, until the workflow task that handled the activity result completed.

Assumes the workflow's first completed activity is the one right before its park point, which
holds for every workflow in this module.
"""
await assert_event_subsequence(
client.get_workflow_handle(workflow_id),
[
EventType.EVENT_TYPE_ACTIVITY_TASK_COMPLETED,
EventType.EVENT_TYPE_WORKFLOW_TASK_COMPLETED,
],
timeout=timedelta(seconds=10),
)


@workflow.defn
class TraceWorkflow:
def __init__(self) -> None:
self._proceed = False
self._ready = False

@workflow.run
async def run(self):
Expand All @@ -256,14 +272,9 @@ async def run(self):
simple_no_context_activity,
start_to_close_timeout=timedelta(seconds=10),
)
self._ready = True
await workflow.wait_condition(lambda: self._proceed)
return "done"

@workflow.query
def ready(self) -> bool:
return self._ready

@workflow.signal
def proceed(self) -> None:
self._proceed = True
Expand All @@ -273,7 +284,6 @@ def proceed(self) -> None:
class SelfTracingWorkflow:
def __init__(self) -> None:
self._proceed = False
self._ready = False

@workflow.run
async def run(self):
Expand All @@ -284,14 +294,9 @@ async def run(self):
simple_no_context_activity,
start_to_close_timeout=timedelta(seconds=10),
)
self._ready = True
await workflow.wait_condition(lambda: self._proceed)
return "done"

@workflow.query
def ready(self) -> bool:
return self._ready

@workflow.signal
def proceed(self) -> None:
self._proceed = True
Expand Down Expand Up @@ -366,11 +371,7 @@ async def test_external_trace_to_workflow_spans(
max_cached_workflows=0,
task_queue=task_queue,
):
# Wait for workflow to be ready
async def ready() -> bool:
return await workflow_handle.query(TraceWorkflow.ready)

await assert_eq_eventually(True, ready)
await wait_for_activity_processed(client, workflow_handle.id)

# Second worker: Complete the workflow with fresh objects (new instrumentation)
async with AgentEnvironment(
Expand Down Expand Up @@ -458,11 +459,7 @@ async def test_external_trace_and_span_to_workflow_spans(
max_cached_workflows=0,
task_queue=task_queue,
):
# Wait for workflow to be ready
async def ready() -> bool:
return await workflow_handle.query(TraceWorkflow.ready)

await assert_eq_eventually(True, ready)
await wait_for_activity_processed(client, workflow_handle.id)

# Second worker: Complete the workflow with fresh objects (new instrumentation)
async with AgentEnvironment(
Expand Down Expand Up @@ -554,11 +551,7 @@ async def test_workflow_only_trace_to_spans(
)
workflow_id = workflow_handle.id

# Wait for workflow to be ready
async def ready() -> bool:
return await workflow_handle.query(SelfTracingWorkflow.ready)

await assert_eq_eventually(True, ready)
await wait_for_activity_processed(client, workflow_handle.id)

# Second worker: Complete the workflow with fresh objects (new instrumentation)
async with AgentEnvironment(
Expand Down Expand Up @@ -805,7 +798,6 @@ def is_descendant_of(child: ReadableSpan, ancestor_span_id: int) -> bool:
class OtelSpanWorkflow:
def __init__(self) -> None:
self._proceed = False
self._ready = False

@workflow.run
async def run(self):
Expand All @@ -818,14 +810,9 @@ async def run(self):
simple_no_context_activity,
start_to_close_timeout=timedelta(seconds=10),
)
self._ready = True
await workflow.wait_condition(lambda: self._proceed)
return "done"

@workflow.query
def ready(self) -> bool:
return self._ready

@workflow.signal
def proceed(self) -> None:
self._proceed = True
Expand Down Expand Up @@ -868,11 +855,7 @@ async def test_sdk_trace_to_otel_span_parenting(
)
workflow_id = workflow_handle.id

# Wait for workflow to be ready
async def ready() -> bool:
return await workflow_handle.query(OtelSpanWorkflow.ready)

await assert_eq_eventually(True, ready)
await wait_for_activity_processed(client, workflow_handle.id)

# Second worker: Complete the workflow with fresh objects (new instrumentation)
async with AgentEnvironment(
Expand Down
12 changes: 8 additions & 4 deletions tests/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
PollWorkflowExecutionUpdateRequest,
UnpauseActivityRequest,
)
from temporalio.client import BuildIdOpAddNewDefault, Client, WorkflowHandle
from temporalio.client import (
BuildIdOpAddNewDefault,
Client,
WorkflowHandle,
)
from temporalio.common import SearchAttributeKey
from temporalio.converter import DataConverter
from temporalio.service import RPCError, RPCStatusCode
Expand Down Expand Up @@ -83,9 +87,9 @@ async def assert_eventually(
if timedelta(seconds=time.monotonic() - start_sec) >= timeout:
raise
except RPCError as e:
if retry_on_rpc_cancelled and e.status == RPCStatusCode.CANCELLED:
continue
else:
if not (retry_on_rpc_cancelled and e.status == RPCStatusCode.CANCELLED):
raise
if timedelta(seconds=time.monotonic() - start_sec) >= timeout:
raise
await asyncio.sleep(interval.total_seconds())

Expand Down
Loading