does_it_crash.py (5101B)
1 #!/usr/bin/env python 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 # You can obtain one at http://mozilla.org/MPL/2.0/. 5 """does_it_crash.py 6 7 Runs a thing to see if it crashes within a set period. 8 """ 9 10 import os 11 import signal 12 import subprocess 13 import sys 14 15 import requests 16 17 sys.path.insert(1, os.path.dirname(sys.path[0])) 18 19 import mozinstall 20 import mozprocess 21 from mozharness.base.script import BaseScript 22 23 24 class DoesItCrash(BaseScript): 25 config_options = [ 26 [ 27 [ 28 "--thing-url", 29 ], 30 { 31 "action": "store", 32 "dest": "thing_url", 33 "type": str, 34 "help": "An URL that points to a package containing the thing to run", 35 }, 36 ], 37 [ 38 [ 39 "--thing-to-run", 40 ], 41 { 42 "action": "store", 43 "dest": "thing_to_run", 44 "type": str, 45 "help": "The thing to run. If --thing-url is a package, this should be " 46 "its location relative to the root of the package.", 47 }, 48 ], 49 [ 50 [ 51 "--thing-arg", 52 ], 53 { 54 "action": "append", 55 "dest": "thing_args", 56 "type": str, 57 "default": [], 58 "help": "Args for the thing. May be passed multiple times", 59 }, 60 ], 61 [ 62 [ 63 "--run-for", 64 ], 65 { 66 "action": "store", 67 "dest": "run_for", 68 "default": 30, 69 "type": int, 70 "help": "How long to run the thing for, in seconds", 71 }, 72 ], 73 ] 74 75 def __init__(self): 76 super().__init__( 77 all_actions=[ 78 "download", 79 "run-thing", 80 ], 81 default_actions=[ 82 "download", 83 "run-thing", 84 ], 85 config_options=self.config_options, 86 ) 87 88 def downloadFile(self, url, file_name): 89 req = requests.get(url, stream=True, timeout=30) 90 file_path = os.path.join(os.getcwd(), file_name) 91 92 with open(file_path, "wb") as f: 93 for chunk in req.iter_content(chunk_size=1024): 94 if not chunk: 95 continue 96 f.write(chunk) 97 f.flush() 98 return file_path 99 100 def download(self): 101 url = self.config["thing_url"] 102 fn = "thing." + url.split(".")[-1] 103 self.downloadFile(url=url, file_name=fn) 104 if mozinstall.is_installer(fn): 105 self.install_dir = mozinstall.install(fn, "thing") 106 else: 107 self.install_dir = "" 108 109 def kill(self, proc): 110 is_win = os.name == "nt" 111 for retry in range(3): 112 if is_win: 113 proc.send_signal(signal.CTRL_BREAK_EVENT) 114 115 # Manually kill all processes we spawned, 116 # not sure why this is required, but without it we hang forever. 117 process_name = self.config["thing_to_run"].split("/")[-1] 118 subprocess.run( 119 ["taskkill", "/T", "/F", "/IM", process_name], check=True 120 ) 121 else: 122 os.killpg(proc.pid, signal.SIGKILL) 123 try: 124 proc.wait(5) 125 self.log("process terminated") 126 break 127 except subprocess.TimeoutExpired: 128 self.error("unable to terminate process!") 129 130 def run_thing(self): 131 self.timed_out = False 132 133 def timeout_handler(proc): 134 self.log(f"timeout detected: killing pid {proc.pid}") 135 self.timed_out = True 136 self.kill(proc) 137 138 self.output = [] 139 140 def output_line_handler(proc, line): 141 self.output.append(line) 142 143 thing = os.path.abspath( 144 os.path.join(self.install_dir, self.config["thing_to_run"]) 145 ) 146 # thing_args is a LockedTuple, which mozprocess doesn't like 147 args = list(self.config["thing_args"]) 148 timeout = self.config["run_for"] 149 150 self.log(f"Running {thing} with args {args}") 151 cmd = [thing] 152 cmd.extend(args) 153 mozprocess.run_and_wait( 154 cmd, 155 timeout=timeout, 156 timeout_handler=timeout_handler, 157 output_line_handler=output_line_handler, 158 ) 159 if not self.timed_out: 160 # It crashed, oh no! 161 self.critical( 162 f"TEST-UNEXPECTED-FAIL: {thing} did not run for {timeout} seconds" 163 ) 164 self.critical("Output was:") 165 for l in self.output: 166 self.critical(l) 167 self.fatal("fail") 168 else: 169 self.info(f"PASS: {thing} ran successfully for {timeout} seconds") 170 171 172 # __main__ {{{1 173 if __name__ == "__main__": 174 crashit = DoesItCrash() 175 crashit.run_and_exit()