From 5b0ca535c16bc3320b154fd4b3ae8092e97d5d0c Mon Sep 17 00:00:00 2001 From: rohit-jsfreaky Date: Sun, 11 Oct 2026 18:36:32 +0530 Subject: [PATCH] test(python): pin that forwarded import edges only gate fixture calls on a rebuild Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf --- graphify/cache.py | 2 +- graphify/cli.py | 2 +- graphify/watch.py | 2 +- tests/test_python_pytest_fixture_receivers.py | 39 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/graphify/cache.py b/graphify/cache.py index a62e9efde..ae9eeca13 100644 --- a/graphify/cache.py +++ b/graphify/cache.py @@ -37,7 +37,7 @@ except Exception: _EXTRACTOR_VERSION = "unknown" # Bump when AST cache-key semantics change independently of the package version. -_AST_CACHE_SCHEMA = 17 # Python pytest-fixture markers (`_py_fixture`, `_python_fixture_param`); prior (10): C# partial types carry `type_path`, the nested/generic partial merge key; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids; prior (12): Java inherited-supertype (`receiver_unbound`) and chained/new/cast receiver typing (`java_receiver_chain`, `_java_ret`); C/C++ quoted cross-directory include provenance (`include_kind`); JS external imports anchored to the package, not the path leaf; (11): Python property-accessor decorators / `Self`, Swift `Self`, Go generic type params, Python forward type-hint file node, PowerShell dot-source, C# verbatim-interpolated string recovery; (10): C# partial types carry `type_path`; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids; prior (13): Kotlin/Scala/C++/Swift generic & template type params no longer mint sourceless stubs; Objective-C NS_ENUM/NS_OPTIONS extraction and `_callable`/`_callable_class` markers; Go and PHP receiver-typed member calls park cross-repo (`go_type_table`/`php_type_table`, receiver fields); (12): Java inherited-supertype (`receiver_unbound`) and chained/new/cast receiver typing (`java_receiver_chain`, `_java_ret`); C/C++ quoted cross-directory include provenance (`include_kind`); JS external imports anchored to the package, not the path leaf; prior (11): Python property-accessor decorators / `Self`, Swift `Self`, Go generic type params, Python forward type-hint file node, PowerShell dot-source, C# verbatim-interpolated string recovery; (10): C# partial types carry `type_path`; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids; prior (16): Rust `use` edges carry the imported name as written (`_rust_use_name`, #4439); (15): Go generic receiver types normalized (`Box[T]` -> `Box`) so methods attach to the type node; prior (14): C# receiver resolution for nameless/`new X()`/`foreach` receivers (defer + receiver-chain markers) and `_`/case-distinct node ids (underscore-salted); prior (13): Kotlin/Scala/C++/Swift generic & template type params no longer mint sourceless stubs; Objective-C NS_ENUM/NS_OPTIONS extraction and `_callable`/`_callable_class` markers; Go and PHP receiver-typed member calls park cross-repo (`go_type_table`/`php_type_table`, receiver fields); prior (12): Java inherited-supertype (`receiver_unbound`) and chained/new/cast receiver typing (`java_receiver_chain`, `_java_ret`); C/C++ quoted cross-directory include provenance (`include_kind`); JS external imports anchored to the package, not the path leaf; prior (11): Python property-accessor decorators / `Self`, Swift `Self`, Go generic type params, Python forward type-hint file node, PowerShell dot-source, C# verbatim-interpolated string recovery; (10): C# partial types carry `type_path`; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids. +_AST_CACHE_SCHEMA = 17 # Python pytest-fixture markers (`_py_fixture`, `_python_fixture_param`); prior (16): Rust `use` edges carry the imported name as written (`_rust_use_name`, #4439); (15): Go generic receiver types normalized (`Box[T]` -> `Box`) so methods attach to the type node; prior (14): C# receiver resolution for nameless/`new X()`/`foreach` receivers (defer + receiver-chain markers) and `_`/case-distinct node ids (underscore-salted); prior (13): Kotlin/Scala/C++/Swift generic & template type params no longer mint sourceless stubs; Objective-C NS_ENUM/NS_OPTIONS extraction and `_callable`/`_callable_class` markers; Go and PHP receiver-typed member calls park cross-repo (`go_type_table`/`php_type_table`, receiver fields); prior (12): Java inherited-supertype (`receiver_unbound`) and chained/new/cast receiver typing (`java_receiver_chain`, `_java_ret`); C/C++ quoted cross-directory include provenance (`include_kind`); JS external imports anchored to the package, not the path leaf; prior (11): Python property-accessor decorators / `Self`, Swift `Self`, Go generic type params, Python forward type-hint file node, PowerShell dot-source, C# verbatim-interpolated string recovery; (10): C# partial types carry `type_path`; (9): Python property-read raw calls and `_py_property` nodes; (8): Rust `use`-group per-binding edges; (7): Python opaque-base/super(args) markers + nested-class enclosing ids. # Per-process memo: distributions() scans site-packages metadata, which is diff --git a/graphify/cli.py b/graphify/cli.py index f1a5de389..e82b93534 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -4300,7 +4300,7 @@ def dispatch_command(cmd: str) -> None: "_rust_impl_key", "_rust_declaration_count", "_ts_public_twin", "_py_property", "_java_ret", "_ts_fields", "_ts_static_fields", - "_ts_public_twin", "_py_property", "_py_fixture", + "_py_fixture", ): if _node.get(_marker): _ctx_node[_marker] = _node[_marker] diff --git a/graphify/watch.py b/graphify/watch.py index 5570c5459..5483e9d89 100644 --- a/graphify/watch.py +++ b/graphify/watch.py @@ -2037,7 +2037,7 @@ def _rebuild_code( "_rust_impl_key", "_rust_declaration_count", "_ts_public_twin", "_py_property", "_java_ret", "_ts_fields", "_ts_static_fields", - "_ts_public_twin", "_py_property", "_py_fixture", + "_py_fixture", ): if node.get(marker): ctx_node[marker] = node[marker] diff --git a/tests/test_python_pytest_fixture_receivers.py b/tests/test_python_pytest_fixture_receivers.py index f5c14815f..6955302a4 100644 --- a/tests/test_python_pytest_fixture_receivers.py +++ b/tests/test_python_pytest_fixture_receivers.py @@ -171,3 +171,42 @@ def test_schema_bump_retires_entries_cached_without_fixture_markers(tmp_path: Pa nodes = {n["id"]: n for n in r["nodes"]} assert any(e["relation"] == "calls" and nodes[e["source"]]["label"] == "test_run()" and nodes[e["target"]]["label"] == ".invoke()" for e in r["edges"]) + + +def test_rebuild_after_the_imported_class_moves_matches_a_full_build(tmp_path: Path): + # The unchanged conftest.py still imports CliRunner from app.testing, which + # no longer defines it: the forwarded import edge is evidence only, and with + # the class gone from that module both builds leave the fixture call unlinked. + # (Only the fixture arm's edges are compared: the unchanged conftest's own + # `CliRunner()` call is not re-resolved on a changed-files rebuild, the + # same on 0.9.85, #3570.) + from graphify.watch import _rebuild_code + + files = { + "app/__init__.py": "", + "app/testing.py": _RUNNER, + "tests/conftest.py": _CONFTEST, + "tests/test_cli.py": "def test_run(runner):\n runner.invoke(None)\n", + } + roots = [] + for name in ("inc", "full"): + root = tmp_path / name + for fname, body in files.items(): + (root / fname).parent.mkdir(parents=True, exist_ok=True) + (root / fname).write_text(body, encoding="utf-8") + roots.append(root) + inc, full = roots + assert _rebuild_code(inc, no_cluster=True, acquire_lock=False, force=True) + for root in roots: + (root / "app" / "testing.py").write_text("def helper():\n return 1\n", encoding="utf-8") + (root / "app" / "moved.py").write_text(_RUNNER, encoding="utf-8") + assert _rebuild_code(inc, changed_paths=[inc / "app" / "testing.py", inc / "app" / "moved.py"], + no_cluster=True, acquire_lock=False) + assert _rebuild_code(full, no_cluster=True, acquire_lock=False, force=True) + + def fixture_calls(root): + g = json.loads((root / "graphify-out" / "graph.json").read_text(encoding="utf-8")) + return {(e["source"], e["target"]) for e in g.get("links", g.get("edges", [])) + if e["relation"] == "calls" and e["source"].endswith("test_run")} + + assert fixture_calls(inc) == fixture_calls(full) == set()