You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by hg...@apache.org on 2026/10/05 13:45:43 UTC

(iggy) branch master updated: feat(cpp): add code coverage (#4118)

This is an automated email from the ASF dual-hosted git repository.

hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new a2895fdc6 feat(cpp): add code coverage (#4118)
a2895fdc6 is described below

commit a2895fdc6ec01a1570a7aecaf82b0c950fe843d5
Author: Rimuksh Kansal <ni...@gmail.com>
AuthorDate: Mon Oct 5 22:45:38 2026 +0900

    feat(cpp): add code coverage (#4118)
---
 .github/actions/cpp-bazel/pre-merge/action.yml | 16 ++++-
 .github/workflows/_test.yml                    | 12 ++++
 .github/workflows/coverage-baseline.yml        | 28 ++++++++-
 .github/workflows/pre-merge.yml                |  2 +
 codecov.yml                                    |  7 +++
 foreign/cpp/BUILD.bazel                        | 50 ++++++++++++++-
 foreign/cpp/MODULE.bazel                       | 17 ++++++
 foreign/cpp/MODULE.bazel.lock                  |  2 +-
 scripts/ci/coverage-baseline-affected.sh       |  3 +-
 scripts/ci/cpp-coverage.sh                     | 84 ++++++++++++++++++++++++++
 scripts/ci/sync-rustc-version.sh               | 21 +++++++
 11 files changed, 233 insertions(+), 9 deletions(-)

diff --git a/.github/actions/cpp-bazel/pre-merge/action.yml b/.github/actions/cpp-bazel/pre-merge/action.yml
index 0cd50e62f..b21f3eb2e 100644
--- a/.github/actions/cpp-bazel/pre-merge/action.yml
+++ b/.github/actions/cpp-bazel/pre-merge/action.yml
@@ -63,7 +63,7 @@ runs:
       shell: bash
       run: |
         cd foreign/cpp
-        cargo clippy --manifest-path Cargo.toml --all-targets --all-features -- -D warnings
+        cargo clippy --all-targets --all-features -- -D warnings
 
     - name: Build
       if: inputs.task == 'build'
@@ -72,22 +72,32 @@ runs:
         cd foreign/cpp
         bazel build --config=ci //:iggy-cpp
 
+    - name: Install cargo-llvm-cov
+      if: inputs.task == 'test'
+      uses: taiki-e/install-action@v2.87.18
+      with:
+        tool: cargo-llvm-cov
+
     - name: Setup server for tests
+      id: iggy
       if: inputs.task == 'test'
       uses: ./.github/actions/utils/server-start
       with:
         cargo-bin: iggy-server
 
-    - name: Test
+    - name: Test with coverage
       if: inputs.task == 'test'
       shell: bash
       run: |
         cd foreign/cpp
-        bazel test --config=ci //...
+        ../../scripts/ci/cpp-coverage.sh
 
     - name: Stop server after tests
       if: always() && inputs.task == 'test'
       uses: ./.github/actions/utils/server-stop
+      with:
+        pid-file: ${{ steps.iggy.outputs.pid_file }}
+        log-file: ${{ steps.iggy.outputs.log_file }}
 
     - name: Validate task
       if: inputs.task != 'lint' && inputs.task != 'build' && inputs.task != 'test'
diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml
index 1c7d0c4bb..0b2ecf77e 100644
--- a/.github/workflows/_test.yml
+++ b/.github/workflows/_test.yml
@@ -241,6 +241,18 @@ jobs:
         with:
           task: ${{ inputs.task }}
 
+      - name: Upload C++ coverage to Codecov
+        if: inputs.component == 'sdk-cpp' && inputs.task == 'test'
+        uses: codecov/codecov-action@v7.0.0
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: reports/cpp-coverage.lcov
+          disable_search: true
+          flags: cpp
+          fail_ci_if_error: false
+          verbose: true
+          override_pr: ${{ github.event.pull_request.number }}
+
       - name: Upload Java coverage to Codecov
         if: inputs.component == 'sdk-java' && inputs.task == 'test'
         uses: codecov/codecov-action@v7.1.1
diff --git a/.github/workflows/coverage-baseline.yml b/.github/workflows/coverage-baseline.yml
index d57606337..a07b71acb 100644
--- a/.github/workflows/coverage-baseline.yml
+++ b/.github/workflows/coverage-baseline.yml
@@ -15,8 +15,8 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Coverage baseline for all 7 languages (Rust, Java, C#, Python, PHP,
-# Node, Go). Runs on push to master so Codecov has data on master SHAs for
+# Coverage baseline for all 8 languages (Rust, Java, C#, Python, PHP,
+# Node, Go, C++). Runs on push to master so Codecov has data on master SHAs for
 # carryforward on PR builds where only a subset of SDKs is tested.
 #
 # Jobs are path-gated: a language job runs only when the paths feeding its
@@ -61,6 +61,7 @@ jobs:
       php: ${{ steps.affected.outputs.php }}
       node: ${{ steps.affected.outputs.node }}
       go: ${{ steps.affected.outputs.go }}
+      cpp: ${{ steps.affected.outputs.cpp }}
     steps:
       - uses: actions/checkout@v7.0.1
         with:
@@ -581,6 +582,29 @@ jobs:
           flags: go
           fail_ci_if_error: false
 
+  cpp-coverage:
+    name: C++ coverage baseline
+    needs: detect
+    if: needs.detect.outputs.cpp == 'true'
+    runs-on: ubuntu-latest
+    timeout-minutes: 45
+    steps:
+      - uses: actions/checkout@v7.0.1
+
+      - name: Run C++ SDK tests with coverage
+        uses: ./.github/actions/cpp-bazel/pre-merge
+        with:
+          task: test
+
+      - name: Upload to Codecov
+        uses: codecov/codecov-action@v7.0.0
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: reports/cpp-coverage.lcov
+          disable_search: true
+          flags: cpp
+          fail_ci_if_error: false
+
   warm-cache-macos:
     name: Warm macOS cache
     needs: detect
diff --git a/.github/workflows/pre-merge.yml b/.github/workflows/pre-merge.yml
index 4fa8c7f98..cf35325a0 100644
--- a/.github/workflows/pre-merge.yml
+++ b/.github/workflows/pre-merge.yml
@@ -167,6 +167,8 @@ jobs:
     with:
       component: ${{ matrix.component }}
       task: ${{ matrix.task }}
+    secrets:
+      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
 
   # Swift SDK
   test-swift:
diff --git a/codecov.yml b/codecov.yml
index cc1533be8..2d927b241 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -76,6 +76,9 @@ flag_management:
     - name: go
       paths:
         - foreign/go/
+    - name: cpp
+      paths:
+        - foreign/cpp/
 
 component_management:
   individual_components:
@@ -107,6 +110,10 @@ component_management:
       name: "Go SDK"
       paths:
         - foreign/go/
+    - component_id: cpp
+      name: "C++ SDK"
+      paths:
+        - foreign/cpp/
 
 comment:
   layout: "header, diff, components, files"
diff --git a/foreign/cpp/BUILD.bazel b/foreign/cpp/BUILD.bazel
index 1d3d2e8d5..c5d655ba1 100644
--- a/foreign/cpp/BUILD.bazel
+++ b/foreign/cpp/BUILD.bazel
@@ -17,11 +17,17 @@
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_import", "cc_library", "cc_test")
 
+config_setting(
+    name = "coverage_enabled",
+    values = {"define": "iggy_cpp_coverage=1"},
+)
+
 genrule(
     name = "cargo_build",
     srcs = glob(["src/**/*.rs"]) + [
         "Cargo.toml",
         "build.rs",
+        "@iggy_source//:cpp_rust_dependencies",
     ],
     outs = [
         "libiggy_cpp.a",
@@ -55,7 +61,7 @@ genrule(
         env -u PWD \
             CARGO_TARGET_DIR="$$CARGO_TARGET_DIR" \
             RUSTC="$$RUSTC" \
-            "$$CARGO" build $$FLAGS
+            "$$CARGO" build --quiet $$FLAGS
 
         cp "$$CARGO_TARGET_DIR/$$PROFILE/libiggy_cpp.a" "$$OUT_LIB"
 
@@ -88,11 +94,48 @@ genrule(
     ],
 )
 
+genrule(
+    name = "extract_profiler_runtime",
+    outs = ["libprofiler_builtins.a"],
+    cmd = """
+        set -euo pipefail
+
+        EXECROOT="$$(pwd)"
+        RUSTC="$$EXECROOT/$(execpath @rs_host_tools//:rustc)"
+        PROFILER_RT_OUT="$$EXECROOT/$(location libprofiler_builtins.a)"
+
+        RT_SYSROOT="$$("$$RUSTC" --print sysroot)"
+        RT_HOST="$$("$$RUSTC" -vV | sed -n 's/^host: //p')"
+        RT_RLIB="$$(find "$$RT_SYSROOT/lib/rustlib/$$RT_HOST/lib" -maxdepth 1 -name 'libprofiler_builtins-*.rlib' -print -quit)"
+        if [ -z "$$RT_RLIB" ]; then
+            echo "ERROR: Failed to locate libprofiler_builtins rlib under $$RT_SYSROOT" >&2
+            exit 1
+        fi
+        cp "$$RT_RLIB" "$$PROFILER_RT_OUT"
+        for member in $$(ar t "$$PROFILER_RT_OUT"); do
+            case "$$member" in
+                *.o) ;;
+                *) ar d "$$PROFILER_RT_OUT" "$$member" ;;
+            esac
+        done
+    """,
+    tools = [
+        "@rs_host_tools//:rust_toolchain",
+        "@rs_host_tools//:rustc",
+    ],
+)
+
 cc_import(
     name = "iggy_cpp_static",
     static_library = ":libiggy_cpp.a",
 )
 
+cc_import(
+    name = "profiler_rt",
+    static_library = ":libprofiler_builtins.a",
+    alwayslink = True,
+)
+
 cc_library(
     name = "iggy-cpp",
     srcs = glob(
@@ -140,7 +183,10 @@ cc_library(
     deps = [
         ":iggy_cpp_static",
         "@abseil-cpp//absl/numeric:int128",
-    ],
+    ] + select({
+        ":coverage_enabled": [":profiler_rt"],
+        "//conditions:default": [],
+    }),
 )
 
 cc_binary(
diff --git a/foreign/cpp/MODULE.bazel b/foreign/cpp/MODULE.bazel
index 0badaf0c6..5b05e43f4 100644
--- a/foreign/cpp/MODULE.bazel
+++ b/foreign/cpp/MODULE.bazel
@@ -27,6 +27,23 @@ bazel_dep(name = "cucumber-cpp", version = "0.8.0.bcr.1")
 bazel_dep(name = "rules_rust", version = "0.74.0")
 bazel_dep(name = "abseil-cpp", version = "20260526.0")
 
+iggy_source = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository")
+iggy_source(
+    name = "iggy_source",
+    build_file_content = """
+filegroup(
+    name = "cpp_rust_dependencies",
+    srcs = glob([
+        "core/binary_protocol/**",
+        "core/common/**",
+        "core/sdk/**",
+    ]) + ["Cargo.toml"],
+    visibility = ["//visibility:public"],
+)
+""",
+    path = "../..",
+)
+
 rust_host_tools = use_extension("@rules_rust//rust:extensions.bzl", "rust_host_tools")
 rust_host_tools.host_tools(
     name = "rs_host_tools",
diff --git a/foreign/cpp/MODULE.bazel.lock b/foreign/cpp/MODULE.bazel.lock
index 840faa16d..7743e6ffc 100644
--- a/foreign/cpp/MODULE.bazel.lock
+++ b/foreign/cpp/MODULE.bazel.lock
@@ -489,7 +489,7 @@
     },
     "@@rules_rust+//crate_universe/private:internal_extensions.bzl%cu_nr": {
       "general": {
-        "bzlTransitiveDigest": "qQd7ktlamEwHDmeHLMSsEyM7+9Y2dOsIhs3Y/RXmCDY=",
+        "bzlTransitiveDigest": "0Uuil/+kAYdA1LXnr4MYuGZOREPtv/5CjQwREuGjTm4=",
         "usagesDigest": "McimNXAztVHDzspa1Kng94T+YeyY/44zx9FkNqIMn2s=",
         "recordedInputs": [
           "REPO_MAPPING:apple_support+,bazel_skylib bazel_skylib+",
diff --git a/scripts/ci/coverage-baseline-affected.sh b/scripts/ci/coverage-baseline-affected.sh
index ef73d043d..aa1187d8f 100755
--- a/scripts/ci/coverage-baseline-affected.sh
+++ b/scripts/ci/coverage-baseline-affected.sh
@@ -33,7 +33,7 @@ set -euo pipefail
 BASE="${1:-}"
 HEAD="${2:-}"
 ZERO="0000000000000000000000000000000000000000"
-GATES=(rust java csharp python php node go)
+GATES=(rust java csharp python php node go cpp)
 
 emit_all() {
   local gate
@@ -83,6 +83,7 @@ for gate in "${GATES[@]}"; do
     node) paths=(foreign/node) ;;
     # The go job also runs the bdd/go suite with foreign/go in -coverpkg.
     go) paths=(foreign/go bdd/go) ;;
+    cpp) paths=(foreign/cpp) ;;
     *)
       echo "coverage-gate: no pathspecs defined for gate '$gate'" >&2
       exit 1
diff --git a/scripts/ci/cpp-coverage.sh b/scripts/ci/cpp-coverage.sh
new file mode 100755
index 000000000..bb5525c12
--- /dev/null
+++ b/scripts/ci/cpp-coverage.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+CPP_ROOT="$REPO_ROOT/foreign/cpp"
+
+cd "$CPP_ROOT"
+
+OUTPUT="$REPO_ROOT/reports/cpp-coverage.lcov"
+COVERAGE_DIR="$CPP_ROOT/target/cpp-coverage"
+# Cargo build scripts also inherit coverage instrumentation. Keep their profiles
+# separate because llvm-cov exports only the unit and e2e test objects below.
+BUILD_PROFRAW_DIR="$COVERAGE_DIR/build"
+PROFDATA="$COVERAGE_DIR/shim.profdata"
+CPP_RAW="$CPP_ROOT/bazel-out/_coverage/_coverage_report.dat"
+
+mkdir -p "$COVERAGE_DIR" "$BUILD_PROFRAW_DIR" "$(dirname "$OUTPUT")"
+rm -f "$COVERAGE_DIR"/*.profraw "$BUILD_PROFRAW_DIR"/*.profraw "$PROFDATA" "$OUTPUT"
+
+if ! LLVM_COV_ENV="$(cargo llvm-cov show-env --no-rustc-wrapper --sh)"; then
+  echo "cpp-coverage: 'cargo llvm-cov show-env' failed" >&2
+  exit 1
+fi
+eval "$LLVM_COV_ENV"
+export LLVM_PROFILE_FILE="$COVERAGE_DIR/%p-%m.profraw"
+
+LLVM_BIN="$(rustc --print target-libdir)/../bin"
+LLVM_COV="$LLVM_BIN/llvm-cov"
+LLVM_PROFDATA="$LLVM_BIN/llvm-profdata"
+
+# --run_under overrides Bazel's profile path so Rust profiles do not replace GCC LCOV data.
+bazel coverage \
+  --config=debug \
+  --lockfile_mode=error \
+  --combined_report=lcov \
+  '--instrumentation_filter=//:iggy-cpp' \
+  --strategy=TestRunner=standalone \
+  --define=iggy_cpp_coverage=1 \
+  --action_env=RUSTFLAGS \
+  "--action_env=LLVM_PROFILE_FILE=$BUILD_PROFRAW_DIR/%p-%m.profraw" \
+  "--run_under=/usr/bin/env LLVM_PROFILE_FILE=$LLVM_PROFILE_FILE" \
+  //:unit //:e2e
+
+"$LLVM_PROFDATA" merge -sparse \
+  "$COVERAGE_DIR"/*.profraw \
+  -o "$PROFDATA"
+
+sed 's|^SF:|SF:foreign/cpp/|' "$CPP_RAW" >"$OUTPUT"
+
+"$LLVM_COV" export \
+  --object bazel-bin/unit \
+  --object bazel-bin/e2e \
+  -instr-profile="$PROFDATA" \
+  -format=lcov \
+  -ignore-filename-regex='(\.cargo/|/rustc/|/core/|cxxbridge|/registry/)' |
+  awk '
+    /^SF:/ {
+      include = /foreign\/cpp\/src\/[^/]+\.rs$/
+      if (include) {
+        sub(/^SF:.*foreign\/cpp\//, "SF:foreign/cpp/")
+      }
+    }
+    include
+  ' >>"$OUTPUT"
+
+"$SCRIPT_DIR/validate-lcov.sh" "$OUTPUT"
diff --git a/scripts/ci/sync-rustc-version.sh b/scripts/ci/sync-rustc-version.sh
index 8cf0709a4..fc7bca5f3 100755
--- a/scripts/ci/sync-rustc-version.sh
+++ b/scripts/ci/sync-rustc-version.sh
@@ -235,6 +235,27 @@ check_devcontainer_json() {
 
 check_devcontainer_json
 
+# Bazel's Rust shim profiles must match the LLVM tools from rust-toolchain.toml.
+CPP_BAZEL_MODULE="foreign/cpp/MODULE.bazel"
+BAZEL_RUST_VERSION=$(sed -n '/rust_host_tools\.host_tools(/,/^)/ s/^[[:space:]]*version = "\([^"]*\)",/\1/p' "$CPP_BAZEL_MODULE")
+if [ -z "$BAZEL_RUST_VERSION" ]; then
+    echo -e "${RED}Error: Could not extract host_tools version from $CPP_BAZEL_MODULE${NC}"
+    exit 1
+fi
+
+TOTAL_FILES=$((TOTAL_FILES + 1))
+if [ "$BAZEL_RUST_VERSION" = "$RUST_VERSION" ]; then
+    echo -e "${GREEN}✓${NC} $CPP_BAZEL_MODULE: host_tools version ${GREEN}$RUST_VERSION${NC}"
+elif [ "$MODE" = "check" ]; then
+    MISALIGNED_FILES+=("$CPP_BAZEL_MODULE")
+    echo -e "${RED}✗${NC} $CPP_BAZEL_MODULE: host_tools version ${RED}$BAZEL_RUST_VERSION${NC} (expected: ${GREEN}$RUST_VERSION${NC})"
+else
+    sed -i.bak -E "/rust_host_tools\.host_tools\(/,/^\)/ s/(version = \")[^\"]+(\",)/\1$RUST_VERSION\2/" "$CPP_BAZEL_MODULE"
+    rm -f "$CPP_BAZEL_MODULE.bak"
+    FIXED_FILES=$((FIXED_FILES + 1))
+    echo -e "${GREEN}Fixed${NC} $CPP_BAZEL_MODULE: host_tools version ${RED}$BAZEL_RUST_VERSION${NC} -> ${GREEN}$RUST_VERSION${NC}"
+fi
+
 echo ""
 echo "────────────────────────────────────────────────"