blob: 2960db6b8128a98b73c19aefb12bb1e0f7911e6d [file] [log] [blame]
iirina190d4f82018-08-23 06:05:34 -07001#!/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.
iirina190d4f82018-08-23 06:05:34 -070030# - 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.
39function uses_llvm() {
40 if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then
41 return 0
42 fi
43 return 1
44}
45
iirinaeb3e07d2018-09-20 05:58:31 -070046# Returns 0 if gcov must be used, 1 otherwise.
47function uses_gcov() {
48 [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0
49 return 1
50}
51
iirina190d4f82018-08-23 06:05:34 -070052function 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
iirinaeb3e07d2018-09-20 05:58:31 -070061# Computes code coverage data using the clang generated metadata found under
62# $COVERAGE_DIR.
63# Writes the collected coverage into the given output file.
iirina190d4f82018-08-23 06:05:34 -070064function llvm_coverage() {
iirinaeb3e07d2018-09-20 05:58:31 -070065 local output_file="${1}"; shift
iirina190d4f82018-08-23 06:05:34 -070066 export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw"
iirinaeb3e07d2018-09-20 05:58:31 -070067 "${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \
68 "${COVERAGE_DIR}"/*.profraw
iirina190d4f82018-08-23 06:05:34 -070069}
70
71# Computes code coverage data using gcda files found under $COVERAGE_DIR.
iirinaeb3e07d2018-09-20 05:58:31 -070072# Writes the collected coverage into the given output file in lcov format.
iirina190d4f82018-08-23 06:05:34 -070073function lcov_coverage() {
iirinaeb3e07d2018-09-20 05:58:31 -070074 local output_file="${1}"; shift
75
iirina190d4f82018-08-23 06:05:34 -070076 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
iirinaeb3e07d2018-09-20 05:58:31 -070080
81 local lcov_tool="$(which lcov)"
82 if [[ ! -x "$lcov_tool" ]]; then
83 lcov_tool=/usr/bin/lcov
84 fi
85
iirina190d4f82018-08-23 06:05:34 -070086 # 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
iirinaeb3e07d2018-09-20 05:58:31 -070089 # --ignore-errors graph - Ignore missing .gcno files; Bazel only instruments
90 # some files
iirina190d4f82018-08-23 06:05:34 -070091 # -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
iirinaeb3e07d2018-09-20 05:58:31 -070097 $lcov_tool -c --no-external --ignore-errors graph \
iirina190d4f82018-08-23 06:05:34 -070098 --gcov-tool "${GCOV}" -b /proc/self/cwd \
iirinaeb3e07d2018-09-20 05:58:31 -070099 -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.
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.
iirinaeb3e07d2018-09-20 05:58:31 -0700121 cat "${COVERAGE_MANIFEST}" | grep ".gcno$" | while read gcno_path; do
122
123 local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda"
iirinaa77cf8b2018-10-02 05:58:57 -0700124 # If the gcda file was not found we skip generating coverage from the gcno
iirinaeb3e07d2018-09-20 05:58:31 -0700125 # 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.
iirinaeb3e07d2018-09-20 05:58:31 -0700138 # -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.
iirinaeb3e07d2018-09-20 05:58:31 -0700142 # 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
iirinaa77cf8b2018-10-02 05:58:57 -0700145 # (or header) file containing code which was compiled to produce the
146 # .gcda files.
iirinad5af6122019-02-15 01:18:54 -0800147 # 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"
iirinaeb3e07d2018-09-20 05:58:31 -0700151
iirinaa77cf8b2018-10-02 05:58:57 -0700152 # 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
iirinaeb3e07d2018-09-20 05:58:31 -0700169 fi
170 done
iirina190d4f82018-08-23 06:05:34 -0700171}
172
173function main() {
174 init_gcov
iirinaeb3e07d2018-09-20 05:58:31 -0700175
iirinaa77cf8b2018-10-02 05:58:57 -0700176 # 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.
iirina190d4f82018-08-23 06:05:34 -0700180 if uses_llvm; then
iirinadc0a4de2018-09-21 08:31:30 -0700181 BAZEL_CC_COVERAGE_TOOL="PROFDATA"
iirina190d4f82018-08-23 06:05:34 -0700182 fi
iirinaeb3e07d2018-09-20 05:58:31 -0700183
iirinaa77cf8b2018-10-02 05:58:57 -0700184 # 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
iirinaeb3e07d2018-09-20 05:58:31 -0700190 # 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" ;;
iirinadc0a4de2018-09-21 08:31:30 -0700195 ("PROFDATA") llvm_coverage "$COVERAGE_DIR/_cc_coverage.profdata" ;;
iirinaeb3e07d2018-09-20 05:58:31 -0700196 (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \
197 && exit 1
198 esac
iirina190d4f82018-08-23 06:05:34 -0700199}
200
iirinaeb3e07d2018-09-20 05:58:31 -0700201main