#!/usr/bin/env python3 """
Run application-services tests
This script provides an interface to run our test/linting/formating scripts
The first argument specifies the operating mode. This is either a particular
type of test (rust-tests, swift-lint, etc.), or it's "changes" which is for devs
to test their changes.
Changes Mode:
- Runs tests/clippy against the changes in your branch as determined by:
- `git merge-base` to find the last commit from the main branch
- `git diff` to find changed files from that commit
- Prioritizes executing the tests in a reasonable amount of time.
- Runs linting and formatting (clippy, rustfmt, etc).
- Lets rustfmt fix any issues it finds. It's recommended to
add or commit code before running the tests so that you can inspect any
changes with git diff.
import argparse import json import os import platform import shlex import subprocess import sys import traceback from enum import Enum from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent
AUTOMATION_DIR = PROJECT_ROOT / "automation"
COMPONENTS_DIR = PROJECT_ROOT / "components"
GRADLE = PROJECT_ROOT / "gradlew" # Ensure this is a proper path, so we can execute it without searching $PATH.
GRADLE = GRADLE.resolve()
IGNORE_PATHS = set(
[ # let's not run tests just for dependency changes "megazords/full/DEPENDENCIES.md", "megazords/full/android/dependency-licenses.xml", "megazords/ios-rust/DEPENDENCIES.md",
]
)
def blue_text(text): ifnot sys.stdout.isatty(): return text return f"\033[96m{text}\033[0m"
def yellow_text(text): ifnot sys.stdout.isatty(): return text return f"\033[93m{text}\033[0m"
def run_command(cmdline, **kwargs):
print(yellow_text(" ".join(shlex.quote(str(part)) for part in cmdline)))
subprocess.check_call(cmdline, **kwargs)
def path_is_relative_to(path, other): """
Implementation of Path.is_relative_to() which was only added in python 3.9 """ return str(path.resolve()).startswith(str(other.resolve()))
def should_run_rust_tests(package, min_version): # There are no tests in examples/ packages, so don't waste time on them. if"examples"in package.manifest_path.parts: returnFalse # Skip the error support trybuild tests when running the min version tests, since we keep the # trybuild output files pinned to the latest rust version. if min_version and package.name == "error-support-tests": returnFalse returnTrue
class BranchChanges: """
Tracks which files have been changed in this branch """
def __init__(self, base_branch): # Calculate the merge base, this is the last commit from the base # branch that's present in this branch.
self.merge_base = get_output(
[ "git", "merge-base", "HEAD",
base_branch,
]
).strip() # Use the merge base to calculate which files have changed from the # base branch.
raw_paths = get_output(
[ "git", "diff", "--name-only",
self.merge_base,
]
).split("\n")
raw_paths = [p for p in raw_paths if p notin IGNORE_PATHS]
self.paths = [PROJECT_ROOT.joinpath(p) for p in raw_paths]
@staticmethod def has_unstanged_changes():
output = get_output(["git", "status", "--porcelain=v1"]) return any(line and line[1] == "M"for line in output.split("\n"))
class RustPackage: """
A rust package that we want to test and run clippy on """
def __init__(self, cargo_metadata):
self.cargo_metadata = cargo_metadata
self.name = cargo_metadata["name"] # Use manifest path to select the package in cargo. This works better # when using the --no-default-features flag
self.manifest_path = Path(cargo_metadata["manifest_path"])
self.directory = self.manifest_path.parent
def has_changes(self, branch_changes): return any(path_is_relative_to(p, self.directory) for p in branch_changes.paths)
class RustFeatures(Enum): """
A set of features to for testing/clippy
Rust tests and clippy can be affected by which features are enabled.
Therefore we want to use different combinations of features when running
tests/clippy. """
DEFAULT = "default features"
ALL = "all features" NONE = "no features"
def calc_rust_items(branch_changes=None, default_features_only=False): """
Calculate which items we want to test and run clippy on
Args:
branch_changes: only yield items for rust packages that have changes
default_features_only: only test with the default features
Returns: list of (RustPackage, RustFeatures) items """
json_data = json.loads(
get_output(
[ "cargo", "metadata", "--no-deps", "--format-version", "1",
]
)
)
packages = [RustPackage(p) for p in json_data["packages"]]
if branch_changes:
packages = [p for p in packages if p.has_changes(branch_changes)]
for p in packages: yield p, RustFeatures.DEFAULT
if default_features_only: return
for p in packages: if p.has_features(): yield p, RustFeatures.ALL for p in packages: if p.has_default_features(): yield p, RustFeatures.NONE
def calc_non_workspace_rust_items(branch_changes=None, default_features_only=False): """
Calculate which items are notin our default workspace, but we might want to
do certain things with.
Returns the same as calc_rust_items """ for path in ["testing/sync-test/Cargo.toml"]:
json_data = json.loads(
get_output(
[ "cargo", "metadata", "--no-deps", "--format-version", "1", "--manifest-path",
path,
]
)
)
packages = [RustPackage(p) for p in json_data["packages"]]
if branch_changes:
packages = [p for p in packages if p.has_changes(branch_changes)]
for p in packages: yield p, RustFeatures.DEFAULT
if default_features_only: return
for p in packages: if p.has_features(): yield p, RustFeatures.ALL for p in packages: if p.has_default_features(): yield p, RustFeatures.NONE
def rust_test_steps(items):
default_features = [package for (package, features) in items if features == RustFeatures.DEFAULT]
all_features = [package for (package, features) in items if features == RustFeatures.ALL]
no_features = [package for (package, features) in items if features == RustFeatures.NONE]
if default_features: yield Step(
f"tests ({RustFeatures.DEFAULT.label()})",
run_rust_tests,
default_features,
RustFeatures.DEFAULT,
) if all_features: yield Step(
f"tests ({RustFeatures.ALL.label()})",
run_rust_tests,
all_features,
RustFeatures.ALL,
) if no_features: yield Step(
f"tests ({RustFeatures.NONE.label()})",
run_rust_tests,
no_features,
RustFeatures.NONE,
)
def rust_clippy_steps(items):
default_features = [package for (package, features) in items if features == RustFeatures.DEFAULT]
all_features = [package for (package, features) in items if features == RustFeatures.ALL]
no_features = [package for (package, features) in items if features == RustFeatures.NONE]
if default_features: yield Step(
f"clippy ({RustFeatures.DEFAULT.label()})",
run_clippy,
default_features,
RustFeatures.DEFAULT,
) if all_features: yield Step(
f"clippy ({RustFeatures.ALL.label()})",
run_clippy,
all_features,
RustFeatures.ALL,
) if no_features: yield Step(
f"clippy ({RustFeatures.NONE.label()})",
run_clippy,
no_features,
RustFeatures.NONE,
)
def check_for_fmt_changes(branch_changes):
print() if branch_changes.has_unstanged_changes():
print("cargo fmt made changes. Make sure to check and commit them.") else:
print("All checks passed!")
class Step: """
Represents a single step of the testing process """
def calc_steps(args): """
Calculate the steps needed to run the tests
Yields a list of (name, func) items. """ if args.mode == "changes": # changes mode is complicated enough that it's split off into its own # function yieldfrom calc_steps_change_mode(args) elif args.mode == "rust-tests":
print_rust_environment()
items = [
(package, features) for package, features in calc_rust_items() if should_run_rust_tests(package, False)
] for step in rust_test_steps(items): yield step elif args.mode == "rust-min-version-tests":
print_rust_environment()
items = [
(package, features) for package, features in calc_rust_items() if should_run_rust_tests(package, True)
] for step in rust_test_steps(items): yield step elif args.mode == "rust-clippy":
print_rust_environment() for step in rust_clippy_steps(calc_rust_items()): yield step # non-workspace items aren't tested, but we do run clippy on them to # make sure they don't go stale. for step in rust_clippy_steps(calc_non_workspace_rust_items()): yield step elif args.mode == "rust-fmt":
print_rust_environment() yield Step("cargo fmt", cargo_fmt) elif args.mode == "ktlint": yield Step("ktlint", run_ktlint) elif args.mode == "gradle": yield Step("gradle tests", run_gradle_tests) elif args.mode == "ios-artifacts": yield Step("ios artifacts", build_ios_artifacts) elif args.mode == "python-tests": yield Step("python tests", run_python_tests) else:
print(f"Invalid mode: {args.mode}")
sys.exit(1)
def calc_steps_change_mode(args): """
Calculate the steps needed for change mode """
print_rust_environment()
branch_changes = BranchChanges(args.base_branch)
rust_items = list(calc_rust_items(branch_changes, default_features_only=True))
rust_packages = list(set(package for package, _ in rust_items))
if branch_changes.has_unstanged_changes():
subprocess.run(["git", "status"], check=False)
print()
print("WARNING: unstaged changes in your branch:")
print( "Consider git add or git commit to stage them since this " "script will run cargo fmt"
)
print("Continue (Y/N)?") if input().lower() != "y":
sys.exit(0)
for step in rust_test_steps(rust_items): yield step for step in rust_clippy_steps(rust_items): yield step for package in rust_packages: yield Step(f"rustfmt for {package.name}", cargo_fmt, package, fix_issues=True) yield Step("Check for changes", check_for_fmt_changes, branch_changes)
def main():
args = parse_args()
os.chdir(PROJECT_ROOT) for step in calc_steps(args):
step.run()
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.