Skip to content

test: pin expected exception contracts - #46

Merged
ananjan-nandi-9 merged 1 commit into
mainfrom
test/pin-exception-contracts
Aug 9, 2026
Merged

test: pin expected exception contracts#46
ananjan-nandi-9 merged 1 commit into
mainfrom
test/pin-exception-contracts

Conversation

@ananjan-nandi-9

Copy link
Copy Markdown
Collaborator

Summary

  • Replaces broad pytest.raises(Exception) assertions with the concrete exception contracts emitted by the code under test.
  • Keeps this change test-only: production behavior, package APIs, and architecture are unchanged.
  • Preserves the one intentionally polymorphic negative-match assertion outside this patch.

Motivation / Context

Broad exception assertions can pass when code fails for the wrong reason. These tests now pin the actual failure contracts observed in the synced test environment, making regressions easier to diagnose without changing runtime behavior.

Delta Breakdown

  1. Pin immutable-model failures. Frozen dataclass mutations now expect FrozenInstanceError; frozen Pydantic model mutations now expect ValidationError. Reviewers should verify each model family uses its native exception type.
  2. Pin lifecycle and process failures. Programmatic device execution expects the propagated TaskExecutionError, while a missing MCP executable expects FileNotFoundError. Reviewers should verify the tests assert at the correct abstraction boundary.
  3. Pin trace validation failures. Static fragment corpus rejection expects TraceValidationError across the parametrized corpus. Reviewers should verify this remains the public validator contract.

Entry Point

test_programmatic_device_execution.py → immutability/model tests → MCP lifecycle test → static fragment corpus test.

The executable entrypoints are the existing pytest tests; this PR introduces no runtime or API entrypoint.

Review Guide

The main review question is whether each narrowed type represents the intended boundary rather than an implementation detail. In particular, the device lifecycle deliberately asserts the wrapper TaskExecutionError, not the inner sandbox exception. The corpus assertion covers 18 parametrized invalid traces with the shared TraceValidationError contract.

Validation

  • Static checks: Ruff check and format check passed for all changed files; uv lock --check passed.
  • Type checks: Not run; this is a test-only assertion change with no source or annotation changes.
  • Tests: 159 tests passed across all affected test modules.
  • Targeted/manual validation: Exception types were verified against the running code in the synced environment.
  • Not run: The shared make baseline completed 27 tests with 5 skips, then hit its existing Linux/macOS mismatch because the test config selects the APFS-only clonefile backend.

Documentation / Changelog / Decisions

No documentation, changelog, or decision record is needed because this PR changes only test precision and has no user-visible behavior.

Risk / Rollback / Migration

Risk is low and limited to exposing tests whose exception contracts change in the future. Rollback is a normal revert of this commit. No migration or compatibility action is required.

Follow-ups

None.

…h types

Stack A / PR-A2. Each narrowed type was observed from the running code via
the synced test env, not guessed:
- Frozen dataclasses -> dataclasses.FrozenInstanceError: ImmutableScope +
  ContextBinding (test_immutability.py x5), Artifact (test_emit_artifact.py),
  RunRef (test_kernel_re_exports.py).
- Frozen pydantic models -> pydantic.ValidationError: KVStoreContext
  (test_kvstore.py), ValidationResult + ToolCallRejected (test_capability_hooks.py x2).
- test_programmatic_device_execution.py: the lifecycle wraps the device's
  SandboxExecutionError and re-raises TaskExecutionError (shepherd_core.errors),
  so the assertion now pins TaskExecutionError (the propagated type), not the
  mock's inner cause.
- test_mcp_stdio_lifecycle.py: spawning a missing binary -> FileNotFoundError.
- test_static_fragment_corpus.py: the core-0 trace validators raise
  TraceValidationError; verified across all 18 parametrized corpus cases.

Left intentionally broad (documented, not a miss): test_lane_c_run.py:84
asserts the raised error is NOT the removed LC-3 fence (a negative-match on the
message), so the type is polymorphic by design.

Tested-by: uv run ruff check (clean, 8 files); pytest on each affected file/test
(immutability 10, kvstore 54, capability_hooks 22, emit_artifact 13,
kernel_re_exports 8, device-cleanup 1, mcp invalid-command 1, corpus 18) — all pass.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying shepherd-agents-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: f3e398a
Status: ✅  Deploy successful!
Preview URL: https://4e97a22e.shepherd-agents-docs.pages.dev
Branch Preview URL: https://test-pin-exception-contracts.shepherd-agents-docs.pages.dev

View logs

@ananjan-nandi-9
ananjan-nandi-9 merged commit a45ca9d into main Aug 9, 2026
5 checks passed
@ananjan-nandi-9
ananjan-nandi-9 deleted the test/pin-exception-contracts branch August 9, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants