Skip to content

Commit bd7a7e5

Browse files
authored
coverage tool support (#107)
1 parent 04916eb commit bd7a7e5

File tree

8 files changed

+343
-23
lines changed

8 files changed

+343
-23
lines changed

.bazelrc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,31 @@ build --copt="-Wall"
44
build --copt="-Werror"
55
build:windows --copt=/wd4716
66

7+
# Pass PATH variable from the environment
8+
build --action_env=PATH
9+
10+
# Common flags for Clang
11+
build:clang --action_env=BAZEL_COMPILER=clang
12+
build:clang --action_env=CC=clang --action_env=CXX=clang++
13+
build:clang --linkopt=-fuse-ld=lld
14+
15+
# Coverage options
16+
coverage --config=coverage
17+
coverage --build_tests_only
18+
build:coverage --config=clang
19+
build:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1
20+
build:coverage --action_env=GCOV=llvm-profdata
21+
build:coverage --combined_report=lcov
22+
build:coverage --experimental_use_llvm_covmap
23+
build:coverage --experimental_generate_llvm_lcov
24+
build:coverage --collect_code_coverage
25+
build:coverage --instrumentation_filter="//source[/:],//cpp2sky[/:]"
26+
build:coverage --coverage_support=@cpp2sky//bazel/coverage:coverage_support
27+
build:coverage --test_env=CC_CODE_COVERAGE_SCRIPT=external/cpp2sky/bazel/coverage/collect_cc_coverage.sh
28+
build:coverage --strategy=TestRunner=local
29+
build:coverage --strategy=CoverageReport=local
30+
build:coverage --experimental_use_llvm_covmap
31+
build:coverage --collect_code_coverage
32+
build:coverage --test_tag_filters=-nocoverage
33+
734
try-import %workspace%/user.bazelrc

.bazelversion

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.7.2
1+
4.0.0

.github/workflows/main.yml

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,72 @@ on:
88
pull_request:
99
branches: [ main ]
1010

11+
env:
12+
BAZEL_LINK: https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64
13+
CLANG_LINK: https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.1/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
14+
1115
jobs:
16+
format:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- run: |
20+
echo "/opt/llvm/bin" >> $GITHUB_PATH
21+
- uses: actions/checkout@v3
22+
- name: Setup clang-format
23+
run: |
24+
sudo wget -O /tmp/clang-llvm.tar.xz $CLANG_LINK
25+
sudo mkdir -p /opt/llvm
26+
sudo tar -xf /tmp/clang-llvm.tar.xz -C /opt/llvm --strip-components 1
27+
git clone https://github.com/Sarcasm/run-clang-format.git
28+
- name: Run clang-format
29+
run: find ./ -iname "*.h" -o -iname "*.cc" | xargs ./run-clang-format/run-clang-format.py
30+
1231
test:
1332
runs-on: ubuntu-latest
1433
steps:
15-
- uses: actions/checkout@v2
34+
- run: |
35+
echo "/opt/llvm/bin" >> $GITHUB_PATH
36+
- uses: actions/checkout@v3
1637
- name: Install Bazel
1738
run: |
18-
sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64
39+
sudo wget -O /usr/local/bin/bazel $BAZEL_LINK
1940
sudo chmod +x /usr/local/bin/bazel
20-
- name: Run bazel test with c++11
41+
- name: Install Clang
42+
run: |
43+
sudo wget -O /tmp/clang-llvm.tar.xz $CLANG_LINK
44+
sudo mkdir -p /opt/llvm
45+
sudo tar -xf /tmp/clang-llvm.tar.xz -C /opt/llvm --strip-components 1
46+
- name: Run bazel test with GCC c++11
2147
run: |
2248
bazel test --cxxopt=-std=c++0x //...
23-
- name: Run bazel test with c++17
49+
- name: Run bazel test with GCC c++17
2450
run: |
25-
bazel test --cxxopt=-std=c++17 //...
26-
27-
format:
28-
runs-on: ubuntu-latest
29-
steps:
30-
- uses: actions/checkout@v1
31-
- name: Setup clang-format
32-
run: |
33-
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.1/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
34-
tar -xvf clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
35-
sudo mv ./clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04/bin/clang-format /usr/local/bin
36-
rm -rf clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04/
37-
git clone https://github.com/Sarcasm/run-clang-format.git
38-
- name: Run clang-format
39-
run: find ./ -iname "*.h" -o -iname "*.cc" | xargs ./run-clang-format/run-clang-format.py
51+
bazel test --cxxopt=-std=c++17 //...
52+
- name: Run bazel test with CLANG c++11
53+
run: |
54+
bazel test --config=clang --cxxopt=-std=c++0x //...
55+
- name: Run bazel test with CLANG c++17
56+
run: |
57+
bazel test --config=clang --cxxopt=-std=c++17 //...
58+
- name: Install lcov and genhtml
59+
run: |
60+
sudo apt update
61+
sudo apt -y install lcov
62+
- name: Run coverage test
63+
run: |
64+
./coverage.sh
65+
- name: upload coverage data to codecov
66+
uses: codecov/codecov-action@v2
67+
with:
68+
fail_ci_if_error: true
69+
files: ./coverage_report/coverage.dat
70+
name: codecov-cpp2sky
71+
verbose: true
4072

4173
e2e-cpp:
4274
runs-on: ubuntu-latest
4375
steps:
44-
- uses: actions/checkout@v2
76+
- uses: actions/checkout@v3
4577
- name: Prepare service container
4678
run: |
4779
docker-compose -f test/e2e/docker/docker-compose.e2e.yml up -d
@@ -55,7 +87,7 @@ jobs:
5587
e2e-python:
5688
runs-on: ubuntu-latest
5789
steps:
58-
- uses: actions/checkout@v2
90+
- uses: actions/checkout@v3
5991
- name: Prepare service container
6092
run: |
6193
docker-compose -f test/e2e/docker/docker-compose.e2e-python.yml up -d

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ user.bazelrc
4444
.vscode/
4545

4646
.clangd/
47-
compile_commands.json
47+
compile_commands.json
48+
49+
coverage_report

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# cpp2sky
22

33
![cpp2sky test](https://github.com/SkyAPM/cpp2sky/workflows/cpp2sky%20test/badge.svg)
4+
![codecov test](https://codecov.io/gh/SkyAPM/cpp2sky/branch/main/graph/badge.svg)
45

56
Distributed tracing and monitor SDK in CPP for Apache SkyWalking APM. This SDK is compatible with C++ 17, C++ 14, and C++ 11.
67

bazel/coverage/BUILD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
licenses(["notice"]) # Apache 2
2+
3+
filegroup(
4+
name = "coverage_support",
5+
srcs = ["collect_cc_coverage.sh"],
6+
)
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#!/bin/bash -x
2+
# This is fork from https://raw.githubusercontent.com/bazelbuild/bazel/master/tools/test/collect_cc_coverage.sh
3+
4+
# Copyright 2016 The Bazel Authors. All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
# This script collects code coverage data for C++ sources, after the tests
19+
# were executed.
20+
#
21+
# Bazel C++ code coverage collection support is poor and limited. There is
22+
# an ongoing effort to improve this (tracking issue #1118).
23+
#
24+
# Bazel uses the lcov tool for gathering coverage data. There is also
25+
# an experimental support for clang llvm coverage, which uses the .profraw
26+
# data files to compute the coverage report.
27+
#
28+
# This script assumes the following environment variables are set:
29+
# - COVERAGE_DIR Directory containing metadata files needed for
30+
# coverage collection (e.g. gcda files, profraw).
31+
# - COVERAGE_MANIFEST Location of the instrumented file manifest.
32+
# - COVERAGE_GCOV_PATH Location of gcov. This is set by the TestRunner.
33+
# - COVERAGE_GCOV_OPTIONS Additional options to pass to gcov.
34+
# - ROOT Location from where the code coverage collection
35+
# was invoked.
36+
#
37+
# The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either
38+
# gcda or profraw) and uses either lcov or gcov to get the coverage data.
39+
# The coverage data is placed in $COVERAGE_OUTPUT_FILE.
40+
41+
# Checks if clang llvm coverage should be used instead of lcov.
42+
function uses_llvm() {
43+
if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then
44+
return 0
45+
fi
46+
return 1
47+
}
48+
49+
# Returns 0 if gcov must be used, 1 otherwise.
50+
function uses_gcov() {
51+
[[ "$GCOV_COVERAGE" -eq "1" ]] && return 0
52+
return 1
53+
}
54+
55+
function init_gcov() {
56+
# Symlink the gcov tool such with a link called gcov. Clang comes with a tool
57+
# called llvm-cov, which behaves like gcov if symlinked in this way (otherwise
58+
# we would need to invoke it with "llvm-cov gcov").
59+
# For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html.
60+
GCOV="${COVERAGE_DIR}/gcov"
61+
if [ ! -f "${COVERAGE_GCOV_PATH}" ]; then
62+
echo "GCov does not exist at the given path: '${COVERAGE_GCOV_PATH}'"
63+
exit 1
64+
fi
65+
# When using a tool from a toolchain COVERAGE_GCOV_PATH will be a relative
66+
# path. To make it work on different working directories it's required to
67+
# convert the path to an absolute one.
68+
COVERAGE_GCOV_PATH_ABS="$(cd "${COVERAGE_GCOV_PATH%/*}" && pwd)/${COVERAGE_GCOV_PATH##*/}"
69+
ln -s "${COVERAGE_GCOV_PATH_ABS}" "${GCOV}"
70+
}
71+
72+
# Computes code coverage data using the clang generated metadata found under
73+
# $COVERAGE_DIR.
74+
# Writes the collected coverage into the given output file.
75+
function llvm_coverage_lcov() {
76+
local output_file="${1}"; shift
77+
export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw"
78+
"${COVERAGE_GCOV_PATH}" merge -output "${output_file}.data" \
79+
"${COVERAGE_DIR}"/*.profraw
80+
81+
local object_param=""
82+
while read -r line; do
83+
if [[ ${line: -24} == "runtime_objects_list.txt" ]]; then
84+
while read -r line_runtime_object; do
85+
if [[ ${line_runtime_object} == *"absl"* ]]; then
86+
continue
87+
fi
88+
object_param+=" -object ${RUNFILES_DIR}/${TEST_WORKSPACE}/${line_runtime_object}"
89+
done < "${line}"
90+
fi
91+
done < "${COVERAGE_MANIFEST}"
92+
93+
"${LLVM_COV}" export -instr-profile "${output_file}.data" -format=lcov \
94+
-ignore-filename-regex='.*external/.+' \
95+
-ignore-filename-regex='/tmp/.+' \
96+
${object_param} | sed 's#/proc/self/cwd/##' > "${output_file}"
97+
}
98+
99+
function llvm_coverage_profdata() {
100+
local output_file="${1}"; shift
101+
export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw"
102+
"${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \
103+
"${COVERAGE_DIR}"/*.profraw
104+
}
105+
106+
# Generates a code coverage report in gcov intermediate text format by invoking
107+
# gcov and using the profile data (.gcda) and notes (.gcno) files.
108+
#
109+
# The profile data files are expected to be found under $COVERAGE_DIR.
110+
# The notes file are expected to be found under $ROOT.
111+
#
112+
# - output_file The location of the file where the generated code coverage
113+
# report is written.
114+
function gcov_coverage() {
115+
local output_file="${1}"; shift
116+
117+
# We'll save the standard output of each the gcov command in this log.
118+
local gcov_log="$output_file.gcov.log"
119+
120+
# Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR
121+
# because gcov expects them to be in the same directory.
122+
while read -r line; do
123+
if [[ ${line: -4} == "gcno" ]]; then
124+
gcno_path=${line}
125+
local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda"
126+
# If the gcda file was not found we skip generating coverage from the gcno
127+
# file.
128+
if [[ -f "$gcda" ]]; then
129+
# gcov expects both gcno and gcda files to be in the same directory.
130+
# We overcome this by copying the gcno to $COVERAGE_DIR where the gcda
131+
# files are expected to be.
132+
if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then
133+
mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno_path})"
134+
cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}"
135+
fi
136+
# Invoke gcov to generate a code coverage report with the flags:
137+
# -i Output gcov file in an intermediate text format.
138+
# The output is a single .gcov file per .gcda file.
139+
# No source code is required.
140+
# -o directory The directory containing the .gcno and
141+
# .gcda data files.
142+
# "${gcda"} The input file name. gcov is looking for data files
143+
# named after the input filename without its extension.
144+
# gcov produces files called <source file name>.gcov in the current
145+
# directory. These contain the coverage information of the source file
146+
# they correspond to. One .gcov file is produced for each source
147+
# (or header) file containing code which was compiled to produce the
148+
# .gcda files.
149+
# Don't generate branch coverage (-b) because of a gcov issue that
150+
# segfaults when both -i and -b are used (see
151+
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879).
152+
"${GCOV}" -i $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}"
153+
154+
# Extract gcov's version: the output of `gcov --version` contains the
155+
# version as a set of major-minor-patch numbers, of which we extract
156+
# the major version.
157+
gcov_major_version=$("${GCOV}" --version | sed -n -E -e 's/^.*\s([0-9]+)\.[0-9]+\.[0-9]+\s?.*$/\1/p')
158+
159+
# Check the gcov version so we can process the data correctly
160+
if [[ $gcov_major_version -ge 9 ]]; then
161+
# gcov 9 or higher use a JSON based format for their coverage reports.
162+
# The output is generated into multiple files: "$(basename ${gcda}).gcov.json.gz"
163+
# Concatenating JSON documents does not yield a valid document, so they are moved individually
164+
mv -- *.gcov.json.gz "$(dirname "$output_file")"
165+
else
166+
# Append all .gcov files in the current directory to the output file.
167+
cat -- *.gcov >> "$output_file"
168+
# Delete the .gcov files.
169+
rm -- *.gcov
170+
fi
171+
fi
172+
fi
173+
done < "${COVERAGE_MANIFEST}"
174+
}
175+
176+
function main() {
177+
init_gcov
178+
179+
# If llvm code coverage is used, we output the raw code coverage report in
180+
# the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other
181+
# format by LcovMerger.
182+
# TODO(#5881): Convert profdata reports to lcov.
183+
if uses_llvm; then
184+
if [[ "${GENERATE_LLVM_LCOV}" == "1" ]]; then
185+
BAZEL_CC_COVERAGE_TOOL="LLVM_LCOV"
186+
else
187+
BAZEL_CC_COVERAGE_TOOL="PROFDATA"
188+
fi
189+
fi
190+
191+
# When using either gcov or lcov, have an output file specific to the test
192+
# and format used. For lcov we generate a ".dat" output file and for gcov
193+
# a ".gcov" output file. It is important that these files are generated under
194+
# COVERAGE_DIR.
195+
# When this script is invoked by tools/test/collect_coverage.sh either of
196+
# these two coverage reports will be picked up by LcovMerger and their
197+
# content will be converted and/or merged with other reports to an lcov
198+
# format, generating the final code coverage report.
199+
case "$BAZEL_CC_COVERAGE_TOOL" in
200+
("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;;
201+
("PROFDATA") llvm_coverage_profdata "$COVERAGE_DIR/_cc_coverage.profdata" ;;
202+
("LLVM_LCOV") llvm_coverage_lcov "$COVERAGE_DIR/_cc_coverage.dat" ;;
203+
(*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \
204+
&& exit 1
205+
esac
206+
}
207+
208+
main

0 commit comments

Comments
 (0)