decompile-verify / tests /test_streaming.py
Edward J. Schwartz
Highlight call sites and argument producers for call mismatches
f469440
Raw History Blame Contribute Delete
5.31 kB
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