Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -416,26 +416,39 @@ build:coverage --test_env=GCOV=llvm-profdata
# BAZEL_LLVM_COV is used by rules_cc to generate coverage reports from profile data
build:coverage --action_env=BAZEL_LLVM_COV=llvm-cov
build:coverage --test_env=BAZEL_LLVM_COV=llvm-cov
# Enable optimizations to reduce coverage overhead while maintaining good coverage quality
build:coverage --copt=-O2
build:coverage --copt=-DNDEBUG
build:coverage --combined_report=lcov
build:coverage --experimental_use_llvm_covmap
build:coverage --experimental_generate_llvm_lcov
# Usually coverage is run as part of the test action, and so by default, we don't get all coverage back as outputs
# of the remote execution by default. These flags override the default and obtain the coverage data.
build:coverage --experimental_fetch_all_coverage_outputs
build:coverage --experimental_split_coverage_postprocessing
build:coverage --collect_code_coverage
build:coverage --instrumentation_filter="^//,-//external"
build:coverage --instrument_test_targets
# KJ uses _exit() by default which bypasses atexit handlers and prevents LLVM profile runtime
# from writing coverage data. KJ_CLEAN_SHUTDOWN forces use of normal exit() instead.
build:coverage --test_env=KJ_CLEAN_SHUTDOWN=1
build:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1
build:coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1
# Optimize V8 and external dependencies since they're excluded from coverage instrumentation anyway
build:coverage --copt=-DNDEBUG
build:coverage --config=v8-codegen-opt
# Flags to run tests locally which are necessary since Bazel C++ LLVM coverage isn't fully supported for remote builds
build:coverage --remote_download_outputs=all
build:coverage --strategy=TestRunner=local,remote
build:coverage --strategy=CoverageReport=local,remote
# Bazel remote caching is incompatible with C++ LLVM coverage so we need to deactivate it for coverage builds
build:coverage --noremote_accept_cached
# Bazel will fail to create coverage information if tests have been cached previously
coverage --nocache_test_results
coverage --test_tag_filters=-off-by-default,-requires-fuzzilli,-requires-container-engine,-lint,-benchmark,-workerd-benchmark,-no-coverage
# Coverage instrumentation slows down test execution, so extend timeouts
# We disable enormous tests due to the slowdown (CI jobs have a 6h max duration)
coverage --test_size_filters=-enormous
coverage --test_timeout=60,240,240,240
coverage --test_timeout=240,240,240,240
coverage --build_tests_only
coverage --config=coverage
coverage --combined_report=lcov

# This config is defined internally and enabled on many machines.
# Defining it as empty just so these machines can run build commands from the workerd repo
Expand Down
2 changes: 2 additions & 0 deletions .github/actions/setup-runner/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ runs:
libclang-rt-19-dev \
llvm-19
sudo ln -s /usr/bin/llvm-symbolizer-19 /usr/bin/llvm-symbolizer
sudo ln -s /usr/bin/llvm-profdata-19 /usr/bin/llvm-profdata
sudo ln -s /usr/bin/llvm-cov-19 /usr/bin/llvm-cov
echo "build:linux --action_env=CC=/usr/lib/llvm-19/bin/clang" >> .bazelrc
echo "build:linux --host_action_env=CC=/usr/lib/llvm-19/bin/clang" >> .bazelrc
echo "build:linux --linkopt=--ld-path=/usr/lib/llvm-19/bin/ld.lld" >> .bazelrc
Expand Down
22 changes: 12 additions & 10 deletions .github/workflows/_bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,15 @@ jobs:
run: |
bazel --nowindows_enable_symlinks build ${{ inputs.extra_bazel_args }} --config=ci --profile build-win-workaround.bazel-profile.gz --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev //src/wpt:wpt-all@tsproject //src/node:node@tsproject //src/pyodide:pyodide_static@tsproject
- name: Bazel build
# timestamps are no longer being added here, the GitHub logs include timestamps (Use
# 'Show timestamps' on the web interface)
# Skip build step for coverage runs - coverage needs instrumented binaries built separately.
if: ${{ !inputs.run_coverage }}
run: |
bazel build --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} //...
- name: Bazel build (coverage)
# Build with coverage instrumentation first, so we can see build vs test time separately.
if: ${{ inputs.run_coverage }}
run: |
bazel build --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci --config=coverage ${{ inputs.extra_bazel_args }} //...
- name: Build and load container images
if: inputs.build_container_images
run: |
Expand All @@ -142,23 +147,20 @@ jobs:
if: inputs.run_tests
run: |
bazel test --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} ${{ inputs.test_target }}
- name: Setup LLVM tools for coverage
if: inputs.run_coverage
run: |
sudo apt-get update && sudo apt-get install -y llvm
sudo ln -sf /usr/bin/llvm-profdata /usr/local/bin/llvm-profdata
sudo ln -sf /usr/bin/llvm-cov /usr/local/bin/llvm-cov
- name: Bazel coverage
if: inputs.run_coverage
run: |
bazel coverage --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci --config=coverage ${{ inputs.extra_bazel_args }} ${{ inputs.test_target }}
bazel coverage --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} ${{ inputs.test_target }}
- name: Upload coverage to Codecov
if: inputs.run_coverage
uses: codecov/codecov-action@v5
with:
files: ./bazel-out/_coverage/_coverage_report.dat
# Use bazel info to get the actual output path, avoiding symlink issues
files: ${{ github.workspace }}/bazel-out/_coverage/_coverage_report.dat
fail_ci_if_error: true
verbose: true
disable_search: true
root_dir: ${{ github.workspace }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Parse headers
Expand Down
16 changes: 16 additions & 0 deletions build/cc_coverage/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")

# Custom collect_cc_coverage.sh that fixes COVERAGE_DIR permissions before running.
# This works around a Bazel issue where COVERAGE_DIR is made read-only before
# coverage post-processing runs with --experimental_split_coverage_postprocessing.
filegroup(
name = "collect_cc_coverage",
srcs = ["collect_cc_coverage.sh"],
visibility = ["//visibility:public"],
)

sh_binary(
name = "collect_cc_coverage_bin",
srcs = ["collect_cc_coverage.sh"],
visibility = ["//visibility:public"],
)
194 changes: 194 additions & 0 deletions build/cc_coverage/collect_cc_coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#!/bin/bash
# Custom collect_cc_coverage.sh that fixes COVERAGE_DIR permissions before running.
# This works around a Bazel issue where COVERAGE_DIR is made read-only before
# coverage post-processing runs with --experimental_split_coverage_postprocessing.

# Fix COVERAGE_DIR permissions if it exists
if [ -n "$COVERAGE_DIR" ] && [ -d "$COVERAGE_DIR" ]; then
chmod -R u+w "$COVERAGE_DIR" 2>/dev/null || true
fi

# Now source and run the original collect_cc_coverage.sh from Bazel
# We inline the original script here since we can't easily source external Bazel files

if [[ -n "$VERBOSE_COVERAGE" ]]; then
set -x
fi

# 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
object_param+=" -object ${line_runtime_object}"
done < "${line}"
fi
done < "${COVERAGE_MANIFEST}"

"${LLVM_COV}" export -instr-profile "${output_file}.data" -format=lcov \
-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

# 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 --version outputs a line like:
# gcov (Debian 7.3.0-5) 7.3.0
# llvm-cov gcov --version outputs a line like:
# LLVM version 9.0.1
gcov_major_version=$("${GCOV}" --version | sed -n -E -e 's/^.*\s([0-9]+)\.[0-9]+\.[0-9]+\s?.*$/\1/p')

# 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 <source file name>.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).

# Don't generate branch coverage (-b) when using gcov 7 or earlier
# 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).
if [[ $gcov_major_version -le 7 ]]; then
"${GCOV}" -i $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}"
else
"${GCOV}" -i -b $COVERAGE_GCOV_OPTIONS -o "$(dirname ${gcda})" "${gcda}"
fi

# Check the type of output: gcov 9 or later outputs compressed JSON
# files, but earlier versions of gcov, and all versions of llvm-cov,
# do not. These output textual information.
if stat --printf='' *.gcov.json.gz > /dev/null 2>&1; then
# Concatenating JSON documents does not yield a valid document, so they are moved individually
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"
# 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
6 changes: 5 additions & 1 deletion build/deps/build_deps.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@
},
{
"name": "aspect_rules_js",
"type": "bazel_dep"
"type": "github_tarball",
"owner": "anonrig",
"repo": "rules_js",
"branch": "yagiz/fix-postprocessing",
"freeze_commit": "93f17a3810649c95f658d85ff277fe963d1a20f8"
},
{
"name": "aspect_rules_ts",
Expand Down
6 changes: 6 additions & 0 deletions build/deps/gen/build_deps.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0")

# aspect_rules_js
bazel_dep(name = "aspect_rules_js", version = "2.8.3")
archive_override(
module_name = "aspect_rules_js",
integrity = "sha256-+3akxsGz4S5NKjTEPS/AafjsL+xU3GsOq2Nd3uVReps=",
strip_prefix = "rules_js-93f17a3810649c95f658d85ff277fe963d1a20f8",
urls = ["https://github.com/anonrig/rules_js/archive/93f17a3810649c95f658d85ff277fe963d1a20f8.tar.gz"],
)

# aspect_rules_lint
bazel_dep(name = "aspect_rules_lint", version = "1.13.0")
Expand Down
1 change: 0 additions & 1 deletion build/fixtures/BUILD.bazel

This file was deleted.

3 changes: 0 additions & 3 deletions build/fixtures/kj_test.sh

This file was deleted.

Loading
Loading