|  | #!/bin/bash -x | 
|  | # Copyright 2016 The Bazel Authors. All rights reserved. | 
|  | # | 
|  | # 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 script collects code coverage data for C++ sources, after the tests | 
|  | # were executed. | 
|  | # | 
|  | # Bazel C++ code coverage collection support is poor and limited. There is | 
|  | # an ongoing effort to improve this (tracking issue #1118). | 
|  | # | 
|  | # Bazel uses the lcov tool for gathering coverage data. There is also | 
|  | # an experimental support for clang llvm coverage, which uses the .profraw | 
|  | # data files to compute the coverage report. | 
|  | # | 
|  | # This script assumes the following environment variables are set: | 
|  | # - COVERAGE_DIR            Directory containing metadata files needed for | 
|  | #                           coverage collection (e.g. gcda files, profraw). | 
|  | # - COVERAGE_MANIFEST       Location of the instrumented file manifest. | 
|  | # - COVERAGE_GCOV_PATH      Location of gcov. This is set by the TestRunner. | 
|  | # - ROOT                    Location from where the code coverage collection | 
|  | #                           was invoked. | 
|  | # | 
|  | # The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either | 
|  | # gcda or profraw) and uses either lcov or gcov to get the coverage data. | 
|  | # The coverage data is placed in $COVERAGE_OUTPUT_FILE. | 
|  |  | 
|  | # Checks if clang llvm coverage should be used instead of lcov. | 
|  | function uses_llvm() { | 
|  | if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then | 
|  | return 0 | 
|  | fi | 
|  | return 1 | 
|  | } | 
|  |  | 
|  | # Returns 0 if gcov must be used, 1 otherwise. | 
|  | function uses_gcov() { | 
|  | [[ "$GCOV_COVERAGE" -eq "1"  ]] && return 0 | 
|  | return 1 | 
|  | } | 
|  |  | 
|  | function init_gcov() { | 
|  | # Symlink the gcov tool such with a link called gcov. Clang comes with a tool | 
|  | # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise | 
|  | # we would need to invoke it with "llvm-cov gcov"). | 
|  | # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html. | 
|  | GCOV="${COVERAGE_DIR}/gcov" | 
|  | ln -s "${COVERAGE_GCOV_PATH}" "${GCOV}" | 
|  | } | 
|  |  | 
|  | # Computes code coverage data using the clang generated metadata found under | 
|  | # $COVERAGE_DIR. | 
|  | # Writes the collected coverage into the given output file. | 
|  | function llvm_coverage() { | 
|  | local output_file="${1}"; shift | 
|  | export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" | 
|  | "${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \ | 
|  | "${COVERAGE_DIR}"/*.profraw | 
|  | } | 
|  |  | 
|  | # Computes code coverage data using gcda files found under $COVERAGE_DIR. | 
|  | # Writes the collected coverage into the given output file in lcov format. | 
|  | function lcov_coverage() { | 
|  | local output_file="${1}"; shift | 
|  |  | 
|  | cat "${COVERAGE_MANIFEST}" | grep ".gcno$" | while read gcno; do | 
|  | mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno})" | 
|  | cp "${ROOT}/${gcno}" "${COVERAGE_DIR}/${gcno}" | 
|  | done | 
|  |  | 
|  | local lcov_tool="$(which lcov)" | 
|  | if [[ ! -x "$lcov_tool" ]]; then | 
|  | lcov_tool=/usr/bin/lcov | 
|  | fi | 
|  |  | 
|  | # Run lcov over the .gcno and .gcda files to generate the lcov tracefile. | 
|  | # -c                    - Collect coverage data | 
|  | # --no-external         - Do not collect coverage data for system files | 
|  | # --ignore-errors graph - Ignore missing .gcno files; Bazel only instruments | 
|  | #                         some files | 
|  | # -q                    - Quiet mode | 
|  | # --gcov-tool "${GCOV}" - Pass the local symlink to be uses as gcov by lcov | 
|  | # -b /proc/self/cwd     - Use this as a prefix for all source files instead of | 
|  | #                         the current directory | 
|  | # -d "${COVERAGE_DIR}"  - Directory to search for .gcda files | 
|  | # -o "${COVERAGE_OUTPUT_FILE}" - Output file | 
|  | $lcov_tool -c --no-external --ignore-errors graph \ | 
|  | --gcov-tool "${GCOV}" -b /proc/self/cwd \ | 
|  | -d "${COVERAGE_DIR}" -o "${output_file}" | 
|  |  | 
|  | # Fix up the paths to be relative by removing the prefix we specified above. | 
|  | sed -i -e "s*/proc/self/cwd/**g" "${output_file}" | 
|  | } | 
|  |  | 
|  | # Generates a code coverage report in gcov intermediate text format by invoking | 
|  | # gcov and using the profile data (.gcda) and notes (.gcno) files. | 
|  | # | 
|  | # The profile data files are expected to be found under $COVERAGE_DIR. | 
|  | # The notes file are expected to be found under $ROOT. | 
|  | # | 
|  | # - output_file     The location of the file where the generated code coverage | 
|  | #                   report is written. | 
|  | function gcov_coverage() { | 
|  | local output_file="${1}"; shift | 
|  |  | 
|  | # We'll save the standard output of each the gcov command in this log. | 
|  | local gcov_log="$output_file.gcov.log" | 
|  |  | 
|  | # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR | 
|  | # because gcov expects them to be in the same directory. | 
|  | cat "${COVERAGE_MANIFEST}" | grep ".gcno$" | while read gcno_path; do | 
|  |  | 
|  | local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda" | 
|  | # If the gcda file was not found we skip generating coverage from the gcno | 
|  | # file. | 
|  | if [[ -f "$gcda" ]]; then | 
|  | # gcov expects both gcno and gcda files to be in the same directory. | 
|  | # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda | 
|  | # files are expected to be. | 
|  | if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then | 
|  | mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno_path})" | 
|  | cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}" | 
|  | fi | 
|  | # Invoke gcov to generate a code coverage report with the flags: | 
|  | # -i              Output gcov file in an intermediate text format. | 
|  | #                 The output is a single .gcov file per .gcda file. | 
|  | #                 No source code is required. | 
|  | # -o directory    The directory containing the .gcno and | 
|  | #                 .gcda data files. | 
|  | # "${gcda"}       The input file name. gcov is looking for data files | 
|  | #                 named after the input filename without its extension. | 
|  | # gcov produces files called <source file name>.gcov in the current | 
|  | # directory. These contain the coverage information of the source file | 
|  | # they correspond to. One .gcov file is produced for each source | 
|  | # (or header) file containing code which was compiled to produce the | 
|  | # .gcda files. | 
|  | # Don't generate branch coverage (-b) because of a gcov issue that | 
|  | # segfaults when both -i and -b are used (see | 
|  | # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). | 
|  | "${GCOV}" -i -o "$(dirname ${gcda})" "${gcda}" &> "$gcov_log" | 
|  |  | 
|  | # Go through all the files that were created by the gcov command above | 
|  | # and append their content to the output .gcov file. | 
|  | # | 
|  | # For each source file gcov outputs to stdout something like this: | 
|  | # | 
|  | # File 'examples/cpp/hello-lib.cc' | 
|  | # Lines executed:100.00% of 8 | 
|  | # Creating 'hello-lib.cc.gcov' | 
|  | # | 
|  | # We grep the names of the files that were created from that output. | 
|  | cat "$gcov_log" | grep "Creating" | cut -d " " -f 2 | cut -d"'" -f2 | \ | 
|  | while read gcov_file; do | 
|  | echo "Processing $gcov_file" | 
|  | cat "$gcov_file" >> "$output_file" | 
|  | # Remove the intermediate gcov file because it is not useful anymore. | 
|  | rm -f "$gcov_file" | 
|  | done | 
|  | fi | 
|  | done | 
|  | } | 
|  |  | 
|  | function main() { | 
|  | init_gcov | 
|  |  | 
|  | # If llvm code coverage is used, we output the raw code coverage report in | 
|  | # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other | 
|  | # format by LcovMerger. | 
|  | # TODO(#5881): Convert profdata reports to lcov. | 
|  | if uses_llvm; then | 
|  | BAZEL_CC_COVERAGE_TOOL="PROFDATA" | 
|  | fi | 
|  |  | 
|  | # When using either gcov or lcov, have an output file specific to the test | 
|  | # and format used. For lcov we generate a ".dat" output file and for gcov | 
|  | # a ".gcov" output file. It is important that these files are generated under | 
|  | # COVERAGE_DIR. | 
|  | # When this script is invoked by tools/test/collect_coverage.sh either of | 
|  | # these two coverage reports will be picked up by LcovMerger and their | 
|  | # content will be converted and/or merged with other reports to an lcov | 
|  | # format, generating the final code coverage report. | 
|  | case "$BAZEL_CC_COVERAGE_TOOL" in | 
|  | ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;; | 
|  | ("LCOV") lcov_coverage "$COVERAGE_DIR/_cc_coverage.dat" ;; | 
|  | ("PROFDATA") llvm_coverage "$COVERAGE_DIR/_cc_coverage.profdata" ;; | 
|  | (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \ | 
|  | && exit 1 | 
|  | esac | 
|  | } | 
|  |  | 
|  | main |