tor-browser

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

runner.py (7283B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 """ Script that launches profiles creation.
      5 """
      6 import os
      7 import shutil
      8 import asyncio
      9 
     10 import mozversion
     11 
     12 from condprof.creator import ProfileCreator
     13 from condprof.desktop import DesktopEnv
     14 from condprof.android import AndroidEnv
     15 from condprof.changelog import Changelog
     16 from condprof.scenarii import scenarii
     17 from condprof.util import logger, get_current_platform, extract_from_dmg
     18 from condprof.customization import get_customizations, find_customization
     19 from condprof.client import read_changelog, ProfileNotFoundError
     20 
     21 
     22 class Runner:
     23    def __init__(
     24        self,
     25        profile,
     26        firefox,
     27        geckodriver,
     28        archive,
     29        device_name,
     30        strict,
     31        force_new,
     32        visible,
     33        skip_logs=False,
     34        remote_test_root="/sdcard/test_root/",
     35    ):
     36        self.force_new = force_new
     37        self.profile = profile
     38        self.geckodriver = geckodriver
     39        self.archive = archive
     40        self.device_name = device_name
     41        self.strict = strict
     42        self.visible = visible
     43        self.skip_logs = skip_logs
     44        self.remote_test_root = remote_test_root
     45        self.env = {}
     46        # unpacking a dmg
     47        # XXX do something similar if we get an apk (but later)
     48        # XXX we want to do
     49        #   adb install -r target.apk
     50        #   and get the installed app name
     51        if firefox is not None and firefox.endswith("dmg"):
     52            target = os.path.join(os.path.dirname(firefox), "firefox.app")
     53            extract_from_dmg(firefox, target)
     54            firefox = os.path.join(target, "Contents", "MacOS", "firefox")
     55        self.firefox = firefox
     56        self.android = self.firefox is not None and self.firefox.startswith(
     57            "org.mozilla"
     58        )
     59 
     60    def prepare(self, scenario, customization):
     61        self.scenario = scenario
     62        self.customization = customization
     63 
     64        # early checks to avoid extra work
     65        if self.customization != "all":
     66            if find_customization(self.customization) is None:
     67                raise IOError("Cannot find customization %r" % self.customization)
     68 
     69        if self.scenario != "all" and self.scenario not in scenarii:
     70            raise IOError("Cannot find scenario %r" % self.scenario)
     71 
     72        if not self.android and self.firefox is not None:
     73            logger.info("Verifying Desktop Firefox binary")
     74            # we want to verify we do have a firefox binary
     75            # XXX so lame
     76            if not os.path.exists(self.firefox):
     77                if "MOZ_FETCHES_DIR" in os.environ:
     78                    target = os.path.join(os.environ["MOZ_FETCHES_DIR"], self.firefox)
     79                    if os.path.exists(target):
     80                        self.firefox = target
     81 
     82            if not os.path.exists(self.firefox):
     83                raise IOError("Cannot find %s" % self.firefox)
     84 
     85            mozversion.get_version(self.firefox)
     86 
     87        logger.info(os.environ)
     88        if self.archive:
     89            self.archive = os.path.abspath(self.archive)
     90            logger.info("Archives directory is %s" % self.archive)
     91            if not os.path.exists(self.archive):
     92                os.makedirs(self.archive, exist_ok=True)
     93 
     94        if shutil.which(self.geckodriver) is None and not os.path.exists(
     95            self.geckodriver
     96        ):
     97            raise IOError("Cannot find %s" % self.geckodriver)
     98 
     99        if not self.skip_logs:
    100            try:
    101                if self.android:
    102                    plat = "%s-%s" % (
    103                        self.device_name,
    104                        self.firefox.split("org.mozilla.")[-1],
    105                    )
    106                else:
    107                    plat = get_current_platform()
    108                self.changelog = read_changelog(plat)
    109                logger.info("Got the changelog from TaskCluster")
    110            except ProfileNotFoundError:
    111                logger.info("changelog not found on TaskCluster, creating a local one.")
    112                self.changelog = Changelog(self.archive)
    113        else:
    114            self.changelog = []
    115 
    116    def _create_env(self):
    117        if self.android:
    118            klass = AndroidEnv
    119        else:
    120            klass = DesktopEnv
    121 
    122        return klass(
    123            self.profile, self.firefox, self.geckodriver, self.archive, self.device_name
    124        )
    125 
    126    def display_error(self, scenario, customization):
    127        logger.error("%s x %s failed." % (scenario, customization), exc_info=True)
    128        # TODO: this might avoid the exceptions that slip through in automation
    129        # if self.strict:
    130        #     raise
    131 
    132    async def one_run(self, scenario, customization):
    133        """Runs one single conditioned profile.
    134 
    135        Create an instance of the environment and run the ProfileCreator.
    136        """
    137        self.env = self._create_env()
    138        return await ProfileCreator(
    139            scenario,
    140            customization,
    141            self.archive,
    142            self.changelog,
    143            self.force_new,
    144            self.env,
    145            skip_logs=self.skip_logs,
    146            remote_test_root=self.remote_test_root,
    147        ).run(not self.visible)
    148 
    149    async def run_all(self):
    150        """Runs the conditioned profile builders"""
    151        if self.scenario != "all":
    152            selected_scenario = [self.scenario]
    153        else:
    154            selected_scenario = scenarii.keys()
    155 
    156        # this is the loop that generates all combinations of profile
    157        # for the current platform when "all" is selected
    158        res = []
    159        failures = 0
    160        for scenario in selected_scenario:
    161            if self.customization != "all":
    162                try:
    163                    res.append(await self.one_run(scenario, self.customization))
    164                except Exception:
    165                    failures += 1
    166                    self.display_error(scenario, self.customization)
    167            else:
    168                for customization in get_customizations():
    169                    logger.info("Customization %s" % customization)
    170                    try:
    171                        res.append(await self.one_run(scenario, customization))
    172                    except Exception:
    173                        failures += 1
    174                        self.display_error(scenario, customization)
    175 
    176        return failures, [one_res for one_res in res if one_res]
    177 
    178    def save(self):
    179        self.changelog.save(self.archive)
    180 
    181 
    182 def run(
    183    archive,
    184    firefox=None,
    185    scenario="all",
    186    profile=None,
    187    customization="all",
    188    visible=False,
    189    archives_dir="/tmp/archives",
    190    force_new=False,
    191    strict=True,
    192    geckodriver="geckodriver",
    193    device_name=None,
    194 ):
    195    runner = Runner(
    196        profile, firefox, geckodriver, archive, device_name, strict, force_new, visible
    197    )
    198 
    199    runner.prepare(scenario, customization)
    200    loop = asyncio.get_event_loop()
    201 
    202    try:
    203        failures, results = loop.run_until_complete(runner.run_all())
    204        logger.info("Saving changelog in %s" % archive)
    205        runner.save()
    206        if failures > 0:
    207            raise Exception("At least one scenario failed")
    208    except Exception as e:
    209        raise e
    210    finally:
    211        loop.close()