blob: 446da6ad651ab569c7f8f65c8caaddbd5a8e82d0 [file] [log] [blame]
#!/bin/bash -eu
#
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Unit tests for tools/test/collect_cc_code_coverage.sh
# Load the test setup defined in the parent directory
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CURRENT_DIR}/../integration_test_setup.sh" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }
# Check if all the tools required by CC coverage are installed.
[[ -z $( which gcov ) ]] && fail "gcov not installed. Skipping test" && exit 0
[[ -z $( which g++ ) ]] && fail "g++ not installed. Skipping test" && exit 0
# These are the variables needed by tools/test/collect_cc_coverage.sh
# They will be properly sub-shelled when invoking the script.
# Directory containing gcno and gcda files.
readonly COVERAGE_DIR_VAR="${PWD}/coverage_dir"
# Location of gcov.
readonly COVERAGE_GCOV_PATH_VAR="${PWD}/mygcov"
# Location from where the code coverage collection was invoked.
readonly ROOT_VAR="${PWD}"
# Location of the instrumented file manifest.
readonly COVERAGE_MANIFEST_VAR="${PWD}/coverage_manifest.txt"
# Path to the canonical C++ coverage script.
readonly COLLECT_CC_COVERAGE_SCRIPT=tools/test/collect_cc_coverage.sh
# Return a string in the form "device_id%inode" for a given file.
#
# Checking if two files have the same deviceID and inode is enough to
# determine if they are the same. For more details about inodes see
# http://www.grymoire.com/Unix/Inodes.html.
#
# - file The absolute path of the file.
function get_file_id() {
local file="${1}"; shift
stat -c "%d:%i" ${file}
}
# Setup to be run for every test.
function set_up() {
mkdir -p "${COVERAGE_DIR_VAR}"
# COVERAGE_DIR has to be different than ROOT and PWD for the test to be
# accurate.
local coverage_dir_id=$(get_file_id "$COVERAGE_DIR_VAR")
[[ $coverage_dir_id == $(get_file_id "$ROOT_VAR") ]] \
&& fail "COVERAGE_DIR_VAR must be different than ROOT_VAR"
[[ $coverage_dir_id == $(get_file_id "$PWD") ]] \
&& fail "COVERAGE_DIR_VAR must be different than PWD"
# The script expects gcov to be at $COVERAGE_GCOV_PATH.
cp $( which gcov ) "$COVERAGE_GCOV_PATH_VAR"
mkdir -p "$COVERAGE_DIR_VAR/coverage_srcs"
# All generated .gcno files need to be in the manifest otherwise
# the coverage report will be incomplete.
echo "coverage_srcs/t.gcno" >> "$COVERAGE_MANIFEST_VAR"
echo "coverage_srcs/a.gcno" >> "$COVERAGE_MANIFEST_VAR"
# Create the CC sources.
mkdir -p "$ROOT_VAR/coverage_srcs/"
cat << EOF > "$ROOT_VAR/coverage_srcs/a.h"
int a(bool what);
EOF
cat << EOF > "$ROOT_VAR/coverage_srcs/a.cc"
#include "a.h"
#include "b.h"
int a(bool what) {
if (what) {
return b(1);
} else {
return b(-1);
}
}
EOF
cat << EOF > "$ROOT_VAR/coverage_srcs/b.h"
int b(int what) {
if (what > 0) {
return 1;
} else {
return 2;
}
}
EOF
cat << EOF > "$ROOT_VAR/coverage_srcs/t.cc"
#include <stdio.h>
#include "a.h"
int main(void) {
a(true);
}
EOF
generate_and_execute_instrumented_binary coverage_srcs/test \
"$COVERAGE_DIR_VAR/coverage_srcs" \
coverage_srcs/a.h coverage_srcs/a.cc \
coverage_srcs/b.h \
coverage_srcs/t.cc
# g++ generates the notes files in the current directory. The documentation
# (https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html#Gcov-Data-Files)
# says they are placed in the same directory as the object file, but they
# are not. Therefore we move them in the same directory.
mv a.gcno coverage_srcs/a.gcno
mv t.gcno coverage_srcs/t.gcno
}
# 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.
#
# Executes the instrumented binary. This step also generates the
# profile data (.gcda) files.
# - path_to_binary destination of the binary produced by g++
function generate_and_execute_instrumented_binary() {
local path_to_binary="${1}"; shift
local gcda_directory="${1}"; shift
# -fprofile-arcs Instruments $path_to_binary. During execution the binary
# records code coverage information.
# -ftest-coverage Produces a notes (.gcno) file that coverage utilities
# (e.g. gcov, lcov) can use to show a coverage report.
# -fprofile-dir Sets the directory where the profile data (gcda) appears.
#
# The profile data files need to be at a specific location where the C++
# coverage scripts expects them to be ($COVERAGE_DIR/path/to/sources/).
g++ -fprofile-arcs -ftest-coverage \
-fprofile-dir="$gcda_directory" \
"$@" -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.
"$path_to_binary" || fail "Couldn't execute the instrumented binary \
$path_to_binary"
}
function tear_down() {
rm -f "$COVERAGE_MANIFEST_VAR"
rm -f "$COVERAGE_GCOV_PATH_VAR"
rm -rf "$COVERAGE_DIR_VAR"
rm -rf coverage_srcs/
}
# Asserts if the given expected coverage result is included in the given output
# file.
#
# - expected_coverage The expected result that must be included in the output.
# - output_file The location of the coverage output file.
function assert_coverage_entry_in_file() {
local expected_coverage="${1}"; shift
local output_file="${1}"; shift
# Replace newlines with commas to facilitate the assertion.
local expected_coverage_no_newlines="$( echo "$expected_coverage" | tr '\n' ',' )"
local output_file_no_newlines="$( cat "$output_file" | tr '\n' ',' )"
(echo "$output_file_no_newlines" | grep "$expected_coverage_no_newlines")\
|| fail "Expected coverage result
<$expected_coverage>
was not found in actual coverage report:
<$( cat $output_file )>"
}
# Asserts if coverage result in gcov format for coverage_srcs/a.cc is included
# in the given output file.
#
# - output_file The location of the coverage output file.
function assert_gcov_coverage_srcs_a_cc() {
local output_file="${1}"; shift
# The expected coverage result for coverage_srcs/a.cc in gcov format.
local expected_gcov_result_a_cc="file:coverage_srcs/a.cc
function:4,1,_Z1ab
lcount:4,1
lcount:5,1
lcount:6,1
lcount:8,0"
assert_coverage_entry_in_file "$expected_gcov_result_a_cc" "$output_file"
}
# Asserts if coverage result in gcov format for coverage_srcs/t.cc is included
# in the given output file.
#
# - output_file The location of the coverage output file.
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
lcount:5,1
lcount:6,1"
assert_coverage_entry_in_file "$expected_gcov_result_t_cc" "$output_file"
}
function assert_gcov_coverage_srcs_b_h() {
local output_file="${1}"; shift
# The expected coverage result for coverage_srcs/t.cc in gcov format.
local expected_gcov_result="file:coverage_srcs/b.h
function:1,1,_Z1bi
lcount:1,1
lcount:2,1
lcount:3,1
lcount:5,0"
assert_coverage_entry_in_file "$expected_gcov_result" "$output_file"
}
function test_cc_test_coverage_gcov() {
"$gcov_location" -version | grep "LLVM" && \
echo "gcov LLVM version not supported. Skipping test." && return
# gcov -v | grep "gcov" outputs a line that looks like this:
# gcov (Debian 7.3.0-5) 7.3.0
local gcov_version="$(gcov -v | grep "gcov" | cut -d " " -f 4 | cut -d "." -f 1)"
[ "$gcov_version" -lt 7 ] \
&& echo "gcov version before 7.0 is not supported. Skipping test." \
&& return
(COVERAGE_DIR="$COVERAGE_DIR_VAR" \
COVERAGE_GCOV_PATH="$COVERAGE_GCOV_PATH_VAR" \
ROOT="$ROOT_VAR" COVERAGE_MANIFEST="$COVERAGE_MANIFEST_VAR" \
BAZEL_CC_COVERAGE_TOOL="GCOV" \
"$COLLECT_CC_COVERAGE_SCRIPT") > "$TEST_log"
# Location of the output file of the C++ coverage script when gcov is used.
local output_file="$COVERAGE_DIR_VAR/_cc_coverage.gcov"
# Assert that the coverage output file contains the coverage data for the
# two cc files: coverage_srcs/a.cc and coverage_srcs/t.cc.
# The result for each source file must be asserted separately because the
# coverage gcov does not guarantee any particular order.
# The order can differ for example based on OS or version. The source files
# order in the coverage report is not relevant.
assert_gcov_coverage_srcs_a_cc "$output_file"
assert_gcov_coverage_srcs_t_cc "$output_file"
assert_gcov_coverage_srcs_b_h "$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 ]] || \
fail "Number of lines in C++ gcov coverage output file is "\
"$nr_lines and different than 17"
}
run_suite "Testing tools/test/collect_cc_coverage.sh"