From 10d15d50ba9b67a0b022ba3ec7055d352ecd5b41 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Fri, 15 Aug 2025 09:45:02 +0100 Subject: [PATCH 1/5] Add test coverage report --- .../workflows/BuildAndTestOnPullRequests.yml | 71 ++++++++++++++++++- test/Stateless.Tests/Stateless.Tests.csproj | 4 ++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/.github/workflows/BuildAndTestOnPullRequests.yml b/.github/workflows/BuildAndTestOnPullRequests.yml index b995b1dc..61637f2f 100644 --- a/.github/workflows/BuildAndTestOnPullRequests.yml +++ b/.github/workflows/BuildAndTestOnPullRequests.yml @@ -18,8 +18,75 @@ jobs: - name: Build Stateless solution run: dotnet build Stateless.sln --configuration Release --no-restore - - name: Test Stateless - run: dotnet test --no-restore --no-build --configuration Release + - name: Test Stateless with coverage + run: | + dotnet test --no-restore --no-build --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage --logger "console;verbosity=normal" + + - name: Generate test summary and coverage + id: test-summary + run: | + # Get test results summary + $testOutput = dotnet test --no-restore --no-build --configuration Release --logger "console;verbosity=minimal" 2>&1 + $testSummary = $testOutput | Select-String "Test Run" | Select-Object -Last 1 + + # Get build configuration from environment or default + $buildConfig = if ($env:BUILD_CONFIGURATION) { $env:BUILD_CONFIGURATION } else { "Release" } + + # Get target frameworks from test project file + $testProjectPath = "./test/Stateless.Tests/Stateless.Tests.csproj" + if (Test-Path $testProjectPath) { + [xml]$testProject = Get-Content $testProjectPath + $targetFrameworks = $testProject.Project.PropertyGroup.TargetFrameworks + if (-not $targetFrameworks) { + $targetFrameworks = $testProject.Project.PropertyGroup.TargetFramework + } + } else { + $targetFrameworks = "Unknown" + } + + # Get coverage percentage + $coverageFile = Get-ChildItem -Path "./coverage" -Recurse -Filter "coverage.cobertura.xml" | Select-Object -First 1 + $coveragePercentage = "N/A" + $badgeColor = "lightgrey" + + if ($coverageFile) { + [xml]$coverage = Get-Content $coverageFile.FullName + $lineRate = [double]$coverage.coverage.'line-rate' + $coveragePercentage = [math]::Round($lineRate * 100, 1) + + $badgeColor = if ($coveragePercentage -ge 90) { "brightgreen" } elseif ($coveragePercentage -ge 80) { "green" } elseif ($coveragePercentage -ge 70) { "yellow" } elseif ($coveragePercentage -ge 60) { "orange" } else { "red" } + } + + # Create summary + $summary = "## ๐Ÿงช Test Results`n`n" + $summary += "โœ… **Tests:** $testSummary`n" + $summary += "๐Ÿ“Š **Coverage:** $coveragePercentage%`n" + $summary += "๐ŸŽฏ **Target Frameworks:** $targetFrameworks`n" + $summary += "โš™๏ธ **Configuration:** $buildConfig`n`n" + + if ($coveragePercentage -ne "N/A") { + $summary += "![Coverage](https://img.shields.io/badge/coverage-${coveragePercentage}%25-${badgeColor}?style=flat-square)" + } + + echo "summary<> $env:GITHUB_OUTPUT + echo "$summary" >> $env:GITHUB_OUTPUT + echo "EOF" >> $env:GITHUB_OUTPUT + echo "coverage_percentage=$coveragePercentage" >> $env:GITHUB_OUTPUT + echo "badge_color=$badgeColor" >> $env:GITHUB_OUTPUT + + - name: Comment PR with test summary + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const summary = `${{ steps.test-summary.outputs.summary }}`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: summary + }); - name: Pack alpha version if: github.ref == 'refs/heads/dev' && github.event_name == 'push' && github.repository == 'dotnet-state-machine/stateless' diff --git a/test/Stateless.Tests/Stateless.Tests.csproj b/test/Stateless.Tests/Stateless.Tests.csproj index 2eed06a8..f9bdc3e7 100644 --- a/test/Stateless.Tests/Stateless.Tests.csproj +++ b/test/Stateless.Tests/Stateless.Tests.csproj @@ -24,6 +24,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + From 587e2f069c01c3bb5d8fe542b5fd3b6fd65d317d Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Sun, 17 Aug 2025 16:34:00 +0100 Subject: [PATCH 2/5] Fix build issue --- .../workflows/BuildAndTestOnPullRequests.yml | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/.github/workflows/BuildAndTestOnPullRequests.yml b/.github/workflows/BuildAndTestOnPullRequests.yml index 61637f2f..c1c3b26e 100644 --- a/.github/workflows/BuildAndTestOnPullRequests.yml +++ b/.github/workflows/BuildAndTestOnPullRequests.yml @@ -18,43 +18,58 @@ jobs: - name: Build Stateless solution run: dotnet build Stateless.sln --configuration Release --no-restore - - name: Test Stateless with coverage - run: | - dotnet test --no-restore --no-build --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage --logger "console;verbosity=normal" - - - name: Generate test summary and coverage + - name: Test Stateless with coverage and generate summary id: test-summary run: | - # Get test results summary + # Run tests with coverage collection + dotnet test --no-restore --no-build --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage --logger "console;verbosity=normal" + + # Get test results summary from the output $testOutput = dotnet test --no-restore --no-build --configuration Release --logger "console;verbosity=minimal" 2>&1 $testSummary = $testOutput | Select-String "Test Run" | Select-Object -Last 1 + # If no test summary found, create a default one + if (-not $testSummary) { + $testSummary = "Test execution completed" + } + # Get build configuration from environment or default $buildConfig = if ($env:BUILD_CONFIGURATION) { $env:BUILD_CONFIGURATION } else { "Release" } # Get target frameworks from test project file $testProjectPath = "./test/Stateless.Tests/Stateless.Tests.csproj" + $targetFrameworks = "Unknown" if (Test-Path $testProjectPath) { - [xml]$testProject = Get-Content $testProjectPath - $targetFrameworks = $testProject.Project.PropertyGroup.TargetFrameworks - if (-not $targetFrameworks) { - $targetFrameworks = $testProject.Project.PropertyGroup.TargetFramework + try { + [xml]$testProject = Get-Content $testProjectPath + $targetFrameworks = $testProject.Project.PropertyGroup.TargetFrameworks + if (-not $targetFrameworks) { + $targetFrameworks = $testProject.Project.PropertyGroup.TargetFramework + } + if (-not $targetFrameworks) { + $targetFrameworks = "Unknown" + } + } catch { + $targetFrameworks = "Error reading project file" } - } else { - $targetFrameworks = "Unknown" } # Get coverage percentage - $coverageFile = Get-ChildItem -Path "./coverage" -Recurse -Filter "coverage.cobertura.xml" | Select-Object -First 1 $coveragePercentage = "N/A" $badgeColor = "lightgrey" - if ($coverageFile) { - [xml]$coverage = Get-Content $coverageFile.FullName - $lineRate = [double]$coverage.coverage.'line-rate' - $coveragePercentage = [math]::Round($lineRate * 100, 1) - - $badgeColor = if ($coveragePercentage -ge 90) { "brightgreen" } elseif ($coveragePercentage -ge 80) { "green" } elseif ($coveragePercentage -ge 70) { "yellow" } elseif ($coveragePercentage -ge 60) { "orange" } else { "red" } + try { + $coverageFile = Get-ChildItem -Path "./coverage" -Recurse -Filter "coverage.cobertura.xml" | Select-Object -First 1 + if ($coverageFile) { + [xml]$coverage = Get-Content $coverageFile.FullName + $lineRate = [double]$coverage.coverage.'line-rate' + $coveragePercentage = [math]::Round($lineRate * 100, 1) + + $badgeColor = if ($coveragePercentage -ge 90) { "brightgreen" } elseif ($coveragePercentage -ge 80) { "green" } elseif ($coveragePercentage -ge 70) { "yellow" } elseif ($coveragePercentage -ge 60) { "orange" } else { "red" } + } + } catch { + $coveragePercentage = "Error calculating coverage" + $badgeColor = "lightgrey" } # Create summary @@ -64,7 +79,7 @@ jobs: $summary += "๐ŸŽฏ **Target Frameworks:** $targetFrameworks`n" $summary += "โš™๏ธ **Configuration:** $buildConfig`n`n" - if ($coveragePercentage -ne "N/A") { + if ($coveragePercentage -ne "N/A" -and $coveragePercentage -ne "Error calculating coverage") { $summary += "![Coverage](https://img.shields.io/badge/coverage-${coveragePercentage}%25-${badgeColor}?style=flat-square)" } From af84335d20c0e947db05e01cf7d2602412211d69 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Sun, 17 Aug 2025 17:06:19 +0100 Subject: [PATCH 3/5] Move coverage logic into a separate script. --- .../workflows/BuildAndTestOnPullRequests.yml | 88 ++----------------- scripts/run-tests-with-coverage.ps1 | 69 +++++++++++++++ 2 files changed, 76 insertions(+), 81 deletions(-) create mode 100644 scripts/run-tests-with-coverage.ps1 diff --git a/.github/workflows/BuildAndTestOnPullRequests.yml b/.github/workflows/BuildAndTestOnPullRequests.yml index c1c3b26e..c286964b 100644 --- a/.github/workflows/BuildAndTestOnPullRequests.yml +++ b/.github/workflows/BuildAndTestOnPullRequests.yml @@ -20,88 +20,14 @@ jobs: - name: Test Stateless with coverage and generate summary id: test-summary - run: | - # Run tests with coverage collection - dotnet test --no-restore --no-build --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage --logger "console;verbosity=normal" - - # Get test results summary from the output - $testOutput = dotnet test --no-restore --no-build --configuration Release --logger "console;verbosity=minimal" 2>&1 - $testSummary = $testOutput | Select-String "Test Run" | Select-Object -Last 1 - - # If no test summary found, create a default one - if (-not $testSummary) { - $testSummary = "Test execution completed" - } - - # Get build configuration from environment or default - $buildConfig = if ($env:BUILD_CONFIGURATION) { $env:BUILD_CONFIGURATION } else { "Release" } - - # Get target frameworks from test project file - $testProjectPath = "./test/Stateless.Tests/Stateless.Tests.csproj" - $targetFrameworks = "Unknown" - if (Test-Path $testProjectPath) { - try { - [xml]$testProject = Get-Content $testProjectPath - $targetFrameworks = $testProject.Project.PropertyGroup.TargetFrameworks - if (-not $targetFrameworks) { - $targetFrameworks = $testProject.Project.PropertyGroup.TargetFramework - } - if (-not $targetFrameworks) { - $targetFrameworks = "Unknown" - } - } catch { - $targetFrameworks = "Error reading project file" - } - } - - # Get coverage percentage - $coveragePercentage = "N/A" - $badgeColor = "lightgrey" - - try { - $coverageFile = Get-ChildItem -Path "./coverage" -Recurse -Filter "coverage.cobertura.xml" | Select-Object -First 1 - if ($coverageFile) { - [xml]$coverage = Get-Content $coverageFile.FullName - $lineRate = [double]$coverage.coverage.'line-rate' - $coveragePercentage = [math]::Round($lineRate * 100, 1) - - $badgeColor = if ($coveragePercentage -ge 90) { "brightgreen" } elseif ($coveragePercentage -ge 80) { "green" } elseif ($coveragePercentage -ge 70) { "yellow" } elseif ($coveragePercentage -ge 60) { "orange" } else { "red" } - } - } catch { - $coveragePercentage = "Error calculating coverage" - $badgeColor = "lightgrey" - } - - # Create summary - $summary = "## ๐Ÿงช Test Results`n`n" - $summary += "โœ… **Tests:** $testSummary`n" - $summary += "๐Ÿ“Š **Coverage:** $coveragePercentage%`n" - $summary += "๐ŸŽฏ **Target Frameworks:** $targetFrameworks`n" - $summary += "โš™๏ธ **Configuration:** $buildConfig`n`n" - - if ($coveragePercentage -ne "N/A" -and $coveragePercentage -ne "Error calculating coverage") { - $summary += "![Coverage](https://img.shields.io/badge/coverage-${coveragePercentage}%25-${badgeColor}?style=flat-square)" - } - - echo "summary<> $env:GITHUB_OUTPUT - echo "$summary" >> $env:GITHUB_OUTPUT - echo "EOF" >> $env:GITHUB_OUTPUT - echo "coverage_percentage=$coveragePercentage" >> $env:GITHUB_OUTPUT - echo "badge_color=$badgeColor" >> $env:GITHUB_OUTPUT + run: ./scripts/run-tests-with-coverage.ps1 - - name: Comment PR with test summary - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - script: | - const summary = `${{ steps.test-summary.outputs.summary }}`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: summary - }); + - name: Output test summary + run: | + echo "## Test Summary" + echo "${{ steps.test-summary.outputs.summary }}" + echo "" + echo "Coverage: ${{ steps.test-summary.outputs.coverage_percentage }}%" - name: Pack alpha version if: github.ref == 'refs/heads/dev' && github.event_name == 'push' && github.repository == 'dotnet-state-machine/stateless' diff --git a/scripts/run-tests-with-coverage.ps1 b/scripts/run-tests-with-coverage.ps1 new file mode 100644 index 00000000..7b6423a8 --- /dev/null +++ b/scripts/run-tests-with-coverage.ps1 @@ -0,0 +1,69 @@ +#!/usr/bin/env pwsh + +# Run tests with coverage collection +dotnet test --no-restore --no-build --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage --logger "console;verbosity=normal" + +# Get test results summary from the output +$testOutput = dotnet test --no-restore --no-build --configuration Release --logger "console;verbosity=minimal" 2>&1 +$testSummary = $testOutput | Select-String "Test Run" | Select-Object -Last 1 + +# If no test summary found, create a default one +if (-not $testSummary) { + $testSummary = "Test execution completed" +} + +# Get build configuration from environment or default +$buildConfig = if ($env:BUILD_CONFIGURATION) { $env:BUILD_CONFIGURATION } else { "Release" } + +# Get target frameworks from test project file +$testProjectPath = "./test/Stateless.Tests/Stateless.Tests.csproj" +$targetFrameworks = "Unknown" +if (Test-Path $testProjectPath) { + try { + [xml]$testProject = Get-Content $testProjectPath + $targetFrameworks = $testProject.Project.PropertyGroup.TargetFrameworks + if (-not $targetFrameworks) { + $targetFrameworks = $testProject.Project.PropertyGroup.TargetFramework + } + if (-not $targetFrameworks) { + $targetFrameworks = "Unknown" + } + } catch { + $targetFrameworks = "Error reading project file" + } +} + +# Get coverage percentage +$coveragePercentage = "N/A" +$badgeColor = "lightgrey" + +try { + $coverageFile = Get-ChildItem -Path "./coverage" -Recurse -Filter "coverage.cobertura.xml" | Select-Object -First 1 + if ($coverageFile) { + [xml]$coverage = Get-Content $coverageFile.FullName + $lineRate = [double]$coverage.coverage.'line-rate' + $coveragePercentage = [math]::Round($lineRate * 100, 1) + + $badgeColor = if ($coveragePercentage -ge 90) { "brightgreen" } elseif ($coveragePercentage -ge 80) { "green" } elseif ($coveragePercentage -ge 70) { "yellow" } elseif ($coveragePercentage -ge 60) { "orange" } else { "red" } + } +} catch { + $coveragePercentage = "Error calculating coverage" + $badgeColor = "lightgrey" +} + + # Create summary + $summary = "## Test Results`n`n" + $summary += "**Tests:** $testSummary`n" + $summary += "**Coverage:** $coveragePercentage%`n" + $summary += "**Target Frameworks:** $targetFrameworks`n" + $summary += "**Configuration:** $buildConfig`n`n" + +if ($coveragePercentage -ne "N/A" -and $coveragePercentage -ne "Error calculating coverage") { + $summary += "![Coverage](https://img.shields.io/badge/coverage-${coveragePercentage}%25-${badgeColor}?style=flat-square)" +} + +echo "summary<> $env:GITHUB_OUTPUT +echo "$summary" >> $env:GITHUB_OUTPUT +echo "EOF" >> $env:GITHUB_OUTPUT +echo "coverage_percentage=$coveragePercentage" >> $env:GITHUB_OUTPUT +echo "badge_color=$badgeColor" >> $env:GITHUB_OUTPUT From a3545c6a322254251b8181b9d4ee3927f247f9ae Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Sun, 17 Aug 2025 17:13:17 +0100 Subject: [PATCH 4/5] Use workflow annotation --- .github/workflows/BuildAndTestOnPullRequests.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/BuildAndTestOnPullRequests.yml b/.github/workflows/BuildAndTestOnPullRequests.yml index c286964b..5538f6af 100644 --- a/.github/workflows/BuildAndTestOnPullRequests.yml +++ b/.github/workflows/BuildAndTestOnPullRequests.yml @@ -22,12 +22,11 @@ jobs: id: test-summary run: ./scripts/run-tests-with-coverage.ps1 - - name: Output test summary + - name: Output test summary as annotations run: | - echo "## Test Summary" - echo "${{ steps.test-summary.outputs.summary }}" - echo "" - echo "Coverage: ${{ steps.test-summary.outputs.coverage_percentage }}%" + echo "::notice::Test Results Summary" + echo "${{ steps.test-summary.outputs.summary }}" | ForEach-Object { echo "::notice::$($_)" } + echo "::notice::Coverage: ${{ steps.test-summary.outputs.coverage_percentage }}%" - name: Pack alpha version if: github.ref == 'refs/heads/dev' && github.event_name == 'push' && github.repository == 'dotnet-state-machine/stateless' From e62ef338dda1d8a77002f2bc8e6f91ef1a519e05 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Sun, 17 Aug 2025 17:18:57 +0100 Subject: [PATCH 5/5] Switch to step summary --- .github/workflows/BuildAndTestOnPullRequests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/BuildAndTestOnPullRequests.yml b/.github/workflows/BuildAndTestOnPullRequests.yml index 5538f6af..77042820 100644 --- a/.github/workflows/BuildAndTestOnPullRequests.yml +++ b/.github/workflows/BuildAndTestOnPullRequests.yml @@ -22,11 +22,11 @@ jobs: id: test-summary run: ./scripts/run-tests-with-coverage.ps1 - - name: Output test summary as annotations + - name: Output test summary run: | - echo "::notice::Test Results Summary" - echo "${{ steps.test-summary.outputs.summary }}" | ForEach-Object { echo "::notice::$($_)" } - echo "::notice::Coverage: ${{ steps.test-summary.outputs.coverage_percentage }}%" + echo "${{ steps.test-summary.outputs.summary }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Coverage:** ${{ steps.test-summary.outputs.coverage_percentage }}%" >> $GITHUB_STEP_SUMMARY - name: Pack alpha version if: github.ref == 'refs/heads/dev' && github.event_name == 'push' && github.repository == 'dotnet-state-machine/stateless'