2021-01-14 21:10:31 +00:00
|
|
|
#!/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.
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
from typing import List, Dict, Set
|
2021-01-14 21:10:31 +00:00
|
|
|
import argparse
|
2021-01-21 19:06:44 +00:00
|
|
|
import enum
|
2021-01-14 21:10:31 +00:00
|
|
|
import os
|
|
|
|
import platform
|
|
|
|
import subprocess
|
2021-01-21 19:06:44 +00:00
|
|
|
import sys
|
|
|
|
|
|
|
|
# Print debug info. Overriden by --verbose.
|
|
|
|
VERBOSE = False
|
|
|
|
|
|
|
|
# Runs tests using the exec_file wrapper, which will run the test inside the
|
|
|
|
# builders built-in VM.
|
|
|
|
VM_TEST_RUNNER = "/workspace/vm/exec_file --no-sync"
|
|
|
|
|
|
|
|
# Runs tests using QEMU user-space emulation.
|
|
|
|
QEMU_TEST_RUNNER = (
|
|
|
|
"qemu-aarch64-static -E LD_LIBRARY_PATH=/workspace/scratch/lib"
|
|
|
|
)
|
2021-01-14 21:10:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Requirements(enum.Enum):
|
2021-01-21 19:06:44 +00:00
|
|
|
# Test can only be built for aarch64.
|
2021-01-14 21:10:31 +00:00
|
|
|
AARCH64 = "aarch64"
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
# Test can only be built for x86_64.
|
2021-01-14 21:10:31 +00:00
|
|
|
X86_64 = "x86_64"
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
# Requires ChromeOS build environment.
|
|
|
|
CROS_BUILD = "cros_build"
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
# Test is disabled explicitly.
|
2021-01-14 21:10:31 +00:00
|
|
|
DISABLED = "disabled"
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
# Test needs to be executed with expanded privileges for device access.
|
|
|
|
PRIVILEGED = "privileged"
|
2021-01-14 21:10:31 +00:00
|
|
|
|
|
|
|
# Test needs to run single-threaded
|
|
|
|
SINGLE_THREADED = "single_threaded"
|
|
|
|
|
|
|
|
|
|
|
|
BUILD_TIME_REQUIREMENTS = [
|
|
|
|
Requirements.AARCH64,
|
|
|
|
Requirements.X86_64,
|
2021-01-21 19:06:44 +00:00
|
|
|
Requirements.CROS_BUILD,
|
2021-01-14 21:10:31 +00:00
|
|
|
Requirements.DISABLED,
|
|
|
|
]
|
|
|
|
|
|
|
|
# A list of all crates and their requirements
|
|
|
|
CRATE_REQUIREMENTS: Dict[str, List[Requirements]] = {
|
2021-01-21 19:06:44 +00:00
|
|
|
"aarch64": [Requirements.AARCH64],
|
|
|
|
"crosvm": [Requirements.DISABLED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"aarch64": [Requirements.AARCH64],
|
|
|
|
"acpi_tables": [],
|
|
|
|
"arch": [],
|
|
|
|
"assertions": [],
|
|
|
|
"base": [],
|
|
|
|
"bit_field": [],
|
|
|
|
"bit_field_derive": [],
|
|
|
|
"cros_async": [Requirements.DISABLED],
|
|
|
|
"crosvm_plugin": [Requirements.X86_64],
|
|
|
|
"data_model": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"devices": [
|
|
|
|
Requirements.SINGLE_THREADED,
|
|
|
|
Requirements.PRIVILEGED,
|
|
|
|
Requirements.X86_64,
|
|
|
|
],
|
2021-01-14 21:10:31 +00:00
|
|
|
"disk": [Requirements.DISABLED],
|
|
|
|
"enumn": [],
|
|
|
|
"fuse": [],
|
|
|
|
"fuzz": [Requirements.DISABLED],
|
|
|
|
"gpu_display": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"hypervisor": [Requirements.PRIVILEGED, Requirements.X86_64],
|
2021-01-14 21:10:31 +00:00
|
|
|
"io_uring": [Requirements.DISABLED],
|
|
|
|
"kernel_cmdline": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"kernel_loader": [Requirements.PRIVILEGED],
|
|
|
|
"kvm_sys": [Requirements.PRIVILEGED],
|
|
|
|
"kvm": [Requirements.PRIVILEGED, Requirements.X86_64],
|
2021-01-14 21:10:31 +00:00
|
|
|
"linux_input_sys": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"msg_socket": [Requirements.PRIVILEGED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"msg_on_socket_derive": [],
|
|
|
|
"net_sys": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"net_util": [Requirements.PRIVILEGED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"power_monitor": [],
|
|
|
|
"protos": [],
|
|
|
|
"qcow_utils": [],
|
|
|
|
"rand_ish": [],
|
|
|
|
"resources": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"rutabaga_gfx": [Requirements.CROS_BUILD],
|
2021-01-14 21:10:31 +00:00
|
|
|
"sync": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"sys_util": [Requirements.SINGLE_THREADED, Requirements.PRIVILEGED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"poll_token_derive": [],
|
|
|
|
"syscall_defines": [],
|
|
|
|
"tempfile": [],
|
|
|
|
"tpm2-sys": [],
|
|
|
|
"tpm2": [],
|
|
|
|
"usb_sys": [],
|
|
|
|
"usb_util": [],
|
|
|
|
"vfio_sys": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"vhost": [Requirements.PRIVILEGED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"virtio_sys": [],
|
|
|
|
"vm_control": [],
|
|
|
|
"vm_memory": [Requirements.DISABLED],
|
2021-01-21 19:06:44 +00:00
|
|
|
"x86_64": [Requirements.X86_64, Requirements.PRIVILEGED],
|
2021-01-14 21:10:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Just like for crates, lists requirements for each cargo feature flag.
|
|
|
|
FEATURE_REQUIREMENTS: Dict[str, List[Requirements]] = {
|
2021-01-21 19:06:44 +00:00
|
|
|
"chromeos": [],
|
2021-01-14 21:10:31 +00:00
|
|
|
"audio": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"gpu": [Requirements.CROS_BUILD],
|
|
|
|
"plugin": [Requirements.PRIVILEGED, Requirements.X86_64],
|
2021-01-14 21:10:31 +00:00
|
|
|
"power-monitor-powerd": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"tpm": [Requirements.CROS_BUILD],
|
2021-01-14 21:10:31 +00:00
|
|
|
"video-decoder": [Requirements.DISABLED],
|
|
|
|
"video-encoder": [Requirements.DISABLED],
|
2021-01-21 19:06:44 +00:00
|
|
|
"wl-dmabuf": [Requirements.DISABLED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"x": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"virgl_renderer_next": [Requirements.CROS_BUILD],
|
2021-01-14 21:10:31 +00:00
|
|
|
"composite-disk": [],
|
2021-01-21 19:06:44 +00:00
|
|
|
"virgl_renderer": [Requirements.CROS_BUILD],
|
|
|
|
"gfxstream": [Requirements.DISABLED],
|
2021-01-14 21:10:31 +00:00
|
|
|
"gdb": [],
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2021-01-21 19:06:44 +00:00
|
|
|
self.needs_privilege = Requirements.PRIVILEGED in requirements
|
2021-01-14 21:10:31 +00:00
|
|
|
|
|
|
|
build_reqs = requirements.intersection(BUILD_TIME_REQUIREMENTS)
|
|
|
|
self.can_build = all(req in capabilities for req in build_reqs)
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
self.can_run = self.can_build and (
|
|
|
|
not self.needs_privilege or Requirements.PRIVILEGED in capabilities
|
2021-01-14 21:10:31 +00:00
|
|
|
)
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return f"{self.name} {self.requirements}"
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
def get_test_runner_env(use_vm: bool):
|
|
|
|
"""Sets the target.*.runner cargo setting to use the correct test runner."""
|
|
|
|
env = os.environ.copy()
|
|
|
|
key = f"CARGO_TARGET_{target_arch().upper()}_UNKNOWN_LINUX_GNU_RUNNER"
|
|
|
|
if use_vm:
|
|
|
|
env[key] = VM_TEST_RUNNER
|
|
|
|
else:
|
|
|
|
if target_arch() == "aarch64":
|
|
|
|
env[key] = QEMU_TEST_RUNNER
|
|
|
|
else:
|
|
|
|
if key in env:
|
|
|
|
del env[key]
|
|
|
|
return env
|
|
|
|
|
|
|
|
|
|
|
|
def cargo_test(
|
2021-01-14 21:10:31 +00:00
|
|
|
crates: List[CrateInfo],
|
|
|
|
features: Set[str],
|
|
|
|
run: bool = True,
|
|
|
|
single_threaded: bool = False,
|
2021-01-21 19:06:44 +00:00
|
|
|
use_vm: bool = False,
|
2021-01-14 21:10:31 +00:00
|
|
|
):
|
|
|
|
"""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"]
|
2021-01-21 19:06:44 +00:00
|
|
|
env = get_test_runner_env(use_vm)
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
msg = ["Running" if run else "Building"]
|
|
|
|
if use_vm:
|
|
|
|
msg.append("in vm")
|
|
|
|
if single_threaded:
|
|
|
|
msg.append("(single-threaded)")
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
print()
|
|
|
|
print(f"{' '.join(msg)}: {', '.join(crate.name for crate in crates)}")
|
|
|
|
if VERBOSE:
|
|
|
|
print("ENV", env)
|
|
|
|
print("CMD", " ".join(cmd))
|
|
|
|
print()
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
process = subprocess.run(cmd, env=env)
|
|
|
|
return process.returncode == 0
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
|
|
|
|
def execute_batched_by_parallelism(
|
|
|
|
crates: List[CrateInfo], features: Set[str], use_vm: bool
|
|
|
|
):
|
|
|
|
"""Batches tests by single-threaded and parallel, then executes them."""
|
2021-01-14 21:10:31 +00:00
|
|
|
passed = True
|
2021-01-21 19:06:44 +00:00
|
|
|
run_single = [crate for crate in crates if crate.single_threaded]
|
|
|
|
if not cargo_test(
|
|
|
|
run_single, features, single_threaded=True, use_vm=use_vm
|
|
|
|
):
|
|
|
|
passed = False
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
run_parallel = [crate for crate in crates if not crate.single_threaded]
|
|
|
|
if not cargo_test(run_parallel, features, use_vm=use_vm):
|
2021-01-14 21:10:31 +00:00
|
|
|
passed = False
|
2021-01-21 19:06:44 +00:00
|
|
|
return passed
|
|
|
|
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
def execute_batched_by_privilege(
|
|
|
|
crates: List[CrateInfo], features: Set[str], use_vm: bool
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Batches tests by whether or not a test needs privileged access to run.
|
|
|
|
|
|
|
|
Non-privileged tests are run first. Privileged tests are executed in
|
|
|
|
a VM if use_vm is set.
|
|
|
|
"""
|
|
|
|
passed = True
|
|
|
|
|
|
|
|
build_crates = [crate for crate in crates if crate.can_build]
|
|
|
|
if not cargo_test(build_crates, features, run=False):
|
|
|
|
return False
|
|
|
|
simple_crates = [
|
|
|
|
crate for crate in crates if crate.can_run and not crate.needs_privilege
|
2021-01-14 21:10:31 +00:00
|
|
|
]
|
2021-01-21 19:06:44 +00:00
|
|
|
execute_batched_by_parallelism(simple_crates, features, use_vm=False)
|
2021-01-14 21:10:31 +00:00
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
privileged_crates = [
|
|
|
|
crate for crate in crates if crate.can_run and crate.needs_privilege
|
2021-01-14 21:10:31 +00:00
|
|
|
]
|
2021-01-21 19:06:44 +00:00
|
|
|
if privileged_crates:
|
|
|
|
if use_vm:
|
|
|
|
subprocess.run("/workspace/vm/sync_so", check=True)
|
|
|
|
execute_batched_by_parallelism(
|
|
|
|
privileged_crates, features, use_vm=True
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
execute_batched_by_parallelism(
|
|
|
|
privileged_crates, features, use_vm=False
|
|
|
|
)
|
2021-01-14 21:10:31 +00:00
|
|
|
return passed
|
|
|
|
|
|
|
|
|
2021-01-21 19:06:44 +00:00
|
|
|
def main(capabilities: Set[Requirements], use_vm: bool):
|
2021-01-14 21:10:31 +00:00
|
|
|
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()
|
|
|
|
]
|
2021-01-21 19:06:44 +00:00
|
|
|
passed = execute_batched_by_privilege(crates, features, use_vm)
|
2021-01-14 21:10:31 +00:00
|
|
|
|
|
|
|
# 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:
|
2021-01-21 19:06:44 +00:00
|
|
|
print("Some tests failed.", file=sys.stderr)
|
2021-01-14 21:10:31 +00:00
|
|
|
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(
|
2021-01-21 19:06:44 +00:00
|
|
|
"--verbose",
|
|
|
|
"-v",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help="Print commands executed.",
|
2021-01-14 21:10:31 +00:00
|
|
|
)
|
2021-01-21 19:06:44 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--run-privileged",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help="Enable tests that requires privileged access to the system.",
|
2021-01-14 21:10:31 +00:00
|
|
|
)
|
2021-01-21 19:06:44 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--cros-build",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help=(
|
|
|
|
"Enables tests that require a ChromeOS build environment. "
|
|
|
|
"Can also be set by CROSVM_CROS_BUILD"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--use-vm",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help=(
|
|
|
|
"Enables privileged tests to run in a VM. "
|
|
|
|
"Can also be set by CROSVM_USE_VM"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--require-all",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help="Requires all tests to run, fail if tests would be disabled.",
|
|
|
|
)
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
VERBOSE = args.verbose # type: ignore
|
|
|
|
use_vm = os.environ.get("CROSVM_USE_VM") != None or args.use_vm
|
|
|
|
cros_build = os.environ.get("CROSVM_CROS_BUILD") != None or args.cros_build
|
|
|
|
|
|
|
|
capabilities = set()
|
|
|
|
if target_arch() == "aarch64":
|
|
|
|
capabilities.add(Requirements.AARCH64)
|
|
|
|
elif target_arch() == "x86_64":
|
|
|
|
capabilities.add(Requirements.X86_64)
|
|
|
|
|
|
|
|
if cros_build:
|
|
|
|
capabilities.add(Requirements.CROS_BUILD)
|
|
|
|
|
|
|
|
if use_vm:
|
|
|
|
if not os.path.exists("/workspace/vm"):
|
|
|
|
print("--use-vm can only be used within the ./ci/builder's.")
|
|
|
|
exit(1)
|
|
|
|
capabilities.add(Requirements.PRIVILEGED)
|
|
|
|
|
|
|
|
if args.run_privileged:
|
|
|
|
capabilities.add(Requirements.PRIVILEGED)
|
|
|
|
|
|
|
|
if args.require_all and not Requirements.PRIVILEGED in capabilities:
|
|
|
|
print("--require-all needs to be run with --use-vm or --run-privileged")
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
main(capabilities, use_vm)
|