Source code

Revision control

Copy as Markdown

Other Tools

#!/usr/bin/env python
# ***** BEGIN LICENSE BLOCK *****
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
# ***** END LICENSE BLOCK *****
""" does_it_crash.py
Runs a thing to see if it crashes within a set period.
"""
import os
import signal
import subprocess
import sys
import requests
sys.path.insert(1, os.path.dirname(sys.path[0]))
import mozinstall
import mozprocess
from mozharness.base.script import BaseScript
class DoesItCrash(BaseScript):
config_options = [
[
[
"--thing-url",
],
{
"action": "store",
"dest": "thing_url",
"type": str,
"help": "An URL that points to a package containing the thing to run",
},
],
[
[
"--thing-to-run",
],
{
"action": "store",
"dest": "thing_to_run",
"type": str,
"help": "The thing to run. If --thing-url is a package, this should be "
"its location relative to the root of the package.",
},
],
[
[
"--thing-arg",
],
{
"action": "append",
"dest": "thing_args",
"type": str,
"default": [],
"help": "Args for the thing. May be passed multiple times",
},
],
[
[
"--run-for",
],
{
"action": "store",
"dest": "run_for",
"default": 30,
"type": int,
"help": "How long to run the thing for, in seconds",
},
],
]
def __init__(self):
super(DoesItCrash, self).__init__(
all_actions=[
"download",
"run-thing",
],
default_actions=[
"download",
"run-thing",
],
config_options=self.config_options,
)
def downloadFile(self, url, file_name):
req = requests.get(url, stream=True, timeout=30)
file_path = os.path.join(os.getcwd(), file_name)
with open(file_path, "wb") as f:
for chunk in req.iter_content(chunk_size=1024):
if not chunk:
continue
f.write(chunk)
f.flush()
return file_path
def download(self):
url = self.config["thing_url"]
fn = "thing." + url.split(".")[-1]
self.downloadFile(url=url, file_name=fn)
if mozinstall.is_installer(fn):
self.install_dir = mozinstall.install(fn, "thing")
else:
self.install_dir = ""
def kill(self, proc):
is_win = os.name == "nt"
for retry in range(3):
if is_win:
proc.send_signal(signal.CTRL_BREAK_EVENT)
else:
os.killpg(proc.pid, signal.SIGKILL)
try:
proc.wait(5)
self.log("process terminated")
break
except subprocess.TimeoutExpired:
self.error("unable to terminate process!")
def run_thing(self):
self.timed_out = False
def timeout_handler(proc):
self.log(f"timeout detected: killing pid {proc.pid}")
self.timed_out = True
self.kill(proc)
self.output = []
def output_line_handler(proc, line):
self.output.append(line)
thing = os.path.abspath(
os.path.join(self.install_dir, self.config["thing_to_run"])
)
# thing_args is a LockedTuple, which mozprocess doesn't like
args = list(self.config["thing_args"])
timeout = self.config["run_for"]
self.log(f"Running {thing} with args {args}")
cmd = [thing]
cmd.extend(args)
mozprocess.run_and_wait(
cmd,
timeout=timeout,
timeout_handler=timeout_handler,
output_line_handler=output_line_handler,
)
if not self.timed_out:
# It crashed, oh no!
self.critical(
f"TEST-UNEXPECTED-FAIL: {thing} did not run for {timeout} seconds"
)
self.critical("Output was:")
for l in self.output:
self.critical(l)
self.fatal("fail")
else:
self.info(f"PASS: {thing} ran successfully for {timeout} seconds")
# __main__ {{{1
if __name__ == "__main__":
crashit = DoesItCrash()
crashit.run_and_exit()