[RouteError] include callsite traceback in route errors

This commit is contained in:
Jan Petykiewicz 2026-08-27 11:22:37 -07:00
commit 1b84c87d9b
4 changed files with 68 additions and 3 deletions

View file

@ -1,8 +1,9 @@
from collections.abc import Iterator
from types import FunctionType
import pytest
from ..builder import Pather
from ..builder import Pather, PathTool
from ..error import BuildError, LibraryError
from ..library import (
INameView,
@ -28,6 +29,17 @@ def _owned_by(report: BuildReport, owner: str) -> set[str]:
}
def _external_failing_route_recipe(lib: ILibrary) -> Pattern:
pather = Pather(
lib,
ports={'A': Port((0, 0), rotation=0, ptype='wire')},
tools=PathTool(layer='M1', width=2, ptype='wire'),
render='deferred',
)
pather.ccw('A', 10, out_ptype='optical')
return pather.pattern
class _MetadataSource(ILibraryView):
def __init__(self, mapping: dict[str, Pattern], child_graph: dict[str, set[str]]) -> None:
self.mapping = mapping
@ -263,6 +275,24 @@ def test_build_library_validate_is_retryable_after_failure() -> None:
assert report.dependency_graph["parent"] == frozenset({"child"})
def test_build_library_error_preserves_route_call_site() -> None:
builder = LibraryBuilder()
external_recipe = FunctionType(
_external_failing_route_recipe.__code__.replace(co_filename='/project/user_recipe.py'),
globals(),
)
builder.cells.top = cell(external_recipe)(builder.library)
with pytest.raises(BuildError) as exc_info:
builder.validate()
message = str(exc_info.value)
assert 'Cause: Unable to plan trace_to route' in message
assert 'Stack trace:' in message
assert 'File "/project/user_recipe.py"' in message
assert 'in _external_failing_route_recipe' in message
def test_build_library_depends_on_supports_hidden_dependencies_for_partial_validation() -> None:
builder = LibraryBuilder()
builder["child"] = Pattern()

View file

@ -60,6 +60,10 @@ def test_route_failure_details_enforces_minimum_status_invariants() -> None:
)
def test_plain_build_error_does_not_include_stacktrace() -> None:
assert str(BuildError('plain builder failure')) == 'plain builder failure'
def test_builder_init() -> None:
lib = Library()
b = Pather(lib, name="mypat")

View file

@ -1,5 +1,6 @@
from collections.abc import Sequence
from typing import Any, Literal, Never
from types import FunctionType
import inspect
import pytest
@ -34,6 +35,10 @@ class PlanningOnlyTool(Tool):
return tree
def _external_failing_ccw(pather: Pather) -> None:
pather.ccw('A', 10, out_ptype='optical')
class FirstPortOnlyTraceTool(PlanningOnlyTool):
def __init__(self) -> None:
self.render_calls = 0
@ -270,6 +275,29 @@ def test_route_error_reports_no_route_at_any_length() -> None:
assert 'no legal route exists at any length' in str(exc_info.value)
def test_route_error_reports_external_pather_call_site() -> None:
p = Pather(
Library(),
ports={'A': Port((0, 0), rotation=0, ptype='wire')},
tools=PathTool(layer='M1', width=2, ptype='wire'),
render='deferred',
)
external_call = FunctionType(
_external_failing_ccw.__code__.replace(co_filename='/project/user_layout.py'),
globals(),
)
with pytest.raises(RouteError) as exc_info:
external_call(p)
message = str(exc_info.value)
assert 'Stack trace:' in message
assert 'File "/project/user_layout.py"' in message
assert 'in _external_failing_ccw' in message
assert '/masque/builder/pather.py' not in message
assert '/masque/builder/planner/' not in message
def test_route_error_reports_failed_minimum_diagnosis(monkeypatch: pytest.MonkeyPatch) -> None:
planner = RoutingPlanner()
context = RoutePortContext(