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