ANDROID: Move android scripts to kernel/tests/

kernel/tests/ is shared with platform branches.
The scripts can be combined with platform tests.

Bug: 355559072
Test: None
Change-Id: I1bf03653ccbc11480f944546c1945bb0ea768ff0
Signed-off-by: Hsin-Yi Chen <hsinyichen@google.com>
This commit is contained in:
Hsin-Yi Chen
2024-10-15 19:29:31 +08:00
parent 97da075331
commit 9af9b0c574
6 changed files with 0 additions and 1161 deletions
-3
View File
@@ -1,3 +0,0 @@
bettyzhou@google.com
edliaw@google.com
hwj@google.com
-43
View File
@@ -1,43 +0,0 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
# acloudb .sh is a handy tool dedicated to kernel users to create remote AVDs without having the
# whole AOSP source tree.
# Constants
DEFAULT_ASUITE_HOME="prebuilts/asuite"
DEFAULT_ACLOUD_HOME="$DEFAULT_ASUITE_HOME/acloud/linux-x86"
ACLOUD_BIN="$DEFAULT_ACLOUD_HOME/acloud"
OPT_SKIP_PRERUNCHECK='--skip-pre-run-check'
OPT_DEFAULT_BRANCH=" --branch aosp-main"
# Color constants
BOLD="$(tput bold)"
END="$(tput sgr0)"
GREEN="$(tput setaf 2)"
RED="$(tput setaf 198)"
function adb_checker() {
[[ "$(uname)" != "Linux" ]] && return
if ! which adb &> /dev/null; then
echo -e "\n${RED}Adb not found!${END}"
fi
}
function main() {
adb_checker
EXTRA_OPTIONS=()
if [[ "$1" == "create" ]]; then
EXTRA_OPTIONS+=$OPT_SKIP_PRERUNCHECK
# Add in branch if not specified
ADD_BRANCH=true
for i in "$@"; do
[[ $i == "--branch" ]] && ADD_BRANCH=false
done
if $ADD_BRANCH; then
EXTRA_OPTIONS+=$OPT_DEFAULT_BRANCH
fi
fi
eval "$ACLOUD_BIN" "$@" "${EXTRA_OPTIONS[@]}"
}
main "$@"
@@ -1,554 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This utility generates a single lcov tracefile from a gcov tar file."""
import argparse
import collections
import fnmatch
import glob
import json
import logging
import os
import pathlib
import shutil
import sys
import tarfile
LCOV = "lcov"
# Relative to the root of the source tree..
OUTPUT_COV_DIR = os.path.join("out", "coverage")
PREBUILT_LLVM_COV_PATH = os.path.join(
"prebuilts", "clang", "host", "linux-x86", "llvm-binutils-stable",
"llvm-cov"
)
EXCLUDED_FILES = [
"*/security/selinux/av_permissions.h",
"*/security/selinux/flask.h",
]
def create_llvm_gcov_sh(
llvm_cov_filename: str,
llvm_gcov_sh_filename: str,
) -> None:
"""Create a shell script that is compatible with gcov.
Args:
llvm_cov_filename: The absolute path to llvm-cov.
llvm_gcov_sh_filename: The path to the script to be created.
"""
file_path = pathlib.Path(llvm_gcov_sh_filename)
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(f'#!/bin/bash\nexec {llvm_cov_filename} gcov "$@"')
os.chmod(llvm_gcov_sh_filename, 0o755)
def generate_lcov_tracefile(
gcov_dir: str,
kernel_source: str,
gcov_filename: str,
tracefile_filename: str,
included_files: [],
) -> None:
"""Call lcov to create tracefile based on gcov data files.
Args:
gcov_dir: Directory that contains the extracted gcov data files as retreived
from debugfs.
kernel_source: Directory containing the kernel source same as what was used
to build system under test.
gcov_filename: The absolute path to gcov or a compatible script.
tracefile_filename: The name of tracefile to create.
included_files: List of source file pattern to include in tracefile. Can be
empty in which case include allo source.
"""
exclude_args = " ".join([f'--exclude "{f}"' for f in EXCLUDED_FILES])
include_args = (
" ".join([f'--include "{f[0]}"' for f in included_files])
if included_files is not None
else ""
)
logging.info("Running lcov on %s", gcov_dir)
lcov_cmd = (
f"{LCOV} -q "
"--ignore-errors=source "
"--rc branch_coverage=1 "
f"-b {kernel_source} "
f"-d {gcov_dir} "
f"--gcov-tool {gcov_filename} "
f"{exclude_args} "
f"{include_args} "
"--ignore-errors gcov,gcov,unused,unused "
"--capture "
f"-o {tracefile_filename} "
)
os.system(lcov_cmd)
def update_symlink_from_mapping(filepath: str, prefix_mappings: {}) -> bool:
"""Update symbolic link based on prefix mappings.
It will attempt to update the given symbolic link based on the prefix
mappings. For every "from" prefix that matches replace with the new "to"
value. If the resulting path doesn't exist, try the next.
Args:
filepath: Path of symbolic link to update.
prefix_mappings: A multimap where the key is the "from" prefix to match, and
the value is an array of "to" values to attempt to replace with.
Returns:
True or false depending on the whether symbolic link was successfully
updated to a new path that exists.
"""
link_target = os.readlink(filepath)
for old_prefix, new_prefix_list in prefix_mappings.items():
for new_prefix in new_prefix_list:
if link_target.startswith(old_prefix):
new_target = os.path.abspath(
link_target.replace(old_prefix, new_prefix)
)
if not os.path.exists(new_target):
continue
os.unlink(filepath) # Remove the old symbolic link
os.symlink(new_target, filepath) # Create the updated link
return True
return False
def correct_symlinks_in_directory(directory: str, prefix_mappings: {}) -> None:
"""Recursively traverses a directory, updating symbolic links.
Replaces 'old_prefix' in the link destination with 'new_prefix'.
Args:
directory: The root directory to traverse.
prefix_mappings: Dictionary where the keys are the old prefixes and the
values are the new prefixes
"""
logging.info("Fixing up symbolic links in %s", directory)
for root, _, files in os.walk(directory):
for filename in files:
filepath = os.path.join(root, filename)
if os.path.islink(filepath):
if not update_symlink_from_mapping(filepath, prefix_mappings):
logging.error(
"Unable to update link at %s with any prefix mappings: %s",
filepath,
prefix_mappings,
)
sys.exit(-1)
def find_most_recent_tarfile(path: str, pattern: str = "*.tar.gz") -> str:
"""Attempts to find a valid tar file given the location.
If location is a directory finds the most recent tarfile or if location is a
a valid tar file returns, if neither of these return None.
Args:
path (str): The path to either a tarfile or a directory.
pattern (str, optional): Glob pattern for matching tarfiles. Defaults to
"*.tar.gz".
Returns:
str: The path to the most recent tarfile found, or the original path
if it was a valid tarfile. None if no matching tarfiles are found.
"""
if os.path.isfile(path):
if tarfile.is_tarfile(path):
return path # Path is a valid tarfile
return None # Path is a file but not a tar file
if os.path.isdir(path):
results = []
for root, _, files in os.walk(path):
for file in files:
if fnmatch.fnmatch(file, pattern):
full_path = os.path.join(root, file)
results.append((full_path, os.path.getmtime(full_path)))
if results:
return max(results, key=lambda item: item[1])[
0
] # Return path of the most recent one
else:
return None # No tarfiles found in the directory
return None # Path is neither a tarfile nor a directory
def make_absolute(path: str, base_dir: str) -> str:
if os.path.isabs(path):
return path
return os.path.join(base_dir, path)
def append_slash(path: str) -> str:
if path is not None and path[-1] != "/":
path += "/"
return path
def update_multimap_from_json(
json_file: str, base_dir: str, result_multimap: collections.defaultdict
) -> None:
"""Reads 'to' and 'from' fields from a JSON file and updates a multimap.
'from' refers to a bazel sandbox directory.
'to' refers to the output directory of gcno files.
The multimap is implemented as a dictionary of lists allowing multiple 'to'
values for each 'from' key.
Sample input:
[
{
"from": "/sandbox/1/execroot/_main/out/android-mainline/common",
"to": "bazel-out/k8-fastbuild/bin/common/kernel_x86_64/kernel_x86_64_gcno"
},
{
"from": "/sandbox/2/execroot/_main/out/android-mainline/common",
"to": "bazel-out/k8-fastbuild/bin/common-modules/virtual-device/virtual_device_x86_64/virtual_device_x86_64_gcno"
}
]
Args:
json_file: The path to the JSON file.
base_dir: Used if either of the 'to' or 'from' paths are relative to make
them absolute by prepending this base_dir value.
result_multimap: A multimap that is updated with every 'to' and 'from'
found.
Returns:
The updated dictionary.
"""
with open(json_file, "r") as file:
data = json.load(file)
for item in data:
to_value = append_slash(item.get("to"))
from_value = append_slash(item.get("from"))
if to_value and from_value:
to_value = make_absolute(to_value, base_dir)
from_value = make_absolute(from_value, base_dir)
result_multimap[from_value].append(to_value)
def read_gcno_mapping_files(
search_dir_pattern: str,
base_dir: str,
result_multimap: collections.defaultdict
) -> None:
"""Search a directory for gcno_mapping."""
found = False
pattern = os.path.join(search_dir_pattern, "gcno_mapping.*.json")
for filepath in glob.iglob(pattern, recursive=False):
found = True
logging.info("Reading %s", filepath)
update_multimap_from_json(filepath, base_dir, result_multimap)
if not found:
logging.error("No gcno_mapping in %s", search_dir_pattern)
def read_gcno_dir(
gcno_dir: str, result_multimap: collections.defaultdict
) -> None:
"""Read a directory containing gcno_mapping and gcno files."""
multimap = collections.defaultdict(list)
read_gcno_mapping_files(gcno_dir, gcno_dir, multimap)
to_value = append_slash(os.path.abspath(gcno_dir))
for from_value in multimap:
result_multimap[from_value].append(to_value)
def get_testname_from_filename(file_path: str) -> str:
filename = os.path.basename(file_path)
if "_kernel_coverage" in filename:
tmp = filename[: filename.find("_kernel_coverage")]
testname = tmp[: tmp.rfind("_")]
else:
testname = filename[: filename.rfind("_")]
return testname
def unpack_gcov_tar(file_path: str, output_dir: str) -> str:
"""Unpack the tar file into the specified directory.
Args:
file_path: The path of the tar file to be unpacked.
output_dir: The root directory where the unpacked folder will reside.
Returns:
The path of extracted data.
"""
testname = get_testname_from_filename(file_path)
logging.info(
"Unpacking %s for test %s...", os.path.basename(file_path), testname
)
test_dest_dir = os.path.join(output_dir, testname)
if os.path.exists(test_dest_dir):
shutil.rmtree(test_dest_dir)
os.makedirs(test_dest_dir)
shutil.unpack_archive(file_path, test_dest_dir, "tar")
return test_dest_dir
def get_parent_path(path: str, levels_up: int) -> str:
"""Goes up a specified number of levels from a given path.
Args:
path: The path to find desired ancestor.
levels_up: The number of levels up to go.
Returns:
The desired ancestor of the given path.
"""
p = pathlib.Path(path)
for _ in range(levels_up):
p = p.parent
return str(p)
def get_kernel_repo_dir() -> str:
this_script_full_path = os.path.abspath(__file__)
# Assume this script is placed this many places from
# the base kernel repo directory, e.g.:
# kernel_repo/common/tools/testing/android/bin/<this_script>
return get_parent_path(this_script_full_path, 6)
class Config:
"""The input and output paths of this script."""
def __init__(self, repo_dir: str, llvm_cov_path: str, tmp_dir: str):
"""Each argument can be empty."""
self._repo_dir = os.path.abspath(repo_dir) if repo_dir else None
self._llvm_cov_path = (
os.path.abspath(llvm_cov_path) if llvm_cov_path else None
)
self._tmp_dir = os.path.abspath(tmp_dir) if tmp_dir else None
self._repo_out_dir = None
@property
def repo_dir(self) -> str:
if not self._repo_dir:
self._repo_dir = get_kernel_repo_dir()
return self._repo_dir
def _get_repo_path(self, rel_path: str) -> str:
repo_path = os.path.join(self.repo_dir, rel_path)
if not os.path.exists(repo_path):
logging.error(
"%s does not exist. If this script is not in the source directory,"
" specify --repo-dir. If you do not have full kernel source,"
" specify --llvm-cov, --gcno-dir, and --tmp-dir.",
repo_path,
)
sys.exit(-1)
return repo_path
@property
def llvm_cov_path(self) -> str:
if not self._llvm_cov_path:
self._llvm_cov_path = self._get_repo_path(PREBUILT_LLVM_COV_PATH)
return self._llvm_cov_path
@property
def repo_out_dir(self) -> str:
if not self._repo_out_dir:
self._repo_out_dir = self._get_repo_path("out")
return self._repo_out_dir
@property
def tmp_dir(self) -> str:
if not self._tmp_dir:
# Temporary directory does not have to exist.
self._tmp_dir = os.path.join(self.repo_dir, OUTPUT_COV_DIR)
return self._tmp_dir
@property
def llvm_gcov_sh_path(self) -> str:
return os.path.join(self.tmp_dir, "tmp", "llvm-gcov.sh")
def main() -> None:
arg_parser = argparse.ArgumentParser(
description="Generate lcov tracefiles from gcov file dumps"
)
arg_parser.add_argument(
"-t",
dest="tar_location",
required=True,
help=(
"Either a path to a gcov tar file or a directory that contains gcov"
" tar file(s). The gcov tar file is expected to be created from"
" Tradefed. If a directory is used, will search the entire directory"
" for files matching *_kernel_coverage*.tar.gz and select the most"
" recent one."
),
)
arg_parser.add_argument(
"-o",
dest="out_file",
required=False,
help="Name of output tracefile generated. Default: cov.info",
default="cov.info",
)
arg_parser.add_argument(
"--include",
action="append",
nargs=1,
required=False,
help=(
"File pattern of source file(s) to include in generated tracefile."
" Multiple patterns can be specified by using multiple --include"
" command line switches. If no includes are specified all source is"
" included."
),
)
arg_parser.add_argument(
"--repo-dir",
required=False,
help="Root directory of kernel source"
)
arg_parser.add_argument(
"--dist-dir",
dest="dist_dirs",
action="append",
default=[],
required=False,
help="Dist directory containing gcno mapping files"
)
arg_parser.add_argument(
"--gcno-dir",
dest="gcno_dirs",
action="append",
default=[],
required=False,
help="Path to an extracted .gcno.tar.gz"
)
arg_parser.add_argument(
"--llvm-cov",
required=False,
help=(
"Path to llvm-cov. Default: " +
os.path.join("<repo dir>", PREBUILT_LLVM_COV_PATH)
)
)
arg_parser.add_argument(
"--tmp-dir",
required=False,
help=(
"Path to the directory where the temporary files are created."
" Default: " + os.path.join("<repo dir>", OUTPUT_COV_DIR)
)
)
arg_parser.add_argument(
"--verbose",
action="store_true",
default=False,
help="Enable verbose logging",
)
args = arg_parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.WARNING)
if shutil.which(LCOV) is None:
logging.error(
"%s is not found and is required for this script. Please install from:",
LCOV,
)
logging.critical(" https://github.com/linux-test-project/lcov")
sys.exit(-1)
if args.repo_dir and not os.path.isdir(args.repo_dir):
logging.error("%s is not a directory.", args.repo_dir)
sys.exit(-1)
if args.llvm_cov and not os.path.isfile(args.llvm_cov):
logging.error("%s is not a file.", args.llvm_cov)
sys.exit(-1)
for gcno_dir in args.gcno_dirs + args.dist_dirs:
if not os.path.isdir(gcno_dir):
logging.error("%s is not a directory.", gcno_dir)
sys.exit(-1)
config = Config(args.repo_dir, args.llvm_cov, args.tmp_dir)
gcno_mappings = collections.defaultdict(list)
if not args.gcno_dirs and not args.dist_dirs:
dist_dir_pattern = os.path.join(config.repo_out_dir, "**", "dist")
read_gcno_mapping_files(dist_dir_pattern, config.repo_dir, gcno_mappings)
for dist_dir in args.dist_dirs:
read_gcno_mapping_files(dist_dir, config.repo_dir, gcno_mappings)
for gcno_dir in args.gcno_dirs:
read_gcno_dir(gcno_dir, gcno_mappings)
if not gcno_mappings:
# read_gcno_mapping_files prints the error messages
sys.exit(-1)
tar_file = find_most_recent_tarfile(
args.tar_location, pattern="*kernel_coverage_*.tar.gz"
)
if tar_file is None:
logging.error("Unable to find a gcov tar under %s", args.tar_location)
sys.exit(-1)
gcov_dir = unpack_gcov_tar(tar_file, config.tmp_dir)
correct_symlinks_in_directory(gcov_dir, gcno_mappings)
create_llvm_gcov_sh(
config.llvm_cov_path,
config.llvm_gcov_sh_path,
)
generate_lcov_tracefile(
gcov_dir,
config.repo_dir,
config.llvm_gcov_sh_path,
args.out_file,
args.include,
)
if __name__ == "__main__":
main()
-227
View File
@@ -1,227 +0,0 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
BAZEL=tools/bazel
BIN_DIR=common/tools/testing/android/bin
ACLOUD=$BIN_DIR/acloudb.sh
TRADEFED=prebuilts/tradefed/filegroups/tradefed/tradefed.sh
DEFAULT_DIST_DIR=out/virtual_device_x86_64/dist
TESTSDIR=/tmp/kselftests
LOG_DIR=$PWD/out/test_logs/$(date +%Y%m%d_%H%M%S)
JDK_PATH=prebuilts/jdk/jdk11/linux-x86
print_help() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "This script runs Selftests on an Android device."
echo "Please run the script with full path common/tools/testing/android/bin/."
echo "Building kernel and launching virtual device are enabled by default;"
echo "use options to skip the workflow."
echo ""
echo "Available options:"
echo " --skip-kernel-build Skip the kernel building step"
echo " --skip-cvd-launch Skip the CVD launch step"
echo " --skip-cvd-kill Do not kill CVD launched by running this script"
echo " -d, --dist-dir=DIR The kernel dist dir (default is $DEFAULT_DIST_DIR)"
echo " -s, --serial=SERIAL The device serial number."
echo " If serial is specified, virtual device launch will be skipped"
echo " -t, --test=TEST_NAME The test target name. Can be repeated"
echo " If test is not specified, all kselftests will be run"
echo " --gcov Collect coverage data from the test result"
echo " -h, --help Display this help message and exit"
echo ""
echo "Examples:"
echo "$0"
echo "$0 -t kselftest_size_test_get_size -t kselftest_binderfs_binderfs_test"
echo "$0 -s 127.0.0.1:45549"
echo ""
exit 0
}
BUILD_KERNEL=true
LAUNCH_CVD=true
KILL_CVD=true
DIST_DIR=$DEFAULT_DIST_DIR
SERIAL_NUMBER=
MODULE_NAME="selftests"
TEST_FILTERS=
SELECTED_TESTS=
GCOV=false
GCOV_DIST_DIR=
while test $# -gt 0; do
case "$1" in
-h|--help)
print_help
;;
--skip-kernel-build)
BUILD_KERNEL=false
shift
;;
--skip-cvd-launch)
LAUNCH_CVD=false
shift
;;
--skip-cvd-kill)
KILL_CVD=false
shift
;;
-d)
shift
if test $# -gt 0; then
DIST_DIR=$1
GCOV_DIST_DIR=$DIST_DIR
else
echo "kernel distribution directory is not specified"
exit 1
fi
shift
;;
--dist-dir*)
DIST_DIR=$(echo $1 | sed -e "s/^[^=]*=//g")
GCOV_DIST_DIR=$DIST_DIR
shift
;;
-s)
shift
if test $# -gt 0; then
SERIAL_NUMBER=$1
BUILD_KERNEL=false
LAUNCH_CVD=false
KILL_CVD=false
else
echo "device serial is not specified"
exit 1
fi
shift
;;
--serial*)
BUILD_KERNEL=false
LAUNCH_CVD=false
KILL_CVD=false
SERIAL_NUMBER=$(echo $1 | sed -e "s/^[^=]*=//g")
shift
;;
-t)
shift
if test $# -gt 0; then
TEST_NAME=$1
SELECTED_TESTS+="$TEST_NAME "
TEST_FILTERS+="--include-filter '$MODULE_NAME $TEST_NAME' "
else
echo "test name is not specified"
exit 1
fi
shift
;;
--test*)
TEST_NAME=$(echo $1 | sed -e "s/^[^=]*=//g")
SELECTED_TESTS+="$TEST_NAME "
TEST_FILTERS+="--include-filter '$MODULE_NAME $TEST_NAME'"
shift
;;
--gcov)
GCOV=true
shift
;;
*)
echo "unknown argument: $1"
exit 1
;;
esac
done
if $BUILD_KERNEL; then
BUILD_FLAGS=
if $GCOV; then
BUILD_FLAGS+=" --gcov"
fi
echo "Building kernel..."
# TODO: add support to build kernel for physical device
$BAZEL run $BUILD_FLAGS //common-modules/virtual-device:virtual_device_x86_64_dist -- \
--destdir=$DIST_DIR
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "Build kernel succeeded"
else
echo "Build kernel failed with exit code $exit_code"
exit 1
fi
fi
if $LAUNCH_CVD; then
echo "Launching cvd..."
CVD_OUT=$($ACLOUD create --local-kernel-image $DIST_DIR)
echo $CVD_OUT
INSTANCE_NAME=$(echo "$CVD_OUT" | grep -o "ins-[^\[]*")
SERIAL_STRING=$(echo "$CVD_OUT" | grep -oE 'device serial: ([0-9]+\.){3}[0-9]+:[0-9]+')
SERIAL_NUMBER=$(echo "$SERIAL_STRING" | sed 's/device serial: //')
echo "acloud launched device $SERIAL_NUMBER with instance $INSTANCE_NAME"
fi
if [ -z "$SERIAL_NUMBER" ]; then
echo "Device serial is not provided by acloud or by command line flag -s|--serial flag"
exit 1
else
echo "Test with device: $SERIAL_NUMBER"
fi
echo "Get abi from device $SERIAL_NUMBER"
ABI=$(adb -s $SERIAL_NUMBER shell getprop ro.product.cpu.abi)
echo "Building kselftests according to device $SERIAL_NUMBER ro.product.cpu.abi $ABI ..."
case $ABI in
arm64*)
$BAZEL run //common:kselftest_tests_arm64_install -- --destdir $TESTSDIR
;;
x86_64*)
$BAZEL run //common:kselftest_tests_x86_64_install -- --destdir $TESTSDIR
;;
*)
echo "$ABI not supported"
exit 1
;;
esac
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "Build kselftest succeeded"
else
echo "Build kselftest failed with exit code $exit_code"
exit 1
fi
if [ -z "$SELECTED_TESTS" ]; then
echo "Running all kselftests with device $SERIAL_NUMBER..."
TEST_FILTERS="--include-filter $MODULE_NAME"
else
echo "Running $SELECTED_TESTS with device $SERIAL_NUMBER ..."
fi
tf_cli="JAVA_HOME=$JDK_PATH PATH=$JDK_PATH/bin:$PATH $TRADEFED run commandAndExit \
template/local_min --template:map test=suite/test_mapping_suite \
$TEST_FILTERS --tests-dir=$TESTSDIR --log-file-path=$LOG_DIR \
--primary-abi-only -s $SERIAL_NUMBER"
if $GCOV; then
tf_cli+=" --coverage --coverage-toolchain GCOV_KERNEL --auto-collect GCOV_KERNEL_COVERAGE"
fi
echo "Running tradefed command: $tf_cli"
eval $tf_cli
echo "Test finished. Log directory: $LOG_DIR"
if $LAUNCH_CVD && $KILL_CVD; then
echo "Deleting cvd instance $INSTANCE_NAME ..."
$ACLOUD delete --instance-names $INSTANCE_NAME
fi
if $GCOV; then
CREATE_TRACEFILE_CLI="common/tools/testing/android/bin/create-tracefile.py \
-t $LOG_DIR -o $LOG_DIR/cov.info"
if [ -n "$GCOV_DIST_DIR" ]; then
CREATE_TRACEFILE_CLI+=" --dist-dir $GCOV_DIST_DIR"
fi
echo "Creating tracefile ..."
$CREATE_TRACEFILE_CLI && echo "Created tracefile at $LOG_DIR/cov.info"
fi
-236
View File
@@ -1,236 +0,0 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
BAZEL=tools/bazel
BIN_DIR=common/tools/testing/android/bin
ACLOUD=$BIN_DIR/acloudb.sh
TRADEFED=prebuilts/tradefed/filegroups/tradefed/tradefed.sh
DEFAULT_DIST_DIR=out/virtual_device_x86_64/dist
TESTSDIR=/tmp/kunit
LOG_DIR=$PWD/out/test_logs/$(date +%Y%m%d_%H%M%S)
JDK_PATH=prebuilts/jdk/jdk11/linux-x86
print_help() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "This script runs KUnit tests on an Android device."
echo "Please run the script with full path common/tools/testing/android/bin/."
echo "Building kernel and launching virtual device are enabled by default;"
echo "use options to skip the workflow."
echo ""
echo "Available options:"
echo " --skip-kernel-build Skip the kernel building step"
echo " --skip-cvd-launch Skip the CVD launch step"
echo " --skip-cvd-kill Do not kill CVD launched by running this script"
echo " -d, --dist-dir=DIR The kernel dist dir (default is $DEFAULT_DIST_DIR)"
echo " -s, --serial=SERIAL The device serial number."
echo " If serial is specified, virtual device launch will be skipped"
echo " -t, --test=TEST_NAME The test target name. Can be repeated"
echo " If test is not specified, all tests will be run"
echo " --gcov Collect coverage data from the test result"
echo " -h, --help Display this help message and exit"
echo ""
echo "Examples:"
echo "$0"
echo "$0 -t regmap-kunit.ko -t lib_test.ko"
echo "$0 -s 127.0.0.1:45549"
echo ""
exit 0
}
BUILD_KERNEL=true
LAUNCH_CVD=true
KILL_CVD=true
DIST_DIR=$DEFAULT_DIST_DIR
SERIAL_NUMBER=
MODULE_NAME="kunit"
TEST_FILTERS=
SELECTED_TESTS=
GCOV=false
GCOV_DIST_DIR=
while test $# -gt 0; do
case "$1" in
-h|--help)
print_help
;;
--skip-kernel-build)
BUILD_KERNEL=false
shift
;;
--skip-cvd-launch)
LAUNCH_CVD=false
shift
;;
--skip-cvd-kill)
KILL_CVD=false
shift
;;
-d)
shift
if test $# -gt 0; then
DIST_DIR=$1
GCOV_DIST_DIR=$DIST_DIR
else
echo "kernel distribution directory is not specified"
exit 1
fi
shift
;;
--dist-dir*)
DIST_DIR=$(echo $1 | sed -e "s/^[^=]*=//g")
GCOV_DIST_DIR=$DIST_DIR
shift
;;
-s)
shift
if test $# -gt 0; then
SERIAL_NUMBER=$1
BUILD_KERNEL=false
LAUNCH_CVD=false
KILL_CVD=false
else
echo "device serial is not specified"
exit 1
fi
shift
;;
--serial*)
BUILD_KERNEL=false
LAUNCH_CVD=false
KILL_CVD=false
SERIAL_NUMBER=$(echo $1 | sed -e "s/^[^=]*=//g")
shift
;;
-t)
shift
if test $# -gt 0; then
TEST_NAME=$1
SELECTED_TESTS+="$TEST_NAME "
TEST_FILTERS+="--include-filter '$MODULE_NAME $TEST_NAME' "
else
echo "test name is not specified"
exit 1
fi
shift
;;
--test*)
TEST_NAME=$(echo $1 | sed -e "s/^[^=]*=//g")
SELECTED_TESTS+="$TEST_NAME "
TEST_FILTERS+="--include-filter '$MODULE_NAME $TEST_NAME'"
shift
;;
--gcov)
GCOV=true
shift
;;
*)
echo "unknown argument: $1"
exit 1
;;
esac
done
# Kernel and modules must be built with the same flags.
BUILD_FLAGS=
if $GCOV; then
BUILD_FLAGS+=" --gcov"
fi
if $BUILD_KERNEL; then
echo "Building kernel..."
# TODO: add support to build kernel for physical device
$BAZEL run $BUILD_FLAGS //common-modules/virtual-device:virtual_device_x86_64_dist -- \
--destdir=$DIST_DIR
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "Command succeeded"
else
echo "Command failed with exit code $exit_code"
exit 1
fi
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "Build kernel succeeded"
else
echo "Build kernel failed with exit code $exit_code"
exit 1
fi
fi
if $LAUNCH_CVD; then
echo "Launching cvd..."
CVD_OUT=$($ACLOUD create --local-kernel-image $DIST_DIR)
echo $CVD_OUT
INSTANCE_NAME=$(echo "$CVD_OUT" | grep -o "ins-[^\[]*")
SERIAL_STRING=$(echo "$CVD_OUT" | grep -oE 'device serial: ([0-9]+\.){3}[0-9]+:[0-9]+')
SERIAL_NUMBER=$(echo "$SERIAL_STRING" | sed 's/device serial: //')
echo "acloud launched device $SERIAL_NUMBER with instance $INSTANCE_NAME"
fi
if [ -z "$SERIAL_NUMBER" ]; then
echo "Device serial is not provided by acloud or by command line flag -s|--serial flag"
exit 1
else
echo "Test with device: $SERIAL_NUMBER"
fi
echo "Get abi from device $SERIAL_NUMBER"
ABI=$(adb -s $SERIAL_NUMBER shell getprop ro.product.cpu.abi)
echo "Building KUnit tests according to device $SERIAL_NUMBER ro.product.cpu.abi $ABI ..."
case $ABI in
arm64*)
TESTSDIR+="_arm64"
$BAZEL run $BUILD_FLAGS //common:kunit_tests_arm64 -- -v --destdir $TESTSDIR
;;
x86_64*)
TESTSDIR+="_x86_64"
$BAZEL run $BUILD_FLAGS //common:kunit_tests_x86_64 -- -v --destdir $TESTSDIR
;;
*)
echo "$ABI not supported"
exit 1
;;
esac
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "Build kunit succeeded"
else
echo "Build kunit failed with exit code $exit_code"
exit 1
fi
if [ -z "$SELECTED_TESTS" ]; then
echo "Running all KUnit tests with device $SERIAL_NUMBER..."
TEST_FILTERS="--include-filter $MODULE_NAME"
else
echo "Running $SELECTED_TESTS with device $SERIAL_NUMBER ..."
fi
tf_cli="JAVA_HOME=$JDK_PATH PATH=$JDK_PATH/bin:$PATH $TRADEFED run commandAndExit \
template/local_min --template:map test=suite/test_mapping_suite \
$TEST_FILTERS --tests-dir=$TESTSDIR --log-file-path=$LOG_DIR \
--primary-abi-only -s $SERIAL_NUMBER"
if $GCOV; then
tf_cli+=" --coverage --coverage-toolchain GCOV_KERNEL --auto-collect GCOV_KERNEL_COVERAGE"
fi
echo "Runing tradefed command: $tf_cli"
eval $tf_cli
if $LAUNCH_CVD && $KILL_CVD; then
echo "Test finished. Deleting cvd instance $INSTANCE_NAME ..."
$ACLOUD delete --instance-names $INSTANCE_NAME
fi
if $GCOV; then
CREATE_TRACEFILE_CLI="common/tools/testing/android/bin/create-tracefile.py \
-t $LOG_DIR -o $LOG_DIR/cov.info"
if [ -n "$GCOV_DIST_DIR" ]; then
CREATE_TRACEFILE_CLI+=" --dist-dir $GCOV_DIST_DIR"
fi
echo "Creating tracefile ..."
$CREATE_TRACEFILE_CLI && echo "Created tracefile at $LOG_DIR/cov.info"
fi
-98
View File
@@ -1,98 +0,0 @@
HOW TO COLLECT KERNEL CODE COVERAGE FROM A TRADEFED TEST RUN
============================================================
## Build and use a kernel with GCOV profile enabled
Build your kernel with the [`--gcov`](https://android.googlesource.com/kernel/build/+/refs/heads/main/kleaf/docs/gcov.md) option to enable
GCOV profiling from the kernel. This will also trigger the build to save the required *.gcno files needed to viewing the collected count data.
For example to build a Cuttlefish (CF) kernel with GCOV profiling enabled run:
```
$ bazel run --gcov //common-modules/virtual-device:virtual_device_x86_64_dist
```
## Run your test(s) using tradefed.sh with kernel coverage collection enabled
'tradefed.sh' can be used to run a number of different types of tests. Adding the appropriate coverage flags
to the tradefed call will trigger tradefed to take care of mounting debugfs, reseting the gcov counts prior
to test run, and the collection of gcov data files from debugfs after test completion.
These coverage arguments are:
```
--coverage --coverage-toolchain GCOV_KERNEL --auto-collect GCOV_KERNEL_COVERAGE
```
The following is a full example call running just the `kselftest_net_socket` test in the
selftests test suite that exists under the 'bazel-bin/common/testcases' directory. The artifact
output has been redirected to 'tf-logs' for easier reference needed in the next step.
```
$ prebuilts/tradefed/filegroups/tradefed/tradefed.sh run commandAndExit \
template/local_min --template:map test=suite/test_mapping_suite \
--include-filter 'selftests kselftest_net_socket' --tests-dir=bazel-bin/common/testcases/ \
--primary-abi-only --log-file-path tf-logs \
--coverage --coverage-toolchain GCOV_KERNEL \
--auto-collect GCOV_KERNEL_COVERAGE
```
## Create an lcov tracefile out of the gcov tar artifact from test run
The previously mentioned tradefed run will produce a tar file artifact in the
tradefed log folder with a name similar to <test>_kernel_coverage_*.tar.gz.
This tar file is an archive of all the gcov data files collected into debugfs/
from the profiled device. In order to make it easier to work with this data,
it needs to be converted to a single lcov tracefile.
The script 'create-tracefile.py' facilitates this generation by handling the
required unpacking, file path corrections and ultimate 'lcov' call.
An example where we generate a tracefile only including results from net/socket.c.
(If no source files are specified as included, then all source file data is used):
```
$ ./common/tools/testing/android/bin/create-tracefile.py -t tf-logs/ --include net/socket.c
```
This will create a local tracefile named 'cov.info'.
## Visualizing Results
With the created tracefile there a number of different ways to view coverage data from it.
Check out 'man lcov' for more options.
### 1. Text Options
#### 1.1 Summary
```
$ lcov --summary --rc lcov_branch_coverage=1 cov.info
Reading tracefile cov.info_fix
Summary coverage rate:
lines......: 6.0% (81646 of 1370811 lines)
functions..: 9.6% (10285 of 107304 functions)
branches...: 3.7% (28639 of 765538 branches)
```
#### 1.2 List
```
$ lcov --list --rc lcov_branch_coverage=1 cov.info
Reading tracefile cov.info_fix
|Lines |Functions|Branches
Filename |Rate Num|Rate Num|Rate Num
================================================================================
[/usr/local/google/home/joefradley/dev/common-android-mainline-2/common/]
arch/x86/crypto/aesni-intel_glue.c |23.9% 623|22.2% 36|15.0% 240
arch/x86/crypto/blake2s-glue.c |50.0% 28|50.0% 2|16.7% 30
arch/x86/crypto/chacha_glue.c | 0.0% 157| 0.0% 10| 0.0% 80
<truncated>
virt/lib/irqbypass.c | 0.0% 137| 0.0% 6| 0.0% 88
================================================================================
Total:| 6.0% 1369k| 9.6% 0M| 3.7% 764k
```
### 2. HTML
The `lcov` tool `genhtml` is used to generate html. To create html with the default settings:
```
$ genhtml --branch-coverage -o html cov.info
```
The page can be viewed at `html\index.html`.
Options of interest:
* `--frame`: Creates a left hand macro view in a source file view.
* `--missed`: Helpful if you want to sort by what source is missing the most as opposed to the default coverage percentages.