diff --git a/tools/crash_test.py b/tools/crash_test.py index 636ec8505b..a9e899610c 100755 --- a/tools/crash_test.py +++ b/tools/crash_test.py @@ -40,20 +40,23 @@ def Begin(self): def End(self): print(f"==={self.status}: {self.name}===") - def Verify(self, cond, reason=None): + def Verify(self, cond, reason=None, fail=True): if not cond: - if reason is not None: - print(f"FAILURE: {reason}") - self.status = "FAILED" - if GIVE_UP: - raise Exception("Giving up on first failure") + if fail: + if reason is not None: + print(f"FAILURE: {reason}") + self.status = "FAILED" + if GIVE_UP: + raise Exception("Giving up on first failure") + else: + print(f"WARNING: {reason}") def Go(self): self.Begin() try: self.Do() except Exception as e: - traceback.print_exception(e) + traceback.print_exc() self.status = "FAILED" self.End() return self.status == "PASSED" @@ -73,12 +76,13 @@ def __init__(self, module, engine, tprefix, fault): def Do(self): vmtype = "0" if SYMBOL_ZIPS else "1" - print("Running daemon...") + print("Running Daemon...") p = subprocess.run([self.engine, "-noforward", # catch stupid Linux persistent sockets (if gamelogic test...) "-homepath", self.dir, "-set", "vm.sgame.type", vmtype, "-set", "vm.cgame.type", vmtype, + "-set", "vm.timeout", "30", # in case of emulator "-set", "sv_fps", "1000", "-set", "common.framerate.max", "0", "-set", "client.errorPopup", "0", @@ -95,16 +99,21 @@ def Do(self): stderr=subprocess.PIPE, check=bool(self.tprefix)) dumps = os.listdir(PathJoin(self.dir, "crashdump")) assert len(dumps) == 1, dumps + if BREAKPAD_DIR is None: + return dump = PathJoin(self.dir, "crashdump", dumps[0]) sw_out = PathJoin(TEMP_DIR, self.name + "_stackwalk.log") with open(sw_out, "a+") as sw_f: print(f"Extracting stack trace to '{sw_out}'...") - sw_f.truncate() - subprocess.run(Virtualize([PathJoin(BREAKPAD_DIR, "src/processor/minidump_stackwalk"), dump, SYMBOL_DIR]), check=True, stdout=sw_f, stderr=subprocess.STDOUT) + sw_f.truncate(0) + subprocess.run(Virtualize([PathJoin(BREAKPAD_DIR, "src/processor/minidump_stackwalk"), dump, SYMBOL_DIR]), + check=True, stdout=sw_f, stderr=sw_f) sw_f.seek(0) sw = sw_f.read() - TRACE_FUNC = "InjectFaultCmd::Run" - self.Verify(TRACE_FUNC in sw, "function names not found in trace (did you build with symbols?)") + # Check both function names and filenames. On Linux it seems like only one of them works at a time?? + self.Verify(srcnames := ("Command.cpp" in sw), "source file names not found in trace", fail=False) + self.Verify(funcnames := ("InjectFaultCmd::Run" in sw), "function names not found in trace", fail=False) + self.Verify(srcnames or funcnames, "neither file nor function names found in trace") def Virtualize(cmdline): bin, *args = cmdline @@ -146,10 +155,11 @@ def Do(self): else: tprefix = "" eng = module + assert os.path.isfile(PathJoin(GAME_DIR, "crash_server" + EXE)) engine = ModulePath(eng) assert os.path.isfile(engine), engine - if not SYMBOL_ZIPS: + if BREAKPAD_DIR is not None and not SYMBOL_ZIPS: target = ModulePath(module) assert os.path.isfile(target), target print(f"Symbolizing '{target}'...") @@ -173,6 +183,7 @@ def ArgParser(usage=None): " Otherwise, enter end-to-end mode: symbols are produced from the binaries and VM type defaults to 1 (NaCl from PWD). In this mode you will likely need to provide pak paths via --daemon-args.") ap.add_argument("--game-dir", type=str, default=".", help="Path to Daemon (+ gamelogic) binaries") ap.add_argument("--breakpad-dir", type=str, default=BREAKPAD_DIR, help=r"Path to Breakpad repo containing built dump_syms and stackwalk binaries. It may be a \\wsl.localhost\ path on Windows hosts in order to symbolize NaCl.") + ap.add_argument("--no-breakpad", action="store_true", help="Do not symbolize or stack-walk; only test for dump file existence") ap.add_argument("--give-up", action="store_true", help="Stop after first test failure") ap.add_argument("--nacl-arch", type=str, choices=["amd64", "i686", "armhf"], default="amd64") # TODO auto-detect? ap.add_argument("module", nargs="*", @@ -191,7 +202,7 @@ def ArgParser(usage=None): help="Extra arguments for Daemon (e.g. -pakpath)") pa = ap.parse_args(sys.argv[1:]) GAME_DIR = pa.game_dir -BREAKPAD_DIR = pa.breakpad_dir +BREAKPAD_DIR = None if pa.no_breakpad else pa.breakpad_dir GIVE_UP = pa.give_up DAEMON_USER_ARGS = pa.daemon_args NACL_ARCH = pa.nacl_arch @@ -207,9 +218,14 @@ def ArgParser(usage=None): if SYMBOL_ZIPS: print("Symbol zip(s) detected. Using release validation mode with pre-built symbols") - for z in SYMBOL_ZIPS: - with zipfile.ZipFile(PathJoin(GAME_DIR, z), 'r') as z: - z.extractall(SYMBOL_DIR) + if BREAKPAD_DIR: + for z in SYMBOL_ZIPS: + with zipfile.ZipFile(PathJoin(GAME_DIR, z), 'r') as z: + z.extractall(SYMBOL_DIR) + if sys.platform == "darwin": + assert os.path.isdir(os.path.join(GAME_DIR, "Unvanquished.app")) + DAEMON_USER_ARGS = ["-pakpath", os.path.join(GAME_DIR, "pkg")] + DAEMON_USER_ARGS + GAME_DIR = os.path.join(GAME_DIR, "Unvanquished.app/Contents/MacOS") else: print("No symbol zip detected. Using end-to-end Breakpad tooling test mode with dump_syms")