twanghcmut's picture
download
raw
3.18 kB
"""Assert that a test's stub module has the same call signature as the real one.
Written after a real failure, and this is the failure: S9's wiring
(``pipeline._run_s9``, ``scripts/run_physics_identification.py``) was built
against an *assumed* ``fpgm.physics.scene`` / ``fpgm.physics.simulate`` API
while those modules were still being written. The wiring's tests stubbed both
modules -- so the stubs encoded the assumed API too, the whole suite passed
green, and the mismatch with the real API was invisible to every test. A stub
that is free to disagree with the thing it stands in for tests only that the
caller is self-consistent, which is the one property that was never in doubt.
So a stub must be checked against the real signature. The check is deliberately
about the *signature*, not the behaviour: replacing MuJoCo or a VLM with a fake
is the entire point of the stub, but taking different arguments than the real
module is never the point.
Usage::
from tests.stub_conformance import assert_stub_matches
module = types.ModuleType("fpgm.physics.scene")
module.build_sim_spec = build_sim_spec
assert_stub_matches("fpgm.physics.scene", module)
return module
Names the stub does not define are ignored (a stub legitimately covers only the
functions its caller reaches). Names the stub defines that the real module does
*not* are an error, since those are exactly the invented-API case above.
"""
from __future__ import annotations
import importlib
import inspect
from types import ModuleType
__all__ = ["assert_stub_matches"]
def assert_stub_matches(real_module_name: str, stub: ModuleType) -> None:
"""Raise ``AssertionError`` unless every public callable on ``stub`` matches.
Args:
real_module_name: Importable name of the module being stubbed.
stub: The stub module object, already populated.
Compared per callable: parameter names, kinds and order, and which
parameters have defaults. Annotations and default *values* are ignored --
a stub returning a canned object has no business reproducing the real
module's type hints, and requiring it to would make the check noise.
"""
real = importlib.import_module(real_module_name)
for name, stub_obj in vars(stub).items():
if name.startswith("_") or not callable(stub_obj):
continue
real_obj = getattr(real, name, None)
assert real_obj is not None, (
f"stub for {real_module_name} defines {name!r}, which does not exist on the "
f"real module -- the stub is standing in for an API that was never written"
)
got = _shape(inspect.signature(stub_obj))
want = _shape(inspect.signature(real_obj))
assert got == want, (
f"{real_module_name}.{name}: stub signature does not match the real one.\n"
f" real: {inspect.signature(real_obj)}\n"
f" stub: {inspect.signature(stub_obj)}"
)
def _shape(sig: inspect.Signature) -> list[tuple[str, str, bool]]:
return [
(p.name, str(p.kind), p.default is not inspect.Parameter.empty)
for p in sig.parameters.values()
if p.name != "self"
]

Xet Storage Details

Size:
3.18 kB
·
Xet hash:
256842c008184f5991983686d96d8bfef580de7877f6f7b6d7d9812fc74a1bac

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.