Skip to content

Commit 207e6a6

Browse files
committed
test(schema): L1⊆L2 superset gate + conformance for max_level 2
1 parent fd63ce5 commit 207e6a6

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

test/conftest_v2.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ def assert_conformant(payload: dict, max_level: int) -> None:
4343
text = mod["source"].encode("utf-8")[lo:hi].decode("utf-8")
4444
assert text.lstrip().startswith(("def ", "async def ", "@")), f"{c['id']} span mismatch"
4545
for node in c.get("body", {}).values():
46-
if node.get("kind") == "call" and max_level >= 2:
47-
assert node.get("callee") is None or isinstance(node["callee"], str)
46+
if node.get("kind") == "call" and "callee" in node:
47+
assert isinstance(node["callee"], str), "resolved callee must be a string id"
48+
if max_level >= 2:
49+
for e in app.get("call_graph", []):
50+
assert isinstance(e["source"], str), f"call_graph edge source must be string: {e}"
51+
assert isinstance(e["target"], str), f"call_graph edge target must be string: {e}"
4852
for mod, c in _iter_callables(app):
4953
node_ids = set(c.get("body", {}).keys())
5054
for lst in ("cfg", "cdg", "ddg", "summary"):

test/test_v2_superset.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
import subprocess
3+
import sys
4+
from pathlib import Path
5+
6+
from conftest_v2 import assert_conformant
7+
8+
9+
def _run(proj, level):
10+
out = subprocess.run(
11+
[sys.executable, "-m", "codeanalyzer", "-i", str(proj), "-a", str(level), "--no-venv"],
12+
capture_output=True, text=True, check=True,
13+
).stdout
14+
return json.loads(out)
15+
16+
17+
def _callables(app):
18+
def wc(c):
19+
yield c
20+
for ic in (c.get("inner_callables") or {}).values():
21+
yield from wc(ic)
22+
for cl in (c.get("inner_classes") or {}).values():
23+
yield from wcl(cl)
24+
25+
def wcl(cl):
26+
for m in (cl.get("methods") or {}).values():
27+
yield from wc(m)
28+
for ic in (cl.get("inner_classes") or {}).values():
29+
yield from wcl(ic)
30+
31+
for mod in app["symbol_table"].values():
32+
for fn in (mod.get("functions") or {}).values():
33+
yield from wc(fn)
34+
for cl in (mod.get("classes") or {}).values():
35+
yield from wcl(cl)
36+
37+
38+
def test_l1_subset_of_l2(tmp_path):
39+
proj = tmp_path / "proj"
40+
proj.mkdir()
41+
(proj / "m.py").write_text("def g():\n return 1\ndef f():\n return g()\n", encoding="utf-8")
42+
l1, l2 = _run(proj, 1), _run(proj, 2)
43+
assert_conformant(l1, max_level=1)
44+
assert_conformant(l2, max_level=2)
45+
a1, a2 = l1["application"], l2["application"]
46+
# every module + callable id present at L1 is present at L2
47+
ids1 = {c["id"] for c in _callables(a1)}
48+
ids2 = {c["id"] for c in _callables(a2)}
49+
assert ids1 <= ids2, "L2 dropped a callable present at L1"
50+
# every L1 body node key is present at L2 (callee may refine null→id)
51+
for c1 in _callables(a1):
52+
c2 = next(c for c in _callables(a2) if c["id"] == c1["id"])
53+
assert set(c1.get("body", {})) <= set(c2.get("body", {})), \
54+
f"L2 dropped a body node from {c1['id']}"

0 commit comments

Comments
 (0)