iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 1 | #!/bin/bash -x |
| 2 | # Copyright 2016 The Bazel Authors. All rights reserved. |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | |
| 16 | # This script collects code coverage data for C++ sources, after the tests |
| 17 | # were executed. |
| 18 | # |
| 19 | # Bazel C++ code coverage collection support is poor and limited. There is |
| 20 | # an ongoing effort to improve this (tracking issue #1118). |
| 21 | # |
| 22 | # Bazel uses the lcov tool for gathering coverage data. There is also |
| 23 | # an experimental support for clang llvm coverage, which uses the .profraw |
| 24 | # data files to compute the coverage report. |
| 25 | # |
| 26 | # This script assumes the following environment variables are set: |
| 27 | # - COVERAGE_DIR Directory containing metadata files needed for |
| 28 | # coverage collection (e.g. gcda files, profraw). |
| 29 | # - COVERAGE_MANIFEST Location of the instrumented file manifest. |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 30 | # - COVERAGE_GCOV_PATH Location of gcov. This is set by the TestRunner. |
| 31 | # - ROOT Location from where the code coverage collection |
| 32 | # was invoked. |
| 33 | # |
| 34 | # The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either |
| 35 | # gcda or profraw) and uses either lcov or gcov to get the coverage data. |
| 36 | # The coverage data is placed in $COVERAGE_OUTPUT_FILE. |
| 37 | |
| 38 | # Checks if clang llvm coverage should be used instead of lcov. |
| 39 | function uses_llvm() { |
| 40 | if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then |
| 41 | return 0 |
| 42 | fi |
| 43 | return 1 |
| 44 | } |
| 45 | |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 46 | # Returns 0 if gcov must be used, 1 otherwise. |
| 47 | function uses_gcov() { |
| 48 | [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0 |
| 49 | return 1 |
| 50 | } |
| 51 | |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 52 | function init_gcov() { |
| 53 | # Symlink the gcov tool such with a link called gcov. Clang comes with a tool |
| 54 | # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise |
| 55 | # we would need to invoke it with "llvm-cov gcov"). |
| 56 | # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html. |
| 57 | GCOV="${COVERAGE_DIR}/gcov" |
| 58 | ln -s "${COVERAGE_GCOV_PATH}" "${GCOV}" |
| 59 | } |
| 60 | |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 61 | # Computes code coverage data using the clang generated metadata found under |
| 62 | # $COVERAGE_DIR. |
| 63 | # Writes the collected coverage into the given output file. |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 64 | function llvm_coverage() { |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 65 | local output_file="${1}"; shift |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 66 | export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 67 | "${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \ |
| 68 | "${COVERAGE_DIR}"/*.profraw |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 69 | } |
| 70 | |
| 71 | # Computes code coverage data using gcda files found under $COVERAGE_DIR. |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 72 | # Writes the collected coverage into the given output file in lcov format. |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 73 | function lcov_coverage() { |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 74 | local output_file="${1}"; shift |
| 75 | |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 76 | cat "${COVERAGE_MANIFEST}" | grep ".gcno$" | while read gcno; do |
| 77 | mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno})" |
| 78 | cp "${ROOT}/${gcno}" "${COVERAGE_DIR}/${gcno}" |
| 79 | done |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 80 | |
| 81 | local lcov_tool="$(which lcov)" |
| 82 | if [[ ! -x "$lcov_tool" ]]; then |
| 83 | lcov_tool=/usr/bin/lcov |
| 84 | fi |
| 85 | |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 86 | # Run lcov over the .gcno and .gcda files to generate the lcov tracefile. |
| 87 | # -c - Collect coverage data |
| 88 | # --no-external - Do not collect coverage data for system files |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 89 | # --ignore-errors graph - Ignore missing .gcno files; Bazel only instruments |
| 90 | # some files |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 91 | # -q - Quiet mode |
| 92 | # --gcov-tool "${GCOV}" - Pass the local symlink to be uses as gcov by lcov |
| 93 | # -b /proc/self/cwd - Use this as a prefix for all source files instead of |
| 94 | # the current directory |
| 95 | # -d "${COVERAGE_DIR}" - Directory to search for .gcda files |
| 96 | # -o "${COVERAGE_OUTPUT_FILE}" - Output file |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 97 | $lcov_tool -c --no-external --ignore-errors graph \ |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 98 | --gcov-tool "${GCOV}" -b /proc/self/cwd \ |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 99 | -d "${COVERAGE_DIR}" -o "${output_file}" |
| 100 | |
| 101 | # Fix up the paths to be relative by removing the prefix we specified above. |
| 102 | sed -i -e "s*/proc/self/cwd/**g" "${output_file}" |
| 103 | } |
| 104 | |
| 105 | # Generates a code coverage report in gcov intermediate text format by invoking |
| 106 | # gcov and using the profile data (.gcda) and notes (.gcno) files. |
| 107 | # |
| 108 | # The profile data files are expected to be found under $COVERAGE_DIR. |
| 109 | # The notes file are expected to be found under $ROOT. |
| 110 | # |
| 111 | # - output_file The location of the file where the generated code coverage |
| 112 | # report is written. |
| 113 | function gcov_coverage() { |
| 114 | local output_file="${1}"; shift |
| 115 | |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 116 | # We'll save the standard output of each the gcov command in this log. |
| 117 | local gcov_log="$output_file.gcov.log" |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 118 | |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 119 | # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR |
| 120 | # because gcov expects them to be in the same directory. |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 121 | cat "${COVERAGE_MANIFEST}" | grep ".gcno$" | while read gcno_path; do |
| 122 | |
| 123 | local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda" |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 124 | # If the gcda file was not found we skip generating coverage from the gcno |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 125 | # file. |
| 126 | if [[ -f "$gcda" ]]; then |
| 127 | # gcov expects both gcno and gcda files to be in the same directory. |
| 128 | # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda |
| 129 | # files are expected to be. |
| 130 | if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then |
| 131 | mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno_path})" |
| 132 | cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}" |
| 133 | fi |
| 134 | # Invoke gcov to generate a code coverage report with the flags: |
| 135 | # -i Output gcov file in an intermediate text format. |
| 136 | # The output is a single .gcov file per .gcda file. |
| 137 | # No source code is required. |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 138 | # -o directory The directory containing the .gcno and |
| 139 | # .gcda data files. |
| 140 | # "${gcda"} The input file name. gcov is looking for data files |
| 141 | # named after the input filename without its extension. |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 142 | # gcov produces files called <source file name>.gcov in the current |
| 143 | # directory. These contain the coverage information of the source file |
| 144 | # they correspond to. One .gcov file is produced for each source |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 145 | # (or header) file containing code which was compiled to produce the |
| 146 | # .gcda files. |
iirina | d5af612 | 2019-02-15 01:18:54 -0800 | [diff] [blame] | 147 | # Don't generate branch coverage (-b) because of a gcov issue that |
| 148 | # segfaults when both -i and -b are used (see |
| 149 | # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). |
| 150 | "${GCOV}" -i -o "$(dirname ${gcda})" "${gcda}" &> "$gcov_log" |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 151 | |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 152 | # Go through all the files that were created by the gcov command above |
| 153 | # and append their content to the output .gcov file. |
| 154 | # |
| 155 | # For each source file gcov outputs to stdout something like this: |
| 156 | # |
| 157 | # File 'examples/cpp/hello-lib.cc' |
| 158 | # Lines executed:100.00% of 8 |
| 159 | # Creating 'hello-lib.cc.gcov' |
| 160 | # |
| 161 | # We grep the names of the files that were created from that output. |
| 162 | cat "$gcov_log" | grep "Creating" | cut -d " " -f 2 | cut -d"'" -f2 | \ |
| 163 | while read gcov_file; do |
| 164 | echo "Processing $gcov_file" |
| 165 | cat "$gcov_file" >> "$output_file" |
| 166 | # Remove the intermediate gcov file because it is not useful anymore. |
| 167 | rm -f "$gcov_file" |
| 168 | done |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 169 | fi |
| 170 | done |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | function main() { |
| 174 | init_gcov |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 175 | |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 176 | # If llvm code coverage is used, we output the raw code coverage report in |
| 177 | # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other |
| 178 | # format by LcovMerger. |
| 179 | # TODO(#5881): Convert profdata reports to lcov. |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 180 | if uses_llvm; then |
iirina | dc0a4de | 2018-09-21 08:31:30 -0700 | [diff] [blame] | 181 | BAZEL_CC_COVERAGE_TOOL="PROFDATA" |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 182 | fi |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 183 | |
iirina | a77cf8b | 2018-10-02 05:58:57 -0700 | [diff] [blame] | 184 | # When using either gcov or lcov, have an output file specific to the test |
| 185 | # and format used. For lcov we generate a ".dat" output file and for gcov |
| 186 | # a ".gcov" output file. It is important that these files are generated under |
| 187 | # COVERAGE_DIR. |
| 188 | # When this script is invoked by tools/test/collect_coverage.sh either of |
| 189 | # these two coverage reports will be picked up by LcovMerger and their |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 190 | # content will be converted and/or merged with other reports to an lcov |
| 191 | # format, generating the final code coverage report. |
| 192 | case "$BAZEL_CC_COVERAGE_TOOL" in |
| 193 | ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;; |
| 194 | ("LCOV") lcov_coverage "$COVERAGE_DIR/_cc_coverage.dat" ;; |
iirina | dc0a4de | 2018-09-21 08:31:30 -0700 | [diff] [blame] | 195 | ("PROFDATA") llvm_coverage "$COVERAGE_DIR/_cc_coverage.profdata" ;; |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 196 | (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \ |
| 197 | && exit 1 |
| 198 | esac |
iirina | 190d4f8 | 2018-08-23 06:05:34 -0700 | [diff] [blame] | 199 | } |
| 200 | |
iirina | eb3e07d | 2018-09-20 05:58:31 -0700 | [diff] [blame] | 201 | main |