lankasailendra/BMCTESTMAIN
0
1"""Offline preflight: no generated bundle ships a case with an empty script.2 3 python scripts/preflight_no_empty_scripts.py # exits non-zero on any failure4 5Covers the gate itself (what counts as empty, what must survive, line-map and6case-list consistency after removal) and the wiring — that both stub-emitting7paths in the builder run the gate before they pack.8"""9 10import pathlib11import sys12 13_REPO = pathlib.Path(__file__).resolve().parent.parent14sys.path.insert(0, str(_REPO / "src"))15 16from script_gate import ( # noqa: E40217 enforce_no_empty_scripts,18 find_empty_scripts,19 gate_summary,20)21 22fails = 023 24 25def check(label, got, want=True):26 global fails27 ok = got == want28 fails += not ok29 print(f"[{'ok ' if ok else 'FAIL'}] {label}")30 if not ok:31 print(f" got {got!r}\n want {want!r}")32 33 34SAMPLE = '''import pytest35 36 37@pytest.mark.functional38@pytest.mark.regression39def test_tc_itsm_wo_001(smartit_page):40 """ITSM-090 / TC-ITSM-WO-001: Create a work order."""41 page = smartit_page42 page.get_by_role("button", name="Save").click()43 assert page.get_by_text("WO000").is_visible()44 45 46@pytest.mark.functional47def test_tc_itsm_wo_002(session):48 """TC-ITSM-WO-002: Create via REST.49 No Helixops script block yet — catalog case only.50 """51 pytest.skip('Helixops script not bundled for TC-ITSM-WO-002')52 53 54@pytest.mark.functional55def test_tc_itsm_wo_003(session):56 """TC-ITSM-WO-003: Broken generation."""57 raise NotImplementedError("Script generation failed: boom")58 59 60def test_tc_itsm_wo_004(session):61 """TC-ITSM-WO-004: Placeholder."""62 pass63 64 65def test_tc_itsm_wo_005(smartit_page):66 """TC-ITSM-WO-005: Conditional skip is a REAL test."""67 if not smartit_page:68 pytest.skip("no page")69 smartit_page.click("#save")70 71 72def test_tc_itsm_wo_006(smartit_page):73 """TC-ITSM-WO-006: One real statement is enough."""74 assert smartit_page.title()75'''76 77print("-- what counts as an empty script --")78found = {f["fn_name"]: f["reason"] for f in find_empty_scripts(SAMPLE)}79check(80 "exactly the four stubs are flagged",81 sorted(found),82 [83 "test_tc_itsm_wo_002",84 "test_tc_itsm_wo_003",85 "test_tc_itsm_wo_004",86 ],87)88check("bare pytest.skip is caught", "skip" in found.get("test_tc_itsm_wo_002", ""))89check(90 "NotImplementedError is caught",91 "NotImplementedError" in found.get("test_tc_itsm_wo_003", ""),92)93check("bare pass is caught", "pass" in found.get("test_tc_itsm_wo_004", ""))94check("a guarded skip is NOT an empty script", "test_tc_itsm_wo_005" not in found)95check("a one-statement real test is NOT empty", "test_tc_itsm_wo_006" not in found)96 97print("\n-- removal keeps the file importable and the case list in step --")98cases = [{"id": f"TC-ITSM-WO-00{i}", "title": f"case {i}"} for i in range(1, 7)]99line_map = {100 f"TC-ITSM-WO-00{i}": {101 "filename": "test_functional_generated.py",102 "fn_name": f"test_tc_itsm_wo_00{i}",103 "fn_line": 0,104 "display_id": f"TC-ITSM-WO-00{i}",105 "title": f"case {i}",106 }107 for i in range(1, 7)108}109out, kept, dropped = enforce_no_empty_scripts(SAMPLE, cases, line_map)110 111import ast as _ast # noqa: E402112 113_ast.parse(out)114check("gated source still parses", True)115check(116 "the three stub bodies are gone",117 sorted(f["fn_name"] for f in find_empty_scripts(out)),118 [],119)120check(121 "surviving test functions",122 sorted(123 n.name124 for n in _ast.parse(out).body125 if isinstance(n, _ast.FunctionDef) and n.name.startswith("test_")126 ),127 ["test_tc_itsm_wo_001", "test_tc_itsm_wo_005", "test_tc_itsm_wo_006"],128)129check(130 "the cases that claimed them are dropped too",131 sorted(c["id"] for c in kept),132 ["TC-ITSM-WO-001", "TC-ITSM-WO-005", "TC-ITSM-WO-006"],133)134check("cases and scripts are 1:1 after the gate", len(kept), 3)135check(136 "line map drops the same ids",137 sorted(line_map),138 ["TC-ITSM-WO-001", "TC-ITSM-WO-005", "TC-ITSM-WO-006"],139)140check(141 "line map fn_line re-points into the NEW source",142 all(143 out.splitlines()[e["fn_line"] - 1].startswith(f"def {e['fn_name']}")144 for e in line_map.values()145 ),146)147check("dropped entries carry a reason", all(d.get("reason") for d in dropped))148check(149 "summary names every dropped case",150 all(d["tc_id"] in gate_summary(dropped) for d in dropped),151)152 153print("\n-- the gate never makes things worse --")154clean = "import pytest\n\n\ndef test_a(session):\n assert 1\n"155o2, k2, d2 = enforce_no_empty_scripts(clean, [{"id": "X"}], {})156check("a clean bundle is untouched", (o2, len(k2), d2), (clean, 1, []))157broken = "def test_a(session):\n assert (1\n"158o3, k3, d3 = enforce_no_empty_scripts(broken, [{"id": "X"}], {})159check(160 "unparseable source is left alone, not silently emptied",161 (o3 == broken, d3),162 (True, []),163)164check("all-stub file does not produce a broken module", True)165allstub = (166 "import pytest\n\n\ndef test_a(session):\n" ' """t"""\n pytest.skip("none")\n'167)168o4, k4, d4 = enforce_no_empty_scripts(allstub, [{"id": "X"}], {})169_ast.parse(o4)170check("every-case-stubbed bundle stays importable", len(d4), 1)171 172print("\n-- the wiring: both stub-emitting paths gate before they pack --")173builder = (_REPO / "src" / "script_builder_itsm.py").read_text(174 encoding="utf-8", errors="ignore"175)176check("gate imported in the builder", builder.count("from script_gate import") >= 2)177check("gate called on both paths", builder.count("enforce_no_empty_scripts(") >= 2)178 179# The OOTB gate must sit between assembling file_content and packing the zip.180i_assemble = builder.index('file_content = imports + "".join(functions)')181i_gate = builder.index("enforce_no_empty_scripts(", i_assemble)182i_pack = builder.index("_pack_itsm_zip_bytes(", i_assemble)183check("OOTB: assemble -> gate -> pack", i_assemble < i_gate < i_pack)184 185# The LLM path's gate must sit AFTER the syntax-repair loop, which is itself a186# stub producer — gating before it would let repaired stubs through.187i_repair = builder.index("repaired syntax error in")188i_gate_b = builder.index("enforce_no_empty_scripts(", i_repair)189i_assign = builder.index("all_files[filename] = _file_src", i_repair)190check("LLM path: syntax repair -> gate -> assign", i_repair < i_gate_b < i_assign)191 192check(193 "build_meta reports what was gated on both paths",194 builder.count('"gated_empty_scripts"') >= 2,195)196check(197 "OOTB README no longer advertises skip stubs",198 "pytest.skip stubs)" not in builder,199)200 201print(f"\n{'ALL PASS' if not fails else str(fails) + ' FAILURE(S)'}")202sys.exit(1 if fails else 0)203 