mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-10 20:19:07 +00:00
The ./run_tests script will select which tests to run on the host. Unfortunately a lot of our tests require dependencies or access to certain kernel devices. We cannot run all tests on all hosts. The test runner works with the CI builders: To run all x86 tests: ./ci/builder ./run_tests --all To run tests on aarch64: ./ci/aarch64_builder ./run_tests --all Or to just run tests that can be run on a standard debian buster system without obscure chromeos dependencies: ./run_tests Eventually, the test runner should run tests via the VM of the aarch64_builder, currently only those tests that can be run with user-space emulation are executed. Currently some tests remain disabled in the runner, as they do not pass in the CI builders yet. These will be enabled as the builders are set up to allow for them to be run. BUG=b:173833661 TEST=Ran the above commands. Change-Id: Id69027793348f4d3f20adf5333d8bdfff1aa9c8b Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2633889 Tested-by: Dennis Kempin <denniskempin@google.com> Reviewed-by: Stephen Barber <smbarber@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org> Commit-Queue: Dennis Kempin <denniskempin@google.com>
292 lines
8.7 KiB
Python
Executable file
292 lines
8.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright 2021 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
#
|
|
# Runs tests for crosvm.
|
|
#
|
|
# This script gives us more flexibility in running tests than using
|
|
# `cargo test --workspace`:
|
|
# - We can also test crates that are not part of the workspace.
|
|
# - We can pick out tests that need to be run single-threaded.
|
|
# - We can filter out tests that cannot be built or run due to missing build-
|
|
# dependencies or missing runtime requirements.
|
|
|
|
import argparse
|
|
import os
|
|
import platform
|
|
import subprocess
|
|
from typing import List, Dict, Set
|
|
import enum
|
|
|
|
|
|
class Requirements(enum.Enum):
|
|
# Test can only be built for aarch64
|
|
AARCH64 = "aarch64"
|
|
|
|
# Test can only be built for x86_64
|
|
X86_64 = "x86_64"
|
|
|
|
# Test requires non-standard dependencies from chromiumos (i.e. those that are
|
|
# not available in debian buster).
|
|
CROS_DEPENDENCIES = "cros_dependencies"
|
|
|
|
# Test is disabled explicitly
|
|
DISABLED = "disabled"
|
|
|
|
# Test requires access to kernel devices at runtime.
|
|
DEVICE_ACCESS = "device_access"
|
|
|
|
# Test needs to be executed natively, not through emulation.
|
|
NATIVE = "native"
|
|
|
|
# Test needs to run single-threaded
|
|
SINGLE_THREADED = "single_threaded"
|
|
|
|
|
|
BUILD_TIME_REQUIREMENTS = [
|
|
Requirements.AARCH64,
|
|
Requirements.X86_64,
|
|
Requirements.CROS_DEPENDENCIES,
|
|
Requirements.DISABLED,
|
|
]
|
|
|
|
RUN_TIME_REQUIREMENTS = [Requirements.DEVICE_ACCESS, Requirements.NATIVE]
|
|
|
|
|
|
# A list of all crates and their requirements
|
|
CRATE_REQUIREMENTS: Dict[str, List[Requirements]] = {
|
|
"crosvm": [Requirements.DEVICE_ACCESS],
|
|
"aarch64": [Requirements.AARCH64],
|
|
"acpi_tables": [],
|
|
"arch": [],
|
|
"assertions": [],
|
|
"base": [],
|
|
"bit_field": [],
|
|
"bit_field_derive": [],
|
|
"cros_async": [Requirements.DISABLED],
|
|
"crosvm_plugin": [Requirements.X86_64],
|
|
"data_model": [],
|
|
"devices": [Requirements.SINGLE_THREADED, Requirements.DEVICE_ACCESS],
|
|
"disk": [Requirements.DISABLED],
|
|
"enumn": [],
|
|
"fuse": [],
|
|
"fuzz": [Requirements.DISABLED],
|
|
"gpu_buffer": [Requirements.CROS_DEPENDENCIES],
|
|
"gpu_display": [],
|
|
"hypervisor": [Requirements.DEVICE_ACCESS],
|
|
"io_uring": [Requirements.DISABLED],
|
|
"kernel_cmdline": [],
|
|
"kernel_loader": [Requirements.NATIVE],
|
|
"kvm_sys": [Requirements.DEVICE_ACCESS],
|
|
"kvm": [Requirements.DEVICE_ACCESS],
|
|
"linux_input_sys": [],
|
|
"msg_socket": [Requirements.NATIVE],
|
|
"msg_on_socket_derive": [],
|
|
"net_sys": [],
|
|
"net_util": [Requirements.DEVICE_ACCESS],
|
|
"power_monitor": [],
|
|
"protos": [],
|
|
"qcow_utils": [],
|
|
"rand_ish": [],
|
|
"resources": [],
|
|
"rutabaga_gfx": [Requirements.CROS_DEPENDENCIES],
|
|
"sync": [],
|
|
"sys_util": [Requirements.SINGLE_THREADED, Requirements.NATIVE],
|
|
"poll_token_derive": [],
|
|
"syscall_defines": [],
|
|
"tempfile": [],
|
|
"tpm2-sys": [],
|
|
"tpm2": [],
|
|
"usb_sys": [],
|
|
"usb_util": [],
|
|
"vfio_sys": [],
|
|
"vhost": [Requirements.DEVICE_ACCESS],
|
|
"virtio_sys": [],
|
|
"vm_control": [],
|
|
"vm_memory": [Requirements.DISABLED],
|
|
"x86_64": [Requirements.X86_64, Requirements.DEVICE_ACCESS],
|
|
}
|
|
|
|
# Just like for crates, lists requirements for each cargo feature flag.
|
|
FEATURE_REQUIREMENTS: Dict[str, List[Requirements]] = {
|
|
"chromeos": [Requirements.CROS_DEPENDENCIES],
|
|
"audio": [],
|
|
"gpu": [Requirements.CROS_DEPENDENCIES],
|
|
"plugin": [Requirements.DEVICE_ACCESS, Requirements.X86_64],
|
|
"power-monitor-powerd": [],
|
|
"tpm": [Requirements.CROS_DEPENDENCIES],
|
|
"video-decoder": [Requirements.DISABLED],
|
|
"video-encoder": [Requirements.DISABLED],
|
|
"wl-dmabuf": [Requirements.CROS_DEPENDENCIES, Requirements.DISABLED],
|
|
"x": [],
|
|
"virgl_renderer_next": [Requirements.CROS_DEPENDENCIES],
|
|
"composite-disk": [],
|
|
"virgl_renderer": [Requirements.CROS_DEPENDENCIES],
|
|
"gfxstream": [Requirements.CROS_DEPENDENCIES, Requirements.DISABLED],
|
|
"gdb": [],
|
|
}
|
|
|
|
|
|
def target_arch():
|
|
"""Returns architecture cargo is set up to build for."""
|
|
if "CARGO_BUILD_TARGET" in os.environ:
|
|
target = os.environ["CARGO_BUILD_TARGET"]
|
|
return target.split("-")[0]
|
|
else:
|
|
return platform.machine()
|
|
|
|
|
|
def is_native():
|
|
"""True if we are building for the architecture we are running on."""
|
|
return target_arch() == platform.machine()
|
|
|
|
|
|
class CrateInfo(object):
|
|
"""Informaton about whether a crate can be built or run on this host."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
requirements: Set[Requirements],
|
|
capabilities: Set[Requirements],
|
|
):
|
|
self.name = name
|
|
self.requirements = requirements
|
|
self.single_threaded = Requirements.SINGLE_THREADED in requirements
|
|
|
|
build_reqs = requirements.intersection(BUILD_TIME_REQUIREMENTS)
|
|
self.can_build = all(req in capabilities for req in build_reqs)
|
|
|
|
run_reqs = requirements.intersection(RUN_TIME_REQUIREMENTS)
|
|
self.can_run = self.can_build and all(
|
|
req in capabilities for req in run_reqs
|
|
)
|
|
|
|
|
|
def execute_tests(
|
|
crates: List[CrateInfo],
|
|
features: Set[str],
|
|
run: bool = True,
|
|
single_threaded: bool = False,
|
|
):
|
|
"""Executes the list of crates via `cargo test`."""
|
|
if not crates:
|
|
return True
|
|
|
|
cmd = ["cargo", "test", "-q"]
|
|
if not run:
|
|
cmd += ["--no-run"]
|
|
if features:
|
|
cmd += ["--no-default-features", "--features", ",".join(features)]
|
|
for crate in sorted(crate.name for crate in crates):
|
|
cmd += ["-p", crate]
|
|
if single_threaded:
|
|
cmd += ["--", "--test-threads=1"]
|
|
|
|
print("$", " ".join(cmd))
|
|
process = subprocess.run(cmd)
|
|
return process.returncode == 0
|
|
|
|
|
|
def execute_test_batches(crates: List[CrateInfo], features: Set[str]):
|
|
"""Groups tests and runs them in 3 batches:
|
|
|
|
- Those that can only be built, but not run.
|
|
- Those that can only be run single-threaded.
|
|
- And those that can be run in parallel.
|
|
"""
|
|
passed = True
|
|
|
|
build_crates = [
|
|
crate for crate in crates if crate.can_build and not crate.can_run
|
|
]
|
|
if not execute_tests(build_crates, features, run=False):
|
|
passed = False
|
|
|
|
run_single = [
|
|
crate for crate in crates if crate.can_run and crate.single_threaded
|
|
]
|
|
if not execute_tests(run_single, features, single_threaded=True):
|
|
passed = False
|
|
|
|
run_parallel = [
|
|
crate for crate in crates if crate.can_run and not crate.single_threaded
|
|
]
|
|
if not execute_tests(run_parallel, features):
|
|
passed = False
|
|
return passed
|
|
|
|
|
|
def main(capabilities: Set[Requirements]):
|
|
if target_arch() == "aarch64":
|
|
capabilities.add(Requirements.AARCH64)
|
|
elif target_arch() == "x86_64":
|
|
capabilities.add(Requirements.X86_64)
|
|
|
|
if is_native():
|
|
capabilities.add(Requirements.NATIVE)
|
|
else:
|
|
capabilities.remove(Requirements.DEVICE_ACCESS)
|
|
|
|
print("Capabilities:", ", ".join(cap.value for cap in capabilities))
|
|
|
|
# Select all features where capabilities meet the requirements
|
|
features = set(
|
|
feature
|
|
for (feature, requirements) in FEATURE_REQUIREMENTS.items()
|
|
if all(r in capabilities for r in requirements)
|
|
)
|
|
|
|
# Disable sandboxing for tests until our builders are set up to run with
|
|
# sandboxing.
|
|
features.add("default-no-sandbox")
|
|
print("Features:", ", ".join(features))
|
|
|
|
crates = [
|
|
CrateInfo(crate, set(requirements), capabilities)
|
|
for (crate, requirements) in CRATE_REQUIREMENTS.items()
|
|
]
|
|
passed = execute_test_batches(crates, features)
|
|
|
|
# TODO: We should parse test output and summarize the results
|
|
# Unfortunately machine readable output for `cargo test` is still a nightly
|
|
# rust feature.
|
|
|
|
print()
|
|
crates_not_built = [crate.name for crate in crates if not crate.can_build]
|
|
print(f"Tests not built: {', '.join(crates_not_built)}")
|
|
|
|
crates_not_run = [
|
|
crate.name for crate in crates if crate.can_build and not crate.can_run
|
|
]
|
|
print(f"Tests not run: {', '.join(crates_not_run)}")
|
|
|
|
disabled_features = set(FEATURE_REQUIREMENTS.keys()).difference(features)
|
|
print(f"Disabled features: {', '.join(disabled_features)}")
|
|
|
|
print()
|
|
if not passed:
|
|
print("Some tests failed.")
|
|
exit(-1)
|
|
else:
|
|
print("All tests passed.")
|
|
|
|
|
|
DESCRIPTION = """\
|
|
Selects a subset of tests from crosvm to run depending on the capabilities of
|
|
the local host.
|
|
"""
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=DESCRIPTION)
|
|
|
|
parser.add_argument(
|
|
"--all", action="store_true", default=False, help="Enable all tests."
|
|
)
|
|
args = parser.parse_args()
|
|
main(
|
|
set([Requirements.DEVICE_ACCESS, Requirements.CROS_DEPENDENCIES])
|
|
if args.all
|
|
else set()
|
|
)
|