File size: 4,395 Bytes
71d239c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | """No-GPU correctness tests for the static-analysis engine.
These run with pydantic (+ tree-sitter if present) only — no model, no network.
They lock down the *facts* every story is grounded in: import edges, fan-in/out,
roles, entry points, and the safe-to-edit verdicts. If these pass, the narration
layer has a trustworthy backbone.
Run: python -m pytest tests/ -q (or) python tests/test_analyzer.py
"""
from __future__ import annotations
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import config # noqa: E402
from analyzer import analyze_project # noqa: E402
from analyzer import deps as deps_mod # noqa: E402
from ingest import from_folder, redact_secrets # noqa: E402
SAMPLE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "scripts", "sample_project")
def _model():
return analyze_project(from_folder(SAMPLE, name="doc-qa"))
def test_all_files_found():
m = _model()
paths = {f.path for f in m.files}
expected = {"app.py", "config.py", "embed.py", "ingest.py",
"retrieve.py", "generate.py"}
assert expected <= paths, f"missing files: {expected - paths}"
def test_edges_resolved():
m = _model()
edges = {f.path: set(f.depends_on) for f in m.files}
assert edges["embed.py"] == {"config.py"}
assert edges["ingest.py"] == {"config.py", "embed.py"}
assert edges["retrieve.py"] == {"config.py", "embed.py", "ingest.py"}
assert edges["generate.py"] == {"config.py", "retrieve.py"}
assert edges["app.py"] == {"generate.py", "ingest.py"}
assert edges["config.py"] == set() # leaf, depends on nothing internal
def test_fan_in():
m = _model()
fan_in = {f.path: f.fan_in for f in m.files}
assert fan_in["config.py"] == 4 # embed, ingest, retrieve, generate
assert fan_in["embed.py"] == 2 # ingest, retrieve
assert fan_in["ingest.py"] == 2 # retrieve, app
assert fan_in["retrieve.py"] == 1 # generate
assert fan_in["generate.py"] == 1 # app
assert fan_in["app.py"] == 0
def test_entry_point():
m = _model()
assert m.entry_points == ["app.py"]
assert m.by_path("app.py").is_entry is True
def test_roles():
m = _model()
role = {f.path: f.role for f in m.files}
assert role["app.py"] == "entry"
assert role["config.py"] == "config"
assert role["embed.py"] == "data" # 'embed' is a data hint
assert role["ingest.py"] == "data" # 'ingest' is a data hint
def test_safe_to_edit():
m = _model()
safety = {f.path: f.safety for f in m.files}
assert safety["app.py"] == config.DANGER # entry point
assert safety["config.py"] == config.DANGER # config + high fan-in
assert safety["embed.py"] == config.CAREFUL # fan-in 2
assert safety["retrieve.py"] == config.CAREFUL # fan-in 1
# every file carries a plain-English reason
assert all(f.safety_reason for f in m.files)
def test_symbols_extracted():
m = _model()
embed = m.by_path("embed.py")
names = {s.name for s in embed.symbols}
assert {"embed_text", "embed_many"} <= names
def test_dependencies_explained():
m = _model()
by_name = {d.name.lower(): d for d in m.deps}
assert "openai" in by_name
assert "chromadb" in by_name
assert by_name["chromadb"].plain # has a plain-English blurb
assert "memory" in by_name["chromadb"].plain.lower()
def test_secret_redaction():
clean, hits = redact_secrets('API_KEY = "sk-abc123def456ghi789jkl012"')
assert "sk-abc123" not in clean
assert hits # at least one rule fired
def test_deps_known_map_direct():
out = deps_mod.parse_requirements("gradio==4.44\nopenai\n# comment\n")
names = {d.name for d in out}
assert names == {"gradio", "openai"}
if __name__ == "__main__":
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
passed = 0
for fn in fns:
try:
fn()
except AssertionError as exc:
print(f"FAIL {fn.__name__}: {exc}")
except Exception as exc: # noqa: BLE001
print(f"ERROR {fn.__name__}: {type(exc).__name__}: {exc}")
else:
print(f"ok {fn.__name__}")
passed += 1
print(f"\n{passed}/{len(fns)} passed")
sys.exit(0 if passed == len(fns) else 1)
|