Skip to content

Commit

Permalink
Fix identical gcov json file name problem
Browse files Browse the repository at this point in the history
**The problem:**

Bazel moves all `.gcov.json.gz` files to one directory. If in a test target, two source files have identical names, then the second `.gcov.json.gz` overwrites the first one.

**The solution:**

I added `gcno_path` to the move destination in order to distinguish multiple `.gcov.json.gz` with the same name.

**Testing:**

In the `test_cc_test_coverage_gcov` test case I added the `different/a.cc` source file, so currently we have the following source tree:
```
coverage_srcs/a.h
coverage_srcs/a.cc
coverage_srcs/b.h
coverage_srcs/t.cc
coverage_srcs/different/a.h
coverage_srcs/different/a.cc
```

gcda and gcno files are created next to the source files. The final `gcov.json` files are placed in the corresponding paths:
```
$COVERAGE_DIR_VAR/coverage_srcs/*a.gcov.json.gz
$COVERAGE_DIR_VAR/coverage_srcs/*t.gcov.json.gz
$COVERAGE_DIR_VAR/coverage_srcs/different/*a.gcov.json.gz
```

Closes bazelbuild#16527.

PiperOrigin-RevId: 483911427
Change-Id: I1608407e4b7264fb5fd436997bdc344344932b97
  • Loading branch information
krisukox committed Nov 3, 2022
1 parent c3259c4 commit 2e10d59
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 52 deletions.
139 changes: 88 additions & 51 deletions src/test/shell/bazel/bazel_cc_code_coverage_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function set_up() {

# Create the CC sources.
mkdir -p "$ROOT_VAR/coverage_srcs/"
mkdir -p "$ROOT_VAR/coverage_srcs/different"
cat << EOF > "$ROOT_VAR/coverage_srcs/a.h"
int a(bool what);
EOF
Expand All @@ -85,6 +86,20 @@ int a(bool what) {
return b(-1);
}
}
EOF

cat << EOF > "$ROOT_VAR/coverage_srcs/different/a.h"
int different_a(bool what);
EOF

cat << EOF > "$ROOT_VAR/coverage_srcs/different/a.cc"
int different_a(bool what) {
if (what) {
return 1;
} else {
return 2;
}
}
EOF

cat << EOF > "$ROOT_VAR/coverage_srcs/b.h"
Expand All @@ -100,37 +115,24 @@ EOF
cat << EOF > "$ROOT_VAR/coverage_srcs/t.cc"
#include <stdio.h>
#include "a.h"
#include "different/a.h"
int main(void) {
a(true);
different_a(false);
}
EOF

generate_and_execute_instrumented_binary coverage_srcs/test \
coverage_srcs/a.h coverage_srcs/a.cc \
coverage_srcs/b.h \
coverage_srcs/t.cc

# Prior to version 11, g++ generates the notes files in the current directory
# instead of next to the object file despite the documentation indicating otherwise:
# https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html#Gcov-Data-Files
# This is fixed in g++ 11 so we have to handle both cases.

local not_found=0
ls coverage_srcs/*a.gcno > /dev/null 2>&1 || not_found=$?
if [[ $not_found -ne 0 ]]; then
agcno=$(ls *a.gcno)
tgcno=$(ls *t.gcno)
agcda=$(ls *a.gcda)
tgcda=$(ls *t.gcda)
mv $agcno coverage_srcs/$agcno
mv $tgcno coverage_srcs/$tgcno
mv $agcda coverage_srcs/$agcda
mv $tgcda coverage_srcs/$tgcda
fi
coverage_srcs/a.cc coverage_srcs/a.o \
coverage_srcs/different/a.cc coverage_srcs/different/a.o \
coverage_srcs/t.cc coverage_srcs/t.o

agcno=$(ls coverage_srcs/*a.gcno)
dagcno=$(ls coverage_srcs/different/*a.gcno)
tgcno=$(ls coverage_srcs/*t.gcno)
agcda=$(ls coverage_srcs/*a.gcda)
dagcda=$(ls coverage_srcs/different/*a.gcda)
tgcda=$(ls coverage_srcs/*t.gcda)
# Even though gcov expects the gcda files to be next to the gcno files,
# during Bazel execution this will not be the case. collect_cc_coverage.sh
Expand All @@ -140,32 +142,41 @@ EOF
# https://github.com/bazelbuild/bazel/issues/16229
mv $agcda "$COVERAGE_DIR_VAR/$agcda"
mv $tgcda "$COVERAGE_DIR_VAR/$tgcda"
mkdir "$COVERAGE_DIR_VAR/$(dirname ${dagcda})"
mv $dagcda "$COVERAGE_DIR_VAR/$dagcda"

# All generated .gcno files need to be in the manifest otherwise
# the coverage report will be incomplete.
echo "$tgcno" >> "$COVERAGE_MANIFEST_VAR"
echo "$agcno" >> "$COVERAGE_MANIFEST_VAR"
echo "$dagcno" >> "$COVERAGE_MANIFEST_VAR"
}

# Generates and executes an instrumented binary:
#
# Reads the list of arguments provided by the caller (using $@) and uses them
# to produce an instrumented binary using g++. This step also generates
# the notes (.gcno) files.
# Reads the list of source files along with object files paths. Uses them
# to produce object files and link them to an instrumented binary using g++.
# This step also generates the notes (.gcno) files.
#
# Executes the instrumented binary. This step also generates the
# profile data (.gcda) files.
#
# - [(source_file, object_file),...] - source files and object file paths
# - path_to_binary destination of the binary produced by g++
function generate_and_execute_instrumented_binary() {
local path_to_binary="${1}"; shift
g++ -coverage \
"$@" -o "$path_to_binary" \
|| fail "Couldn't produce the instrumented binary for $@ \
with path_to_binary $path_to_binary"

# Execute the instrumented binary and generates the profile data (.gcda)
# file.
# The profile data file is placed in $gcda_directory.
local src_file=""
local obj_file=""
local obj_files=""
while [[ $# -ge 2 ]]; do
src_file=$1; shift
obj_file=$1; shift
obj_files="${obj_files} $obj_file"
g++ -coverage -fprofile-arcs -ftest-coverage -lgcov -c \
"$src_file" -o "$obj_file"
done

g++ -coverage -fprofile-arcs -ftest-coverage -lgcov -o "$path_to_binary" $obj_files
"$path_to_binary" || fail "Couldn't execute the instrumented binary \
$path_to_binary"
}
Expand Down Expand Up @@ -223,11 +234,12 @@ function assert_gcov_coverage_srcs_t_cc() {
local output_file="${1}"; shift

# The expected coverage result for coverage_srcs/t.cc in gcov format.
local expected_gcov_result_t_cc="file:coverage_srcs/t.cc
function:4,1,main
lcount:4,1
local expected_gcov_result_t_cc="coverage_srcs/t.cc
function:5,1,main
lcount:5,1
lcount:6,1"
lcount:6,1
lcount:7,1
lcount:8,1"
assert_coverage_entry_in_file "$expected_gcov_result_t_cc" "$output_file"
}

Expand All @@ -244,6 +256,19 @@ lcount:5,0"
assert_coverage_entry_in_file "$expected_gcov_result" "$output_file"
}

function assert_gcov_coverage_srcs_d_a_cc() {
local output_file="${1}"; shift

# The expected coverage result for coverage_srcs/different/a.cc in gcov format.
local expected_gcov_result_d_a_cc="file:coverage_srcs/different/a.cc
function:1,1,_Z11different_ab
lcount:1,1
lcount:2,1
lcount:3,0
lcount:5,1"
assert_coverage_entry_in_file "$expected_gcov_result_d_a_cc" "$output_file"
}

function assert_gcov_coverage_srcs_a_cc_json() {
local output_file="${1}"; shift

Expand All @@ -265,7 +290,7 @@ function assert_gcov_coverage_srcs_t_cc_json() {

# The expected coverage result for coverage_srcs/t.cc in gcov format.
cat > expected_gcov_result_t_cc <<EOF
{"lines": [{"branches": [], "count": 1, "line_number": 4, "unexecuted_block": false, "function_name": "main"}, {"branches": [], "count": 1, "line_number": 5, "unexecuted_block": false, "function_name": "main"}, {"branches": [], "count": 1, "line_number": 6, "unexecuted_block": false, "function_name": "main"}], "functions": [{"blocks": 3, "end_column": 1, "start_line": 4, "name": "main", "blocks_executed": 3, "execution_count": 1, "demangled_name": "main", "start_column": 5, "end_line": 6}], "file": "coverage_srcs/t.cc"}
{"lines": [{"branches": [], "count": 1, "line_number": 5, "unexecuted_block": false, "function_name": "main"}, {"branches": [], "count": 1, "line_number": 6, "unexecuted_block": false, "function_name": "main"}, {"branches": [], "count": 1, "line_number": 7, "unexecuted_block": false, "function_name": "main"}, {"branches": [], "count": 1, "line_number": 8, "unexecuted_block": false, "function_name": "main"}], "functions": [{"blocks": 4, "end_column": 1, "start_line": 5, "name": "main", "blocks_executed": 4, "execution_count": 1, "demangled_name": "main", "start_column": 5, "end_line": 8}], "file": "coverage_srcs/t.cc"}
EOF
local expected_gcov_result_t_cc=$(cat expected_gcov_result_t_cc | tr -d '\n')
assert_coverage_entry_in_file "$expected_gcov_result_t_cc" "$output_file"
Expand All @@ -274,14 +299,24 @@ EOF
function assert_gcov_coverage_srcs_b_h_json() {
local output_file="${1}"; shift

# The expected coverage result for coverage_srcs/t.cc in gcov format.
# The expected coverage result for coverage_srcs/b.h in gcov format.
cat > expected_gcov_result_b_h <<EOF
{"lines": [{"branches": [], "count": 1, "line_number": 1, "unexecuted_block": false, "function_name": "_Z1bi"}, {"branches": [], "count": 1, "line_number": 2, "unexecuted_block": false, "function_name": "_Z1bi"}, {"branches": [], "count": 1, "line_number": 3, "unexecuted_block": false, "function_name": "_Z1bi"}, {"branches": [], "count": 0, "line_number": 5, "unexecuted_block": true, "function_name": "_Z1bi"}], "functions": [{"blocks": 4, "end_column": 1, "start_line": 1, "name": "_Z1bi", "blocks_executed": 3, "execution_count": 1, "demangled_name": "b(int)", "start_column": 5, "end_line": 7}], "file": "coverage_srcs/b.h"}
EOF
local expected_gcov_result_b_h=$(cat expected_gcov_result_b_h | tr -d '\n')
assert_coverage_entry_in_file "$expected_gcov_result_b_h" "$output_file"
}

function assert_gcov_coverage_srcs_d_a_cc_json() {
local output_file="${1}"; shift

# The expected coverage result for coverage_srcs/different/a.cc in gcov format.
cat > expected_gcov_result_d_a_cc <<EOF
{"lines": [{"branches": [], "count": 1, "line_number": 1, "unexecuted_block": false, "function_name": "_Z11different_ab"}, {"branches": [], "count": 1, "line_number": 2, "unexecuted_block": false, "function_name": "_Z11different_ab"}, {"branches": [], "count": 0, "line_number": 3, "unexecuted_block": true, "function_name": "_Z11different_ab"}, {"branches": [], "count": 1, "line_number": 5, "unexecuted_block": false, "function_name": "_Z11different_ab"}], "functions": [{"blocks": 4, "end_column": 1, "start_line": 1, "name": "_Z11different_ab", "blocks_executed": 3, "execution_count": 1, "demangled_name": "different_a(bool)", "start_column": 5, "end_line": 7}], "file": "coverage_srcs/different/a.cc"}
EOF
local expected_gcov_result_d_a_cc=$(cat expected_gcov_result_d_a_cc | tr -d '\n')
assert_coverage_entry_in_file "$expected_gcov_result_d_a_cc" "$output_file"
}

function test_cc_test_coverage_gcov() {
local -r gcov_location=$(which gcov)
Expand All @@ -303,9 +338,6 @@ function test_cc_test_coverage_gcov() {
# Location of the output file of the C++ coverage script when gcov is used.
local output_file="$COVERAGE_DIR_VAR/_cc_coverage.gcov"

# Location of the output file of the C++ coverage script when gcov is used.
local output_file="$COVERAGE_DIR_VAR/_cc_coverage.gcov"

# If the file _cc_coverge.gcov does not exist, it means we are using gcc 9
# or higher which uses json.gz files as intermediate format. We will keep
# testing gcc 7 and 8 until most users have migrated.
Expand All @@ -319,35 +351,40 @@ function test_cc_test_coverage_gcov() {
assert_gcov_coverage_srcs_a_cc "$output_file"
assert_gcov_coverage_srcs_t_cc "$output_file"
assert_gcov_coverage_srcs_b_h "$output_file"
assert_gcov_coverage_srcs_d_a_cc "$output_file"

# This assertion is needed to make sure no other source files are included
# in the output file.
local nr_lines="$(wc -l < "$output_file")"
[[ "$nr_lines" == 17 ]] || \
[[ "$nr_lines" == 24 ]] || \
fail "Number of lines in C++ gcov coverage output file is "\
"$nr_lines and different than 17"
"$nr_lines and different than 24"
else

# There may or may not be "gcda" in the extension.
local not_found=0
ls $COVERAGE_DIR_VAR/*.gcda.gcov.json.gz > /dev/null 2>&1 || not_found=$?
ls $COVERAGE_DIR_VAR/coverage_srcs/*.gcda.gcov.json.gz > /dev/null 2>&1 || not_found=$?
if [[ $not_found -ne 0 ]]; then
agcda=$(ls $COVERAGE_DIR_VAR/*a.gcov.json.gz)
tgcda=$(ls $COVERAGE_DIR_VAR/*t.gcov.json.gz)
agcda=$(ls $COVERAGE_DIR_VAR/coverage_srcs/*a.gcov.json.gz)
tgcda=$(ls $COVERAGE_DIR_VAR/coverage_srcs/*t.gcov.json.gz)
dagcda=$(ls $COVERAGE_DIR_VAR/coverage_srcs/different/*a.gcov.json.gz)
else
agcda=$(ls $COVERAGE_DIR_VAR/*a.gcda.gcov.json.gz)
tgcda=$(ls $COVERAGE_DIR_VAR/*t.gcda.gcov.json.gz)
agcda=$(ls $COVERAGE_DIR_VAR/coverage_srcs/*a.gcda.gcov.json.gz)
tgcda=$(ls $COVERAGE_DIR_VAR/coverage_srcs/*t.gcda.gcov.json.gz)
dagcda=$(ls $COVERAGE_DIR_VAR/coverage_srcs/different/*a.gcda.gcov.json.gz)
fi
output_file_json="output_file.json"
zcat $agcda $tgcda > $output_file_json
zcat $agcda $tgcda $dagcda > $output_file_json

assert_gcov_coverage_srcs_a_cc_json "$output_file_json"
assert_gcov_coverage_srcs_t_cc_json "$output_file_json"
assert_gcov_coverage_srcs_b_h_json "$output_file_json"
assert_gcov_coverage_srcs_d_a_cc_json "$output_file_json"

local nr_files="$(grep -o -i "\"file\":" "$output_file_json" | wc -l)"
[[ "$nr_files" == 3 ]] || \
[[ "$nr_files" == 4 ]] || \
fail "Number of files in C++ gcov coverage output file is "\
"$nr_files and different than 3"
"$nr_files and different than 4"
fi
}

Expand Down
2 changes: 1 addition & 1 deletion tools/test/collect_cc_coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ function gcov_coverage() {
# gcov 9 or higher use a JSON based format for their coverage reports.
# The output is generated into multiple files: "$(basename ${gcda}).gcov.json.gz"
# Concatenating JSON documents does not yield a valid document, so they are moved individually
mv -- *.gcov.json.gz "$(dirname "$output_file")"
mv -- *.gcov.json.gz "$(dirname "$output_file")/$(dirname ${gcno_path})"
else
# Append all .gcov files in the current directory to the output file.
cat -- *.gcov >> "$output_file"
Expand Down

0 comments on commit 2e10d59

Please sign in to comment.