]> FriiDump Source - friidump.git/blob - tests/test_xbox_cancel_contract.py
Fix MSVC CMake SHA1 header resolution
[friidump.git] / tests / test_xbox_cancel_contract.py
1 #!/usr/bin/env python3
2 import argparse
3 import re
4 from pathlib import Path
5
6
7 def require(text: str, token: str, label: str) -> None:
8     if token not in text:
9         raise SystemExit(f"FAIL: {label}: missing {token!r}")
10
11
12 def main() -> int:
13     parser = argparse.ArgumentParser()
14     parser.add_argument("--source-root", required=True)
15     args = parser.parse_args()
16     root = Path(args.source_root)
17
18     bridge_h = (root / "libfriidump/xbox_ref_bridge.h").read_text()
19     bridge_c = (root / "libfriidump/xbox_ref_bridge.c").read_text()
20     dumper_c = (root / "libfriidump/dumper.c").read_text()
21     utils_c = (root / "libfriidump/xbox_ref/utils.c").read_text()
22     friidump_c = (root / "src/friidump.c").read_text()
23     msvc_rsp = (root / "msvc32_friidump.rsp").read_text()
24
25     # Candidate18 removed the authRecovery parameter from the copied Windows
26     # helper but left three callers with a stale eighth argument.  MSVC only
27     # warned (C4020), so enforce the exact seven-argument contract in source
28     # and make any future C4020 a production-build error.
29     call_matches = list(
30         re.finditer(r"DumpSectorRangeWithRetry\s*\((.*?)\)", utils_c, re.S)
31     )
32     if len(call_matches) < 2:
33         raise SystemExit("FAIL: DumpSectorRangeWithRetry declaration/calls not found")
34     for index, match in enumerate(call_matches[1:], start=1):
35         argument_count = match.group(1).count(",") + 1
36         if argument_count != 7:
37             raise SystemExit(
38                 f"FAIL: DumpSectorRangeWithRetry call {index} has "
39                 f"{argument_count} arguments; expected 7"
40             )
41     require(msvc_rsp, "/we4020", "MSVC excess-argument warning is fatal")
42
43     require(bridge_h, "typedef int (*xbox_ref_cancel_func)", "bridge callback type")
44     require(bridge_h, "int cancelled;", "result cancellation state")
45     require(bridge_h, "uint32_t completed_sectors;", "result partial progress")
46     require(bridge_c, "cancel, cancel_data, result", "bridge callback handoff")
47     require(dumper_c, "dumper_xbox_ref_cancel_requested", "dumper adapter")
48     if dumper_c.count("dumper_xbox_ref_cancel_requested, dmp") < 2:
49         raise SystemExit("FAIL: callback is not passed to both copied Xbox ISO and XISO paths")
50     if utils_c.count("XboxDumpCancelRequested()") < 5:
51         raise SystemExit("FAIL: copied Xbox long-running paths do not contain enough cancellation checks")
52     require(utils_c, "result->completed_sectors =", "partial sector finalization")
53     require(
54         utils_c,
55         "[HASH] Finalizing controlled partial Xbox output hashes",
56         "partial output hashing",
57     )
58     require(
59         utils_c,
60         "result->cancelled &&",
61         "partial hash cancellation guard",
62     )
63     require(
64         utils_c,
65         "CalculateFileHashes(filename",
66         "partial full-file hash calculation",
67     )
68     require(utils_c, "g_xbox_dump_cancel = NULL;", "callback cleanup")
69     require(friidump_c, "if (xbox_result.cancelled)", "native-report cancellation merge")
70     require(
71         friidump_c,
72         "g_validation_summary.expected_output_sectors",
73         "Xbox expected-output geometry summary",
74     )
75     require(
76         friidump_c,
77         "g_validation_summary.source_sectors",
78         "Xbox source geometry summary",
79     )
80     require(
81         friidump_c,
82         "g_validation_summary.region",
83         "Xbox region summary",
84     )
85     require(
86         friidump_c,
87         "xbox_result.title",
88         "Xbox title summary handoff",
89     )
90     require(
91         friidump_c,
92         "xbox_result.media_id",
93         "Xbox media-ID summary handoff",
94     )
95
96     print("Xbox copied-dumper cancellation, hashing, and summary contract: PASS")
97     return 0
98
99
100 if __name__ == "__main__":
101     raise SystemExit(main())