[pather / RouteError] improve error reporting

This commit is contained in:
Jan Petykiewicz 2026-08-27 13:02:29 -07:00
commit 4d9aaf2fd9
4 changed files with 118 additions and 25 deletions

View file

@ -1,9 +1,10 @@
from collections.abc import Iterator
from types import FunctionType
import traceback
import pytest
from ..builder import Pather, PathTool
from ..builder import Pather, PathTool, RouteError
from ..error import BuildError, LibraryError
from ..library import (
INameView,
@ -288,9 +289,19 @@ def test_build_library_error_preserves_route_call_site() -> None:
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
assert 'Stack trace:' not in message
route_error = exc_info.value.__cause__
assert isinstance(route_error, RouteError)
assert route_error.__traceback__ is not None
route_frames = traceback.extract_tb(route_error.__traceback__)
assert sum(frame.filename == '/project/user_recipe.py' for frame in route_frames) == 1
assert not any('/builder/planner/' in frame.filename for frame in route_frames)
assert route_error.__cause__ is not None
assert any('/builder/planner/' in frame.filename for frame in route_error._call_stack)
native = ''.join(traceback.format_exception(exc_info.value))
assert native.count('/project/user_recipe.py') == 1
def test_build_library_depends_on_supports_hidden_dependencies_for_partial_validation() -> None:

View file

@ -2,6 +2,7 @@ from collections.abc import Sequence
from typing import Any, Literal, Never
from types import FunctionType
import inspect
import traceback
import pytest
import numpy
@ -39,6 +40,14 @@ def _external_failing_ccw(pather: Pather) -> None:
pather.ccw('A', 10, out_ptype='optical')
def _external_failing_trace_to(pather: Pather) -> None:
pather.trace_to('A', True, length=10, out_ptype='optical')
def _external_failing_portpather_ccw(pather: Pather) -> None:
pather.at('A').ccw(10, out_ptype='optical')
class FirstPortOnlyTraceTool(PlanningOnlyTool):
def __init__(self) -> None:
self.render_calls = 0
@ -291,11 +300,72 @@ def test_route_error_reports_external_pather_call_site() -> None:
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
assert 'Stack trace:' not in message
route_error = exc_info.value
native_frames = traceback.extract_tb(route_error.__traceback__)
assert native_frames
assert [frame.name for frame in native_frames if frame.filename.endswith('/builder/pather.py')] == [
'ccw',
'bend',
]
assert not any('/builder/planner/' in frame.filename for frame in native_frames)
native = ''.join(traceback.format_exception(route_error))
assert native.count('/project/user_layout.py') == 1
assert '\nStack trace:\n' not in native
assert route_error.__cause__ is not None
assert isinstance(route_error._call_stack, tuple)
assert sum(frame.filename == '/project/user_layout.py' for frame in route_error._call_stack) == 1
assert any(frame.filename.endswith('/builder/pather.py') for frame in route_error._call_stack)
assert any('/builder/planner/' in frame.filename for frame in route_error._call_stack)
def test_route_error_direct_trace_to_starts_native_traceback_at_caller() -> 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_trace_to.__code__.replace(co_filename='/project/direct_route.py'),
globals(),
)
with pytest.raises(RouteError) as exc_info:
external_call(p)
frames = traceback.extract_tb(exc_info.value.__traceback__)
assert sum(frame.filename == '/project/direct_route.py' for frame in frames) == 1
assert not any(frame.filename.endswith('/builder/pather.py') for frame in frames)
assert not any('/builder/planner/' in frame.filename for frame in frames)
def test_route_error_portpather_keeps_only_forwarding_frames() -> 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_portpather_ccw.__code__.replace(co_filename='/project/selected_route.py'),
globals(),
)
with pytest.raises(RouteError) as exc_info:
external_call(p)
frames = traceback.extract_tb(exc_info.value.__traceback__)
assert sum(frame.filename == '/project/selected_route.py' for frame in frames) == 1
assert [frame.name for frame in frames if frame.filename.endswith('/builder/pather.py')] == [
'ccw',
'bend',
'trace_to',
]
assert not any('/builder/planner/' in frame.filename for frame in frames)
def test_route_error_reports_failed_minimum_diagnosis(monkeypatch: pytest.MonkeyPatch) -> None: