From 40deebafacd512e1bd93300ffa6cb1dd4081b436 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 11:14:22 +0530 Subject: [PATCH 01/39] FEAT: Full Stack Code Coverage --- .github/workflows/pr-code-coverage.yml | 85 +++++++++++++++++++++++ generate_codecov.sh | 93 ++++++++++++++++++++++++++ mssql_python/pybind/build.sh | 7 +- 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/pr-code-coverage.yml create mode 100644 generate_codecov.sh diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml new file mode 100644 index 00000000..60563ee6 --- /dev/null +++ b/.github/workflows/pr-code-coverage.yml @@ -0,0 +1,85 @@ +name: PR Validation (macOS with Unified Coverage) + +on: + pull_request: + branches: + - main + +jobs: + pytest-macos: + name: macOS Unified Coverage + runs-on: macos-latest + + env: + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install CMake + run: | + brew update + brew uninstall cmake --ignore-dependencies || echo "CMake not installed" + brew install cmake + + - name: Install and start Colima-based Docker + run: | + brew install docker colima + colima start --cpu 4 --memory 8 --disk 50 + docker context use colima >/dev/null || true + docker version + docker ps + + - name: Start SQL Server container + run: | + docker pull mcr.microsoft.com/mssql/server:2022-latest + docker run \ + --name sqlserver \ + -e ACCEPT_EULA=Y \ + -e MSSQL_SA_PASSWORD="${DB_PASSWORD}" \ + -p 1433:1433 \ + -d mcr.microsoft.com/mssql/server:2022-latest + + # Wait until SQL Server is ready + for i in {1..30}; do + docker exec sqlserver \ + /opt/mssql-tools18/bin/sqlcmd \ + -S localhost \ + -U SA \ + -P "$DB_PASSWORD" \ + -C -Q "SELECT 1" && break + sleep 2 + done + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Build pybind bindings + run: | + cd mssql_python/pybind + ./build.sh + + - name: Run pytest (with raw coverage) + run: | + echo "Running pytest..." + python -m pytest -v --junitxml=test-results.xml --cov=. --cov-report=xml --capture=tee-sys --cache-clear + + - name: Generate unified coverage (Python + C++) + run: | + chmod +x ./generate_codecov.sh + ./generate_codecov.sh + + - name: Comment unified coverage on PR + uses: marocchino/sticky-pull-request-comment@v2 + with: + path: unified-coverage/index.html + header: Unified Coverage Report \ No newline at end of file diff --git a/generate_codecov.sh b/generate_codecov.sh new file mode 100644 index 00000000..f3c79269 --- /dev/null +++ b/generate_codecov.sh @@ -0,0 +1,93 @@ +#!/bin/bash +set -euo pipefail + +echo "===================================" +echo "[STEP 1] Installing dependencies" +echo "===================================" + +# Ensure Homebrew exists +if ! command -v brew &>/dev/null; then + echo "[ERROR] Homebrew is required. Install from https://brew.sh/" + exit 1 +fi + +# Install LLVM (for llvm-profdata, llvm-cov) +if ! command -v llvm-profdata &>/dev/null; then + echo "[ACTION] Installing LLVM via Homebrew" + brew install llvm +fi +export PATH="/opt/homebrew/opt/llvm/bin:$PATH" + +# Install lcov (provides lcov + genhtml) +if ! command -v genhtml &>/dev/null; then + echo "[ACTION] Installing lcov via Homebrew" + brew install lcov +fi + +# Install Python plugin for LCOV export +if ! python -m pip show coverage-lcov &>/dev/null; then + echo "[ACTION] Installing coverage-lcov via pip" + python -m pip install coverage-lcov +fi + +echo "===================================" +echo "[STEP 2] Running pytest with Python coverage" +echo "===================================" + +# Cleanup old coverage +rm -f .coverage coverage.xml python-coverage.info cpp-coverage.info total.info +rm -rf htmlcov unified-coverage + +# Run pytest with Python coverage (XML + HTML output) +python -m pytest -v \ + --junitxml=test-results.xml \ + --cov=mssql_python \ + --cov-report=xml \ + --cov-report=html \ + --capture=tee-sys \ + --cache-clear + +# Convert Python coverage to LCOV format (restrict to your repo only) +echo "[ACTION] Converting Python coverage to LCOV" +coverage lcov -o python-coverage.info --include="mssql_python/*" + +echo "===================================" +echo "[STEP 3] Processing C++ coverage (Clang/LLVM)" +echo "===================================" + +# Merge raw profile data from pybind runs +if [ ! -f default.profraw ]; then + echo "[ERROR] default.profraw not found. Did you build with -fprofile-instr-generate?" + exit 1 +fi + +llvm-profdata merge -sparse default.profraw -o default.profdata + +# Find the pybind .so (assuming universal2 build) +PYBIND_SO=$(find mssql_python -name "*.so" | grep "universal2" | head -n 1) +if [ -z "$PYBIND_SO" ]; then + echo "[ERROR] Could not find pybind .so (universal2 build)." + exit 1 +fi + +echo "[INFO] Using pybind module: $PYBIND_SO" + +# Export C++ coverage, excluding Python headers, pybind11, and Homebrew includes +llvm-cov export "$PYBIND_SO" \ + -instr-profile=default.profdata \ + -arch arm64 \ + -ignore-filename-regex='(python3\.13|cpython|pybind11|homebrew)' \ + -format=lcov > cpp-coverage.info + +echo "===================================" +echo "[STEP 4] Merging Python + C++ coverage" +echo "===================================" + +# Merge LCOV reports (ignore minor inconsistencies in Python LCOV export) +lcov -a python-coverage.info -a cpp-coverage.info -o total.info \ + --ignore-errors inconsistent,corrupt + +# Generate unified HTML report +genhtml total.info --output-directory unified-coverage + +echo "[SUCCESS] Unified coverage report generated at unified-coverage/index.html" \ No newline at end of file diff --git a/mssql_python/pybind/build.sh b/mssql_python/pybind/build.sh index dbd1e6c3..f1a1f0b1 100755 --- a/mssql_python/pybind/build.sh +++ b/mssql_python/pybind/build.sh @@ -56,8 +56,11 @@ echo "[DIAGNOSTIC] Changed to build directory: ${BUILD_DIR}" # Configure CMake (architecture settings handled in CMakeLists.txt) echo "[DIAGNOSTIC] Running CMake configure" if [[ "$OS" == "macOS" ]]; then - echo "[DIAGNOSTIC] Configuring for macOS (universal2 is set automatically)" - cmake -DMACOS_STRING_FIX=ON "${SOURCE_DIR}" + echo "[ACTION] Configuring for macOS with Clang coverage instrumentation" + cmake -DMACOS_STRING_FIX=ON \ + -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + -DCMAKE_C_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + "${SOURCE_DIR}" else echo "[DIAGNOSTIC] Configuring for Linux with architecture: $DETECTED_ARCH" cmake -DARCHITECTURE="$DETECTED_ARCH" "${SOURCE_DIR}" From f11fa4193c64a4c7357b9769747dd4d1712ce28a Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 11:20:39 +0530 Subject: [PATCH 02/39] Link to HTML and summary only --- .github/workflows/pr-code-coverage.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 60563ee6..653697b2 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -77,9 +77,24 @@ jobs: run: | chmod +x ./generate_codecov.sh ./generate_codecov.sh + # Create summary text file from LCOV report + genhtml total.info \ + --output-directory unified-coverage \ + --quiet \ + --title "Unified Coverage Report" \ + --summary-only > unified-coverage/summary.txt - - name: Comment unified coverage on PR + - name: Upload full HTML coverage artifact + uses: actions/upload-artifact@v4 + with: + name: UnifiedCoverageHTML + path: unified-coverage + + - name: Comment unified coverage summary on PR uses: marocchino/sticky-pull-request-comment@v2 with: - path: unified-coverage/index.html - header: Unified Coverage Report \ No newline at end of file + header: Unified Coverage Report + message: | + $(cat unified-coverage/summary.txt) + + 👉 [Download full HTML report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts) From a7aeb06bfa8d6bb58d133a6820a70f94ad73378a Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 11:22:18 +0530 Subject: [PATCH 03/39] Potential fix for code scanning alert no. 310: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/pr-code-coverage.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 653697b2..9b0f7e4c 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -1,4 +1,7 @@ name: PR Validation (macOS with Unified Coverage) +permissions: + contents: read + pull-requests: write on: pull_request: From acaa2f36bea0777b03990555811d66193b507454 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 14:28:02 +0530 Subject: [PATCH 04/39] port to linux since macOS doesnt support docker in GH runner --- .github/workflows/pr-code-coverage.yml | 43 ++++++++++++-------------- mssql_python/pybind/build.sh | 17 ++++++---- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 9b0f7e4c..47273eba 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -1,17 +1,18 @@ -name: PR Validation (macOS with Unified Coverage) -permissions: - contents: read - pull-requests: write +name: PR Validation (Linux with Unified Coverage) on: pull_request: branches: - main +permissions: + contents: read + pull-requests: write + jobs: - pytest-macos: - name: macOS Unified Coverage - runs-on: macos-latest + pytest-linux: + name: Ubuntu Unified Coverage + runs-on: ubuntu-latest env: DB_PASSWORD: ${{ secrets.DB_PASSWORD }} @@ -26,19 +27,10 @@ jobs: with: python-version: '3.13' - - name: Install CMake - run: | - brew update - brew uninstall cmake --ignore-dependencies || echo "CMake not installed" - brew install cmake - - - name: Install and start Colima-based Docker + - name: Install build dependencies run: | - brew install docker colima - colima start --cpu 4 --memory 8 --disk 50 - docker context use colima >/dev/null || true - docker version - docker ps + sudo apt-get update + sudo apt-get install -y cmake gcc g++ lcov unixodbc-dev - name: Start SQL Server container run: | @@ -65,6 +57,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + pip install coverage-lcov - name: Build pybind bindings run: | @@ -73,13 +66,17 @@ jobs: - name: Run pytest (with raw coverage) run: | - echo "Running pytest..." - python -m pytest -v --junitxml=test-results.xml --cov=. --cov-report=xml --capture=tee-sys --cache-clear + python -m pytest -v \ + --junitxml=test-results.xml \ + --cov=. \ + --cov-report=xml \ + --capture=tee-sys \ + --cache-clear - name: Generate unified coverage (Python + C++) run: | - chmod +x ./generate_codecov.sh - ./generate_codecov.sh + chmod +x ./generate_universal_codecoverage.sh + ./generate_universal_codecoverage.sh # Create summary text file from LCOV report genhtml total.info \ --output-directory unified-coverage \ diff --git a/mssql_python/pybind/build.sh b/mssql_python/pybind/build.sh index f1a1f0b1..afd9fcf1 100755 --- a/mssql_python/pybind/build.sh +++ b/mssql_python/pybind/build.sh @@ -47,23 +47,28 @@ if [ -d "build" ]; then echo "Build directory removed." fi -# Create build directory for universal binary +# Create build directory BUILD_DIR="${SOURCE_DIR}/build" mkdir -p "${BUILD_DIR}" cd "${BUILD_DIR}" echo "[DIAGNOSTIC] Changed to build directory: ${BUILD_DIR}" -# Configure CMake (architecture settings handled in CMakeLists.txt) +# Configure CMake (with Clang coverage instrumentation on Linux) echo "[DIAGNOSTIC] Running CMake configure" -if [[ "$OS" == "macOS" ]]; then +if [[ "$OS" == "Linux" ]]; then + echo "[ACTION] Configuring for Linux with Clang coverage instrumentation" + cmake -DARCHITECTURE="$DETECTED_ARCH" \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + -DCMAKE_C_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + "${SOURCE_DIR}" +else echo "[ACTION] Configuring for macOS with Clang coverage instrumentation" cmake -DMACOS_STRING_FIX=ON \ -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ -DCMAKE_C_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ "${SOURCE_DIR}" -else - echo "[DIAGNOSTIC] Configuring for Linux with architecture: $DETECTED_ARCH" - cmake -DARCHITECTURE="$DETECTED_ARCH" "${SOURCE_DIR}" fi # Check if CMake configuration succeeded From 9d02e9b4bef1e920af3a523ff02dde1c38b0bc1e Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 14:42:51 +0530 Subject: [PATCH 05/39] correct the sh filename --- .github/workflows/pr-code-coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 47273eba..a2a5b387 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -75,8 +75,8 @@ jobs: - name: Generate unified coverage (Python + C++) run: | - chmod +x ./generate_universal_codecoverage.sh - ./generate_universal_codecoverage.sh + chmod +x ./generate_codecov.sh + ./generate_codecov.sh # Create summary text file from LCOV report genhtml total.info \ --output-directory unified-coverage \ From b369afee9630a804d096301ca06a40845111e42e Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 14:51:32 +0530 Subject: [PATCH 06/39] codecov sh file to linux --- generate_codecov.sh | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/generate_codecov.sh b/generate_codecov.sh index f3c79269..1074b9e1 100644 --- a/generate_codecov.sh +++ b/generate_codecov.sh @@ -5,23 +5,19 @@ echo "===================================" echo "[STEP 1] Installing dependencies" echo "===================================" -# Ensure Homebrew exists -if ! command -v brew &>/dev/null; then - echo "[ERROR] Homebrew is required. Install from https://brew.sh/" - exit 1 -fi +# Update package list +sudo apt-get update # Install LLVM (for llvm-profdata, llvm-cov) if ! command -v llvm-profdata &>/dev/null; then - echo "[ACTION] Installing LLVM via Homebrew" - brew install llvm + echo "[ACTION] Installing LLVM via apt" + sudo apt-get install -y llvm fi -export PATH="/opt/homebrew/opt/llvm/bin:$PATH" # Install lcov (provides lcov + genhtml) if ! command -v genhtml &>/dev/null; then - echo "[ACTION] Installing lcov via Homebrew" - brew install lcov + echo "[ACTION] Installing lcov via apt" + sudo apt-get install -y lcov fi # Install Python plugin for LCOV export @@ -63,20 +59,19 @@ fi llvm-profdata merge -sparse default.profraw -o default.profdata -# Find the pybind .so (assuming universal2 build) -PYBIND_SO=$(find mssql_python -name "*.so" | grep "universal2" | head -n 1) +# Find the pybind .so file (Linux build) +PYBIND_SO=$(find mssql_python -name "*.so" | head -n 1) if [ -z "$PYBIND_SO" ]; then - echo "[ERROR] Could not find pybind .so (universal2 build)." + echo "[ERROR] Could not find pybind .so" exit 1 fi echo "[INFO] Using pybind module: $PYBIND_SO" -# Export C++ coverage, excluding Python headers, pybind11, and Homebrew includes +# Export C++ coverage, excluding Python headers, pybind11, and system includes llvm-cov export "$PYBIND_SO" \ -instr-profile=default.profdata \ - -arch arm64 \ - -ignore-filename-regex='(python3\.13|cpython|pybind11|homebrew)' \ + -ignore-filename-regex='(python3\.[0-9]+|cpython|pybind11|/usr/include/|/usr/lib/)' \ -format=lcov > cpp-coverage.info echo "===================================" @@ -90,4 +85,4 @@ lcov -a python-coverage.info -a cpp-coverage.info -o total.info \ # Generate unified HTML report genhtml total.info --output-directory unified-coverage -echo "[SUCCESS] Unified coverage report generated at unified-coverage/index.html" \ No newline at end of file +echo "[SUCCESS] Unified coverage report generated at unified-coverage/index.html" From 89266e926e1d16f4cb4435f3a2ccfe3545b69201 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 15:05:14 +0530 Subject: [PATCH 07/39] fixing bugs --- .github/workflows/pr-code-coverage.yml | 2 +- generate_codecov.sh | 7 +++++-- mssql_python/pybind/build.sh | 23 ++++++++++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index a2a5b387..26825f7e 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -62,7 +62,7 @@ jobs: - name: Build pybind bindings run: | cd mssql_python/pybind - ./build.sh + ./build.sh codecov - name: Run pytest (with raw coverage) run: | diff --git a/generate_codecov.sh b/generate_codecov.sh index 1074b9e1..eb299588 100644 --- a/generate_codecov.sh +++ b/generate_codecov.sh @@ -82,7 +82,10 @@ echo "===================================" lcov -a python-coverage.info -a cpp-coverage.info -o total.info \ --ignore-errors inconsistent,corrupt -# Generate unified HTML report -genhtml total.info --output-directory unified-coverage +# Generate full HTML report +genhtml total.info --output-directory unified-coverage --quiet --title "Unified Coverage Report" + +# Generate plain-text summary for PR comment +lcov --summary total.info > unified-coverage/summary.txt echo "[SUCCESS] Unified coverage report generated at unified-coverage/index.html" diff --git a/mssql_python/pybind/build.sh b/mssql_python/pybind/build.sh index afd9fcf1..7a20b61c 100755 --- a/mssql_python/pybind/build.sh +++ b/mssql_python/pybind/build.sh @@ -26,6 +26,13 @@ else exit 1 fi +# Check for coverage mode and set flags accordingly +COVERAGE_MODE=false +if [[ "${1:-}" == "codecov" || "${1:-}" == "--coverage" ]]; then + COVERAGE_MODE=true + echo "[MODE] Enabling Clang coverage instrumentation" +fi + # Get Python version from active interpreter PYTAG=$(python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')") @@ -53,9 +60,9 @@ mkdir -p "${BUILD_DIR}" cd "${BUILD_DIR}" echo "[DIAGNOSTIC] Changed to build directory: ${BUILD_DIR}" -# Configure CMake (with Clang coverage instrumentation on Linux) +# Configure CMake (with Clang coverage instrumentation on Linux only - codecov is not supported for macOS) echo "[DIAGNOSTIC] Running CMake configure" -if [[ "$OS" == "Linux" ]]; then +if [[ "$COVERAGE_MODE" == "true" && "$OS" == "Linux" ]]; then echo "[ACTION] Configuring for Linux with Clang coverage instrumentation" cmake -DARCHITECTURE="$DETECTED_ARCH" \ -DCMAKE_C_COMPILER=clang \ @@ -64,11 +71,13 @@ if [[ "$OS" == "Linux" ]]; then -DCMAKE_C_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ "${SOURCE_DIR}" else - echo "[ACTION] Configuring for macOS with Clang coverage instrumentation" - cmake -DMACOS_STRING_FIX=ON \ - -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ - -DCMAKE_C_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ - "${SOURCE_DIR}" + if [[ "$OS" == "macOS" ]]; then + echo "[ACTION] Configuring for macOS (default build)" + cmake -DMACOS_STRING_FIX=ON "${SOURCE_DIR}" + else + echo "[ACTION] Configuring for Linux with architecture: $DETECTED_ARCH" + cmake -DARCHITECTURE="$DETECTED_ARCH" "${SOURCE_DIR}" + fi fi # Check if CMake configuration succeeded From b55d146351f5a2384ed92036ceb560cb8633ff43 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 15:09:52 +0530 Subject: [PATCH 08/39] do things only once --- .github/workflows/pr-code-coverage.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 26825f7e..8535025c 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -64,25 +64,11 @@ jobs: cd mssql_python/pybind ./build.sh codecov - - name: Run pytest (with raw coverage) - run: | - python -m pytest -v \ - --junitxml=test-results.xml \ - --cov=. \ - --cov-report=xml \ - --capture=tee-sys \ - --cache-clear - - name: Generate unified coverage (Python + C++) run: | chmod +x ./generate_codecov.sh ./generate_codecov.sh # Create summary text file from LCOV report - genhtml total.info \ - --output-directory unified-coverage \ - --quiet \ - --title "Unified Coverage Report" \ - --summary-only > unified-coverage/summary.txt - name: Upload full HTML coverage artifact uses: actions/upload-artifact@v4 From c4c47a4b2340a93485fb576ac4ee11b2d3cc8dab Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 15:33:26 +0530 Subject: [PATCH 09/39] add this in ADO --- eng/pipelines/pr-validation-pipeline.yml | 103 +++++++++++++++++++++-- generate_codecov.sh | 4 + 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 6621b10d..e9960045 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -72,11 +72,11 @@ jobs: testResultsFiles: '**/test-results.xml' testRunTitle: 'Publish test results' - - task: PublishCodeCoverageResults@1 - inputs: - codeCoverageTool: 'Cobertura' - summaryFileLocation: 'coverage.xml' - displayName: 'Publish code coverage results' + # - task: PublishCodeCoverageResults@1 + # inputs: + # codeCoverageTool: 'Cobertura' + # summaryFileLocation: 'coverage.xml' + # displayName: 'Publish code coverage results' - job: PytestOnMacOS displayName: 'macOS x86_64' @@ -1477,3 +1477,96 @@ jobs: inputs: testResultsFiles: '**/test-results-alpine-arm64.xml' testRunTitle: 'Publish pytest results on Alpine ARM64' + +- job: CodeCoverageReport + displayName: 'Full Stack Coverage Report using Ubuntu Linux' + pool: + vmImage: 'ubuntu-latest' + + steps: + - script: | + # Install build dependencies + sudo apt-get update + sudo apt-get install -y cmake gcc g++ lcov unixodbc-dev llvm clang + displayName: 'Install build dependencies' + + - script: | + # Start SQL Server container + docker pull mcr.microsoft.com/mssql/server:2022-latest + docker run \ + --name sqlserver \ + -e ACCEPT_EULA=Y \ + -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ + -p 1433:1433 \ + -d mcr.microsoft.com/mssql/server:2022-latest + + # Wait until SQL Server is ready + for i in {1..30}; do + docker exec sqlserver \ + /opt/mssql-tools18/bin/sqlcmd \ + -S localhost \ + -U SA \ + -P "$(DB_PASSWORD)" \ + -C -Q "SELECT 1" && break + sleep 2 + done + displayName: 'Start SQL Server container' + env: + DB_PASSWORD: $(DB_PASSWORD) + + - script: | + # Install Python dependencies + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install coverage-lcov lcov-cobertura + displayName: 'Install Python dependencies' + + - script: | + # Build pybind bindings with coverage instrumentation + cd mssql_python/pybind + ./build.sh codecov + displayName: 'Build pybind bindings with coverage' + + - script: | + # Generate unified coverage (Python + C++) + chmod +x ./generate_codecov.sh + ./generate_codecov.sh + + # Convert unified LCOV to Cobertura XML for ADO reporting + lcov_cobertura total.info --output unified-coverage/coverage.xml + displayName: 'Generate unified coverage (Python + C++)' + env: + DB_CONNECTION_STRING: 'Driver=ODBC Driver 18 for SQL Server;Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + DB_PASSWORD: $(DB_PASSWORD) + + - task: PublishTestResults@2 + condition: succeededOrFailed() + inputs: + testResultsFiles: '**/test-results.xml' + testRunTitle: 'Publish pytest results with unified coverage' + + - task: PublishCodeCoverageResults@2 + condition: succeededOrFailed() + inputs: + codeCoverageTool: Cobertura + summaryFileLocation: 'unified-coverage/coverage.xml' + reportDirectory: 'unified-coverage' + failIfCoverageEmpty: true + displayName: 'Publish unified code coverage results' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: 'unified-coverage' + ArtifactName: 'UnifiedCoverageHTML' + publishLocation: 'Container' + displayName: 'Publish unified coverage HTML report' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: 'total.info' + ArtifactName: 'UnifiedCoverageLCOV' + publishLocation: 'Container' + displayName: 'Publish unified coverage LCOV data' + continueOnError: true diff --git a/generate_codecov.sh b/generate_codecov.sh index eb299588..127af040 100644 --- a/generate_codecov.sh +++ b/generate_codecov.sh @@ -88,4 +88,8 @@ genhtml total.info --output-directory unified-coverage --quiet --title "Unified # Generate plain-text summary for PR comment lcov --summary total.info > unified-coverage/summary.txt +# Append link to artifact +echo "" >> unified-coverage/summary.txt +echo "👉 [Download full HTML report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}#artifacts)" >> unified-coverage/summary.txt + echo "[SUCCESS] Unified coverage report generated at unified-coverage/index.html" From 6c977adf6593ca7c1d16a2b325f5ca32b200e984 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 15:38:58 +0530 Subject: [PATCH 10/39] fix Github comment --- .github/workflows/pr-code-coverage.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 8535025c..3c8be655 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -80,7 +80,4 @@ jobs: uses: marocchino/sticky-pull-request-comment@v2 with: header: Unified Coverage Report - message: | - $(cat unified-coverage/summary.txt) - - 👉 [Download full HTML report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts) + path: unified-coverage/summary.txt From 541945b47456106cd83a6c19f29e3f930078e70b Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 15:57:20 +0530 Subject: [PATCH 11/39] fix Github comment, core logic is now in PR validation --- .github/workflows/pr-code-coverage.yml | 107 ++++++++++--------------- 1 file changed, 41 insertions(+), 66 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 3c8be655..a8f67844 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -1,83 +1,58 @@ -name: PR Validation (Linux with Unified Coverage) +name: PR Code Coverage on: pull_request: branches: - main -permissions: - contents: read - pull-requests: write - jobs: - pytest-linux: - name: Ubuntu Unified Coverage + coverage-report: runs-on: ubuntu-latest - - env: - DB_PASSWORD: ${{ secrets.DB_PASSWORD }} - DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }} + permissions: + pull-requests: write + contents: read steps: - - name: Checkout code + - name: Checkout repo uses: actions/checkout@v4 - - name: Set up Python 3.13 - uses: actions/setup-python@v5 - with: - python-version: '3.13' - - - name: Install build dependencies + - name: Wait for ADO build to succeed run: | - sudo apt-get update - sudo apt-get install -y cmake gcc g++ lcov unixodbc-dev - - - name: Start SQL Server container - run: | - docker pull mcr.microsoft.com/mssql/server:2022-latest - docker run \ - --name sqlserver \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="${DB_PASSWORD}" \ - -p 1433:1433 \ - -d mcr.microsoft.com/mssql/server:2022-latest - - # Wait until SQL Server is ready - for i in {1..30}; do - docker exec sqlserver \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$DB_PASSWORD" \ - -C -Q "SELECT 1" && break - sleep 2 + PR_NUMBER=${{ github.event.pull_request.number }} + API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&queryOrder=queueTimeDescending&%24top=10&api-version=7.1-preview.7" + + echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." + + for i in {1..60}; do + BUILD_INFO=$(curl -s "$API_URL" | jq -c --arg PR "$PR_NUMBER" '.value[] | select(.triggerInfo["pr.number"]==$PR) | .') + if [[ -n "$BUILD_INFO" ]]; then + STATUS=$(echo "$BUILD_INFO" | jq -r .status) + RESULT=$(echo "$BUILD_INFO" | jq -r .result) + BUILD_ID=$(echo "$BUILD_INFO" | jq -r .id) + WEB_URL=$(echo "$BUILD_INFO" | jq -r ._links.web.href) + + echo "Found build $BUILD_ID, status=$STATUS, result=$RESULT" + + if [[ "$STATUS" == "completed" ]]; then + if [[ "$RESULT" == "succeeded" ]]; then + echo "✅ Build succeeded: $WEB_URL" + echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV + break + else + echo "❌ Build failed" + exit 1 + fi + fi + fi + + echo "⏳ Build not ready yet... retrying in 20s" + sleep 20 done - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install coverage-lcov - - - name: Build pybind bindings - run: | - cd mssql_python/pybind - ./build.sh codecov - - - name: Generate unified coverage (Python + C++) - run: | - chmod +x ./generate_codecov.sh - ./generate_codecov.sh - # Create summary text file from LCOV report - - - name: Upload full HTML coverage artifact - uses: actions/upload-artifact@v4 - with: - name: UnifiedCoverageHTML - path: unified-coverage - - - name: Comment unified coverage summary on PR + - name: Comment ADO coverage link on PR uses: marocchino/sticky-pull-request-comment@v2 with: - header: Unified Coverage Report - path: unified-coverage/summary.txt + header: Azure DevOps Coverage Report + message: | + ✅ Azure DevOps build completed successfully. + 👉 [View Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) \ No newline at end of file From fa3d6cc43e591cdff52e9902be61d56eae073f74 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 16:07:39 +0530 Subject: [PATCH 12/39] fix spamming in GH workflow --- .github/workflows/pr-code-coverage.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index a8f67844..0c7e5651 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -23,23 +23,21 @@ jobs: echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." - for i in {1..60}; do - BUILD_INFO=$(curl -s "$API_URL" | jq -c --arg PR "$PR_NUMBER" '.value[] | select(.triggerInfo["pr.number"]==$PR) | .') + for i in {1..100}; do + BUILD_INFO=$(curl -s "$API_URL" | jq -c --arg PR "$PR_NUMBER" '.value[] | select(.triggerInfo["pr.number"]==$PR) | .' | head -n 1) if [[ -n "$BUILD_INFO" ]]; then STATUS=$(echo "$BUILD_INFO" | jq -r .status) RESULT=$(echo "$BUILD_INFO" | jq -r .result) BUILD_ID=$(echo "$BUILD_INFO" | jq -r .id) WEB_URL=$(echo "$BUILD_INFO" | jq -r ._links.web.href) - echo "Found build $BUILD_ID, status=$STATUS, result=$RESULT" - if [[ "$STATUS" == "completed" ]]; then if [[ "$RESULT" == "succeeded" ]]; then - echo "✅ Build succeeded: $WEB_URL" + echo "✅ Build $BUILD_ID succeeded: $WEB_URL" echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV break else - echo "❌ Build failed" + echo "❌ Build $BUILD_ID failed" exit 1 fi fi From 3d671332de1068bed4c5328c60a1b095c75f71aa Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 16:19:38 +0530 Subject: [PATCH 13/39] fix spamming in GH workflow --- .github/workflows/pr-code-coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 0c7e5651..e36eac96 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -24,8 +24,8 @@ jobs: echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." for i in {1..100}; do - BUILD_INFO=$(curl -s "$API_URL" | jq -c --arg PR "$PR_NUMBER" '.value[] | select(.triggerInfo["pr.number"]==$PR) | .' | head -n 1) - if [[ -n "$BUILD_INFO" ]]; then + BUILD_INFO=$(curl -s "$API_URL" | jq -c --arg PR "$PR_NUMBER" '[.value[] | select(.triggerInfo["pr.number"]==$PR)][0] // empty') + if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" ]]; then STATUS=$(echo "$BUILD_INFO" | jq -r .status) RESULT=$(echo "$BUILD_INFO" | jq -r .result) BUILD_ID=$(echo "$BUILD_INFO" | jq -r .id) From aed05f03c4e127e6a433cf232e28094632c11f33 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 16:59:42 +0530 Subject: [PATCH 14/39] line coverage only for cpp --- generate_codecov.sh | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/generate_codecov.sh b/generate_codecov.sh index 127af040..328c3ac7 100644 --- a/generate_codecov.sh +++ b/generate_codecov.sh @@ -31,14 +31,14 @@ echo "[STEP 2] Running pytest with Python coverage" echo "===================================" # Cleanup old coverage -rm -f .coverage coverage.xml python-coverage.info cpp-coverage.info total.info +rm -f .coverage coverage.xml python-coverage.info cpp-coverage.info total.info total.cleaned.info rm -rf htmlcov unified-coverage # Run pytest with Python coverage (XML + HTML output) python -m pytest -v \ --junitxml=test-results.xml \ --cov=mssql_python \ - --cov-report=xml \ + --cov-report=xml:coverage.xml \ --cov-report=html \ --capture=tee-sys \ --cache-clear @@ -72,6 +72,7 @@ echo "[INFO] Using pybind module: $PYBIND_SO" llvm-cov export "$PYBIND_SO" \ -instr-profile=default.profdata \ -ignore-filename-regex='(python3\.[0-9]+|cpython|pybind11|/usr/include/|/usr/lib/)' \ + --skip-functions \ -format=lcov > cpp-coverage.info echo "===================================" @@ -82,14 +83,9 @@ echo "===================================" lcov -a python-coverage.info -a cpp-coverage.info -o total.info \ --ignore-errors inconsistent,corrupt -# Generate full HTML report -genhtml total.info --output-directory unified-coverage --quiet --title "Unified Coverage Report" - -# Generate plain-text summary for PR comment -lcov --summary total.info > unified-coverage/summary.txt +# Normalize paths so everything starts from mssql_python/ +echo "[ACTION] Normalizing paths in LCOV report" +lcov --path . --base-directory . --strip-path $(pwd) -o total.cleaned.info -a total.info -# Append link to artifact -echo "" >> unified-coverage/summary.txt -echo "👉 [Download full HTML report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}#artifacts)" >> unified-coverage/summary.txt - -echo "[SUCCESS] Unified coverage report generated at unified-coverage/index.html" +# Generate full HTML report +genhtml total.cleaned.info --output-directory unified-coverage --quiet --title "Unified Coverage Report" From a65c52da4f6b679b1bb53c2a27da8b3b9c640557 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 17:07:53 +0530 Subject: [PATCH 15/39] line coverage only for cpp --- generate_codecov.sh | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/generate_codecov.sh b/generate_codecov.sh index 328c3ac7..65bc3fd6 100644 --- a/generate_codecov.sh +++ b/generate_codecov.sh @@ -26,12 +26,18 @@ if ! python -m pip show coverage-lcov &>/dev/null; then python -m pip install coverage-lcov fi +# Install LCOV → Cobertura converter (for ADO) +if ! python -m pip show lcov-cobertura &>/dev/null; then + echo "[ACTION] Installing lcov-cobertura via pip" + python -m pip install lcov-cobertura +fi + echo "===================================" echo "[STEP 2] Running pytest with Python coverage" echo "===================================" # Cleanup old coverage -rm -f .coverage coverage.xml python-coverage.info cpp-coverage.info total.info total.cleaned.info +rm -f .coverage coverage.xml python-coverage.info cpp-coverage.info total.info rm -rf htmlcov unified-coverage # Run pytest with Python coverage (XML + HTML output) @@ -43,7 +49,7 @@ python -m pytest -v \ --capture=tee-sys \ --cache-clear -# Convert Python coverage to LCOV format (restrict to your repo only) +# Convert Python coverage to LCOV format (restrict to repo only) echo "[ACTION] Converting Python coverage to LCOV" coverage lcov -o python-coverage.info --include="mssql_python/*" @@ -79,13 +85,31 @@ echo "===================================" echo "[STEP 4] Merging Python + C++ coverage" echo "===================================" -# Merge LCOV reports (ignore minor inconsistencies in Python LCOV export) +# Merge LCOV reports (ignore inconsistencies in Python LCOV export) lcov -a python-coverage.info -a cpp-coverage.info -o total.info \ --ignore-errors inconsistent,corrupt # Normalize paths so everything starts from mssql_python/ echo "[ACTION] Normalizing paths in LCOV report" -lcov --path . --base-directory . --strip-path $(pwd) -o total.cleaned.info -a total.info +sed -i "s|$(pwd)/||g" total.info # Generate full HTML report -genhtml total.cleaned.info --output-directory unified-coverage --quiet --title "Unified Coverage Report" +genhtml total.info \ + --output-directory unified-coverage \ + --quiet \ + --title "Unified Coverage Report" \ + --prefix "$(pwd)" + +# Generate Cobertura XML (for Azure DevOps Code Coverage tab) +lcov_cobertura total.info --output coverage.xml + +# Generate plain-text summary for PR comment +lcov --summary total.info > unified-coverage/summary.txt + +# Append link to artifact (for GH Actions) +echo "" >> unified-coverage/summary.txt +echo "👉 [Download full HTML report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}#artifacts)" >> unified-coverage/summary.txt + +echo "[SUCCESS] Unified coverage report generated:" +echo " - HTML: unified-coverage/index.html" +echo " - Cobertura XML: coverage.xml" \ No newline at end of file From 2173309c34b60f54b28b2faa789ffe8a43f402e8 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 17:44:16 +0530 Subject: [PATCH 16/39] refactor --- generate_codecov.sh | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/generate_codecov.sh b/generate_codecov.sh index 65bc3fd6..d9c69018 100644 --- a/generate_codecov.sh +++ b/generate_codecov.sh @@ -97,19 +97,7 @@ sed -i "s|$(pwd)/||g" total.info genhtml total.info \ --output-directory unified-coverage \ --quiet \ - --title "Unified Coverage Report" \ - --prefix "$(pwd)" + --title "Unified Coverage Report" # Generate Cobertura XML (for Azure DevOps Code Coverage tab) lcov_cobertura total.info --output coverage.xml - -# Generate plain-text summary for PR comment -lcov --summary total.info > unified-coverage/summary.txt - -# Append link to artifact (for GH Actions) -echo "" >> unified-coverage/summary.txt -echo "👉 [Download full HTML report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}#artifacts)" >> unified-coverage/summary.txt - -echo "[SUCCESS] Unified coverage report generated:" -echo " - HTML: unified-coverage/index.html" -echo " - Cobertura XML: coverage.xml" \ No newline at end of file From 054c0988bd2e89c9ee8f6bb3eb4c0edfd73621d3 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 18:01:20 +0530 Subject: [PATCH 17/39] added a nice GH comment --- .github/workflows/pr-code-coverage.yml | 75 ++++++++++++++++++++++-- eng/pipelines/pr-validation-pipeline.yml | 17 ------ 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index e36eac96..5cf3779e 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -35,6 +35,7 @@ jobs: if [[ "$RESULT" == "succeeded" ]]; then echo "✅ Build $BUILD_ID succeeded: $WEB_URL" echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV + echo "BUILD_ID=$BUILD_ID" >> $GITHUB_ENV break else echo "❌ Build $BUILD_ID failed" @@ -47,10 +48,76 @@ jobs: sleep 20 done - - name: Comment ADO coverage link on PR + - name: Download and parse coverage report + run: | + BUILD_ID=${{ env.BUILD_ID }} + ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" + + echo "📥 Fetching artifacts for build $BUILD_ID..." + ARTIFACTS=$(curl -s "$ARTIFACTS_URL") + + # Find the coverage report artifact + COVERAGE_ARTIFACT=$(echo "$ARTIFACTS" | jq -r '.value[] | select(.name | test("Code Coverage Report")) | .resource.downloadUrl') + + if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" ]]; then + echo "📊 Downloading coverage report..." + curl -L "$COVERAGE_ARTIFACT" -o coverage-report.zip + unzip -q coverage-report.zip + + # Find the main index.html file + INDEX_FILE=$(find . -name "index.html" -path "*/Code Coverage Report*" | head -1) + + if [[ -f "$INDEX_FILE" ]]; then + echo "🔍 Parsing coverage data from $INDEX_FILE..." + + # Extract coverage metrics using grep and sed + OVERALL_PERCENTAGE=$(grep -o 'card-header">Line coverage.*cardpercentagebar[0-9]*">[0-9]*%' "$INDEX_FILE" | grep -o '[0-9]*%' | head -1) + COVERED_LINES=$(grep -o 'Covered lines:.*title="[0-9]*"' "$INDEX_FILE" | grep -o '[0-9]*' | tail -1) + TOTAL_LINES=$(grep -o 'Coverable lines:.*title="[0-9]*"' "$INDEX_FILE" | grep -o '[0-9]*' | tail -1) + + echo "COVERAGE_PERCENTAGE=$OVERALL_PERCENTAGE" >> $GITHUB_ENV + echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV + echo "TOTAL_LINES=$TOTAL_LINES" >> $GITHUB_ENV + + # Extract top files with low coverage + echo "📋 Extracting file-level coverage..." + LOW_COVERAGE_FILES=$(grep -o '[^<]*[0-9]*[0-9]*[0-9]*[0-9]*[0-9]*\.[0-9]*%' "$INDEX_FILE" | sed 's/\([^<]*\)<\/a><\/td>[^<]*<\/td>[^<]*<\/td>[^<]*<\/td>[^<]*<\/td>\([0-9]*\.[0-9]*\)%/\1: \2%/' | sort -t: -k2 -n | head -5) + + echo "LOW_COVERAGE_FILES<> $GITHUB_ENV + echo "$LOW_COVERAGE_FILES" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + echo "✅ Coverage data extracted successfully" + else + echo "❌ Could not find index.html in coverage report" + exit 1 + fi + else + echo "❌ Could not find coverage report artifact" + exit 1 + fi + + - name: Comment coverage summary on PR uses: marocchino/sticky-pull-request-comment@v2 with: - header: Azure DevOps Coverage Report + header: Code Coverage Report message: | - ✅ Azure DevOps build completed successfully. - 👉 [View Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) \ No newline at end of file + ## 📊 Code Coverage Report + + **Overall Coverage:** ${{ env.COVERAGE_PERCENTAGE }} (${{ env.COVERED_LINES }}/${{ env.TOTAL_LINES }} lines) + + ### 📈 Coverage Status + ${{ env.COVERAGE_PERCENTAGE >= '80' && '🟢 **Good Coverage**' || env.COVERAGE_PERCENTAGE >= '60' && '🟡 **Moderate Coverage**' || '🔴 **Low Coverage**' }} + + ### 📋 Files Needing Attention +
+ Files with lowest coverage (click to expand) + + ``` + ${{ env.LOW_COVERAGE_FILES }} + ``` +
+ + --- + ✅ **Azure DevOps Build:** [View Full Report](${{ env.ADO_URL }}&view=codecoverage-tab) + 📊 **Detailed Coverage:** [Browse Coverage Details](${{ env.ADO_URL }}&view=codecoverage-tab) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index e9960045..98b81722 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -1553,20 +1553,3 @@ jobs: reportDirectory: 'unified-coverage' failIfCoverageEmpty: true displayName: 'Publish unified code coverage results' - - - task: PublishBuildArtifacts@1 - condition: succeededOrFailed() - inputs: - PathtoPublish: 'unified-coverage' - ArtifactName: 'UnifiedCoverageHTML' - publishLocation: 'Container' - displayName: 'Publish unified coverage HTML report' - - - task: PublishBuildArtifacts@1 - condition: succeededOrFailed() - inputs: - PathtoPublish: 'total.info' - ArtifactName: 'UnifiedCoverageLCOV' - publishLocation: 'Container' - displayName: 'Publish unified coverage LCOV data' - continueOnError: true From 014099db976ddefe433dc0db3c62c6da04b0d000 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 19:14:12 +0530 Subject: [PATCH 18/39] added a nice GH comment --- .github/workflows/pr-code-coverage.yml | 45 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 5cf3779e..4eea9733 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -70,18 +70,46 @@ jobs: if [[ -f "$INDEX_FILE" ]]; then echo "🔍 Parsing coverage data from $INDEX_FILE..." - # Extract coverage metrics using grep and sed - OVERALL_PERCENTAGE=$(grep -o 'card-header">Line coverage.*cardpercentagebar[0-9]*">[0-9]*%' "$INDEX_FILE" | grep -o '[0-9]*%' | head -1) - COVERED_LINES=$(grep -o 'Covered lines:.*title="[0-9]*"' "$INDEX_FILE" | grep -o '[0-9]*' | tail -1) - TOTAL_LINES=$(grep -o 'Coverable lines:.*title="[0-9]*"' "$INDEX_FILE" | grep -o '[0-9]*' | tail -1) + # Debug: Show relevant parts of the HTML + echo "Debug: Looking for coverage data..." + grep -n "cardpercentagebar\|Covered lines\|Coverable lines" "$INDEX_FILE" | head -10 + + # Extract coverage metrics using simpler, more reliable patterns + OVERALL_PERCENTAGE=$(grep -o 'cardpercentagebar[0-9]*">[0-9]*%' "$INDEX_FILE" | head -1 | grep -o '[0-9]*%') + COVERED_LINES=$(grep -A1 "Covered lines:" "$INDEX_FILE" | grep -o 'title="[0-9]*"' | head -1 | grep -o '[0-9]*') + TOTAL_LINES=$(grep -A1 "Coverable lines:" "$INDEX_FILE" | grep -o 'title="[0-9]*"' | head -1 | grep -o '[0-9]*') + + # Fallback method if the above doesn't work + if [[ -z "$OVERALL_PERCENTAGE" ]]; then + echo "Trying alternative parsing method..." + OVERALL_PERCENTAGE=$(grep -o 'large.*">[0-9]*%' "$INDEX_FILE" | head -1 | grep -o '[0-9]*%') + fi + + echo "Extracted values:" + echo "OVERALL_PERCENTAGE=$OVERALL_PERCENTAGE" + echo "COVERED_LINES=$COVERED_LINES" + echo "TOTAL_LINES=$TOTAL_LINES" echo "COVERAGE_PERCENTAGE=$OVERALL_PERCENTAGE" >> $GITHUB_ENV echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV echo "TOTAL_LINES=$TOTAL_LINES" >> $GITHUB_ENV - # Extract top files with low coverage + # Extract top files with low coverage - improved approach echo "📋 Extracting file-level coverage..." - LOW_COVERAGE_FILES=$(grep -o '
[^<]*[0-9]*[0-9]*[0-9]*[0-9]*[0-9]*\.[0-9]*%' "$INDEX_FILE" | sed 's/\([^<]*\)<\/a><\/td>[^<]*<\/td>[^<]*<\/td>[^<]*<\/td>[^<]*<\/td>\([0-9]*\.[0-9]*\)%/\1: \2%/' | sort -t: -k2 -n | head -5) + + # Extract file coverage data more reliably + LOW_COVERAGE_FILES=$(grep -o '[^<]*[0-9]*[0-9]*[0-9]*[0-9]*[0-9]*\.[0-9]*%' "$INDEX_FILE" | \ + sed 's/\([^<]*\)<\/a><\/td>.*class="right">\([0-9]*\.[0-9]*\)%/\1: \2%/' | \ + sort -t: -k2 -n | head -5) + + # Alternative method if above fails + if [[ -z "$LOW_COVERAGE_FILES" ]]; then + echo "Trying alternative file parsing..." + LOW_COVERAGE_FILES=$(grep -E "\.py.*[0-9]+\.[0-9]+%" "$INDEX_FILE" | \ + grep -o "[^>]*\.py[^<]*.*[0-9]*\.[0-9]*%" | \ + sed 's/\([^<]*\)<\/a>.*\([0-9]*\.[0-9]*\)%/\1: \2%/' | \ + sort -t: -k2 -n | head -5) + fi echo "LOW_COVERAGE_FILES<> $GITHUB_ENV echo "$LOW_COVERAGE_FILES" >> $GITHUB_ENV @@ -106,10 +134,7 @@ jobs: **Overall Coverage:** ${{ env.COVERAGE_PERCENTAGE }} (${{ env.COVERED_LINES }}/${{ env.TOTAL_LINES }} lines) - ### 📈 Coverage Status - ${{ env.COVERAGE_PERCENTAGE >= '80' && '🟢 **Good Coverage**' || env.COVERAGE_PERCENTAGE >= '60' && '🟡 **Moderate Coverage**' || '🔴 **Low Coverage**' }} - - ### 📋 Files Needing Attention + ### Files Needing Attention
Files with lowest coverage (click to expand) From 39cbd9335f58b4232fa2c7b7360f5e75111262cb Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 19:44:41 +0530 Subject: [PATCH 19/39] comment is prettier now --- .github/workflows/pr-code-coverage.yml | 47 ++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 4eea9733..b58810bd 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -130,19 +130,52 @@ jobs: with: header: Code Coverage Report message: | - ## 📊 Code Coverage Report + # 📊 Code Coverage Report - **Overall Coverage:** ${{ env.COVERAGE_PERCENTAGE }} (${{ env.COVERED_LINES }}/${{ env.TOTAL_LINES }} lines) + + + + + + +
- ### Files Needing Attention + ### 🎯 Coverage + ## **${{ env.COVERAGE_PERCENTAGE }}** + + + + **📈 Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` + **📁 Project:** `mssql-python` + **🔍 Parser:** `Cobertura` + +
+ + --- + + ### 📋 Files Needing Attention
- Files with lowest coverage (click to expand) + 📉 Files with lowest coverage (click to expand) +
- ``` + ```diff ${{ env.LOW_COVERAGE_FILES }} ``` + + > 💡 **Tip:** Focus on improving coverage for these files to boost overall project health +
--- - ✅ **Azure DevOps Build:** [View Full Report](${{ env.ADO_URL }}&view=codecoverage-tab) - 📊 **Detailed Coverage:** [Browse Coverage Details](${{ env.ADO_URL }}&view=codecoverage-tab) + +
+ + ### 🔗 Quick Links + + | � **Build Status** | 📊 **Coverage Details** | + |:---:|:---:| + | [View Azure DevOps Build](${{ env.ADO_URL }}) | [Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) | + +
+ + 🤖 This report was automatically generated from Azure DevOps build artifacts From 67de4282f73366da67d747a4512802d30e9cd526 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 17 Sep 2025 20:41:27 +0530 Subject: [PATCH 20/39] added graceful handling of failures --- .github/workflows/pr-code-coverage.yml | 91 +++++++++++++++++++++----- 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index b58810bd..5e3f6643 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -24,12 +24,29 @@ jobs: echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." for i in {1..100}; do - BUILD_INFO=$(curl -s "$API_URL" | jq -c --arg PR "$PR_NUMBER" '[.value[] | select(.triggerInfo["pr.number"]==$PR)][0] // empty') - if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" ]]; then - STATUS=$(echo "$BUILD_INFO" | jq -r .status) - RESULT=$(echo "$BUILD_INFO" | jq -r .result) - BUILD_ID=$(echo "$BUILD_INFO" | jq -r .id) - WEB_URL=$(echo "$BUILD_INFO" | jq -r ._links.web.href) + echo "Attempt $i/100: Checking build status..." + + # Fetch API response with error handling + API_RESPONSE=$(curl -s "$API_URL") + + # Check if response is valid JSON + if ! echo "$API_RESPONSE" | jq . >/dev/null 2>&1; then + echo "❌ Invalid JSON response from Azure DevOps API" + echo "Response received: $API_RESPONSE" + echo "This usually indicates the Azure DevOps pipeline has failed or API is unavailable" + exit 1 + fi + + # Parse build info safely + BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" '[.value[]? | select(.triggerInfo["pr.number"]?==$PR)] | .[0] // empty' 2>/dev/null) + + if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" && "$BUILD_INFO" != "empty" ]]; then + STATUS=$(echo "$BUILD_INFO" | jq -r '.status // "unknown"') + RESULT=$(echo "$BUILD_INFO" | jq -r '.result // "unknown"') + BUILD_ID=$(echo "$BUILD_INFO" | jq -r '.id // "unknown"') + WEB_URL=$(echo "$BUILD_INFO" | jq -r '._links.web.href // "unknown"') + + echo "Found build: ID=$BUILD_ID, Status=$STATUS, Result=$RESULT" if [[ "$STATUS" == "completed" ]]; then if [[ "$RESULT" == "succeeded" ]]; then @@ -38,13 +55,24 @@ jobs: echo "BUILD_ID=$BUILD_ID" >> $GITHUB_ENV break else - echo "❌ Build $BUILD_ID failed" + echo "❌ Azure DevOps build $BUILD_ID failed with result: $RESULT" + echo "🔗 Build URL: $WEB_URL" + echo "This coverage workflow cannot proceed when the main build fails." exit 1 fi + else + echo "⏳ Build $BUILD_ID is still $STATUS..." fi + else + echo "⏳ No build found for PR #$PR_NUMBER yet... (attempt $i/100)" + fi + + if [[ $i -eq 100 ]]; then + echo "❌ Timeout: No build found for PR #$PR_NUMBER after 100 attempts" + echo "This may indicate the Azure DevOps pipeline was not triggered or failed to start" + exit 1 fi - echo "⏳ Build not ready yet... retrying in 20s" sleep 20 done @@ -54,15 +82,34 @@ jobs: ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" echo "📥 Fetching artifacts for build $BUILD_ID..." - ARTIFACTS=$(curl -s "$ARTIFACTS_URL") + + # Fetch artifacts with error handling + ARTIFACTS_RESPONSE=$(curl -s "$ARTIFACTS_URL") + + # Check if response is valid JSON + if ! echo "$ARTIFACTS_RESPONSE" | jq . >/dev/null 2>&1; then + echo "❌ Invalid JSON response from artifacts API" + echo "Response received: $ARTIFACTS_RESPONSE" + echo "This indicates the Azure DevOps build may not have completed successfully or artifacts are not available" + exit 1 + fi # Find the coverage report artifact - COVERAGE_ARTIFACT=$(echo "$ARTIFACTS" | jq -r '.value[] | select(.name | test("Code Coverage Report")) | .resource.downloadUrl') + COVERAGE_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("Code Coverage Report")) | .resource.downloadUrl // empty' 2>/dev/null) - if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" ]]; then + if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" && "$COVERAGE_ARTIFACT" != "empty" ]]; then echo "📊 Downloading coverage report..." - curl -L "$COVERAGE_ARTIFACT" -o coverage-report.zip - unzip -q coverage-report.zip + if ! curl -L "$COVERAGE_ARTIFACT" -o coverage-report.zip --fail --silent; then + echo "❌ Failed to download coverage report from Azure DevOps" + echo "This indicates the coverage artifacts may not be available or accessible" + exit 1 + fi + + if ! unzip -q coverage-report.zip; then + echo "❌ Failed to extract coverage report zip file" + echo "The downloaded artifact may be corrupted" + exit 1 + fi # Find the main index.html file INDEX_FILE=$(find . -name "index.html" -path "*/Code Coverage Report*" | head -1) @@ -90,9 +137,16 @@ jobs: echo "COVERED_LINES=$COVERED_LINES" echo "TOTAL_LINES=$TOTAL_LINES" + # Validate that we got the essential data + if [[ -z "$OVERALL_PERCENTAGE" ]]; then + echo "❌ Could not extract coverage percentage from the report" + echo "The coverage report format may have changed or be incomplete" + exit 1 + fi + echo "COVERAGE_PERCENTAGE=$OVERALL_PERCENTAGE" >> $GITHUB_ENV - echo "COVERED_LINES=$COVERED_LINES" >> $GITHUB_ENV - echo "TOTAL_LINES=$TOTAL_LINES" >> $GITHUB_ENV + echo "COVERED_LINES=${COVERED_LINES:-N/A}" >> $GITHUB_ENV + echo "TOTAL_LINES=${TOTAL_LINES:-N/A}" >> $GITHUB_ENV # Extract top files with low coverage - improved approach echo "📋 Extracting file-level coverage..." @@ -112,16 +166,21 @@ jobs: fi echo "LOW_COVERAGE_FILES<> $GITHUB_ENV - echo "$LOW_COVERAGE_FILES" >> $GITHUB_ENV + echo "${LOW_COVERAGE_FILES:-No detailed file data available}" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV echo "✅ Coverage data extracted successfully" else echo "❌ Could not find index.html in coverage report" + echo "Available files in the coverage report:" + find . -name "*.html" | head -10 || echo "No HTML files found" exit 1 fi else echo "❌ Could not find coverage report artifact" + echo "Available artifacts from the build:" + echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"' 2>/dev/null || echo "Could not parse artifacts list" + echo "This indicates the Azure DevOps build may not have generated coverage reports" exit 1 fi From 8e5c604574b58b25515850bcccdcd1901fb036a0 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Thu, 18 Sep 2025 17:30:50 +0530 Subject: [PATCH 21/39] comment final --- .github/workflows/pr-code-coverage.yml | 48 +++++++++++++++----------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 5e3f6643..96abd842 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -154,7 +154,7 @@ jobs: # Extract file coverage data more reliably LOW_COVERAGE_FILES=$(grep -o '[^<]*[0-9]*[0-9]*[0-9]*[0-9]*[0-9]*\.[0-9]*%' "$INDEX_FILE" | \ sed 's/\([^<]*\)<\/a><\/td>.*class="right">\([0-9]*\.[0-9]*\)%/\1: \2%/' | \ - sort -t: -k2 -n | head -5) + sort -t: -k2 -n | head -10) # Alternative method if above fails if [[ -z "$LOW_COVERAGE_FILES" ]]; then @@ -162,7 +162,7 @@ jobs: LOW_COVERAGE_FILES=$(grep -E "\.py.*[0-9]+\.[0-9]+%" "$INDEX_FILE" | \ grep -o "[^>]*\.py[^<]*.*[0-9]*\.[0-9]*%" | \ sed 's/\([^<]*\)<\/a>.*\([0-9]*\.[0-9]*\)%/\1: \2%/' | \ - sort -t: -k2 -n | head -5) + sort -t: -k2 -n | head -10) fi echo "LOW_COVERAGE_FILES<> $GITHUB_ENV @@ -193,18 +193,16 @@ jobs: - - @@ -221,20 +219,30 @@ jobs: ${{ env.LOW_COVERAGE_FILES }} ``` - > 💡 **Tip:** Focus on improving coverage for these files to boost overall project health - --- - -
- ### 🔗 Quick Links - - | � **Build Status** | 📊 **Coverage Details** | - |:---:|:---:| - | [View Azure DevOps Build](${{ env.ADO_URL }}) | [Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) | - -
- - 🤖 This report was automatically generated from Azure DevOps build artifacts + +
+ ### 🎯 Coverage - ## **${{ env.COVERAGE_PERCENTAGE }}** - + ### **${{ env.COVERAGE_PERCENTAGE }}** +
**📈 Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` **📁 Project:** `mssql-python` - **🔍 Parser:** `Cobertura`
+ + + + + + + + +
+ ⚙️ Build Summary + + 📋 Coverage Details +
+ + [View Azure DevOps Build](${{ env.ADO_URL }}) + + + + [Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) + +
From d8444732e9cb50337f66c9233a0f1160a9b9c5d4 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Thu, 18 Sep 2025 17:44:08 +0530 Subject: [PATCH 22/39] trying patch coverage --- .github/workflows/pr-code-coverage.yml | 122 ++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 96abd842..b8a8d0ca 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -184,6 +184,107 @@ jobs: exit 1 fi + # - name: Comment coverage summary on PR + # uses: marocchino/sticky-pull-request-comment@v2 + # with: + # header: Code Coverage Report + # message: | + # # 📊 Code Coverage Report + + # + # + # + # + # + #
+ + # ### 🎯 Coverage + # ### **${{ env.COVERAGE_PERCENTAGE }}** + #
+ #
+ + # **📈 Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` + # **📁 Project:** `mssql-python` + + #
+ + # --- + + # ### 📋 Files Needing Attention + #
+ # 📉 Files with lowest coverage (click to expand) + #
+ + # ```diff + # ${{ env.LOW_COVERAGE_FILES }} + # ``` + + #
+ + # --- + # ### 🔗 Quick Links + + # + # + # + # + # + # + # + # + # + #
+ # ⚙️ Build Summary + # + # 📋 Coverage Details + #
+ + # [View Azure DevOps Build](${{ env.ADO_URL }}) + + # + + # [Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) + + #
+ + - name: Download coverage XML from ADO + run: | + # Download the Cobertura XML directly instead of HTML + BUILD_ID=${{ env.BUILD_ID }} + ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" + + # Find and download coverage.xml + ARTIFACTS_RESPONSE=$(curl -s "$ARTIFACTS_URL") + COVERAGE_XML_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("coverage")) | .resource.downloadUrl // empty' 2>/dev/null) + + if [[ -n "$COVERAGE_XML_ARTIFACT" ]]; then + curl -L "$COVERAGE_XML_ARTIFACT" -o coverage-artifacts.zip + unzip -q coverage-artifacts.zip + find . -name "coverage*.xml" -exec cp {} ./coverage.xml \; + fi + + - name: Generate patch coverage report + run: | + # Install diff-cover and jq + pip install diff-cover jq + # Generate diff coverage report comparing against main + diff-cover coverage.xml \ + --compare-branch=origin/main \ + --html-report=patch-coverage.html \ + --json-report=patch-coverage.json \ + --markdown-report=patch-coverage.md + + # Extract patch coverage percentage + PATCH_COVERAGE=$(jq -r '.total_percent_covered // "N/A"' patch-coverage.json) + echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + + # Extract summary for comment + if [[ -f patch-coverage.md ]]; then + echo "PATCH_COVERAGE_SUMMARY<> $GITHUB_ENV + cat patch-coverage.md >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + fi + - name: Comment coverage summary on PR uses: marocchino/sticky-pull-request-comment@v2 with: @@ -195,10 +296,16 @@ jobs: - ### 🎯 Coverage + ### 🎯 Overall Coverage ### **${{ env.COVERAGE_PERCENTAGE }}**
+ + + ### 🔥 Patch Coverage + ### **${{ env.PATCH_COVERAGE_PCT }}** +
+ **📈 Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` @@ -211,6 +318,17 @@ jobs: --- ### 📋 Files Needing Attention + +
+ 🎯 Patch Coverage Details (lines changed in this PR) +
+ + > **Patch Coverage** shows the percentage of **newly added or modified lines** that are covered by tests. + + ${{ env.PATCH_COVERAGE_SUMMARY }} + +
+
📉 Files with lowest coverage (click to expand)
@@ -245,4 +363,4 @@ jobs: - + \ No newline at end of file From 6ad50873f5eb17cd9cbc00ff682110970a90d59b Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:23:55 +0530 Subject: [PATCH 23/39] fix xml search and dont trigger PR validation anymore --- .github/workflows/pr-code-coverage.yml | 118 ++++++++++++++++++++--- eng/pipelines/pr-validation-pipeline.yml | 8 +- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index b8a8d0ca..abb2bbcd 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -249,40 +249,130 @@ jobs: - name: Download coverage XML from ADO run: | - # Download the Cobertura XML directly instead of HTML + # Download the Cobertura XML directly from the CodeCoverageReport job BUILD_ID=${{ env.BUILD_ID }} ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" - # Find and download coverage.xml + echo "📥 Fetching artifacts for build $BUILD_ID to find coverage files..." + + # Fetch artifacts with error handling ARTIFACTS_RESPONSE=$(curl -s "$ARTIFACTS_URL") - COVERAGE_XML_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("coverage")) | .resource.downloadUrl // empty' 2>/dev/null) - if [[ -n "$COVERAGE_XML_ARTIFACT" ]]; then - curl -L "$COVERAGE_XML_ARTIFACT" -o coverage-artifacts.zip - unzip -q coverage-artifacts.zip - find . -name "coverage*.xml" -exec cp {} ./coverage.xml \; + # Check if response is valid JSON + if ! echo "$ARTIFACTS_RESPONSE" | jq . >/dev/null 2>&1; then + echo "❌ Invalid JSON response from artifacts API" + echo "Response received: $ARTIFACTS_RESPONSE" + exit 1 + fi + + echo "🔍 Available artifacts:" + echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"' + + # Look for the unified coverage artifact from CodeCoverageReport job + COVERAGE_XML_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("unified-coverage|Code Coverage Report|coverage")) | .resource.downloadUrl // empty' 2>/dev/null | head -1) + + if [[ -n "$COVERAGE_XML_ARTIFACT" && "$COVERAGE_XML_ARTIFACT" != "null" && "$COVERAGE_XML_ARTIFACT" != "empty" ]]; then + echo "📊 Downloading coverage artifact from: $COVERAGE_XML_ARTIFACT" + if ! curl -L "$COVERAGE_XML_ARTIFACT" -o coverage-artifacts.zip --fail --silent; then + echo "❌ Failed to download coverage artifacts" + exit 1 + fi + + if ! unzip -q coverage-artifacts.zip; then + echo "❌ Failed to extract coverage artifacts" + exit 1 + fi + + echo "🔍 Looking for coverage XML files in extracted artifacts..." + find . -name "*.xml" -type f | head -10 + + # Look for the main coverage.xml file in unified-coverage directory or any coverage XML + if [[ -f "unified-coverage/coverage.xml" ]]; then + echo "✅ Found unified coverage file at unified-coverage/coverage.xml" + cp "unified-coverage/coverage.xml" ./coverage.xml + elif [[ -f "coverage.xml" ]]; then + echo "✅ Found coverage.xml in root directory" + # Already in the right place + else + # Try to find any coverage XML file + COVERAGE_FILE=$(find . -name "*coverage*.xml" -type f | head -1) + if [[ -n "$COVERAGE_FILE" ]]; then + echo "✅ Found coverage file: $COVERAGE_FILE" + cp "$COVERAGE_FILE" ./coverage.xml + else + echo "❌ No coverage XML file found in artifacts" + echo "Available files:" + find . -name "*.xml" -type f + exit 1 + fi + fi + + echo "✅ Coverage XML file is ready at ./coverage.xml" + ls -la ./coverage.xml + else + echo "❌ Could not find coverage artifacts" + echo "This indicates the Azure DevOps CodeCoverageReport job may not have run successfully" + exit 1 fi - name: Generate patch coverage report run: | - # Install diff-cover and jq + # Install dependencies pip install diff-cover jq - # Generate diff coverage report comparing against main + sudo apt-get update && sudo apt-get install -y libxml2-utils + + # Verify coverage.xml exists before proceeding + if [[ ! -f coverage.xml ]]; then + echo "❌ coverage.xml not found in current directory" + echo "Available files:" + ls -la | head -20 + exit 1 + fi + + echo "✅ coverage.xml found, size: $(wc -c < coverage.xml) bytes" + echo "🔍 Coverage file preview (first 10 lines):" + head -10 coverage.xml + + # Generate diff coverage report using the new command format + echo "🚀 Generating patch coverage report..." + + # Use the new format for diff-cover commands diff-cover coverage.xml \ --compare-branch=origin/main \ - --html-report=patch-coverage.html \ - --json-report=patch-coverage.json \ - --markdown-report=patch-coverage.md + --format html:patch-coverage.html \ + --format json:patch-coverage.json \ + --format markdown:patch-coverage.md || { + echo "❌ diff-cover failed with exit code $?" + echo "Checking if coverage.xml is valid XML..." + if ! xmllint --noout coverage.xml 2>/dev/null; then + echo "❌ coverage.xml is not valid XML" + echo "First 50 lines of coverage.xml:" + head -50 coverage.xml + else + echo "✅ coverage.xml is valid XML" + fi + exit 1 + } # Extract patch coverage percentage - PATCH_COVERAGE=$(jq -r '.total_percent_covered // "N/A"' patch-coverage.json) - echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + if [[ -f patch-coverage.json ]]; then + PATCH_COVERAGE=$(jq -r '.total_percent_covered // "N/A"' patch-coverage.json) + echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + echo "✅ Patch coverage: ${PATCH_COVERAGE}%" + else + echo "⚠️ patch-coverage.json not generated, setting default" + echo "PATCH_COVERAGE_PCT=N/A" >> $GITHUB_ENV + fi # Extract summary for comment if [[ -f patch-coverage.md ]]; then echo "PATCH_COVERAGE_SUMMARY<> $GITHUB_ENV cat patch-coverage.md >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + echo "✅ Patch coverage markdown summary ready" + else + echo "⚠️ patch-coverage.md not generated" + echo "PATCH_COVERAGE_SUMMARY=Patch coverage report could not be generated." >> $GITHUB_ENV fi - name: Comment coverage summary on PR diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 98b81722..315bb0a1 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -1,10 +1,10 @@ name: pr-validation-pipeline # Trigger the pipeline on merge to main branch -trigger: - branches: - include: - - main +# trigger: +# branches: +# include: +# - main jobs: - job: PytestOnWindows From 45747b01056548b9b568a1c0a27441df18a7f487 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:26:29 +0530 Subject: [PATCH 24/39] undo trigger PR validation and use a specified build for code coevrage task --- .github/workflows/pr-code-coverage.yml | 3 ++- eng/pipelines/pr-validation-pipeline.yml | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index abb2bbcd..6c14c0a7 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -18,7 +18,8 @@ jobs: - name: Wait for ADO build to succeed run: | - PR_NUMBER=${{ github.event.pull_request.number }} + # PR_NUMBER=${{ github.event.pull_request.number }} + PR_NUMBER=244 API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&queryOrder=queueTimeDescending&%24top=10&api-version=7.1-preview.7" echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 315bb0a1..98b81722 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -1,10 +1,10 @@ name: pr-validation-pipeline # Trigger the pipeline on merge to main branch -# trigger: -# branches: -# include: -# - main +trigger: + branches: + include: + - main jobs: - job: PytestOnWindows From 6e0336985219e2a2915fa21b035028a5413555b8 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:29:55 +0530 Subject: [PATCH 25/39] specific build id --- .github/workflows/pr-code-coverage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 6c14c0a7..42b2b196 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -79,7 +79,8 @@ jobs: - name: Download and parse coverage report run: | - BUILD_ID=${{ env.BUILD_ID }} + # BUILD_ID=${{ env.BUILD_ID }} + BUILD_ID=125342 ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" echo "📥 Fetching artifacts for build $BUILD_ID..." From 66139e0042c0ac2103a0163b70bc792e4fe79640 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:30:22 +0530 Subject: [PATCH 26/39] specific build id --- .github/workflows/pr-code-coverage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 42b2b196..e8fd2d4b 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -252,7 +252,8 @@ jobs: - name: Download coverage XML from ADO run: | # Download the Cobertura XML directly from the CodeCoverageReport job - BUILD_ID=${{ env.BUILD_ID }} + # BUILD_ID=${{ env.BUILD_ID }} + BUILD_ID=125342 ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" echo "📥 Fetching artifacts for build $BUILD_ID to find coverage files..." From b3329a0ed108acd795eb15bda7520731896a311b Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:31:35 +0530 Subject: [PATCH 27/39] specific build id --- .github/workflows/pr-code-coverage.yml | 107 ++++++++++++------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index e8fd2d4b..c1468a32 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -16,66 +16,65 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 - - name: Wait for ADO build to succeed - run: | - # PR_NUMBER=${{ github.event.pull_request.number }} - PR_NUMBER=244 - API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&queryOrder=queueTimeDescending&%24top=10&api-version=7.1-preview.7" + # - name: Wait for ADO build to succeed + # run: | + # PR_NUMBER=${{ github.event.pull_request.number }} + # API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&queryOrder=queueTimeDescending&%24top=10&api-version=7.1-preview.7" - echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." + # echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." - for i in {1..100}; do - echo "Attempt $i/100: Checking build status..." - - # Fetch API response with error handling - API_RESPONSE=$(curl -s "$API_URL") - - # Check if response is valid JSON - if ! echo "$API_RESPONSE" | jq . >/dev/null 2>&1; then - echo "❌ Invalid JSON response from Azure DevOps API" - echo "Response received: $API_RESPONSE" - echo "This usually indicates the Azure DevOps pipeline has failed or API is unavailable" - exit 1 - fi - - # Parse build info safely - BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" '[.value[]? | select(.triggerInfo["pr.number"]?==$PR)] | .[0] // empty' 2>/dev/null) - - if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" && "$BUILD_INFO" != "empty" ]]; then - STATUS=$(echo "$BUILD_INFO" | jq -r '.status // "unknown"') - RESULT=$(echo "$BUILD_INFO" | jq -r '.result // "unknown"') - BUILD_ID=$(echo "$BUILD_INFO" | jq -r '.id // "unknown"') - WEB_URL=$(echo "$BUILD_INFO" | jq -r '._links.web.href // "unknown"') + # for i in {1..100}; do + # echo "Attempt $i/100: Checking build status..." + + # # Fetch API response with error handling + # API_RESPONSE=$(curl -s "$API_URL") + + # # Check if response is valid JSON + # if ! echo "$API_RESPONSE" | jq . >/dev/null 2>&1; then + # echo "❌ Invalid JSON response from Azure DevOps API" + # echo "Response received: $API_RESPONSE" + # echo "This usually indicates the Azure DevOps pipeline has failed or API is unavailable" + # exit 1 + # fi + + # # Parse build info safely + # BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" '[.value[]? | select(.triggerInfo["pr.number"]?==$PR)] | .[0] // empty' 2>/dev/null) + + # if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" && "$BUILD_INFO" != "empty" ]]; then + # STATUS=$(echo "$BUILD_INFO" | jq -r '.status // "unknown"') + # RESULT=$(echo "$BUILD_INFO" | jq -r '.result // "unknown"') + # BUILD_ID=$(echo "$BUILD_INFO" | jq -r '.id // "unknown"') + # WEB_URL=$(echo "$BUILD_INFO" | jq -r '._links.web.href // "unknown"') - echo "Found build: ID=$BUILD_ID, Status=$STATUS, Result=$RESULT" + # echo "Found build: ID=$BUILD_ID, Status=$STATUS, Result=$RESULT" - if [[ "$STATUS" == "completed" ]]; then - if [[ "$RESULT" == "succeeded" ]]; then - echo "✅ Build $BUILD_ID succeeded: $WEB_URL" - echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV - echo "BUILD_ID=$BUILD_ID" >> $GITHUB_ENV - break - else - echo "❌ Azure DevOps build $BUILD_ID failed with result: $RESULT" - echo "🔗 Build URL: $WEB_URL" - echo "This coverage workflow cannot proceed when the main build fails." - exit 1 - fi - else - echo "⏳ Build $BUILD_ID is still $STATUS..." - fi - else - echo "⏳ No build found for PR #$PR_NUMBER yet... (attempt $i/100)" - fi + # if [[ "$STATUS" == "completed" ]]; then + # if [[ "$RESULT" == "succeeded" ]]; then + # echo "✅ Build $BUILD_ID succeeded: $WEB_URL" + # echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV + # echo "BUILD_ID=$BUILD_ID" >> $GITHUB_ENV + # break + # else + # echo "❌ Azure DevOps build $BUILD_ID failed with result: $RESULT" + # echo "🔗 Build URL: $WEB_URL" + # echo "This coverage workflow cannot proceed when the main build fails." + # exit 1 + # fi + # else + # echo "⏳ Build $BUILD_ID is still $STATUS..." + # fi + # else + # echo "⏳ No build found for PR #$PR_NUMBER yet... (attempt $i/100)" + # fi - if [[ $i -eq 100 ]]; then - echo "❌ Timeout: No build found for PR #$PR_NUMBER after 100 attempts" - echo "This may indicate the Azure DevOps pipeline was not triggered or failed to start" - exit 1 - fi + # if [[ $i -eq 100 ]]; then + # echo "❌ Timeout: No build found for PR #$PR_NUMBER after 100 attempts" + # echo "This may indicate the Azure DevOps pipeline was not triggered or failed to start" + # exit 1 + # fi - sleep 20 - done + # sleep 20 + # done - name: Download and parse coverage report run: | From 2b4927d8e925ece10ed02298e64e8f6bb8166e7a Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:33:14 +0530 Subject: [PATCH 28/39] unzip error --- .github/workflows/pr-code-coverage.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index c1468a32..3cc02099 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -105,10 +105,11 @@ jobs: echo "This indicates the coverage artifacts may not be available or accessible" exit 1 fi - - if ! unzip -q coverage-report.zip; then - echo "❌ Failed to extract coverage report zip file" - echo "The downloaded artifact may be corrupted" + + if ! unzip -o -q coverage-report.zip; then + echo "❌ Failed to extract coverage artifacts" + echo "Trying to extract with verbose output for debugging..." + unzip -l coverage-report.zip || echo "Failed to list archive contents" exit 1 fi @@ -280,8 +281,10 @@ jobs: exit 1 fi - if ! unzip -q coverage-artifacts.zip; then + if ! unzip -o -q coverage-artifacts.zip; then echo "❌ Failed to extract coverage artifacts" + echo "Trying to extract with verbose output for debugging..." + unzip -l coverage-artifacts.zip || echo "Failed to list archive contents" exit 1 fi From 2103aaa6e7b068f534a11a901463e5041e3ef97d Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:35:57 +0530 Subject: [PATCH 29/39] git main --- .github/workflows/pr-code-coverage.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 3cc02099..933cfe48 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -341,9 +341,18 @@ jobs: # Generate diff coverage report using the new command format echo "🚀 Generating patch coverage report..." + # Debug: Show git status and branches before running diff-cover + echo "🔍 Git status before diff-cover:" + git status --porcelain || echo "Git status failed" + echo "Current branch: $(git branch --show-current)" + echo "Available branches:" + git branch -a + echo "Checking if main branch is accessible:" + git log --oneline -n 5 main || echo "Could not access main branch" + # Use the new format for diff-cover commands diff-cover coverage.xml \ - --compare-branch=origin/main \ + --compare-branch=main \ --format html:patch-coverage.html \ --format json:patch-coverage.json \ --format markdown:patch-coverage.md || { From 7f268464c35075571d2b4f4cee23d4ed79eea462 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:39:21 +0530 Subject: [PATCH 30/39] pull all branches --- .github/workflows/pr-code-coverage.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 933cfe48..5ef84bbe 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -15,6 +15,19 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup git for diff-cover + run: | + # Fetch the main branch for comparison + git fetch origin main:main + # Show available branches for debugging + echo "Available branches:" + git branch -a + # Verify main branch exists + git show-ref --verify refs/heads/main || echo "Warning: main branch not found" + git show-ref --verify refs/remotes/origin/main || echo "Warning: origin/main not found" # - name: Wait for ADO build to succeed # run: | From 67fb0d24433cd0923623926c395973382ac31021 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 17:56:42 +0530 Subject: [PATCH 31/39] polling for artifact instead of build status --- .github/workflows/pr-code-coverage.yml | 167 ++++++++++++++----------- mssql_python/cursor.py | 9 ++ 2 files changed, 104 insertions(+), 72 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 5ef84bbe..87e58464 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -29,87 +29,111 @@ jobs: git show-ref --verify refs/heads/main || echo "Warning: main branch not found" git show-ref --verify refs/remotes/origin/main || echo "Warning: origin/main not found" - # - name: Wait for ADO build to succeed - # run: | - # PR_NUMBER=${{ github.event.pull_request.number }} - # API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&queryOrder=queueTimeDescending&%24top=10&api-version=7.1-preview.7" + - name: Wait for ADO build to start + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&queryOrder=queueTimeDescending&%24top=10&api-version=7.1-preview.7" - # echo "Waiting for Azure DevOps build for PR #$PR_NUMBER ..." + echo "Waiting for Azure DevOps build to start for PR #$PR_NUMBER ..." - # for i in {1..100}; do - # echo "Attempt $i/100: Checking build status..." - - # # Fetch API response with error handling - # API_RESPONSE=$(curl -s "$API_URL") - - # # Check if response is valid JSON - # if ! echo "$API_RESPONSE" | jq . >/dev/null 2>&1; then - # echo "❌ Invalid JSON response from Azure DevOps API" - # echo "Response received: $API_RESPONSE" - # echo "This usually indicates the Azure DevOps pipeline has failed or API is unavailable" - # exit 1 - # fi - - # # Parse build info safely - # BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" '[.value[]? | select(.triggerInfo["pr.number"]?==$PR)] | .[0] // empty' 2>/dev/null) - - # if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" && "$BUILD_INFO" != "empty" ]]; then - # STATUS=$(echo "$BUILD_INFO" | jq -r '.status // "unknown"') - # RESULT=$(echo "$BUILD_INFO" | jq -r '.result // "unknown"') - # BUILD_ID=$(echo "$BUILD_INFO" | jq -r '.id // "unknown"') - # WEB_URL=$(echo "$BUILD_INFO" | jq -r '._links.web.href // "unknown"') + for i in {1..30}; do + echo "Attempt $i/30: Checking if build has started..." + + # Fetch API response with error handling + API_RESPONSE=$(curl -s "$API_URL") + + # Check if response is valid JSON + if ! echo "$API_RESPONSE" | jq . >/dev/null 2>&1; then + echo "❌ Invalid JSON response from Azure DevOps API" + echo "Response received: $API_RESPONSE" + echo "This usually indicates the Azure DevOps pipeline has failed or API is unavailable" + exit 1 + fi + + # Parse build info safely + BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" '[.value[]? | select(.triggerInfo["pr.number"]?==$PR)] | .[0] // empty' 2>/dev/null) + + if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" && "$BUILD_INFO" != "empty" ]]; then + STATUS=$(echo "$BUILD_INFO" | jq -r '.status // "unknown"') + RESULT=$(echo "$BUILD_INFO" | jq -r '.result // "unknown"') + BUILD_ID=$(echo "$BUILD_INFO" | jq -r '.id // "unknown"') + WEB_URL=$(echo "$BUILD_INFO" | jq -r '._links.web.href // "unknown"') - # echo "Found build: ID=$BUILD_ID, Status=$STATUS, Result=$RESULT" - - # if [[ "$STATUS" == "completed" ]]; then - # if [[ "$RESULT" == "succeeded" ]]; then - # echo "✅ Build $BUILD_ID succeeded: $WEB_URL" - # echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV - # echo "BUILD_ID=$BUILD_ID" >> $GITHUB_ENV - # break - # else - # echo "❌ Azure DevOps build $BUILD_ID failed with result: $RESULT" - # echo "🔗 Build URL: $WEB_URL" - # echo "This coverage workflow cannot proceed when the main build fails." - # exit 1 - # fi - # else - # echo "⏳ Build $BUILD_ID is still $STATUS..." - # fi - # else - # echo "⏳ No build found for PR #$PR_NUMBER yet... (attempt $i/100)" - # fi + echo "✅ Found build: ID=$BUILD_ID, Status=$STATUS, Result=$RESULT" + echo "🔗 Build URL: $WEB_URL" + echo "ADO_URL=$WEB_URL" >> $GITHUB_ENV + echo "BUILD_ID=$BUILD_ID" >> $GITHUB_ENV + + # Check if build has failed early + if [[ "$STATUS" == "completed" && "$RESULT" == "failed" ]]; then + echo "❌ Azure DevOps build $BUILD_ID failed early" + echo "This coverage workflow cannot proceed when the main build fails." + exit 1 + fi + + echo "🚀 Build has started, proceeding to poll for coverage artifacts..." + break + else + echo "⏳ No build found for PR #$PR_NUMBER yet... (attempt $i/30)" + fi - # if [[ $i -eq 100 ]]; then - # echo "❌ Timeout: No build found for PR #$PR_NUMBER after 100 attempts" - # echo "This may indicate the Azure DevOps pipeline was not triggered or failed to start" - # exit 1 - # fi + if [[ $i -eq 30 ]]; then + echo "❌ Timeout: No build found for PR #$PR_NUMBER after 30 attempts" + echo "This may indicate the Azure DevOps pipeline was not triggered" + exit 1 + fi - # sleep 20 - # done + sleep 10 + done - name: Download and parse coverage report run: | - # BUILD_ID=${{ env.BUILD_ID }} - BUILD_ID=125342 + BUILD_ID=${{ env.BUILD_ID }} ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" - echo "📥 Fetching artifacts for build $BUILD_ID..." - - # Fetch artifacts with error handling - ARTIFACTS_RESPONSE=$(curl -s "$ARTIFACTS_URL") - - # Check if response is valid JSON - if ! echo "$ARTIFACTS_RESPONSE" | jq . >/dev/null 2>&1; then - echo "❌ Invalid JSON response from artifacts API" - echo "Response received: $ARTIFACTS_RESPONSE" - echo "This indicates the Azure DevOps build may not have completed successfully or artifacts are not available" - exit 1 - fi + echo "📥 Polling for coverage artifacts for build $BUILD_ID..." - # Find the coverage report artifact - COVERAGE_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("Code Coverage Report")) | .resource.downloadUrl // empty' 2>/dev/null) + # Poll for coverage artifacts with retry logic + COVERAGE_ARTIFACT="" + for i in {1..60}; do + echo "Attempt $i/60: Checking for coverage artifacts..." + + # Fetch artifacts with error handling + ARTIFACTS_RESPONSE=$(curl -s "$ARTIFACTS_URL") + + # Check if response is valid JSON + if ! echo "$ARTIFACTS_RESPONSE" | jq . >/dev/null 2>&1; then + echo "⚠️ Invalid JSON response from artifacts API (attempt $i/60)" + if [[ $i -eq 60 ]]; then + echo "❌ Persistent API issues after 60 attempts" + echo "Response received: $ARTIFACTS_RESPONSE" + exit 1 + fi + sleep 30 + continue + fi + + # Show available artifacts for debugging + echo "🔍 Available artifacts:" + echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"' + + # Find the coverage report artifact + COVERAGE_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("Code Coverage Report")) | .resource.downloadUrl // empty' 2>/dev/null) + + if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" && "$COVERAGE_ARTIFACT" != "empty" ]]; then + echo "✅ Found coverage artifact on attempt $i!" + break + else + echo "⏳ Coverage report not ready yet (attempt $i/60)..." + if [[ $i -eq 60 ]]; then + echo "❌ Timeout: Coverage report artifact not found after 60 attempts" + echo "Available artifacts:" + echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"' + exit 1 + fi + sleep 30 + fi + done if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" && "$COVERAGE_ARTIFACT" != "empty" ]]; then echo "📊 Downloading coverage report..." @@ -265,8 +289,7 @@ jobs: - name: Download coverage XML from ADO run: | # Download the Cobertura XML directly from the CodeCoverageReport job - # BUILD_ID=${{ env.BUILD_ID }} - BUILD_ID=125342 + BUILD_ID=${{ env.BUILD_ID }} ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5" echo "📥 Fetching artifacts for build $BUILD_ID to find coverage files..." diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index b6b309cf..ece74947 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -1501,6 +1501,15 @@ def _transpose_rowwise_to_columnwise(self, seq_of_parameters: list) -> tuple[lis return columnwise, row_count + def test_non_covered_method(self): + """ + A test method to ensure code coverage for methods not covered by other tests. + """ + self._check_closed() + self._reset_cursor() + self._clear_rownumber() + return True + def _compute_column_type(self, column): """ Determine representative value and integer min/max for a column. From f05ef05e4c88f1bae7c0103bc31d0073f70df274 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 18:09:17 +0530 Subject: [PATCH 32/39] test method increased, patch coverage % debugging --- .github/workflows/pr-code-coverage.yml | 66 ++++++++++++++++++++++++-- mssql_python/cursor.py | 15 ++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 87e58464..583acfc6 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -386,7 +386,22 @@ jobs: echo "Checking if main branch is accessible:" git log --oneline -n 5 main || echo "Could not access main branch" + # Debug: Show what diff-cover will analyze + echo "🔍 Git diff analysis:" + echo "Files changed between main and current branch:" + git diff --name-only main || echo "Could not get diff" + echo "Detailed diff for Python files:" + git diff main -- "*.py" | head -50 || echo "Could not get Python diff" + + # Debug: Check coverage.xml content for specific files + echo "🔍 Coverage.xml analysis:" + echo "Python files mentioned in coverage.xml:" + grep -o 'filename="[^"]*\.py"' coverage.xml | head -10 || echo "Could not extract filenames" + echo "Sample coverage data:" + head -20 coverage.xml + # Use the new format for diff-cover commands + echo "🚀 Running diff-cover..." diff-cover coverage.xml \ --compare-branch=main \ --format html:patch-coverage.html \ @@ -400,18 +415,61 @@ jobs: head -50 coverage.xml else echo "✅ coverage.xml is valid XML" + echo "🔍 diff-cover verbose output:" + diff-cover coverage.xml --compare-branch=main --format markdown:debug-patch-coverage.md -v || echo "Verbose diff-cover also failed" fi - exit 1 + # Don't exit here, let's see what files were created } + # Check what files were generated + echo "🔍 Files generated after diff-cover:" + ls -la patch-coverage.* || echo "No patch-coverage files found" + ls -la *.md *.html *.json | grep -E "(patch|coverage)" || echo "No coverage-related files found" + # Extract patch coverage percentage if [[ -f patch-coverage.json ]]; then + echo "🔍 Patch coverage analysis:" + echo "Raw JSON content:" + cat patch-coverage.json | jq . || echo "Could not parse JSON" + PATCH_COVERAGE=$(jq -r '.total_percent_covered // "N/A"' patch-coverage.json) - echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + TOTAL_STATEMENTS=$(jq -r '.total_num_lines // "N/A"' patch-coverage.json) + MISSING_STATEMENTS=$(jq -r '.total_num_missing // "N/A"' patch-coverage.json) + echo "✅ Patch coverage: ${PATCH_COVERAGE}%" + echo "📊 Total lines: $TOTAL_STATEMENTS, Missing: $MISSING_STATEMENTS" + + # Debug: Show per-file breakdown + echo "📁 Per-file coverage breakdown:" + jq -r '.src_stats // {} | to_entries[] | "\(.key): \(.value.percent_covered)% (\(.value.num_lines) lines, \(.value.num_missing) missing)"' patch-coverage.json || echo "Could not extract per-file stats" + + # Check if we actually have coverage data + if [[ "$PATCH_COVERAGE" == "null" || "$PATCH_COVERAGE" == "N/A" ]]; then + echo "⚠️ Patch coverage is null/N/A - checking why..." + if [[ "$TOTAL_STATEMENTS" == "0" || "$TOTAL_STATEMENTS" == "null" ]]; then + echo "🔍 No lines found in diff - this could mean:" + echo " 1. No Python files were changed" + echo " 2. Only non-executable lines were changed (comments, docstrings)" + echo " 3. File paths in coverage.xml don't match git diff paths" + echo "PATCH_COVERAGE_PCT=No changes detected" >> $GITHUB_ENV + else + echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + fi + else + echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + fi else - echo "⚠️ patch-coverage.json not generated, setting default" - echo "PATCH_COVERAGE_PCT=N/A" >> $GITHUB_ENV + echo "⚠️ patch-coverage.json not generated" + echo "🔍 Checking for other output files:" + if [[ -f patch-coverage.md ]]; then + echo "Found patch-coverage.md:" + cat patch-coverage.md + fi + if [[ -f debug-patch-coverage.md ]]; then + echo "Found debug output:" + cat debug-patch-coverage.md + fi + echo "PATCH_COVERAGE_PCT=Report not generated" >> $GITHUB_ENV fi # Extract summary for comment diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index ece74947..a389031d 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -1508,6 +1508,21 @@ def test_non_covered_method(self): self._check_closed() self._reset_cursor() self._clear_rownumber() + self._check_closed() + self._reset_cursor() + self._clear_rownumber() + self._check_closed() + self._reset_cursor() + self._clear_rownumber() + self._check_closed() + self._reset_cursor() + self._clear_rownumber() + self._check_closed() + self._reset_cursor() + self._clear_rownumber() + self._check_closed() + self._reset_cursor() + self._clear_rownumber() return True def _compute_column_type(self, column): From 049b6119ef360d364c09811661b8bd14766548c8 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 18:27:52 +0530 Subject: [PATCH 33/39] fix % extraction, make format better --- .github/workflows/pr-code-coverage.yml | 67 ++++++++++++-------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 583acfc6..f55e07ea 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -404,9 +404,9 @@ jobs: echo "🚀 Running diff-cover..." diff-cover coverage.xml \ --compare-branch=main \ - --format html:patch-coverage.html \ - --format json:patch-coverage.json \ - --format markdown:patch-coverage.md || { + --html-report patch-coverage.html \ + --json-report patch-coverage.json \ + --markdown-report patch-coverage.md || { echo "❌ diff-cover failed with exit code $?" echo "Checking if coverage.xml is valid XML..." if ! xmllint --noout coverage.xml 2>/dev/null; then @@ -416,7 +416,7 @@ jobs: else echo "✅ coverage.xml is valid XML" echo "🔍 diff-cover verbose output:" - diff-cover coverage.xml --compare-branch=main --format markdown:debug-patch-coverage.md -v || echo "Verbose diff-cover also failed" + diff-cover coverage.xml --compare-branch=main --markdown-report debug-patch-coverage.md -v || echo "Verbose diff-cover also failed" fi # Don't exit here, let's see what files were created } @@ -428,7 +428,7 @@ jobs: # Extract patch coverage percentage if [[ -f patch-coverage.json ]]; then - echo "🔍 Patch coverage analysis:" + echo "🔍 Patch coverage analysis from JSON:" echo "Raw JSON content:" cat patch-coverage.json | jq . || echo "Could not parse JSON" @@ -443,32 +443,29 @@ jobs: echo "📁 Per-file coverage breakdown:" jq -r '.src_stats // {} | to_entries[] | "\(.key): \(.value.percent_covered)% (\(.value.num_lines) lines, \(.value.num_missing) missing)"' patch-coverage.json || echo "Could not extract per-file stats" - # Check if we actually have coverage data - if [[ "$PATCH_COVERAGE" == "null" || "$PATCH_COVERAGE" == "N/A" ]]; then - echo "⚠️ Patch coverage is null/N/A - checking why..." - if [[ "$TOTAL_STATEMENTS" == "0" || "$TOTAL_STATEMENTS" == "null" ]]; then - echo "🔍 No lines found in diff - this could mean:" - echo " 1. No Python files were changed" - echo " 2. Only non-executable lines were changed (comments, docstrings)" - echo " 3. File paths in coverage.xml don't match git diff paths" - echo "PATCH_COVERAGE_PCT=No changes detected" >> $GITHUB_ENV - else - echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV - fi - else + echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + elif [[ -f patch-coverage.md ]]; then + echo "🔍 Extracting patch coverage from markdown file:" + echo "Markdown content:" + cat patch-coverage.md + + # Extract coverage percentage from markdown + PATCH_COVERAGE=$(grep -o "Coverage.*[0-9]*%" patch-coverage.md | grep -o "[0-9]*%" | head -1 | sed 's/%//') + TOTAL_LINES=$(grep -o "Total.*[0-9]* lines" patch-coverage.md | grep -o "[0-9]*" | head -1) + MISSING_LINES=$(grep -o "Missing.*[0-9]* lines" patch-coverage.md | grep -o "[0-9]*" | tail -1) + + if [[ -n "$PATCH_COVERAGE" ]]; then + echo "✅ Extracted patch coverage: ${PATCH_COVERAGE}%" + echo "📊 Total lines: $TOTAL_LINES, Missing: $MISSING_LINES" echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV + else + echo "⚠️ Could not extract coverage percentage from markdown" + echo "PATCH_COVERAGE_PCT=Could not parse" >> $GITHUB_ENV fi else - echo "⚠️ patch-coverage.json not generated" + echo "⚠️ No patch coverage files generated" echo "🔍 Checking for other output files:" - if [[ -f patch-coverage.md ]]; then - echo "Found patch-coverage.md:" - cat patch-coverage.md - fi - if [[ -f debug-patch-coverage.md ]]; then - echo "Found debug output:" - cat debug-patch-coverage.md - fi + ls -la *coverage* || echo "No coverage files found" echo "PATCH_COVERAGE_PCT=Report not generated" >> $GITHUB_ENV fi @@ -494,14 +491,14 @@ jobs: - ### 🎯 Overall Coverage - ### **${{ env.COVERAGE_PERCENTAGE }}** + ### 🔥 Patch Coverage + ### **${{ env.PATCH_COVERAGE_PCT }}**
- ### 🔥 Patch Coverage - ### **${{ env.PATCH_COVERAGE_PCT }}** + ### 🎯 Overall Coverage + ### **${{ env.COVERAGE_PERCENTAGE }}**
@@ -515,9 +512,8 @@ jobs: --- - ### 📋 Files Needing Attention + ## 📋 Patch Coverage Report -
🎯 Patch Coverage Details (lines changed in this PR)
@@ -525,10 +521,11 @@ jobs: ${{ env.PATCH_COVERAGE_SUMMARY }} -
+ --- + ### 📋 Files Needing Attention
- 📉 Files with lowest coverage (click to expand) + 📉 Files with overall lowest coverage (click to expand)
```diff From da0c0c5ad855a8e362e0630ffa2af2c6b629484e Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 18:29:49 +0530 Subject: [PATCH 34/39] add something in cpp --- mssql_python/pybind/ddbc_bindings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index fe8197fd..c44df6a4 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -2613,6 +2613,10 @@ SQLRETURN SQLFetchScroll_wrap(SqlHandlePtr StatementHandle, SQLSMALLINT FetchOri return ret; } +// Add a dummy function to check if code coverage tools are working +void DummyFunctionForCodeCoverage() { + LOG("This is a dummy function to ensure code coverage tools are working."); +} // For column in the result set, binds a buffer to retrieve column data // TODO: Move to anonymous namespace, since it is not used outside this file From c25030569b5bbfaa1bfb90e77298741fd36e945e Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 19:11:04 +0530 Subject: [PATCH 35/39] change name from test to dummy in cursor --- mssql_python/cursor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index a389031d..05347f21 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -1501,7 +1501,7 @@ def _transpose_rowwise_to_columnwise(self, seq_of_parameters: list) -> tuple[lis return columnwise, row_count - def test_non_covered_method(self): + def dummy_non_covered_method(self): """ A test method to ensure code coverage for methods not covered by other tests. """ From 8ad00e9707d3a5a2cd6e5b2bb15021f7193a0905 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Fri, 19 Sep 2025 19:28:17 +0530 Subject: [PATCH 36/39] remove redundant code and add a re-label check on pr-format workflow --- .github/workflows/pr-code-coverage.yml | 63 -------------------------- .github/workflows/pr-format-check.yml | 37 +++++++++------ 2 files changed, 24 insertions(+), 76 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index f55e07ea..01d89023 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -223,69 +223,6 @@ jobs: exit 1 fi - # - name: Comment coverage summary on PR - # uses: marocchino/sticky-pull-request-comment@v2 - # with: - # header: Code Coverage Report - # message: | - # # 📊 Code Coverage Report - - # - # - # - # - # - #
- - # ### 🎯 Coverage - # ### **${{ env.COVERAGE_PERCENTAGE }}** - #
- #
- - # **📈 Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` - # **📁 Project:** `mssql-python` - - #
- - # --- - - # ### 📋 Files Needing Attention - #
- # 📉 Files with lowest coverage (click to expand) - #
- - # ```diff - # ${{ env.LOW_COVERAGE_FILES }} - # ``` - - #
- - # --- - # ### 🔗 Quick Links - - # - # - # - # - # - # - # - # - # - #
- # ⚙️ Build Summary - # - # 📋 Coverage Details - #
- - # [View Azure DevOps Build](${{ env.ADO_URL }}) - - # - - # [Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) - - #
- - name: Download coverage XML from ADO run: | # Download the Cobertura XML directly from the CodeCoverageReport job diff --git a/.github/workflows/pr-format-check.yml b/.github/workflows/pr-format-check.yml index 3155eed9..55c3129d 100644 --- a/.github/workflows/pr-format-check.yml +++ b/.github/workflows/pr-format-check.yml @@ -94,24 +94,35 @@ jobs: labelToAdd = 'pr-size: large'; } - // Remove existing size labels if any + // Get existing labels const existingLabels = pr.labels.map(l => l.name); const sizeLabels = ['pr-size: small', 'pr-size: medium', 'pr-size: large']; - for (const label of existingLabels) { - if (sizeLabels.includes(label)) { + + // Find current size label (if any) + const currentSizeLabel = existingLabels.find(label => sizeLabels.includes(label)); + + // Only make changes if the label needs to be updated + if (currentSizeLabel !== labelToAdd) { + console.log(`Current size label: ${currentSizeLabel || 'none'}`); + console.log(`Required size label: ${labelToAdd} (Total changes: ${totalChanges})`); + + // Remove existing size label if different from required + if (currentSizeLabel) { + console.log(`Removing outdated label: ${currentSizeLabel}`); await github.rest.issues.removeLabel({ ...context.repo, issue_number: pr.number, - name: label, + name: currentSizeLabel, }); } - } - // Add new size label - await github.rest.issues.addLabels({ - ...context.repo, - issue_number: pr.number, - labels: [labelToAdd], - }); - - console.log(`Added label: ${labelToAdd} (Total changes: ${totalChanges})`); + // Add new size label + console.log(`Adding new label: ${labelToAdd}`); + await github.rest.issues.addLabels({ + ...context.repo, + issue_number: pr.number, + labels: [labelToAdd], + }); + } else { + console.log(`Label already correct: ${labelToAdd} (Total changes: ${totalChanges}) - no changes needed`); + } From 328b35cc1ea8156245dc8df8c855d2b227552351 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 24 Sep 2025 09:40:35 +0530 Subject: [PATCH 37/39] cleanup --- mssql_python/cursor.py | 24 ------------------------ mssql_python/pybind/ddbc_bindings.cpp | 4 ---- 2 files changed, 28 deletions(-) diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 05347f21..b6b309cf 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -1501,30 +1501,6 @@ def _transpose_rowwise_to_columnwise(self, seq_of_parameters: list) -> tuple[lis return columnwise, row_count - def dummy_non_covered_method(self): - """ - A test method to ensure code coverage for methods not covered by other tests. - """ - self._check_closed() - self._reset_cursor() - self._clear_rownumber() - self._check_closed() - self._reset_cursor() - self._clear_rownumber() - self._check_closed() - self._reset_cursor() - self._clear_rownumber() - self._check_closed() - self._reset_cursor() - self._clear_rownumber() - self._check_closed() - self._reset_cursor() - self._clear_rownumber() - self._check_closed() - self._reset_cursor() - self._clear_rownumber() - return True - def _compute_column_type(self, column): """ Determine representative value and integer min/max for a column. diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index c44df6a4..fe8197fd 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -2613,10 +2613,6 @@ SQLRETURN SQLFetchScroll_wrap(SqlHandlePtr StatementHandle, SQLSMALLINT FetchOri return ret; } -// Add a dummy function to check if code coverage tools are working -void DummyFunctionForCodeCoverage() { - LOG("This is a dummy function to ensure code coverage tools are working."); -} // For column in the result set, binds a buffer to retrieve column data // TODO: Move to anonymous namespace, since it is not used outside this file From 2176fbaa51b9acc675f06539e0f8b0559dbfb319 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 24 Sep 2025 09:43:19 +0530 Subject: [PATCH 38/39] rename PR validation task --- eng/pipelines/pr-validation-pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 98b81722..f4e23852 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -1479,7 +1479,7 @@ jobs: testRunTitle: 'Publish pytest results on Alpine ARM64' - job: CodeCoverageReport - displayName: 'Full Stack Coverage Report using Ubuntu Linux' + displayName: 'Full Code Coverage Report in Ubuntu x86_64' pool: vmImage: 'ubuntu-latest' From dfb25371165ca2b90197b69edde6761223dc1823 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 24 Sep 2025 09:48:34 +0530 Subject: [PATCH 39/39] Reformatted Comment --- .github/workflows/pr-code-coverage.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 01d89023..d00666b9 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -428,7 +428,7 @@ jobs: - ### 🔥 Patch Coverage + ### 🔥 Diff Coverage ### **${{ env.PATCH_COVERAGE_PCT }}**
@@ -440,7 +440,7 @@ jobs: - **📈 Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` + **📈 Total Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` **📁 Project:** `mssql-python` @@ -449,13 +449,6 @@ jobs: --- - ## 📋 Patch Coverage Report - - 🎯 Patch Coverage Details (lines changed in this PR) -
- - > **Patch Coverage** shows the percentage of **newly added or modified lines** that are covered by tests. - ${{ env.PATCH_COVERAGE_SUMMARY }} ---