Bug 1856942: part 5) Factor async loading of a sheet out of `Loader::LoadSheet`....
[gecko.git] / testing / mozharness / scripts / does_it_crash.py
blob8e923ce582d1abb67e4d44a27f0b686ca8fd7f0c
1 #!/usr/bin/env python
2 # ***** BEGIN LICENSE BLOCK *****
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 # You can obtain one at http://mozilla.org/MPL/2.0/.
6 # ***** END LICENSE BLOCK *****
7 """ does_it_crash.py
9 Runs a thing to see if it crashes within a set period.
10 """
11 import os
12 import signal
13 import subprocess
14 import sys
16 import requests
18 sys.path.insert(1, os.path.dirname(sys.path[0]))
20 import mozinstall
21 import mozprocess
22 from mozharness.base.script import BaseScript
25 class DoesItCrash(BaseScript):
26 config_options = [
29 "--thing-url",
32 "action": "store",
33 "dest": "thing_url",
34 "type": str,
35 "help": "An URL that points to a package containing the thing to run",
40 "--thing-to-run",
43 "action": "store",
44 "dest": "thing_to_run",
45 "type": str,
46 "help": "The thing to run. If --thing-url is a package, this should be "
47 "its location relative to the root of the package.",
52 "--thing-arg",
55 "action": "append",
56 "dest": "thing_args",
57 "type": str,
58 "default": [],
59 "help": "Args for the thing. May be passed multiple times",
64 "--run-for",
67 "action": "store",
68 "dest": "run_for",
69 "default": 30,
70 "type": int,
71 "help": "How long to run the thing for, in seconds",
76 def __init__(self):
77 super(DoesItCrash, self).__init__(
78 all_actions=[
79 "download",
80 "run-thing",
82 default_actions=[
83 "download",
84 "run-thing",
86 config_options=self.config_options,
89 def downloadFile(self, url, file_name):
90 req = requests.get(url, stream=True, timeout=30)
91 file_path = os.path.join(os.getcwd(), file_name)
93 with open(file_path, "wb") as f:
94 for chunk in req.iter_content(chunk_size=1024):
95 if not chunk:
96 continue
97 f.write(chunk)
98 f.flush()
99 return file_path
101 def download(self):
102 url = self.config["thing_url"]
103 fn = "thing." + url.split(".")[-1]
104 self.downloadFile(url=url, file_name=fn)
105 if mozinstall.is_installer(fn):
106 self.install_dir = mozinstall.install(fn, "thing")
107 else:
108 self.install_dir = ""
110 def kill(self, proc):
111 is_win = os.name == "nt"
112 for retry in range(3):
113 if is_win:
114 proc.send_signal(signal.CTRL_BREAK_EVENT)
115 else:
116 os.killpg(proc.pid, signal.SIGKILL)
117 try:
118 proc.wait(5)
119 self.log("process terminated")
120 break
121 except subprocess.TimeoutExpired:
122 self.error("unable to terminate process!")
124 def run_thing(self):
125 self.timed_out = False
127 def timeout_handler(proc):
128 self.log(f"timeout detected: killing pid {proc.pid}")
129 self.timed_out = True
130 self.kill(proc)
132 self.output = []
134 def output_line_handler(proc, line):
135 self.output.append(line)
137 thing = os.path.abspath(
138 os.path.join(self.install_dir, self.config["thing_to_run"])
140 # thing_args is a LockedTuple, which mozprocess doesn't like
141 args = list(self.config["thing_args"])
142 timeout = self.config["run_for"]
144 self.log(f"Running {thing} with args {args}")
145 cmd = [thing]
146 cmd.extend(args)
147 mozprocess.run_and_wait(
148 cmd,
149 timeout=timeout,
150 timeout_handler=timeout_handler,
151 output_line_handler=output_line_handler,
153 if not self.timed_out:
154 # It crashed, oh no!
155 self.critical(
156 f"TEST-UNEXPECTED-FAIL: {thing} did not run for {timeout} seconds"
158 self.critical("Output was:")
159 for l in self.output:
160 self.critical(l)
161 self.fatal("fail")
162 else:
163 self.info(f"PASS: {thing} ran successfully for {timeout} seconds")
166 # __main__ {{{1
167 if __name__ == "__main__":
168 crashit = DoesItCrash()
169 crashit.run_and_exit()