Skip to content

.github/workflows/ci.yml #278

.github/workflows/ci.yml

.github/workflows/ci.yml #278

Workflow file for this run

name: CI
on:
push:
branches: [main]
paths-ignore:
- "docs/**"
- "*.md"
- ".github/instructions/**"
- ".github/skills/**"
- "docs/assets/**"
pull_request:
branches: [main]
paths-ignore:
- "docs/**"
- "*.md"
- ".github/instructions/**"
- ".github/skills/**"
- "docs/assets/**"
schedule:
# Run mutation testing weekly (Monday 04:00 UTC) to avoid burning minutes on every push
- cron: "0 4 * * 1"
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build-and-test:
runs-on: windows-latest
timeout-minutes: 45
env:
# Prevent MSBuild worker nodes from persisting between steps and holding
# file handles on *.cache files, which would cause MSB3492 errors.
MSBUILDDISABLENODEREUSE: 1
steps:
- uses: actions/checkout@v6
- uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Cache NuGet
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', 'Directory.Packages.props', 'global.json') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Build
run: dotnet build RegiLattice.sln -c Release
- name: Check for vulnerable NuGet packages
shell: pwsh
run: |
$output = dotnet list RegiLattice.sln package --vulnerable --include-transitive 2>&1
Write-Host $output
if ($output -match 'has the following vulnerable packages') {
Write-Warning "::warning::Vulnerable NuGet packages detected — review Dependabot PRs"
}
# Run test projects individually (not as solution) to guarantee sequential
# execution and prevent shared-file races between Core.Tests and GUI.Tests.
# See tests/.runsettings — MaxCpuCount=1 note.
- name: Test (Core) with coverage
run: >-
dotnet test tests/RegiLattice.Core.Tests/RegiLattice.Core.Tests.csproj
-c Release --no-restore
--settings tests/.runsettings
--blame-hang-timeout 30s
--collect:"XPlat Code Coverage"
--logger "console;verbosity=minimal"
- name: Test (CLI) with coverage
run: >-
dotnet test tests/RegiLattice.CLI.Tests/RegiLattice.CLI.Tests.csproj
-c Release --no-restore
--settings tests/.runsettings
--blame-hang-timeout 30s
--collect:"XPlat Code Coverage"
--logger "console;verbosity=minimal"
- name: Test (GUI) with coverage
run: >-
dotnet test tests/RegiLattice.GUI.Tests/RegiLattice.GUI.Tests.csproj
-c Release --no-restore
--settings tests/.runsettings
--blame-hang-timeout 30s
--collect:"XPlat Code Coverage"
--logger "console;verbosity=minimal"
- name: Validate TweakDef integrity
# Runs --validate to catch duplicate IDs, broken DependsOn, circular deps,
# and ImpactScore/SafetyRating out-of-range errors before merging.
run: >-
dotnet run --project src/RegiLattice.CLI/RegiLattice.CLI.csproj
-c Release --no-build
-- --validate
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: "**/coverage.cobertura.xml"
fail_ci_if_error: false
- name: Write job summary
if: always()
env:
JOB_STATUS: ${{ job.status }}
shell: pwsh
run: |
$icon = if ($env:JOB_STATUS -eq 'success') { '\u2705' } else { '\u274c' }
@"
## CI Build & Test Summary
| Field | Value |
|-------|-------|
| **Commit** | \`${{ github.sha }}\` |
| **Branch** | \`${{ github.ref_name }}\` |
| **Run** | [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |
| **Triggered by** | \`${{ github.event_name }}\` |
Codecov dashboard: https://codecov.io/gh/${{ github.repository }}
"@ | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8
mutation-testing:
# Mutation testing is expensive (~15 min on windows-latest).
# Run only on the weekly schedule or manual dispatch — NOT on every push to main.
needs: build-and-test
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: windows-latest
timeout-minutes: 45
env:
MSBUILDDISABLENODEREUSE: 1
steps:
- uses: actions/checkout@v6
- uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Cache NuGet
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', 'Directory.Packages.props', 'global.json') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Install dotnet-stryker
run: dotnet tool restore
- name: Build (for mutation)
# Build the full solution so all assembly references are present when
# Stryker scans the solution graph (avoids "could not find mutable
# assembly for RegiLattice.GUI.Tests" error in Stryker 4.x).
run: dotnet build RegiLattice.sln -c Debug
- name: Run Stryker mutation tests
# Run from src/RegiLattice.Core so Stryker does NOT auto-detect
# RegiLattice.sln in CWD and enter solution-scan mode (which fails on
# GUI.Tests — a WinForms project with no mutable assembly reference).
# STRYKER_BUILD=1 disables the %TEMP% build-path redirect in
# Directory.Build.props so Stryker's Buildalyzer can analyze projects
# using standard local bin/obj paths (required for design-time analysis).
# Break threshold is 55% — CI fails if mutation score drops below that.
# Target: 60%+ kill score on Core library (T6.6).
working-directory: src/RegiLattice.Core
env:
STRYKER_BUILD: "1"
run: dotnet stryker --config-file ../../stryker-config.json
- name: Stryker diagnostics on failure
# Runs --diag to surface detailed analysis errors when Stryker fails.
# Helps debug issues like missing assembly references or config problems.
if: failure()
working-directory: src/RegiLattice.Core
env:
STRYKER_BUILD: "1"
shell: pwsh
run: |
Write-Host "::group::Stryker --diag output (first 300 lines)"
dotnet stryker --config-file ../../stryker-config.json --diag 2>&1 |
Select-Object -First 300 |
ForEach-Object { Write-Host $_ }
Write-Host "::endgroup::"
- name: Upload Stryker HTML report
# Only upload on failure — the HTML report is large and is only needed for debugging
if: failure()
uses: actions/upload-artifact@v7
with:
name: stryker-report
path: src/RegiLattice.Core/StrykerOutput/
if-no-files-found: warn