import json from pathlib import Path import pytest from demo.app import run_verification from demo.examples import EXAMPLE_NAMES @pytest.mark.parametrize("compiler", ["gcc", "clang"]) def test_stream_download_repeatability_and_edited_candidate(tmp_path, compiler, example_rows): rows = example_rows for name, expected in (("identical", "agree"), ("rotate-vs-shift", "mismatch")): args = rows[EXAMPLE_NAMES.index(name)].copy() args[5], args[8] = 5, compiler updates = list(run_verification(*args, report_directory=tmp_path / "reports")) assert len(updates) == 6 assert len(updates[0][1]) == 1 and len(updates[-1][1]) == 5 first = Path(updates[-1][3]).read_bytes() assert json.loads(first)["verdict"] == expected repeated = list(run_verification(*args, report_directory=tmp_path / "reports")) assert Path(repeated[-1][3]).read_bytes() == first args[2] = "unsigned target(unsigned x) { return 0; }" updates = list(run_verification(*args, report_directory=tmp_path / "reports")) assert json.loads(Path(updates[-1][3]).read_text())["verdict"] == "mismatch" def test_stream_exposes_call_and_memory_details(tmp_path, example_rows): rows = example_rows args = rows[EXAMPLE_NAMES.index("transient-global")].copy() args[5] = 2 updates = list(run_verification(*args, report_directory=tmp_path / "reports")) assert len(updates) == 3 assert updates[-1][1][0][3].startswith("call[0].memory[") assert updates[-1][2]["original_calls"][0]["target"] == updates[-1][2]["candidate_calls"][0]["target"] report = json.loads(Path(updates[-1][3]).read_text()) assert report["scope"] == "calls_non_frame_memory_typed_return" assert report["verdict"] == "mismatch" difference = report["results"][0]["difference"] for side in ("original", "candidate"): writer = difference[f"{side}_writer"] assert writer["kind"] == "instruction" and writer["assembly"] ui_writer = updates[-1][2]["difference"][f"{side}_writer"] assert int(ui_writer["instruction"]) == writer["instruction"] assert ui_writer["assembly"] == writer["assembly"] @pytest.mark.parametrize("compiler", ["gcc", "clang"]) def test_return_mismatch_exposes_source_and_assembly_producers(tmp_path, example_rows, compiler): args = example_rows[EXAMPLE_NAMES.index("sign-extension")].copy() args[5], args[8] = 2, compiler updates = list(run_verification(*args, report_directory=tmp_path / "reports")) diff = updates[-1][2]["difference"] assert diff["location"] == "return" report = json.loads(Path(updates[-1][3]).read_text()) saved = next(result["difference"] for result in report["results"] if result["verdict"] == "mismatch") for side in ("original", "candidate"): writers = diff[f"{side}_writers"] assert len(writers) == 1 and writers[0]["kind"] == "instruction" writer = writers[0] assembly = updates[-1][4 if side == "original" else 5] assert f'{int(writer["instruction"]):#x}: {writer["assembly"]} ;' in assembly assert int(writer["instruction"]) == saved[f"{side}_writers"][0]["instruction"] assert diff["candidate_writers"][0]["source_file"] == "candidate.c" assert diff["candidate_writers"][0]["source_lines"] @pytest.mark.parametrize("compiler", ["gcc", "clang"]) @pytest.mark.parametrize("case", ["argument", "extra", "missing"]) def test_call_mismatch_exposes_source_sites_and_argument_producers(tmp_path, example_rows, compiler, case): from demo.presentation import json_detail, mismatch_summary args = example_rows[EXAMPLE_NAMES.index("transient-global")].copy() calls = {"argument": " foo(x + 1);", "extra": " foo(x);\n foo(x);", "missing": ""}[case] args[2] = ("extern volatile unsigned g;\nextern unsigned foo(unsigned);\n" "unsigned target(unsigned x) {\n g = 1;\n" + calls + "\n g = 3;\n return x;\n}\n") args[5], args[8] = 1, compiler updates = list(run_verification(*args, report_directory=tmp_path / "reports")) report = json.loads(Path(updates[-1][3]).read_text()) assert report["verdict"] == "mismatch" diff = report["results"][0]["difference"] assert diff["location"] == {"argument": "call[0].argument[0]", "extra": "call[1].presence", "missing": "call[0].presence"}[case] html = mismatch_summary(json_detail(updates[-1][2]), args[2]) for side in ("original", "candidate"): site = diff[f"{side}_callsite"] if site is None: assert f"data-{side}-instruction" not in html continue assembly = updates[-1][4 if side == "original" else 5] assert f'{site["instruction"]:#x}: {site["assembly"]} ;' in assembly assert hex(site["instruction"]) in html if side == "candidate": assert site["source_file"] == "candidate.c" assert site["source_lines"] == [5 if case == "argument" else 6] if case == "argument": writers = diff["candidate_writers"] assert writers and all(w["kind"] == "instruction" for w in writers) assert all(w["source_file"] == "candidate.c" and 5 in w["source_lines"] for w in writers) assert "Candidate argument writer" in html