xref: /aosp_15_r20/external/crosvm/tools/nextest_package (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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