Skip to content

deps: Bump the test-and-analyzers group with 4 updates #304

deps: Bump the test-and-analyzers group with 4 updates

deps: Bump the test-and-analyzers group with 4 updates #304

Workflow file for this run

name: CI / Tests
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build-and-test:
# Use a matrix to avoid duplicating the job for Linux and Windows
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.x
# NuGet package cache (speeds up restore)
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Install linux components (OpenMP)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update -y && sudo apt-get install -y libomp-dev
- name: Security Audit (Vulnerable Packages)
run: dotnet list package --vulnerable --include-transitive
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build -c Release --no-restore
- name: Test (Windows)
if: matrix.os == 'windows-latest'
run: dotnet test -c Release --no-build --verbosity normal
# XPlat Code Coverage instruments every sequence point; on the numerical hot loops (matmul, autograd,
# optimizers) that is a 10-900× slowdown for the training/integration tests — which is why the Linux
# coverage job took minutes while the uninstrumented Windows job runs the same tests in ~25s. The
# coverlet.runsettings excludes those hot namespaces from instrumentation, so coverage stays fast while
# the report still covers the orchestration code (loaders, runtime, agents, data, anomalies, …).
- name: Test with Coverage (Linux)
if: matrix.os == 'ubuntu-latest'
run: dotnet test ./Tests/Tests.csproj -c Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings coverlet.runsettings --results-directory ./coverage
# Generate and upload reports (Linux only, to avoid doing it twice)
- name: Install ReportGenerator
if: matrix.os == 'ubuntu-latest'
run: dotnet tool install -g dotnet-reportgenerator-globaltool
- name: Generate Coverage Report
if: matrix.os == 'ubuntu-latest'
run: reportgenerator -reports:./coverage/*/coverage.cobertura.xml -targetdir:./coverage/report -reporttypes:Html
- name: Upload coverage artifact
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: code-coverage-report
path: ./coverage/report
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v4
with:
files: ./coverage/*/coverage.cobertura.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }} # Required in V4!
# NATIVE AOT GUARD: publishes the AotSmokeTest executable under Native AOT.
#
# The previous version of this job published Sources/Main (a class library)
# under PublishAot=true. That did NOT invoke ILCompiler — Native AOT requires
# an executable entry point, so publishing a library is symbolic at best.
#
# AotSmokeTest is a thin console exe that references DevOnBike.Overfit and
# touches a slice of the public surface. With PublishAot=true on the exe,
# ILCompiler actually runs end-to-end: static trim/AOT analyzers, IL→native
# code generation, and native linking. With TreatWarningsAsErrors=true on
# the smoketest, any IL2026 / IL3050 / IL31xx warning surfacing in the
# reachable graph fails the build.
#
# If someone adds LINQ, Reflection, Activator, Expression, Array.Copy, raw
# ArrayPool<T>.Shared (banned by RS0030), or any trim/AOT-incompatible API
# to Sources/Main and it's reachable from the smoketest, this step fails.
#
# The job also runs the produced binary as a smoke test — a non-zero exit
# from the AOT-compiled binary fails the job.
aot-guard:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.x
- name: Install Native AOT prerequisites (clang + zlib)
run: sudo apt-get update -y && sudo apt-get install -y clang zlib1g-dev libomp-dev
- name: Publish AotSmokeTest under Native AOT
# TreatWarningsAsErrors promotes the trim/AOT analyzer warnings (IL2026 / IL3050 / IL2xxx /
# IL3xxx) to errors — that is the point of this guard, and a genuine AOT regression still fails it.
# * GenerateDocumentationFile=false — the smoke test does not need XML docs; this drops the
# CS15xx doc-comment warning family (e.g. CS1574 unresolved cref).
# The project's own advisory analyzers (OVERFIT* perf rules, IDISP* disposable style) are exempted
# centrally via <WarningsNotAsErrors> in Directory.Build.props — do NOT re-add a -p:WarningsNotAsErrors
# here: a global -p OVERRIDES (does not merge with) the props value and would drop the OVERFIT carve-out,
# re-failing the guard on ~1000 perf suggestions unrelated to AOT.
run: |
dotnet publish ./Tests/AotSmokeTest/AotSmokeTest.csproj \
-c Release -r linux-x64 \
-p:PublishAot=true \
-p:TreatWarningsAsErrors=true \
-p:GenerateDocumentationFile=false \
-o ./aot-publish
- name: Run AOT-compiled smoketest binary
run: ./aot-publish/AotSmokeTest
# The `overfit` CLI is the real Native-AOT consumer of the serve path (Sources/Server's HttpListener
# OpenAI server + System.Text.Json source-gen) AND of Sources/Main via OverfitClient. Publishing it
# under PublishAot=true with TreatWarningsAsErrors=true makes ILCompiler run end-to-end over that whole
# reachable graph, so any IL2026 / IL3050 / IL31xx trim/AOT regression on the serve/chat path fails CI
# (OVERFIT*/IDISP* advisory analyzers exempted centrally in Directory.Build.props — unrelated to AOT/trim
# safety; see the smoketest step above for why a -p:WarningsNotAsErrors must NOT be re-added here).
- name: Publish overfit CLI under Native AOT
run: |
dotnet publish ./Sources/Cli/Cli.csproj \
-c Release -r linux-x64 \
-p:PublishAot=true \
-p:TreatWarningsAsErrors=true \
-p:GenerateDocumentationFile=false \
-o ./aot-publish-cli
- name: Run AOT-compiled overfit CLI (smoke)
# `list` exercises the CLI end-to-end (System.CommandLine parse → command action → model-store scan)
# and exits 0 with no models present — a non-blocking smoke check (unlike `serve`, which would block).
run: ./aot-publish-cli/overfit list
# ── Analyzer guard-of-the-guard ──────────────────────────────────────────────
# The custom perf analyzer (Sources/Analyzers, OVERFIT001-015) can die SILENTLY: if its
# Microsoft.CodeAnalysis.CSharp reference is NEWER than the SDK compiler's Roslyn, the compiler
# rejects the whole assembly (warning CS9057) and every rule stops running — while the build
# still "succeeds". This happened for real on 2026-06-12 (a 4.14 → 5.3 bump vs the SDK's 5.0).
# A normal green build therefore proves nothing about the analyzer. This job proves the rules
# actually FIRE: it drops a deliberate OVERFIT008 violation (an error-severity rule) into
# Sources/Main and REQUIRES the build to fail with exactly that diagnostic.
analyzer-guard:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.x
- name: Tripwire — a deliberate violation must fail the build with OVERFIT008
run: |
cat > Sources/Main/Runtime/__AnalyzerTripwire.cs <<'EOF'
// CI tripwire (analyzer-guard job): deliberate OVERFIT008 violation — never committed.
using System.Threading.Tasks;
namespace DevOnBike.Overfit.Runtime
{
internal static class __AnalyzerTripwire
{
public static void Run()
{
Parallel.For(0, 4, i => _ = i);
}
}
}
EOF
set +e
OUTPUT=$(dotnet build ./Sources/Main/Main.csproj -c Release 2>&1)
STATUS=$?
set -e
rm Sources/Main/Runtime/__AnalyzerTripwire.cs
if [ "$STATUS" -eq 0 ]; then
echo "$OUTPUT" | tail -30
if echo "$OUTPUT" | grep -q "CS9057"; then
echo "::error::Analyzer assembly REJECTED by the compiler (CS9057) — the Microsoft.CodeAnalysis.CSharp pin in Directory.Packages.props is newer than the SDK's Roslyn. Every OVERFIT rule is silently OFF."
else
echo "::error::Analyzer is DEAD: the tripwire build succeeded, but error OVERFIT008 was expected."
fi
exit 1
fi
if ! echo "$OUTPUT" | grep -q "OVERFIT008"; then
echo "$OUTPUT" | tail -30
echo "::error::The tripwire build failed, but NOT with OVERFIT008 — investigate."
exit 1
fi
echo "Analyzer alive: the tripwire failed the build with OVERFIT008, as required."
# Second tripwire: the [OverfitHotPath] escalation (OVERFIT900) can rot independently of the
# rules themselves (a bad SupportedDiagnostics/Report wiring stops the error firing while every
# rule still reports its warning). Drop a per-call allocation inside a [OverfitHotPath] method and
# REQUIRE the build to fail with OVERFIT900.
- name: Tripwire — [OverfitHotPath] must escalate a per-call rule to OVERFIT900
run: |
cat > Sources/Main/Runtime/__HotPathTripwire.cs <<'EOF'
// CI tripwire (analyzer-guard job): [OverfitHotPath] escalation — never committed.
using DevOnBike.Overfit.Diagnostics;
namespace DevOnBike.Overfit.Runtime
{
internal static class __HotPathTripwire
{
[OverfitHotPath]
public static int[] Run()
{
return new int[4];
}
}
}
EOF
set +e
OUTPUT=$(dotnet build ./Sources/Main/Main.csproj -c Release 2>&1)
STATUS=$?
set -e
rm Sources/Main/Runtime/__HotPathTripwire.cs
if [ "$STATUS" -eq 0 ]; then
echo "$OUTPUT" | tail -30
echo "::error::[OverfitHotPath] escalation is DEAD: a per-call allocation inside a marked method did NOT fail the build with OVERFIT900."
exit 1
fi
if ! echo "$OUTPUT" | grep -q "OVERFIT900"; then
echo "$OUTPUT" | tail -30
echo "::error::The hot-path tripwire build failed, but NOT with OVERFIT900 — investigate."
exit 1
fi
echo "Hot-path escalation alive: the tripwire failed the build with OVERFIT900, as required."