diff --git a/.github/scripts/patch-coverage.cs b/.github/scripts/patch-coverage.cs index be1cd435083..80f97c9947a 100644 --- a/.github/scripts/patch-coverage.cs +++ b/.github/scripts/patch-coverage.cs @@ -49,15 +49,21 @@ // 2. Parse cobertura: absolute path -> (line -> hits). Merge partial-class duplicates by max hits. var coverage = ParseCobertura(coberturaPath); -// Whole-project line coverage (every instrumented file in the report). -var projValid = coverage.Sum(f => f.Value.Count); -var projCovered = coverage.Sum(f => f.Value.Values.Count(h => h > 0)); +// Whole-project line coverage (production files only; test/benchmark sources are excluded). +var prodFiles = coverage.Where(f => IsProductionPath(f.Key)).ToList(); +var projValid = prodFiles.Sum(f => f.Value.Count); +var projCovered = prodFiles.Sum(f => f.Value.Values.Count(h => h > 0)); var projPct = projValid == 0 ? 100.0 : 100.0 * projCovered / projValid; // 3. Intersect per file. A changed line counts only if it appears in coverage (executable). var rows = new List(); foreach (var (path, lines) in changed.OrderBy(x => x.Key, StringComparer.Ordinal)) { + if (!IsProductionPath(path)) + { + continue; // patch coverage measures production code, not test/benchmark sources + } + var hits = Match(coverage, path); if (hits is null) { @@ -349,6 +355,16 @@ static string BuildJson(string sha, List withMisses) return b.ToString(); } +static bool IsProductionPath(string path) +{ + // Exclude test and benchmark sources; everything else counts as production. + var p = "/" + path.Replace('\\', '/') + "/"; + return !(p.Contains("/test/", StringComparison.OrdinalIgnoreCase) + || p.Contains("/tests/", StringComparison.OrdinalIgnoreCase) + || p.Contains("/benchmark/", StringComparison.OrdinalIgnoreCase) + || p.Contains("/benchmarks/", StringComparison.OrdinalIgnoreCase)); +} + static string Sha256Hex(string value) { var bytes = System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(value));