-
Notifications
You must be signed in to change notification settings - Fork 0
117 lines (97 loc) · 4.25 KB
/
dotnet.yml
File metadata and controls
117 lines (97 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
name: .NET
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
jobs:
build_and_test:
name: Build and Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
dotnet-quality: "preview"
cache: true
cache-dependency-path: |
**/*.csproj
**/*.props
**/*.targets
**/packages.lock.json
- name: Restore
run: dotnet restore CoreIdent.sln
- name: Build
run: dotnet build CoreIdent.sln -c Release --no-restore
- name: Test (with coverage, excluding E2E)
run: |
dotnet test tests/CoreIdent.Core.Tests -c Release --no-build --collect:"XPlat Code Coverage" --logger "console;verbosity=minimal"
dotnet test tests/CoreIdent.Integration.Tests -c Release --no-build --collect:"XPlat Code Coverage" --logger "console;verbosity=minimal"
dotnet test tests/CoreIdent.Cli.Tests -c Release --no-build --logger "console;verbosity=minimal"
dotnet test tests/CoreIdent.Templates.Tests -c Release --no-build --logger "console;verbosity=minimal"
- name: OpenAPI Validation Gate
run: dotnet test tests/CoreIdent.Integration.Tests/CoreIdent.Integration.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CoreIdent.Integration.Tests.Infrastructure.OpenApiFixtureTests" --logger "console;verbosity=minimal"
- name: Coverage Gate (CoreIdent.Core >= 90%)
run: |
python3 - <<'PY'
import xml.etree.ElementTree as ET
from pathlib import Path
import glob
from collections import defaultdict
THRESHOLD = 90.0
def norm(p: str) -> str:
return p.replace('\\\\', '/')
def canonical(p: str) -> str:
p = norm(p)
if p.startswith('src/'):
p = p[4:]
return p
candidates = [Path(p) for p in glob.glob('tests/*/TestResults/*/coverage.cobertura.xml')]
if not candidates:
raise SystemExit('No coverage.cobertura.xml files found under tests/*/TestResults/*/')
by_project = defaultdict(list)
for p in candidates:
parts = p.parts
idx = parts.index('tests')
project = parts[idx + 1]
by_project[project].append(p)
selected = {project: max(files, key=lambda x: x.stat().st_mtime) for project, files in by_project.items()}
canon_files = defaultdict(dict) # canonical filename -> {line:int -> covered:bool}
for p in selected.values():
root = ET.parse(p).getroot()
for cls in root.findall('.//class'):
fn = cls.attrib.get('filename')
if not fn:
continue
fn = canonical(fn)
lines_node = cls.find('lines')
if lines_node is None:
continue
m = canon_files[fn]
for ln in lines_node.findall('line'):
num = ln.attrib.get('number')
hits = ln.attrib.get('hits')
if num is None or hits is None:
continue
try:
n = int(num)
h = int(hits)
except ValueError:
continue
cov = h > 0
prev = m.get(n)
m[n] = cov if prev is None else (prev or cov)
core = {fn: m for fn, m in canon_files.items() if fn.startswith('CoreIdent.Core/')}
valid = sum(len(m) for m in core.values())
covered = sum(1 for m in core.values() for v in m.values() if v)
rate = (covered / valid * 100.0) if valid else 0.0
print(f'Normalized merged CoreIdent.Core line coverage: {covered}/{valid} = {rate:.2f}%')
if rate + 1e-9 < THRESHOLD:
raise SystemExit(f'Coverage gate failed: CoreIdent.Core line coverage {rate:.2f}% is below {THRESHOLD:.2f}%')
PY