From 54e9a320c7670ab44934eb5a847abecd0fb832a1 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Tue, 2 Jun 2026 16:28:33 +0300 Subject: [PATCH 01/10] introduce code coverage to the cpp sdk --- .github/actions/gcovr/action.yaml | 58 +++++++++++++++++++++++ .github/actions/gcovr/run_gcovr.sh | 49 +++++++++++++++++++ .github/workflows/coverage.yml | 75 ++++++++++++++++++++++++++++++ CMakeLists.txt | 1 + CMakePresets.json | 15 ++++++ cmake/ccache.cmake | 4 +- cmake/common.cmake | 2 +- cmake/coverage.cmake | 19 ++++++++ cmake/testing.cmake | 1 + 9 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 .github/actions/gcovr/action.yaml create mode 100755 .github/actions/gcovr/run_gcovr.sh create mode 100644 .github/workflows/coverage.yml create mode 100644 cmake/coverage.cmake diff --git a/.github/actions/gcovr/action.yaml b/.github/actions/gcovr/action.yaml new file mode 100644 index 00000000000..b822bf9f1d8 --- /dev/null +++ b/.github/actions/gcovr/action.yaml @@ -0,0 +1,58 @@ +name: Gcovr +description: Generate code coverage reports with gcovr and llvm-cov + +inputs: + build-directory: + description: Build directory containing coverage data (.gcda / .gcno) + required: false + default: build + source-root: + description: Source root for gcovr --root + required: false + default: ${{ github.workspace }} + gcov-executable: + description: gcov executable passed to gcovr (e.g. llvm-cov-16 gcov for Clang) + required: false + default: llvm-cov-16 gcov + gcovr-version: + description: gcovr PyPI version to install + required: false + default: "8.4" + output-directory: + description: Directory for generated coverage reports + required: false + default: build/coverage + fail-under-line: + description: Fail if line coverage is below this percent (0 disables the gate) + required: false + default: "0" + +runs: + using: composite + steps: + - name: Install gcovr + shell: bash + run: | + python3 -m pip install --user "gcovr==${{ inputs.gcovr-version }}" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Verify llvm-cov + shell: bash + run: | + llvm_cov="${{ inputs.gcov-executable }}" + llvm_cov_bin="${llvm_cov%% *}" + if ! command -v "${llvm_cov_bin}" >/dev/null 2>&1; then + echo "::error::${llvm_cov_bin} not found; install LLVM or adjust gcov-executable" + exit 1 + fi + + - name: Generate coverage report + shell: bash + run: | + chmod +x "${{ github.action_path }}/run_gcovr.sh" + SOURCE_ROOT="${{ inputs.source-root }}" \ + BUILD_DIR="${{ github.workspace }}/${{ inputs.build-directory }}" \ + OUTPUT_DIR="${{ github.workspace }}/${{ inputs.output-directory }}" \ + GCOV_EXECUTABLE="${{ inputs.gcov-executable }}" \ + FAIL_UNDER_LINE="${{ inputs.fail-under-line }}" \ + "${{ github.action_path }}/run_gcovr.sh" diff --git a/.github/actions/gcovr/run_gcovr.sh b/.github/actions/gcovr/run_gcovr.sh new file mode 100755 index 00000000000..94d3c418f43 --- /dev/null +++ b/.github/actions/gcovr/run_gcovr.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${SOURCE_ROOT:?SOURCE_ROOT is required}" +: "${BUILD_DIR:?BUILD_DIR is required}" +: "${OUTPUT_DIR:?OUTPUT_DIR is required}" +: "${GCOV_EXECUTABLE:=llvm-cov-16 gcov}" + +mkdir -p "${OUTPUT_DIR}" + +gcovr_args=( + --root "${SOURCE_ROOT}" + --object-directory "${BUILD_DIR}" + --gcov-executable "${GCOV_EXECUTABLE}" + --print-summary + --json-summary "${OUTPUT_DIR}/summary.json" + --html --html-details + -o "${OUTPUT_DIR}/index.html" + --cobertura "${OUTPUT_DIR}/cobertura.xml" + --exclude '.*contrib/.*' + --exclude '.*tests/.*' + --exclude '.*/_deps/.*' + --filter 'src/' + --filter 'include/ydb-cpp-sdk/' + --filter 'plugins/' +) + +if [[ -n "${FAIL_UNDER_LINE:-}" && "${FAIL_UNDER_LINE}" != "0" ]]; then + gcovr_args+=(--fail-under-line "${FAIL_UNDER_LINE}") +fi + +gcovr "${gcovr_args[@]}" + +if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "${OUTPUT_DIR}/summary.json" ]]; then + { + echo "## Code coverage" + echo "" + echo "| Metric | Covered | Total | Percent |" + echo "| --- | ---: | ---: | ---: |" + jq -r ' + .line as $l + | .branch as $b + | .function as $f + | "| Line | \($l.covered) | \($l.num) | \($l.percent // 0)% |", + "| Branch | \($b.covered) | \($b.num) | \($b.percent // 0)% |", + "| Function | \($f.covered) | \($f.num) | \($f.percent // 0)% |" + ' "${OUTPUT_DIR}/summary.json" + } >> "${GITHUB_STEP_SUMMARY}" +fi diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000000..3dd28ad5c61 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,75 @@ +name: Coverage + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - main + +concurrency: + group: coverage-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + coverage: + runs-on: ubuntu-22.04 + services: + ydb: + image: ydbplatform/local-ydb:24.4 + ports: + - 2135:2135 + - 2136:2136 + - 8765:8765 + volumes: + - /tmp/ydb_certs:/ydb_certs + env: + YDB_LOCAL_SURVIVE_RESTART: true + YDB_USE_IN_MEMORY_PDISKS: true + YDB_TABLE_ENABLE_PREPARED_DDL: true + options: '-h localhost' + steps: + - name: Checkout PR + uses: actions/checkout@v4 + if: github.event.pull_request.head.sha != '' + with: + submodules: true + ref: ${{ github.event.pull_request.head.sha }} + + - name: Checkout + uses: actions/checkout@v4 + if: github.event.pull_request.head.sha == '' + with: + submodules: true + + - name: Install dependencies + uses: ./.github/actions/prepare_vm + + - name: Configure and build with coverage + shell: bash + run: | + mkdir -p build + rm -rf build/* + cmake --preset coverage-test-clang + cmake --build build -j"$(nproc)" + + - name: Test + shell: bash + run: | + ctest -j2 --preset all --output-on-failure + + - name: Generate coverage report + uses: ./.github/actions/gcovr + with: + build-directory: build + source-root: ${{ github.workspace }} + gcov-executable: llvm-cov-16 gcov + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: build/coverage/ + retention-days: 14 diff --git a/CMakeLists.txt b/CMakeLists.txt index f4177996491..2cf1bc9e668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ include(CMakePackageConfigHelpers) list(APPEND CMAKE_MODULE_PATH ${YDB_SDK_SOURCE_DIR}/cmake) include(cmake/global_flags.cmake) +include(cmake/coverage.cmake) include(cmake/global_vars.cmake) include(cmake/install.cmake) include(cmake/common.cmake) diff --git a/CMakePresets.json b/CMakePresets.json index e0a4f0ad0d2..508c9b83e3b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -118,6 +118,21 @@ "name": "debug-test-gcc", "inherits": ["debug-base", "gcc-toolchain", "test"], "displayName": "Default Debug Test Config (GCC)" + }, + { + "name": "coverage-base", + "inherits": ["debug-base", "test"], + "hidden": true, + "cacheVariables": { + "YDB_SDK_COVERAGE": "ON", + "ARCADIA_ROOT": "${sourceDir}", + "ARCADIA_BUILD_ROOT": "." + } + }, + { + "name": "coverage-test-clang", + "inherits": ["coverage-base", "clang-toolchain"], + "displayName": "Coverage Test Config (Clang)" } ], "buildPresets": [ diff --git a/cmake/ccache.cmake b/cmake/ccache.cmake index ef68402c465..311a9b8f128 100644 --- a/cmake/ccache.cmake +++ b/cmake/ccache.cmake @@ -1,5 +1,7 @@ find_program(CCACHE_PATH ccache) -if (NOT CCACHE_PATH) +if (YDB_SDK_COVERAGE) + message(STATUS "YDB_SDK_COVERAGE is ON: ccache compiler launchers disabled") +elseif (NOT CCACHE_PATH) message(AUTHOR_WARNING "Ccache is not found, that will increase the re-compilation time; " "pass -DCCACHE_PATH=path/to/bin to specify the path to the `ccache` binary file." diff --git a/cmake/common.cmake b/cmake/common.cmake index fff0c92fcdf..b40eee34324 100644 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -216,6 +216,7 @@ function(_ydb_sdk_add_library Tgt) target_compile_definitions(${Tgt} ${includeMode} YDB_SDK_OSS ) + _ydb_sdk_apply_coverage(${Tgt}) endfunction() @@ -462,4 +463,3 @@ function(_ydb_sdk_validate_public_headers) ) target_include_directories(validate_public_interface PUBLIC ${YDB_SDK_BINARY_DIR}/__validate_headers_dir/include) endfunction() - diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake new file mode 100644 index 00000000000..182b80d77ae --- /dev/null +++ b/cmake/coverage.cmake @@ -0,0 +1,19 @@ +option(YDB_SDK_COVERAGE "Instrument SDK and tests for gcov/llvm-cov coverage" OFF) + +function(_ydb_sdk_apply_coverage target) + if (NOT YDB_SDK_COVERAGE) + return() + endif() + get_target_property(is_iface ${target} TYPE) + if (is_iface STREQUAL "INTERFACE_LIBRARY") + return() + endif() + target_compile_options(${target} PRIVATE + --coverage + -O0 + -g + -fno-inline + -fprofile-abs-path + ) + target_link_options(${target} PRIVATE --coverage) +endfunction() diff --git a/cmake/testing.cmake b/cmake/testing.cmake index 641c855d8da..999e6a596d6 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -51,6 +51,7 @@ function(add_ydb_test) endif() add_executable(${YDB_TEST_NAME}) + _ydb_sdk_apply_coverage(${YDB_TEST_NAME}) target_include_directories(${YDB_TEST_NAME} PRIVATE ${YDB_TEST_INCLUDE_DIRS}) target_link_libraries(${YDB_TEST_NAME} PRIVATE ${YDB_TEST_LINK_LIBRARIES}) target_sources(${YDB_TEST_NAME} PRIVATE ${YDB_TEST_SOURCES}) From b87026e36a8a8f9e12afa4e51d0e9ad7e5b776c5 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Tue, 2 Jun 2026 17:24:18 +0300 Subject: [PATCH 02/10] fix --- cmake/coverage.cmake | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake index 182b80d77ae..33adc91e885 100644 --- a/cmake/coverage.cmake +++ b/cmake/coverage.cmake @@ -1,5 +1,9 @@ option(YDB_SDK_COVERAGE "Instrument SDK and tests for gcov/llvm-cov coverage" OFF) +if (YDB_SDK_COVERAGE) + add_link_options(--coverage) +endif() + function(_ydb_sdk_apply_coverage target) if (NOT YDB_SDK_COVERAGE) return() @@ -8,12 +12,10 @@ function(_ydb_sdk_apply_coverage target) if (is_iface STREQUAL "INTERFACE_LIBRARY") return() endif() - target_compile_options(${target} PRIVATE - --coverage - -O0 - -g - -fno-inline - -fprofile-abs-path - ) + set(_coverage_compile_flags --coverage -O0 -g -fno-inline) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND _coverage_compile_flags -fprofile-abs-path) + endif() + target_compile_options(${target} PRIVATE ${_coverage_compile_flags}) target_link_options(${target} PRIVATE --coverage) endfunction() From 15ccdcd8bf91fded48df4b1e34ad3f15f8ef1392 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Wed, 3 Jun 2026 15:04:38 +0300 Subject: [PATCH 03/10] fix timeout --- .github/workflows/coverage.yml | 2 +- CMakePresets.json | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3dd28ad5c61..1c1c48714f7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -58,7 +58,7 @@ jobs: - name: Test shell: bash run: | - ctest -j2 --preset all --output-on-failure + ctest -j1 --preset coverage-all --output-on-failure - name: Generate coverage report uses: ./.github/actions/gcovr diff --git a/CMakePresets.json b/CMakePresets.json index 508c9b83e3b..d41ee7ce3e0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -188,6 +188,24 @@ "YDB_ENDPOINT": "localhost:2136", "YDB_DATABASE": "/local" } + }, + { + "name": "coverage-all", + "inherits": "all", + "displayName": "Run All Tests (coverage CI)", + "execution": { + "timeout": 1800 + }, + "filter": { + "exclude": { + "name": "ManyMessages" + } + }, + "environment": { + "YDB_ENDPOINT": "localhost:2136", + "YDB_DATABASE": "/local", + "YDB_VERSION": "24.4" + } } ] } From 704d22724aa3bf1634c25bc9647719ef5c46217a Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Wed, 3 Jun 2026 16:00:00 +0300 Subject: [PATCH 04/10] add gh comment action --- .github/actions/gcovr/action.yaml | 21 +++++ .github/actions/gcovr/post_pr_comment.sh | 100 +++++++++++++++++++++++ .github/workflows/coverage.yml | 4 + 3 files changed, 125 insertions(+) create mode 100755 .github/actions/gcovr/post_pr_comment.sh diff --git a/.github/actions/gcovr/action.yaml b/.github/actions/gcovr/action.yaml index b822bf9f1d8..a128d35d2bc 100644 --- a/.github/actions/gcovr/action.yaml +++ b/.github/actions/gcovr/action.yaml @@ -26,6 +26,14 @@ inputs: description: Fail if line coverage is below this percent (0 disables the gate) required: false default: "0" + post-pr-comment: + description: Post or update a sticky coverage summary on the pull request + required: false + default: "true" + github-token: + description: Token for posting PR comments + required: false + default: ${{ github.token }} runs: using: composite @@ -56,3 +64,16 @@ runs: GCOV_EXECUTABLE="${{ inputs.gcov-executable }}" \ FAIL_UNDER_LINE="${{ inputs.fail-under-line }}" \ "${{ github.action_path }}/run_gcovr.sh" + + - name: Post coverage PR comment + if: inputs.post-pr-comment == 'true' + shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + OUTPUT_DIR: ${{ github.workspace }}/${{ inputs.output-directory }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_RUN_ID: ${{ github.run_id }} + run: | + chmod +x "${{ github.action_path }}/post_pr_comment.sh" + "${{ github.action_path }}/post_pr_comment.sh" diff --git a/.github/actions/gcovr/post_pr_comment.sh b/.github/actions/gcovr/post_pr_comment.sh new file mode 100755 index 00000000000..031a64632e5 --- /dev/null +++ b/.github/actions/gcovr/post_pr_comment.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${OUTPUT_DIR:?OUTPUT_DIR is required}" +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" +: "${GITHUB_RUN_ID:?GITHUB_RUN_ID is required}" + +MARKER='' +SUMMARY_JSON="${OUTPUT_DIR}/summary.json" + +if [[ -z "${PR_NUMBER:-}" ]]; then + echo "PR_NUMBER is not set; skipping coverage PR comment" + exit 0 +fi + +if [[ ! -f "${SUMMARY_JSON}" ]]; then + echo "::warning::${SUMMARY_JSON} not found; skipping coverage PR comment" + exit 0 +fi + +if ! command -v gh >/dev/null 2>&1; then + echo "::error::gh CLI is required to post PR comments" + exit 1 +fi + +BODY="$(MARKER="${MARKER}" SUMMARY_JSON="${SUMMARY_JSON}" GITHUB_REPOSITORY="${GITHUB_REPOSITORY}" GITHUB_RUN_ID="${GITHUB_RUN_ID}" python3 <<'PY' +import json +import os +from pathlib import Path + +marker = os.environ["MARKER"] +summary = json.loads(Path(os.environ["SUMMARY_JSON"]).read_text()) +repo = os.environ["GITHUB_REPOSITORY"] +run_id = os.environ["GITHUB_RUN_ID"] + +line = summary["line"] +branch = summary["branch"] +func = summary["function"] + +def bar(percent: float) -> str: + filled = max(0, min(10, int(round(percent / 10)))) + return "█" * filled + "░" * (10 - filled) + +files = [ + f for f in summary.get("files", []) + if f.get("line_total", 0) >= 20 +] +files.sort(key=lambda f: f.get("line_percent") or 0) +lowest = files[:8] + +run_url = f"https://github.com/{repo}/actions/runs/{run_id}" +lines = [ + marker, + "## Code coverage", + "", + f"Workflow run: [Coverage job #{run_id}]({run_url}) · download the **coverage-report** artifact for the HTML report.", + "", + "| Metric | Coverage | Covered / total |", + "| --- | --- | --- |", + f"| Line | **{line['percent']:.1f}%** {bar(line['percent'])} | {line['covered']} / {line['num']} |", + f"| Branch | **{branch['percent']:.1f}%** {bar(branch['percent'])} | {branch['covered']} / {branch['num']} |", + f"| Function | **{func['percent']:.1f}%** {bar(func['percent'])} | {func['covered']} / {func['num']} |", + "", + "Scope: `src/`, `include/ydb-cpp-sdk/`, `plugins/` (contrib, tests, and `_deps` excluded).", +] + +if lowest: + lines.extend([ + "", + "
", + "Lowest line coverage (≥ 20 lines)", + "", + "| File | Line % | Covered / total |", + "| --- | ---: | --- |", + ]) + for f in lowest: + pct = f.get("line_percent") or 0 + lines.append( + f"| `{f['filename']}` | {pct:.1f}% | {f['line_covered']} / {f['line_total']} |" + ) + lines.extend(["", "
"]) + +print("\n".join(lines)) +PY +)" + +COMMENT_ID="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ + --paginate \ + --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" \ + | head -n1)" + +if [[ -n "${COMMENT_ID}" ]]; then + gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" \ + -f body="${BODY}" >/dev/null + echo "Updated coverage comment ${COMMENT_ID} on PR #${PR_NUMBER}" +else + gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ + -f body="${BODY}" >/dev/null + echo "Posted coverage comment on PR #${PR_NUMBER}" +fi diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1c1c48714f7..c7bcea5640d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,6 +16,9 @@ concurrency: jobs: coverage: runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: write services: ydb: image: ydbplatform/local-ydb:24.4 @@ -66,6 +69,7 @@ jobs: build-directory: build source-root: ${{ github.workspace }} gcov-executable: llvm-cov-16 gcov + post-pr-comment: ${{ github.event_name == 'pull_request' }} - name: Upload coverage reports uses: actions/upload-artifact@v4 From c9d12d1721c451075a8b1e278bbebc80b677374c Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Wed, 3 Jun 2026 16:11:41 +0300 Subject: [PATCH 05/10] run the whole test suite --- CMakePresets.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index d41ee7ce3e0..a1bc9ad68de 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -195,16 +195,6 @@ "displayName": "Run All Tests (coverage CI)", "execution": { "timeout": 1800 - }, - "filter": { - "exclude": { - "name": "ManyMessages" - } - }, - "environment": { - "YDB_ENDPOINT": "localhost:2136", - "YDB_DATABASE": "/local", - "YDB_VERSION": "24.4" } } ] From 17281d8d2fe774d44d98e6015098fe62a2639d80 Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Wed, 3 Jun 2026 18:43:31 +0300 Subject: [PATCH 06/10] fix build issues --- .github/workflows/coverage.yml | 4 ++-- CMakePresets.json | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c7bcea5640d..24bbb2deed1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -55,7 +55,7 @@ jobs: run: | mkdir -p build rm -rf build/* - cmake --preset coverage-test-clang + cmake --preset coverage-test-gcc cmake --build build -j"$(nproc)" - name: Test @@ -68,7 +68,7 @@ jobs: with: build-directory: build source-root: ${{ github.workspace }} - gcov-executable: llvm-cov-16 gcov + gcov-executable: gcov-13 post-pr-comment: ${{ github.event_name == 'pull_request' }} - name: Upload coverage reports diff --git a/CMakePresets.json b/CMakePresets.json index a1bc9ad68de..750174e9159 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -121,18 +121,16 @@ }, { "name": "coverage-base", - "inherits": ["debug-base", "test"], + "inherits": ["release-base", "test"], "hidden": true, "cacheVariables": { - "YDB_SDK_COVERAGE": "ON", - "ARCADIA_ROOT": "${sourceDir}", - "ARCADIA_BUILD_ROOT": "." + "YDB_SDK_COVERAGE": "ON" } }, { - "name": "coverage-test-clang", - "inherits": ["coverage-base", "clang-toolchain"], - "displayName": "Coverage Test Config (Clang)" + "name": "coverage-test-gcc", + "inherits": ["coverage-base", "gcc-toolchain"], + "displayName": "Coverage Test Config (GCC)" } ], "buildPresets": [ @@ -193,8 +191,19 @@ "name": "coverage-all", "inherits": "all", "displayName": "Run All Tests (coverage CI)", + "configurePreset": "coverage-test-gcc", "execution": { "timeout": 1800 + }, + "filter": { + "exclude": { + "name": "(ManyMessages|DiscoveryHang|DescribeHang)" + } + }, + "environment": { + "YDB_ENDPOINT": "localhost:2136", + "YDB_DATABASE": "/local", + "YDB_VERSION": "24.4" } } ] From 30e1f06a2721e437507cc55d8edffd873ebe390f Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 4 Jun 2026 09:37:50 +0300 Subject: [PATCH 07/10] fix relpath abspath issue --- cmake/coverage.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake index 33adc91e885..d21180b7d3e 100644 --- a/cmake/coverage.cmake +++ b/cmake/coverage.cmake @@ -12,10 +12,10 @@ function(_ydb_sdk_apply_coverage target) if (is_iface STREQUAL "INTERFACE_LIBRARY") return() endif() + # Do not use -fprofile-abs-path: it expands __FILE__/__LOCATION__ to absolute paths + # and breaks util/system/src_location_ut and src_root_ut (and similar assertions). + # gcovr --root maps profile data to the source tree without it. set(_coverage_compile_flags --coverage -O0 -g -fno-inline) - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - list(APPEND _coverage_compile_flags -fprofile-abs-path) - endif() target_compile_options(${target} PRIVATE ${_coverage_compile_flags}) target_link_options(${target} PRIVATE --coverage) endfunction() From b24bbfbb85fed2f5d21d6c58554af450523fb15a Mon Sep 17 00:00:00 2001 From: Artem Ermoshkin Date: Thu, 4 Jun 2026 11:06:32 +0300 Subject: [PATCH 08/10] fix relpath 2.0 --- CMakePresets.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 750174e9159..2061ba6b78f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -124,7 +124,9 @@ "inherits": ["release-base", "test"], "hidden": true, "cacheVariables": { - "YDB_SDK_COVERAGE": "ON" + "YDB_SDK_COVERAGE": "ON", + "ARCADIA_ROOT": "${sourceDir}", + "ARCADIA_BUILD_ROOT": "${sourceDir}/build" } }, { From 816dde0d0edcdfd3cf345a15c8eae25d512550ff Mon Sep 17 00:00:00 2001 From: Shfdis Date: Fri, 19 Jun 2026 12:43:33 +0300 Subject: [PATCH 09/10] add proper codecov label to the repo --- .github/actions/gcovr/post_pr_comment.sh | 15 ++++++++++++--- .github/actions/gcovr/run_gcovr.sh | 16 +++++++++++++--- .github/workflows/coverage.yml | 12 ++++++++++++ README.md | 2 ++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/.github/actions/gcovr/post_pr_comment.sh b/.github/actions/gcovr/post_pr_comment.sh index 031a64632e5..7c5212fa51c 100755 --- a/.github/actions/gcovr/post_pr_comment.sh +++ b/.github/actions/gcovr/post_pr_comment.sh @@ -33,9 +33,18 @@ summary = json.loads(Path(os.environ["SUMMARY_JSON"]).read_text()) repo = os.environ["GITHUB_REPOSITORY"] run_id = os.environ["GITHUB_RUN_ID"] -line = summary["line"] -branch = summary["branch"] -func = summary["function"] +def metric(name: str) -> dict: + if name in summary: + return summary[name] + return { + "covered": summary[f"{name}_covered"], + "num": summary[f"{name}_total"], + "percent": summary.get(f"{name}_percent") or 0, + } + +line = metric("line") +branch = metric("branch") +func = metric("function") def bar(percent: float) -> str: filled = max(0, min(10, int(round(percent / 10)))) diff --git a/.github/actions/gcovr/run_gcovr.sh b/.github/actions/gcovr/run_gcovr.sh index 94d3c418f43..18bc940cb4c 100755 --- a/.github/actions/gcovr/run_gcovr.sh +++ b/.github/actions/gcovr/run_gcovr.sh @@ -38,9 +38,19 @@ if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "${OUTPUT_DIR}/summary.json" ]]; then echo "| Metric | Covered | Total | Percent |" echo "| --- | ---: | ---: | ---: |" jq -r ' - .line as $l - | .branch as $b - | .function as $f + def metric($name): + if has($name) then + .[$name] + else + { + covered: .[$name + "_covered"], + num: .[$name + "_total"], + percent: .[$name + "_percent"] + } + end; + metric("line") as $l + | metric("branch") as $b + | metric("function") as $f | "| Line | \($l.covered) | \($l.num) | \($l.percent // 0)% |", "| Branch | \($b.covered) | \($b.num) | \($b.percent // 0)% |", "| Function | \($f.covered) | \($f.num) | \($f.percent // 0)% |" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 24bbb2deed1..20ad15c24c0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,6 +18,7 @@ jobs: runs-on: ubuntu-22.04 permissions: contents: read + id-token: write pull-requests: write services: ydb: @@ -71,6 +72,17 @@ jobs: gcov-executable: gcov-13 post-pr-comment: ${{ github.event_name == 'pull_request' }} + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + use_oidc: true + files: build/coverage/cobertura.xml + disable_search: true + flags: sdk + name: ydb-cpp-sdk + fail_ci_if_error: true + verbose: true + - name: Upload coverage reports uses: actions/upload-artifact@v4 with: diff --git a/README.md b/README.md index a1d15c65aba..affad1c8443 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # YDB C++ SDK: driver for [YDB](https://github.com/ydb-platform/ydb) +[![Codecov](https://codecov.io/gh/ydb-platform/ydb-cpp-sdk/branch/main/graph/badge.svg)](https://app.codecov.io/gh/ydb-platform/ydb-cpp-sdk) + ## Building YDB C++ SDK from sources ### Prerequisites From 24ec5b3199170bff830db5edaf8973d089ae509d Mon Sep 17 00:00:00 2001 From: Shfdis Date: Fri, 19 Jun 2026 12:48:28 +0300 Subject: [PATCH 10/10] simplify the action --- .github/actions/gcovr/action.yaml | 21 ----- .github/actions/gcovr/post_pr_comment.sh | 109 ----------------------- .github/actions/gcovr/run_gcovr.sh | 27 ------ .github/workflows/coverage.yml | 2 - 4 files changed, 159 deletions(-) delete mode 100755 .github/actions/gcovr/post_pr_comment.sh diff --git a/.github/actions/gcovr/action.yaml b/.github/actions/gcovr/action.yaml index a128d35d2bc..b822bf9f1d8 100644 --- a/.github/actions/gcovr/action.yaml +++ b/.github/actions/gcovr/action.yaml @@ -26,14 +26,6 @@ inputs: description: Fail if line coverage is below this percent (0 disables the gate) required: false default: "0" - post-pr-comment: - description: Post or update a sticky coverage summary on the pull request - required: false - default: "true" - github-token: - description: Token for posting PR comments - required: false - default: ${{ github.token }} runs: using: composite @@ -64,16 +56,3 @@ runs: GCOV_EXECUTABLE="${{ inputs.gcov-executable }}" \ FAIL_UNDER_LINE="${{ inputs.fail-under-line }}" \ "${{ github.action_path }}/run_gcovr.sh" - - - name: Post coverage PR comment - if: inputs.post-pr-comment == 'true' - shell: bash - env: - GH_TOKEN: ${{ inputs.github-token }} - PR_NUMBER: ${{ github.event.pull_request.number }} - OUTPUT_DIR: ${{ github.workspace }}/${{ inputs.output-directory }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_RUN_ID: ${{ github.run_id }} - run: | - chmod +x "${{ github.action_path }}/post_pr_comment.sh" - "${{ github.action_path }}/post_pr_comment.sh" diff --git a/.github/actions/gcovr/post_pr_comment.sh b/.github/actions/gcovr/post_pr_comment.sh deleted file mode 100755 index 7c5212fa51c..00000000000 --- a/.github/actions/gcovr/post_pr_comment.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -: "${OUTPUT_DIR:?OUTPUT_DIR is required}" -: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" -: "${GITHUB_RUN_ID:?GITHUB_RUN_ID is required}" - -MARKER='' -SUMMARY_JSON="${OUTPUT_DIR}/summary.json" - -if [[ -z "${PR_NUMBER:-}" ]]; then - echo "PR_NUMBER is not set; skipping coverage PR comment" - exit 0 -fi - -if [[ ! -f "${SUMMARY_JSON}" ]]; then - echo "::warning::${SUMMARY_JSON} not found; skipping coverage PR comment" - exit 0 -fi - -if ! command -v gh >/dev/null 2>&1; then - echo "::error::gh CLI is required to post PR comments" - exit 1 -fi - -BODY="$(MARKER="${MARKER}" SUMMARY_JSON="${SUMMARY_JSON}" GITHUB_REPOSITORY="${GITHUB_REPOSITORY}" GITHUB_RUN_ID="${GITHUB_RUN_ID}" python3 <<'PY' -import json -import os -from pathlib import Path - -marker = os.environ["MARKER"] -summary = json.loads(Path(os.environ["SUMMARY_JSON"]).read_text()) -repo = os.environ["GITHUB_REPOSITORY"] -run_id = os.environ["GITHUB_RUN_ID"] - -def metric(name: str) -> dict: - if name in summary: - return summary[name] - return { - "covered": summary[f"{name}_covered"], - "num": summary[f"{name}_total"], - "percent": summary.get(f"{name}_percent") or 0, - } - -line = metric("line") -branch = metric("branch") -func = metric("function") - -def bar(percent: float) -> str: - filled = max(0, min(10, int(round(percent / 10)))) - return "█" * filled + "░" * (10 - filled) - -files = [ - f for f in summary.get("files", []) - if f.get("line_total", 0) >= 20 -] -files.sort(key=lambda f: f.get("line_percent") or 0) -lowest = files[:8] - -run_url = f"https://github.com/{repo}/actions/runs/{run_id}" -lines = [ - marker, - "## Code coverage", - "", - f"Workflow run: [Coverage job #{run_id}]({run_url}) · download the **coverage-report** artifact for the HTML report.", - "", - "| Metric | Coverage | Covered / total |", - "| --- | --- | --- |", - f"| Line | **{line['percent']:.1f}%** {bar(line['percent'])} | {line['covered']} / {line['num']} |", - f"| Branch | **{branch['percent']:.1f}%** {bar(branch['percent'])} | {branch['covered']} / {branch['num']} |", - f"| Function | **{func['percent']:.1f}%** {bar(func['percent'])} | {func['covered']} / {func['num']} |", - "", - "Scope: `src/`, `include/ydb-cpp-sdk/`, `plugins/` (contrib, tests, and `_deps` excluded).", -] - -if lowest: - lines.extend([ - "", - "
", - "Lowest line coverage (≥ 20 lines)", - "", - "| File | Line % | Covered / total |", - "| --- | ---: | --- |", - ]) - for f in lowest: - pct = f.get("line_percent") or 0 - lines.append( - f"| `{f['filename']}` | {pct:.1f}% | {f['line_covered']} / {f['line_total']} |" - ) - lines.extend(["", "
"]) - -print("\n".join(lines)) -PY -)" - -COMMENT_ID="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ - --paginate \ - --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" \ - | head -n1)" - -if [[ -n "${COMMENT_ID}" ]]; then - gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" \ - -f body="${BODY}" >/dev/null - echo "Updated coverage comment ${COMMENT_ID} on PR #${PR_NUMBER}" -else - gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ - -f body="${BODY}" >/dev/null - echo "Posted coverage comment on PR #${PR_NUMBER}" -fi diff --git a/.github/actions/gcovr/run_gcovr.sh b/.github/actions/gcovr/run_gcovr.sh index 18bc940cb4c..c89a81e8878 100755 --- a/.github/actions/gcovr/run_gcovr.sh +++ b/.github/actions/gcovr/run_gcovr.sh @@ -30,30 +30,3 @@ if [[ -n "${FAIL_UNDER_LINE:-}" && "${FAIL_UNDER_LINE}" != "0" ]]; then fi gcovr "${gcovr_args[@]}" - -if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "${OUTPUT_DIR}/summary.json" ]]; then - { - echo "## Code coverage" - echo "" - echo "| Metric | Covered | Total | Percent |" - echo "| --- | ---: | ---: | ---: |" - jq -r ' - def metric($name): - if has($name) then - .[$name] - else - { - covered: .[$name + "_covered"], - num: .[$name + "_total"], - percent: .[$name + "_percent"] - } - end; - metric("line") as $l - | metric("branch") as $b - | metric("function") as $f - | "| Line | \($l.covered) | \($l.num) | \($l.percent // 0)% |", - "| Branch | \($b.covered) | \($b.num) | \($b.percent // 0)% |", - "| Function | \($f.covered) | \($f.num) | \($f.percent // 0)% |" - ' "${OUTPUT_DIR}/summary.json" - } >> "${GITHUB_STEP_SUMMARY}" -fi diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 20ad15c24c0..97cc17eca74 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,7 +19,6 @@ jobs: permissions: contents: read id-token: write - pull-requests: write services: ydb: image: ydbplatform/local-ydb:24.4 @@ -70,7 +69,6 @@ jobs: build-directory: build source-root: ${{ github.workspace }} gcov-executable: gcov-13 - post-pr-comment: ${{ github.event_name == 'pull_request' }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v5