tools/run_tests: Add --cov option

The option will generate an lcov file in the standard name lcov.info
and print a coverage report after running tests.

The lcov.info file is understood by many IDE plugins, e.g. "Coverage
Gutters" for vscode.

BUG=b:239255082
TEST=./tools/run_tests --cov

Change-Id: I475c62ada73f100a984ad863e511083c69807aff
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3840956
Commit-Queue: Dennis Kempin <denniskempin@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dennis Kempin <denniskempin@google.com>
This commit is contained in:
Dennis Kempin 2022-08-19 00:27:59 +00:00 committed by crosvm LUCI
parent 3b60f0ca33
commit 1e16dc6a8f

View file

@ -5,7 +5,6 @@
import argparse import argparse
import fnmatch import fnmatch
import functools import functools
import itertools
import json import json
import os import os
import random import random
@ -13,11 +12,12 @@ import subprocess
import sys import sys
from multiprocessing import Pool from multiprocessing import Pool
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable, List, NamedTuple, Optional from typing import Dict, Iterable, List, NamedTuple
from . import test_target, testvm from . import test_target, testvm
from .common import all_tracked_files
from .test_config import BUILD_FEATURES, CRATE_OPTIONS, TestOption
from .test_target import TestTarget, Triple from .test_target import TestTarget, Triple
from .test_config import CRATE_OPTIONS, TestOption, BUILD_FEATURES
USAGE = """\ USAGE = """\
Runs tests for crosvm locally, in a vm or on a remote device. Runs tests for crosvm locally, in a vm or on a remote device.
@ -420,25 +420,42 @@ def find_crosvm_binary(executables: List[Executable]):
raise Exception("Cannot find crosvm executable") raise Exception("Cannot find crosvm executable")
def generate_lcov(results: List[ExecutableResults], lcov_file: str): def generate_lcov(
results: List[ExecutableResults], crosvm_binary: Path, lcov_file: str, print_report: bool
):
print("Merging profiles") print("Merging profiles")
merged_file = testvm.cargo_target_dir() / "merged.profraw" merged_file = testvm.cargo_target_dir() / "merged.profraw"
profiles = [str(p) for r in results if r.profile_files for p in r.profile_files] profiles = [str(p) for r in results if r.profile_files for p in r.profile_files]
subprocess.check_call(["rust-profdata", "merge", "-sparse", *profiles, "-o", str(merged_file)]) subprocess.check_call(["rust-profdata", "merge", "-sparse", *profiles, "-o", str(merged_file)])
print("Exporting report") print("Generating lcov")
all_rust_src = [f for f in all_tracked_files() if f.suffix == ".rs"]
lcov_data = subprocess.check_output( lcov_data = subprocess.check_output(
[ [
"rust-cov", "rust-cov",
"export", "export",
"--format=lcov", "--format=lcov",
"--ignore-filename-regex='/registry/'",
f"--instr-profile={merged_file}", f"--instr-profile={merged_file}",
*(f"--object={r.binary_file}" for r in results), *(f"--object={r.binary_file}" for r in results),
str(crosvm_binary),
*all_rust_src,
], ],
text=True, text=True,
) )
open(lcov_file, "w").write(lcov_data) open(lcov_file, "w").write(lcov_data)
if print_report:
subprocess.check_call(
[
"rust-cov",
"report",
"-show-region-summary=False",
"-show-branch-summary=False",
f"-instr-profile={merged_file}",
*(f"-object={r.binary_file}" for r in results),
str(crosvm_binary),
*all_rust_src,
]
)
def main(): def main():
@ -473,6 +490,11 @@ def main():
"--build-only", "--build-only",
action="store_true", action="store_true",
) )
parser.add_argument(
"--cov",
action="store_true",
help="Generates lcov.info and prints coverage report.",
)
parser.add_argument( parser.add_argument(
"--generate-lcov", "--generate-lcov",
help="Generate an lcov code coverage profile", help="Generate an lcov code coverage profile",
@ -518,7 +540,9 @@ def main():
print() print()
build_target = Triple.from_shorthand(args.arch) build_target = Triple.from_shorthand(args.arch)
collect_coverage = args.generate_lcov if args.cov:
args.generate_lcov = "lcov.info"
collect_coverage = bool(args.generate_lcov)
emulator_cmd = args.emulator.split(" ") if args.emulator else None emulator_cmd = args.emulator.split(" ") if args.emulator else None
build_target = Triple.from_shorthand(args.build_target) if args.build_target else None build_target = Triple.from_shorthand(args.build_target) if args.build_target else None
target = test_target.TestTarget(args.target, build_target, emulator_cmd) target = test_target.TestTarget(args.target, build_target, emulator_cmd)
@ -537,11 +561,8 @@ def main():
# Upload dependencies plus the main crosvm binary for integration tests if the # Upload dependencies plus the main crosvm binary for integration tests if the
# crosvm binary is not excluded from testing. # crosvm binary is not excluded from testing.
extra_files = ( crosvm_binary = find_crosvm_binary(executables).binary_path
[find_crosvm_binary(executables).binary_path] extra_files = [crosvm_binary] if not exclude_crosvm(target.build_triple) else []
if not exclude_crosvm(target.build_triple)
else []
)
test_target.prepare_target(target, extra_files=extra_files) test_target.prepare_target(target, extra_files=extra_files)
@ -556,8 +577,8 @@ def main():
print() print()
print(f"Round {i+1}/{args.repeat}:") print(f"Round {i+1}/{args.repeat}:")
results = [*execute_all(test_executables, target, args.retry + 1, collect_coverage)] results = [*execute_all(test_executables, target, args.retry + 1, collect_coverage)]
if args.generate_lcov: if args.generate_lcov and i == args.repeat - 1:
generate_lcov(results, args.generate_lcov) generate_lcov(results, crosvm_binary, args.generate_lcov, args.cov)
all_results.extend(results) all_results.extend(results)
random.shuffle(test_executables) random.shuffle(test_executables)