|
| 1 | +#!/bin/bash |
| 2 | +# Custom collect_cc_coverage.sh that fixes COVERAGE_DIR permissions before running. |
| 3 | +# This works around a Bazel issue where COVERAGE_DIR is made read-only before |
| 4 | +# coverage post-processing runs with --experimental_split_coverage_postprocessing. |
| 5 | + |
| 6 | +# Fix COVERAGE_DIR permissions if it exists |
| 7 | +if [ -n "$COVERAGE_DIR" ] && [ -d "$COVERAGE_DIR" ]; then |
| 8 | + chmod -R u+w "$COVERAGE_DIR" 2>/dev/null || true |
| 9 | +fi |
| 10 | + |
| 11 | +# Now source and run the original collect_cc_coverage.sh from Bazel |
| 12 | +# We inline the original script here since we can't easily source external Bazel files |
| 13 | + |
| 14 | +if [[ -n "$VERBOSE_COVERAGE" ]]; then |
| 15 | + set -x |
| 16 | +fi |
| 17 | + |
| 18 | +# Checks if clang llvm coverage should be used instead of lcov. |
| 19 | +function uses_llvm() { |
| 20 | + if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then |
| 21 | + return 0 |
| 22 | + fi |
| 23 | + return 1 |
| 24 | +} |
| 25 | + |
| 26 | +# Returns 0 if gcov must be used, 1 otherwise. |
| 27 | +function uses_gcov() { |
| 28 | + [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0 |
| 29 | + return 1 |
| 30 | +} |
| 31 | + |
| 32 | +function init_gcov() { |
| 33 | + # Symlink the gcov tool such with a link called gcov. Clang comes with a tool |
| 34 | + # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise |
| 35 | + # we would need to invoke it with "llvm-cov gcov"). |
| 36 | + # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html. |
| 37 | + GCOV="${COVERAGE_DIR}/gcov" |
| 38 | + if [ ! -f "${COVERAGE_GCOV_PATH}" ]; then |
| 39 | + echo "GCov does not exist at the given path: '${COVERAGE_GCOV_PATH}'" |
| 40 | + exit 1 |
| 41 | + fi |
| 42 | + # When using a tool from a toolchain COVERAGE_GCOV_PATH will be a relative |
| 43 | + # path. To make it work on different working directories it's required to |
| 44 | + # convert the path to an absolute one. |
| 45 | + COVERAGE_GCOV_PATH_ABS="$(cd "${COVERAGE_GCOV_PATH%/*}" && pwd)/${COVERAGE_GCOV_PATH##*/}" |
| 46 | + ln -s "${COVERAGE_GCOV_PATH_ABS}" "${GCOV}" |
| 47 | +} |
| 48 | + |
| 49 | +# Computes code coverage data using the clang generated metadata found under |
| 50 | +# $COVERAGE_DIR. |
| 51 | +# Writes the collected coverage into the given output file. |
| 52 | +function llvm_coverage_lcov() { |
| 53 | + local output_file="${1}"; shift |
| 54 | + export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" |
| 55 | + "${COVERAGE_GCOV_PATH}" merge -output "${output_file}.data" \ |
| 56 | + "${COVERAGE_DIR}"/*.profraw |
| 57 | + |
| 58 | + local object_param="" |
| 59 | + while read -r line; do |
| 60 | + if [[ ${line: -24} == "runtime_objects_list.txt" ]]; then |
| 61 | + while read -r line_runtime_object; do |
| 62 | + object_param+=" -object ${line_runtime_object}" |
| 63 | + done < "${line}" |
| 64 | + fi |
| 65 | + done < "${COVERAGE_MANIFEST}" |
| 66 | + |
| 67 | + "${LLVM_COV}" export -instr-profile "${output_file}.data" -format=lcov \ |
| 68 | + -ignore-filename-regex='^/tmp/.+' \ |
| 69 | + ${object_param} | sed 's#/proc/self/cwd/##' > "${output_file}" |
| 70 | +} |
| 71 | + |
| 72 | +function llvm_coverage_profdata() { |
| 73 | + local output_file="${1}"; shift |
| 74 | + export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" |
| 75 | + "${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \ |
| 76 | + "${COVERAGE_DIR}"/*.profraw |
| 77 | +} |
| 78 | + |
| 79 | +# Generates a code coverage report in gcov intermediate text format by invoking |
| 80 | +# gcov and using the profile data (.gcda) and notes (.gcno) files. |
| 81 | +# |
| 82 | +# The profile data files are expected to be found under $COVERAGE_DIR. |
| 83 | +# The notes file are expected to be found under $ROOT. |
| 84 | +# |
| 85 | +# - output_file The location of the file where the generated code coverage |
| 86 | +# report is written. |
| 87 | +function gcov_coverage() { |
| 88 | + local output_file="${1}"; shift |
| 89 | + |
| 90 | + # We'll save the standard output of each the gcov command in this log. |
| 91 | + local gcov_log="$output_file.gcov.log" |
| 92 | + |
| 93 | + # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR |
| 94 | + # because gcov expects them to be in the same directory. |
| 95 | + while read -r line; do |
| 96 | + if [[ ${line: -4} == "gcno" ]]; then |
| 97 | + gcno_path=${line} |
| 98 | + local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda" |
| 99 | + # If the gcda file was not found we skip generating coverage from the gcno |
| 100 | + # file. |
| 101 | + if [[ -f "$gcda" ]]; then |
| 102 | + # gcov expects both gcno and gcda files to be in the same directory. |
| 103 | + # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda |
| 104 | + # files are expected to be. |
| 105 | + if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then |
| 106 | + mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno_path})" |
| 107 | + cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}" |
| 108 | + fi |
| 109 | + |
| 110 | + # Extract gcov's version: the output of `gcov --version` contains the |
| 111 | + # version as a set of major-minor-patch numbers, of which we extract |
| 112 | + # the major version. |
| 113 | + # gcov --version outputs a line like: |
| 114 | + # gcov (Debian 7.3.0-5) 7.3.0 |
| 115 | + # llvm-cov gcov --version outputs a line like: |
| 116 | + # LLVM version 9.0.1 |
| 117 | + gcov_major_version=$("${GCOV}" --version | sed -n -E -e 's/^.*\s([0-9]+)\.[0-9]+\.[0-9]+\s?.*$/\1/p') |
| 118 | + |
| 119 | + # Invoke gcov to generate a code coverage report with the flags: |
| 120 | + # -i Output gcov file in an intermediate text format. |
| 121 | + # The output is a single .gcov file per .gcda file. |
| 122 | + # No source code is required. |
| 123 | + # -o directory The directory containing the .gcno and |
| 124 | + # .gcda data files. |
| 125 | + # "${gcda"} The input file name. gcov is looking for data files |
| 126 | + # named after the input filename without its extension. |
| 127 | + # gcov produces files called <source file name>.gcov in the current |
| 128 | + # directory. These contain the coverage information of the source file |
| 129 | + # they correspond to. One .gcov file is produced for each source |
| 130 | + # (or header) file containing code which was compiled to produce the |
| 131 | + # .gcda files. |
| 132 | + # Don't generate branch coverage (-b) because of a gcov issue that |
| 133 | + # segfaults when both -i and -b are used (see |
| 134 | + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). |
| 135 | + |
| 136 | + # Don't generate branch coverage (-b) when using gcov 7 or earlier |
| 137 | + # because of a gcov issue that segfaults when both -i and -b are used |
| 138 | + # (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). |
| 139 | + if [[ $gcov_major_version -le 7 ]]; then |
| 140 | + "${GCOV}" -i $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}" |
| 141 | + else |
| 142 | + "${GCOV}" -i -b $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}" |
| 143 | + fi |
| 144 | + |
| 145 | + # Check the type of output: gcov 9 or later outputs compressed JSON |
| 146 | + # files, but earlier versions of gcov, and all versions of llvm-cov, |
| 147 | + # do not. These output textual information. |
| 148 | + if stat --printf='' *.gcov.json.gz > /dev/null 2>&1; then |
| 149 | + # Concatenating JSON documents does not yield a valid document, so they are moved individually |
| 150 | + mv -- *.gcov.json.gz "$(dirname "$output_file")/$(dirname ${gcno_path})" |
| 151 | + else |
| 152 | + # Append all .gcov files in the current directory to the output file. |
| 153 | + cat -- *.gcov >> "$output_file" |
| 154 | + # Delete the .gcov files. |
| 155 | + rm -- *.gcov |
| 156 | + fi |
| 157 | + fi |
| 158 | + fi |
| 159 | + done < "${COVERAGE_MANIFEST}" |
| 160 | +} |
| 161 | + |
| 162 | +function main() { |
| 163 | + init_gcov |
| 164 | + |
| 165 | + # If llvm code coverage is used, we output the raw code coverage report in |
| 166 | + # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other |
| 167 | + # format by LcovMerger. |
| 168 | + # TODO(#5881): Convert profdata reports to lcov. |
| 169 | + if uses_llvm; then |
| 170 | + if [[ "${GENERATE_LLVM_LCOV}" == "1" ]]; then |
| 171 | + BAZEL_CC_COVERAGE_TOOL="LLVM_LCOV" |
| 172 | + else |
| 173 | + BAZEL_CC_COVERAGE_TOOL="PROFDATA" |
| 174 | + fi |
| 175 | + fi |
| 176 | + |
| 177 | + # When using either gcov or lcov, have an output file specific to the test |
| 178 | + # and format used. For lcov we generate a ".dat" output file and for gcov |
| 179 | + # a ".gcov" output file. It is important that these files are generated under |
| 180 | + # COVERAGE_DIR. |
| 181 | + # When this script is invoked by tools/test/collect_coverage.sh either of |
| 182 | + # these two coverage reports will be picked up by LcovMerger and their |
| 183 | + # content will be converted and/or merged with other reports to an lcov |
| 184 | + # format, generating the final code coverage report. |
| 185 | + case "$BAZEL_CC_COVERAGE_TOOL" in |
| 186 | + ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;; |
| 187 | + ("PROFDATA") llvm_coverage_profdata "$COVERAGE_DIR/_cc_coverage.profdata" ;; |
| 188 | + ("LLVM_LCOV") llvm_coverage_lcov "$COVERAGE_DIR/_cc_coverage.dat" ;; |
| 189 | + (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \ |
| 190 | + && exit 1 |
| 191 | + esac |
| 192 | +} |
| 193 | + |
| 194 | +main |
0 commit comments