Loading .gitlab-ci.yml +4 −0 Original line number Diff line number Diff line stages: - update-container-images - lint - test - startup-test - update-translations variables: Loading @@ -11,4 +13,6 @@ include: - local: '.gitlab/ci/mixins.yml' - local: '.gitlab/ci/jobs/lint/lint.yml' - local: '.gitlab/ci/jobs/test/python-test.yml' - local: '.gitlab/ci/jobs/startup-test/startup-test.yml' - local: '.gitlab/ci/jobs/update-containers.yml' - local: '.gitlab/ci/jobs/update-translations.yml' .gitlab/ci/containers/base/Containerfile 0 → 100644 +53 −0 Original line number Diff line number Diff line # This image is published in containers.torproject.org/tpo/applications/tor-browser/{android,desktop} # # This image is built and published on CI. The CI job that does this is manual, # therefore it's up to the developer or reviewer to trigger this CI job when necessary # e.g. after a RR rebase or big changes to this file. FROM containers.torproject.org/tpo/tpa/base-images/python:trixie ARG APPLICATION_CHOICE RUN apt-get update && apt-get install -y \ curl \ git \ libc6-dev \ xvfb \ zlib1g-dev RUN git clone --single-branch --depth 1 https://gitlab.torproject.org/tpo/applications/tor-browser.git # Bootstrap will download and install all dependencies required for building / linting / etc. # # Note: mozcheck is usually built at lint time and not fetched at bootstrap time. # We need to do it manually so it's available come lint time. RUN cd tor-browser && \ yes | MOZBUILD_STATE_PATH=/var/tmp/mozbuild ./mach bootstrap --application-choice "$APPLICATION_CHOICE" && \ ./mach artifact toolchain --from-build toolchain-linux64-mozcheck && mv mozcheck /var/tmp/mozbuild && \ cd .. RUN rm -rf tor-browser && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ rm -rf /var/tmp/mozbuild/toolchains && \ rm -rf /var/tmp/mozbuild/indices && \ rm -rf /var/tmp/mozbuild/android-device && \ rm -rf /var/root/.cargo && \ rm -rf /var/root/.rustup RUN adduser --uid 1000 --disabled-password --gecos "" gitlab-runner && \ chown -R 1000:1000 /var/tmp/mozbuild/ USER gitlab-runner WORKDIR /home/gitlab-runner # Install Rust for the gitlab-runner user. RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ENV PATH="/var/tmp/mozbuild/clang/bin:/home/gitlab-runner/.cargo/bin:$PATH" ENV MOZ_FETCHES_DIR="/var/tmp/mozbuild" RUN echo "$APPLICATION_CHOICE" | grep -qi android && \ rustup target add armv7-linux-androideabi \ aarch64-linux-android \ i686-linux-android \ x86_64-linux-android \ || true .gitlab/ci/jobs/startup-test/startup-test-android.py 0 → 100644 +231 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 import argparse import json import os import sys import time from datetime import datetime, timedelta from enum import Enum import requests """ This script runs Android tests on BrowserStack using the BrowserStack App Automate Espresso API. Usage: startup-test-android.py --devices <devices> [--tests <tests>] [--app_file_path <app_file_path>] [--test_file_path <test_file_path>] Arguments: --devices: Comma-separated list of devices to test on (required). --tests: Comma-separated list of tests to run (optional). If not provided, all tests will run. --app_file_path: Path to the app file (optional). If not provided, yesterday's nightly will be downloaded. --test_file_path: Path to the test file (optional). If not provided, yesterday's nightly will be downloaded. Environment Variables: BROWSERSTACK_USERNAME: BrowserStack username (required). BROWSERSTACK_API_KEY: BrowserStack API key (required). Description: - If app and test file paths are not provided, the script downloads the latest nightly build from the Tor Project. - Uploads the app and test files to BrowserStack. - Triggers the test run on the specified devices. - Polls for the test status until completion or timeout. - Prints the test results and exits with an appropriate status code. """ parser = argparse.ArgumentParser( description="Run Android startup tests on BrowserStack." ) parser.add_argument( "--devices", type=str, help="Comma-separated list of devices to test on", required=True, ) parser.add_argument("--tests", type=str, help="Comma-separated list of tests to run") parser.add_argument("--app_file_path", type=str, help="Path to the app file") parser.add_argument("--test_file_path", type=str, help="Path to the test file") args = parser.parse_args() if args.app_file_path: app_file_path = args.app_file_path test_file_path = args.test_file_path if not test_file_path: print( "\033[1;31mIf either app or test file paths are provided, both must be provided.\033[0m" ) else: def download_file(url, dest_path): try: response = requests.get(url, stream=True) response.raise_for_status() with open(dest_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) except Exception as e: print(f"\033[1;31mFailed to download file from {url}.\033[0m") print(e) sys.exit(1) yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y.%m.%d") download_url_base = f"https://nightlies.tbb.torproject.org/nightly-builds/tor-browser-builds/tbb-nightly.{yesterday}/nightly-android-aarch64" print( f"No file paths provided, downloading yesterday's nightly from {download_url_base}" ) app_file_url = f"{download_url_base}/tor-browser-noopt-android-aarch64-tbb-nightly.{yesterday}.apk" test_file_url = ( f"{download_url_base}/tor-browser-tbb-nightly.{yesterday}-androidTest.apk" ) # BrowserStack will fail if there are `.` in the file name other than before the extension. yesterday = yesterday.replace(".", "-") app_file_path = f"/tmp/nightly-{yesterday}.apk" test_file_path = f"/tmp/nightly-test-{yesterday}.apk" download_file(app_file_url, app_file_path) download_file(test_file_url, test_file_path) devices = [device.strip() for device in args.devices.split(",")] tests = args.tests.split(",") if args.tests else [] browserstack_username = os.getenv("BROWSERSTACK_USERNAME") browserstack_api_key = os.getenv("BROWSERSTACK_API_KEY") if not browserstack_username or not browserstack_api_key: print( "\033[1;31mEnvironment variables BROWSERSTACK_USERNAME and BROWSERSTACK_API_KEY must be set.\033[0m" ) sys.exit(1) # Upload app file with open(app_file_path, "rb") as app_file: response = requests.post( "https://api-cloud.browserstack.com/app-automate/espresso/v2/app", auth=(browserstack_username, browserstack_api_key), files={"file": app_file}, ) if response.status_code != 200: print("\033[1;31mFailed to upload app file.\033[0m") print(response.text) sys.exit(1) bs_app_url = response.json().get("app_url") print("\033[1;32mSuccessfully uploaded app file.\033[0m") print(f"App URL: {bs_app_url}") # Upload test file with open(test_file_path, "rb") as test_file: response = requests.post( "https://api-cloud.browserstack.com/app-automate/espresso/v2/test-suite", auth=(browserstack_username, browserstack_api_key), files={"file": test_file}, ) if response.status_code != 200: print("\033[1;31mFailed to upload test file.\033[0m") print(response.text) sys.exit(1) bs_test_url = response.json().get("test_suite_url") print("\033[1;32mSuccessfully uploaded test file.\033[0m") print(f"Test URL: {bs_test_url}") # Trigger tests test_params = { "app": bs_app_url, "testSuite": bs_test_url, "devices": devices, "class": tests, } response = requests.post( "https://api-cloud.browserstack.com/app-automate/espresso/v2/build", auth=(browserstack_username, browserstack_api_key), headers={"Content-Type": "application/json"}, data=json.dumps(test_params), ) if response.status_code != 200: print("\033[1;31mFailed to trigger test run.\033[0m") print(response.text) sys.exit(1) build_id = response.json().get("build_id") print("\033[1;32mSuccessfully triggered test run.\033[0m") print( f"Test status also available at: https://app-automate.browserstack.com/builds/{build_id}\n===" ) # Poll for status POLLING_TIMEOUT = 30 * 60 # 30min POLLING_INTERVAL = 30 # 30s class TestStatus(Enum): QUEUED = "queued" RUNNING = "running" ERROR = "error" FAILED = "failed" PASSED = "passed" TIMED_OUT = "timed out" SKIPPED = "skipped" @classmethod def from_string(cls, s): try: return cls[s.upper().replace(" ", "_")] except KeyError: raise ValueError(f"\033[1;31m'{s}' is not a valid test status.\033[0m") def is_terminal(self): return self not in {TestStatus.QUEUED, TestStatus.RUNNING} def is_success(self): return self in {TestStatus.PASSED, TestStatus.SKIPPED} def color_print(self): if self == TestStatus.PASSED: return f"\033[1;32m{self.value}\033[0m" if self in {TestStatus.ERROR, TestStatus.FAILED, TestStatus.TIMED_OUT}: return f"\033[1;31m{self.value}\033[0m" if self == TestStatus.SKIPPED: return f"\033[1;33m{self.value}\033[0m" return self.value start_time = time.time() elapsed_time = 0 test_status = None while elapsed_time <= POLLING_TIMEOUT: response = requests.get( f"https://api-cloud.browserstack.com/app-automate/espresso/v2/builds/{build_id}", auth=(browserstack_username, browserstack_api_key), ) if response.status_code != 200: print("\033[1;31mFailed to get test status.\033[0m") print(response.text) sys.exit(1) test_status = TestStatus.from_string(response.json().get("status")) if test_status.is_terminal(): print(f"===\nTest finished. Result: {test_status.color_print()}") break else: elapsed_time = time.time() - start_time print(f"Test status: {test_status.value} ({elapsed_time:.2f}s)") if elapsed_time > POLLING_TIMEOUT: print("===\n\033[1;33mWaited for tests for too long.\033[0m") break time.sleep(POLLING_INTERVAL) if test_status is None or not test_status.is_success(): sys.exit(1) .gitlab/ci/jobs/startup-test/startup-test.py 0 → 100644 +102 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 import argparse import subprocess from datetime import datetime, timedelta PLATFORM_TO_ARCH = { "linux": ["x86_64", "i686"], "macos": ["x86_64", "aarch64"], "windows": ["x86_64", "i686"], } class DynamicArchAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): platform = getattr(namespace, "platform", None) if not platform: raise argparse.ArgumentError( self, "The --platform argument must be provided before --arch." ) valid_archs = PLATFORM_TO_ARCH.get(platform, []) if values not in valid_archs: raise argparse.ArgumentError( self, f"Invalid architecture '{values}' for platform '{platform}'. " f"Valid options are: {', '.join(valid_archs)}", ) setattr(namespace, self.dest, values) parser = argparse.ArgumentParser( description="Downloads and executes yesterday's build of Tor or Mullvad browser nightly." ) parser.add_argument( "--platform", required=True, help="Specify the platform (linux, macos or windows). Must be provided before --arch.", choices=PLATFORM_TO_ARCH.keys(), ) parser.add_argument( "--arch", required=True, help="Specify the architecture (validated dynamically based on --platform).", action=DynamicArchAction, ) parser.add_argument( "--browser", required=True, choices=["tor", "mullvad"], help="Specify the browser (tor or mullvad)", ) args = parser.parse_args() arch = f"-{args.arch}" extra = "" if args.platform == "linux": archive_extension = "tar.xz" binary = f"Browser/start-{args.browser}-browser" elif args.platform == "macos": archive_extension = "dmg" # The URL doesn't include the architecture for MacOS, # because it's a universal build. arch = "" if args.browser == "tor": binary = "Contents/MacOS/firefox" else: binary = "Contents/MacOS/mullvadbrowser" elif args.platform == "windows": archive_extension = "exe" if args.browser == "tor": extra = "-portable" binary = "Browser/firefox.exe" else: binary = "mullvadbrowser.exe" yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y.%m.%d") download_url_base = ( "https://nightlies.tbb.torproject.org/nightly-builds/tor-browser-builds" ) if args.browser == "tor": download_url = f"{download_url_base}/tbb-nightly.{yesterday}/nightly-{args.platform}{arch}/{args.browser}-browser-{args.platform}{arch}{extra}-tbb-nightly.{yesterday}.{archive_extension}" else: download_url = f"{download_url_base}/tbb-nightly.{yesterday}/mullvadbrowser-nightly-{args.platform}{arch}/{args.browser}-browser-{args.platform}{arch}-tbb-nightly.{yesterday}.{archive_extension}" subprocess.run( [ "python3", "testing/mozharness/scripts/does_it_crash.py", "--run-for", "30", "--thing-url", download_url, "--thing-to-run", binary, ], check=True, ) .gitlab/ci/jobs/startup-test/startup-test.yml 0 → 100644 +63 −0 Original line number Diff line number Diff line # startup-test-windows: # extends: .with-local-repo-pwsh # variables: # LOCAL_REPO_PATH: "C:\\Users\\windoes\\tor-browser.git" # stage: startup-test # interruptible: true # parallel: # matrix: # - BROWSER: ["tor", "mullvad"] # tags: # - x86-win11 # script: # - ./mach python testing/mozbase/setup_development.py # - ./mach python .gitlab/ci/jobs/startup-test/startup-test.py --platform windows --arch x86_64 --browser $BROWSER # rules: # - if: $CI_PIPELINE_SOURCE == "schedule" # startup-test-macos: # extends: .with-local-repo-bash # variables: # LOCAL_REPO_PATH: "/Users/gitlab-runner/tor-browser.git" # stage: startup-test # interruptible: true # parallel: # matrix: # - BROWSER: ["tor", "mullvad"] # tags: # - x86-macos # script: # - ./mach python testing/mozbase/setup_development.py # - ./mach python .gitlab/ci/jobs/startup-test/startup-test.py --platform macos --arch x86_64 --browser $BROWSER # rules: # - if: $CI_PIPELINE_SOURCE == "schedule" startup-test-linux: extends: .with-local-repo-bash image: $IMAGE_PATH stage: startup-test interruptible: true parallel: matrix: - BROWSER: ["tor", "mullvad"] tags: - firefox script: - Xvfb :99 -screen 0 1400x900x24 & - export DISPLAY=:99 - ./mach python testing/mozbase/setup_development.py - ./mach python .gitlab/ci/jobs/startup-test/startup-test.py --platform linux --arch x86_64 --browser $BROWSER rules: - if: $CI_PIPELINE_SOURCE == "schedule" startup-test-android: extends: .with-local-repo-bash image: $IMAGE_PATH stage: startup-test interruptible: true tags: - firefox script: - ./mach python .gitlab/ci/jobs/startup-test/startup-test-android.py --devices "Samsung Galaxy S23-13.0, Samsung Galaxy S8-7.0" --tests org.mozilla.fenix.LaunchTest rules: - if: $CI_PIPELINE_SOURCE == "schedule" Loading
.gitlab-ci.yml +4 −0 Original line number Diff line number Diff line stages: - update-container-images - lint - test - startup-test - update-translations variables: Loading @@ -11,4 +13,6 @@ include: - local: '.gitlab/ci/mixins.yml' - local: '.gitlab/ci/jobs/lint/lint.yml' - local: '.gitlab/ci/jobs/test/python-test.yml' - local: '.gitlab/ci/jobs/startup-test/startup-test.yml' - local: '.gitlab/ci/jobs/update-containers.yml' - local: '.gitlab/ci/jobs/update-translations.yml'
.gitlab/ci/containers/base/Containerfile 0 → 100644 +53 −0 Original line number Diff line number Diff line # This image is published in containers.torproject.org/tpo/applications/tor-browser/{android,desktop} # # This image is built and published on CI. The CI job that does this is manual, # therefore it's up to the developer or reviewer to trigger this CI job when necessary # e.g. after a RR rebase or big changes to this file. FROM containers.torproject.org/tpo/tpa/base-images/python:trixie ARG APPLICATION_CHOICE RUN apt-get update && apt-get install -y \ curl \ git \ libc6-dev \ xvfb \ zlib1g-dev RUN git clone --single-branch --depth 1 https://gitlab.torproject.org/tpo/applications/tor-browser.git # Bootstrap will download and install all dependencies required for building / linting / etc. # # Note: mozcheck is usually built at lint time and not fetched at bootstrap time. # We need to do it manually so it's available come lint time. RUN cd tor-browser && \ yes | MOZBUILD_STATE_PATH=/var/tmp/mozbuild ./mach bootstrap --application-choice "$APPLICATION_CHOICE" && \ ./mach artifact toolchain --from-build toolchain-linux64-mozcheck && mv mozcheck /var/tmp/mozbuild && \ cd .. RUN rm -rf tor-browser && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ rm -rf /var/tmp/mozbuild/toolchains && \ rm -rf /var/tmp/mozbuild/indices && \ rm -rf /var/tmp/mozbuild/android-device && \ rm -rf /var/root/.cargo && \ rm -rf /var/root/.rustup RUN adduser --uid 1000 --disabled-password --gecos "" gitlab-runner && \ chown -R 1000:1000 /var/tmp/mozbuild/ USER gitlab-runner WORKDIR /home/gitlab-runner # Install Rust for the gitlab-runner user. RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ENV PATH="/var/tmp/mozbuild/clang/bin:/home/gitlab-runner/.cargo/bin:$PATH" ENV MOZ_FETCHES_DIR="/var/tmp/mozbuild" RUN echo "$APPLICATION_CHOICE" | grep -qi android && \ rustup target add armv7-linux-androideabi \ aarch64-linux-android \ i686-linux-android \ x86_64-linux-android \ || true
.gitlab/ci/jobs/startup-test/startup-test-android.py 0 → 100644 +231 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 import argparse import json import os import sys import time from datetime import datetime, timedelta from enum import Enum import requests """ This script runs Android tests on BrowserStack using the BrowserStack App Automate Espresso API. Usage: startup-test-android.py --devices <devices> [--tests <tests>] [--app_file_path <app_file_path>] [--test_file_path <test_file_path>] Arguments: --devices: Comma-separated list of devices to test on (required). --tests: Comma-separated list of tests to run (optional). If not provided, all tests will run. --app_file_path: Path to the app file (optional). If not provided, yesterday's nightly will be downloaded. --test_file_path: Path to the test file (optional). If not provided, yesterday's nightly will be downloaded. Environment Variables: BROWSERSTACK_USERNAME: BrowserStack username (required). BROWSERSTACK_API_KEY: BrowserStack API key (required). Description: - If app and test file paths are not provided, the script downloads the latest nightly build from the Tor Project. - Uploads the app and test files to BrowserStack. - Triggers the test run on the specified devices. - Polls for the test status until completion or timeout. - Prints the test results and exits with an appropriate status code. """ parser = argparse.ArgumentParser( description="Run Android startup tests on BrowserStack." ) parser.add_argument( "--devices", type=str, help="Comma-separated list of devices to test on", required=True, ) parser.add_argument("--tests", type=str, help="Comma-separated list of tests to run") parser.add_argument("--app_file_path", type=str, help="Path to the app file") parser.add_argument("--test_file_path", type=str, help="Path to the test file") args = parser.parse_args() if args.app_file_path: app_file_path = args.app_file_path test_file_path = args.test_file_path if not test_file_path: print( "\033[1;31mIf either app or test file paths are provided, both must be provided.\033[0m" ) else: def download_file(url, dest_path): try: response = requests.get(url, stream=True) response.raise_for_status() with open(dest_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) except Exception as e: print(f"\033[1;31mFailed to download file from {url}.\033[0m") print(e) sys.exit(1) yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y.%m.%d") download_url_base = f"https://nightlies.tbb.torproject.org/nightly-builds/tor-browser-builds/tbb-nightly.{yesterday}/nightly-android-aarch64" print( f"No file paths provided, downloading yesterday's nightly from {download_url_base}" ) app_file_url = f"{download_url_base}/tor-browser-noopt-android-aarch64-tbb-nightly.{yesterday}.apk" test_file_url = ( f"{download_url_base}/tor-browser-tbb-nightly.{yesterday}-androidTest.apk" ) # BrowserStack will fail if there are `.` in the file name other than before the extension. yesterday = yesterday.replace(".", "-") app_file_path = f"/tmp/nightly-{yesterday}.apk" test_file_path = f"/tmp/nightly-test-{yesterday}.apk" download_file(app_file_url, app_file_path) download_file(test_file_url, test_file_path) devices = [device.strip() for device in args.devices.split(",")] tests = args.tests.split(",") if args.tests else [] browserstack_username = os.getenv("BROWSERSTACK_USERNAME") browserstack_api_key = os.getenv("BROWSERSTACK_API_KEY") if not browserstack_username or not browserstack_api_key: print( "\033[1;31mEnvironment variables BROWSERSTACK_USERNAME and BROWSERSTACK_API_KEY must be set.\033[0m" ) sys.exit(1) # Upload app file with open(app_file_path, "rb") as app_file: response = requests.post( "https://api-cloud.browserstack.com/app-automate/espresso/v2/app", auth=(browserstack_username, browserstack_api_key), files={"file": app_file}, ) if response.status_code != 200: print("\033[1;31mFailed to upload app file.\033[0m") print(response.text) sys.exit(1) bs_app_url = response.json().get("app_url") print("\033[1;32mSuccessfully uploaded app file.\033[0m") print(f"App URL: {bs_app_url}") # Upload test file with open(test_file_path, "rb") as test_file: response = requests.post( "https://api-cloud.browserstack.com/app-automate/espresso/v2/test-suite", auth=(browserstack_username, browserstack_api_key), files={"file": test_file}, ) if response.status_code != 200: print("\033[1;31mFailed to upload test file.\033[0m") print(response.text) sys.exit(1) bs_test_url = response.json().get("test_suite_url") print("\033[1;32mSuccessfully uploaded test file.\033[0m") print(f"Test URL: {bs_test_url}") # Trigger tests test_params = { "app": bs_app_url, "testSuite": bs_test_url, "devices": devices, "class": tests, } response = requests.post( "https://api-cloud.browserstack.com/app-automate/espresso/v2/build", auth=(browserstack_username, browserstack_api_key), headers={"Content-Type": "application/json"}, data=json.dumps(test_params), ) if response.status_code != 200: print("\033[1;31mFailed to trigger test run.\033[0m") print(response.text) sys.exit(1) build_id = response.json().get("build_id") print("\033[1;32mSuccessfully triggered test run.\033[0m") print( f"Test status also available at: https://app-automate.browserstack.com/builds/{build_id}\n===" ) # Poll for status POLLING_TIMEOUT = 30 * 60 # 30min POLLING_INTERVAL = 30 # 30s class TestStatus(Enum): QUEUED = "queued" RUNNING = "running" ERROR = "error" FAILED = "failed" PASSED = "passed" TIMED_OUT = "timed out" SKIPPED = "skipped" @classmethod def from_string(cls, s): try: return cls[s.upper().replace(" ", "_")] except KeyError: raise ValueError(f"\033[1;31m'{s}' is not a valid test status.\033[0m") def is_terminal(self): return self not in {TestStatus.QUEUED, TestStatus.RUNNING} def is_success(self): return self in {TestStatus.PASSED, TestStatus.SKIPPED} def color_print(self): if self == TestStatus.PASSED: return f"\033[1;32m{self.value}\033[0m" if self in {TestStatus.ERROR, TestStatus.FAILED, TestStatus.TIMED_OUT}: return f"\033[1;31m{self.value}\033[0m" if self == TestStatus.SKIPPED: return f"\033[1;33m{self.value}\033[0m" return self.value start_time = time.time() elapsed_time = 0 test_status = None while elapsed_time <= POLLING_TIMEOUT: response = requests.get( f"https://api-cloud.browserstack.com/app-automate/espresso/v2/builds/{build_id}", auth=(browserstack_username, browserstack_api_key), ) if response.status_code != 200: print("\033[1;31mFailed to get test status.\033[0m") print(response.text) sys.exit(1) test_status = TestStatus.from_string(response.json().get("status")) if test_status.is_terminal(): print(f"===\nTest finished. Result: {test_status.color_print()}") break else: elapsed_time = time.time() - start_time print(f"Test status: {test_status.value} ({elapsed_time:.2f}s)") if elapsed_time > POLLING_TIMEOUT: print("===\n\033[1;33mWaited for tests for too long.\033[0m") break time.sleep(POLLING_INTERVAL) if test_status is None or not test_status.is_success(): sys.exit(1)
.gitlab/ci/jobs/startup-test/startup-test.py 0 → 100644 +102 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 import argparse import subprocess from datetime import datetime, timedelta PLATFORM_TO_ARCH = { "linux": ["x86_64", "i686"], "macos": ["x86_64", "aarch64"], "windows": ["x86_64", "i686"], } class DynamicArchAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): platform = getattr(namespace, "platform", None) if not platform: raise argparse.ArgumentError( self, "The --platform argument must be provided before --arch." ) valid_archs = PLATFORM_TO_ARCH.get(platform, []) if values not in valid_archs: raise argparse.ArgumentError( self, f"Invalid architecture '{values}' for platform '{platform}'. " f"Valid options are: {', '.join(valid_archs)}", ) setattr(namespace, self.dest, values) parser = argparse.ArgumentParser( description="Downloads and executes yesterday's build of Tor or Mullvad browser nightly." ) parser.add_argument( "--platform", required=True, help="Specify the platform (linux, macos or windows). Must be provided before --arch.", choices=PLATFORM_TO_ARCH.keys(), ) parser.add_argument( "--arch", required=True, help="Specify the architecture (validated dynamically based on --platform).", action=DynamicArchAction, ) parser.add_argument( "--browser", required=True, choices=["tor", "mullvad"], help="Specify the browser (tor or mullvad)", ) args = parser.parse_args() arch = f"-{args.arch}" extra = "" if args.platform == "linux": archive_extension = "tar.xz" binary = f"Browser/start-{args.browser}-browser" elif args.platform == "macos": archive_extension = "dmg" # The URL doesn't include the architecture for MacOS, # because it's a universal build. arch = "" if args.browser == "tor": binary = "Contents/MacOS/firefox" else: binary = "Contents/MacOS/mullvadbrowser" elif args.platform == "windows": archive_extension = "exe" if args.browser == "tor": extra = "-portable" binary = "Browser/firefox.exe" else: binary = "mullvadbrowser.exe" yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y.%m.%d") download_url_base = ( "https://nightlies.tbb.torproject.org/nightly-builds/tor-browser-builds" ) if args.browser == "tor": download_url = f"{download_url_base}/tbb-nightly.{yesterday}/nightly-{args.platform}{arch}/{args.browser}-browser-{args.platform}{arch}{extra}-tbb-nightly.{yesterday}.{archive_extension}" else: download_url = f"{download_url_base}/tbb-nightly.{yesterday}/mullvadbrowser-nightly-{args.platform}{arch}/{args.browser}-browser-{args.platform}{arch}-tbb-nightly.{yesterday}.{archive_extension}" subprocess.run( [ "python3", "testing/mozharness/scripts/does_it_crash.py", "--run-for", "30", "--thing-url", download_url, "--thing-to-run", binary, ], check=True, )
.gitlab/ci/jobs/startup-test/startup-test.yml 0 → 100644 +63 −0 Original line number Diff line number Diff line # startup-test-windows: # extends: .with-local-repo-pwsh # variables: # LOCAL_REPO_PATH: "C:\\Users\\windoes\\tor-browser.git" # stage: startup-test # interruptible: true # parallel: # matrix: # - BROWSER: ["tor", "mullvad"] # tags: # - x86-win11 # script: # - ./mach python testing/mozbase/setup_development.py # - ./mach python .gitlab/ci/jobs/startup-test/startup-test.py --platform windows --arch x86_64 --browser $BROWSER # rules: # - if: $CI_PIPELINE_SOURCE == "schedule" # startup-test-macos: # extends: .with-local-repo-bash # variables: # LOCAL_REPO_PATH: "/Users/gitlab-runner/tor-browser.git" # stage: startup-test # interruptible: true # parallel: # matrix: # - BROWSER: ["tor", "mullvad"] # tags: # - x86-macos # script: # - ./mach python testing/mozbase/setup_development.py # - ./mach python .gitlab/ci/jobs/startup-test/startup-test.py --platform macos --arch x86_64 --browser $BROWSER # rules: # - if: $CI_PIPELINE_SOURCE == "schedule" startup-test-linux: extends: .with-local-repo-bash image: $IMAGE_PATH stage: startup-test interruptible: true parallel: matrix: - BROWSER: ["tor", "mullvad"] tags: - firefox script: - Xvfb :99 -screen 0 1400x900x24 & - export DISPLAY=:99 - ./mach python testing/mozbase/setup_development.py - ./mach python .gitlab/ci/jobs/startup-test/startup-test.py --platform linux --arch x86_64 --browser $BROWSER rules: - if: $CI_PIPELINE_SOURCE == "schedule" startup-test-android: extends: .with-local-repo-bash image: $IMAGE_PATH stage: startup-test interruptible: true tags: - firefox script: - ./mach python .gitlab/ci/jobs/startup-test/startup-test-android.py --devices "Samsung Galaxy S23-13.0, Samsung Galaxy S8-7.0" --tests org.mozilla.fenix.LaunchTest rules: - if: $CI_PIPELINE_SOURCE == "schedule"