diff --git a/.bazelrc b/.bazelrc index 6ec6eb4..b4dd9f2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,4 +4,31 @@ build --copt="-Wall" build --copt="-Werror" build:windows --copt=/wd4716 +# Pass PATH variable from the environment +build --action_env=PATH + +# Common flags for Clang +build:clang --action_env=BAZEL_COMPILER=clang +build:clang --action_env=CC=clang --action_env=CXX=clang++ +build:clang --linkopt=-fuse-ld=lld + +# Coverage options +coverage --config=coverage +coverage --build_tests_only +build:coverage --config=clang +build:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 +build:coverage --action_env=GCOV=llvm-profdata +build:coverage --combined_report=lcov +build:coverage --experimental_use_llvm_covmap +build:coverage --experimental_generate_llvm_lcov +build:coverage --collect_code_coverage +build:coverage --instrumentation_filter="//source[/:],//cpp2sky[/:]" +build:coverage --coverage_support=@cpp2sky//bazel/coverage:coverage_support +build:coverage --test_env=CC_CODE_COVERAGE_SCRIPT=external/cpp2sky/bazel/coverage/collect_cc_coverage.sh +build:coverage --strategy=TestRunner=local +build:coverage --strategy=CoverageReport=local +build:coverage --experimental_use_llvm_covmap +build:coverage --collect_code_coverage +build:coverage --test_tag_filters=-nocoverage + try-import %workspace%/user.bazelrc diff --git a/.bazelversion b/.bazelversion index 0b2eb36..fcdb2e1 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -3.7.2 +4.0.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3f4878a..06f538e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,40 +8,72 @@ on: pull_request: branches: [ main ] +env: + BAZEL_LINK: https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64 + 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 + jobs: + format: + runs-on: ubuntu-latest + steps: + - run: | + echo "/opt/llvm/bin" >> $GITHUB_PATH + - uses: actions/checkout@v3 + - name: Setup clang-format + run: | + sudo wget -O /tmp/clang-llvm.tar.xz $CLANG_LINK + sudo mkdir -p /opt/llvm + sudo tar -xf /tmp/clang-llvm.tar.xz -C /opt/llvm --strip-components 1 + git clone https://github.com/Sarcasm/run-clang-format.git + - name: Run clang-format + run: find ./ -iname "*.h" -o -iname "*.cc" | xargs ./run-clang-format/run-clang-format.py + test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - run: | + echo "/opt/llvm/bin" >> $GITHUB_PATH + - uses: actions/checkout@v3 - name: Install Bazel run: | - sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64 + sudo wget -O /usr/local/bin/bazel $BAZEL_LINK sudo chmod +x /usr/local/bin/bazel - - name: Run bazel test with c++11 + - name: Install Clang + run: | + sudo wget -O /tmp/clang-llvm.tar.xz $CLANG_LINK + sudo mkdir -p /opt/llvm + sudo tar -xf /tmp/clang-llvm.tar.xz -C /opt/llvm --strip-components 1 + - name: Run bazel test with GCC c++11 run: | bazel test --cxxopt=-std=c++0x //... - - name: Run bazel test with c++17 + - name: Run bazel test with GCC c++17 run: | - bazel test --cxxopt=-std=c++17 //... - - format: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Setup clang-format - run: | - 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 - tar -xvf clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz - sudo mv ./clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04/bin/clang-format /usr/local/bin - rm -rf clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04/ - git clone https://github.com/Sarcasm/run-clang-format.git - - name: Run clang-format - run: find ./ -iname "*.h" -o -iname "*.cc" | xargs ./run-clang-format/run-clang-format.py + bazel test --cxxopt=-std=c++17 //... + - name: Run bazel test with CLANG c++11 + run: | + bazel test --config=clang --cxxopt=-std=c++0x //... + - name: Run bazel test with CLANG c++17 + run: | + bazel test --config=clang --cxxopt=-std=c++17 //... + - name: Install lcov and genhtml + run: | + sudo apt update + sudo apt -y install lcov + - name: Run coverage test + run: | + ./coverage.sh + - name: upload coverage data to codecov + uses: codecov/codecov-action@v2 + with: + fail_ci_if_error: true + files: ./coverage_report/coverage.dat + name: codecov-cpp2sky + verbose: true e2e-cpp: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Prepare service container run: | docker-compose -f test/e2e/docker/docker-compose.e2e.yml up -d @@ -55,7 +87,7 @@ jobs: e2e-python: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Prepare service container run: | docker-compose -f test/e2e/docker/docker-compose.e2e-python.yml up -d diff --git a/.gitignore b/.gitignore index 3ec3e07..7c0087b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,6 @@ user.bazelrc .vscode/ .clangd/ -compile_commands.json \ No newline at end of file +compile_commands.json + +coverage_report diff --git a/README.md b/README.md index c4a957e..3817526 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # cpp2sky ![cpp2sky test](https://github.com/SkyAPM/cpp2sky/workflows/cpp2sky%20test/badge.svg) +![codecov test](https://codecov.io/gh/SkyAPM/cpp2sky/branch/main/graph/badge.svg) Distributed tracing and monitor SDK in CPP for Apache SkyWalking APM. This SDK is compatible with C++ 17, C++ 14, and C++ 11. diff --git a/bazel/coverage/BUILD b/bazel/coverage/BUILD new file mode 100644 index 0000000..cc132e8 --- /dev/null +++ b/bazel/coverage/BUILD @@ -0,0 +1,6 @@ +licenses(["notice"]) # Apache 2 + +filegroup( + name = "coverage_support", + srcs = ["collect_cc_coverage.sh"], +) diff --git a/bazel/coverage/collect_cc_coverage.sh b/bazel/coverage/collect_cc_coverage.sh new file mode 100755 index 0000000..90e6704 --- /dev/null +++ b/bazel/coverage/collect_cc_coverage.sh @@ -0,0 +1,208 @@ +#!/bin/bash -x +# This is fork from https://raw.githubusercontent.com/bazelbuild/bazel/master/tools/test/collect_cc_coverage.sh + +# Copyright 2016 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. + +# This script collects code coverage data for C++ sources, after the tests +# were executed. +# +# Bazel C++ code coverage collection support is poor and limited. There is +# an ongoing effort to improve this (tracking issue #1118). +# +# Bazel uses the lcov tool for gathering coverage data. There is also +# an experimental support for clang llvm coverage, which uses the .profraw +# data files to compute the coverage report. +# +# This script assumes the following environment variables are set: +# - COVERAGE_DIR Directory containing metadata files needed for +# coverage collection (e.g. gcda files, profraw). +# - COVERAGE_MANIFEST Location of the instrumented file manifest. +# - COVERAGE_GCOV_PATH Location of gcov. This is set by the TestRunner. +# - COVERAGE_GCOV_OPTIONS Additional options to pass to gcov. +# - ROOT Location from where the code coverage collection +# was invoked. +# +# The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either +# gcda or profraw) and uses either lcov or gcov to get the coverage data. +# The coverage data is placed in $COVERAGE_OUTPUT_FILE. + +# Checks if clang llvm coverage should be used instead of lcov. +function uses_llvm() { + if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Returns 0 if gcov must be used, 1 otherwise. +function uses_gcov() { + [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0 + return 1 +} + +function init_gcov() { + # Symlink the gcov tool such with a link called gcov. Clang comes with a tool + # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise + # we would need to invoke it with "llvm-cov gcov"). + # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html. + GCOV="${COVERAGE_DIR}/gcov" + if [ ! -f "${COVERAGE_GCOV_PATH}" ]; then + echo "GCov does not exist at the given path: '${COVERAGE_GCOV_PATH}'" + exit 1 + fi + # When using a tool from a toolchain COVERAGE_GCOV_PATH will be a relative + # path. To make it work on different working directories it's required to + # convert the path to an absolute one. + COVERAGE_GCOV_PATH_ABS="$(cd "${COVERAGE_GCOV_PATH%/*}" && pwd)/${COVERAGE_GCOV_PATH##*/}" + ln -s "${COVERAGE_GCOV_PATH_ABS}" "${GCOV}" +} + +# Computes code coverage data using the clang generated metadata found under +# $COVERAGE_DIR. +# Writes the collected coverage into the given output file. +function llvm_coverage_lcov() { + local output_file="${1}"; shift + export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" + "${COVERAGE_GCOV_PATH}" merge -output "${output_file}.data" \ + "${COVERAGE_DIR}"/*.profraw + + local object_param="" + while read -r line; do + if [[ ${line: -24} == "runtime_objects_list.txt" ]]; then + while read -r line_runtime_object; do + if [[ ${line_runtime_object} == *"absl"* ]]; then + continue + fi + object_param+=" -object ${RUNFILES_DIR}/${TEST_WORKSPACE}/${line_runtime_object}" + done < "${line}" + fi + done < "${COVERAGE_MANIFEST}" + + "${LLVM_COV}" export -instr-profile "${output_file}.data" -format=lcov \ + -ignore-filename-regex='.*external/.+' \ + -ignore-filename-regex='/tmp/.+' \ + ${object_param} | sed 's#/proc/self/cwd/##' > "${output_file}" +} + +function llvm_coverage_profdata() { + local output_file="${1}"; shift + export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" + "${COVERAGE_GCOV_PATH}" merge -output "${output_file}" \ + "${COVERAGE_DIR}"/*.profraw +} + +# Generates a code coverage report in gcov intermediate text format by invoking +# gcov and using the profile data (.gcda) and notes (.gcno) files. +# +# The profile data files are expected to be found under $COVERAGE_DIR. +# The notes file are expected to be found under $ROOT. +# +# - output_file The location of the file where the generated code coverage +# report is written. +function gcov_coverage() { + local output_file="${1}"; shift + + # We'll save the standard output of each the gcov command in this log. + local gcov_log="$output_file.gcov.log" + + # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR + # because gcov expects them to be in the same directory. + while read -r line; do + if [[ ${line: -4} == "gcno" ]]; then + gcno_path=${line} + local gcda="${COVERAGE_DIR}/$(dirname ${gcno_path})/$(basename ${gcno_path} .gcno).gcda" + # If the gcda file was not found we skip generating coverage from the gcno + # file. + if [[ -f "$gcda" ]]; then + # gcov expects both gcno and gcda files to be in the same directory. + # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda + # files are expected to be. + if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then + mkdir -p "${COVERAGE_DIR}/$(dirname ${gcno_path})" + cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}" + fi + # Invoke gcov to generate a code coverage report with the flags: + # -i Output gcov file in an intermediate text format. + # The output is a single .gcov file per .gcda file. + # No source code is required. + # -o directory The directory containing the .gcno and + # .gcda data files. + # "${gcda"} The input file name. gcov is looking for data files + # named after the input filename without its extension. + # gcov produces files called .gcov in the current + # directory. These contain the coverage information of the source file + # they correspond to. One .gcov file is produced for each source + # (or header) file containing code which was compiled to produce the + # .gcda files. + # Don't generate branch coverage (-b) because of a gcov issue that + # segfaults when both -i and -b are used (see + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). + "${GCOV}" -i $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}" + + # Extract gcov's version: the output of `gcov --version` contains the + # version as a set of major-minor-patch numbers, of which we extract + # the major version. + gcov_major_version=$("${GCOV}" --version | sed -n -E -e 's/^.*\s([0-9]+)\.[0-9]+\.[0-9]+\s?.*$/\1/p') + + # Check the gcov version so we can process the data correctly + if [[ $gcov_major_version -ge 9 ]]; then + # 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")" + else + # Append all .gcov files in the current directory to the output file. + cat -- *.gcov >> "$output_file" + # Delete the .gcov files. + rm -- *.gcov + fi + fi + fi + done < "${COVERAGE_MANIFEST}" +} + +function main() { + init_gcov + + # If llvm code coverage is used, we output the raw code coverage report in + # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other + # format by LcovMerger. + # TODO(#5881): Convert profdata reports to lcov. + if uses_llvm; then + if [[ "${GENERATE_LLVM_LCOV}" == "1" ]]; then + BAZEL_CC_COVERAGE_TOOL="LLVM_LCOV" + else + BAZEL_CC_COVERAGE_TOOL="PROFDATA" + fi + fi + + # When using either gcov or lcov, have an output file specific to the test + # and format used. For lcov we generate a ".dat" output file and for gcov + # a ".gcov" output file. It is important that these files are generated under + # COVERAGE_DIR. + # When this script is invoked by tools/test/collect_coverage.sh either of + # these two coverage reports will be picked up by LcovMerger and their + # content will be converted and/or merged with other reports to an lcov + # format, generating the final code coverage report. + case "$BAZEL_CC_COVERAGE_TOOL" in + ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;; + ("PROFDATA") llvm_coverage_profdata "$COVERAGE_DIR/_cc_coverage.profdata" ;; + ("LLVM_LCOV") llvm_coverage_lcov "$COVERAGE_DIR/_cc_coverage.dat" ;; + (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \ + && exit 1 + esac +} + +main diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 0000000..7c6bfd3 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +[[ -z "${SRCDIR}" ]] && SRCDIR="${PWD}" + +OUTPUT_DIR="./coverage_report/" +DATA_DIR="${SRCDIR}/bazel-testlogs/" +PROJECT=$(basename "${SRCDIR}") + +# This is the target that will be run to generate coverage data. It can be overridden +# by consumer projects that want to run coverage on a different/combined target. +# Command-line arguments take precedence over ${COVERAGE_TARGET}. +if [[ $# -gt 0 ]]; then + COVERAGE_TARGETS=$* +elif [[ -n "${COVERAGE_TARGET}" ]]; then + COVERAGE_TARGETS=${COVERAGE_TARGET} +else + COVERAGE_TARGETS=//test/... +fi + +echo "Starting gen_coverage.sh..." +echo " PWD=$(pwd)" +echo " OUTPUT_DIR=${OUTPUT_DIR}" +echo " DATA_DIR=${DATA_DIR}" +echo " TARGETS=${COVERAGE_TARGETS}" + +echo "Generating coverage data..." +bazel coverage ${COVERAGE_TARGETS} --test_output=errors + +rm -rf ${OUTPUT_DIR} +mkdir -p ${OUTPUT_DIR} + +COVERAGE_DATA="${OUTPUT_DIR}/coverage.dat" +cp bazel-out/_coverage/_coverage_report.dat "${COVERAGE_DATA}" + +echo "Generating report..." + +genhtml --title ${PROJECT} --ignore-errors "source" ${COVERAGE_DATA} -o "${OUTPUT_DIR}" +tar -zcf ${PROJECT}_coverage.tar.gz ${OUTPUT_DIR} +mv ${PROJECT}_coverage.tar.gz ${OUTPUT_DIR} + +echo "HTML coverage report is in ${OUTPUT_DIR}/index.html" +echo "All coverage report files are in ${OUTPUT_DIR}/${PROJECT}_coverage.tar.gz"