diff --git a/automation/build_against_all.py b/automation/build_against_all.py new file mode 100755 index 0000000000..a99801f48c --- /dev/null +++ b/automation/build_against_all.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# Purpose: Run various smoke tests against this application-services working tree. +# Requirements: +# - python +# - application-services built and working. +# For the mac builds: +# - xcpretty (`gem install xcpretty`) +# - xcode + xcodebuild + xcodetools setup and running (a successful build of the firefox-ios repository) +# Arguments: +# --action => Can be either `run-tests` (default) or `build-without-testing` +# --use_local_firefox_ios => Use a local firefox-ios repository instead (at the provided path). +# --verbose => Includes the stdout of subprocesses (like the xcodebuild output, or other bootstrapping scripts) +# --allow-clears => Clear existing uniffi bindings, swift files, and so on during the various build processes. +# --use-local-firefox-ios => Use a local copy of firefox-ios instead of cloning it for iOS tests. Exclusive with `remote-ios-repo-url` +# --remote-ios-repo-url => Clone a different firefox-ios repository for iOS tests. Exclusive with `use-local-firefox-ios` +# --ios-scheme => The scheme to run for iOS tests. Likely: `Fennec` (default) or `Firefox` +# --ios-test-plan => The test plan to test with for iOS tests. Likely: `Smoketest` (default) or `FullFunctionalTestPlan` +import argparse +import time +from shared import err_msg, step_msg +from build_against_hnt import build_against_hnt +from build_against_fenix import build_against_fenix +from build_against_ios import build_against_ios + +parser = argparse.ArgumentParser( + description="Run groups of tests against this application-services working tree." +) + +group = parser.add_mutually_exclusive_group() +parser.add_argument( + "--firefox-dir", + required=True, + help="Path to existing bootstrapped `mozilla-central` directory.", +) +parser.add_argument( + "--verbose", + help="Display subprocess logs for compilation processes (off by default).", + action=argparse.BooleanOptionalAction, +) +parser.add_argument( + "--allow-clears", + help="Clear existing uniffi bindings, swift files, and so on during the various build processes (what gets cleared varies per platform test).", + action=argparse.BooleanOptionalAction, +) +parser.add_argument( + "--action", + required=True, + choices=["run-tests", "build-without-testing"], + help="Run the following action for target's test", +) + +# iOS arguments to pass down +group = parser.add_mutually_exclusive_group() +group.add_argument( + "--use-local-firefox-ios", + metavar="LOCAL_IOS_REPO_PATH", + help="Use a local copy of firefox-ios instead of cloning it for iOS tests. Exclusive with `remote-ios-repo-url`", +) +group.add_argument( + "--remote-ios-repo-url", + metavar="REMOTE_REPO_PATH", + help="Clone a different firefox-ios repository for iOS tests. Exclusive with `use-local-firefox-ios`", +) +parser.add_argument( + "--ios-scheme", + help="The scheme to run for iOS tests. Likely: `Fennec` (default) or `Firefox`", + default="Fennec", +) +parser.add_argument( + "--ios-test-plan", + help="The test plan to test with for iOS tests. Likely: `Smoketest` (default) or `FullFunctionalTestPlan`", + default="Smoketest", +) + +# HNT argument to pass down +parser.add_argument( + "--hnt-test", + help="Name of the test file to run, as if you were running `./mach test ARG`.", +) + + +# Fenix arguments to pass down +parser.add_argument( + "--prefix-ff", + help="Prefix name to pass to mozilla-central gradlew compilation to reduce the amount needing to build or test. For example: `geckoview`, `fenix`, `focus`.", + default="fenix", +) + + +args = parser.parse_args() +firefox_dir = args.firefox_dir +verbose = args.verbose if args.verbose else False +allow_clears = args.allow_clears +action = args.action + +local_firefox_ios = args.use_local_firefox_ios +remote_ios_repo_url = args.remote_ios_repo_url +ios_scheme = args.ios_scheme +ios_test_plan = args.ios_test_plan + +hnt_test = args.hnt_test + +prefix_ff = args.prefix_ff + +# Build against iOS +start_time_ios = time.time() +success_ios = build_against_ios( + local_firefox_ios, + remote_ios_repo_url, + ios_scheme, + ios_test_plan, + clear_previous_bindings=allow_clears, + clean_ios_caches=allow_clears, + verbose=verbose, + action=action, +) +time_diff_ios = time.time() - start_time_ios + +# Build against Fenix +start_time_fenix = time.time() +success_fenix = build_against_fenix( + firefox_dir, + None, + prefix_ff, + prefix_as=None, + clear_bindings=allow_clears, + verbose=verbose, + action=action, +) +time_diff_fenix = time.time() - start_time_fenix + +# Build against Desktop +start_time_hnt = time.time() +success_hnt = build_against_hnt(firefox_dir, None, True, hnt_test=hnt_test, verbose=verbose, action=action) +time_diff_hnt = time.time() - start_time_hnt + +did_tests_string = "" if action != "run-tests" else " (and tested)" +do_tests_string = "" if action != "run-tests" else " (and test)" +step_msg("Finished building. Results:") +if success_ios: + step_msg( + f"Successfully built{did_tests_string} against iOS (elapsed {time_diff_ios:.2f}s)" + ) +else: + err_msg( + f"Failed to build{do_tests_string} against iOS (elapsed {time_diff_ios:.2f}s)" + ) +if success_fenix: + step_msg( + f"Successfully built{did_tests_string} against Fenix (elapsed {time_diff_fenix:.2f}s)" + ) +else: + err_msg( + f"Failed to build{do_tests_string} against Fenix (elapsed {time_diff_fenix:.2f}s)" + ) +if success_hnt: + step_msg( + f"Successfully built{did_tests_string} against HNT (elapsed {time_diff_hnt:.2f}s)" + ) +else: + err_msg( + f"Failed to build{do_tests_string} against HNT (elapsed {time_diff_hnt:.2f}s)" + ) \ No newline at end of file diff --git a/automation/build_against_fenix.py b/automation/build_against_fenix.py new file mode 100755 index 0000000000..4d89ae2aba --- /dev/null +++ b/automation/build_against_fenix.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# Purpose: Run Firefox fenix tests against this application-services working tree. +# https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-fenix.md +# So, for now, we need to use an existing respository. +# +# Requirements: +# - python +# - application-services built and working. +# - a `firefox`/`mozilla-central` repository set up and working to use. +# - See: https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html +# +# Usage: ./automation/build_against_fenix.py --action build-without-testing --firefox-dir ../firefox --prefix-ff fenix --prefix-as ads-client --verbose +# +# Arguments: +# --action => Can be either `run-tests` (default) or `build-without-testing` +# --firefox-dir => Working mozilla-central directory +# https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html +# --mozconfig => Absolute path to the mozconfig file to be used. +# --prefix-ff => Prefix to be used in gradle commands to firefox repo. eg: `./gradlew fenix:assembleDebug`. For example: `geckoview`, `fenix`, `focus`." +# --prefix-as => Crate prefix to be used in gradle commands to application-services repo. eg: `./gradlew ads-client:assembleDebug`. For example: `ads-client`, `fxaclient`." +# --verbose => Includes the stdout of subprocesses (like the xcodebuild output, or other bootstrapping scripts) +# --clear-bindings => Whether or not to clear existing bindings and cached artifacts, such as with "./gradlew fenix:clean" +import argparse +import subprocess +import os +import tempfile +from pathlib import Path +import re +from shared import ( + find_app_services_root, + set_gradle_substitution_path, + step_msg, + err_msg, + run_cmd_is_successful, + dir_file_sanity_check, +) + +DEFAULT_MOZ_CONFIG_LOCATION = "mozconfig_android" +DEFAULT_MOZ_CONFIG = """ +ac_add_options --enable-project=mobile/android +""" +MOZILLA_FF_GRADLE_PROPERTIES_PATH = "gradle.properties" + + +# Replaces org.gradle.configuration-cache=true with a commented version. +def comment_gradle_cache_line(firefox_repo_path): + """ + Comments out the gradle cache line pursuant to step 2 here. + https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-fenix.md#pre-requisites + """ + + properties_file_path = Path(firefox_repo_path) / MOZILLA_FF_GRADLE_PROPERTIES_PATH + if not os.path.isfile(properties_file_path): + err_msg( + "Could not find an instance of `gradle.properties` to modify. Please ensure the `m-c`/`firefox` directory is lined up correctly." + ) + return False + + # Uses regex. Matches the binaryTarget listed and replaces it with the following string. + replace_with = """# org.gradle.configuration-cache=true""" + step_msg(f"Writing to gradle.properties:\n{replace_with}") + regex = re.compile(r"^#*\s*org\.gradle\.configuration-cache=true\s*$", re.MULTILINE) + + with open(properties_file_path, "r+") as f: + data = f.read() + # Regex string matches this tidbit. + package_file = regex.sub(replace_with, data) + f.seek(0) + f.write(package_file) + f.truncate() + + return True + + +def build_against_fenix( + firefox_dir, + moz_config_location, + prefix_ff, + prefix_as, + clear_bindings, + verbose, + action, +): + subprocess_stdout = None if verbose else subprocess.DEVNULL + subprocess_stderr = None if verbose else subprocess.DEVNULL + + if action is None: + action = "run-tests" + + firefox_repo_path = Path(firefox_dir) + tmp_dir_path = Path(tempfile.mkdtemp(suffix="-test-fenix")) + + app_services_path = find_app_services_root() + + step_msg("Checking for sanity of application-services repository...") + if not dir_file_sanity_check( + app_services_path, "application-services", ["megazords", "components"] + ): + return False + + prefix_as_string = f"{prefix_as}:" if prefix_as else "" + prefix_ff_string = f"{prefix_ff}:" if prefix_ff else "" + + # MOZCONFIG handling. + # Idea here is that mozconfig settings (primary indicator of how firefox is built) can't be passed + # without `configure`, which is not recommended. However, we can pass test fixture mozconfig files themselves as env variables. + if moz_config_location is None: + moz_config_location = os.path.abspath( + tmp_dir_path / DEFAULT_MOZ_CONFIG_LOCATION + ) + with open(moz_config_location, "w") as file: + file.write(DEFAULT_MOZ_CONFIG) + + if not os.path.isabs(moz_config_location): + err_msg( + f"`mozconfig` path passed: `{moz_config_location}` must be an absolute path." + ) + return False + if not os.path.isfile(moz_config_location): + err_msg(f"`mozconfig` path passed: `{moz_config_location}` could not be found.") + return False + step_msg(f"Using `mozconfig` path: `{moz_config_location}`. Displaying:") + with open(moz_config_location) as f: + print(f.read()) + + # Basic sanity check here. Not remotely exhaustive, just to make sure the wrong directory wasn't passed. + step_msg("Checking for sanity of firefox repository...") + if not dir_file_sanity_check( + firefox_repo_path, + "mozilla-central", + ["mach", "CLOBBER", "gradlew", "Cargo.toml", "local.properties"], + ): + return False + + # The following steps modify several property files in the m-c repository provided: + # Key step: gradle can use a different application-services directory + step_msg(f"Configuring {firefox_repo_path} to autopublish appservices") + if not set_gradle_substitution_path( + firefox_repo_path, + "autoPublish.application-services.dir", + find_app_services_root(), + ): + err_msg( + "Failed in attempting to set `local.properties` `autoPublish.application-services.dir`" + ) + return False + + # Comments out gradle cache in local.properties + step_msg(f"Configuring {firefox_repo_path} to disable gradle configuration-cache") + if not comment_gradle_cache_line(firefox_repo_path): + err_msg( + "Failed in attempting to set `gradle.properties` `#org.gradle.configuration-cache=true`" + ) + return False + + # Environment verification check + step_msg("Verifying Android environment...") + if not run_cmd_is_successful( + "./libs/verify-android-environment.sh", + cwd=app_services_path, + shell=True, + stdout=subprocess_stdout, + stderr=subprocess_stderr, + ): + err_msg( + "Failed to run `./libs/verify-android-environment.sh` in app-services environment. Run this script and follow any instructions given until it succeeds, then try again." + ) + return False + + # Gradle clean cached files + if clear_bindings: + step_msg( + "Cleaning application-services with gradle to clear cached android bindings..." + ) + if not run_cmd_is_successful( + f"./gradlew {prefix_as_string}clean", + cwd=app_services_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg( + "Could not run ./gradlew clean. Please check to ensure the mozilla-center folder structure is sound." + ) + return False + + # Run gradle compilations and tests + step_msg("Compiling application-services with gradle to test android bindings...") + if not run_cmd_is_successful( + f"./gradlew {prefix_as_string}assembleDebug", + cwd=app_services_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to compile application-services with gradle.") + return False + + step_msg( + f"Compiling firefox with mozconfig with `./gradlew {prefix_ff_string}assembleDebug` (mozconfig=`{moz_config_location}`)..." + ) + if not run_cmd_is_successful( + f"MOZCONFIG={moz_config_location} ./gradlew {prefix_ff_string}assembleDebug", + cwd=firefox_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to compile firefox with gradle.") + return False + + if action == "run-tests": + step_msg( + f"Compiling firefox with mozconfig with `./gradlew {prefix_ff_string}testDebug` (mozconfig=`{moz_config_location}`)..." + ) + if not run_cmd_is_successful( + f"MOZCONFIG={moz_config_location} ./gradlew {prefix_ff_string}testDebug", + cwd=firefox_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to run tests against firefox with gradle.") + return False + step_msg("Successfully built against Android!") + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run Firefox Android tests against this application-services working tree." + ) + + parser.add_argument( + "--verbose", + help="Display subprocess logs for compilation processes (off by default).", + action=argparse.BooleanOptionalAction, + ) + parser.add_argument( + "--action", + choices=["run-tests", "build-without-testing"], + help="Whether to run tests after the build step is complete..", + ) + parser.add_argument( + "--firefox-dir", + required=True, + help="Path to existing bootstrapped `mozilla-central` directory.", + ) + parser.add_argument( + "--mozconfig", + help="Absolute path to the desired mozconfig file. This affects the build destination, ensure it specifies android if you override it.", + ) + parser.add_argument( + "--prefix-ff", + help="Prefix name to pass to mozilla-central compilation to reduce the amount needing to build or test. For example: `geckoview`, `fenix`, `focus`.", + ) + parser.add_argument( + "--prefix-as", + help="Crate name to pass for preliminary application-services android building step. For example: `ads-client`, `fxaclient`", + ) + parser.add_argument( + "--clear-previous-bindings", + help="Clear existing uniffi binding files from the firefox android build folder. (`/gradlew clean`). This shares any prefixes supplied by `--prefix-as`. If unrelated files need to be cleared, do not pass a --prefix-as argument", + action=argparse.BooleanOptionalAction, + ) + + args = parser.parse_args() + firefox_dir = args.firefox_dir + verbose = args.verbose + moz_config_location = args.mozconfig + action = args.action + prefix_ff = args.prefix_ff + prefix_as = args.prefix_as + clear_bindings = args.clear_previous_bindings + build_against_fenix( + firefox_dir, + moz_config_location, + prefix_ff, + prefix_as, + clear_bindings, + verbose, + action, + ) diff --git a/automation/build_against_hnt.py b/automation/build_against_hnt.py new file mode 100755 index 0000000000..b579e7d41e --- /dev/null +++ b/automation/build_against_hnt.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# Purpose: Run Firefox fenix tests against this application-services working tree. +# https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-fenix.md +# So, for now, we need to use an existing respository. +# +# Requirements: +# - python +# - application-services built and working. +# - a `firefox`/`mozilla-central` repository set up and working to use. +# - See: https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html +# Usage: ./automation/build_against_hnt.py --action build-without-testing --firefox-dir ../firefox --verbose +# and arg to clean up only? +# Arguments: +# --action => Can be either `run-tests` (default) or `build-without-testing`, or `run` (which runs it locally with `./mach run`) +# --firefox-dir => Working mozilla-central directory +# https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html +# --mozconfig => Absolute path to the mozconfig file to be used. +# --verbose => Includes the stdout of subprocesses (like the xcodebuild output, or other bootstrapping scripts) +# --clean-up => Whether to perform the on-success cleanup step at the end of a successful build (default is True). This clean-up step happens either way on an error or graceful exit (such as with `--action run`). +# --hnt-test => Test name to run with `./mach test`. If `run-tests` is attached, but no `--test` is provided, the default command will be `./mach test --auto` where appropriate tests will be guessed. +import argparse +import subprocess +import os +import signal +import tempfile +import sys +from pathlib import Path +from shared import ( + find_app_services_root, + step_msg, + err_msg, + run_cmd_is_successful, + dir_file_sanity_check, +) + +DEFAULT_MOZ_CONFIG_LOCATION = "mozconfig_desktop" +DEFAULT_MOZ_CONFIG = """ +ac_add_options --enable-project=browser +""" +MOZILLA_FF_GRADLE_PROPERTIES_PATH = "gradle.properties" +COMPONENTS_FOLDER_AS_SUBPATH = "components" +COMPONENTS_FOLDER_MC_SUBPATH = "third_party/application-services/components" +COMPONENTS_FOLDER_MC_SUBPATH_TMP = "third_party/application-services/components_tmp" + + +# Catch sigint escape (for example, for long running tests) to still safely clean up the m-z directory +def safe_exit(firefox_repo_path): + step_msg("Exit signal caught, gracefully exiting...") + clean_up_func(firefox_repo_path) + step_msg("Exiting...") + sys.exit(0) + + +# Clean up symlinks/modified files that need to revert to their previous state +# Running this doesn't indicate something went wrong +def clean_up_func(firefox_repo_path): + symlink_src = firefox_repo_path / COMPONENTS_FOLDER_MC_SUBPATH + components_tmp_dir = firefox_repo_path / COMPONENTS_FOLDER_MC_SUBPATH_TMP + step_msg("Cleaning up, restoring symlinks...") + + # Test to see if we were interrupted/ended after making the symlink + try: + if os.path.islink(symlink_src) and os.path.isdir(components_tmp_dir): + os.unlink(symlink_src) + # if symlink does not exist (or no longer does) but components was moved, move it back + if os.path.isdir(components_tmp_dir): + os.rename(components_tmp_dir, symlink_src) + except OSError: + err_msg( + "Failed to restore the m-c state. Please remove the symlink and return the 'components' directory to it's intended spot." + ) + return False + return True + + +# External function handle cleanup on failure +def build_against_hnt( + firefox_dir, + moz_config_location, + clean_up, + hnt_test, + verbose, + action, +): + # Run cleanup at start + firefox_repo_path = Path(firefox_dir) + clean_up_func(firefox_repo_path) + + # Catch sigint for graceful exit + signal.signal(signal.SIGINT, lambda _s, _h: safe_exit(firefox_repo_path)) + step_msg("Registered sigint trap...") + + success = build_against_hnt_inner( + firefox_dir, + moz_config_location, + hnt_test, + verbose, + action, + ) + + if success: + step_msg("Finished running against HNT.") + else: + err_msg("Building against HNT failed.") + + if clean_up: + step_msg("Cleaning up...") + clean_up_func(firefox_repo_path) + else: + step_msg( + "Skipping cleanup step. Rerunning the command will cleanup before recompiling." + ) + + return success + +def build_against_hnt_inner( + firefox_dir, + moz_config_location, + hnt_test, + verbose, + action, +): + subprocess_stdout = None if verbose else subprocess.DEVNULL + subprocess_stderr = None if verbose else subprocess.DEVNULL + + if action is None: + action = "run-tests" + + firefox_repo_path = Path(firefox_dir) + tmp_dir_path = Path(tempfile.mkdtemp(suffix="-test-fenix")) + + app_services_path = find_app_services_root() + + step_msg("Checking for sanity of application-services repository...") + if not dir_file_sanity_check( + app_services_path, "application-services", ["megazords", "components"] + ): + return False + + # MOZCONFIG handling. + # Idea here is that mozconfig settings (primary indicator of how firefox is built) can't be passed + # without `configure`, which is not recommended. However, we can pass test fixture mozconfig files themselves as env variables. + if moz_config_location is None: + moz_config_location = os.path.abspath( + tmp_dir_path / DEFAULT_MOZ_CONFIG_LOCATION + ) + with open(moz_config_location, "w") as file: + file.write(DEFAULT_MOZ_CONFIG) + + if not os.path.isabs(moz_config_location): + err_msg( + f"`mozconfig` path passed: `{moz_config_location}` must be an absolute path." + ) + return False + if not os.path.isfile(moz_config_location): + err_msg(f"`mozconfig` path passed: `{moz_config_location}` could not be found.") + return False + step_msg(f"Using `mozconfig` path: `{moz_config_location}`. Displaying:") + with open(moz_config_location) as f: + print(f.read()) + + # Basic sanity check here. Not remotely exhaustive, just to make sure the wrong directory wasn't passed. + step_msg("Checking for sanity of firefox repository...") + if not dir_file_sanity_check( + firefox_repo_path, + "mozilla-central", + ["mach", "CLOBBER", "gradlew", "Cargo.toml", "local.properties"], + ): + return False + + # Environment verification check + step_msg("Verifying Desktop environment...") + if not run_cmd_is_successful( + "./libs/verify-desktop-environment.sh", + cwd=app_services_path, + shell=True, + stdout=subprocess_stdout, + stderr=subprocess_stderr, + ): + err_msg( + "Failed to run `./libs/verify-android-environment.sh` in app-services environment. Run this script and follow any instructions given until it succeeds, then try again." + ) + return False + + # The following steps *modify* a couple key parts of the m-c directory. + symlink_dest = app_services_path / COMPONENTS_FOLDER_AS_SUBPATH + symlink_src = firefox_repo_path / COMPONENTS_FOLDER_MC_SUBPATH + components_tmp_dir = firefox_repo_path / COMPONENTS_FOLDER_MC_SUBPATH_TMP + step_msg(f"Creating symlink in {firefox_repo_path} to link to local appservices") + + # First, move /components folder in m-c to a temporary backup. + os.rename(symlink_src, components_tmp_dir) + + # Then, create a symlink between the app-services/components and m-c/third_party/app-services folder. + os.symlink(symlink_dest, symlink_src) + + # We are pointing to a new area as if we vendored, so we regenerate. + step_msg("Regenerating uniffi bindings (mozconfig=`{moz_config_location}`)...") + if not run_cmd_is_successful( + "./mach uniffi generate", + cwd=firefox_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to generate uniffi bindings with: `./mach uniffi generate`/") + return False + + step_msg( + f"Compiling firefox with `./mach build` (mozconfig=`{moz_config_location}`)..." + ) + if not run_cmd_is_successful( + f"MOZCONFIG={moz_config_location} ./mach build", + cwd=firefox_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to compile firefox with `./mach build`.") + return False + + if action == "run-tests": + step_msg( + f"Compiling firefox with mozconfig with `./mach test` (mozconfig=`{moz_config_location}`)..." + ) + test_string = hnt_test if hnt_test is not None else "--auto" + step_msg(f"Running test command `./mach test {test_string}`") + if not run_cmd_is_successful( + f"MOZCONFIG={moz_config_location} ./mach test {test_string}", + cwd=firefox_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg(f"Failed to run tests against firefox with ./mach test {test_string}.") + return False + elif action == "run": + step_msg( + f"Running firefox with mozconfig with `./mach run` (mozconfig=`{moz_config_location}`)..." + ) + if not run_cmd_is_successful( + f"MOZCONFIG={moz_config_location} ./mach run", + cwd=firefox_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to run tests against firefox with ./mach run.") + return False + + step_msg("Successfully built against HNT!") + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run Firefox HNT tests against this application-services working tree." + ) + + parser.add_argument( + "--verbose", + help="Display subprocess logs for compilation processes (off by default).", + action=argparse.BooleanOptionalAction, + ) + parser.add_argument( + "--action", + choices=["run", "run-tests", "build-without-testing"], + help="Whether to run tests after the build step is complete..", + ) + parser.add_argument( + "--firefox-dir", + required=True, + help="Path to existing bootstrapped `mozilla-central` directory.", + ) + parser.add_argument( + "--mozconfig", + help="Absolute path to the desired mozconfig file. This affects the build destination, ensure it specifies android if you override it.", + ) + parser.add_argument( + "--hnt-test", + help="Name of the test file to run, as if you were running `./mach test ARG`.", + ) + + parser.add_argument( + "--clean-up", + help="Skip the on-success cleanup step done at the end of a successful build. This does not skip the cleanup step if there is an error or graceful exit (such as with `--action run`).", + action=argparse.BooleanOptionalAction, + default=True, + ) + + args = parser.parse_args() + firefox_dir = args.firefox_dir + verbose = args.verbose + moz_config_location = args.mozconfig + action = args.action + clean_up = args.clean_up + hnt_test = args.hnt_test + build_against_hnt(firefox_dir, moz_config_location, clean_up, hnt_test, verbose, action) diff --git a/automation/build_against_ios.py b/automation/build_against_ios.py new file mode 100755 index 0000000000..086ff5aa9f --- /dev/null +++ b/automation/build_against_ios.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# Purpose: Run Firefox-iOS tests against this application-services working tree. +# https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-firefox-ios.md +# Requirements: +# - python +# - application-services built and working. +# - xcpretty (`gem install xcpretty`) +# - xcode + xcodebuild + xcodetools setup and running (a successful build of the firefox-ios repository) +# +# Usage: ./automation/build_against_ios.py +# Arguments: +# --action => Can be either `run-tests` (default) or `build-without-testing` +# --remote-ios-repo-url => Fetch firefox-ios repository from this URL instead. Exclusive with `use-local-firefox-ios` +# --use-local-firefox-ios => Use a local firefox-ios repository instead (at the provided path). Exclusive with `remote-ios-repo-url`. +# --verbose => Includes the stdout of subprocesses (like the xcodebuild output, or other bootstrapping scripts) +# --clear-previous-bindings => Clear existing uniffi binding swift files from both the iOS and A-S generated folders. Use if files were created that need to be cleared (eg: a file of a name that is no longer used). +# --clean-ios-caches => Runs the code equivalent of Xcode's 'Clean Build Folder' +# --ios-scheme => The XCode scheme to build with, such as 'Fennec' or 'Firefox' +# --ios-test-plan => The XCode test plan to run tests with (if action is `run-tests`), such as 'Smoketest' or 'FullFunctionalTestPlan' +# +import argparse +import subprocess +import os +import tempfile +from pathlib import Path +import shutil +import glob +import re +from shared import ( + fatal_err, + find_app_services_root, + run_cmd_checked, + step_msg, + err_msg, + run_cmd_is_successful, + dir_file_sanity_check, +) + +DEFAULT_REMOTE_REPO_URL = "https://github.com/mozilla-mobile/firefox-ios.git" +MOZILLA_RUST_COMPONENTS_IOS_PATH = "MozillaRustComponents/Package.swift" +MOZILLA_RUST_COMPONENTS_AS_PATH = "megazords/ios-rust/MozillaRustComponents.xcframework" + + +def replace_swift_package_artifact(ios_repo_path, as_repo_path): + """ + Replaces the local artifact pursuant to step 2 here. + https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-firefox-ios.md#step-2--point-firefox-ios-to-your-local-artifact + """ + + package_file_path = Path(ios_repo_path) / MOZILLA_RUST_COMPONENTS_IOS_PATH + xcframework_file_path = Path(as_repo_path) / MOZILLA_RUST_COMPONENTS_AS_PATH + + if not os.path.isfile(package_file_path): + err_msg( + "Could not find an instance of `MozillaRustComponents/Package.swift` to modify. Please ensure the iOS directory is lined up correctly." + ) + return False + if not os.path.isdir(xcframework_file_path): + err_msg( + f"Could not find an instance of `MozillaRustComponents.xcframework` to link at {xcframework_file_path}. Please ensure the a-s directory is lined up correctly." + ) + return False + xcframework_file_path_relative = os.path.relpath( + xcframework_file_path, Path(package_file_path.parent) + ) + + # Uses regex. Matches the binaryTarget listed and replaces it with the following string. + replace_with = f""" + binaryTarget( + name: "MozillaRustComponents", + path: "{xcframework_file_path_relative}" + ) + """ + step_msg(f"Writing to MozillaRustComponents/Package.swift:\n{replace_with}") + regex = re.compile( + r"binaryTarget\([\n\s]*name\: \"MozillaRustComponents\",[\S\n\t\v ]*MozillaRustComponents\.xcframework\"[\n\s]*\)" + ) + + with open(package_file_path, "r+") as f: + data = f.read() + # Regex string matches this tidbit. + package_file = regex.sub(replace_with, data) + f.seek(0) + f.write(package_file) + f.truncate() + + return True + + +def build_against_ios( + local_ios_repo_path, + remote_ios_repo_url, + scheme, + test_plan, + clear_previous_bindings, + clean_ios_caches, + verbose, + action, +): + subprocess_stdout = None if verbose else subprocess.DEVNULL + subprocess_stderr = None if verbose else subprocess.DEVNULL + + if action is None: + action = "run-tests" + + ios_repo_path = local_ios_repo_path + app_services_path = find_app_services_root() + + # Naive sanity check here. Not remotely exhaustive, just to make sure some extremely incorrect directory wasn't passed. + step_msg("Checking for sanity of application-services repository...") + if not dir_file_sanity_check( + app_services_path, "application-services", ["megazords", "components"] + ): + return False + + step_msg("Checking for existence of xcodebuild...") + if not run_cmd_is_successful("xcodebuild -version", cwd=ios_repo_path, shell=True): + err_msg( + "xcodebuild is required to compile application-services for iOS. Please clone the firefox-ios repository and follow the instructions therein." + ) + return False + + # Creating temp directory and cloning repository + step_msg(f"Building application-services against iOS with action: `{action}`") + if local_ios_repo_path is None: + ios_repo_path = tempfile.mkdtemp(suffix="-test-ios") + if remote_ios_repo_url is None: + remote_ios_repo_url = DEFAULT_REMOTE_REPO_URL + step_msg(f"Cloning {remote_ios_repo_url}") + run_cmd_checked(["git", "clone", remote_ios_repo_url, ios_repo_path]) + + ios_generated_uniffi_files_path = f"{ios_repo_path}/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated" + local_repo_generated_uniffi_files_path = f"{app_services_path}/megazords/ios-rust/Sources/MozillaRustComponentsWrapper/Generated" + + # Bootstrapping the iOS repository + step_msg("Running the firefox-ios bootstrap script...") + if not run_cmd_is_successful( + "./bootstrap.sh", cwd=ios_repo_path, shell=True, stdout=subprocess_stdout + ): + err_msg( + "Failed to bootstrap firefox-ios repository. Please clone the firefox-ios repository and follow the instructions therein." + ) + return False + + # Verification check + step_msg("Verifying iOS environment...") + if not run_cmd_is_successful( + "./libs/verify-ios-environment.sh", + cwd=app_services_path, + shell=True, + stdout=subprocess_stdout, + stderr=subprocess_stderr, + ): + err_msg( + "Failed to verify environment for iOS. Please run `./libs/verify-ios-environment.sh`, making suggested changes until it succeeds." + ) + return False + + # Uniffi sanity check + if not os.path.isdir(ios_generated_uniffi_files_path): + err_msg( + f"Expected path `{ios_generated_uniffi_files_path}` in firefox-ios is missing. Please confirm the repository structure or try cloning it again." + ) + return False + + if clear_previous_bindings: + step_msg("'clear-previous-bindings' is set, clearing uniffi folders") + + # Clear the uniffi bindings in the A-S repository as extra files created are not deleted + # Not relevant to tmp repository + if os.path.isdir(local_repo_generated_uniffi_files_path): + for p in Path(local_repo_generated_uniffi_files_path).glob("*.swift"): + p.unlink() + + # Clear equivalents from the ios repository + # (Relevant if we are using an existing directory) + if not run_cmd_is_successful( + ["git", "checkout", "."], cwd=ios_generated_uniffi_files_path + ): + fatal_err( + "Found an error running git commands to clear previous uniffi folders. Exiting." + ) + if not run_cmd_is_successful( + ["git", "clean", "-f"], cwd=ios_generated_uniffi_files_path + ): + fatal_err( + "Found an error running git commands to clear previous uniffi folders. Exiting." + ) + + # Build artifacts + # Unfortunately build_ios_artifacts is writing to stderr, so we need to hide it when it's not verbose. + step_msg("Building application-services iOS artifacts...") + if not run_cmd_is_successful( + "./automation/build_ios_artifacts.sh", + cwd=app_services_path, + shell=True, + check=True, + stdout=subprocess_stdout, + stderr=subprocess_stderr, + ): + err_msg( + "Failed to build ios artifacts. Please ensure the code compiles and try running `./automation/build_ios_artifacts.sh` from the folder." + ) + return False + + # Replace with regex some data in the swift package file + # https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-firefox-ios.md#step-2--point-firefox-ios-to-your-local-artifact + if not replace_swift_package_artifact(ios_repo_path, app_services_path): + err_msg("Failed to point firefox-ios to local a-s xcframework artifact") + return False + + if not os.path.isdir(local_repo_generated_uniffi_files_path): + err_msg( + f"Expected path `{local_repo_generated_uniffi_files_path}` in application-services is missing after building. Please confirm the repository structure or try cloning it again." + ) + return False + + # Copies folder of uniffi bindings. + step_msg("Copying uniffi bindings from:") + step_msg( + f"{local_repo_generated_uniffi_files_path} -> {ios_generated_uniffi_files_path}" + ) + for file in glob.glob("*.swift", root_dir=local_repo_generated_uniffi_files_path): + shutil.copy( + f"{local_repo_generated_uniffi_files_path}/{file}", + ios_generated_uniffi_files_path, + ) + + # Remove the glean_sym file. + # https://github.com/mozilla/application-services/blob/main/docs/howtos/locally-published-components-in-firefox-ios.md + step_msg(f"Removing: {ios_generated_uniffi_files_path}/glean_sym.swift") + os.remove(f"{ios_generated_uniffi_files_path}/glean_sym.swift") + + # Clean packages in xcodebuild + scheme = "Fennec" if scheme is None else scheme + test_plan = "Smoketest" if test_plan is None else test_plan + if clean_ios_caches: + # TODO: "Reset package caches" part not done yet. Currently not a great script way to do it seemingly other + # than deleting ~/Library files, so needs examination for a code or xcodebuild based solution. + + # Clean build folder + step_msg("Cleaning build folder...") + if not run_cmd_is_successful( + f"""\ + set -o pipefail && \ + xcodebuild \ + -workspace ./firefox-ios/Client.xcodeproj/project.xcworkspace \ + -scheme {scheme} \ + clean | \ + xcpretty + """, + cwd=ios_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg( + "Failed to clean and compile tests on iOS", + ) + return False + + # Run the build action + if action == "build-without-testing": + step_msg("Running xcodebuild without testing (this may take a few minutes)...") + if not run_cmd_is_successful( + f"""\ + set -o pipefail && \ + xcodebuild \ + -workspace ./firefox-ios/Client.xcodeproj/project.xcworkspace \ + -scheme {scheme} \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ + build-for-testing | \ + xcpretty + """, + cwd=ios_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg( + "Failed to compile and run tests on iOS", + ) + return False + elif action == "run-tests": + step_msg( + "Building firefox-ios and running tests (this may take a few minutes)..." + ) + if not run_cmd_is_successful( + f"""\ + set -o pipefail && \ + xcodebuild \ + -workspace ./firefox-ios/Client.xcodeproj/project.xcworkspace \ + -scheme {scheme} \ + -destination 'platform=iOS Simulator,name=iPhone 17' \ + -testPlan {test_plan} \ + test | \ + xcpretty + """, + cwd=ios_repo_path, + shell=True, + stdout=subprocess_stdout, + ): + err_msg("Failed to compile and run tests on iOS") + return False + + else: + err_msg( + "You must either run `--action run-tests` or `--action build-without-testing` " + ) + return False + + step_msg("Successfully built against iOS!") + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run Firefox-iOS tests against this application-services working tree." + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--use-local-firefox-ios", + metavar="LOCAL_IOS_REPO_PATH", + help="Use a local copy of firefox-ios instead of cloning it. Exclusive with `remote-ios-repo-url`", + ) + group.add_argument( + "--remote-ios-repo-url", + metavar="REMOTE_REPO_PATH", + help="Clone a different firefox-ios repository. Exclusive with `use-local-firefox-ios`", + ) + + parser.add_argument( + "--verbose", + help="Display subprocess logs for compilation processes (off by default).", + action=argparse.BooleanOptionalAction, + ) + parser.add_argument( + "--clear-previous-bindings", + help="Clear existing uniffi binding swift files from both the iOS and A-S generated folders. Use if files were created that need to be cleared (eg: a file of a name that is no longer used).", + action=argparse.BooleanOptionalAction, + ) + + parser.add_argument( + "--clean-ios-caches", + help="Run Xcode 'Clean Build Folder'", + action=argparse.BooleanOptionalAction, + ) + + parser.add_argument( + "--ios-scheme", + help="The scheme to run. Likely: `Fennec` (default) or `Firefox`", + default="Fennec", + ) + + parser.add_argument( + "--ios-test-plan", + help="The test plan to test with. Likely: `Smoketest` (default) or `FullFunctionalTestPlan`", + default="Smoketest", + ) + + parser.add_argument( + "--action", + choices=["run-tests", "build-without-testing"], + help="Run the following action once firefox-ios is set up.", + ) + + args = parser.parse_args() + local_ios_repo_path = args.use_local_firefox_ios + remote_ios_repo_url = args.remote_ios_repo_url + clear_previous_bindings = args.clear_previous_bindings + clean_ios_caches = args.clean_ios_caches + scheme = args.ios_scheme + test_plan = args.ios_test_plan + verbose = args.verbose + action = args.action + + build_against_ios( + local_ios_repo_path, + remote_ios_repo_url, + scheme, + test_plan, + clear_previous_bindings, + clean_ios_caches, + verbose, + action, + ) diff --git a/automation/shared.py b/automation/shared.py index 3a490e7cb2..11d6a62994 100644 --- a/automation/shared.py +++ b/automation/shared.py @@ -15,15 +15,21 @@ def step_msg(msg): def fatal_err(msg): - print(f"\033[31mError: {msg}\033[0m") + err_msg(msg) sys.exit(1) +def err_msg(msg): + print(f"\033[31mError: {msg}\033[0m") def run_cmd_checked(*args, **kwargs): """Run a command, throwing an exception if it exits with non-zero status.""" kwargs["check"] = True return subprocess.run(*args, **kwargs) # noqa: PLW1510 +def run_cmd_is_successful(*args, **kwargs): + """Run a subprocess command, returning False if it exits with non-zero status (True otherwise).""" + return subprocess.run(*args, **kwargs).returncode == 0 + def check_output(*args, **kwargs): """Run a command, throwing an exception if it exits with non-zero status.""" @@ -44,6 +50,18 @@ def find_app_services_root(): return cur_dir.absolute() +def dir_file_sanity_check(directory_path, dir_name, example_file_names): + """ + Extremely rudimentary and naive check for a few basic files in a directory, for better handling if the wrong directory is passed. + """ + for example_file in example_file_names: + new_path = Path(directory_path) / example_file + if not os.path.isfile(new_path) and not os.path.isdir(new_path): + err_msg(f"`{example_file}` is missing in root of `{dir_name}` directory. Please confirm this is a valid local copy of `{dir_name}` at: {directory_path}") + return False + return True + + def get_moz_remote(): """ Get the name of the remote for the official mozilla application-services repo @@ -69,6 +87,8 @@ def set_gradle_substitution_path(project_dir, name, value): If the named property already exists with the correct value then it will silently succeed; if the named property already exists with a different value then it will noisily fail. + + Returns False on such a failure, otherwise returns True. """ project_dir = Path(project_dir).resolve() properties_file = project_dir / "local.properties" @@ -86,7 +106,9 @@ def set_gradle_substitution_path(project_dir, name, value): fatal_err( f"Conflicting property {name}={cur_value} (not {abs_value})" ) - return + return False + else: + return True # The file does not contain the required property, append it. # Note that the project probably expects a path relative to the project root. ancestor = Path(os.path.commonpath([project_dir, abs_value])) @@ -98,6 +120,7 @@ def set_gradle_substitution_path(project_dir, name, value): step_msg(f"Setting relative path from {project_dir} to {abs_value} as {relpath}") with properties_file.open("a") as f: f.write(f"{name}={relpath}\n") + return True class RefNames: diff --git a/automation/smoke-test-fenix.py b/automation/smoke-test-fenix.py index c3afffb76e..55ea1f123b 100755 --- a/automation/smoke-test-fenix.py +++ b/automation/smoke-test-fenix.py @@ -3,7 +3,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. - +# DEPRECATED. Please consider using ./build_against_fenix.py # Purpose: Run Fenix tests against this application-services working tree. # Usage: ./automation/smoke-test-fenix.py diff --git a/automation/smoke-test-fxios.py b/automation/smoke-test-fxios.py index eb8df81183..246266a916 100755 --- a/automation/smoke-test-fxios.py +++ b/automation/smoke-test-fxios.py @@ -3,6 +3,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. +# DEPRECATED. Please consider using ./build_against_ios.py # Purpose: Run Firefox-iOS tests against this application-services working tree. # Usage: ./automation/smoke-test-fxios.py diff --git a/docs/howtos/locally-published-components-in-firefox-hnt.md b/docs/howtos/locally-published-components-in-firefox-hnt.md new file mode 100644 index 0000000000..1a22df7bcf --- /dev/null +++ b/docs/howtos/locally-published-components-in-firefox-hnt.md @@ -0,0 +1,88 @@ +# How to locally test application-services components on HNT / Desktop + +> This guide explains how to build and test **HNT against a local Application Services** checkout. + +--- + +## At a glance + +**Goal:** Build a local Firefox Desktop against a local Application Services. + +**Current workflow (recommended):** + +1. Verify the local build of `A-S` is ready for a desktop build. +2. Move the A-S components folder in M-C/Firefox to a temporary rename. +3. Create a symlink between the A-S and M-C components folders. +4. Generate uniffi bindings. +5. Run and build. +6. Cleanup of symlink and temporary renames. + +--- + +## Prerequisites + +1. Ensure you have a regular [build of application-services working](../building.md). +2. Ensure you have a regular [build of firefox from mozilla-central](https://firefox-source-docs.mozilla.org/setup/index.html#for-firefox-desktop) testable with `./mach build` and `./mach run`. + +--- + +## Step 1 — Verify the local build of A-S is ready for a desktop build + +From the root of your `application-services` checkout, execute: + +```bash +./libs/verify-desktop-environment.sh +``` + +This will check for environment variables. If it provides any instruction on environment variables to set, follow the instructions until it passes. + +## Step 2 - Move the A-S components folder in M-C/Firefox to a temporary rename + +We will be temporarily replacing the components in the `application-services` repository in `mozilla-central` with a symlink that points to our local `application-services` build. To conserve the old folder, we temporarily rename it. From the **mozilla-central** root. + +```bash +mv third_party/application-services/components third_party/application-services/components-tmp +``` + +## Step 3 - Create a symlink between the A-S and M-C components folders. + +Now, the former `components` path should have a symlink to the local `application-services` components. Assuming `application-services` is in the same folder as your `mozilla-central` checkout, you can run (from the **mozilla-central** root): + +```bash +ln -s $(realpath ../application-services/components) third_party/application-services/components +``` + +## Step 4 - Generate uniffi bindings. + +You may need to regenerate uniffi bindings, as if you vendored new `A-S` code. From the mozilla-central root: + +```bash +./mach uniffi generate +``` + +## Step 5 - Run and build! + +Now that `components` will read from your local build, you can build, run, and test. From your local m-c checkout, run: + +```bash +./mach build +``` + +And if so desired: + +```bash +./mach run +``` + +## Step 6 - Cleanup + +After completing your tests, you should revert your files and symlinks to ensure `m-c` continues to behave as expected: + +```bash +unlink third_party/application-services/components +mv third_party/application-services/components-tmp third_party/application-services/components +``` + +## Automated testing + +You can also automate this process by running the Desktop smoke test found at `automation/build_against_hnt.py`. You can see more detailed instructions about this [at the smoke testing guide](./smoke-testing-app-services.md). diff --git a/docs/howtos/smoke-testing-app-services.md b/docs/howtos/smoke-testing-app-services.md index a2c0a8f9dd..92ffe07e8c 100644 --- a/docs/howtos/smoke-testing-app-services.md +++ b/docs/howtos/smoke-testing-app-services.md @@ -7,17 +7,70 @@ The testing can be done manually using substitution scripts, but we also have sc Run `pip3 install -r automation/requirements.txt` to install the required Python packages. -## Android Components +## Usage + +You can easily run a smoke test against iOS and Fenix by running the following: + +`./automation/build_against_all.py --firefox-dir ../firefox --action build-without-testing --allow-clears` + +- In this case, `firefox-dir` must point to a bootstrapped and working installation of `mozilla-central` ([see instructions here](https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html)). It is used for the compilation and test of the Android and HNT builds. + +- The `--action` argument can be either `build-without-testing` or `run-tests`. + +- The `--allow-clears` argument allows the various subscripts to clear their various caches as appropriate (such as XCode cleaning iOS caches). + +You can also run against specific platforms directly with the following examples: + +- iOS: + + ```./automation/build_against_ios.py --action build-without-testing --clear-previous-bindings --clean-ios-caches --use-local-firefox-ios ../firefox-ios``` + + - By default, this script creates a `tmp` directory for `firefox-ios` and uses it. If you have a running `firefox-ios`, you can add the optional argument `--use-local-firefox-ios ../firefox-ios` (as shown), which may result in speed gains and the ability to run it on XCode immediately after a failure. This can also be passed to `./build_against_all.py`. + + - You can also customize the run scheme (`--ios-scheme`) or test plan (`--ios-test-plan`). Available schemes include `Fennec` (default) and `Firefox`. Available test plans include: `Smoketest`, `FullFunctionalTestPlan` and `UnitTest`. + + - A full list of schemes and their corresponding test plans can be found [in the firefox-ios](https://github.com/mozilla-mobile/firefox-ios/tree/main/firefox-ios/Client.xcodeproj/xcshareddata/xcschemes) respository. + + - Both of these arguments can be used in `./build_against_all.py`, and they will be passed to the underlying `build_against_ios.py` call. + +- Fenix: + + ```./automation/build_against_fenix.py --action build-without-testing --firefox-dir ../firefox --prefix-ff fenix --clear-previous-bindings``` + + - The `--prefix-ff` argument here refers to the prefix passed to commands like `./gradlew fenix:assembleDebug`. It can be omitted, but may cause failures on non-fenix projects. + + - The `--clear-previous-bindings` argument here runs a `./gradlew prefix:clear` before recompiling. It is not always necessary, and can be excluded for some speed gains, but can result in some cache reuse. + +- HNT / Desktop: + + ```./automation/build_against_hnt.py --action build-without-testing --firefox-dir ../firefox``` + + - This script uses the symlink method described [at the local A-S against HNT tutorial](./locally-published-components-in-firefox-hnt.md), cleaning up after a successful run. You can avoid this cleanup process (specifically on a successful run) by passing `--no-clean-up`, which will keep the symlinks. For example, you might run with `--action build-without-testing --no-clean-up` to experiment after with `./mach run`. + + - Unlike the other tests, HNT has the additional `action` variant of `--action run`, because it can be run from the terminal directly. + + - You can pass `--hnt-test` (eg: `--hnt-test dom/notification`) to pass a set of tests to use if the `action` argument is `run-tests`. Otherwise, `./mach test --auto` will be used, which takes a guess at which tests would be best to run. You can pass `--hnt-test` to the `build_against_all.py` script as well. + +All test scripts also accept the `--verbose` argument to show the output of run subprocesses (such as `./mach build`). + +## Limitations + +Note that these tests are primarily smoke tests against the building and compilation of application-services. There are a wide array of possible regressions that can only be caught with tests, including ones that crash the build immediately on running. To ensure any regressions for your component are caught, tests should be created for them rather than just building. + +## Deprecated tests + +### Android Components The `automation/smoke-test-android-components.py` script will clone (or use a local version) of android-components and run a subset of its tests against the current `application-services` worktree. It tries to only run tests that might be relevant to `application-services` functionality. -## Fenix +### Fenix The `automation/smoke-test-fenix.py` script will clone (or use a local version) of Fenix and run tests against the current `application-services` worktree. -## Firefox iOS +### Firefox iOS + The `automation/smoke-test-fxios.py` script will clone (or use a local version) of Firefox iOS and run tests against the current `application-services` worktree.