Spaces:
Running
Running
Download tests/test_presentation.py from ejschwartz/decompile-verify: direct link, hf CLI and curl.
- Browser
- Download file 14.6 kB
-
https://huggingface.co/spaces/ejschwartz/decompile-verify/resolve/main/tests/test_presentation.py
- Command line
-
hf download hf://spaces/ejschwartz/decompile-verify/tests/test_presentation.py
-
curl -L -o test_presentation.py https://huggingface.co/spaces/ejschwartz/decompile-verify/resolve/main/tests/test_presentation.py
14.6 kB
| def test_literal_diagnostics_escape_html(): | |
| from demo.presentation import literal_details | |
| html = literal_details({"literal_bindings": [{"literal": '"<script>"', "status": "unmatched", | |
| "address": None}]}) | |
| assert "<script>" in html and "<script>" not in html | |
| assert "No original match; kept local" in html | |
| assert literal_details({}) == "" | |
| def test_memory_mismatch_shows_writers_with_exact_addresses_and_escaped_assembly(): | |
| from demo.presentation import json_detail, mismatch_summary | |
| address = (1 << 63) + 123 | |
| detail = {"seed": 0, "difference": {"location": "return.memory[0x600000]", | |
| "original": 1, "candidate": 2, "width": 8, | |
| "original_writer": {"kind": "instruction", "instruction": address, | |
| "address": 0x600000, "size": 4, "assembly": "mov <test>"}, | |
| "candidate_writer": {"kind": "unwritten"}}} | |
| html = mismatch_summary(json_detail(detail)) | |
| assert "Original last writer" in html and "Candidate last writer" in html | |
| assert hex(address) in html and "store 4 bytes at 0x600000" in html | |
| assert "mov <test>" in html and "<test>" not in html | |
| assert "Not written during execution (initial memory)" in html | |
| def test_memory_mismatch_handles_missing_assembly_and_non_instruction_write(): | |
| from demo.presentation import mismatch_summary | |
| html = mismatch_summary({"seed": 0, "difference": { | |
| "original_writer": {"kind": "instruction", "instruction": 0x400000, "assembly": None}, | |
| "candidate_writer": {"kind": "non_instruction", "address": 0x600000, "size": 1}}}) | |
| assert "0x400000: Assembly unavailable" in html | |
| assert "Non-instruction write" in html and "store 1 byte at 0x600000" in html | |
| def test_older_mismatch_reports_without_writers_still_render(): | |
| from demo.presentation import mismatch_summary | |
| html = mismatch_summary({"seed": 1, "difference": {"location": "return", "original": 1, | |
| "candidate": 2, "width": 32}}) | |
| assert "Original value" in html and "0x1" in html | |
| assert "last writer" not in html | |
| assert "No mismatch to display" in mismatch_summary(None) | |
| def test_candidate_source_highlight_marker_preserves_snapshot_and_line_numbers(): | |
| from html import unescape | |
| from demo.presentation import json_detail, mismatch_summary | |
| source = 'void target(void) {\n g = a < b ? 1 : 2;\n}' | |
| detail = {"seed": 0, "difference": {"candidate_writer": { | |
| "kind": "instruction", "instruction": 0x400000, | |
| "source_file": "candidate.c", "source_lines": [2, 3]}}} | |
| html = mismatch_summary(json_detail(detail), source) | |
| assert "candidate.c:2, 3" in html and 'data-lines="2,3"' in html | |
| assert f'data-source="{source}"' in unescape(html) | |
| assert "a < b" in html | |
| assert 'data-candidate-instruction="0x400000"' in html | |
| assert "candidate-writer-location" not in mismatch_summary(detail) | |
| def test_assembly_highlight_marker_retains_exact_addresses_without_source_lines(): | |
| from demo.presentation import json_detail, mismatch_summary | |
| address = (1 << 63) + 123 | |
| detail = {"seed": 0, "difference": { | |
| "original_writer": {"kind": "instruction", "instruction": address}, | |
| "candidate_writer": {"kind": "unwritten"}}} | |
| html = mismatch_summary(json_detail(detail), "void target(void) {}") | |
| assert f'data-original-instruction="{address:#x}"' in html | |
| assert 'data-lines=""' in html | |
| assert "data-candidate-instruction" not in html | |
| def test_return_mismatch_highlights_all_producers_and_candidate_lines(): | |
| from demo.presentation import json_detail, mismatch_summary | |
| detail = {"seed": 0, "difference": {"location": "return", "width": 32, | |
| "original": 513, "candidate": 0, "original_writers": [ | |
| {"kind": "instruction", "instruction": 0x401000, "assembly": "mov eax, 1", "register": "rax"}, | |
| {"kind": "instruction", "instruction": 0x401005, "assembly": "mov ah, 2", "register": "ah"}], | |
| "candidate_writers": [ | |
| {"kind": "instruction", "instruction": 0x500000, "assembly": "mov eax, 0", | |
| "register": "rax", "source_lines": [2, 3]}, | |
| {"kind": "instruction", "instruction": 0x500005, "assembly": "mov ah, 0", | |
| "register": "ah", "source_lines": [3, 4]}]}} | |
| html = mismatch_summary(json_detail(detail), "unsigned target(void) { return 0; }") | |
| assert html.count("Original return writer") == html.count("Candidate return writer") == 2 | |
| assert "write to rax" in html and "write to ah" in html | |
| assert 'data-original-instruction="0x401000,0x401005"' in html | |
| assert 'data-candidate-instruction="0x500000,0x500005"' in html | |
| assert 'data-lines="2,3,4"' in html | |
| assert "store" not in html | |
| detail["difference"]["original_writers"] = [{"kind": "unwritten", "register": None}] | |
| assert "initial register value" in mismatch_summary(detail) | |
| def test_call_argument_highlights_sites_and_producers_with_candidate_line_union(): | |
| from demo.presentation import json_detail, mismatch_summary | |
| detail = {"seed": 0, "difference": {"location": "call[0].argument[0]", "width": 32, | |
| "original": 1, "candidate": 2, "xor": 3, | |
| "original_callsite": {"kind": "call", "instruction": 0x401005, "assembly": "call <foo>"}, | |
| "candidate_callsite": {"kind": "tail", "instruction": 0x500005, "source_lines": [4]}, | |
| "original_writers": [{"kind": "instruction", "instruction": 0x401000, "register": "rdi"}], | |
| "candidate_writers": [{"kind": "instruction", "instruction": 0x500000, "register": "rdi", | |
| "source_file": "candidate.c", "source_lines": [2, 4]}]}} | |
| html = mismatch_summary(json_detail(detail), "void root(void) { foo(2); }") | |
| assert "Original call site" in html and "Candidate call site" in html | |
| assert "Original argument writer" in html and "Candidate argument writer" in html | |
| assert "return writer" not in html and "call <foo>" in html | |
| assert 'data-original-instruction="0x401000,0x401005"' in html | |
| assert 'data-candidate-instruction="0x500000,0x500005"' in html | |
| assert 'data-lines="2,4"' in html | |
| def test_call_presence_highlights_only_present_side_and_renders_absence(): | |
| from demo.presentation import json_detail, mismatch_summary | |
| detail = {"seed": 0, "difference": {"location": "call[1].presence", "width": None, | |
| "original": False, "candidate": True, "original_callsite": None, | |
| "candidate_callsite": {"kind": "call", "instruction": 0x500005, "source_lines": [3]}, | |
| "original_writers": [], "candidate_writers": []}} | |
| html = mismatch_summary(json_detail(detail), "void root(void) { foo(); foo(); }") | |
| assert "No corresponding call" in html and "Call present" in html | |
| assert "Original call site" not in html and "Candidate call site" in html | |
| assert "data-original-instruction" not in html | |
| assert 'data-candidate-instruction="0x500005"' in html and 'data-lines="3"' in html | |
| detail["difference"].update(location="call[0].target", original="foo", candidate="bar") | |
| assert "foo" in mismatch_summary(json_detail(detail)) # Nullable width is not a numeric value. | |
| def test_editor_highlights_writer_and_clears_stale_or_edited_source(tmp_path): | |
| import json | |
| from pathlib import Path | |
| import shutil | |
| import subprocess | |
| import pytest | |
| node = shutil.which("node") | |
| if node is None: | |
| pytest.skip("editor behavior check requires Node.js") | |
| editor = tmp_path / "Code-test.js" | |
| editor.write_text('''module.exports = class EditorView { | |
| static findFromDOM(pane) { return pane.view || globalThis.view; } | |
| static scrollIntoView(position) { return { type: "scroll", position }; } | |
| };''') | |
| icon = tmp_path / "Code-icon.js" | |
| icon.write_text("exports.C = function CodeIcon() {};") | |
| script = Path(__file__).resolve().parents[1] / "demo/workbench.js" | |
| runner = tmp_path / "check.mjs" | |
| runner.write_text(''' | |
| import assert from "node:assert/strict"; | |
| import fs from "node:fs"; | |
| const source = "unsigned target(void) {\\n g = 2;\\n}"; | |
| let classWrites = 0; | |
| const elements = [1, 2, 3].map(number => ({ number, highlighted: false, | |
| classList: { contains: () => elements[number - 1].highlighted, | |
| toggle(name, value) { classWrites++; elements[number - 1].highlighted = value; } } })); | |
| const content = { getAttribute: () => "Candidate C" }; | |
| const pane = { querySelector: selector => selector === ".cm-content" ? content : null, | |
| querySelectorAll: () => elements }; | |
| const assemblyPane = text => { | |
| const rows = text.split("\\n").map((value, i) => ({ number: i + 1, highlighted: false, | |
| classList: { contains: () => rows[i].highlighted, | |
| toggle(name, highlight) { classWrites++; rows[i].highlighted = highlight; } } })); | |
| const scrolls = []; | |
| return { rows, scrolls, querySelector: () => null, querySelectorAll: () => rows, | |
| view: { state: { doc: { toString: () => text, lines: rows.length, | |
| lineAt: position => ({ number: position }), line: number => ({ from: number }) } }, | |
| posAtDOM: element => element.number, | |
| dispatch: transaction => scrolls.push(transaction.effects) } }; | |
| }; | |
| const original = assemblyPane("0x401000: nop\\n0x401004: mov [rdi], 1\\n0x4010040: ret"); | |
| const candidate = assemblyPane("0x500000: nop\\n0x5000040: ret\\n0x500004: mov [rdi], 2"); | |
| const panes = { "candidate-source": pane, "original-assembly": original, | |
| "candidate-assembly": candidate }; | |
| let marker = { dataset: { source, lines: "2", originalInstruction: "0x401004", | |
| candidateInstruction: "0x500004" } }; | |
| let stale = null; | |
| const scrolls = []; | |
| globalThis.view = { | |
| state: { doc: { toString: () => source, lines: 3, | |
| lineAt: position => ({ number: position }), line: number => ({ from: number }) } }, | |
| posAtDOM: element => element.number, | |
| dispatch: transaction => scrolls.push(transaction.effects) | |
| }; | |
| globalThis.document = { | |
| body: {}, getElementById: id => panes[id] || null, | |
| querySelectorAll: () => [], | |
| querySelector: selector => selector.includes("candidate-writer-location") ? marker : stale | |
| }; | |
| // Gradio's Code icon loads before the module containing the editor. | |
| const assets = [{ name: ICON_URL }]; | |
| globalThis.performance = { getEntriesByType: () => assets }; | |
| globalThis.MutationObserver = class { | |
| constructor(callback) { globalThis.refresh = callback; } | |
| observe(target, options) { | |
| assert.equal(options.attributes, true); | |
| assert.deepEqual(options.attributeFilter, ["class"]); | |
| } | |
| }; | |
| new Function(fs.readFileSync(SCRIPT_PATH, "utf8"))(); | |
| const tick = () => new Promise(resolve => setTimeout(resolve, 30)); | |
| await tick(); | |
| assert(elements.every(e => !e.highlighted)); | |
| assets.push({ name: EDITOR_URL }); | |
| refresh(); | |
| for (let i = 0; i < 30 && !elements[1].highlighted; i++) await tick(); | |
| assert.deepEqual(elements.map(e => e.highlighted), [false, true, false]); | |
| assert.deepEqual(original.rows.map(e => e.highlighted), [false, true, false]); | |
| assert.deepEqual(candidate.rows.map(e => e.highlighted), [false, false, true]); | |
| assert.deepEqual(scrolls, [{ type: "scroll", position: 2 }]); | |
| assert.deepEqual(original.scrolls, [{ type: "scroll", position: 2 }]); | |
| assert.deepEqual(candidate.scrolls, [{ type: "scroll", position: 3 }]); | |
| refresh(); await tick(); | |
| assert.equal(scrolls.length, 1); // DOM rerenders do not repeatedly scroll. | |
| assert.equal(original.scrolls.length, 1); | |
| assert.equal(candidate.scrolls.length, 1); | |
| // A CodeMirror layout pass can rewrite only the class attribute. | |
| original.rows[1].highlighted = false; | |
| refresh([{ type: "attributes", attributeName: "class", target: original.rows[1] }]); | |
| await tick(); | |
| assert(original.rows[1].highlighted); | |
| assert.equal(original.scrolls.length, 1); | |
| const previousWrites = classWrites; | |
| refresh(); await tick(); // Observing our own highlight must settle without a loop. | |
| assert.equal(classWrites, previousWrites); | |
| marker.dataset.originalInstruction = "0x401000,0x401004"; | |
| marker.dataset.candidateInstruction = "0x500000,0x500004"; | |
| refresh(); await tick(); | |
| assert.deepEqual(original.rows.map(e => e.highlighted), [true, true, false]); | |
| assert.deepEqual(candidate.rows.map(e => e.highlighted), [true, false, true]); | |
| view.state.doc.toString = () => source + " "; | |
| refresh(); await tick(); | |
| assert(elements.every(e => !e.highlighted)); | |
| assert([...original.rows, ...candidate.rows].every(e => !e.highlighted)); | |
| view.state.doc.toString = () => source; | |
| stale = {}; | |
| refresh(); await tick(); | |
| assert(elements.every(e => !e.highlighted)); | |
| stale = null; | |
| marker.dataset.lines = "2,3"; | |
| refresh(); await tick(); | |
| assert.deepEqual(elements.map(e => e.highlighted), [false, true, true]); | |
| marker.dataset.lines = ""; // Assembly remains useful without C debug lines. | |
| refresh(); await tick(); | |
| assert(elements.every(e => !e.highlighted)); | |
| assert(original.rows[1].highlighted && candidate.rows[2].highlighted); | |
| marker.dataset.candidateInstruction = "0x500005"; | |
| delete marker.dataset.originalInstruction; // No writer on the original side. | |
| refresh(); await tick(); | |
| assert([...original.rows, ...candidate.rows].every(e => !e.highlighted)); | |
| marker = null; | |
| refresh(); await tick(); | |
| assert(elements.every(e => !e.highlighted)); | |
| assert([...original.rows, ...candidate.rows].every(e => !e.highlighted)); | |
| '''.replace("EDITOR_URL", json.dumps(editor.as_uri())).replace("ICON_URL", json.dumps(icon.as_uri())) | |
| .replace("SCRIPT_PATH", json.dumps(str(script)))) | |
| result = subprocess.run([node, str(runner)], capture_output=True, text=True, timeout=10) | |
| assert result.returncode == 0, result.stderr | |