From 50629115da71cc0997b056a531cfcc5b652f0eb4 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Mon, 11 Sep 2023 15:05:53 +0200 Subject: [PATCH] Bug 1763188 - Add Snap support using TC builds --- mozregression/bisector.py | 63 ++++++++++++ mozregression/branches.py | 5 + mozregression/build_info.py | 5 +- mozregression/cli.py | 47 +++++++++ mozregression/fetch_configs.py | 86 ++++++++++++++++ mozregression/launchers.py | 173 ++++++++++++++++++++++++++++++++- mozregression/main.py | 15 ++- mozregression/test_runner.py | 34 ++++++- tests/unit/test_cli.py | 34 +++++++ tests/unit/test_main.py | 2 + tests/unit/test_test_runner.py | 43 ++++++-- 11 files changed, 487 insertions(+), 20 deletions(-) diff --git a/mozregression/bisector.py b/mozregression/bisector.py index 7b9df68c2..a51bc289f 100644 --- a/mozregression/bisector.py +++ b/mozregression/bisector.py @@ -351,6 +351,51 @@ def handle_merge(self): return result +""" +We are just using this to make it clear we have no merge to take care of +We are running an Integration because builds are triggered from cron jobs +on mozilla-central for all Snap package branches +""" + + +class SnapHandler(IntegrationHandler): + snap_repo = None + _build_infos = {} + snap_rev = {} + + def __init__(self, **kwargs): + super(IntegrationHandler, self).__init__(**kwargs) + + def record_build_infos(self, build_infos): + self._build_infos["_changeset"] = build_infos._changeset + self._build_infos["_repo_url"] = build_infos._repo_url + self.snap_repo = build_infos._repo_url + + def update_build_infos(self, build_infos): + # _build_infos here holds the mozilla-central ones, + # build_infos should be the snap-specific one + self.snap_rev[self._build_infos["_changeset"]] = build_infos.changeset + self.snap_repo = build_infos._repo_url + + def get_pushlog_url(self): + # somehow, self.found_repo from this class would not reflect + first_rev, last_rev = self.get_range() + if first_rev == last_rev: + return "%s/pushloghtml?changeset=%s" % (self.snap_repo, first_rev) + return "%s/pushloghtml?fromchange=%s&tochange=%s" % ( + self.snap_repo, + first_rev, + last_rev, + ) + + def revert_build_infos(self, build_infos): + build_infos._changeset = self._build_infos["_changeset"] + build_infos._repo_url = self._build_infos["_repo_url"] + + def handle_merge(self): + return None + + class IndexPromise(object): """ A promise to get a build index. @@ -503,11 +548,29 @@ def start_dl(r): return self.build_range.index(bdata) def evaluate(self, build_infos): + # we force getting data from app info for snap since we are building everything + # out of mozilla-central + if isinstance(self.handler, SnapHandler): + self.handler.record_build_infos(build_infos) + build_infos._force_update = True verdict = self.test_runner.evaluate(build_infos, allow_back=bool(self.history)) # old builds do not have metadata about the repo. But once # the build is installed, we may have it if self.handler.found_repo is None: self.handler.found_repo = build_infos.repo_url + if isinstance(self.handler, SnapHandler): + # Some Snap nightly builds are missing SourceRepository/SourceStamp + # So since we dont have a better source of information, let's get back + # what we had + if build_infos.repo_url is None: + LOG.warning( + "Bisection on a Snap package missing SourceRepository/SourceStamp," + " falling back to mozilla-central revs." + ) + build_infos._force_update = False + self.handler.revert_build_infos(build_infos) + else: + self.handler.update_build_infos(build_infos) return verdict def ensure_good_and_bad(self): diff --git a/mozregression/branches.py b/mozregression/branches.py index 976cb4e60..98e175976 100644 --- a/mozregression/branches.py +++ b/mozregression/branches.py @@ -78,6 +78,11 @@ def create_branches(): ): for alias in aliases: branches.set_alias(alias, name) + + # All of the snap packages builds are done on mozilla-central cron jobs + for name in ("snap-nightly", "snap-beta", "snap-stable", "snap-esr"): + branches.set_branch(name, "mozilla-central") + return branches diff --git a/mozregression/build_info.py b/mozregression/build_info.py index f4ac8e76e..e276da7d1 100644 --- a/mozregression/build_info.py +++ b/mozregression/build_info.py @@ -36,6 +36,7 @@ def __init__( task_id=None, ): self._fetch_config = fetch_config + self._force_update = False # will be set True by SnapHandler self._build_type = build_type self._build_url = build_url self._build_date = build_date @@ -136,9 +137,9 @@ def update_from_app_info(self, app_info): This helps to build the pushlog url for old nightlies. """ - if self._changeset is None: + if self._changeset is None or self._force_update is True: self._changeset = app_info.get("application_changeset") - if self._repo_url is None: + if self._repo_url is None or self._force_update is True: self._repo_url = app_info.get("application_repository") def persist_filename_for(self, data, regex=True): diff --git a/mozregression/cli.py b/mozregression/cli.py index 835ec623d..4dfa346a1 100644 --- a/mozregression/cli.py +++ b/mozregression/cli.py @@ -417,6 +417,21 @@ def create_parser(defaults): help="Helps to write the configuration file.", ) + parser.add_argument( + "--allow-sudo", + action="store_true", + help=( + "[Snap] Allow the use of sudo for Snap install/remove operations (otherwise," + " you will be prompted on each)" + ), + ) + + parser.add_argument( + "--disable-snap-connect", + action="store_true", + help="[Snap] Do not automatically perform 'snap connect'", + ) + parser.add_argument("--debug", "-d", action="store_true", help="Show the debug output.") return parser @@ -589,6 +604,11 @@ def validate(self): "x86", "x86_64", ], + "firefox-snap": [ + "aarch64", # will be morphed into arm64 + "arm", # will be morphed into armf + "x86_64", # will be morphed into amd64 + ], } user_defined_bits = options.bits is not None @@ -607,6 +627,10 @@ def validate(self): self.logger.warning( "--arch ignored for Firefox for macOS as it uses unified binary." ) + elif options.app in ("firefox-snap"): + self.logger.warning( + "--arch ignored for Firefox Snap package." + ) options.arch = None elif options.arch not in arch_options[options.app]: raise MozRegressionError( @@ -618,6 +642,29 @@ def validate(self): f"`--arch` required for specified app ({options.app}). " f"Please specify one of {', '.join(arch_options[options.app])}." ) + elif options.app == "firefox-snap" and options.allow_sudo is False: + self.logger.warning( + "Bisection on Snap package without --allow-sudo, you will be prompted for" + " credential on each 'snap' command." + ) + elif options.allow_sudo is True and options.app != "firefox-snap": + raise MozRegressionError( + f"--allow-sudo specified for app ({options.app}), but only valid for " + f"firefox-snap. Please verify your config." + ) + elif options.disable_snap_connect is True and options.app != "firefox-snap": + raise MozRegressionError( + f"--disable-snap-conncet specified for app ({options.app}), but only valid for " + f"firefox-snap. Please verify your config." + ) + + if options.app == "firefox-snap" and ( + options.repo is None or not options.repo.startswith("snap-") + ): + raise MozRegressionError( + f"--repo not specified for app ({options.app}), or not starting with snap-. " + f"Please use correct repo for bisecting Snap package." + ) fetch_config = create_config( options.app, mozinfo.os, options.bits, mozinfo.processor, options.arch diff --git a/mozregression/fetch_configs.py b/mozregression/fetch_configs.py index a24549075..e79e5b022 100644 --- a/mozregression/fetch_configs.py +++ b/mozregression/fetch_configs.py @@ -812,3 +812,89 @@ def build_regex(self): part = "mac" psuffix = "-asan" if "asan" in self.build_type else "" return r"jsshell-%s%s\.zip$" % (part, psuffix) + + +TIMESTAMP_SNAP_UPSTREAM_BUILD = to_utc_timestamp(datetime.datetime(2023, 7, 26, 9, 39, 21)) +TIMESTAMP_SNAP_INDEX_RENAME = to_utc_timestamp(datetime.datetime(2023, 11, 17, 21, 46, 39)) +# This needs to be updated when we land cross-compilation on treeherder +TIMESTAMP_SNAP_CROSS_COMPILATION = to_utc_timestamp(datetime.datetime(3023, 11, 21, 15, 15, 00)) + + +class FirefoxSnapNightlyConfigMixin(NightlyConfigMixin): + def _get_nightly_repo(self, date): + return "mozilla-central" + + +class FirefoxSnapIntegrationConfigMixin(IntegrationConfigMixin): + def _idx_key(self, date): + branch_name = self.integration_branch.split("-")[-1] + valid_branches = ("nightly", "beta", "stable", "esr") + if branch_name not in valid_branches: + raise errors.MozRegressionError( + f"No such branch available ({branch_name}), valid are ','.join(valid_branches)" + " (prefix with snap- for --repo)" + ) + + if date < TIMESTAMP_SNAP_UPSTREAM_BUILD: + raise errors.MozRegressionError("No build before this date") + elif date >= TIMESTAMP_SNAP_UPSTREAM_BUILD and date < TIMESTAMP_SNAP_INDEX_RENAME: + index_base = "" + elif date >= TIMESTAMP_SNAP_INDEX_RENAME: + index_base = "{}-".format(self.arch) + + if self.arch != "amd64" and date < TIMESTAMP_SNAP_CROSS_COMPILATION: + raise errors.MozRegressionError(f"No support for build other than amd64 ({self.arch}) provided") + + return "{}{}".format(index_base, branch_name) + + def tk_routes(self, push): + for build_type in self.build_types: + name = "gecko.v2.mozilla-central.revision.{}.firefox.{}{}".format( + push.changeset, + self._idx_key(push.timestamp), + "-{}".format(build_type) + if build_type != "opt" and build_type != "shippable" + else "", + ) + yield name + self._inc_used_build() + return + + +class SnapCommonConfig(CommonConfig): + def should_use_archive(self): + # We only want to use TaskCluster builds + return False + + def build_regex(self): + return r"(firefox_.*)\.snap" + + +@REGISTRY.register("firefox-snap") +class FirefoxSnapConfig( + SnapCommonConfig, FirefoxSnapIntegrationConfigMixin, FirefoxSnapNightlyConfigMixin +): + BUILD_TYPES = ("shippable", "opt", "debug") + BUILD_TYPE_FALLBACKS = { + "shippable": ("opt",), + "opt": ("shippable",), + } + + def __init__(self, os, bits, processor, arch): + super(FirefoxSnapConfig, self).__init__(os, bits, processor, arch) + self.set_build_type("shippable") + + def available_archs(self): + return [ + "aarch64", + "arm", + "x86_64", + ] + + def set_arch(self, arch): + mapping = { + "aarch64": "arm64", + "arm": "armhf", + "x86_64": "amd64", + } + self.arch = mapping.get(arch, "amd64") diff --git a/mozregression/launchers.py b/mozregression/launchers.py index bbfd91436..08d2e54c9 100644 --- a/mozregression/launchers.py +++ b/mozregression/launchers.py @@ -4,14 +4,17 @@ from __future__ import absolute_import, print_function +import hashlib import json import os import stat +import subprocess import sys import time import zipfile from abc import ABCMeta, abstractmethod from enum import Enum +from shutil import move from subprocess import STDOUT, CalledProcessError, call, check_output from threading import Thread @@ -22,7 +25,7 @@ from mozfile import remove from mozlog.structured import get_default_logger, get_proxy_logger from mozprofile import Profile, ThunderbirdProfile -from mozrunner import Runner +from mozrunner import GeckoRuntimeRunner, Runner from mozregression.class_registry import ClassRegistry from mozregression.errors import LauncherError, LauncherNotRunnable @@ -338,11 +341,16 @@ def get_app_info(self): REGISTRY = ClassRegistry("app_name") -def create_launcher(buildinfo): +def create_launcher(buildinfo, allow_sudo, disable_snap_connect): """ Create and returns an instance launcher for the given buildinfo. """ - return REGISTRY.get(buildinfo.app_name)(buildinfo.build_file, task_id=buildinfo.task_id) + return REGISTRY.get(buildinfo.app_name)( + buildinfo.build_file, + task_id=buildinfo.task_id, + allow_sudo=allow_sudo, + disable_snap_connect=disable_snap_connect, + ) class FirefoxRegressionProfile(Profile): @@ -616,3 +624,162 @@ def cleanup(self): # always remove tempdir if self.tempdir is not None: remove(self.tempdir) + + +# Should this be part of mozrunner ? +class SnapRunner(GeckoRuntimeRunner): + _allow_sudo = False + _snap_pkg = None + + def __init__(self, binary, cmdargs, allow_sudo=False, snap_pkg=None, **runner_args): + self._allow_sudo = allow_sudo + self._snap_pkg = snap_pkg + super().__init__(binary, cmdargs, **runner_args) + + @property + def command(self): + """ + Rewrite the command for performing the actual execution with + "snap run PKG", keeping everything else + """ + self._command = FirefoxSnapLauncher._get_snap_command( + self._allow_sudo, "run", [self._snap_pkg] + super().command[1:] + ) + return self._command + + +@REGISTRY.register("firefox-snap") +class FirefoxSnapLauncher(MozRunnerLauncher): + profile_class = FirefoxRegressionProfile + instanceKey = None + snap_pkg = None + binary = None + allow_sudo = False + disable_snap_connect = False + runner = None + + def __init__(self, dest, allow_sudo, disable_snap_connect, **kwargs): + self.allow_sudo = allow_sudo + self.disable_snap_connect = disable_snap_connect + super().__init__(dest) + + def get_snap_command(self, action, extra): + return FirefoxSnapLauncher._get_snap_command(self.allow_sudo, action, extra) + + def _get_snap_command(allow_sudo, action, extra): + if action not in ("connect", "install", "run", "refresh", "remove"): + raise LauncherError(f"Snap operation {action} unsupported") + + cmd = [] + if allow_sudo and action in ("connect", "install", "refresh", "remove"): + cmd += ["sudo"] + + cmd += ["snap", action] + cmd += extra + + return cmd + + def _install(self, dest): + # From https://snapcraft.io/docs/parallel-installs#heading--naming + # - The instance key needs to be manually appended to the snap name, + # and takes the following format: _ + # - The instance key must match the following regular expression: + # ^[a-z0-9]{1,10}$. + self.instanceKey = hashlib.sha1(os.path.basename(dest).encode("utf8")).hexdigest()[0:9] + self.snap_pkg = "firefox_{}".format(self.instanceKey) + self.binary = "/snap/{}/current/usr/lib/firefox/firefox".format(self.snap_pkg) + + subprocess.run( + self.get_snap_command( + "install", ["--name", self.snap_pkg, "--dangerous", "{}".format(dest)] + ), + check=True, + ) + self._fix_connections() + + self.binarydir = os.path.dirname(self.binary) + self.appdir = os.path.normpath(os.path.join(self.binarydir, "..", "..")) + + LOG.debug(f"snap package: {self.snap_pkg} {self.binary}") + + # On Snap updates are already disabled + + def _fix_connections(self): + if self.disable_snap_connect: + return + + existing = {} + for line in subprocess.getoutput("snap connections {}".format(self.snap_pkg)).splitlines()[ + 1: + ]: + interface, plug, slot, _ = line.split() + existing[plug] = slot + + for line in subprocess.getoutput("snap connections firefox").splitlines()[1:]: + interface, plug, slot, _ = line.split() + ex_plug = plug.replace("firefox:", "{}:".format(self.snap_pkg)) + ex_slot = slot.replace("firefox:", "{}:".format(self.snap_pkg)) + if existing[ex_plug] == "-": + if ex_plug != "-" and ex_slot != "-": + cmd = self.get_snap_command( + "connect", ["{}".format(ex_plug), "{}".format(ex_slot)] + ) + LOG.debug(f"snap connect: {cmd}") + subprocess.run(cmd, check=True) + + def _create_profile(self, profile=None, addons=(), preferences=None): + """ + Let's create a profile as usual, but rewrite its path to be in Snap's + dir because it looks like MozProfile class will consider a profile=xxx + to be a pre-existing one + """ + real_profile = super()._create_profile(profile, addons, preferences) + snap_profile_dir = os.path.abspath( + os.path.expanduser("~/snap/{}/common/.mozilla/firefox/".format(self.snap_pkg)) + ) + if not os.path.exists(snap_profile_dir): + os.makedirs(snap_profile_dir) + profile_dir_name = os.path.basename(real_profile.profile) + snap_profile = os.path.join(snap_profile_dir, profile_dir_name) + move(real_profile.profile, snap_profile_dir) + real_profile.profile = snap_profile + return real_profile + + def _start( + self, + profile=None, + addons=(), + cmdargs=(), + preferences=None, + adb_profile_dir=None, + allow_sudo=False, + disable_snap_connect=False, + ): + profile = self._create_profile(profile=profile, addons=addons, preferences=preferences) + + LOG.info("Launching %s [%s]" % (self.binary, self.allow_sudo)) + self.runner = SnapRunner( + binary=self.binary, + cmdargs=cmdargs, + profile=profile, + allow_sudo=self.allow_sudo, + snap_pkg=self.snap_pkg, + ) + self.runner.start() + + def _wait(self): + self.runner.wait() + + def _stop(self): + self.runner.stop() + # release the runner since it holds a profile reference + del self.runner + + def cleanup(self): + try: + Launcher.cleanup(self) + finally: + subprocess.run(self.get_snap_command("remove", [self.snap_pkg])) + + def get_app_info(self): + return safe_get_version(binary=self.binary) diff --git a/mozregression/main.py b/mozregression/main.py index b0d50a4dc..6c91ed674 100644 --- a/mozregression/main.py +++ b/mozregression/main.py @@ -17,7 +17,13 @@ from mozregression import __version__ from mozregression.approx_persist import ApproxPersistChooser -from mozregression.bisector import Bisection, Bisector, IntegrationHandler, NightlyHandler +from mozregression.bisector import ( + Bisection, + Bisector, + IntegrationHandler, + NightlyHandler, + SnapHandler, +) from mozregression.bugzilla import bug_url, find_bugids_in_push from mozregression.cli import cli from mozregression.config import DEFAULT_EXPAND, TC_CREDENTIALS_FNAME @@ -89,6 +95,8 @@ def test_runner(self): cmdargs=self.options.cmdargs, preferences=self.options.preferences, adb_profile_dir=self.options.adb_profile_dir, + allow_sudo=self.options.allow_sudo, + disable_snap_connect=self.options.disable_snap_connect, ) ) else: @@ -163,9 +171,8 @@ def _bisect_integration(self, good_rev, bad_rev, ensure_good_and_bad=False, expa "Getting %s builds between %s and %s" % (self.fetch_config.integration_branch, good_rev, bad_rev) ) - handler = IntegrationHandler( - find_fix=self.options.find_fix, ensure_good_and_bad=ensure_good_and_bad - ) + handler_class = IntegrationHandler if not self.options.app else SnapHandler + handler = handler_class(find_fix=self.options.find_fix, ensure_good_and_bad=ensure_good_and_bad) result = self._do_bisect(handler, good_rev, bad_rev, expand=expand) if result == Bisection.FINISHED: LOG.info("No more integration revisions, bisection finished.") diff --git a/mozregression/test_runner.py b/mozregression/test_runner.py index 5ca0d22d5..34335c128 100644 --- a/mozregression/test_runner.py +++ b/mozregression/test_runner.py @@ -20,7 +20,7 @@ LOG = get_proxy_logger("Test Runner") -def create_launcher(build_info): +def create_launcher(build_info, allow_sudo=False, disable_snap_connect=False): """ Create and returns a :class:`mozregression.launchers.Launcher`. """ @@ -36,7 +36,7 @@ def create_launcher(build_info): ) LOG.info("Running %s build %s" % (build_info.repo_name, desc)) - return mozlauncher(build_info) + return mozlauncher(build_info, allow_sudo, disable_snap_connect) class TestRunner(metaclass=ABCMeta): @@ -82,6 +82,19 @@ def index_to_try_after_skip(self, build_range): """ return build_range.mid_point() + def maybe_snap(self): + """ + Checking if the launcher migth contain Snap specific bits and return + them if it's the case, defaulting to False else. + """ + if hasattr(self, "launcher_kwargs") and "allow_sudo" in self.launcher_kwargs.keys(): + return ( + self.launcher_kwargs["allow_sudo"], + self.launcher_kwargs["disable_snap_connect"], + ) + else: + return (False, False) + class ManualTestRunner(TestRunner): """ @@ -117,7 +130,8 @@ def get_verdict(self, build_info, allow_back): return verdict[0] def evaluate(self, build_info, allow_back=False): - with create_launcher(build_info) as launcher: + (allow_sudo, disable_snap_connect) = self.maybe_snap() + with create_launcher(build_info, allow_sudo, disable_snap_connect) as launcher: launcher.start(**self.launcher_kwargs) build_info.update_from_app_info(launcher.get_app_info()) verdict = self.get_verdict(build_info, allow_back) @@ -131,7 +145,12 @@ def evaluate(self, build_info, allow_back=False): return verdict def run_once(self, build_info): - with create_launcher(build_info) as launcher: + (allow_sudo, disable_snap_connect) = self.maybe_snap() + with create_launcher( + build_info, + allow_sudo, + disable_snap_connect, + ) as launcher: launcher.start(**self.launcher_kwargs) build_info.update_from_app_info(launcher.get_app_info()) return launcher.wait() @@ -190,7 +209,12 @@ def __init__(self, command): self.command = command def evaluate(self, build_info, allow_back=False): - with create_launcher(build_info) as launcher: + (allow_sudo, disable_snap_connect) = self.maybe_snap() + with create_launcher( + build_info, + allow_sudo, + disable_snap_connect, + ) as launcher: build_info.update_from_app_info(launcher.get_app_info()) variables = {k: v for k, v in build_info.to_dict().items()} if hasattr(launcher, "binary"): diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index edf919f48..826954cb5 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -110,6 +110,40 @@ def test_warn_invalid_build_type_in_conf(self): warns, ) + def test_snap_no_repo(self): + with self.assertRaisesRegex( + errors.MozRegressionError, "Please use correct repo for bisecting Snap package." + ): + conf = cli.cli(["--app", "firefox-snap"]) + conf.validate() + + def test_snap_defaults(self): + conf = cli.cli(["--app", "firefox-snap", "--repo", "snap-beta"]) + warns = [] + conf.logger.warning = warns.append + conf.validate() + self.assertIn( + "Bisection on Snap package without --allow-sudo, you will be prompted" + " for credential on each 'snap' command.", + warns, + ) + assert conf.options.allow_sudo is False + assert conf.options.disable_snap_connect is False + + def test_snap_allow_sudo(self): + conf = cli.cli(["--app", "firefox-snap", "--repo", "snap-beta", "--allow-sudo"]) + warns = [] + conf.logger.warning = warns.append + conf.validate() + assert conf.options.allow_sudo is True + + def test_snap_disable_snap_connect(self): + conf = cli.cli(["--app", "firefox-snap", "--repo", "snap-beta", "--disable-snap-connect"]) + warns = [] + conf.logger.warning = warns.append + conf.validate() + assert conf.options.disable_snap_connect is True + def do_cli(*argv, conf_file=None): conf = cli.cli(argv, conf_file=conf_file) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 4df780f7b..250d405da 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -57,6 +57,8 @@ def test_app_get_manual_test_runner(create_app): cmdargs=["--allow-downgrade"], preferences=[], adb_profile_dir=None, + allow_sudo=False, + disable_snap_connect=False, ) diff --git a/tests/unit/test_test_runner.py b/tests/unit/test_test_runner.py index d2a2f793d..f24c79d40 100644 --- a/tests/unit/test_test_runner.py +++ b/tests/unit/test_test_runner.py @@ -36,7 +36,7 @@ def test_nightly_create_launcher(self, create_launcher): create_launcher.return_value = launcher info = mockinfo(build_type="nightly", app_name="firefox", build_file="/path/to") result_launcher = test_runner.create_launcher(info) - create_launcher.assert_called_with(info) + create_launcher.assert_called_with(info, False, False) self.assertEqual(result_launcher, launcher) @@ -53,7 +53,7 @@ def test_nightly_create_launcher_buildid(self, log, mozlauncher): repo_name="mozilla-central", ) result_launcher = test_runner.create_launcher(info) - mozlauncher.assert_called_with(info) + mozlauncher.assert_called_with(info, False, False) log.info.assert_called_with("Running mozilla-central build for buildid 20151106050403") self.assertEqual(result_launcher, launcher) @@ -65,7 +65,38 @@ def test_inbound_create_launcher(self, mozlauncher, download): mozlauncher.return_value = launcher info = mockinfo(build_type="inbound", app_name="firefox", build_file="/path/to") result_launcher = test_runner.create_launcher(info) - mozlauncher.assert_called_with(info) + mozlauncher.assert_called_with(info, False, False) + self.assertEqual(result_launcher, launcher) + + @patch("mozregression.test_runner.mozlauncher") + def test_snap_create_launcher(self, create_launcher): + launcher = Mock() + create_launcher.return_value = launcher + info = mockinfo(build_type="integration", app_name="firefox-snap", build_file="/path/to") + result_launcher = test_runner.create_launcher(info, True, False) + create_launcher.assert_called_with(info, True, False) + + self.assertEqual(result_launcher, launcher) + + @patch("mozregression.test_runner.mozlauncher") + @patch("mozregression.test_runner.LOG") + def test_snap_create_launcher_buildid(self, log, mozlauncher): + launcher = Mock() + mozlauncher.return_value = launcher + info = mockinfo( + build_type="integration", + app_name="firefox-snap", + build_file="/path/to", + build_date=datetime.datetime(2023, 11, 6, 5, 4, 3), + repo_name="mozilla-central", + short_changeset="1234", + ) + result_launcher = test_runner.create_launcher(info, True, False) + mozlauncher.assert_called_with(info, True, False) + log.info.assert_called_with( + "Running mozilla-central build built on 2023-11-06 05:04:03, revision 1234" + ) + self.assertEqual(result_launcher, launcher) @patch("mozregression.test_runner.input") @@ -98,7 +129,7 @@ def test_evaluate(self, get_verdict, create_launcher): build_infos = mockinfo() result = self.runner.evaluate(build_infos) - create_launcher.assert_called_with(build_infos) + create_launcher.assert_called_with(build_infos, False, False) launcher.get_app_info.assert_called_with() launcher.start.assert_called_with() get_verdict.assert_called_with(build_infos, False) @@ -124,7 +155,7 @@ def test_run_once(self, create_launcher): create_launcher.return_value = Launcher(launcher) build_infos = mockinfo() self.assertEqual(self.runner.run_once(build_infos), 0) - create_launcher.assert_called_with(build_infos) + create_launcher.assert_called_with(build_infos, False, False) launcher.get_app_info.assert_called_with() launcher.start.assert_called_with() launcher.wait.assert_called_with() @@ -136,7 +167,7 @@ def test_run_once_ctrlc(self, create_launcher): build_infos = mockinfo() with self.assertRaises(KeyboardInterrupt): self.runner.run_once(build_infos) - create_launcher.assert_called_with(build_infos) + create_launcher.assert_called_with(build_infos, False, False) launcher.get_app_info.assert_called_with() launcher.start.assert_called_with() launcher.wait.assert_called_with()