Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7265686
fix: resolve CS8032 assembly version mismatch for System.Collections.…
rjmurillo Feb 16, 2026
368dc64
fix: add SRM check to MSBuild gate, fix PerfDiff NU1109
rjmurillo Feb 16, 2026
ffc4d3a
ci: add analyzer host compatibility check to CI pipeline
rjmurillo Feb 16, 2026
2c2ba70
fix(ci): correct DLL paths in analyzer host compatibility check
cursoragent Feb 16, 2026
94f0b9b
fix(build): enhance analyzer host compatibility validation
cursoragent Feb 16, 2026
3519f32
ci: add analyzer load integration test for supported SDK versions
rjmurillo Feb 16, 2026
e8414c5
ci: add net10.0 to analyzer load integration test matrix
rjmurillo Feb 16, 2026
9ff4f72
fix(ci): check build exit code in analyzer-load-test
cursoragent Feb 16, 2026
6fefa40
ci: add .NET Framework 4.7.2, 4.8, 4.8.1 to analyzer load test matrix
rjmurillo Feb 16, 2026
063d773
ci: use x64 runners for .NET Framework analyzer load tests
rjmurillo Feb 16, 2026
32d9a52
fix(ci): restore correct artifact paths for analyzer host validation
rjmurillo Feb 16, 2026
7a68343
ci: use msbuild.exe on Windows for .NET Framework analyzer load tests
rjmurillo Feb 16, 2026
7a9fd93
docs: add gh act workflow verification guide to CONTRIBUTING.md
rjmurillo Feb 16, 2026
67c7132
ci: test analyzer loading on both VS 2022 and VS 2026 hosts
rjmurillo Feb 16, 2026
c388191
ci: split build and test jobs, remove Windows ARM build
rjmurillo Feb 16, 2026
40802cc
ci: extract perf into separate job, unblock test and load-test
rjmurillo Feb 16, 2026
d4a3717
fix(ci): convert MSYS path to Windows format for local NuGet feed
cursoragent Feb 16, 2026
6bdd5db
ci: switch analyzer-load-test to pwsh, fix msbuild not found
rjmurillo Feb 16, 2026
86e37e7
ci: fix misleading OK output in host compatibility check
rjmurillo Feb 16, 2026
18a6d96
fix(ci): add null check and build exit code validation in analyzer-lo…
cursoragent Feb 16, 2026
49ad078
fix: address PR review feedback for analyzer load test robustness
rjmurillo Feb 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ updates:
- dependency-name: "Microsoft.CodeAnalysis.CSharp.Workspaces"
- dependency-name: "Microsoft.CodeAnalysis.Common"
- dependency-name: "Microsoft.CodeAnalysis.Workspaces.Common"
# Analyzer-shipped BCL packages must match minimum supported SDK host.
# See: https://github.com/rjmurillo/moq.analyzers/issues/850
- dependency-name: "System.Collections.Immutable"
- dependency-name: "System.Reflection.Metadata"
Comment thread
rjmurillo marked this conversation as resolved.
- dependency-name: "Microsoft.CodeAnalysis.AnalyzerUtilities"
304 changes: 268 additions & 36 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,96 @@
actions: read

jobs:
# Build, validate, and package. Fast gate (~5 min).
# Tests, analyzer-load-test, and perf run as separate downstream jobs.
build:
runs-on: ubuntu-24.04-arm

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup, Restore, and Build Solution
uses: ./.github/actions/setup-restore-build

- name: Validate analyzer host compatibility
shell: pwsh
run: |
# Verify shipped analyzer DLLs don't reference assembly versions
# exceeding what the minimum supported SDK host (.NET 8) provides.
# See: https://github.com/rjmurillo/moq.analyzers/issues/850
$maxVersions = @{
'System.Collections.Immutable' = [Version]'8.0.0.0'
'System.Reflection.Metadata' = [Version]'8.0.0.0'
}
$shippedDlls = @(
'artifacts/bin/Moq.Analyzers/release/Moq.Analyzers.dll',
'artifacts/bin/Moq.Analyzers/release/Moq.CodeFixes.dll',
'artifacts/bin/Moq.Analyzers/release/Microsoft.CodeAnalysis.AnalyzerUtilities.dll'
)
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
rjmurillo marked this conversation as resolved.
$failed = $false
foreach ($dll in $shippedDlls) {
$name = [System.IO.Path]::GetFileName($dll)
$bytes = [System.IO.File]::ReadAllBytes($dll)
$asm = [System.Reflection.Assembly]::Load($bytes)
$dllFailed = $false
foreach ($ref in $asm.GetReferencedAssemblies()) {
if ($maxVersions.ContainsKey($ref.Name) -and $ref.Version -gt $maxVersions[$ref.Name]) {
Write-Error "$name references $($ref.Name) $($ref.Version), max allowed is $($maxVersions[$ref.Name])"
$failed = $true
$dllFailed = $true
}
}
if (-not $dllFailed) { Write-Host "$name - OK" }
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if ($failed) { exit 1 }

- name: Upload binlogs
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: binlogs
path: ./artifacts/logs
if-no-files-found: error

- name: Upload SARIF files
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: SARIF files
path: ./artifacts/obj/**/*.sarif

- name: Upload packages
uses: actions/upload-artifact@v6
with:
name: packages
path: |
./artifacts/package
if-no-files-found: error

- name: Upload artifacts
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: artifacts
path: ./artifacts
if-no-files-found: error

# Run unit tests on multiple platforms.
# Build happens independently on each runner so tests use platform-native binaries.
test:
strategy:
fail-fast: false
matrix:
os: [windows-11-arm, ubuntu-24.04-arm]
os: [ubuntu-24.04-arm, windows-latest]

runs-on: ${{ matrix.os }}

env:
IS_CODACY_COVERAGE_ALLOWED: ${{ secrets.CODACY_PROJECT_TOKEN != '' }}
IS_QLTY_COVERAGE_ALLOWED: ${{ secrets.QLTY_COVERAGE_TOKEN != '' }}
IS_TARGET_MAIN: ${{ github.ref == 'refs/heads/main' }}
RUN_FULL_PERF: ${{ (github.event_name == 'schedule' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_performance == 'true') }}
FORCE_PERF_BASELINE: ${{ github.event.inputs.force_baseline }} # This is also used in PerfCore.ps1 to determine if the baseline should be forced

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -75,29 +151,14 @@
name: CoverageHistory-${{ matrix.os }}
path: ./artifacts/TestResults/coveragehistory

- name: Upload binlogs
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: binlogs-${{ matrix.os }}
path: ./artifacts/logs
if-no-files-found: error

- name: Upload *.received.* files
uses: actions/upload-artifact@v6
if: failure()
with:
name: verify-test-results
name: verify-test-results-${{ matrix.os }}
path: |
**/*.received.*

- name: Upload SARIF files
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: SARIF files (${{ matrix.os }})
path: ./artifacts/obj/**/*.sarif

- name: Upload Test Report
uses: actions/upload-artifact@v6
if: success() || failure()
Expand Down Expand Up @@ -131,26 +192,205 @@
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
files: ${{ github.workspace }}/artifacts/TestResults/coverage/Cobertura.xml

- name: Upload packages
uses: actions/upload-artifact@v6
# Verify the shipped nupkg loads without CS8032 on every supported host.
# This is the end-to-end integration test for issue #850.
#
# The analyzer targets netstandard2.0 because it must load in two hosts:
# 1. dotnet CLI - csc runs on .NET (8/9/10), tested on Linux ARM
# 2. msbuild.exe - csc runs on .NET Framework (VS), tested on Windows x64
analyzer-load-test:
needs: build
Comment thread
rjmurillo marked this conversation as resolved.
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
# dotnet CLI compiler host (modern .NET runtime)
- dotnet-version: '8.0.x'
tfm: net8.0
runs-on: ubuntu-24.04-arm
build-engine: dotnet
- dotnet-version: '9.0.x'
tfm: net9.0
runs-on: ubuntu-24.04-arm
build-engine: dotnet
- dotnet-version: '10.0.x'
tfm: net10.0
runs-on: ubuntu-24.04-arm
build-engine: dotnet
# MSBuild/.NET Framework compiler host - VS 2022 (GA)
- dotnet-version: '8.0.x'
tfm: net472
runs-on: windows-2022
build-engine: msbuild
- dotnet-version: '8.0.x'
tfm: net48
runs-on: windows-2022
build-engine: msbuild
- dotnet-version: '8.0.x'
tfm: net481
runs-on: windows-2022
build-engine: msbuild
# MSBuild/.NET Framework compiler host - VS 2026 (beta)
- dotnet-version: '8.0.x'
tfm: net472
runs-on: windows-2025-vs2026
build-engine: msbuild
- dotnet-version: '8.0.x'
tfm: net48
runs-on: windows-2025-vs2026
build-engine: msbuild
- dotnet-version: '8.0.x'
tfm: net481
runs-on: windows-2025-vs2026
build-engine: msbuild
Comment thread
rjmurillo marked this conversation as resolved.
Comment thread
rjmurillo marked this conversation as resolved.

steps:
- name: Setup .NET ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v5
with:
name: packages-${{ matrix.os }}
path: |
./artifacts/package
if-no-files-found: error
dotnet-version: ${{ matrix.dotnet-version }}

- name: Setup MSBuild
if: matrix.build-engine == 'msbuild'
uses: microsoft/setup-msbuild@v2

Check warning on line 256 in .github/workflows/main.yml

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.github/workflows/main.yml#L256

An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release.
Comment thread
rjmurillo marked this conversation as resolved.

- name: Download nupkg artifact
uses: actions/download-artifact@v7
with:
name: packages
path: ./local-feed

- name: Create test project
shell: pwsh
run: |
$pkg = Get-ChildItem ./local-feed -Recurse -Filter 'Moq.Analyzers.*.nupkg' |
Where-Object { $_.Name -notlike '*.symbols.*' } |
Select-Object -First 1
if (-not $pkg) {
Write-Error "Could not find Moq.Analyzers.*.nupkg (excluding symbols) in ./local-feed"
exit 1
}
$version = $pkg.Name -replace '^Moq\.Analyzers\.' -replace '\.nupkg$'
$feedDir = (Resolve-Path $pkg.DirectoryName).Path
Comment thread
rjmurillo marked this conversation as resolved.
Write-Host "Testing Moq.Analyzers $version with ${{ matrix.build-engine }} / ${{ matrix.tfm }}"
echo "ANALYZER_VERSION=$version" >> $env:GITHUB_ENV
echo "FEED_DIR=$feedDir" >> $env:GITHUB_ENV

New-Item -ItemType Directory -Path test-project | Out-Null

@"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="$feedDir" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
Comment thread
cursor[bot] marked this conversation as resolved.
</packageSources>
</configuration>
"@ | Set-Content test-project/nuget.config

@"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>${{ matrix.tfm }}</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq.Analyzers" Version="$version" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
</Project>
"@ | Set-Content test-project/TestAnalyzerLoad.csproj

@"
namespace TestAnalyzerLoad;
public class Placeholder { }
"@ | Set-Content test-project/Placeholder.cs

- name: Build with dotnet CLI
if: matrix.build-engine == 'dotnet'
shell: pwsh
working-directory: test-project
run: |
$output = dotnet build -v n 2>&1 | Out-String
$buildExitCode = $LASTEXITCODE
Write-Host $output

if ($buildExitCode -ne 0) {
Write-Host "::error::Build failed with exit code $buildExitCode (dotnet / ${{ matrix.tfm }})"
exit $buildExitCode
}

if ($output -match 'CS8032') {
Write-Host "::error::CS8032: analyzer failed to load (dotnet / ${{ matrix.tfm }}). See https://github.com/rjmurillo/moq.analyzers/issues/850"
exit 1
}
if ($output -match '(?i)could not load file or assembly') {
Write-Host "::error::Assembly binding failure (dotnet / ${{ matrix.tfm }}). See https://github.com/rjmurillo/moq.analyzers/issues/850"
exit 1
}

Write-Host ""
Write-Host "Analyzer loaded successfully (dotnet / ${{ matrix.tfm }})"
Comment thread
rjmurillo marked this conversation as resolved.

- name: Build with MSBuild
if: matrix.build-engine == 'msbuild'
shell: pwsh
working-directory: test-project
run: |
$output = msbuild TestAnalyzerLoad.csproj -restore -p:Configuration=Release -v:n 2>&1 | Out-String
$buildExitCode = $LASTEXITCODE
Write-Host $output

if ($buildExitCode -ne 0) {
Write-Host "::error::Build failed with exit code $buildExitCode (msbuild / ${{ matrix.tfm }})"
exit $buildExitCode
}

if ($output -match 'CS8032') {
Write-Host "::error::CS8032: analyzer failed to load (msbuild / ${{ matrix.tfm }}). See https://github.com/rjmurillo/moq.analyzers/issues/850"
exit 1
}
if ($output -match '(?i)could not load file or assembly') {
Write-Host "::error::Assembly binding failure (msbuild / ${{ matrix.tfm }}). See https://github.com/rjmurillo/moq.analyzers/issues/850"
exit 1
}

Write-Host ""
Write-Host "Analyzer loaded successfully (msbuild / ${{ matrix.tfm }})"

# Performance validation runs last, after build and tests confirm correctness.
# Builds from source on Linux ARM to get consistent benchmark results.
perf:
needs: [build, test]
runs-on: ubuntu-24.04-arm

env:
RUN_FULL_PERF: ${{ (github.event_name == 'schedule' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_performance == 'true') }}
FORCE_PERF_BASELINE: ${{ github.event.inputs.force_baseline }}

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup, Restore, and Build Solution
uses: ./.github/actions/setup-restore-build

- name: Get baseline SHA
id: get-baseline-sha
run: |
if (-not (Test-Path build/perf/baseline.json)) {
Write-Error "baseline.json not found aborting performance job."
Write-Error "baseline.json not found - aborting performance job."
exit 1
}
$baseline = Get-Content build/perf/baseline.json | ConvertFrom-Json
echo "sha=$($baseline.sha)" >> $env:GITHUB_OUTPUT
shell: pwsh

# The machine is not guaranteed to have the .NET SDK installed from the baseline SHA, so we need to install it
# The baseline SHA may require a different .NET SDK version
- name: Checkout baseline
uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -214,14 +454,6 @@
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: performance-${{ matrix.os }}
name: performance
path: |
./artifacts/performance/**

- name: Upload artifacts
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: artifacts-${{ matrix.os }}
path: ./artifacts
if-no-files-found: error
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ jobs:
uses: actions/download-artifact@v7
with:
path: packages
pattern: packages-ubuntu-*
merge-multiple: true
name: packages
- name: Publish NuGet package
shell: pwsh
run: |
Expand Down
Loading
Loading