Implement coverage for Windows/Linux/MacOS (#126) (#128)

* Implement coverage for Windows/Linux/MacOS (#126)

* reverted

---------

Co-authored-by: Saikari <lin@sz.cn.eu.org>
This commit is contained in:
2025-12-25 02:28:17 +03:00
committed by GitHub
parent d16984a8b2
commit 29da13d244
11 changed files with 874 additions and 34 deletions

169
scripts/coverage-llvm.sh Executable file
View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# scripts/coverage-llvm.sh
# LLVM coverage script that generates LCOV-style reports
set -e
SOURCE_DIR="${1:-.}"
BINARY_DIR="${2:-cmake-build/build}"
TEST_BINARY="${3:-}"
OUTPUT_DIR="${4:-${BINARY_DIR}/coverage}"
echo "[*] Source dir: ${SOURCE_DIR}"
echo "[*] Binary dir: ${BINARY_DIR}"
echo "[*] Output dir: ${OUTPUT_DIR}"
# Find llvm tools - handle versioned names (Linux) and xcrun (macOS)
find_llvm_tool() {
local tool_name="$1"
# macOS: use xcrun
if [[ "$(uname)" == "Darwin" ]]; then
if xcrun --find "${tool_name}" &>/dev/null; then
echo "xcrun ${tool_name}"
return 0
fi
fi
# Try versioned names (Linux with LLVM 21, 20, 19, etc.)
for version in 21 20 19 18 17 ""; do
local versioned_name="${tool_name}${version:+-$version}"
if command -v "${versioned_name}" &>/dev/null; then
echo "${versioned_name}"
return 0
fi
done
echo ""
return 1
}
LLVM_PROFDATA=$(find_llvm_tool "llvm-profdata")
LLVM_COV=$(find_llvm_tool "llvm-cov")
if [[ -z "${LLVM_PROFDATA}" ]] || [[ -z "${LLVM_COV}" ]]; then
echo "Error: llvm-profdata or llvm-cov not found" >&2
echo "On Linux, install llvm or clang package" >&2
echo "On macOS, Xcode command line tools should provide these" >&2
exit 1
fi
echo "[*] Using: ${LLVM_PROFDATA}"
echo "[*] Using: ${LLVM_COV}"
# Find test binary
if [[ -z "${TEST_BINARY}" ]]; then
for path in \
"${SOURCE_DIR}/out/Debug/unit_tests" \
"${SOURCE_DIR}/out/Release/unit_tests" \
"${BINARY_DIR}/unit_tests" \
"${BINARY_DIR}/tests/unit_tests"; do
if [[ -x "${path}" ]]; then
TEST_BINARY="${path}"
break
fi
done
fi
if [[ -z "${TEST_BINARY}" ]] || [[ ! -x "${TEST_BINARY}" ]]; then
echo "Error: unit_tests binary not found" >&2
echo "Searched in: out/Debug, out/Release, ${BINARY_DIR}" >&2
exit 1
fi
echo "[*] Test binary: ${TEST_BINARY}"
# Clean previous coverage data
rm -rf "${OUTPUT_DIR}"
rm -f "${BINARY_DIR}"/*.profraw "${BINARY_DIR}"/*.profdata
mkdir -p "${OUTPUT_DIR}"
# Run tests with profiling enabled
PROFILE_FILE="${BINARY_DIR}/default_%p.profraw"
echo "[*] Running tests with LLVM_PROFILE_FILE=${PROFILE_FILE}"
export LLVM_PROFILE_FILE="${PROFILE_FILE}"
"${TEST_BINARY}" || echo "[!] Some tests failed, continuing with coverage..."
# Find all generated .profraw files
PROFRAW_FILES=$(find "${BINARY_DIR}" -name "*.profraw" -type f 2>/dev/null)
if [[ -z "${PROFRAW_FILES}" ]]; then
# Also check current directory
PROFRAW_FILES=$(find . -maxdepth 3 -name "*.profraw" -type f 2>/dev/null)
fi
if [[ -z "${PROFRAW_FILES}" ]]; then
echo "Error: No .profraw files generated" >&2
echo "Make sure the binary was built with -fprofile-instr-generate -fcoverage-mapping" >&2
exit 1
fi
echo "[*] Found profraw files:"
echo "${PROFRAW_FILES}"
# Merge profiles
PROFDATA_FILE="${BINARY_DIR}/coverage.profdata"
echo "[*] Merging profiles into ${PROFDATA_FILE}"
${LLVM_PROFDATA} merge -sparse ${PROFRAW_FILES} -o "${PROFDATA_FILE}"
# Generate text summary
echo "[*] Coverage Summary:"
${LLVM_COV} report "${TEST_BINARY}" \
-instr-profile="${PROFDATA_FILE}" \
-ignore-filename-regex="tests/.*" \
-ignore-filename-regex="googletest/.*" \
-ignore-filename-regex="gtest/.*" \
-ignore-filename-regex="_deps/.*" \
-ignore-filename-regex="vcpkg_installed/.*"
# Export lcov format (for tools like codecov)
LCOV_FILE="${OUTPUT_DIR}/coverage.lcov"
echo "[*] Exporting LCOV format to ${LCOV_FILE}"
${LLVM_COV} export "${TEST_BINARY}" \
-instr-profile="${PROFDATA_FILE}" \
-format=lcov \
-ignore-filename-regex="tests/.*" \
-ignore-filename-regex="googletest/.*" \
-ignore-filename-regex="gtest/.*" \
-ignore-filename-regex="_deps/.*" \
-ignore-filename-regex="vcpkg_installed/.*" \
> "${LCOV_FILE}" || true
# Generate LCOV-style HTML report using genhtml
if command -v genhtml >/dev/null 2>&1; then
echo "[*] Generating LCOV-style HTML report using genhtml"
genhtml "${LCOV_FILE}" \
--ignore-errors inconsistent,corrupt \
--output-directory "${OUTPUT_DIR}" \
--title "Omath Coverage Report" \
--show-details \
--legend \
--demangle-cpp \
--num-spaces 4 \
--sort \
--function-coverage \
--branch-coverage
echo "[*] LCOV-style HTML report generated at: ${OUTPUT_DIR}/index.html"
else
echo "[!] genhtml not found. Installing lcov package..."
echo "[!] On Ubuntu/Debian: sudo apt-get install lcov"
echo "[!] On macOS: brew install lcov"
echo "[!] Falling back to LLVM HTML report..."
# Fall back to LLVM HTML report
${LLVM_COV} show "${TEST_BINARY}" \
-instr-profile="${PROFDATA_FILE}" \
-format=html \
-output-dir="${OUTPUT_DIR}" \
-show-line-counts-or-regions \
-show-instantiations=false \
-ignore-filename-regex="tests/.*" \
-ignore-filename-regex="googletest/.*" \
-ignore-filename-regex="gtest/.*" \
-ignore-filename-regex="_deps/.*" \
-ignore-filename-regex="vcpkg_installed/.*"
fi
echo "[*] Coverage report generated at: ${OUTPUT_DIR}/index.html"
echo "[*] LCOV file at: ${LCOV_FILE}"

8
scripts/coverage.bat.in Normal file
View File

@@ -0,0 +1,8 @@
@echo off
REM scripts/coverage.bat.in
REM Simple wrapper to run coverage.ps1
set SOURCE_DIR=@CMAKE_SOURCE_DIR@
set BINARY_DIR=@CMAKE_BINARY_DIR@
powershell -ExecutionPolicy Bypass -File "%BINARY_DIR%\scripts\coverage.ps1" -SourceDir "%SOURCE_DIR%" -BinaryDir "%BINARY_DIR%" %*

132
scripts/coverage.ps1.in Normal file
View File

@@ -0,0 +1,132 @@
# scripts/coverage.ps1.in
# Windows coverage script using OpenCppCoverage
param(
[Parameter(Mandatory=$true)]
[string]$SourceDir,
[Parameter(Mandatory=$true)]
[string]$BinaryDir,
[string]$TestBinary = "",
[switch]$Cobertura,
[switch]$Html
)
$ErrorActionPreference = "Stop"
# CMake-injected variables
$LCOV_IGNORE_ERRORS = '@LCOV_IGNORE_ERRORS@'
# Resolve paths
$SourceDir = Resolve-Path $SourceDir
$BinaryDir = Resolve-Path $BinaryDir
Write-Host "[*] Source directory: $SourceDir" -ForegroundColor Cyan
Write-Host "[*] Binary directory: $BinaryDir" -ForegroundColor Cyan
# Find test binary
if (-not $TestBinary) {
$searchPaths = @(
"$BinaryDir\Debug\unit_tests.exe",
"$BinaryDir\Release\unit_tests.exe",
"$BinaryDir\unit_tests.exe",
"$SourceDir\out\Debug\unit_tests.exe",
"$SourceDir\out\Release\unit_tests.exe"
)
foreach ($path in $searchPaths) {
if (Test-Path $path) {
$TestBinary = $path
break
}
}
}
if (-not $TestBinary -or -not (Test-Path $TestBinary)) {
Write-Error "unit_tests.exe not found. Searched: $($searchPaths -join ', ')"
exit 1
}
$TestBinary = Resolve-Path $TestBinary
Write-Host "[*] Test binary: $TestBinary" -ForegroundColor Cyan
# Check for OpenCppCoverage
$opencppcov = Get-Command "OpenCppCoverage" -ErrorAction SilentlyContinue
if (-not $opencppcov) {
# Try common installation paths
$possiblePaths = @(
"$env:ProgramFiles\OpenCppCoverage\OpenCppCoverage.exe",
"${env:ProgramFiles(x86)}\OpenCppCoverage\OpenCppCoverage.exe",
"$env:LOCALAPPDATA\Programs\OpenCppCoverage\OpenCppCoverage.exe"
)
foreach ($path in $possiblePaths) {
if (Test-Path $path) {
$opencppcov = Get-Item $path
break
}
}
}
if (-not $opencppcov) {
Write-Host @"
OpenCppCoverage not found!
Install it from: https://github.com/OpenCppCoverage/OpenCppCoverage/releases
Or via Chocolatey:
choco install opencppcoverage
Or via winget:
winget install OpenCppCoverage.OpenCppCoverage
"@ -ForegroundColor Red
exit 1
}
$OpenCppCoveragePath = if ($opencppcov.Source) { $opencppcov.Source } else { $opencppcov.FullName }
Write-Host "[*] Using OpenCppCoverage: $OpenCppCoveragePath" -ForegroundColor Cyan
# Create output directory
$CoverageDir = Join-Path $BinaryDir "coverage"
if (-not (Test-Path $CoverageDir)) {
New-Item -ItemType Directory -Path $CoverageDir | Out-Null
}
# Build OpenCppCoverage arguments
$coverageArgs = @(
"--sources", "$SourceDir\include",
"--sources", "$SourceDir\source",
"--excluded_sources", "*\tests\*",
"--excluded_sources", "*\googletest\*",
"--excluded_sources", "*\gtest\*",
"--excluded_sources", "*\_deps\*",
"--excluded_sources", "*\vcpkg_installed\*",
"--export_type", "html:$CoverageDir",
"--export_type", "cobertura:$CoverageDir\coverage.xml",
"--cover_children",
"--"
)
Write-Host "[*] Running OpenCppCoverage..." -ForegroundColor Cyan
Write-Host " Command: $OpenCppCoveragePath $($coverageArgs -join ' ') $TestBinary"
& $OpenCppCoveragePath @coverageArgs $TestBinary
if ($LASTEXITCODE -ne 0) {
Write-Warning "OpenCppCoverage exited with code $LASTEXITCODE (tests may have failed)"
}
# Check outputs
$htmlIndex = Join-Path $CoverageDir "index.html"
$coberturaXml = Join-Path $CoverageDir "coverage.xml"
if (Test-Path $htmlIndex) {
Write-Host "[*] HTML coverage report: $htmlIndex" -ForegroundColor Green
}
if (Test-Path $coberturaXml) {
Write-Host "[*] Cobertura XML report: $coberturaXml" -ForegroundColor Green
}
Write-Host "[*] Coverage collection complete!" -ForegroundColor Green