From 5654b74444828ae40217887b8c78931c58319b44 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 18 May 2026 10:20:25 -0500 Subject: [PATCH 1/2] Address flaky tests in private fork only - Only load EFCore-dependant assemblies when scanning for DbContext types, exclude Microsoft EF Core libraries like Microsoft.EntityFrameworkCore.InMemory - Accept TimeoutException for Posgres health down - Eureka: track mock requests individually to avoid unknown unfulfilled expectations - Also apply previous EventPipeThreadDumperTest fix to all TFMs - PeriodicRefreshCanBeTurnedOnAfterStart only needs to call register more than once to succeed --- .../RelationalDatabaseHealthContributorTest.cs | 4 +++- .../test/Eureka.Test/EurekaHealthCheckHandlerTest.cs | 9 ++++++--- .../DbMigrations/DbMigrationsEndpointHandler.cs | 3 +++ .../Actuators/ThreadDump/EventPipeThreadDumperTest.cs | 10 ++-------- .../SpringBootAdminClient/HostBuilderTest.cs | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs index 42da1e5d2d..97a670e44d 100644 --- a/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs +++ b/src/Connectors/test/Connectors.Test/RelationalDatabaseHealthContributorTest.cs @@ -32,7 +32,9 @@ public async Task PostgreSQL_Not_Connected_Returns_Down_Status() result.Description.Should().Be("PostgreSQL health check failed"); result.Details.Should().Contain("host", "localhost"); result.Details.Should().Contain("service", "Example"); - result.Details.Should().ContainKey("error").WhoseValue.As().Should().StartWith("NpgsqlException: "); + + result.Details.Should().ContainKey("error").WhoseValue.As().Should().Match(error => + error.StartsWith("NpgsqlException: ", StringComparison.Ordinal) || error.StartsWith("TimeoutException: ", StringComparison.Ordinal)); } [Fact(Skip = "Integration test - Requires local PostgreSQL server")] diff --git a/src/Discovery/test/Eureka.Test/EurekaHealthCheckHandlerTest.cs b/src/Discovery/test/Eureka.Test/EurekaHealthCheckHandlerTest.cs index ee346b04b9..32917ce301 100644 --- a/src/Discovery/test/Eureka.Test/EurekaHealthCheckHandlerTest.cs +++ b/src/Discovery/test/Eureka.Test/EurekaHealthCheckHandlerTest.cs @@ -141,8 +141,10 @@ public async Task AddEurekaDiscoveryClient_uses_health_check_handler() builder.Services.AddEurekaDiscoveryClient(); var handler = new DelegateToMockHttpClientHandler(); - handler.Mock.Expect(HttpMethod.Post, "http://localhost:8761/eureka/apps/FOO").Respond(HttpStatusCode.NoContent); - handler.Mock.Expect(HttpMethod.Put, "http://localhost:8761/eureka/apps/FOO/localhost%3Afoo").Respond("application/json", "{}"); + MockedRequest registerMock = handler.Mock.When(HttpMethod.Post, "http://localhost:8761/eureka/apps/FOO").Respond(HttpStatusCode.NoContent); + + MockedRequest heartbeatMock = + handler.Mock.When(HttpMethod.Put, "http://localhost:8761/eureka/apps/FOO/localhost%3Afoo").Respond("application/json", "{}"); await using WebApplication app = builder.Build(); @@ -157,7 +159,8 @@ public async Task AddEurekaDiscoveryClient_uses_health_check_handler() var infoManager = app.Services.GetRequiredService(); infoManager.Instance.Status.Should().Be(InstanceStatus.Up); - handler.Mock.VerifyNoOutstandingExpectation(); + handler.Mock.GetMatchCount(registerMock).Should().Be(1); + handler.Mock.GetMatchCount(heartbeatMock).Should().BeGreaterThan(0); } private sealed class TestHealthContributor : IHealthContributor diff --git a/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs b/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs index 8352763605..0642fdb148 100644 --- a/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs +++ b/src/Management/src/Endpoint/Actuators/DbMigrations/DbMigrationsEndpointHandler.cs @@ -51,6 +51,9 @@ public async Task> InvokeAsync(object Type[] knownDbContextTypes = _scanner.AssemblyToScan .GetReferencedAssemblies() .Select(Assembly.Load) + .Where(assembly => Array.Exists(assembly.GetReferencedAssemblies(), reference => reference.Name == "Microsoft.EntityFrameworkCore" && !assembly + .GetName() + .Name!.StartsWith("Microsoft.EntityFrameworkCore", StringComparison.Ordinal))) .SelectMany(assembly => assembly.DefinedTypes) .Union(_scanner.AssemblyToScan.DefinedTypes) .Where(type => !type.IsAbstract && type.AsType() != dbContextType && dbContextType.GetTypeInfo() diff --git a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs index 43711fff72..e7e70e12f1 100644 --- a/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs +++ b/src/Management/test/Endpoint.Test/Actuators/ThreadDump/EventPipeThreadDumperTest.cs @@ -30,15 +30,11 @@ public async Task Can_resolve_source_location_from_pdb() using var loggerFactory = new LoggerFactory([loggerProvider]); ILogger logger = loggerFactory.CreateLogger(); -#if NET8_0 - // Use a longer collection window on .NET 8 to compensate for the Sleep(0) yield. + // Use a longer collection window to compensate for overloaded runners in CI. var optionsMonitor = TestOptionsMonitor.Create(new ThreadDumpEndpointOptions { Duration = 100 }); -#else - var optionsMonitor = new TestOptionsMonitor(); -#endif var dumper = new EventPipeThreadDumper(optionsMonitor, logger); @@ -106,10 +102,8 @@ public static void BackgroundThreadCallback(object? argument) { // Only actively-running threads are shown in the thread dump, so we need to make sure the CPU is in use. Thread.SpinWait(250); -#if NET8_0 - // Yield to allow the EventPipe rundown thread to make progress on .NET 8. + // Yield to allow the EventPipe sampler/rundown thread to make progress on loaded CI runners. Thread.Sleep(0); -#endif } } } diff --git a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs index 81dfc18dee..a3bbd1c583 100644 --- a/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs +++ b/src/Management/test/Endpoint.Test/SpringBootAdminClient/HostBuilderTest.cs @@ -205,7 +205,7 @@ public async Task PeriodicRefreshCanBeTurnedOnAfterStart() fileProvider.NotifyChanged(); await Task.Delay(500.Milliseconds(), TestContext.Current.CancellationToken); - handler.Mock.GetMatchCount(registerMock).Should().BeGreaterThan(2); + handler.Mock.GetMatchCount(registerMock).Should().BeGreaterThan(1); } [Fact] From acc145cecfc94f656fe0f3ac4557637045ce07cf Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 28 May 2026 17:04:03 -0500 Subject: [PATCH 2/2] Address Sonar complaints - Move secrets from GHA template expressions into native shell variables - use double quotes so bash var expansion can work - Add SRI integrity hash to the Bootstrap CSS CDN link in _Layout.cshtml. --- .github/workflows/package.yml | 11 ++++++++--- .github/workflows/sonarcube.yml | 4 ++-- .../RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 39dad17f94..91e4dbf21a 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -162,13 +162,16 @@ jobs: subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Sign packages + env: + AZURE_KEY_VAULT_URL: ${{ secrets.AZURE_KEY_VAULT_URL }} + AZURE_SIGN_CERTIFICATE_ID: ${{ secrets.AZURE_SIGN_CERTIFICATE_ID }} run: >- sign code azure-key-vault '**/*.nupkg' --base-directory '${{ github.workspace }}/packages' --azure-key-vault-managed-identity true --azure-credential-type 'azure-cli' - --azure-key-vault-url '${{ secrets.AZURE_KEY_VAULT_URL }}' - --azure-key-vault-certificate '${{ secrets.AZURE_SIGN_CERTIFICATE_ID }}' + --azure-key-vault-url "$AZURE_KEY_VAULT_URL" + --azure-key-vault-certificate "$AZURE_SIGN_CERTIFICATE_ID" --publisher-name 'Steeltoe' --description 'Steeltoe' --description-url 'https://steeltoe.io/' @@ -249,7 +252,9 @@ jobs: path: packages - name: Push packages to nuget.org - run: dotnet nuget push '${{ github.workspace }}/packages/*.nupkg' --skip-duplicate --api-key '${{ secrets.STEELTOE_NUGET_API_KEY }}' --source 'nuget.org' + env: + STEELTOE_NUGET_API_KEY: ${{ secrets.STEELTOE_NUGET_API_KEY }} + run: dotnet nuget push '${{ github.workspace }}/packages/*.nupkg' --skip-duplicate --api-key "$STEELTOE_NUGET_API_KEY" --source 'nuget.org' open_pr: name: Open pull request to bump Steeltoe version after stable release diff --git a/.github/workflows/sonarcube.yml b/.github/workflows/sonarcube.yml index cf7f52bc8c..38f397559f 100644 --- a/.github/workflows/sonarcube.yml +++ b/.github/workflows/sonarcube.yml @@ -87,7 +87,7 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: >- - dotnet sonarscanner begin /k:"SteeltoeOSS_steeltoe" /o:"steeltoeoss" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + dotnet sonarscanner begin /k:"SteeltoeOSS_steeltoe" /o:"steeltoeoss" /d:sonar.token="$SONAR_TOKEN" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=**/coverage.opencover.xml - name: Restore packages @@ -106,4 +106,4 @@ jobs: if: ${{ !cancelled() && steps.sonar_begin.outcome == 'success' }} env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + run: dotnet sonarscanner end /d:sonar.token="$SONAR_TOKEN" diff --git a/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml b/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml index 4748f9de45..d46fa4ccc7 100644 --- a/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml +++ b/src/Management/test/RazorPagesTestWebApp/Pages/Shared/_Layout.cshtml @@ -4,7 +4,7 @@ @ViewData["Title"] - Steeltoe.Management.Endpoint.RazorPagesTestWebApp - +