Buckets:
| """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.