88import traceback
99import zipfile
1010
11- class CrashTest :
11+ if os .name == "nt" :
12+ EXE = '.exe'
13+ else :
14+ EXE = ""
15+
16+ def PathJoin (* paths ):
17+ p = os .path .join (* paths )
18+ if EXE :
19+ p = p .replace ("\\ " , "/" ) # for WSL
20+ return p
21+
22+ class Test :
1223 def __init__ (self , name ):
1324 self .name = name
1425
@@ -37,13 +48,13 @@ def Go(self):
3748 self .End ()
3849 return self .status == "PASSED"
3950
40- class BreakpadCrashTest (CrashTest ):
51+ class BreakpadCrashTest (Test ):
4152 def __init__ (self , module , engine , tprefix , fault ):
4253 super ().__init__ (module + "." + fault )
4354 self .engine = engine
4455 self .tprefix = tprefix
4556 self .fault = fault
46- self .dir = os . path . join (TEMP_DIR , self .name )
57+ self .dir = PathJoin (TEMP_DIR , self .name )
4758 try :
4859 shutil .rmtree (self .dir )
4960 except FileNotFoundError :
@@ -71,14 +82,14 @@ def Do(self):
7182 "+delay 20f echo CRASHTEST_END" ,
7283 "+delay 40f quit" ],
7384 stderr = subprocess .PIPE , check = bool (self .tprefix ))
74- dumps = os .listdir (os . path . join (self .dir , "crashdump" ))
85+ dumps = os .listdir (PathJoin (self .dir , "crashdump" ))
7586 assert len (dumps ) == 1 , dumps
76- dump = os . path . join (self .dir , "crashdump" , dumps [0 ])
77- sw_out = os . path . join (TEMP_DIR , self .name + "_stackwalk.log" )
87+ dump = PathJoin (self .dir , "crashdump" , dumps [0 ])
88+ sw_out = PathJoin (TEMP_DIR , self .name + "_stackwalk.log" )
7889 with open (sw_out , "a+" ) as sw_f :
7990 print (f"Extracting stack trace to '{ sw_out } '..." )
8091 sw_f .truncate ()
81- subprocess .run (Virtualize ([os . path . join (BREAKPAD_DIR , "src/processor/minidump_stackwalk" ), dump , SYMBOL_DIR ]), check = True , stdout = sw_f , stderr = subprocess .STDOUT )
92+ subprocess .run (Virtualize ([PathJoin (BREAKPAD_DIR , "src/processor/minidump_stackwalk" ), dump , SYMBOL_DIR ]), check = True , stdout = sw_f , stderr = subprocess .STDOUT )
8293 sw_f .seek (0 )
8394 sw = sw_f .read ()
8495 TRACE_FUNC = "InjectFaultCmd::Run"
@@ -106,9 +117,9 @@ def ModulePath(module):
106117 "sgame" : f"sgame-{ NACL_ARCH } .nexe" ,
107118 "cgame" : f"cgame-{ NACL_ARCH } .nexe" ,
108119 }[module ]
109- return os . path . join ( GAME_BUILD_DIR , base )
120+ return PathJoin ( GAME_DIR , base )
110121
111- class ModuleCrashTests (CrashTest ):
122+ class ModuleCrashTests (Test ):
112123 def __init__ (self , module , engine = None ):
113124 super ().__init__ (module )
114125 self .engine = engine
@@ -131,7 +142,7 @@ def Do(self):
131142 target = ModulePath (module )
132143 assert os .path .isfile (target ), target
133144 print (f"Symbolizing '{ target } '..." )
134- subprocess .check_call (Virtualize ([os . path . join (BREAKPAD_DIR , "symbolize.py" ),
145+ subprocess .check_call (Virtualize ([PathJoin (BREAKPAD_DIR , "symbolize.py" ),
135146 "--symbol-directory" , SYMBOL_DIR , target ]))
136147
137148 self .Verify (BreakpadCrashTest (module , engine , tprefix , "segfault" ).Go ())
@@ -143,52 +154,53 @@ def Do(self):
143154 self .Verify (BreakpadCrashTest (module , engine , tprefix , "throw" ).Go ())
144155
145156def ArgParser (usage = None ):
146- ap = argparse .ArgumentParser (usage = usage )
157+ ap = argparse .ArgumentParser (
158+ usage = usage ,
159+ description = "Verify that Breakpad toolchain can produce usable stack traces."
160+ " A Daemon build must be found in the current directory. Also Breakpad's tools must be built in its source tree."
161+ " If a symbols zip is found in the current directory, enter release validation mode: prebuilt symbols are used and VM type defaults to 0 (NaCl from paks)."
162+ " 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." )
163+ ap .add_argument ("--game-dir" , type = str , default = "." , help = "Path to Daemon (+ gamelogic) binaries" )
147164 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." )
148165 ap .add_argument ("--give-up" , action = "store_true" , help = "Stop after first test failure" )
149166 ap .add_argument ("--nacl-arch" , type = str , choices = ["amd64" , "i686" , "armhf" ], default = "amd64" ) # TODO auto-detect?
150167 ap .add_argument ("module" , nargs = "*" ,
151168 default = "server" , # bogus default needed due to buggy argparse
152169 choices = ["dummyapp" , "server" , "ttyclient" , "client" ,
153- "cgame" , "ttyclient:cgame" , "client:cgame" ,
154- "sgame" , "server:sgame" , "ttyclient:sgame" , "client:sgame" ])
170+ "cgame" , "ttyclient:cgame" , "client:cgame" ,
171+ "sgame" , "server:sgame" , "ttyclient:sgame" , "client:sgame" ])
155172 return ap
156173
157- BREAKPAD_DIR = os .path .abspath (os . path . join (
174+ BREAKPAD_DIR = os .path .abspath (PathJoin (
158175 os .path .dirname (os .path .realpath (__file__ )), "../libs/breakpad" ))
159- GAME_BUILD_DIR = '.' # WSL calls rely on relative paths
160- TEMP_DIR = os .path .join (GAME_BUILD_DIR , "crashtest-tmp" )
161- SYMBOL_DIR = os .path .join (TEMP_DIR , "symbols" )
162-
163- os .makedirs (TEMP_DIR , exist_ok = True )
164- os .makedirs (SYMBOL_DIR , exist_ok = True )
165176
166- if os .name == "nt" :
167- EXE = '.exe'
168- else :
169- EXE = ""
170-
171- ap = ArgParser (usage = ArgParser ().format_usage ().rstrip ().removeprefix ("usage: " )
172- + " [--daemon-args ARGS...]" )
177+ ap = ArgParser (
178+ usage = ArgParser ().format_usage ().rstrip ().removeprefix ("usage: " ) + " [--daemon-args ARGS...]" )
173179ap .add_argument ("--daemon-args" , nargs = argparse .REMAINDER , default = [],
174180 help = "Extra arguments for Daemon (e.g. -pakpath)" )
175181pa = ap .parse_args (sys .argv [1 :])
182+ GAME_DIR = pa .game_dir
176183BREAKPAD_DIR = pa .breakpad_dir
177184GIVE_UP = pa .give_up
178185DAEMON_USER_ARGS = pa .daemon_args
179186NACL_ARCH = pa .nacl_arch
180- SYMBOL_ZIPS = [p for p in os .listdir (GAME_BUILD_DIR ) if p .startswith ("symbols" ) and p .endswith (".zip" )]
187+ SYMBOL_ZIPS = [p for p in os .listdir (GAME_DIR ) if p .startswith ("symbols" ) and p .endswith (".zip" )]
181188modules = pa .module
182189if isinstance (modules , str ):
183190 modules = ["server" , "ttyclient" , "sgame" , "cgame" ]
184191
192+ TEMP_DIR = "crashtest-tmp" # WSL relies on this being relative
193+ SYMBOL_DIR = PathJoin (TEMP_DIR , "symbols" )
194+ os .makedirs (TEMP_DIR , exist_ok = True )
195+ os .makedirs (SYMBOL_DIR , exist_ok = True )
196+
185197if SYMBOL_ZIPS :
186198 print ("Symbol zip(s) detected. Using release validation mode with pre-built symbols" )
187199 for z in SYMBOL_ZIPS :
188- with zipfile .ZipFile (z , 'r' ) as z :
200+ with zipfile .ZipFile (PathJoin ( GAME_DIR , z ) , 'r' ) as z :
189201 z .extractall (SYMBOL_DIR )
190202else :
191- print ("No symbol zip detected. Using end2end Breakpad tooling test mode with dump_syms" )
203+ print ("No symbol zip detected. Using end-to-end Breakpad tooling test mode with dump_syms" )
192204
193205passed = True
194206for module in modules :
0 commit comments