| #!/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 $@ |