blob: 4635372f037fc295aa452b26a00b9b96a38e0b8f [file] [log] [blame]
Keith Smiley615e1b12021-04-20 02:10:23 -07001#!/bin/bash
iirina190d4f82018-08-23 06:05:34 -07002 # 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.
iirina190d4f82018-08-23 06:05:34 -070030# - COVERAGE_GCOV_PATH Location of gcov. This is set by the TestRunner.
ulfjack1fe5cb02020-01-23 08:31:00 -080031# - COVERAGE_GCOV_OPTIONS Additional options to pass to gcov.
iirina190d4f82018-08-23 06:05:34 -070032# - ROOT Location from where the code coverage collection
33# was invoked.
Keith Smiley615e1b12021-04-20 02:10:23 -070034# - VERBOSE_COVERAGE Print debug info from the coverage scripts
iirina190d4f82018-08-23 06:05:34 -070035#
36# The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either
37# gcda or profraw) and uses either lcov or gcov to get the coverage data.
38# The coverage data is placed in $COVERAGE_OUTPUT_FILE.
39
Keith Smiley615e1b12021-04-20 02:10:23 -070040if [[ -n "$VERBOSE_COVERAGE" ]]; then
41 set -x
42fi
43
iirina190d4f82018-08-23 06:05:34 -070044# Checks if clang llvm coverage should be used instead of lcov.
45function uses_llvm() {
46 if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then
47 return 0
48 fi
49 return 1
50}
51
iirinaeb3e07d2018-09-20 05:58:31 -070052# Returns 0 if gcov must be used, 1 otherwise.
53function uses_gcov() {
54 [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0
55 return 1
56}
57
iirina190d4f82018-08-23 06:05:34 -070058function init_gcov() {
59 # Symlink the gcov tool such with a link called gcov. Clang comes with a tool
60 # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise
61 # we would need to invoke it with "llvm-cov gcov").
62 # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html.
63 GCOV="${COVERAGE_DIR}/gcov"
Michael Klemm1c778442020-05-27 05:26:02 -070064 if [ ! -f "${COVERAGE_GCOV_PATH}" ]; then
65 echo "GCov does not exist at the given path: '${COVERAGE_GCOV_PATH}'"
66 exit 1
67 fi
68 # When using a tool from a toolchain COVERAGE_GCOV_PATH will be a relative
69 # path. To make it work on different working directories it's required to
70 # convert the path to an absolute one.
71 COVERAGE_GCOV_PATH_ABS="$(cd "${COVERAGE_GCOV_PATH%/*}" && pwd)/${COVERAGE_GCOV_PATH##*/}"
72 ln -s "${COVERAGE_GCOV_PATH_ABS}" "${GCOV}"
iirina190d4f82018-08-23 06:05:34 -070073}
74
iirinaeb3e07d2018-09-20 05:58:31 -070075# Computes code coverage data using the clang generated metadata found under
76# $COVERAGE_DIR.
77# Writes the collected coverage into the given output file.
plf21b5eb62020-10-21 06:53:30 -070078function llvm_coverage_lcov() {
79 local output_file="${1}"; shift
80 export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw"
81 "${COVERAGE_GCOV_PATH}" merge -output "${output_file}.data" \
82 "${COVERAGE_DIR}"/*.profraw
83
84 local object_param=""
85 while read -r line; do
86 if [[ ${line: -24} == "runtime_objects_list.txt" ]]; then
87 while read -r line_runtime_object; do
Fabian Meumertzheim9d3b3752023-06-20 14:46:20 -070088 object_param+=" -object ${line_runtime_object}"
plf21b5eb62020-10-21 06:53:30 -070089 done < "${line}"
90 fi
91 done < "${COVERAGE_MANIFEST}"
92
93 "${LLVM_COV}" export -instr-profile "${output_file}.data" -format=lcov \
Fabian Meumertzheim88b51f52023-01-10 06:07:43 -080094 -ignore-filename-regex='^/tmp/.+' \
plf21b5eb62020-10-21 06:53:30 -070095 ${object_param} | sed 's#/proc/self/cwd/##' > "${output_file}"
96}
97
98function llvm_coverage_profdata() {
iirinaeb3e07d2018-09-20 05:58:31 -070099 local output_file="${1}"; shift
iirina190d4f82018-08-23 06:05:34 -0700100 export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw"
iirinaeb3e07d2018-09-20 05:58:31 -0700101 "${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \
102 "${COVERAGE_DIR}"/*.profraw
iirina190d4f82018-08-23 06:05:34 -0700103}
104
iirinaeb3e07d2018-09-20 05:58:31 -0700105# 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.
113function gcov_coverage() {
114 local output_file="${1}"; shift
115
iirinaa77cf8b2018-10-02 05:58:57 -0700116 # We'll save the standard output of each the gcov command in this log.
117 local gcov_log="$output_file.gcov.log"
iirinaeb3e07d2018-09-20 05:58:31 -0700118
iirinaa77cf8b2018-10-02 05:58:57 -0700119 # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR
120 # because gcov expects them to be in the same directory.
iirina43532f92019-05-16 08:07:13 -0700121 while read -r line; do
122 if [[ ${line: -4} == "gcno" ]]; then
123 gcno_path=${line}
124 local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda"
125 # If the gcda file was not found we skip generating coverage from the gcno
126 # file.
127 if [[ -f "$gcda" ]]; then
128 # gcov expects both gcno and gcda files to be in the same directory.
129 # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda
130 # files are expected to be.
131 if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then
132 mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno_path})"
133 cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}"
134 fi
Charles Mita57ae1ff2023-07-11 06:04:07 -0700135
136 # Extract gcov's version: the output of `gcov --version` contains the
137 # version as a set of major-minor-patch numbers, of which we extract
138 # the major version.
139 # gcov --version outputs a line like:
140 # gcov (Debian 7.3.0-5) 7.3.0
141 # llvm-cov gcov --version outputs a line like:
142 # LLVM version 9.0.1
143 gcov_major_version=$("${GCOV}" --version | sed -n -E -e 's/^.*\s([0-9]+)\.[0-9]+\.[0-9]+\s?.*$/\1/p')
144
iirina43532f92019-05-16 08:07:13 -0700145 # Invoke gcov to generate a code coverage report with the flags:
146 # -i Output gcov file in an intermediate text format.
147 # The output is a single .gcov file per .gcda file.
148 # No source code is required.
149 # -o directory The directory containing the .gcno and
150 # .gcda data files.
151 # "${gcda"} The input file name. gcov is looking for data files
152 # named after the input filename without its extension.
153 # gcov produces files called <source file name>.gcov in the current
154 # directory. These contain the coverage information of the source file
155 # they correspond to. One .gcov file is produced for each source
156 # (or header) file containing code which was compiled to produce the
157 # .gcda files.
158 # Don't generate branch coverage (-b) because of a gcov issue that
159 # segfaults when both -i and -b are used (see
160 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879).
Charles Mita57ae1ff2023-07-11 06:04:07 -0700161
162 # Don't generate branch coverage (-b) when using gcov 7 or earlier
163 # because of a gcov issue that segfaults when both -i and -b are used
164 # (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879).
165 if [[ $gcov_major_version -le 7 ]]; then
166 "${GCOV}" -i $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}"
167 else
168 "${GCOV}" -i -b $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}"
169 fi
iirinaeb3e07d2018-09-20 05:58:31 -0700170
Charles Mita7f628d72023-07-10 09:52:13 -0700171 # Check the type of output: gcov 9 or later outputs compressed JSON
172 # files, but earlier versions of gcov, and all versions of llvm-cov,
173 # do not. These output textual information.
174 if stat --printf='' *.gcov.json.gz > /dev/null 2>&1; then
Henk van der Laan3b75bc72020-06-16 02:22:02 -0700175 # Concatenating JSON documents does not yield a valid document, so they are moved individually
Krzysztof Naglikf02bcf82022-10-26 02:38:50 -0700176 mv -- *.gcov.json.gz "$(dirname "$output_file")/$(dirname ${gcno_path})"
Henk van der Laan3b75bc72020-06-16 02:22:02 -0700177 else
178 # Append all .gcov files in the current directory to the output file.
179 cat -- *.gcov >> "$output_file"
180 # Delete the .gcov files.
181 rm -- *.gcov
182 fi
iirina43532f92019-05-16 08:07:13 -0700183 fi
iirinaeb3e07d2018-09-20 05:58:31 -0700184 fi
iirina43532f92019-05-16 08:07:13 -0700185 done < "${COVERAGE_MANIFEST}"
iirina190d4f82018-08-23 06:05:34 -0700186}
187
188function main() {
189 init_gcov
iirinaeb3e07d2018-09-20 05:58:31 -0700190
iirinaa77cf8b2018-10-02 05:58:57 -0700191 # If llvm code coverage is used, we output the raw code coverage report in
192 # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other
193 # format by LcovMerger.
194 # TODO(#5881): Convert profdata reports to lcov.
iirina190d4f82018-08-23 06:05:34 -0700195 if uses_llvm; then
plf21b5eb62020-10-21 06:53:30 -0700196 if [[ "${GENERATE_LLVM_LCOV}" == "1" ]]; then
197 BAZEL_CC_COVERAGE_TOOL="LLVM_LCOV"
198 else
199 BAZEL_CC_COVERAGE_TOOL="PROFDATA"
200 fi
iirina190d4f82018-08-23 06:05:34 -0700201 fi
iirinaeb3e07d2018-09-20 05:58:31 -0700202
iirinaa77cf8b2018-10-02 05:58:57 -0700203 # When using either gcov or lcov, have an output file specific to the test
204 # and format used. For lcov we generate a ".dat" output file and for gcov
205 # a ".gcov" output file. It is important that these files are generated under
206 # COVERAGE_DIR.
207 # When this script is invoked by tools/test/collect_coverage.sh either of
208 # these two coverage reports will be picked up by LcovMerger and their
iirinaeb3e07d2018-09-20 05:58:31 -0700209 # content will be converted and/or merged with other reports to an lcov
210 # format, generating the final code coverage report.
211 case "$BAZEL_CC_COVERAGE_TOOL" in
212 ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;;
plf21b5eb62020-10-21 06:53:30 -0700213 ("PROFDATA") llvm_coverage_profdata "$COVERAGE_DIR/_cc_coverage.profdata" ;;
214 ("LLVM_LCOV") llvm_coverage_lcov "$COVERAGE_DIR/_cc_coverage.dat" ;;
iirinaeb3e07d2018-09-20 05:58:31 -0700215 (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \
216 && exit 1
217 esac
iirina190d4f82018-08-23 06:05:34 -0700218}
219
iirinaeb3e07d2018-09-20 05:58:31 -0700220main