blob: f5abc5612fe60f32e1cd74b9fbc255fb97f90f41 [file]
#!/usr/bin/env bash
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#### CATEGORY=Test
#### PREBUILTS=${PREBUILT_CLANG_DIR} ${PREBUILT_PYTHON3}
### Runs test and exports coverage data.
## usage: fx coverage [--lcov-output-path <path>] [--html-output-dir <directory>] [--json-output-path <path>] [--include-filename-regex <pattern>] [--ignore-filename-regex <pattern>] [--test-output-dir <directory>] [-v|--verbose] [-h|--help] [testName ...]
##
## Runs test and exports coverage data.
##
## --html-output-dir: directory to output test coverage HTML to
## --lcov-output-path: path to write output lcov to
## --json-output-path: path to write output JSON to
## --include-filename-regex: regex pattern to only include matching source files
## --ignore-filename-regex: regex pattern to skip matching source files
## --test-output-dir: directory to use as test output, skipping running fx test
## -v|--verbose: print all executed commands for debugging
## -h|--help: prints help message
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../lib/vars.sh || exit $?
fx-config-read
# Turn on checking after reading config. Otherwise unbound variables in the config read
# can cause the script to fail.
set -euo pipefail
function main() {
local lcov_output_path=''
local html_output_dir=''
local json_output_path=''
local include_filename_regex=''
local ignore_filename_regex=''
local verbose=false
local test_output_dir=''
local ffx_cov_args=()
while [[ $# -ge 1 ]]; do
case "$1" in
--lcov-output-path)
shift
# Get absolute path because `ffx coverage` is executed in a different
# dir.
lcov_output_path="$(realpath ${1})"
;;
--html-output-dir)
shift
# Get absolute path because `ffx coverage` is executed in a different
# dir.
html_output_dir="$(realpath ${1})"
;;
--json-output-path)
shift
# Create directory if it doesn't exist so realpath succeeds.
local json_dir="$(dirname "${1}")"
if [[ ! -d "${json_dir}" ]]; then
mkdir -p "${json_dir}"
fi
# Get absolute path because `ffx coverage` is executed in a different
# dir.
json_output_path="$(realpath "${1}")"
;;
--include-filename-regex)
shift
include_filename_regex="${1}"
;;
--ignore-filename-regex)
shift
ignore_filename_regex="${1}"
;;
--test-output-dir)
shift
test_output_dir="$(realpath ${1})"
;;
-v|--verbose)
verbose=true
;;
-h|--help)
fx-command-help
exit 0
;;
*)
break
;;
esac
shift
done
if [[ -z "${test_output_dir}" ]]; then
test_output_dir="$(mktemp -d)"
trap "rm -rf ${test_output_dir}" EXIT
echo "Running tests to collect coverage data ..."
# Prevent ambient host tools (dldist, ffx, etc.) from emitting .profraw files into the current
# working directory.
export LLVM_PROFILE_FILE=/dev/null
# %p = Process ID, %m = LLVM binary signature (prevents file collisions during parallel runs).
# Pass via --env so that `fx test` propagates it to the actual host test subprocesses.
fx-command-run test --no-timestamp-artifacts \
--env "LLVM_PROFILE_FILE=${test_output_dir}/%p-%m.profraw" \
--ffx-output-directory "${test_output_dir}" "${@}"
else
echo "Using existing test output directory: ${test_output_dir}"
fi
# Ensure debug symbols for pure Bazel tests are registered into .build-id/ before exporting coverage.
"${PREBUILT_PYTHON3}" "${FUCHSIA_DIR}/build/bazel/scripts/copy_bazel_debug_symbols.py" "${FUCHSIA_BUILD_DIR}" 2>/dev/null || true
# Check if any .profraw files were generated before invoking ffx coverage.
if [[ -z "$(find "${test_output_dir}" -name "*.profraw" -print -quit)" ]]; then
fx-warn "No coverage profile (.profraw) files found in ${test_output_dir}"
fx-info "Ensure the test target was built with a coverage variant enabled:"
fx-info " - Fuchsia component tests: --variant coverage"
fx-info " - Host tests: --variant host_coverage"
if [[ -f "${FUCHSIA_BUILD_DIR}/args.gn" ]] && ! grep -q "host_coverage" "${FUCHSIA_BUILD_DIR}/args.gn"; then
if grep -q "coverage" "${FUCHSIA_BUILD_DIR}/args.gn"; then
fx-info "Note: ${FUCHSIA_BUILD_DIR}/args.gn enables 'coverage' (Fuchsia target only), which does not instrument host tests."
else
fx-info "Note: No coverage variant was found in ${FUCHSIA_BUILD_DIR}/args.gn."
fi
fi
fx-info "See //docs/contribute/testing/coverage.md for details."
exit 1
fi
if [[ -n "$(find "${test_output_dir}" -name "*.profdata" -print -quit)" ]]; then
fx-warn "Unexpected coverage profile (.profdata) files found in ${test_output_dir}"
fx-info "Ensure no coverage profiles were generated by other processes."
exit 1
fi
echo "Exporting coverage data ..."
if [[ -n "${lcov_output_path}" ]]; then
ffx_cov_args+=("--export-lcov" "${lcov_output_path}")
fi
if [[ -n "${html_output_dir}" ]]; then
ffx_cov_args+=("--export-html" "${html_output_dir}")
fi
if [[ -n "${json_output_path}" ]]; then
ffx_cov_args+=("--export-json" "${json_output_path}")
fi
if [[ -n "${include_filename_regex}" ]]; then
ffx_cov_args+=("--include-filename-regex" "${include_filename_regex}")
fi
if [[ -n "${ignore_filename_regex}" ]]; then
ffx_cov_args+=("--ignore-filename-regex" "${ignore_filename_regex}")
fi
if [[ ${verbose} == true ]]; then
ffx_cov_args+=("--verbose")
fi
# TODO(https://fxbug.dev/42054992): realpath is not available on Mac, use a
# platform-neutral implementation instead.
readonly _REL_BUILD_DIR="$(realpath -s --relative-to="${FUCHSIA_BUILD_DIR}" "${FUCHSIA_DIR}")"
readonly start_time=$SECONDS
fx-command-run host-tool ffx-coverage \
coverage \
--clang-dir "${PREBUILT_CLANG_DIR}" \
--symbol-index-json "${FUCHSIA_BUILD_DIR}/.symbol-index.json" \
--test-output-dir "${test_output_dir}" \
${ffx_cov_args[@]+"${ffx_cov_args[@]}"} \
--path-remappings "${_REL_BUILD_DIR},${FUCHSIA_DIR}" \
--compilation-dir "${FUCHSIA_BUILD_DIR}"
readonly duration=$((SECONDS - start_time))
echo "Coverage exported in ${duration} seconds."
if [[ -n "${lcov_output_path}" ]]; then
echo "Coverage LCOV exported to ${lcov_output_path}"
fi
if [[ -n "${html_output_dir}" ]]; then
echo "Coverage HTML exported to ${html_output_dir}"
fi
if [[ -n "${json_output_path}" ]]; then
echo "Coverage JSON exported to ${json_output_path}"
fi
}
main $@