
brizental pushed to branch base-browser-140.2.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 85b4d8d5 by Beatriz Rizental at 2025-09-08T19:22:50+02:00 BB 43564: Modify ./mach bootstrap for Base Browser - - - - - 02a18b4d by Beatriz Rizental at 2025-09-08T19:22:58+02:00 fixup! BB 43564: Modify ./mach bootstrap for Base Browser EXTRA: Stop asking to configure git during bootstrap. - - - - - 4937c1b6 by Beatriz Rizental at 2025-09-08T19:23:06+02:00 fixup! BB 43564: Modify ./mach bootstrap for Base Browser - - - - - 98a01dbd by Beatriz Rizental at 2025-09-08T19:23:32+02:00 fixup! BB 41803: Add some developer tools for working on tor-browser. - - - - - 10ce4ee0 by Beatriz Rizental at 2025-09-08T19:28:44+02:00 fixup! BB 43564: Modify ./mach bootstrap for Base Browser - - - - - e841a083 by Beatriz Rizental at 2025-09-08T19:28:49+02:00 fixup! BB 43564: Modify ./mach bootstrap for Base Browser - - - - - 25 changed files: - + build/moz.configure/basebrowser-resources.configure - build/moz.configure/bootstrap.configure - build/moz.configure/init.configure - moz.configure - python/mozboot/mozboot/bootstrap.py - python/mozbuild/mozbuild/action/tooltool.py - python/mozbuild/mozbuild/artifact_commands.py - python/mozbuild/mozbuild/backend/base.py - + python/mozbuild/mozbuild/tbbutils.py - python/mozbuild/mozbuild/test/python.toml - + python/mozbuild/mozbuild/test/test_tbbutils.py - − tools/base-browser/l10n/combine/tests/README - tools/base-browser/git-rebase-fixup-preprocessor → tools/base_browser/git-rebase-fixup-preprocessor - tools/base-browser/l10n/combine-translation-versions.py → tools/base_browser/l10n/combine-translation-versions.py - tools/base-browser/l10n/combine/__init__.py → tools/base_browser/l10n/combine/__init__.py - tools/base-browser/l10n/combine/combine.py → tools/base_browser/l10n/combine/combine.py - tools/base-browser/l10n/combine/tests/__init__.py → tools/base_browser/l10n/combine/tests/__init__.py - + tools/base_browser/l10n/combine/tests/python.toml - tools/base-browser/l10n/combine/tests/test_android.py → tools/base_browser/l10n/combine/tests/test_android.py - tools/base-browser/l10n/combine/tests/test_dtd.py → tools/base_browser/l10n/combine/tests/test_dtd.py - tools/base-browser/l10n/combine/tests/test_fluent.py → tools/base_browser/l10n/combine/tests/test_fluent.py - tools/base-browser/l10n/combine/tests/test_properties.py → tools/base_browser/l10n/combine/tests/test_properties.py - tools/base-browser/missing-css-variables.py → tools/base_browser/missing-css-variables.py - tools/base-browser/tb-dev → tools/base_browser/tb-dev - tools/moz.build Changes: ===================================== build/moz.configure/basebrowser-resources.configure ===================================== @@ -0,0 +1,88 @@ +# Helpers +# ------------------------------------------------- + + +@depends(build_project) +def is_desktop_build(build_project): + return build_project == "browser" + + +# Bootstrap resources +# ------------------------------------------------- + + +option( + "--with-noscript", + env="NOSCRIPT", + nargs=1, + default=None, + help="Path to noscript .xpi extension archive.", +) + + +@depends( + "--with-noscript", + mozbuild_state_path, + bootstrap_path( + "noscript", no_unpack=True, when=depends("--with-noscript")(lambda x: not x) + ), +) +@checking("for noscript") +@imports(_from="pathlib", _import="Path") +def noscript(value, mozbuild_state_path, _bootstrapped): + if value: + path = Path(value[0]) + if path.is_file() and path.suffix == ".xpi": + return value[0] + else: + die("--with-noscript must be an existing .xpi file") + + bootstrapped_location = Path(mozbuild_state_path) / "browser" + for file in bootstrapped_location.glob(f"*.xpi"): + if "noscript" in file.name: + return str(bootstrapped_location / file) + + # noscript is not required for building. + return None + + +set_config("NOSCRIPT", noscript) + + +option( + "--with-tor-browser-fonts", + env="TOR_BROWSER_FONTS", + nargs=1, + default=None, + help="Path to location of fonts directory.", +) + + +@depends( + "--with-tor-browser-fonts", + mozbuild_state_path, + bootstrap_path( + "fonts", + when=depends("--with-tor-browser-fonts")(lambda x: not x) & is_desktop_build, + ), +) +@checking("for tor-browser fonts directory") +@imports(_from="pathlib", _import="Path") +def tor_browser_fonts(value, mozbuild_state_path, _bootstrapped): + if value: + path = Path(value[0]) + # TODO: Do a more thorough check on the directory. + if path.is_dir(): + return value[0] + else: + die("--with-tor-browser-fonts must point to a real directory.") + + bootstrapped_location = Path(mozbuild_state_path) / "fonts" + if bootstrapped_location.is_dir(): + return str(bootstrapped_location) + + # tor browser fonts directory is not required for building. + return None + + +set_config("TOR_BROWSER_FONTS", tor_browser_fonts) ===================================== build/moz.configure/bootstrap.configure ===================================== @@ -4,6 +4,29 @@ # 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/. +option( + "--with-tor-browser-build-out", + env="TOR_BROWSER_BUILD_OUT", + nargs=1, + default="https://tb-build-06.torproject.org/~tb-builder/tor-browser-build/out", + help="URL pointing to a Tor Browser Build out folder, served over HTTP[S].", +) + + +@depends("--with-tor-browser-build-out") +def tor_browser_build_out(value): + if value: + return value[0] + + +option( + "--enable-tor-browser-build-only-bootstrap", + env="TBB_ONLY_BOOTSTRAP", + default=False, + help="Flag that disables bootstrapping any artifact from Mozilla's Taskcluster. Will only bootstrap artifacts from tor-browser-build.", +) + + option( env="MOZ_FETCHES_DIR", nargs=1, @@ -115,9 +138,10 @@ def bootstrap_toolchain_tasks(host): def bootstrap_path(path, **kwargs): when = kwargs.pop("when", None) allow_failure = kwargs.pop("allow_failure", None) + no_unpack = kwargs.pop("no_unpack", False) if kwargs: configure_error( - "bootstrap_path only takes `when` and `allow_failure` as a keyword argument" + "bootstrap_path only takes `when`, `allow_failure` and `no_unpack` as keyword arguments" ) @depends( @@ -129,11 +153,16 @@ def bootstrap_path(path, **kwargs): build_environment, dependable(path), dependable(allow_failure), + dependable(no_unpack), + tor_browser_build_out, + "--enable-tor-browser-build-only-bootstrap", + target, when=when, ) @imports("os") @imports("subprocess") @imports("sys") + @imports("mozbuild.tbbutils") @imports(_from="mozbuild.dirutils", _import="ensureParentDir") @imports(_from="importlib", _import="import_module") @imports(_from="shutil", _import="rmtree") @@ -148,6 +177,10 @@ def bootstrap_path(path, **kwargs): build_env, path, allow_failure, + no_unpack, + tor_browser_build_out, + tbb_only_bootstrap, + target, ): if not path: return @@ -158,6 +191,83 @@ def bootstrap_path(path, **kwargs): if path_parts[0] == "clang-tools": path_prefix = path_parts.pop(0) + # Small hack because noscript is inside the browser folder. + if path_parts[0] == "noscript": + path_prefix = "browser" + + def try_tbb_bootstrap(exists): + if not tor_browser_build_out: + return False + + # Tor browser build doesn't have artifacts for all targets supported + # by the Firefox build system. When this is empty it means we are + # building for a platform which tbb doesn't support. + if not target.tor_browser_build_alias: + return False + + artifact = mozbuild.tbbutils.get_artifact_name(path_parts[0], tasks.prefix) + if not artifact: + log.info("%s is not mapped to a tbb artifact", path_parts[0]) + return False + + artifact_path = mozbuild.tbbutils.get_artifact_path( + tor_browser_build_out, + artifact, + target, + prefix=path_prefix, + log=log.warning, + ) + if not artifact_path: + log.info("no path found in tbb/out for %s", artifact) + return False + + # We will use the name of the artifact as the index. + # + # It's usually unique to the artifact version, but each artifact follows + # a different naming convention, so we can't really get more specific here. + artifact_index = artifact_path.rsplit("/", 1)[-1] + index_file = os.path.join(toolchains_base_dir, "indices", artifact) + try: + with open(index_file) as fh: + index = fh.read().strip() + except Exception: + index = None + if index == artifact_index and exists: + log.debug("%s is up-to-date", artifact) + return True + + command = ["artifact", "toolchain", "--from-url", artifact_path] + + if no_unpack: + command.append("--no-unpack") + + # Note to rebasers: + # From here on, it's a slightly modified copy/paste + # from the end of the try_bootstrap function + log.info( + "%s bootstrapped toolchain from TBB in %s", + "Updating" if exists else "Installing", + os.path.join(toolchains_base_dir, path_prefix, artifact), + ) + os.makedirs(os.path.join(toolchains_base_dir, path_prefix), exist_ok=True) + proc = subprocess.run( + [ + sys.executable, + os.path.join(build_env.topsrcdir, "mach"), + "--log-no-times", + ] + + command, + cwd=os.path.join(toolchains_base_dir, path_prefix), + check=not allow_failure, + ) + if proc.returncode != 0 and allow_failure: + return False + ensureParentDir(index_file) + with open(index_file, "w") as fh: + fh.write(artifact_index) + + return True + def try_bootstrap(exists): if not tasks: return False @@ -280,9 +390,10 @@ def bootstrap_path(path, **kwargs): try: # With --enable-bootstrap=no-update, we don't `try_bootstrap`, except # when the toolchain can't be found. - if ( - "no-update" not in enable_bootstrap or not exists - ) and not try_bootstrap(exists): + if ("no-update" not in enable_bootstrap or not exists) and not ( + try_tbb_bootstrap(exists) + or (not tbb_only_bootstrap and try_bootstrap(exists)) + ): # If there aren't toolchain artifacts to use for this build, # don't return a path. return None ===================================== build/moz.configure/init.configure ===================================== @@ -590,6 +590,21 @@ def split_triplet(triplet, allow_wasi=False): else: toolchain = "%s-%s" % (cpu, os) + # In tor-browser-build we use slightly different terminology for + # the supported platforms. Let's prepare that OS string here. + # + # Not all possible platforms listed here are supported in tbb, + # so this value will be empty sometimes. + tor_browser_build_alias = None + if canonical_os == "Android" and canonical_kernel == "Linux": + tor_browser_build_alias = f"android" + elif canonical_os == "GNU" and canonical_kernel == "Linux": + tor_browser_build_alias = f"linux" + elif canonical_os == "OSX" and canonical_kernel == "Darwin": + tor_browser_build_alias = f"macos" + elif canonical_os == "WINNT" and canonical_kernel == "WINNT": + tor_browser_build_alias = f"windows" + return namespace( alias=triplet, cpu=CPU(canonical_cpu), @@ -604,6 +619,7 @@ def split_triplet(triplet, allow_wasi=False): toolchain=toolchain, vendor=vendor, sub_configure_alias=sub_configure_alias, + tor_browser_build_alias=tor_browser_build_alias, ) ===================================== moz.configure ===================================== @@ -229,6 +229,7 @@ check_prog("WGET", ("wget",), allow_missing=True) include("build/moz.configure/toolchain.configure", when="--enable-compile-environment") +include("build/moz.configure/basebrowser-resources.configure") include("build/moz.configure/pkg.configure") include("build/moz.configure/memory.configure", when="--enable-compile-environment") ===================================== python/mozboot/mozboot/bootstrap.py ===================================== @@ -52,21 +52,28 @@ Note on Artifact Mode: Artifact builds download prebuilt C++ components rather than building them locally. Artifact builds are faster! -Artifact builds are recommended for people working on Firefox or -Firefox for Android frontends, or the GeckoView Java API. They are unsuitable +Artifact builds are recommended for people working on Tor Browser or +Base Browser for Android frontends, or the GeckoView Java API. They are unsuitable for those working on C++ code. For more information see: https://firefox-source-docs.mozilla.org/contributing/build/artifact_builds.h.... -Please choose the version of Firefox you want to build (see note above): +# Note to Base Browser developers + +This is still highly experimental. Expect bugs! + +Please choose the version of Base Browser you want to build (see note above): %s Your choice: """ APPLICATIONS = OrderedDict( [ - ("Firefox for Desktop Artifact Mode", "browser_artifact_mode"), - ("Firefox for Desktop", "browser"), - ("GeckoView/Firefox for Android Artifact Mode", "mobile_android_artifact_mode"), - ("GeckoView/Firefox for Android", "mobile_android"), + ("Base Browser for Desktop Artifact Mode", "browser_artifact_mode"), + ("Base Browser for Desktop", "browser"), + ( + "GeckoView/Base Browser for Android Artifact Mode", + "mobile_android_artifact_mode", + ), + ("GeckoView/Base Browser for Android", "mobile_android"), ("SpiderMonkey JavaScript engine", "js"), ] ) @@ -360,6 +367,8 @@ class Bootstrapper: getattr(self.instance, "ensure_%s_packages" % application)() def check_code_submission(self, checkout_root: Path): + return + if self.instance.no_interactive or which("moz-phab"): return @@ -474,8 +483,7 @@ class Bootstrapper: configure_mercurial(hg, state_dir) # Offer to configure Git, if the current checkout or repo type is Git. - elif git and checkout_type == "git": - should_configure_git = False + elif False and git and checkout_type == "git": if not self.instance.no_interactive: should_configure_git = self.instance.prompt_yesno(prompt=CONFIGURE_GIT) else: ===================================== python/mozbuild/mozbuild/action/tooltool.py ===================================== @@ -1029,14 +1029,29 @@ def unpack_file(filename): """Untar `filename`, assuming it is uncompressed or compressed with bzip2, xz, gzip, zst, or unzip a zip file. The file is assumed to contain a single directory with a name matching the base of the given filename. - Xz support is handled by shelling out to 'tar'.""" + Xz support is handled by shelling out to 'tar'. + + tor-browser#41564 - For supporting tor-browser-build artifacts that contain + multiple directories, the archive is extracted into a directory with the + same name as the base of the filename. This modification is only applied to + tar archives, because that is all that was necessary. + """ if os.path.isfile(filename) and tarfile.is_tarfile(filename): tar_file, zip_ext = os.path.splitext(filename) base_file, tar_ext = os.path.splitext(tar_file) clean_path(base_file) log.info('untarring "%s"' % filename) with TarFile.open(filename) as tar: - safe_extract(tar) + top_level_directories = set() + for name in tar.getnames(): + dir = name.split("/", 1)[0] + top_level_directories.add(dir) + if len(top_level_directories) == 1: + safe_extract(tar) + else: + safe_extract( + tar, path=os.path.join(os.path.dirname(filename), base_file) + ) elif os.path.isfile(filename) and filename.endswith(".tar.zst"): import zstandard ===================================== python/mozbuild/mozbuild/artifact_commands.py ===================================== @@ -244,6 +244,12 @@ def artifact_clear_cache(command_context, tree=None, job=None, verbose=False): nargs="+", help="Download toolchain artifact from a given task.", ) +@CommandArgument( + "--from-url", + metavar="URL", + nargs="+", + help="Download toolchain artifact from an arbitrary address.", +) @CommandArgument( "--tooltool-manifest", metavar="MANIFEST", @@ -273,6 +279,7 @@ def artifact_toolchain( skip_cache=False, from_build=(), from_task=(), + from_url=[], tooltool_manifest=None, no_unpack=False, retry=0, @@ -504,6 +511,13 @@ def artifact_toolchain( record = ArtifactRecord(task_id, name) records[record.filename] = record + if from_url: + for file in from_url: + record = DownloadRecord( + file, file.rsplit("/", 1)[-1], None, None, None, True + ) + records[record.filename] = record + for record in records.values(): command_context.log( logging.INFO, ===================================== python/mozbuild/mozbuild/backend/base.py ===================================== @@ -2,11 +2,14 @@ # 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/. +import errno import itertools +import logging import os import time from abc import ABCMeta, abstractmethod from contextlib import contextmanager +from pathlib import Path import mozpack.path as mozpath from mach.mixin.logging import LoggingMixin @@ -239,6 +242,72 @@ class BuildBackend(LoggingMixin): with open(mozpath.join(dir, ".purgecaches"), "w") as f: f.write("\n") + def _setup_tor_browser_environment(self, config): + app = config.substs["MOZ_BUILD_APP"] + + noscript_target_filename = "{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi" + noscript_location = config.substs.get("NOSCRIPT") + + def _infallible_symlink(src, dst): + try: + os.symlink(src, dst) + except OSError as e: + if e.errno == errno.EEXIST: + # If the symlink already exists, remove it and try again. + os.remove(dst) + os.symlink(src, dst) + else: + return + + if app == "browser": + tbdir = Path(config.topobjdir) / "dist" / "bin" + + if config.substs.get("OS_TARGET") == "Darwin": + tbdir = next(tbdir.glob("*.app")) + paths = { + "docs": tbdir / "Contents/Resources/TorBrowser/Docs", + "exts": tbdir / "Contents/Resources/distribution/extensions", + "fonts": tbdir / "Resources/fonts", + } + else: + paths = { + "docs": tbdir / "TorBrowser/Docs", + "exts": tbdir / "distribution/extensions", + "fonts": tbdir / "fonts", + } + + fonts_location = config.substs.get("TOR_BROWSER_FONTS") + if fonts_location: + self.log( + logging.INFO, + "_setup_tor_browser_environment", + { + "fonts_location": fonts_location, + "fonts_target": str(paths["fonts"]), + }, + "Creating symlink for fonts files from {fonts_location} to {fonts_target}", + ) + + for file in Path(fonts_location).iterdir(): + target = paths["fonts"] / file.name + _infallible_symlink(file, target) + + # Set up NoScript extension + if noscript_location: + noscript_target = paths["exts"] / noscript_target_filename + self.log( + logging.INFO, + "_setup_tor_browser_environment", + { + "noscript_location": noscript_location, + "noscript_target": str(noscript_target), + }, + "Creating symlink for NoScript from {noscript_location} to {noscript_target}", + ) + + paths["exts"].mkdir(parents=True, exist_ok=True) + _infallible_symlink(noscript_location, noscript_target) + def post_build(self, config, output, jobs, verbose, status): """Called late during 'mach build' execution, after `build(...)` has finished. @@ -257,6 +326,9 @@ class BuildBackend(LoggingMixin): """ self._write_purgecaches(config) + if status == 0: + self._setup_tor_browser_environment(config) + return status @contextmanager ===================================== python/mozbuild/mozbuild/tbbutils.py ===================================== @@ -0,0 +1,103 @@ +import re +from urllib.request import Request, urlopen + + +def list_files_http(url): + try: + req = Request(url, method="GET") + with urlopen(req) as response: + if response.status != 200: + return [] + html = response.read().decode() + except Exception: + return [] + + links = [] + for href in re.findall(r'<a href="([^"]+)"', html): + if href == "../": + continue + + links.append(href) + + return links + + +TOR_BROWSER_BUILD_ARTIFACTS = [ + # Tor Browser Build-only artifacts, these artifacts are not common with Firefox. + "noscript", + "fonts", +] + +# Mapping of artifacts from taskcluster to tor-browser-build. +ARTIFACT_NAME_MAP = { + "cbindgen": "cbindgen", + # FIXME (tor-browser-build#41471): nasm is more or less ready to go, but it needs to have the + # executable in the root of the artifact folder instead of nasm/bin. + # "nasm": "nasm", + # FIXME (tor-browser-build#41421): the clang project as is, is not ready to use. It needs + # to be repackaged with a bunch of things that differ per platform. Fun stuff. + # "clang": "clang", + "node": "node", +} + + +def get_artifact_name(original_artifact_name, host): + # These are not build artifacts, they are pre-built artifacts to be added to the final build, + # therefore this check can come before the host check. + if original_artifact_name in TOR_BROWSER_BUILD_ARTIFACTS: + return original_artifact_name + + if host != "linux64": + # Tor browser build only has development artifacts for linux64 host systems. + return None + + return ARTIFACT_NAME_MAP.get(original_artifact_name) + + +def get_artifact_path(url, artifact, target, prefix="", log=lambda *args, **kwargs: {}): + if prefix: + path = prefix + else: + path = artifact + + # The `?C=M;O=D` parameters make it so links are ordered by + # the last modified date. This here to make us get the latest + # version of file in the case there are multiple and we just + # grab the first one. + files = list_files_http(f"{url}/{path}?C=M;O=D") + + if not files: + log(f"No files found in {url} for {artifact}.") + return None + + def filter_files(files, keyword): + return [file for file in files if keyword in file] + + artifact_files = [file for file in files if file.startswith(artifact)] + + if len(artifact_files) == 0: + log(f"No files found in {url} for {artifact}.") + return None + + if len(artifact_files) == 1: + return f"{url}/{path}/{artifact_files[0]}" + + files_per_os = filter_files(artifact_files, target.tor_browser_build_alias) + + # If there are files in the folder, but they don't have the OS in the name + # it probably means we can get any of them because they can be used to build + # for any OS. So let's just get the first one. + # + # Note: It could be the case that the artifact _is_ OS dependant, but there + # just are no files for the OS we are looking for. In that case, this will + # return an incorrect artifact. This should not happen often though and is + # something we cannot address until artifact names are standardized on tbb. + if len(files_per_os) == 0: + return f"{url}/{artifact}/{artifact_files[0]}" + + elif len(files_per_os) == 1: + return f"{url}/{artifact}/{files_per_os[0]}" + + matches = filter_files(files_per_os, target.cpu) + + return f"{url}/{artifact}/{matches[0]}" if matches else None ===================================== python/mozbuild/mozbuild/test/python.toml ===================================== @@ -111,6 +111,9 @@ subsuite = "mozbuild" ["test_rewrite_mozbuild.py"] +["test_tbbutils.py"] +subsuite = "base-browser" + ["test_telemetry.py"] ["test_telemetry_settings.py"] ===================================== python/mozbuild/mozbuild/test/test_tbbutils.py ===================================== @@ -0,0 +1,139 @@ +import unittest +from types import SimpleNamespace +from unittest.mock import MagicMock, patch + +import mozunit + +from mozbuild.tbbutils import get_artifact_path, list_files_http + + +class TestGetArtifactName(unittest.TestCase): + def setUp(self): + self.artifact = "artifact" + self.host = "linux64" + + @patch("mozbuild.tbbutils.TOR_BROWSER_BUILD_ARTIFACTS", new=["artifact"]) + def test_artifact_in_tbb_artifacts(self): + from mozbuild.tbbutils import get_artifact_name + + result = get_artifact_name(self.artifact, self.host) + self.assertEqual(result, self.artifact) + + @patch("mozbuild.tbbutils.ARTIFACT_NAME_MAP", new={"artifact": "tcafitra"}) + def test_host_is_not_linux64(self): + from mozbuild.tbbutils import get_artifact_name + + result = get_artifact_name(self.artifact, "linux64-aarch64") + self.assertIsNone(result) + + @patch("mozbuild.tbbutils.ARTIFACT_NAME_MAP", new={"artifact": "tcafitra"}) + def test_mapped_artifact(self): + from mozbuild.tbbutils import get_artifact_name + + result = get_artifact_name(self.artifact, self.host) + self.assertEqual(result, self.artifact[::-1]) + + +class TestGetArtifactPath(unittest.TestCase): + def setUp(self): + self.url = "http://example.com" + self.artifact = "artifact" + # This is just an example target which is valid. But it doesn't make + # any difference and could be anything for these tests. + self.target = SimpleNamespace(tor_browser_build_alias="linux", cpu="x86_64") + + @patch("mozbuild.tbbutils.list_files_http") + def test_no_files_returns_none(self, mock_list_files): + mock_list_files.return_value = [] + result = get_artifact_path(self.url, self.artifact, self.target) + self.assertIsNone(result) + + @patch("mozbuild.tbbutils.list_files_http") + def test_no_matching_files_returns_none(self, mock_list_files): + mock_list_files.return_value = ["somethingelse.zip", "yetanotherthing.zip"] + result = get_artifact_path(self.url, self.artifact, self.target) + self.assertIsNone(result) + + @patch("mozbuild.tbbutils.list_files_http") + def test_single_artifact_match(self, mock_list_files): + mock_list_files.return_value = ["artifact-1.zip"] + result = get_artifact_path(self.url, self.artifact, self.target) + self.assertEqual(result, f"{self.url}/{self.artifact}/artifact-1.zip") + + @patch("mozbuild.tbbutils.list_files_http") + def test_artifact_without_os_returns_first(self, mock_list_files): + mock_list_files.return_value = ["artifact-1.zip", "artifact-2.zip"] + result = get_artifact_path(self.url, self.artifact, self.target) + self.assertTrue(result.startswith(f"{self.url}/{self.artifact}/")) + self.assertIn("artifact-", result) + + @patch("mozbuild.tbbutils.list_files_http") + def test_artifact_with_os_match(self, mock_list_files): + mock_list_files.return_value = [ + "artifact-windows.zip", + "artifact-linux.zip", + ] + result = get_artifact_path(self.url, self.artifact, self.target) + self.assertEqual(result, f"{self.url}/{self.artifact}/artifact-linux.zip") + + @patch("mozbuild.tbbutils.list_files_http") + def test_artifact_with_cpu_match(self, mock_list_files): + mock_list_files.return_value = [ + "artifact-linux-arm.zip", + "artifact-linux-x86_64.zip", + ] + result = get_artifact_path(self.url, self.artifact, self.target) + self.assertEqual( + result, f"{self.url}/{self.artifact}/artifact-linux-x86_64.zip" + ) + + @patch("mozbuild.tbbutils.list_files_http") + def test_artifact_with_prefix(self, mock_list_files): + mock_list_files.return_value = ["artifact-1.zip"] + + prefix = "prefix" + result = get_artifact_path(self.url, self.artifact, self.target, prefix=prefix) + self.assertEqual(result, f"{self.url}/{prefix}/artifact-1.zip") + mock_list_files.assert_called_with(f"{self.url}/{prefix}?C=M;O=D") + + +class TestListFilesHttp(unittest.TestCase): + def setUp(self): + self.url = "http://example.com" + + @patch("mozbuild.tbbutils.urlopen") + def test_non_200_status_returns_empty(self, mock_urlopen): + mock_resp = MagicMock() + mock_resp.status = 404 + mock_resp.read.return_value = b"" + mock_urlopen.return_value.__enter__.return_value = mock_resp + + result = list_files_http(self.url) + self.assertEqual(result, []) + + @patch("mozbuild.tbbutils.urlopen") + def test_exception_returns_empty(self, mock_urlopen): + mock_urlopen.side_effect = Exception("network error") + result = list_files_http(self.url) + self.assertEqual(result, []) + + @patch("mozbuild.tbbutils.urlopen") + def test_regular_links(self, mock_urlopen): + html = b""" + <html><body> + <a href="../">Parent</a> + <a href="file1.zip">file1</a> + <a href="file2.zip">file2</a> + </body></html> + """ + mock_resp = MagicMock() + mock_resp.status = 200 + mock_resp.read.return_value = html + mock_urlopen.return_value.__enter__.return_value = mock_resp + + result = list_files_http(self.url) + self.assertEqual(result, ["file1.zip", "file2.zip"]) + + +if __name__ == "__main__": + mozunit.main() ===================================== tools/base-browser/l10n/combine/tests/README deleted ===================================== @@ -1,2 +0,0 @@ -python tests to be run with pytest. -Requires the compare-locales package. ===================================== tools/base-browser/git-rebase-fixup-preprocessor → tools/base_browser/git-rebase-fixup-preprocessor ===================================== ===================================== tools/base-browser/l10n/combine-translation-versions.py → tools/base_browser/l10n/combine-translation-versions.py ===================================== ===================================== tools/base-browser/l10n/combine/__init__.py → tools/base_browser/l10n/combine/__init__.py ===================================== ===================================== tools/base-browser/l10n/combine/combine.py → tools/base_browser/l10n/combine/combine.py ===================================== ===================================== tools/base-browser/l10n/combine/tests/__init__.py → tools/base_browser/l10n/combine/tests/__init__.py ===================================== ===================================== tools/base_browser/l10n/combine/tests/python.toml ===================================== @@ -0,0 +1,10 @@ +[DEFAULT] +subsuite = "base-browser" + +["test_android.py"] + +["test_dtd.py"] + +["test_fluent.py"] + +["test_properties.py"] ===================================== tools/base-browser/l10n/combine/tests/test_android.py → tools/base_browser/l10n/combine/tests/test_android.py ===================================== @@ -1,6 +1,7 @@ import textwrap -from combine import combine_files +import mozunit +from base_browser.l10n.combine import combine_files def wrap_in_xml(content): @@ -413,3 +414,7 @@ def test_alternatives(): <string name="string_4_alt">Other string</string> """, ) + + +if __name__ == "__main__": + mozunit.main() ===================================== tools/base-browser/l10n/combine/tests/test_dtd.py → tools/base_browser/l10n/combine/tests/test_dtd.py ===================================== @@ -1,6 +1,7 @@ import textwrap -from combine import combine_files +import mozunit +from base_browser.l10n.combine import combine_files def assert_result(new_content, old_content, expect): @@ -411,3 +412,7 @@ def test_alternatives(): <!ENTITY string.4.alt "Other string"> """, ) + + +if __name__ == "__main__": + mozunit.main() ===================================== tools/base-browser/l10n/combine/tests/test_fluent.py → tools/base_browser/l10n/combine/tests/test_fluent.py ===================================== @@ -1,6 +1,7 @@ import textwrap -from combine import combine_files +import mozunit +from base_browser.l10n.combine import combine_files def assert_result(new_content, old_content, expect): @@ -475,3 +476,7 @@ def test_alternatives(): -string-4-alt = Other string """, ) + + +if __name__ == "__main__": + mozunit.main() ===================================== tools/base-browser/l10n/combine/tests/test_properties.py → tools/base_browser/l10n/combine/tests/test_properties.py ===================================== @@ -1,6 +1,7 @@ import textwrap -from combine import combine_files +import mozunit +from base_browser.l10n.combine import combine_files def assert_result(new_content, old_content, expect): @@ -408,3 +409,7 @@ def test_alternatives(): string.4.alt = Other string """, ) + + +if __name__ == "__main__": + mozunit.main() ===================================== tools/base-browser/missing-css-variables.py → tools/base_browser/missing-css-variables.py ===================================== ===================================== tools/base-browser/tb-dev → tools/base_browser/tb-dev ===================================== ===================================== tools/moz.build ===================================== @@ -71,6 +71,7 @@ with Files("tryselect/docs/**"): SCHEDULES.exclusive = ["docs"] PYTHON_UNITTEST_MANIFESTS += [ + "base_browser/l10n/combine/tests/python.toml", "fuzzing/smoke/python.toml", "lint/test/python.toml", "tryselect/test/python.toml", View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/53201cb... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/53201cb... You're receiving this email because of your account on gitlab.torproject.org.