1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2023 The ChromiumOS Authors 3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file. 5*bb4ee6a4SAndroid Build Coastguard Worker 6*bb4ee6a4SAndroid Build Coastguard Workerimport argparse 7*bb4ee6a4SAndroid Build Coastguard Workerimport json 8*bb4ee6a4SAndroid Build Coastguard Workerfrom multiprocessing.pool import ThreadPool 9*bb4ee6a4SAndroid Build Coastguard Workerimport shlex 10*bb4ee6a4SAndroid Build Coastguard Workerimport shutil 11*bb4ee6a4SAndroid Build Coastguard Workerfrom fnmatch import fnmatch 12*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path 13*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import Any, List, Tuple 14*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.common import ( 15*bb4ee6a4SAndroid Build Coastguard Worker CROSVM_ROOT, 16*bb4ee6a4SAndroid Build Coastguard Worker all_tracked_files, 17*bb4ee6a4SAndroid Build Coastguard Worker chdir, 18*bb4ee6a4SAndroid Build Coastguard Worker cmd, 19*bb4ee6a4SAndroid Build Coastguard Worker cwd_context, 20*bb4ee6a4SAndroid Build Coastguard Worker parallel, 21*bb4ee6a4SAndroid Build Coastguard Worker print_timing_info, 22*bb4ee6a4SAndroid Build Coastguard Worker quoted, 23*bb4ee6a4SAndroid Build Coastguard Worker record_time, 24*bb4ee6a4SAndroid Build Coastguard Worker) 25*bb4ee6a4SAndroid Build Coastguard Worker 26*bb4ee6a4SAndroid Build Coastguard Worker# List of globs matching files in the source tree required by tests at runtime. 27*bb4ee6a4SAndroid Build Coastguard Worker# This is hard-coded specifically for crosvm tests. 28*bb4ee6a4SAndroid Build Coastguard WorkerTEST_DATA_FILES = [ 29*bb4ee6a4SAndroid Build Coastguard Worker # Requried by nextest to obtain metadata 30*bb4ee6a4SAndroid Build Coastguard Worker "*.toml", 31*bb4ee6a4SAndroid Build Coastguard Worker # Configured by .cargo/config.toml to execute tests with the right emulator 32*bb4ee6a4SAndroid Build Coastguard Worker ".cargo/runner.py", 33*bb4ee6a4SAndroid Build Coastguard Worker # Requried by plugin tests 34*bb4ee6a4SAndroid Build Coastguard Worker "crosvm_plugin/crosvm.h", 35*bb4ee6a4SAndroid Build Coastguard Worker "tests/plugin.policy", 36*bb4ee6a4SAndroid Build Coastguard Worker] 37*bb4ee6a4SAndroid Build Coastguard Worker 38*bb4ee6a4SAndroid Build Coastguard WorkerTEST_DATA_EXCLUDE = [ 39*bb4ee6a4SAndroid Build Coastguard Worker # config.toml is configured for x86 hosts. We cannot use that for remote tests. 40*bb4ee6a4SAndroid Build Coastguard Worker ".cargo/config.toml", 41*bb4ee6a4SAndroid Build Coastguard Worker] 42*bb4ee6a4SAndroid Build Coastguard Worker 43*bb4ee6a4SAndroid Build Coastguard Workercargo = cmd("cargo") 44*bb4ee6a4SAndroid Build Coastguard Workertar = cmd("tar") 45*bb4ee6a4SAndroid Build Coastguard Workerrust_strip = cmd("rust-strip") 46*bb4ee6a4SAndroid Build Coastguard Worker 47*bb4ee6a4SAndroid Build Coastguard Worker 48*bb4ee6a4SAndroid Build Coastguard Workerdef collect_rust_libs(): 49*bb4ee6a4SAndroid Build Coastguard Worker "Collect rust shared libraries required by the tests at runtime." 50*bb4ee6a4SAndroid Build Coastguard Worker lib_dir = Path(cmd("rustc --print=sysroot").stdout()) / "lib" 51*bb4ee6a4SAndroid Build Coastguard Worker for lib_file in lib_dir.glob("libstd-*"): 52*bb4ee6a4SAndroid Build Coastguard Worker yield (lib_file, Path("debug/deps") / lib_file.name) 53*bb4ee6a4SAndroid Build Coastguard Worker for lib_file in lib_dir.glob("libtest-*"): 54*bb4ee6a4SAndroid Build Coastguard Worker yield (lib_file, Path("debug/deps") / lib_file.name) 55*bb4ee6a4SAndroid Build Coastguard Worker 56*bb4ee6a4SAndroid Build Coastguard Worker 57*bb4ee6a4SAndroid Build Coastguard Workerdef collect_test_binaries(metadata: Any, strip: bool): 58*bb4ee6a4SAndroid Build Coastguard Worker "Collect all test binaries that are needed to run the tests." 59*bb4ee6a4SAndroid Build Coastguard Worker target_dir = Path(metadata["rust-build-meta"]["target-directory"]) 60*bb4ee6a4SAndroid Build Coastguard Worker test_binaries = [ 61*bb4ee6a4SAndroid Build Coastguard Worker Path(suite["binary-path"]).relative_to(target_dir) 62*bb4ee6a4SAndroid Build Coastguard Worker for suite in metadata["rust-binaries"].values() 63*bb4ee6a4SAndroid Build Coastguard Worker ] 64*bb4ee6a4SAndroid Build Coastguard Worker 65*bb4ee6a4SAndroid Build Coastguard Worker non_test_binaries = [ 66*bb4ee6a4SAndroid Build Coastguard Worker Path(binary["path"]) 67*bb4ee6a4SAndroid Build Coastguard Worker for crate in metadata["rust-build-meta"]["non-test-binaries"].values() 68*bb4ee6a4SAndroid Build Coastguard Worker for binary in crate 69*bb4ee6a4SAndroid Build Coastguard Worker ] 70*bb4ee6a4SAndroid Build Coastguard Worker 71*bb4ee6a4SAndroid Build Coastguard Worker def process_binary(binary_path: Path): 72*bb4ee6a4SAndroid Build Coastguard Worker source_path = target_dir / binary_path 73*bb4ee6a4SAndroid Build Coastguard Worker destination_path = binary_path 74*bb4ee6a4SAndroid Build Coastguard Worker if strip: 75*bb4ee6a4SAndroid Build Coastguard Worker stripped_path = source_path.with_suffix(".stripped") 76*bb4ee6a4SAndroid Build Coastguard Worker if ( 77*bb4ee6a4SAndroid Build Coastguard Worker not stripped_path.exists() 78*bb4ee6a4SAndroid Build Coastguard Worker or source_path.stat().st_ctime > stripped_path.stat().st_ctime 79*bb4ee6a4SAndroid Build Coastguard Worker ): 80*bb4ee6a4SAndroid Build Coastguard Worker rust_strip(f"--strip-all {source_path} -o {stripped_path}").fg() 81*bb4ee6a4SAndroid Build Coastguard Worker return (stripped_path, destination_path) 82*bb4ee6a4SAndroid Build Coastguard Worker else: 83*bb4ee6a4SAndroid Build Coastguard Worker return (source_path, destination_path) 84*bb4ee6a4SAndroid Build Coastguard Worker 85*bb4ee6a4SAndroid Build Coastguard Worker # Parallelize rust_strip calls. 86*bb4ee6a4SAndroid Build Coastguard Worker pool = ThreadPool() 87*bb4ee6a4SAndroid Build Coastguard Worker return pool.map(process_binary, test_binaries + non_test_binaries) 88*bb4ee6a4SAndroid Build Coastguard Worker 89*bb4ee6a4SAndroid Build Coastguard Worker 90*bb4ee6a4SAndroid Build Coastguard Workerdef collect_test_data_files(): 91*bb4ee6a4SAndroid Build Coastguard Worker "List additional files from the source tree that are required by tests at runtime." 92*bb4ee6a4SAndroid Build Coastguard Worker for file in all_tracked_files(): 93*bb4ee6a4SAndroid Build Coastguard Worker for glob in TEST_DATA_FILES: 94*bb4ee6a4SAndroid Build Coastguard Worker if fnmatch(str(file), glob): 95*bb4ee6a4SAndroid Build Coastguard Worker if str(file) not in TEST_DATA_EXCLUDE: 96*bb4ee6a4SAndroid Build Coastguard Worker yield (file, file) 97*bb4ee6a4SAndroid Build Coastguard Worker break 98*bb4ee6a4SAndroid Build Coastguard Worker 99*bb4ee6a4SAndroid Build Coastguard Worker 100*bb4ee6a4SAndroid Build Coastguard Workerdef collect_files(metadata: Any, output_directory: Path, strip_binaries: bool): 101*bb4ee6a4SAndroid Build Coastguard Worker # List all files we need as (source path, path in output_directory) tuples 102*bb4ee6a4SAndroid Build Coastguard Worker manifest: List[Tuple[Path, Path]] = [ 103*bb4ee6a4SAndroid Build Coastguard Worker *collect_test_binaries(metadata, strip=strip_binaries), 104*bb4ee6a4SAndroid Build Coastguard Worker *collect_rust_libs(), 105*bb4ee6a4SAndroid Build Coastguard Worker *collect_test_data_files(), 106*bb4ee6a4SAndroid Build Coastguard Worker ] 107*bb4ee6a4SAndroid Build Coastguard Worker 108*bb4ee6a4SAndroid Build Coastguard Worker # Create all target directories 109*bb4ee6a4SAndroid Build Coastguard Worker for folder in set((output_directory / d).parent.resolve() for _, d in manifest): 110*bb4ee6a4SAndroid Build Coastguard Worker folder.mkdir(exist_ok=True, parents=True) 111*bb4ee6a4SAndroid Build Coastguard Worker 112*bb4ee6a4SAndroid Build Coastguard Worker # Use multiple processes of rsync copy the files fast (and only if needed) 113*bb4ee6a4SAndroid Build Coastguard Worker parallel( 114*bb4ee6a4SAndroid Build Coastguard Worker *( 115*bb4ee6a4SAndroid Build Coastguard Worker cmd("rsync -a", source, output_directory / destination) 116*bb4ee6a4SAndroid Build Coastguard Worker for source, destination in manifest 117*bb4ee6a4SAndroid Build Coastguard Worker ) 118*bb4ee6a4SAndroid Build Coastguard Worker ).fg() 119*bb4ee6a4SAndroid Build Coastguard Worker 120*bb4ee6a4SAndroid Build Coastguard Worker 121*bb4ee6a4SAndroid Build Coastguard Workerdef generate_run_script(metadata: Any, output_directory: Path): 122*bb4ee6a4SAndroid Build Coastguard Worker # Generate metadata files for nextest 123*bb4ee6a4SAndroid Build Coastguard Worker binares_metadata_file = "binaries-metadata.json" 124*bb4ee6a4SAndroid Build Coastguard Worker (output_directory / binares_metadata_file).write_text(json.dumps(metadata)) 125*bb4ee6a4SAndroid Build Coastguard Worker cargo_metadata_file = "cargo-metadata.json" 126*bb4ee6a4SAndroid Build Coastguard Worker cargo("metadata --format-version 1").write_to(output_directory / cargo_metadata_file) 127*bb4ee6a4SAndroid Build Coastguard Worker 128*bb4ee6a4SAndroid Build Coastguard Worker # Put together command line to run nextest 129*bb4ee6a4SAndroid Build Coastguard Worker run_cmd = [ 130*bb4ee6a4SAndroid Build Coastguard Worker "cargo-nextest", 131*bb4ee6a4SAndroid Build Coastguard Worker "nextest", 132*bb4ee6a4SAndroid Build Coastguard Worker "run", 133*bb4ee6a4SAndroid Build Coastguard Worker f"--binaries-metadata={binares_metadata_file}", 134*bb4ee6a4SAndroid Build Coastguard Worker f"--cargo-metadata={cargo_metadata_file}", 135*bb4ee6a4SAndroid Build Coastguard Worker "--target-dir-remap=.", 136*bb4ee6a4SAndroid Build Coastguard Worker "--workspace-remap=.", 137*bb4ee6a4SAndroid Build Coastguard Worker ] 138*bb4ee6a4SAndroid Build Coastguard Worker command_line = [ 139*bb4ee6a4SAndroid Build Coastguard Worker "#!/usr/bin/env bash", 140*bb4ee6a4SAndroid Build Coastguard Worker 'cd "$(dirname "${BASH_SOURCE[0]}")" || die', 141*bb4ee6a4SAndroid Build Coastguard Worker f'{shlex.join(run_cmd)} "$@"', 142*bb4ee6a4SAndroid Build Coastguard Worker ] 143*bb4ee6a4SAndroid Build Coastguard Worker 144*bb4ee6a4SAndroid Build Coastguard Worker # Write command to a unix shell script 145*bb4ee6a4SAndroid Build Coastguard Worker shell_script = output_directory / "run.sh" 146*bb4ee6a4SAndroid Build Coastguard Worker shell_script.write_text("\n".join(command_line)) 147*bb4ee6a4SAndroid Build Coastguard Worker shell_script.chmod(0o755) 148*bb4ee6a4SAndroid Build Coastguard Worker 149*bb4ee6a4SAndroid Build Coastguard Worker # TODO(denniskempin): Add an equivalent windows bash script 150*bb4ee6a4SAndroid Build Coastguard Worker 151*bb4ee6a4SAndroid Build Coastguard Worker 152*bb4ee6a4SAndroid Build Coastguard Workerdef generate_archive(output_directory: Path, output_archive: Path): 153*bb4ee6a4SAndroid Build Coastguard Worker with cwd_context(output_directory.parent): 154*bb4ee6a4SAndroid Build Coastguard Worker tar("-ca", output_directory.name, "-f", output_archive).fg() 155*bb4ee6a4SAndroid Build Coastguard Worker 156*bb4ee6a4SAndroid Build Coastguard Worker 157*bb4ee6a4SAndroid Build Coastguard Workerdef main(): 158*bb4ee6a4SAndroid Build Coastguard Worker """ 159*bb4ee6a4SAndroid Build Coastguard Worker Builds a package to execute tests remotely. 160*bb4ee6a4SAndroid Build Coastguard Worker 161*bb4ee6a4SAndroid Build Coastguard Worker ## Basic usage 162*bb4ee6a4SAndroid Build Coastguard Worker 163*bb4ee6a4SAndroid Build Coastguard Worker ``` 164*bb4ee6a4SAndroid Build Coastguard Worker $ tools/nextest_package -o foo.tar.zst ... nextest args 165*bb4ee6a4SAndroid Build Coastguard Worker ``` 166*bb4ee6a4SAndroid Build Coastguard Worker 167*bb4ee6a4SAndroid Build Coastguard Worker The archive will contain all necessary test binaries, required shared libraries and test data 168*bb4ee6a4SAndroid Build Coastguard Worker files required at runtime. 169*bb4ee6a4SAndroid Build Coastguard Worker A cargo nextest binary is included along with a `run.sh` script to invoke it with the required 170*bb4ee6a4SAndroid Build Coastguard Worker arguments. THe archive can be copied anywhere and executed: 171*bb4ee6a4SAndroid Build Coastguard Worker 172*bb4ee6a4SAndroid Build Coastguard Worker ``` 173*bb4ee6a4SAndroid Build Coastguard Worker $ tar xaf foo.tar.zst && cd foo.tar.d && ./run.sh 174*bb4ee6a4SAndroid Build Coastguard Worker ``` 175*bb4ee6a4SAndroid Build Coastguard Worker 176*bb4ee6a4SAndroid Build Coastguard Worker ## Nextest Arguments 177*bb4ee6a4SAndroid Build Coastguard Worker 178*bb4ee6a4SAndroid Build Coastguard Worker All additional arguments will be passed to `nextest list`. Additional arguments to `nextest run` 179*bb4ee6a4SAndroid Build Coastguard Worker can be passed to the `run.sh` invocation. 180*bb4ee6a4SAndroid Build Coastguard Worker 181*bb4ee6a4SAndroid Build Coastguard Worker For example: 182*bb4ee6a4SAndroid Build Coastguard Worker 183*bb4ee6a4SAndroid Build Coastguard Worker ``` 184*bb4ee6a4SAndroid Build Coastguard Worker $ tools/nextest_package -d foo --tests 185*bb4ee6a4SAndroid Build Coastguard Worker $ cd foo && ./run.sh --test-threads=1 186*bb4ee6a4SAndroid Build Coastguard Worker ``` 187*bb4ee6a4SAndroid Build Coastguard Worker 188*bb4ee6a4SAndroid Build Coastguard Worker Will only list and package integration tests (--tests) and run them with --test-threads=1. 189*bb4ee6a4SAndroid Build Coastguard Worker 190*bb4ee6a4SAndroid Build Coastguard Worker ## Stripping Symbols 191*bb4ee6a4SAndroid Build Coastguard Worker 192*bb4ee6a4SAndroid Build Coastguard Worker Debug symbols are stripped by default to reduce the package size. This can be disabled via 193*bb4ee6a4SAndroid Build Coastguard Worker the `--no-strip` argument. 194*bb4ee6a4SAndroid Build Coastguard Worker 195*bb4ee6a4SAndroid Build Coastguard Worker """ 196*bb4ee6a4SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 197*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--no-strip", action="store_true") 198*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--output-directory", "-d") 199*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--output-archive", "-o") 200*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--clean", action="store_true") 201*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--timing-info", action="store_true") 202*bb4ee6a4SAndroid Build Coastguard Worker (args, nextest_list_args) = parser.parse_known_args() 203*bb4ee6a4SAndroid Build Coastguard Worker chdir(CROSVM_ROOT) 204*bb4ee6a4SAndroid Build Coastguard Worker 205*bb4ee6a4SAndroid Build Coastguard Worker # Determine output archive / directory 206*bb4ee6a4SAndroid Build Coastguard Worker output_directory = Path(args.output_directory).resolve() if args.output_directory else None 207*bb4ee6a4SAndroid Build Coastguard Worker output_archive = Path(args.output_archive).resolve() if args.output_archive else None 208*bb4ee6a4SAndroid Build Coastguard Worker if not output_directory and output_archive: 209*bb4ee6a4SAndroid Build Coastguard Worker output_directory = output_archive.with_suffix(".d") 210*bb4ee6a4SAndroid Build Coastguard Worker if not output_directory: 211*bb4ee6a4SAndroid Build Coastguard Worker print("Must specify either --output-directory or --output-archive") 212*bb4ee6a4SAndroid Build Coastguard Worker return 213*bb4ee6a4SAndroid Build Coastguard Worker 214*bb4ee6a4SAndroid Build Coastguard Worker if args.clean and output_directory.exists(): 215*bb4ee6a4SAndroid Build Coastguard Worker shutil.rmtree(output_directory) 216*bb4ee6a4SAndroid Build Coastguard Worker 217*bb4ee6a4SAndroid Build Coastguard Worker with record_time("Listing tests"): 218*bb4ee6a4SAndroid Build Coastguard Worker cargo( 219*bb4ee6a4SAndroid Build Coastguard Worker "nextest list", 220*bb4ee6a4SAndroid Build Coastguard Worker *(quoted(a) for a in nextest_list_args), 221*bb4ee6a4SAndroid Build Coastguard Worker ).fg() 222*bb4ee6a4SAndroid Build Coastguard Worker with record_time("Listing tests metadata"): 223*bb4ee6a4SAndroid Build Coastguard Worker metadata = cargo( 224*bb4ee6a4SAndroid Build Coastguard Worker "nextest list --list-type binaries-only --message-format json", 225*bb4ee6a4SAndroid Build Coastguard Worker *(quoted(a) for a in nextest_list_args), 226*bb4ee6a4SAndroid Build Coastguard Worker ).json() 227*bb4ee6a4SAndroid Build Coastguard Worker 228*bb4ee6a4SAndroid Build Coastguard Worker with record_time("Collecting files"): 229*bb4ee6a4SAndroid Build Coastguard Worker collect_files(metadata, output_directory, strip_binaries=not args.no_strip) 230*bb4ee6a4SAndroid Build Coastguard Worker generate_run_script(metadata, output_directory) 231*bb4ee6a4SAndroid Build Coastguard Worker 232*bb4ee6a4SAndroid Build Coastguard Worker if output_archive: 233*bb4ee6a4SAndroid Build Coastguard Worker with record_time("Generating archive"): 234*bb4ee6a4SAndroid Build Coastguard Worker generate_archive(output_directory, output_archive) 235*bb4ee6a4SAndroid Build Coastguard Worker 236*bb4ee6a4SAndroid Build Coastguard Worker if args.timing_info: 237*bb4ee6a4SAndroid Build Coastguard Worker print_timing_info() 238*bb4ee6a4SAndroid Build Coastguard Worker 239*bb4ee6a4SAndroid Build Coastguard Worker 240*bb4ee6a4SAndroid Build Coastguard Workerif __name__ == "__main__": 241*bb4ee6a4SAndroid Build Coastguard Worker main() 242