tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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()