Skip to content

Commit 64de730

Browse files
committed
fix(tests): handle new context_create fixture cases and portable fixture paths
- Add elif branches for distributed_cancel_token_post_deserialize_null and distributed_global_deadline_post_deserialize_null in test_context_create_unified_signature (both assert null after deserialize) - Replace hardcoded absolute paths in test_v022_event_delivery.py and test_v022_registry_ordering.py with APCORE_SPEC_REPO env-var / sibling directory discovery (mirrors test_conformance.py discovery logic) Signed-off-by: tercel <tercel.yi@gmail.com>
1 parent ccadc85 commit 64de730

5 files changed

Lines changed: 66 additions & 29 deletions

File tree

‎examples/pipeline_demo.py‎

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@
4444

4545
# Per-step roles, used by TracingMiddleware to narrate the pipeline.
4646
STEP_ROLES: dict[str, str] = {
47-
"context_creation": "create execution context, set global deadline",
48-
"call_chain_guard": "check call depth & repeat limits",
49-
"module_lookup": "resolve module from registry",
50-
"acl_check": "enforce access control (default-deny)",
51-
"approval_gate": "human approval gate (if required)",
47+
"context_creation": "create execution context, set global deadline",
48+
"call_chain_guard": "check call depth & repeat limits",
49+
"module_lookup": "resolve module from registry",
50+
"acl_check": "enforce access control (default-deny)",
51+
"approval_gate": "human approval gate (if required)",
5252
"middleware_before": "run before-middleware chain (in order)",
53-
"input_validation": "validate inputs against schema",
54-
"execute": "invoke the module",
53+
"input_validation": "validate inputs against schema",
54+
"execute": "invoke the module",
5555
"output_validation": "validate output against schema",
56-
"middleware_after": "run after-middleware chain (reverse order)",
57-
"return_result": "finalize and return output",
56+
"middleware_after": "run after-middleware chain (reverse order)",
57+
"return_result": "finalize and return output",
5858
}
5959

6060

@@ -80,16 +80,12 @@ def before_step(self, step_name: str, state: PipelineState) -> None:
8080
role = "CUSTOM step inserted via insert_after / replace"
8181
print(f" {label} {step_name:<19} — {role}")
8282

83-
def after_step(
84-
self, step_name: str, state: PipelineState, result: StepResult
85-
) -> None:
83+
def after_step(self, step_name: str, state: PipelineState, result: StepResult) -> None:
8684
elapsed_ms = (time.perf_counter() - self._starts.pop(step_name, time.perf_counter())) * 1000
8785
detail = self._summarize(step_name, state.context, result)
8886
print(f" ✓ {elapsed_ms:>6.2f} ms · {detail}")
8987

90-
def on_step_error(
91-
self, step_name: str, state: PipelineState, error: Exception
92-
) -> None:
88+
def on_step_error(self, step_name: str, state: PipelineState, error: Exception) -> None:
9389
print(f" ✗ {type(error).__name__}: {error}")
9490
# Returning None lets the original exception propagate.
9591

@@ -177,8 +173,7 @@ def add(a: int, b: int) -> int:
177173
banner("Section 3: Orchestration — insert_after + replace")
178174
strategy.insert_after("output_validation", AuditLogStep())
179175
custom = [n for n in strategy.step_names() if n not in CANONICAL_STEPS]
180-
print(f"after insert_after: 11 standard + {len(custom)} custom = "
181-
f"{len(strategy.steps)} steps")
176+
print(f"after insert_after: 11 standard + {len(custom)} custom = " f"{len(strategy.steps)} steps")
182177
for i, name in enumerate(strategy.step_names(), start=1):
183178
marker = " ← CUSTOM (inserted)" if name not in CANONICAL_STEPS else ""
184179
print(f" {i:>2}. {name}{marker}")
@@ -187,8 +182,10 @@ def add(a: int, b: int) -> int:
187182
client.call("math.add", {"a": 2, "b": 3})
188183

189184
strategy.replace("audit_log", QuietAuditLogStep())
190-
print(f"\nafter replace: {len(strategy.steps)} steps "
191-
f"(audit_log still at index {strategy.step_names().index('audit_log')})")
185+
print(
186+
f"\nafter replace: {len(strategy.steps)} steps "
187+
f"(audit_log still at index {strategy.step_names().index('audit_log')})"
188+
)
192189

193190
print("\ncalling with the quiet replacement:")
194191
client.call("math.add", {"a": 7, "b": 9})

‎src/apcore/context.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,7 @@ def _bind_executor(self, executor: Any) -> None:
133133
# Imported lazily to avoid a circular import (errors -> context).
134134
from apcore.errors import ContextBindingError
135135

136-
raise ContextBindingError(
137-
"Context already bound to a different Executor instance"
138-
)
136+
raise ContextBindingError("Context already bound to a different Executor instance")
139137
# else: same executor instance, noop.
140138

141139
def serialize(self) -> dict[str, Any]:

‎tests/conformance/test_v022_event_delivery.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
import os
67
import re
78
from pathlib import Path
89

@@ -11,11 +12,17 @@
1112
from apcore.events.emitter import ApCoreEvent, EventEmitter, _DLQ_EVENT_TYPE
1213
from apcore.events.retry import EventRetryConfig
1314

14-
_FIXTURE_PATH = Path("/Users/tercel/WorkSpace/aipartnerup/apcore/conformance/fixtures/event_delivery_semantics.json")
15+
16+
def _fixture_path() -> Path:
17+
env = os.environ.get("APCORE_SPEC_REPO")
18+
if env:
19+
return Path(env) / "conformance" / "fixtures" / "event_delivery_semantics.json"
20+
repo_root = Path(__file__).resolve().parent.parent.parent
21+
return repo_root.parent / "apcore" / "conformance" / "fixtures" / "event_delivery_semantics.json"
1522

1623

1724
def _load_fixture() -> dict:
18-
with _FIXTURE_PATH.open() as f:
25+
with _fixture_path().open() as f:
1926
return json.load(f)
2027

2128

‎tests/conformance/test_v022_registry_ordering.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
import os
67
import threading
78
import time
89
from pathlib import Path
@@ -14,11 +15,17 @@
1415
from apcore.events.emitter import ApCoreEvent, EventEmitter
1516
from apcore.registry import Registry
1617

17-
_FIXTURE_PATH = Path("/Users/tercel/WorkSpace/aipartnerup/apcore/conformance/fixtures/registry_load_ordering.json")
18+
19+
def _fixture_path() -> Path:
20+
env = os.environ.get("APCORE_SPEC_REPO")
21+
if env:
22+
return Path(env) / "conformance" / "fixtures" / "registry_load_ordering.json"
23+
repo_root = Path(__file__).resolve().parent.parent.parent
24+
return repo_root.parent / "apcore" / "conformance" / "fixtures" / "registry_load_ordering.json"
1825

1926

2027
def _load_fixture() -> dict:
21-
with _FIXTURE_PATH.open() as f:
28+
with _fixture_path().open() as f:
2229
return json.load(f)
2330

2431

‎tests/test_conformance.py‎

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,9 +1839,37 @@ def test_context_create_unified_signature(case: dict[str, Any]) -> None:
18391839
assert carried is not None and len(carried) == len(tp_in["tracestate"])
18401840
# And the signature does NOT expose a separate tracestate parameter.
18411841
params = _inspect.signature(Context.create).parameters
1842-
assert "tracestate" not in params, (
1843-
"tracestate MUST live inside TraceParent — no separate Context.create parameter"
1844-
)
1842+
assert (
1843+
"tracestate" not in params
1844+
), "tracestate MUST live inside TraceParent — no separate Context.create parameter"
1845+
1846+
elif case_id == "distributed_cancel_token_post_deserialize_null":
1847+
# Negative invariant: cancel_token MUST NOT serialize. A deserialized
1848+
# Context on a remote node has cancel_token == None (PROTOCOL_SPEC §5.7).
1849+
serialized: dict[str, Any] = {
1850+
"_context_version": 1,
1851+
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
1852+
"caller_id": "remote.caller",
1853+
"call_chain": ["remote.caller"],
1854+
"identity": None,
1855+
"data": {},
1856+
}
1857+
restored = Context.deserialize(serialized)
1858+
assert restored.cancel_token is None, "cancel_token MUST be null after deserialization"
1859+
1860+
elif case_id == "distributed_global_deadline_post_deserialize_null":
1861+
# Negative invariant: global_deadline MUST NOT serialize. A deserialized
1862+
# Context on a remote node has global_deadline == None (PROTOCOL_SPEC §5.7).
1863+
serialized = {
1864+
"_context_version": 1,
1865+
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
1866+
"caller_id": "remote.caller",
1867+
"call_chain": [],
1868+
"identity": None,
1869+
"data": {},
1870+
}
1871+
restored = Context.deserialize(serialized)
1872+
assert restored.global_deadline is None, "global_deadline MUST be null after deserialization"
18451873

18461874
else:
18471875
pytest.fail(f"Unknown context_create fixture case id: {case_id!r}")

0 commit comments

Comments
 (0)