From 2e9319359b5a20e3eae3d27471805754aba833c7 Mon Sep 17 00:00:00 2001 From: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:08:40 +0900 Subject: [PATCH] Python: Add regression tests for Entry JoinExecutor Workflow.Inputs initialization (#4335) * Python: Add regression tests for #3948 - Entry JoinExecutor initializes Workflow.Inputs Add tests verifying that when workflow.run() is called with a dict or string input, the Entry node (JoinExecutor with kind: 'Entry') correctly initializes Workflow.Inputs via _ensure_state_initialized so that: - Expressions like =inputs.age resolve to the correct value - Conditions like =Local.age < 13 evaluate based on actual input (not blank/0) - String inputs populate both inputs.input and System.LastMessage.Text Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply pre-commit auto-fixes * Fix D420 and RUF070 lint errors across packages * Revert _workflow.py yield-inside-context-manager changes Moving yield inside `with _framework_event_origin()` blocks in the async generator causes ContextVar token reset failures on Python 3.12 Windows. The token stays un-reset while the generator is suspended, and async generator finalization in a different contextvars.Context triggers ValueError, corrupting OpenTelemetry span state and causing test_span_creation_and_attributes to see leaked spans. Keep yields outside the context manager blocks to ensure tokens are reset immediately before the generator suspends. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/test_workflow_factory.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/python/packages/declarative/tests/test_workflow_factory.py b/python/packages/declarative/tests/test_workflow_factory.py index 25c1249a50..f08f5993e5 100644 --- a/python/packages/declarative/tests/test_workflow_factory.py +++ b/python/packages/declarative/tests/test_workflow_factory.py @@ -159,6 +159,75 @@ actions: _text_outputs = [str(o) for o in outputs if isinstance(o, str) or hasattr(o, "data")] # noqa: F841 assert any("Condition was true" in str(o) for o in outputs) + @pytest.mark.asyncio + async def test_entry_join_executor_initializes_workflow_inputs(self): + """Regression test for #3948: Entry JoinExecutor must initialize Workflow.Inputs. + + When workflow.run() is called with a dict input, the Entry node (JoinExecutor + with kind: 'Entry') must call _ensure_state_initialized so that Workflow.Inputs + is populated. Without this, expressions like =inputs.age resolve to blank and + conditions like =Local.age < 13 always evaluate as true (blank treated as 0). + """ + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: entry-inputs-test +actions: + - kind: SetValue + id: get_age + path: Local.age + value: =inputs.age + - kind: If + id: check_age + condition: =Local.age < 13 + then: + - kind: SendActivity + activity: + text: child + else: + - kind: SendActivity + activity: + text: adult +""") + + # age=8 -> child branch + result_child = await workflow.run({"age": 8}) + outputs_child = result_child.get_outputs() + assert any("child" in str(o) for o in outputs_child), f"Expected 'child' for age=8 but got: {outputs_child}" + assert not any("adult" in str(o) for o in outputs_child), ( + f"Did not expect 'adult' for age=8 but got: {outputs_child}" + ) + + # age=25 -> adult branch (bug: blank treated as 0 made this always go to child) + result_adult = await workflow.run({"age": 25}) + outputs_adult = result_adult.get_outputs() + assert any("adult" in str(o) for o in outputs_adult), f"Expected 'adult' for age=25 but got: {outputs_adult}" + assert not any("child" in str(o) for o in outputs_adult), ( + f"Did not expect 'child' for age=25 but got: {outputs_adult}" + ) + + @pytest.mark.asyncio + async def test_entry_join_executor_initializes_workflow_inputs_string(self): + """Regression test for #3948: Entry JoinExecutor must initialize Workflow.Inputs for string input. + + When workflow.run() is called with a string input, Workflow.Inputs.input and + System.LastMessage.Text should be set correctly. + """ + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: entry-string-inputs-test +actions: + - kind: SetValue + path: Local.msg + value: =inputs.input + - kind: SendActivity + activity: + text: =Local.msg +""") + + result = await workflow.run("hello-world") + outputs = result.get_outputs() + assert any("hello-world" in str(o) for o in outputs), f"Expected 'hello-world' in outputs but got: {outputs}" + class TestWorkflowFactoryAgentRegistration: """Tests for agent registration."""