Skip to content

Commit

Permalink
Move C++ coverage collection logic out of collect_coverage.sh
Browse files Browse the repository at this point in the history
This PR pulls the C++ code coverage collection logic out of `toos/test/of collect_coverage.sh` and moves it to a new script `tools/test/collect_cc_coverage.sh`. There are 2 reasons for this:
* to make the scripts easier to understand and maintain
* in preparation for having a more general code collection logic

This PR does not change the behavior of Bazel, is just a no-op refactoring.

Progress on #5882

Closes #5801.

PiperOrigin-RevId: 209923852
  • Loading branch information
iirina authored and Copybara-Service committed Aug 23, 2018
1 parent 9aea8fd commit 190d4f8
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 48 deletions.
Expand Up @@ -53,6 +53,9 @@
*/
public final class TestActionBuilder {

private static final String CC_CODE_COVERAGE_SCRIPT = "CC_CODE_COVERAGE_SCRIPT";
private static final String LCOV_MERGER = "LCOV_MERGER";

private final RuleContext ruleContext;
private RunfilesSupport runfilesSupport;
private Artifact executable;
Expand Down Expand Up @@ -242,21 +245,29 @@ private TestParams createTestAction(int shards) {
inputsBuilder.addTransitive(metadataFiles);
inputsBuilder.addTransitive(
PrerequisiteArtifacts.nestedSet(ruleContext, ":coverage_support", Mode.DONT_CHECK));

if (ruleContext.isAttrDefined("$collect_cc_coverage", LABEL)) {
Artifact collectCcCoverage =
ruleContext.getHostPrerequisiteArtifact("$collect_cc_coverage");
inputsBuilder.add(collectCcCoverage);
extraTestEnv.put(CC_CODE_COVERAGE_SCRIPT, collectCcCoverage.getExecPathString());
}

// We don't add this attribute to non-supported test target
if (ruleContext.isAttrDefined("$lcov_merger", LABEL)) {
TransitiveInfoCollection lcovMerger =
ruleContext.getPrerequisite("$lcov_merger", Mode.TARGET);
FilesToRunProvider lcovFilesToRun = lcovMerger.getProvider(FilesToRunProvider.class);
if (lcovFilesToRun != null) {
extraTestEnv.put("LCOV_MERGER", lcovFilesToRun.getExecutable().getExecPathString());
extraTestEnv.put(LCOV_MERGER, lcovFilesToRun.getExecutable().getExecPathString());
inputsBuilder.addTransitive(lcovFilesToRun.getFilesToRun());
} else {
NestedSet<Artifact> filesToBuild =
lcovMerger.getProvider(FileProvider.class).getFilesToBuild();

if (Iterables.size(filesToBuild) == 1) {
Artifact lcovMergerArtifact = Iterables.getOnlyElement(filesToBuild);
extraTestEnv.put("LCOV_MERGER", lcovMergerArtifact.getExecPathString());
extraTestEnv.put(LCOV_MERGER, lcovMergerArtifact.getExecPathString());
inputsBuilder.add(lcovMergerArtifact);
} else {
ruleContext.attributeError("$lcov_merger",
Expand Down
Expand Up @@ -15,12 +15,14 @@
package com.google.devtools.build.lib.bazel.rules.cpp;

import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.TRISTATE;
import static com.google.devtools.build.lib.syntax.Type.BOOLEAN;

import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.analysis.config.HostTransition;
import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses.CcBinaryBaseRule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
Expand All @@ -42,6 +44,11 @@ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env)
// to decorate data symbols imported from DLL.
.override(attr("linkstatic", BOOLEAN).value(OS.getCurrent() == OS.WINDOWS))
.override(attr("stamp", TRISTATE).value(TriState.NO))
.add(
attr("$collect_cc_coverage", LABEL)
.cfg(HostTransition.INSTANCE)
.singleArtifact()
.value(env.getToolsLabel("//tools/test:collect_cc_coverage")))
.build();
}

Expand Down
Expand Up @@ -131,6 +131,7 @@ public void setupMockClient(MockToolsConfig config) throws IOException {
"filegroup(name = 'test_setup', srcs = ['test-setup.sh'])",
"filegroup(name = 'test_xml_generator', srcs = ['test-xml-generator.sh'])",
"filegroup(name = 'collect_coverage', srcs = ['collect_coverage.sh'])",
"filegroup(name = 'collect_cc_coverage', srcs = ['collect_cc_coverage.sh'])",
"filegroup(name='coverage_support', srcs=['collect_coverage.sh'])",
"filegroup(name = 'coverage_report_generator', srcs = ['coverage_report_generator.sh'])");

Expand Down
9 changes: 9 additions & 0 deletions src/test/shell/bazel/BUILD
Expand Up @@ -195,6 +195,15 @@ sh_test(
],
)

sh_test(
name = "bazel_cc_code_coverage_test",
srcs = ["bazel_cc_code_coverage_test.sh"],
data = [":test-deps"],
tags = [
"no_windows",
],
)

sh_test(
name = "bazel_localtest_test",
srcs = ["bazel_localtest_test.sh"],
Expand Down
188 changes: 188 additions & 0 deletions src/test/shell/bazel/bazel_cc_code_coverage_test.sh
@@ -0,0 +1,188 @@
#!/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.
[[ ! -x /usr/bin/lcov ]] && echo "lcov not installed. Skipping test" && exit 0
[[ -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 gcda files.
readonly COVERAGE_DIR_VAR="${PWD}"
# 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_VAR.txt"
# Location of the final coverage report.
readonly COVERAGE_OUTPUT_FILE_VAR="${PWD}/coverage_report.dat"

# Path to the canonical C++ coverage script.
readonly COLLECT_CC_COVERAGE_SCRIPT=tools/test/collect_cc_coverage.sh

# Setup to be run for every test.
function set_up() {
# The script expects gcov to be at $COVERAGE_GCOV_PATH.
cp $( which gcov ) "$COVERAGE_GCOV_PATH_VAR"

# The script expects the output file to already exist.
# TODO(iirina): In the future it would be better if the
# script creates the output file.
touch "$COVERAGE_OUTPUT_FILE_VAR"
echo "coverage_srcs/a.gcno" >> "$COVERAGE_MANIFEST_VAR"

# Create the CC sources.
mkdir -p coverage_srcs/
cat << EOF > coverage_srcs/a.h
int a(bool what);
EOF

cat << EOF > coverage_srcs/a.cc
#include "a.h"
int a(bool what) {
if (what) {
return 1;
} else {
return 2;
}
}
EOF

cat << EOF > coverage_srcs/t.cc
#include <stdio.h>
#include "a.h"
int main(void) {
a(true);
}
EOF

generate_gcno_files coverage_srcs/a.h coverage_srcs/a.cc coverage_srcs/t.cc
generate_instrumented_binary ./coverage_srcs/test coverage_srcs/a.h \
coverage_srcs/a.cc coverage_srcs/t.cc
generate_gcda_file ./coverage_srcs/test
}

# Reads the list of arguments provided by the caller (using $@) and uses them
# to produco .gcno files using g++.
function generate_gcno_files() {
# "-fprofile-arcs -ftest-coverage" tells the compiler to generate coverage
# information needed by gcov and include additional code in the object files
# for generating the profiling.
g++ -fprofile-arcs -ftest-coverage "$@" && return 0
fail "Couldn't produce .gcno files for $@"
return 1
}

# Reads the list of arguments provided by the caller (using $@) and uses them
# to produce an instrumented binary using g++.
# - path_to_binary destination of the binary produced by g++
function generate_instrumented_binary() {
local path_to_binary="${1}"; shift
# "-fprofile-arcs -ftest-coverage" tells the compiler to generate coverage
# information needed by gcov and include additional code in the object files
# for generating the profiling.
g++ -fprofile-arcs -ftest-coverage "$@" -o "$path_to_binary" && return 0
fail "Couldn't produce the instrumented binary for $@ \
with path_to_binary $path_to_binary"
return 1
}

# Execute an instrumented binary and generate the gcda file.
# - path_to_binary path of instrumented binary
function generate_gcda_file() {
local path_to_binary="${1}"
"$path_to_binary" && return 0
fail "Couldn't execute the instrumented binary $path_to_binary"
return 1
}

function tear_down() {
rm -rf coverage_srcs/
}

# Runs the script that computes the code coverage report for CC code.
# Sets up the sub-shell environment accordingly:
# - COVERAGE_DIR Directory containing gcda files.
# - COVERAGE_MANIFEST Location of the instrumented file manifest.
# - COVERAGE_OUTPUT_FILE Location of the final coverage report.
# - COVERAGE_GCOV_PATH Location of gcov.
# - ROOT Location from where the code coverage collection
# was invoked.
function run_coverage() {
(COVERAGE_DIR="$COVERAGE_DIR_VAR" \
COVERAGE_GCOV_PATH="$COVERAGE_GCOV_PATH_VAR" \
ROOT="$ROOT_VAR" COVERAGE_MANIFEST="$COVERAGE_MANIFEST_VAR" \
COVERAGE_OUTPUT_FILE="$COVERAGE_OUTPUT_FILE_VAR" \
"$COLLECT_CC_COVERAGE_SCRIPT")
}

function test_cc_test_coverage() {
run_coverage > "$TEST_log"

# After running the test in coverage_srcs/t.cc, the sources covered are the
# test itself and the source file a.cc.
# For more details about the lcov format see
# http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
# The expected result can be constructed manually by following the lcov
# documentation and manually checking what lines of code are covered when
# running the test.
cat <<EOF > expected_result.dat
TN:
SF:coverage_srcs/a.cc
FN:3,_Z1ab
FNDA:1,_Z1ab
FNF:1
FNH:1
DA:3,1
DA:4,1
DA:5,1
DA:7,0
LF:4
LH:3
end_of_record
TN:
SF:coverage_srcs/t.cc
FN:4,main
FNDA:1,main
FNF:1
FNH:1
DA:4,1
DA:5,1
DA:6,1
LF:3
LH:3
end_of_record
EOF

# tools/test/collect_cc_coverage.sh places the coverage result in
# $COVERAGE_OUTPUT_FILE
diff -u expected_result.dat "$COVERAGE_OUTPUT_FILE_VAR" >> "$TEST_log" \
|| fail "Coverage output file is different than the expected file"
}

run_suite "Testing tools/test/collect_cc_coverage.sh"
6 changes: 6 additions & 0 deletions tools/test/BUILD
Expand Up @@ -28,6 +28,11 @@ filegroup(
srcs = ["collect_coverage.sh"],
)

filegroup(
name = "collect_cc_coverage",
srcs = ["collect_cc_coverage.sh"],
)

filegroup(
name = "coverage_report_generator",
srcs = ["@bazel_tools//tools/test/LcovMerger/java/com/google/devtools/lcovmerger:Main"],
Expand All @@ -54,6 +59,7 @@ filegroup(
"test-setup.sh",
"generate-xml.sh",
"collect_coverage.sh",
"collect_cc_coverage.sh",
] + glob(["LcovMerger/**"]) + select({
"@bazel_tools//src/conditions:windows": ["test_wrapper_bin"],
"//conditions:default": [],
Expand Down
5 changes: 5 additions & 0 deletions tools/test/BUILD.tools
Expand Up @@ -23,6 +23,11 @@ filegroup(
srcs = ["collect_coverage.sh"],
)

filegroup(
name = "collect_cc_coverage",
srcs = ["collect_cc_coverage.sh"],
)

filegroup(
name = "coverage_support",
srcs = ["collect_coverage.sh"],
Expand Down

0 comments on commit 190d4f8

Please sign in to comment.