diff --git a/.github/workflows/official-build.yaml b/.github/workflows/official-build.yaml index c240aa1..5158e8c 100644 --- a/.github/workflows/official-build.yaml +++ b/.github/workflows/official-build.yaml @@ -35,6 +35,14 @@ jobs: run: dotnet test --verbosity normal working-directory: src/ReferenceCop.MSBuild.Tests + - name: Pack ReferenceCop for playground + run: dotnet pack -c Debug --nologo + working-directory: src/ReferenceCop.Package + + - name: Run end-to-end tests + run: ./playground/Run-E2ETests.ps1 + shell: pwsh + - name: Set NuGet Package Version Environment Variable from tag (only for tagged builds) if: ${{ github.ref_type == 'tag' }} run: echo "PKGVERSION=$('${{ github.ref_name }}'.replace('v',''))" >> $env:GITHUB_ENV diff --git a/.github/workflows/pr-build.yaml b/.github/workflows/pr-build.yaml index 1a78a27..d00b945 100644 --- a/.github/workflows/pr-build.yaml +++ b/.github/workflows/pr-build.yaml @@ -33,4 +33,12 @@ jobs: - name: Run Tests in ReferenceCop.MSBuild.Tests run: dotnet test --verbosity normal - working-directory: src/ReferenceCop.MSBuild.Tests \ No newline at end of file + working-directory: src/ReferenceCop.MSBuild.Tests + + - name: Pack ReferenceCop for playground + run: dotnet pack -c Release --nologo -p:IsDevEnvironment=true + working-directory: src/ReferenceCop.Package + + - name: Run end-to-end tests + run: ./playground/Run-E2ETests.ps1 + shell: pwsh \ No newline at end of file diff --git a/playground/Run-E2ETests.ps1 b/playground/Run-E2ETests.ps1 new file mode 100644 index 0000000..acf6614 --- /dev/null +++ b/playground/Run-E2ETests.ps1 @@ -0,0 +1,222 @@ +<# +.SYNOPSIS + Automated end-to-end test runner for ReferenceCop playground scenarios. + +.DESCRIPTION + Builds various test configurations in the playground and validates that + ReferenceCop correctly detects violations and allows valid references. + +.PARAMETER PackFirst + If set, builds and packs the ReferenceCop package before running tests. +#> +param( + [switch]$PackFirst +) + +$ErrorActionPreference = "Stop" +$script:TestsPassed = 0 +$script:TestsFailed = 0 +$script:Failures = @() + +$RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path +$PlaygroundRoot = "$PSScriptRoot/TestProject" + +function Write-TestHeader($name) { + Write-Host "`n========================================" -ForegroundColor Cyan + Write-Host " TEST: $name" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan +} + +function Assert-BuildFails($project, $testName, $expectedDiagnostic) { + Write-TestHeader $testName + $output = dotnet build "$PlaygroundRoot/$project" --no-restore 2>&1 | Out-String + $exitCode = $LASTEXITCODE + + if ($exitCode -eq 0) { + Write-Host " FAIL: Build succeeded but was expected to fail" -ForegroundColor Red + Write-Host " Output: $output" -ForegroundColor Gray + $script:TestsFailed++ + $script:Failures += $testName + return + } + + if ($expectedDiagnostic -and ($output -notmatch $expectedDiagnostic)) { + Write-Host " FAIL: Build failed but diagnostic '$expectedDiagnostic' not found in output" -ForegroundColor Red + Write-Host " Output: $output" -ForegroundColor Gray + $script:TestsFailed++ + $script:Failures += $testName + return + } + + Write-Host " PASS: Build failed as expected with diagnostic '$expectedDiagnostic'" -ForegroundColor Green + $script:TestsPassed++ +} + +function Assert-BuildSucceeds($project, $testName) { + Write-TestHeader $testName + $output = dotnet build "$PlaygroundRoot/$project" --no-restore 2>&1 | Out-String + $exitCode = $LASTEXITCODE + + if ($exitCode -ne 0) { + Write-Host " FAIL: Build failed but was expected to succeed" -ForegroundColor Red + Write-Host " Output: $output" -ForegroundColor Gray + $script:TestsFailed++ + $script:Failures += $testName + return + } + + Write-Host " PASS: Build succeeded as expected" -ForegroundColor Green + $script:TestsPassed++ +} + +# --- Setup --- + +if ($PackFirst) { + Write-Host "`nPacking ReferenceCop..." -ForegroundColor Yellow + Push-Location "$RepoRoot/src/ReferenceCop.Package" + dotnet pack -c Debug --nologo -v quiet + if ($LASTEXITCODE -ne 0) { + Write-Host "FATAL: Failed to pack ReferenceCop" -ForegroundColor Red + exit 1 + } + Pop-Location +} + +# Restore all playground projects +Write-Host "`nRestoring playground projects..." -ForegroundColor Yellow +dotnet restore "$PlaygroundRoot/SampleApp/SampleApp.csproj" --nologo -v quiet +dotnet restore "$PlaygroundRoot/ValidApp/ValidApp.csproj" --nologo -v quiet + +# --- Test Scenarios --- + +# Scenario 1: AssemblyName rule - SampleLibrary references Newtonsoft.Json which is blocked +Assert-BuildFails "SampleLibrary" ` + "AssemblyName rule: Newtonsoft.Json blocked" ` + "RC000[12]" + +# Scenario 2: ProjectPath rule - SampleApp referencing InternalLib should be blocked +# We need to temporarily add a project reference for this test +$sampleAppCsproj = "$PlaygroundRoot/SampleApp/SampleApp.csproj" +$originalContent = Get-Content $sampleAppCsproj -Raw + +# Add InternalLib reference +$modifiedContent = $originalContent -replace '(\s*)', @" + + + + + + + +"@ +# Fix: replace last properly +$modifiedContent = $originalContent -replace '(\s* + + + + + + + Exe + net8.0 + enable + enable + App + + + + + + + + +"@ + +# Save originals, write modified project and minimal program +$originalSampleApp = Get-Content $sampleAppCsproj -Raw +$sampleAppProgram = "$PlaygroundRoot/SampleApp/Program.cs" +$originalProgram = Get-Content $sampleAppProgram -Raw +$minimalProgram = "namespace SampleApp;`npublic class Program { public static void Main() { } }" + +Set-Content $sampleAppCsproj -Value $testCsproj -NoNewline +Set-Content $sampleAppProgram -Value $minimalProgram -NoNewline +dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null + +Assert-BuildFails "SampleApp" ` + "ProjectPath rule: App cannot reference Internal" ` + "RC000[12]" + +# Restore originals +Set-Content $sampleAppCsproj -Value $originalSampleApp -NoNewline +Set-Content $sampleAppProgram -Value $originalProgram -NoNewline +dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null + +# Scenario 3: ProjectTag rule - App referencing Tools project should warn +$toolsTestCsproj = @" + + + Exe + net8.0 + enable + enable + App + + + + + + + + +"@ + +Set-Content $sampleAppCsproj -Value $toolsTestCsproj -NoNewline +Set-Content $sampleAppProgram -Value $minimalProgram -NoNewline +dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null + +# ProjectTag rule has Severity=Warning, so build may succeed but with warnings +# We check for the diagnostic in output regardless of exit code +Write-TestHeader "ProjectTag rule: App cannot reference Tools (warning)" +$output = dotnet build "$PlaygroundRoot/SampleApp" --no-restore 2>&1 | Out-String +if ($output -match "RC000[12]") { + Write-Host " PASS: ProjectTag violation diagnostic detected" -ForegroundColor Green + $script:TestsPassed++ +} else { + Write-Host " FAIL: Expected ProjectTag warning diagnostic not found" -ForegroundColor Red + Write-Host " Output: $output" -ForegroundColor Gray + $script:TestsFailed++ + $script:Failures += "ProjectTag rule: App cannot reference Tools (warning)" +} + +# Restore originals +Set-Content $sampleAppCsproj -Value $originalSampleApp -NoNewline +Set-Content $sampleAppProgram -Value $originalProgram -NoNewline +dotnet restore "$sampleAppCsproj" --nologo -v quiet 2>$null + +# Scenario 4: Valid project builds successfully (no violations) +Assert-BuildSucceeds "ValidApp" ` + "Valid project: No violations, build succeeds" + +# --- Summary --- +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host " RESULTS" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Passed: $script:TestsPassed" -ForegroundColor Green +Write-Host " Failed: $script:TestsFailed" -ForegroundColor $(if ($script:TestsFailed -gt 0) { "Red" } else { "Green" }) + +if ($script:TestsFailed -gt 0) { + Write-Host "`n Failed tests:" -ForegroundColor Red + foreach ($f in $script:Failures) { + Write-Host " - $f" -ForegroundColor Red + } + exit 1 +} + +Write-Host "`n All e2e tests passed!" -ForegroundColor Green +exit 0 diff --git a/playground/TEST-SCENARIOS.md b/playground/TEST-SCENARIOS.md new file mode 100644 index 0000000..547a080 --- /dev/null +++ b/playground/TEST-SCENARIOS.md @@ -0,0 +1,27 @@ +# E2E Test Scenarios + +This document describes the automated end-to-end test scenarios run by `Run-E2ETests.ps1`. + +## Scenarios + +| # | Rule Type | Scenario | Expected Result | +|---|-----------|----------|-----------------| +| 1 | AssemblyName | SampleLibrary references Newtonsoft.Json (blocked by `NoNewtonsoftJson` rule) | Build fails with RC0001/RC0002 | +| 2 | ProjectPath | SampleApp references InternalLib (blocked by `AppCannotReferenceInternal` rule) | Build fails with RC0001/RC0002 | +| 3 | ProjectTag | SampleApp (tag=App) references ToolsProject (tag=Tools), blocked by `AppCannotReferenceTools` rule | Warning diagnostic RC0001/RC0002 emitted | +| 4 | Valid | ValidApp with no violations | Build succeeds cleanly | + +## Running Locally + +```powershell +# Pack first, then run tests +./playground/Run-E2ETests.ps1 -PackFirst + +# Or if already packed: +./playground/Run-E2ETests.ps1 +``` + +## CI Integration + +The tests run automatically in both PR and official build workflows after the unit tests pass. +The workflow packs the package in Debug mode, then invokes the test runner. diff --git a/playground/TestProject/InternalLib/InternalHelper.cs b/playground/TestProject/InternalLib/InternalHelper.cs new file mode 100644 index 0000000..ae50d00 --- /dev/null +++ b/playground/TestProject/InternalLib/InternalHelper.cs @@ -0,0 +1,6 @@ +namespace InternalLib; + +public class InternalHelper +{ + public static string GetSecret() => "internal"; +} diff --git a/playground/TestProject/InternalLib/InternalLib.csproj b/playground/TestProject/InternalLib/InternalLib.csproj new file mode 100644 index 0000000..6f94e6a --- /dev/null +++ b/playground/TestProject/InternalLib/InternalLib.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + Library + + + diff --git a/playground/TestProject/ReferenceCop.config b/playground/TestProject/ReferenceCop.config index dce9c77..b196501 100644 --- a/playground/TestProject/ReferenceCop.config +++ b/playground/TestProject/ReferenceCop.config @@ -3,15 +3,7 @@ true true - - - NoSystemWebReferences - System.Web assemblies should not be referenced - Error - System.Web* - - - + AppCannotReferenceTools Application projects should not reference tool projects @@ -26,14 +18,14 @@ Applications should not reference internal implementation libraries Error **/SampleApp/** - **/Internal/** + **/InternalLib/** NoNewtonsoftJson Use System.Text.Json instead of Newtonsoft.Json - Warning + Error Newtonsoft.Json* diff --git a/playground/TestProject/ToolsProject/Tool.cs b/playground/TestProject/ToolsProject/Tool.cs new file mode 100644 index 0000000..ced6dcc --- /dev/null +++ b/playground/TestProject/ToolsProject/Tool.cs @@ -0,0 +1,6 @@ +namespace ToolsProject; + +public class Tool +{ + public static void Run() { } +} diff --git a/playground/TestProject/ToolsProject/ToolsProject.csproj b/playground/TestProject/ToolsProject/ToolsProject.csproj new file mode 100644 index 0000000..5ca2e66 --- /dev/null +++ b/playground/TestProject/ToolsProject/ToolsProject.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + Tools + + + diff --git a/playground/TestProject/ValidApp/Program.cs b/playground/TestProject/ValidApp/Program.cs new file mode 100644 index 0000000..8012955 --- /dev/null +++ b/playground/TestProject/ValidApp/Program.cs @@ -0,0 +1 @@ +Console.WriteLine("Hello from ValidApp!"); diff --git a/playground/TestProject/ValidApp/ValidApp.csproj b/playground/TestProject/ValidApp/ValidApp.csproj new file mode 100644 index 0000000..7d79ec2 --- /dev/null +++ b/playground/TestProject/ValidApp/ValidApp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + App + + + + + + + diff --git a/playground/nuget.config b/playground/nuget.config index f45e370..3af9dc5 100644 --- a/playground/nuget.config +++ b/playground/nuget.config @@ -7,7 +7,7 @@ - + diff --git a/playground/playground.slnx b/playground/playground.slnx index c5ee38d..c0fecb1 100644 --- a/playground/playground.slnx +++ b/playground/playground.slnx @@ -7,4 +7,7 @@ + + +