Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
25cab81
--wip
bart-vmware Jul 9, 2026
2092c0b
Atomic move
bart-vmware Jul 14, 2026
3c8b117
Better error handling (full stack trace on unexpected failures)
bart-vmware Jul 15, 2026
314a8f6
Fix broken macos build
bart-vmware Jul 15, 2026
e0d0047
Add test for git remotes
bart-vmware Jul 15, 2026
ffe0e56
Another macos fix (symlink temp path)
bart-vmware Jul 15, 2026
70f57d6
increase test coverage
bart-vmware Jul 16, 2026
4ff4aca
Speed up test runs, cancel immediately on stop
bart-vmware Jul 16, 2026
9811168
Extract tests into separate files
bart-vmware Jul 16, 2026
c65745b
Test improvements
bart-vmware Jul 17, 2026
b796205
Code cleaning
bart-vmware Jul 17, 2026
6d7fe83
Cleanup tests, fixing primitive obsession antipattern
bart-vmware Jul 17, 2026
9cee238
Simplify tests more
bart-vmware Jul 17, 2026
2c25745
Cleanup comments
bart-vmware Jul 20, 2026
a096c18
Refactor and cleanup tests
bart-vmware Jul 21, 2026
e87f033
Handle diag IDs, bugfixes
bart-vmware Jul 21, 2026
b5dacb8
Add test for invalid git version
bart-vmware Jul 22, 2026
f24282e
Hide fallback file in IDE
bart-vmware Jul 22, 2026
02ec2bb
add note about stale info
bart-vmware Jul 22, 2026
159f5a2
enable to hide write log messages in IDE
bart-vmware Jul 22, 2026
6c511d5
Move tests into subcategories
bart-vmware Jul 23, 2026
379837d
cleanup
bart-vmware Jul 23, 2026
496141a
Remove TestPaths
bart-vmware Jul 23, 2026
3199758
Increase test timeouts
bart-vmware Jul 23, 2026
19c63cb
Don't collect coverage in components builds (unused)
bart-vmware Jul 23, 2026
85dadb3
Fix coverage
bart-vmware Jul 23, 2026
2a5994c
More coverage fixes
bart-vmware Jul 23, 2026
36a06e8
Take branch coverage into account
bart-vmware Jul 23, 2026
3d0e8f8
Extract hardcoded path from script
bart-vmware Jul 23, 2026
3b7f85a
Hide unrelated libs in coverage
bart-vmware Jul 23, 2026
fe8b192
Fix crash on macOS
bart-vmware Jul 24, 2026
0ff0b1b
Improve coverage
bart-vmware Jul 24, 2026
3eb40d5
Improve coverage
bart-vmware Jul 24, 2026
af96f6c
Improve coverage more
bart-vmware Jul 25, 2026
f13fb6d
Even more coverage fixes
bart-vmware Jul 25, 2026
07c21ae
Add comment why git patch is optional
bart-vmware Jul 25, 2026
cd7f398
Save 8% perf on test runs
bart-vmware Jul 25, 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
7 changes: 7 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
"regitlint"
],
"rollForward": false
},
"dotnet-coverage": {
"version": "18.9.0",
"commands": [
"dotnet-coverage"
],
"rollForward": false
}
}
}
193 changes: 193 additions & 0 deletions .github/scripts/normalize-coverage-paths.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
param(
[Parameter(Mandatory = $true)][string]$CoverageFile,
[Parameter(Mandatory = $true)][string]$RepoRoot,
[Parameter(Mandatory = $true)][string]$ProjectRelativePath
)

# Some test suites (e.g. GitProperties.Build.Test) run dotnet build/publish subprocesses against their own temporary
# copy of the repo, so the PDB (and therefore the coverage report) embeds an ephemeral temp path per test run instead
# of the real checkout path, and dotnet-coverage records one separate <class> entry per subprocess rather than
# recognizing them as the same logical file. This script rewrites any path ending in $ProjectRelativePath to the real
# checkout path, then merges the resulting duplicate <class> entries: line hits are summed, and each branch
# <condition> (always a binary jump, so its "coverage" is always 0%/50%/100%) takes the maximum observed across
# subprocesses. Without that merge, tools that correlate coverage by file path (SonarCloud) or re-aggregate reports
# (ReportGenerator's Cobertura export) both pick an arbitrary "last one wins" entry instead of the union of what
# every subprocess actually executed.
#
# Branch merging is a best-effort approximation: taking the max per condition cannot distinguish "two subprocesses
# both took the same half of a 50/50 branch" from "they took different halves", so it can still undercount versus a
# true union, but it never overstates coverage and is strictly more accurate than last-one-wins.
Add-Type -AssemblyName System.Xml.Linq

$normalizedRelativePath = $ProjectRelativePath.Replace('\', '/').Trim('/')
$escapedSegments = $normalizedRelativePath -split '/' | ForEach-Object { [Regex]::Escape($_) }
$pattern = '[^"]*[\\/]' + ($escapedSegments -join '[\\/]') + '[\\/]'
$canonicalPrefix = ($RepoRoot.TrimEnd('/', '\').Replace('\', '/')) + '/' + $normalizedRelativePath
$replacement = "$canonicalPrefix/"

$content = Get-Content -Path $CoverageFile -Raw
$content = [System.Text.RegularExpressions.Regex]::Replace($content, $pattern, $replacement)

$doc = [System.Xml.Linq.XDocument]::Parse($content)
$invariantCulture = [System.Globalization.CultureInfo]::InvariantCulture

function Format-Percentage([double]$value) {
$rounded = [Math]::Round($value, 2)

if ($rounded -eq [Math]::Floor($rounded)) {
return "$([int]$rounded)%"
}

return "$($rounded.ToString($invariantCulture))%"
}

function Get-LineRate($lines) {
$lines = @($lines)

if ($lines.Count -eq 0) {
return "0"
}

$covered = @($lines | Where-Object { [int]$_.Attribute("hits").Value -gt 0 }).Count
return ($covered / $lines.Count).ToString($invariantCulture)
}

function Get-BranchCounts($lines) {
$total = 0
$covered = 0

foreach ($line in ($lines | Where-Object { $_.Attribute("branch").Value -eq "True" })) {
$conditionsElement = $line.Element("conditions")

if ($null -eq $conditionsElement) {
continue
}

foreach ($condition in $conditionsElement.Elements("condition")) {
$percentage = [double]($condition.Attribute("coverage").Value.TrimEnd('%'))
$total += 2
$covered += [Math]::Round($percentage / 100 * 2)
}
}

return [PSCustomObject]@{ Total = $total; Covered = $covered }
}

function Get-BranchRate($lines) {
$counts = Get-BranchCounts $lines

if ($counts.Total -eq 0) {
return "1"
}

return ($counts.Covered / $counts.Total).ToString($invariantCulture)
}

foreach ($package in $doc.Descendants("package")) {
$classesElement = $package.Element("classes")

if ($null -eq $classesElement) {
continue
}

$classGroups = $classesElement.Elements("class") | Group-Object {
$_.Attribute("name").Value + "|" + $_.Attribute("filename").Value
}

foreach ($group in $classGroups) {
$keptClass = $group.Group[0]

if ($group.Count -gt 1) {
$lineHits = @{}
$conditionCoverage = @{}

foreach ($class in $group.Group) {
foreach ($line in $class.Element("lines").Elements("line")) {
$number = $line.Attribute("number").Value
$hits = [int]$line.Attribute("hits").Value

if ($lineHits.ContainsKey($number)) {
$lineHits[$number] += $hits
}
else {
$lineHits[$number] = $hits
}

$conditionsElement = $line.Element("conditions")

if ($null -eq $conditionsElement) {
continue
}

foreach ($condition in $conditionsElement.Elements("condition")) {
$key = "$number|$($condition.Attribute("number").Value)"
$percentage = [double]($condition.Attribute("coverage").Value.TrimEnd('%'))

if (-not $conditionCoverage.ContainsKey($key) -or $percentage -gt $conditionCoverage[$key]) {
$conditionCoverage[$key] = $percentage
}
}
}
}

foreach ($line in $keptClass.Descendants("line")) {
$line.SetAttributeValue("hits", [string]$lineHits[$line.Attribute("number").Value])

$conditionsElement = $line.Element("conditions")

if ($null -eq $conditionsElement) {
continue
}

$lineTotal = 0
$lineCovered = 0

foreach ($condition in $conditionsElement.Elements("condition")) {
$key = "$($line.Attribute("number").Value)|$($condition.Attribute("number").Value)"
$percentage = $conditionCoverage[$key]
$condition.SetAttributeValue("coverage", (Format-Percentage $percentage))
$lineTotal += 2
$lineCovered += [Math]::Round($percentage / 100 * 2)
}

$lineCoverageDescription = "$(Format-Percentage (($lineCovered / $lineTotal) * 100)) ($lineCovered/$lineTotal)"
$line.SetAttributeValue("condition-coverage", $lineCoverageDescription)
}

foreach ($duplicate in $group.Group | Select-Object -Skip 1) {
$duplicate.Remove()
}
}

$keptClassLines = $keptClass.Element("lines").Elements("line")
$keptClass.SetAttributeValue("line-rate", (Get-LineRate $keptClassLines))
$keptClass.SetAttributeValue("branch-rate", (Get-BranchRate $keptClassLines))
}

$packageLines = $classesElement.Elements("class").Element("lines").Elements("line")
$package.SetAttributeValue("line-rate", (Get-LineRate $packageLines))
$package.SetAttributeValue("branch-rate", (Get-BranchRate $packageLines))
}

# SetAttributeValue (create-or-update) is used instead of Attribute().Value to avoid null-refs: dotnet-coverage omits
# these attributes from the root element entirely (rather than writing zeros) when its profiler never initialized.
$allLines = @($doc.Descendants("class") | ForEach-Object { $_.Element("lines").Elements("line") })
$doc.Root.SetAttributeValue("lines-valid", [string]$allLines.Count)
$doc.Root.SetAttributeValue("lines-covered", [string]@($allLines | Where-Object { [int]$_.Attribute("hits").Value -gt 0 }).Count)
$doc.Root.SetAttributeValue("line-rate", (Get-LineRate $allLines))

$branchCounts = Get-BranchCounts $allLines
$doc.Root.SetAttributeValue("branches-valid", [string]$branchCounts.Total)
$doc.Root.SetAttributeValue("branches-covered", [string]$branchCounts.Covered)
$doc.Root.SetAttributeValue("branch-rate", (Get-BranchRate $allLines))

# A profiler that never initializes (e.g. dynamic instrumentation isn't supported on macOS arm64 runners, and this
# suite's build/publish subprocesses can't be statically instrumented since their assemblies don't exist until
# mid-run) yields zero packages/classes here. That's a platform limitation, not proof the code is untested.
if ($allLines.Count -eq 0) {
$message = "Skipping $CoverageFile - no coverage data found"
Write-Warning $message
Write-Output "::warning::$message"
}

$doc.Save($CoverageFile)
28 changes: 23 additions & 5 deletions .github/workflows/Steeltoe.All.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ env:
SOLUTION_FILE: 'src/Steeltoe.All.slnx'
COMMON_TEST_ARGS: >-
--no-build --configuration Release --collect "XPlat Code Coverage" --logger trx --results-directory ${{ github.workspace }}/TestOutput
--settings coverlet.runsettings --blame-crash --blame-hang-timeout 1m
--settings coverlet.runsettings --blame-crash --blame-hang-timeout 5m

jobs:
build:
Expand Down Expand Up @@ -99,6 +99,9 @@ jobs:
docker cp src/Configuration/test/Encryption.Test/Cryptography/server.jks steeltoe-config:/workspace/server.jks
docker restart steeltoe-config

- name: Restore tools
run: dotnet tool restore --verbosity minimal

- name: Restore packages
run: dotnet restore ${{ env.SOLUTION_FILE }} /p:Configuration=Release /p:NuGetAudit=false --verbosity minimal

Expand All @@ -107,14 +110,29 @@ jobs:

- name: Test
id: test
run: dotnet test ${{ env.SOLUTION_FILE }} --filter "${{ matrix.skipIntegrationTests == true && 'Category!=MemoryDumps&Category!=Integration' || 'Category!=MemoryDumps' }}" ${{ env.COMMON_TEST_ARGS }}
run: dotnet test ${{ env.SOLUTION_FILE }} --filter "${{ matrix.skipIntegrationTests == true && 'Category!=MemoryDumps&Category!=Integration&FullyQualifiedName!~Steeltoe.Management.GitProperties.Build.Test' || 'Category!=MemoryDumps&FullyQualifiedName!~Steeltoe.Management.GitProperties.Build.Test' }}" ${{ env.COMMON_TEST_ARGS }}

- name: Test (memory dumps)
id: test-memory-dumps
run: dotnet test src/Management/test/Endpoint.Test --filter "Category=MemoryDumps" ${{ env.COMMON_TEST_ARGS }}

- name: Test (GitProperties.Build)
id: test-gitproperties-build
run: >-
dotnet dotnet-coverage collect -f cobertura -o ${{ github.workspace }}/TestOutput/GitProperties.Build.Test.cobertura.xml --
dotnet test src/Management/test/GitProperties.Build.Test --no-build --configuration Release --filter "Category!=MemoryDumps"
--logger trx --results-directory ${{ github.workspace }}/TestOutput --blame-crash --blame-hang-timeout 5m

- name: Normalize coverage paths (GitProperties.Build)
shell: pwsh
run: >-
./.github/scripts/normalize-coverage-paths.ps1
-CoverageFile ${{ github.workspace }}/TestOutput/GitProperties.Build.Test.cobertura.xml
-RepoRoot ${{ github.workspace }}
-ProjectRelativePath src/Management/src/GitProperties.Build

- name: Upload crash/hang dumps (on failure)
if: ${{ !cancelled() && (steps.test.outcome == 'failure' || steps.test-memory-dumps.outcome == 'failure') }}
if: ${{ !cancelled() && (steps.test.outcome == 'failure' || steps.test-memory-dumps.outcome == 'failure' || steps.test-gitproperties-build.outcome == 'failure') }}
uses: actions/upload-artifact@v7
with:
name: FailedTestOutput-${{ matrix.os }}
Expand All @@ -124,7 +142,7 @@ jobs:
if-no-files-found: ignore

- name: Report test results
if: ${{ !cancelled() && (steps.test.outcome != 'skipped' || steps.test-memory-dumps.outcome != 'skipped') }}
if: ${{ !cancelled() && (steps.test.outcome != 'skipped' || steps.test-memory-dumps.outcome != 'skipped' || steps.test-gitproperties-build.outcome != 'skipped') }}
uses: dorny/test-reporter@v3
with:
name: ${{ matrix.os }} test results
Expand All @@ -136,7 +154,7 @@ jobs:
- name: Generate code coverage report
uses: danielpalme/ReportGenerator-GitHub-Action@v5
with:
reports: '**/coverage.opencover.xml'
reports: '**/coverage.opencover.xml;**/*.cobertura.xml'
targetdir: 'coveragereport'
reporttypes: 'MarkdownAssembliesSummary;MarkdownSummaryGithub'
filefilters: '-*.g.cs'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/component-shared-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ env:
DOTNET_NOLOGO: true
SOLUTION_FILE: 'src/Steeltoe.${{ inputs.component }}.slnf'
COMMON_TEST_ARGS: >-
--no-build --configuration Release --collect "XPlat Code Coverage" --logger trx --results-directory ${{ github.workspace }}/TestOutput
--settings coverlet.runsettings --blame-crash --blame-hang-timeout 1m
--no-build --configuration Release --logger trx --results-directory ${{ github.workspace }}/TestOutput
--blame-crash --blame-hang-timeout 5m

jobs:
build:
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/sonarcube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,17 @@ jobs:
docker cp src/Configuration/test/Encryption.Test/Cryptography/server.jks steeltoe-config:/workspace/server.jks
docker restart steeltoe-config

- name: Restore tools
run: dotnet tool restore --verbosity minimal

- name: Begin Sonar .NET scanner
id: sonar_begin
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >-
dotnet sonarscanner begin /k:"SteeltoeOSS_steeltoe" /o:"steeltoeoss" /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
/d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=**/coverage.opencover.xml
/d:sonar.cs.cobertura.reportsPaths=**/*.cobertura.xml

- name: Restore packages
run: dotnet restore ${{ env.SOLUTION_FILE }} --verbosity minimal /p:Configuration=Release /p:NuGetAuditLevel=low /p:WarningsNotAsErrors='${{ env.NUGET_VULNERABLE_PACKAGE_WARNINGS }}'
Expand All @@ -97,11 +101,24 @@ jobs:
run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release --verbosity minimal /p:NuGetAuditLevel=low /p:WarningsNotAsErrors='${{ env.NUGET_VULNERABLE_PACKAGE_WARNINGS }}'

- name: Test
run: dotnet test ${{ env.SOLUTION_FILE }} --filter "Category!=MemoryDumps" ${{ env.SONAR_TEST_ARGS }}
run: dotnet test ${{ env.SOLUTION_FILE }} --filter "Category!=MemoryDumps&FullyQualifiedName!~Steeltoe.Management.GitProperties.Build.Test" ${{ env.SONAR_TEST_ARGS }}

- name: Test (memory dumps)
run: dotnet test src/Management/test/Endpoint.Test --filter "Category=MemoryDumps" ${{ env.SONAR_TEST_ARGS }}

- name: Test (GitProperties.Build)
run: >-
dotnet dotnet-coverage collect -f cobertura -o ${{ github.workspace }}/TestOutput/GitProperties.Build.Test.cobertura.xml --
dotnet test src/Management/test/GitProperties.Build.Test --no-build --configuration Release --logger trx --results-directory ${{ github.workspace }}/TestOutput

- name: Normalize coverage paths (GitProperties.Build)
shell: pwsh
run: >-
./.github/scripts/normalize-coverage-paths.ps1
-CoverageFile ${{ github.workspace }}/TestOutput/GitProperties.Build.Test.cobertura.xml
-RepoRoot ${{ github.workspace }}
-ProjectRelativePath src/Management/src/GitProperties.Build

- name: End Sonar .NET scanner
if: ${{ !cancelled() && steps.sonar_begin.outcome == 'success' }}
env:
Expand Down
7 changes: 4 additions & 3 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
PackageId.targets file which brings the ConfigurationSchema.json file into the Json Schema.
-->
<PropertyGroup>
<ConfigurationSchemaGeneratorEnabled Condition="'$(ConfigurationSchemaGeneratorEnabled)' == ''">true</ConfigurationSchemaGeneratorEnabled>
<ConfigurationSchemaPath>$(MSBuildProjectDirectory)\ConfigurationSchema.json</ConfigurationSchemaPath>
<ConfigurationSchemaExists Condition="Exists('$(ConfigurationSchemaPath)')">true</ConfigurationSchemaExists>
</PropertyGroup>
Expand All @@ -39,15 +40,15 @@
<!--
Logic for generating and comparing the ConfigurationSchema.json file
-->
<PropertyGroup Condition="'$(IsPackable)' == 'true'">
<PropertyGroup Condition="'$(IsPackable)' == 'true' AND '$(ConfigurationSchemaGeneratorEnabled)' == 'true'">
<TargetsTriggeredByCompilation Condition="'$(DesignTimeBuild)' != 'true'">$(TargetsTriggeredByCompilation);GenerateConfigurationSchema</TargetsTriggeredByCompilation>

<ConfigurationSchemaGeneratorProjectPath>$(MSBuildThisFileDirectory)src\Tools\src\ConfigurationSchemaGenerator\ConfigurationSchemaGenerator.csproj</ConfigurationSchemaGeneratorProjectPath>
<ConfigurationSchemaGeneratorRspPath>$(IntermediateOutputPath)$(AsemblyName).configschema.rsp</ConfigurationSchemaGeneratorRspPath>
<ConfigurationSchemaGeneratorRspPath>$(IntermediateOutputPath)$(AssemblyName).configschema.rsp</ConfigurationSchemaGeneratorRspPath>
<GeneratedConfigurationSchemaOutputPath>$(IntermediateOutputPath)ConfigurationSchema.json</GeneratedConfigurationSchemaOutputPath>
</PropertyGroup>

<ItemGroup Condition="'$(IsPackable)' == 'true'">
<ItemGroup Condition="'$(IsPackable)' == 'true' AND '$(ConfigurationSchemaGeneratorEnabled)' == 'true'">
<!-- ensure the config generator is built -->
<ProjectReference Include="$(ConfigurationSchemaGeneratorProjectPath)"
Private="false"
Expand Down
6 changes: 5 additions & 1 deletion shared-package.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@
PackagePath="" />
</ItemGroup>

<ItemGroup>
<PropertyGroup>
<PublicApiAnalyzersEnabled Condition="'$(PublicApiAnalyzersEnabled)' == ''">true</PublicApiAnalyzersEnabled>
</PropertyGroup>

<ItemGroup Condition="'$(PublicApiAnalyzersEnabled)' == 'true'">
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="$(PublicApiAnalyzersVersion)" PrivateAssets="All" />
</ItemGroup>

Expand Down
Loading
Loading