From e694fca77af996e6250720451326eb10d345545d Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Thu, 3 Apr 2025 21:47:05 +0200 Subject: [PATCH 01/13] :construction_worker: include test --- .github/workflows/pipelines.yml | 91 ++++++++++++++------------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/.github/workflows/pipelines.yml b/.github/workflows/pipelines.yml index 2f4dfa2..608db0c 100644 --- a/.github/workflows/pipelines.yml +++ b/.github/workflows/pipelines.yml @@ -21,7 +21,7 @@ on: jobs: build: name: 🛠️ Build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: configuration: [Debug, Release] @@ -62,7 +62,7 @@ jobs: pack: name: 📦 Pack - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: configuration: [Debug, Release] @@ -80,10 +80,16 @@ jobs: uploadPackedArtifact: true version: ${{ needs.build.outputs.version }} - sonarcloud: - name: 🔬 Code Quality Analysis + test: + name: 🧪 Test needs: [build] - runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, windows-2022] + configuration: [Debug, Release] + runs-on: ${{ matrix.os }} + timeout-minutes: 15 steps: - name: Checkout uses: codebeltnet/git-checkout@v1 @@ -93,64 +99,43 @@ jobs: with: includePreview: true - - name: Install .NET Tool - Sonar Scanner - uses: codebeltnet/dotnet-tool-install-sonarscanner@v1 - - - name: Restore Dependencies - uses: codebeltnet/dotnet-restore@v2 - - - name: Run SonarCloud Analysis - uses: codebeltnet/sonarcloud-scan@v1 - with: - token: ${{ secrets.SONAR_TOKEN }} - organization: geekle - projectKey: bootstrapper - version: ${{ needs.build.outputs.version }} + - name: Install .NET Tool - Report Generator + uses: codebeltnet/dotnet-tool-install-reportgenerator@v1 - - name: Build - uses: codebeltnet/dotnet-build@v2 + - name: Test with ${{ matrix.configuration }} build + uses: codebeltnet/dotnet-test@v3 with: + configuration: ${{ matrix.configuration }} buildSwitches: -p:SkipSignAssembly=true - uploadBuildArtifact: false - - name: Finalize SonarCloud Analysis - uses: codebeltnet/sonarcloud-scan-finalize@v1 - with: - token: ${{ secrets.SONAR_TOKEN }} + sonarcloud: + name: call-sonarcloud + needs: [build,test] + uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v1 + with: + organization: geekle + projectKey: bootstrapper + version: ${{ needs.build.outputs.version }} + secrets: inherit + + codecov: + name: call-codecov + needs: [build,test] + uses: codebeltnet/jobs-codecov/.github/workflows/default.yml@v1 + with: + repository: codebeltnet/bootstrapper + secrets: inherit codeql: - name: 🛡️ Security Analysis - needs: [build] - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: codebeltnet/git-checkout@v1 - - - name: Install .NET - uses: codebeltnet/install-dotnet@v1 - with: - includePreview: true - - - name: Restore Dependencies - uses: codebeltnet/dotnet-restore@v2 - - - name: Prepare CodeQL SAST Analysis - uses: codebeltnet/codeql-scan@v1 - - - name: Build - uses: codebeltnet/dotnet-build@v2 - with: - buildSwitches: -p:SkipSignAssembly=true - uploadBuildArtifact: false - - - name: Finalize CodeQL SAST Analysis - uses: codebeltnet/codeql-scan-finalize@v1 + name: call-codeql + needs: [build,test] + uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1 deploy: if: github.event_name != 'pull_request' name: 🚀 Deploy v${{ needs.build.outputs.version }} - runs-on: ubuntu-22.04 - needs: [build,pack,sonarcloud,codeql] + runs-on: ubuntu-24.04 + needs: [build, pack, test, sonarcloud, codecov, codeql] environment: Production steps: - uses: codebeltnet/nuget-push@v1 From 1e8c2344d4f747d93c2bb03551010f2bffe70e76 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Thu, 3 Apr 2025 21:48:02 +0200 Subject: [PATCH 02/13] :white_check_mark: first batch of testing --- Codebelt.Bootstrapper.sln | 16 +++ .../Assets/TestFixture.cs | 35 ++++++ .../Assets/TestStartup.cs | 17 +++ .../BootstrapperLifetimeTest.cs | 101 ++++++++++++++++++ ...debelt.Bootstrapper.FunctionalTests.csproj | 17 +++ .../Assets/StartupRootUnsafeAccessor.cs | 15 +++ .../Assets/TestStartupRoot.cs | 20 ++++ .../Codebelt.Bootstrapper.Tests.csproj | 11 ++ .../StartupRootTest.cs | 81 ++++++++++++++ 9 files changed, 313 insertions(+) create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj create mode 100644 test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs create mode 100644 test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs create mode 100644 test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj create mode 100644 test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs diff --git a/Codebelt.Bootstrapper.sln b/Codebelt.Bootstrapper.sln index 9922c94..e4684f1 100644 --- a/Codebelt.Bootstrapper.sln +++ b/Codebelt.Bootstrapper.sln @@ -39,6 +39,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.Minim EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.MinimalWorker.App", "app\Codebelt.Bootstrapper.MinimalWorker.App\Codebelt.Bootstrapper.MinimalWorker.App.csproj", "{E87D127B-4CD1-4009-962E-EEF70A522C13}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.Tests", "test\Codebelt.Bootstrapper.Tests\Codebelt.Bootstrapper.Tests.csproj", "{CC656409-7479-4553-8E9B-0854801E1590}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.FunctionalTests", "test\Codebelt.Bootstrapper.FunctionalTests\Codebelt.Bootstrapper.FunctionalTests.csproj", "{8970491F-E0BD-489D-AA7E-617A67C2E39F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -109,6 +115,14 @@ Global {E87D127B-4CD1-4009-962E-EEF70A522C13}.Debug|Any CPU.Build.0 = Debug|Any CPU {E87D127B-4CD1-4009-962E-EEF70A522C13}.Release|Any CPU.ActiveCfg = Release|Any CPU {E87D127B-4CD1-4009-962E-EEF70A522C13}.Release|Any CPU.Build.0 = Release|Any CPU + {CC656409-7479-4553-8E9B-0854801E1590}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC656409-7479-4553-8E9B-0854801E1590}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC656409-7479-4553-8E9B-0854801E1590}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC656409-7479-4553-8E9B-0854801E1590}.Release|Any CPU.Build.0 = Release|Any CPU + {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -130,6 +144,8 @@ Global {3C43A01F-51E7-4166-91D5-E2088E68F4E5} = {28409021-5670-4008-9061-DBAAA0FC4DA6} {497AD6CD-1E7E-4B4D-ACE5-CDD005A14F96} = {28409021-5670-4008-9061-DBAAA0FC4DA6} {E87D127B-4CD1-4009-962E-EEF70A522C13} = {28409021-5670-4008-9061-DBAAA0FC4DA6} + {CC656409-7479-4553-8E9B-0854801E1590} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {8970491F-E0BD-489D-AA7E-617A67C2E39F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {72FB037E-3629-4CDB-812E-D577A3D4FD26} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs new file mode 100644 index 0000000..52f0c27 --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Codebelt.Extensions.Xunit.Hosting; +using Codebelt.Extensions.Xunit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Bootstrapper.Assets +{ + public class TestHostFixture : HostFixture + { + public override void ConfigureHost(Test hostTest) + { + var hb = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((context, config) => + { + ConfigureCallback(config.Build(), context.HostingEnvironment); + }) + .ConfigureServices((context, services) => + { + Configuration = context.Configuration; + HostingEnvironment = context.HostingEnvironment; + ConfigureServicesCallback(services); + }) + .ConfigureHostConfiguration(builder => + { + builder.AddInMemoryCollection(new Dictionary + { + { HostDefaults.ApplicationKey, hostTest.CallerType.Assembly.GetName().Name } + }); + }); + ConfigureHostCallback(hb); + Host = hb.Build(); + } + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs new file mode 100644 index 0000000..d8461ab --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestStartup.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Bootstrapper.Assets +{ + public class TestStartup : StartupRoot + { + public TestStartup(IConfiguration configuration, IHostEnvironment environment) : base(configuration, environment) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + } + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs new file mode 100644 index 0000000..a4b7522 --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Codebelt.Bootstrapper.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Bootstrapper +{ + public class BootstrapperLifetimeTest : Test + { + public BootstrapperLifetimeTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void BootstrapperLifetime_ShouldBeRegistered() + { + + using var test = GenericHostTestFactory.Create(services => + { + + }, hb => + { + hb.UseBootstrapperLifetime(); + hb.UseBootstrapperStartup(); + }, new TestHostFixture()); + + var bootstrapperLifetime = test.ServiceProvider.GetService(); + Assert.NotNull(bootstrapperLifetime); + Assert.IsType(bootstrapperLifetime); + } + + [Fact] + public void BootstrapperLifetime_ShouldCallOnApplicationStarted() + { + var started = false; + BootstrapperLifetime.OnApplicationStartedCallback = () => { started = true; }; + + using var test = GenericHostTestFactory.Create(services => + { + services.AddXunitTestLogging(TestOutput); + }, hb => + { + hb.UseBootstrapperLifetime(); + hb.UseBootstrapperStartup(); + }, new TestHostFixture()); + + test.Host.Start(); + + Assert.True(started); + } + + [Fact] + public void BootstrapperLifetime_ShouldCallOnApplicationStoppingCallback_And_OnApplicationStoppedCallback() + { + var stopped = false; + var stopping = false; + + BootstrapperLifetime.OnApplicationStoppingCallback = () => { stopping = true; }; + BootstrapperLifetime.OnApplicationStoppedCallback = () => { stopped = true; }; + + using var test = GenericHostTestFactory.Create(services => + { + services.AddXunitTestLogging(TestOutput); + }, hb => + { + hb.UseBootstrapperLifetime(); + hb.UseBootstrapperStartup(); + }, new TestHostFixture()); + + test.Host.Start(); + test.Host.StopAsync().GetAwaiter().GetResult(); + + Assert.True(stopping && stopped); + } + + + + //public override void ConfigureServices(IServiceCollection services) + //{ + // services.AddXunitTestLoggingOutputHelperAccessor(); + // services.AddXunitTestLogging(TestOutput); + //} + + //protected override void ConfigureHost(IHostBuilder hb) + //{ + // BootstrapperLifetime.OnApplicationStartedCallback = () => { _started = true; }; + // BootstrapperLifetime.OnApplicationStoppingCallback = () => { _stopping = true; }; + // BootstrapperLifetime.OnApplicationStoppedCallback = () => { _stopped = true; }; + // hb.UseBootstrapperLifetime(); + // hb.UseBootstrapperStartup(); + //} + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj new file mode 100644 index 0000000..e778aeb --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj @@ -0,0 +1,17 @@ + + + + Codebelt.Bootstrapper + + + + + + + + + + + + + diff --git a/test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs b/test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs new file mode 100644 index 0000000..fca1272 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Tests/Assets/StartupRootUnsafeAccessor.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Bootstrapper.Assets +{ + public static class StartupRootUnsafeAccessor + { + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_Configuration")] + public static extern IConfiguration GetConfiguration(StartupRoot startup); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_Environment")] + public static extern IHostEnvironment GetEnvironment(StartupRoot startup); + } +} diff --git a/test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs b/test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs new file mode 100644 index 0000000..34df4c4 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Tests/Assets/TestStartupRoot.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Bootstrapper.Assets +{ + public class TestStartupRoot : StartupRoot + { + public TestStartupRoot(IConfiguration configuration, IHostEnvironment environment) + : base(configuration, environment) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + // Add test services here + services.AddSingleton("TestService"); + } + } +} diff --git a/test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj b/test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj new file mode 100644 index 0000000..dc95b98 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Tests/Codebelt.Bootstrapper.Tests.csproj @@ -0,0 +1,11 @@ + + + + Codebelt.Bootstrapper + + + + + + + diff --git a/test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs b/test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs new file mode 100644 index 0000000..65d5471 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Tests/StartupRootTest.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.IO; +using Codebelt.Bootstrapper.Assets; +using Codebelt.Extensions.Xunit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.Internal; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Bootstrapper +{ + public class StartupRootTest : Test + { + private readonly IConfiguration _configuration; + private readonly IHostEnvironment _environment; + + public StartupRootTest(ITestOutputHelper output) : base(output) + { + var inMemorySettings = new Dictionary { + {"TestKey", "TestValue"} + }; + + _configuration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings) + .Build(); + + _environment = new HostingEnvironment() + { + EnvironmentName = Environments.Development, + ApplicationName = "TestApp", + ContentRootPath = Directory.GetCurrentDirectory() + }; + } + + [Fact] + public void ConfigurationProperty_ShouldReturnInjectedConfiguration() + { + // Arrange + var startup = new TestStartupRoot(_configuration, _environment); + + // Act + var configuration = StartupRootUnsafeAccessor.GetConfiguration(startup); + + // Assert + Assert.Equal(_configuration, configuration); + } + + + + [Fact] + public void EnvironmentProperty_ShouldReturnInjectedEnvironment() + { + // Arrange + var startup = new TestStartupRoot(_configuration, _environment); + + // Act + var environment = StartupRootUnsafeAccessor.GetEnvironment(startup); + + // Assert + Assert.Equal(_environment, environment); + } + + [Fact] + public void ConfigureServices_ShouldAddServicesToServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var startup = new TestStartupRoot(_configuration, _environment); + + // Act + startup.ConfigureServices(services); + var serviceProvider = services.BuildServiceProvider(); + var testService = serviceProvider.GetService(); + + // Assert + Assert.Equal("TestService", testService); + } + } +} From 5ee6616f044e74c1baf2041b659a5aa9c45dcacf Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Thu, 3 Apr 2025 21:48:32 +0200 Subject: [PATCH 03/13] :arrow_up: bump dependencies --- Directory.Build.props | 2 +- Directory.Packages.props | 24 ++++++++++++------------ testenvironments.json | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 testenvironments.json diff --git a/Directory.Build.props b/Directory.Build.props index c107324..6e78383 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -73,6 +73,6 @@ - + diff --git a/Directory.Packages.props b/Directory.Packages.props index c60caa3..a26b3ba 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,26 +4,26 @@ - - - - + + + + - + - - - - - + + + + + - - + + diff --git a/testenvironments.json b/testenvironments.json new file mode 100644 index 0000000..328e82b --- /dev/null +++ b/testenvironments.json @@ -0,0 +1,15 @@ +{ + "version": "1", + "environments": [ + { + "name": "WSL-Ubuntu", + "type": "wsl", + "wslDistribution": "Ubuntu-24.04" + }, + { + "name": "Docker-Ubuntu", + "type": "docker", + "dockerImage": "gimlichael/ubuntu-testrunner:net8.0.407-9.0.202" + } + ] +} From 7d37dac110d2b48efdd152e7fc882dd802d78d00 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 4 Apr 2025 15:46:59 +0200 Subject: [PATCH 04/13] :white_check_mark: further test coverage of bootstrapper --- .../Assets/TestBackgroundService.cs | 32 +++++++++++ .../BootstrapperLifetimeTest.cs | 55 +++---------------- .../HostApplicationBuilderExtensionsTest.cs | 36 ++++++++++++ .../HostBuilderExtensionsTest.cs | 49 +++++++++++++++++ .../HostedServiceExtensionsTest.cs | 49 +++++++++++++++++ 5 files changed, 174 insertions(+), 47 deletions(-) create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs create mode 100644 test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs new file mode 100644 index 0000000..eb6790e --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Codebelt.Bootstrapper.Assets +{ + public class TestBackgroundService : BackgroundService + { + private readonly ILogger _logger; + + public TestBackgroundService(ILogger logger) + { + _logger = logger; + + BootstrapperLifetime.OnApplicationStartedCallback += () => logger.LogInformation("TestBackgroundService started"); + } + + public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var sw = Stopwatch.StartNew(); + await this.WaitForApplicationStartedAnnouncementAsync(stoppingToken).ConfigureAwait(false); + sw.Stop(); + Elapsed = sw.Elapsed; + _logger.LogInformation("TestBackgroundService started after {Elapsed}", Elapsed); + } + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs index a4b7522..cced1cf 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Codebelt.Bootstrapper.Assets; +using Codebelt.Bootstrapper.Assets; using Codebelt.Extensions.Xunit; using Codebelt.Extensions.Xunit.Hosting; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; using Xunit.Abstractions; @@ -20,31 +14,14 @@ public BootstrapperLifetimeTest(ITestOutputHelper output) : base(output) } [Fact] - public void BootstrapperLifetime_ShouldBeRegistered() - { - - using var test = GenericHostTestFactory.Create(services => - { - - }, hb => - { - hb.UseBootstrapperLifetime(); - hb.UseBootstrapperStartup(); - }, new TestHostFixture()); - - var bootstrapperLifetime = test.ServiceProvider.GetService(); - Assert.NotNull(bootstrapperLifetime); - Assert.IsType(bootstrapperLifetime); - } - - [Fact] - public void BootstrapperLifetime_ShouldCallOnApplicationStarted() + public void OnApplicationStartedCallback_ShouldBeInvokedWhenStartingHost() { var started = false; - BootstrapperLifetime.OnApplicationStartedCallback = () => { started = true; }; + BootstrapperLifetime.OnApplicationStartedCallback += () => { started = true; }; using var test = GenericHostTestFactory.Create(services => { + services.AddXunitTestLoggingOutputHelperAccessor(); services.AddXunitTestLogging(TestOutput); }, hb => { @@ -58,16 +35,17 @@ public void BootstrapperLifetime_ShouldCallOnApplicationStarted() } [Fact] - public void BootstrapperLifetime_ShouldCallOnApplicationStoppingCallback_And_OnApplicationStoppedCallback() + public void OnApplicationStoppingCallback_OnApplicationStoppedCallback_ShouldBeInvokedWhenStoppingHost() { var stopped = false; var stopping = false; - BootstrapperLifetime.OnApplicationStoppingCallback = () => { stopping = true; }; - BootstrapperLifetime.OnApplicationStoppedCallback = () => { stopped = true; }; + BootstrapperLifetime.OnApplicationStoppingCallback += () => { stopping = true; }; + BootstrapperLifetime.OnApplicationStoppedCallback += () => { stopped = true; }; using var test = GenericHostTestFactory.Create(services => { + services.AddXunitTestLoggingOutputHelperAccessor(); services.AddXunitTestLogging(TestOutput); }, hb => { @@ -80,22 +58,5 @@ public void BootstrapperLifetime_ShouldCallOnApplicationStoppingCallback_And_OnA Assert.True(stopping && stopped); } - - - - //public override void ConfigureServices(IServiceCollection services) - //{ - // services.AddXunitTestLoggingOutputHelperAccessor(); - // services.AddXunitTestLogging(TestOutput); - //} - - //protected override void ConfigureHost(IHostBuilder hb) - //{ - // BootstrapperLifetime.OnApplicationStartedCallback = () => { _started = true; }; - // BootstrapperLifetime.OnApplicationStoppingCallback = () => { _stopping = true; }; - // BootstrapperLifetime.OnApplicationStoppedCallback = () => { _stopped = true; }; - // hb.UseBootstrapperLifetime(); - // hb.UseBootstrapperStartup(); - //} } } diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs new file mode 100644 index 0000000..0b7c598 --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Codebelt.Bootstrapper.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; +using static System.Net.Mime.MediaTypeNames; + +namespace Codebelt.Bootstrapper +{ + public class HostApplicationBuilderExtensionsTest : Test + { + public HostApplicationBuilderExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime() + { + var host = Host.CreateApplicationBuilder() + .UseBootstrapperLifetime() + .Build(); + + var bootstrapperLifetime = host.Services.GetService(); + + Assert.NotNull(bootstrapperLifetime); + Assert.IsType(bootstrapperLifetime); + } + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs new file mode 100644 index 0000000..defe9b4 --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs @@ -0,0 +1,49 @@ +using Codebelt.Bootstrapper.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Bootstrapper +{ + public class HostBuilderExtensionsTest : Test + { + public HostBuilderExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime() + { + using var test = GenericHostTestFactory.Create(services => + { + }, hb => + { + hb.UseBootstrapperLifetime(); + }, new TestHostFixture()); + + var bootstrapperLifetime = test.ServiceProvider.GetService(); + + Assert.NotNull(bootstrapperLifetime); + Assert.IsType(bootstrapperLifetime); + } + + [Fact] + public void UseBootstrapperStartup_ShouldRegisterStartupFactory() + { + using var test = GenericHostTestFactory.Create(services => + { + }, hb => + { + hb.UseBootstrapperStartup(); + }, new TestHostFixture()); + + var startupFactory = test.ServiceProvider.GetService>(); + + Assert.NotNull(startupFactory); + Assert.IsType>(startupFactory); + } + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs new file mode 100644 index 0000000..b95bda1 --- /dev/null +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Codebelt.Bootstrapper.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Bootstrapper +{ + public class HostedServiceExtensionsTest : Test + { + public HostedServiceExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task WaitForApplicationStartedAnnouncementAsync_MustWaitForApplicationStarted() + { + var timeToWait = TimeSpan.FromMilliseconds(50); + var started = false; + BootstrapperLifetime.OnApplicationStartedCallback += () => + { + Thread.Sleep(timeToWait); + }; + + using var test = GenericHostTestFactory.Create(services => + { + services.AddXunitTestLoggingOutputHelperAccessor(); + services.AddXunitTestLogging(TestOutput); + services.AddHostedService(); + }, hb => + { + hb.UseBootstrapperLifetime(); + }, new TestHostFixture()); + + var bgs = test.ServiceProvider.GetRequiredService() as TestBackgroundService; + + await test.Host.StartAsync().ConfigureAwait(false); + + await Task.Delay(timeToWait).ConfigureAwait(false); + + Assert.True(bgs.Elapsed >= timeToWait, $"{bgs.Elapsed} >= {timeToWait}"); + } + } +} From 6cd2c44abd5d4490874dee9298d7bc2b79335e47 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 4 Apr 2025 16:01:57 +0200 Subject: [PATCH 05/13] :art: reordered callbacks --- .../BootstrapperLifetimeTest.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs index cced1cf..58b1e38 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs @@ -1,4 +1,6 @@ -using Codebelt.Bootstrapper.Assets; +using System; +using System.Threading; +using Codebelt.Bootstrapper.Assets; using Codebelt.Extensions.Xunit; using Codebelt.Extensions.Xunit.Hosting; using Microsoft.Extensions.Hosting; @@ -9,16 +11,20 @@ namespace Codebelt.Bootstrapper { public class BootstrapperLifetimeTest : Test { + private bool _started = false; + private bool _stopping = false; + private bool _stopped = false; + public BootstrapperLifetimeTest(ITestOutputHelper output) : base(output) { + BootstrapperLifetime.OnApplicationStartedCallback += () => { _started = true; }; + BootstrapperLifetime.OnApplicationStoppingCallback += () => { _stopping = true; }; + BootstrapperLifetime.OnApplicationStoppedCallback += () => { _stopped = true; }; } [Fact] public void OnApplicationStartedCallback_ShouldBeInvokedWhenStartingHost() { - var started = false; - BootstrapperLifetime.OnApplicationStartedCallback += () => { started = true; }; - using var test = GenericHostTestFactory.Create(services => { services.AddXunitTestLoggingOutputHelperAccessor(); @@ -31,18 +37,12 @@ public void OnApplicationStartedCallback_ShouldBeInvokedWhenStartingHost() test.Host.Start(); - Assert.True(started); + Assert.True(_started); } [Fact] public void OnApplicationStoppingCallback_OnApplicationStoppedCallback_ShouldBeInvokedWhenStoppingHost() { - var stopped = false; - var stopping = false; - - BootstrapperLifetime.OnApplicationStoppingCallback += () => { stopping = true; }; - BootstrapperLifetime.OnApplicationStoppedCallback += () => { stopped = true; }; - using var test = GenericHostTestFactory.Create(services => { services.AddXunitTestLoggingOutputHelperAccessor(); @@ -56,7 +56,7 @@ public void OnApplicationStoppingCallback_OnApplicationStoppedCallback_ShouldBeI test.Host.Start(); test.Host.StopAsync().GetAwaiter().GetResult(); - Assert.True(stopping && stopped); + Assert.True(_stopping && _stopped); } } } From c4c78c940d913c1a314c55c1931879b6bd2b4c6b Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 4 Apr 2025 16:13:20 +0200 Subject: [PATCH 06/13] :bug: prevent parallel run of xunit --- .../Assets/TestBackgroundService.cs | 2 -- .../BootstrapperLifetimeTest.cs | 6 +++--- .../Codebelt.Bootstrapper.FunctionalTests.csproj | 8 +++++--- .../HostedServiceExtensionsTest.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs index eb6790e..ceb5176 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs @@ -14,8 +14,6 @@ public class TestBackgroundService : BackgroundService public TestBackgroundService(ILogger logger) { _logger = logger; - - BootstrapperLifetime.OnApplicationStartedCallback += () => logger.LogInformation("TestBackgroundService started"); } public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero; diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs index 58b1e38..79d35fb 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs @@ -17,9 +17,9 @@ public class BootstrapperLifetimeTest : Test public BootstrapperLifetimeTest(ITestOutputHelper output) : base(output) { - BootstrapperLifetime.OnApplicationStartedCallback += () => { _started = true; }; - BootstrapperLifetime.OnApplicationStoppingCallback += () => { _stopping = true; }; - BootstrapperLifetime.OnApplicationStoppedCallback += () => { _stopped = true; }; + BootstrapperLifetime.OnApplicationStartedCallback = () => { _started = true; }; + BootstrapperLifetime.OnApplicationStoppingCallback = () => { _stopping = true; }; + BootstrapperLifetime.OnApplicationStoppedCallback = () => { _stopped = true; }; } [Fact] diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj index e778aeb..5e9152a 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj @@ -4,10 +4,12 @@ Codebelt.Bootstrapper + - - - + + <_Parameter1>DisableTestParallelization = true + <_Parameter1_IsLiteral>true + diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs index b95bda1..a830336 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs @@ -22,7 +22,7 @@ public async Task WaitForApplicationStartedAnnouncementAsync_MustWaitForApplicat { var timeToWait = TimeSpan.FromMilliseconds(50); var started = false; - BootstrapperLifetime.OnApplicationStartedCallback += () => + BootstrapperLifetime.OnApplicationStartedCallback = () => { Thread.Sleep(timeToWait); }; From abb4691ee734cda0e1526002a964df5496d97d2b Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 20:44:34 +0200 Subject: [PATCH 07/13] :building_construction: changed static callback to instance callbacks including consequence changes --- .../ConsoleHostedService.cs | 61 ++++++++++--------- .../MinimalConsoleHostedService.cs | 51 +++++++++------- .../BootstrapperLifetime.cs | 30 ++++----- .../HostBuilderExtensions.cs | 1 + .../HostedServiceExtensions.cs | 25 -------- .../IHostLifetimeEvents.cs | 25 ++++++++ 6 files changed, 96 insertions(+), 97 deletions(-) delete mode 100644 src/Codebelt.Bootstrapper/HostedServiceExtensions.cs create mode 100644 src/Codebelt.Bootstrapper/IHostLifetimeEvents.cs diff --git a/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs b/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs index 39d92c2..02f3396 100644 --- a/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs +++ b/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs @@ -20,18 +20,21 @@ public class ConsoleHostedService : IHostedService where TStartup : Co private ILogger _logger; private bool _ranToCompletion; private Task _runAsyncTask; + private readonly IHostLifetimeEvents _events; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The dependency injected . - /// The dependency injected . - /// The dependency injected . - public ConsoleHostedService(IStartupFactory factory, IHostApplicationLifetime applicationLifetime, IServiceProvider provider) + /// The dependency injected . + /// The dependency injected . + /// The dependency injected . + /// The dependency injected . + public ConsoleHostedService(IStartupFactory factory, IHostApplicationLifetime applicationLifetime, IServiceProvider provider, IHostLifetimeEvents events) { _factory = factory; _applicationLifetime = applicationLifetime; _provider = provider; + _events = events; } /// @@ -41,36 +44,34 @@ public ConsoleHostedService(IStartupFactory factory, IHostApplicationL /// A that represents the asynchronous operation. public Task StartAsync(CancellationToken cancellationToken) { - var startup = _factory.Instance; - _logger = _provider.GetRequiredService>(); - - _runAsyncTask = Task.Run(async () => + var startup = _factory.Instance; + if (startup != null) { - try + startup.ConfigureConsole(_provider); + _events.OnApplicationStartedCallback += () => { - if (startup != null) - { - startup.ConfigureConsole(_provider); - - await this.WaitForApplicationStartedAnnouncementAsync(cancellationToken).ConfigureAwait(false); - - _logger.LogInformation("RunAsync started."); - await startup.RunAsync(_provider, cancellationToken).ConfigureAwait(false); - _ranToCompletion = true; - } - else + _runAsyncTask = Task.Run(async () => { - _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", typeof(TStartup).FullName); - } - } - catch (Exception e) - { - _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", typeof(TStartup).FullName); - } - }, cancellationToken); + try + { + _logger.LogInformation("RunAsync started."); + await startup.RunAsync(_provider, cancellationToken).ConfigureAwait(false); + _ranToCompletion = true; + } + catch (Exception e) + { + _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", typeof(TStartup).FullName); + } + }, cancellationToken); - StartWaitForCompletionOfRunAsync().ConfigureAwait(false); + StartWaitForCompletionOfRunAsync().ConfigureAwait(false); + }; + } + else + { + _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", typeof(TStartup).FullName); + } return Task.CompletedTask; } diff --git a/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs b/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs index 1f4c3e0..67e68c9 100644 --- a/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs +++ b/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs @@ -16,9 +16,10 @@ public class MinimalConsoleHostedService : IHostedService private readonly IProgramFactory _factory; private readonly IHostApplicationLifetime _applicationLifetime; private readonly IServiceProvider _provider; - private ILogger _logger; + private ILogger _logger; private bool _ranToCompletion; private Task _runAsyncTask; + private readonly IHostLifetimeEvents _events; /// /// Initializes a new instance of the class. @@ -26,11 +27,13 @@ public class MinimalConsoleHostedService : IHostedService /// The dependency injected . /// The dependency injected . /// The dependency injected . - public MinimalConsoleHostedService(IProgramFactory factory, IHostApplicationLifetime applicationLifetime, IServiceProvider provider) + /// The dependency injected . + public MinimalConsoleHostedService(IProgramFactory factory, IHostApplicationLifetime applicationLifetime, IServiceProvider provider, IHostLifetimeEvents events) { _factory = factory; _applicationLifetime = applicationLifetime; _provider = provider; + _events = events; } /// @@ -40,35 +43,37 @@ public MinimalConsoleHostedService(IProgramFactory factory, IHostApplicationLife /// A that represents the asynchronous operation. public Task StartAsync(CancellationToken cancellationToken) { - var program = _factory.Instance; - var programType = program?.GetType() ?? typeof(MinimalConsoleProgram); + _events.OnApplicationStartedCallback += () => + { + var program = _factory.Instance; + var programType = program?.GetType() ?? typeof(MinimalConsoleProgram); + var loggerType = typeof(ILogger<>).MakeGenericType(programType); - _logger = _provider.GetRequiredService>(); + _logger = _provider.GetRequiredService(loggerType) as ILogger; - _runAsyncTask = Task.Run(async () => - { - try + _runAsyncTask = Task.Run(async () => { - if (program != null) + try { - await this.WaitForApplicationStartedAnnouncementAsync(cancellationToken).ConfigureAwait(false); - - _logger.LogInformation("RunAsync started."); - await program.RunAsync(_provider, cancellationToken).ConfigureAwait(false); - _ranToCompletion = true; + if (program != null) + { + _logger.LogInformation("RunAsync started."); + await program.RunAsync(_provider, cancellationToken).ConfigureAwait(false); + _ranToCompletion = true; + } + else + { + _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", programType.FullName); + } } - else + catch (Exception e) { - _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", programType.FullName); + _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", programType.FullName); } - } - catch (Exception e) - { - _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", programType.FullName); - } - }, cancellationToken); + }, cancellationToken); - StartWaitForCompletionOfRunAsync().ConfigureAwait(false); + StartWaitForCompletionOfRunAsync().ConfigureAwait(false); + }; return Task.CompletedTask; } diff --git a/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs b/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs index 375c039..24a5160 100644 --- a/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs +++ b/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs @@ -14,28 +14,24 @@ namespace Codebelt.Bootstrapper /// /// /// - public class BootstrapperLifetime : Disposable, IHostLifetime + public class BootstrapperLifetime : Disposable, IHostLifetime, IHostLifetimeEvents { - private readonly ConsoleLifetimeOptions _options; private readonly ConsoleLifetime _hostLifetime; private readonly IHostApplicationLifetime _applicationLifetime; /// /// Triggered when the application host has fully started. /// - public static Action OnApplicationStartedCallback { get; set; } + public Action OnApplicationStartedCallback { get; set; } /// /// Triggered when the application host is starting a graceful shutdown. - /// Shutdown will block until all callbacks registered on this token have completed. /// - public static Action OnApplicationStoppingCallback { get; set; } + public Action OnApplicationStoppingCallback { get; set; } /// - /// Triggered when the application host has completed a graceful shutdown. - /// The application will not exit until all callbacks registered on this token have completed. - /// - public static Action OnApplicationStoppedCallback { get; set; } + /// Triggered when the application host has completed a graceful shutdown./// + public Action OnApplicationStoppedCallback { get; set; } /// /// Initializes a new instance of the class. @@ -49,7 +45,6 @@ public BootstrapperLifetime(IOptions options, IHostEnvir { _hostLifetime = new ConsoleLifetime(options, environment, applicationLifetime, hostOptions, loggerFactory); _applicationLifetime = applicationLifetime; - _options = options.Value; } /// @@ -70,26 +65,23 @@ public Task StopAsync(CancellationToken cancellationToken) /// A that represents the asynchronous operation. public Task WaitForStartAsync(CancellationToken cancellationToken) { - if (!_options.SuppressStatusMessages) - { - _applicationLifetime.ApplicationStarted.Register(OnApplicationStarted); - _applicationLifetime.ApplicationStopped.Register(OnApplicationStopped); - _applicationLifetime.ApplicationStopping.Register(OnApplicationStopping); - } + _applicationLifetime.ApplicationStarted.Register(OnApplicationStarted); + _applicationLifetime.ApplicationStopped.Register(OnApplicationStopped); + _applicationLifetime.ApplicationStopping.Register(OnApplicationStopping); return _hostLifetime.WaitForStartAsync(cancellationToken); } - private static void OnApplicationStarted() + private void OnApplicationStarted() { OnApplicationStartedCallback?.Invoke(); } - private static void OnApplicationStopped() + private void OnApplicationStopped() { OnApplicationStoppedCallback?.Invoke(); } - private static void OnApplicationStopping() + private void OnApplicationStopping() { OnApplicationStoppingCallback?.Invoke(); } diff --git a/src/Codebelt.Bootstrapper/HostBuilderExtensions.cs b/src/Codebelt.Bootstrapper/HostBuilderExtensions.cs index d449814..b1d2475 100644 --- a/src/Codebelt.Bootstrapper/HostBuilderExtensions.cs +++ b/src/Codebelt.Bootstrapper/HostBuilderExtensions.cs @@ -21,6 +21,7 @@ public static IHostBuilder UseBootstrapperLifetime(this IHostBuilder hostBuilder return hostBuilder.ConfigureServices(services => { services.Replace(ServiceDescriptor.Singleton()); + services.AddSingleton(provider => provider.GetRequiredService() as BootstrapperLifetime); }); } diff --git a/src/Codebelt.Bootstrapper/HostedServiceExtensions.cs b/src/Codebelt.Bootstrapper/HostedServiceExtensions.cs deleted file mode 100644 index 2184e69..0000000 --- a/src/Codebelt.Bootstrapper/HostedServiceExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; - -namespace Codebelt.Bootstrapper -{ - /// - /// Extension methods for the . - /// - public static class HostedServiceExtensions - { - /// - /// Waits for the application to start and present an informational message. - /// - /// The to extend. - /// The cancellation token. - /// A task that represents the asynchronous operation. - public static async Task WaitForApplicationStartedAnnouncementAsync(this IHostedService hostedService, CancellationToken cancellationToken = default) - { - var tsc = new TaskCompletionSource(); - BootstrapperLifetime.OnApplicationStartedCallback += () => tsc.SetResult(); - await tsc.Task.ConfigureAwait(false); // give time for the host to start and present informational message - } - } -} diff --git a/src/Codebelt.Bootstrapper/IHostLifetimeEvents.cs b/src/Codebelt.Bootstrapper/IHostLifetimeEvents.cs new file mode 100644 index 0000000..e79ace0 --- /dev/null +++ b/src/Codebelt.Bootstrapper/IHostLifetimeEvents.cs @@ -0,0 +1,25 @@ +using System; + +namespace Codebelt.Bootstrapper +{ + /// + /// Provides a convenient way to be notified of host lifetime events. + /// + public interface IHostLifetimeEvents + { + /// + /// Triggered when the application host has fully started. + /// + Action OnApplicationStartedCallback { get; set; } + + /// + /// Triggered when the application host is starting a graceful shutdown. + /// + Action OnApplicationStoppingCallback { get; set; } + + /// + /// Triggered when the application host has completed a graceful shutdown. + /// + Action OnApplicationStoppedCallback { get; set; } + } +} From b7040707d01957f9dd52734b1263ff3abae78fa6 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 20:49:03 +0200 Subject: [PATCH 08/13] :recycle: depend on abstractions, instead of concretions --- .../HostApplicationBuilderExtensions.cs | 10 +++++----- .../MinimalConsoleProgram.cs | 9 +++++---- .../MinimalWorkerProgram.cs | 5 +++-- .../HostApplicationBuilderExtensions.cs | 9 +++++---- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Codebelt.Bootstrapper.Console/HostApplicationBuilderExtensions.cs b/src/Codebelt.Bootstrapper.Console/HostApplicationBuilderExtensions.cs index cc12581..d3ce6b8 100644 --- a/src/Codebelt.Bootstrapper.Console/HostApplicationBuilderExtensions.cs +++ b/src/Codebelt.Bootstrapper.Console/HostApplicationBuilderExtensions.cs @@ -5,17 +5,17 @@ namespace Codebelt.Bootstrapper.Console { /// - /// Extension methods for the . + /// Extension methods for the . /// public static class HostApplicationBuilderExtensions { /// /// Provides an implementation of a conventional based . /// - /// The to configure. + /// The to configure. /// The that must be assignable from . - /// The same instance of the for chaining. - public static HostApplicationBuilder UseBootstrapperProgram(this HostApplicationBuilder hostBuilder, Type minimalConsoleProgramType) + /// The same instance of the for chaining. + public static IHostApplicationBuilder UseBootstrapperProgram(this IHostApplicationBuilder hostBuilder, Type minimalConsoleProgramType) { hostBuilder.Services.AddSingleton(new ProgramFactory(minimalConsoleProgramType)); return hostBuilder; @@ -26,7 +26,7 @@ public static HostApplicationBuilder UseBootstrapperProgram(this HostApplication /// /// The to configure. /// The same instance of the for chaining. - public static HostApplicationBuilder UseMinimalConsoleProgram(this HostApplicationBuilder hostBuilder) + public static IHostApplicationBuilder UseMinimalConsoleProgram(this IHostApplicationBuilder hostBuilder) { hostBuilder.Services.AddHostedService(); return hostBuilder; diff --git a/src/Codebelt.Bootstrapper.Console/MinimalConsoleProgram.cs b/src/Codebelt.Bootstrapper.Console/MinimalConsoleProgram.cs index 68c54e5..3849930 100644 --- a/src/Codebelt.Bootstrapper.Console/MinimalConsoleProgram.cs +++ b/src/Codebelt.Bootstrapper.Console/MinimalConsoleProgram.cs @@ -17,10 +17,11 @@ public abstract class MinimalConsoleProgram : ProgramRoot /// The initialized . protected static HostApplicationBuilder CreateHostBuilder(string[] args) { - return Host.CreateApplicationBuilder(args) - .UseBootstrapperLifetime() - .UseBootstrapperProgram(typeof(MinimalConsoleProgram)) - .UseMinimalConsoleProgram(); + var hb = Host.CreateApplicationBuilder(args); + hb.UseBootstrapperLifetime(); + hb.UseBootstrapperProgram(typeof(MinimalConsoleProgram)); + hb.UseMinimalConsoleProgram(); + return hb; } /// diff --git a/src/Codebelt.Bootstrapper.Worker/MinimalWorkerProgram.cs b/src/Codebelt.Bootstrapper.Worker/MinimalWorkerProgram.cs index 37108d4..46c8c23 100644 --- a/src/Codebelt.Bootstrapper.Worker/MinimalWorkerProgram.cs +++ b/src/Codebelt.Bootstrapper.Worker/MinimalWorkerProgram.cs @@ -14,8 +14,9 @@ public abstract class MinimalWorkerProgram : ProgramRoot /// The initialized . protected static HostApplicationBuilder CreateHostBuilder(string[] args) { - return Host.CreateApplicationBuilder(args) - .UseBootstrapperLifetime(); + var hb = Host.CreateApplicationBuilder(args); + hb.UseBootstrapperLifetime(); + return hb; } } } diff --git a/src/Codebelt.Bootstrapper/HostApplicationBuilderExtensions.cs b/src/Codebelt.Bootstrapper/HostApplicationBuilderExtensions.cs index 1314f1d..b22396d 100644 --- a/src/Codebelt.Bootstrapper/HostApplicationBuilderExtensions.cs +++ b/src/Codebelt.Bootstrapper/HostApplicationBuilderExtensions.cs @@ -6,19 +6,20 @@ namespace Codebelt.Bootstrapper { /// - /// Extension methods for the . + /// Extension methods for the . /// public static class HostApplicationBuilderExtensions { /// /// Listens for Ctrl+C or SIGTERM and calls to start the shutdown process. /// - /// The to configure. - /// The same instance of the for chaining. + /// The to configure. + /// The same instance of the for chaining. /// Complements the default implementation of . - public static HostApplicationBuilder UseBootstrapperLifetime(this HostApplicationBuilder hostBuilder) + public static IHostApplicationBuilder UseBootstrapperLifetime(this IHostApplicationBuilder hostBuilder) { hostBuilder.Services.Replace(ServiceDescriptor.Singleton()); + hostBuilder.Services.AddSingleton(provider => provider.GetRequiredService() as BootstrapperLifetime); return hostBuilder; } } From 053f95c35023d3da9d91f4f4169242f6771e5066 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 20:54:45 +0200 Subject: [PATCH 09/13] :rocket: updated samples to reflect changes --- app/Codebelt.Bootstrapper.Console.App/Startup.cs | 13 ++++++++++--- .../Program.cs | 8 +++++--- .../FakeHostedService.cs | 10 +++++----- .../FakeHostedService.cs | 9 ++++----- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/Codebelt.Bootstrapper.Console.App/Startup.cs b/app/Codebelt.Bootstrapper.Console.App/Startup.cs index 959ef3b..a727e2d 100644 --- a/app/Codebelt.Bootstrapper.Console.App/Startup.cs +++ b/app/Codebelt.Bootstrapper.Console.App/Startup.cs @@ -24,14 +24,21 @@ public override void ConfigureServices(IServiceCollection services) public override void ConfigureConsole(IServiceProvider serviceProvider) { var logger = serviceProvider.GetRequiredService>(); - BootstrapperLifetime.OnApplicationStartedCallback = () => logger.LogWarning("Console started."); - BootstrapperLifetime.OnApplicationStoppingCallback = () => + var events = serviceProvider.GetRequiredService(); + + events.OnApplicationStartedCallback = () => + { + logger.LogWarning("Console started."); + }; + + events.OnApplicationStoppingCallback = () => { logger.LogWarning("Stopping and cleaning .."); Thread.Sleep(TimeSpan.FromSeconds(5)); // simulate graceful shutdown logger.LogWarning(".. done!"); }; - BootstrapperLifetime.OnApplicationStoppedCallback = () => logger.LogCritical("Console stopped."); + + events.OnApplicationStoppedCallback = () => logger.LogCritical("Console stopped."); } public async override Task RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) diff --git a/app/Codebelt.Bootstrapper.MinimalConsole.App/Program.cs b/app/Codebelt.Bootstrapper.MinimalConsole.App/Program.cs index 38c33dc..b37566a 100644 --- a/app/Codebelt.Bootstrapper.MinimalConsole.App/Program.cs +++ b/app/Codebelt.Bootstrapper.MinimalConsole.App/Program.cs @@ -18,14 +18,16 @@ static Task Main(string[] args) var host = builder.Build(); var logger = host.Services.GetRequiredService>(); - BootstrapperLifetime.OnApplicationStartedCallback = () => logger.LogWarning("Console started."); - BootstrapperLifetime.OnApplicationStoppingCallback = () => + var events = host.Services.GetRequiredService(); + + events.OnApplicationStartedCallback = () => logger.LogWarning("Console started."); + events.OnApplicationStoppingCallback = () => { logger.LogWarning("Stopping and cleaning .."); Thread.Sleep(TimeSpan.FromSeconds(5)); // simulate graceful shutdown logger.LogWarning(".. done!"); }; - BootstrapperLifetime.OnApplicationStoppedCallback = () => logger.LogCritical("Console stopped."); + events.OnApplicationStoppedCallback = () => logger.LogCritical("Console stopped."); return host.RunAsync(); } diff --git a/app/Codebelt.Bootstrapper.MinimalWorker.App/FakeHostedService.cs b/app/Codebelt.Bootstrapper.MinimalWorker.App/FakeHostedService.cs index 96e46e1..9ff8335 100644 --- a/app/Codebelt.Bootstrapper.MinimalWorker.App/FakeHostedService.cs +++ b/app/Codebelt.Bootstrapper.MinimalWorker.App/FakeHostedService.cs @@ -5,24 +5,24 @@ public class FakeHostedService : BackgroundService private readonly ILogger _logger; private bool _gracefulShutdown; - public FakeHostedService(ILogger logger) + public FakeHostedService(ILogger logger, IHostLifetimeEvents events) { _logger = logger; - BootstrapperLifetime.OnApplicationStartedCallback = () => logger.LogInformation("Started"); - BootstrapperLifetime.OnApplicationStoppingCallback = () => + + events.OnApplicationStartedCallback = () => logger.LogInformation("Started"); + events.OnApplicationStoppingCallback = () => { _gracefulShutdown = true; logger.LogWarning("Stopping and cleaning .."); Thread.Sleep(TimeSpan.FromSeconds(5)); // simulate graceful shutdown logger.LogWarning(".. done!"); }; - BootstrapperLifetime.OnApplicationStoppedCallback = () => logger.LogCritical("Stopped"); + events.OnApplicationStoppedCallback = () => logger.LogCritical("Stopped"); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - await this.WaitForApplicationStartedAnnouncementAsync(stoppingToken).ConfigureAwait(false); while (!stoppingToken.IsCancellationRequested) { if (_gracefulShutdown) { return; } diff --git a/app/Codebelt.Bootstrapper.Worker.App/FakeHostedService.cs b/app/Codebelt.Bootstrapper.Worker.App/FakeHostedService.cs index 2d6d0f1..7dd9c9c 100644 --- a/app/Codebelt.Bootstrapper.Worker.App/FakeHostedService.cs +++ b/app/Codebelt.Bootstrapper.Worker.App/FakeHostedService.cs @@ -11,24 +11,23 @@ public class FakeHostedService : BackgroundService private readonly ILogger _logger; private bool _gracefulShutdown; - public FakeHostedService(ILogger logger) + public FakeHostedService(ILogger logger, IHostLifetimeEvents events) { _logger = logger; - BootstrapperLifetime.OnApplicationStartedCallback = () => logger.LogInformation("Started"); - BootstrapperLifetime.OnApplicationStoppingCallback = () => + events.OnApplicationStartedCallback = () => logger.LogInformation("Started"); + events.OnApplicationStoppingCallback = () => { _gracefulShutdown = true; logger.LogWarning("Stopping and cleaning .."); Thread.Sleep(TimeSpan.FromSeconds(5)); // simulate graceful shutdown logger.LogWarning(".. done!"); }; - BootstrapperLifetime.OnApplicationStoppedCallback = () => logger.LogCritical("Stopped"); + events.OnApplicationStoppedCallback = () => logger.LogCritical("Stopped"); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - await this.WaitForApplicationStartedAnnouncementAsync(stoppingToken).ConfigureAwait(false); while (!stoppingToken.IsCancellationRequested) { if (_gracefulShutdown) { return; } From 4853eb5febb2489e8dccc7780fa017250efbb352 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 20:55:22 +0200 Subject: [PATCH 10/13] :pencil2: fixed typo (HostApplicationBuilder -> WebApplicationBuilder) --- src/Codebelt.Bootstrapper.Web/MinimalWebProgram.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Codebelt.Bootstrapper.Web/MinimalWebProgram.cs b/src/Codebelt.Bootstrapper.Web/MinimalWebProgram.cs index fdbf564..ae550d7 100644 --- a/src/Codebelt.Bootstrapper.Web/MinimalWebProgram.cs +++ b/src/Codebelt.Bootstrapper.Web/MinimalWebProgram.cs @@ -9,7 +9,7 @@ namespace Codebelt.Bootstrapper.Web public abstract class MinimalWebProgram : ProgramRoot { /// - /// Creates an used to set up the host. + /// Creates an used to set up the host. /// /// The command line arguments. /// The initialized . From 814958eeed914f9ffe9bd0a101b2c907b009ec19 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 20:57:18 +0200 Subject: [PATCH 11/13] :arrow_up: bump dependencies --- Codebelt.Bootstrapper.sln | 7 ++ Directory.Packages.props | 20 ++--- .../Assets/ManualGenericHostFixture.cs | 12 +++ .../Assets/ManualMinimalHostFixture.cs | 12 +++ .../Assets/TestConsoleStartup.cs | 28 +++++++ .../Assets/TestHostFixture.cs | 35 ++++++++ .../Assets/TestMinimalConsoleProgram.cs | 18 +++++ ...ootstrapper.Console.FunctionalTests.csproj | 11 +++ .../ConsoleHostedServiceTest.cs | 42 ++++++++++ .../LoggerExtensions.cs | 26 ++++++ .../MinimalConsoleHostedServiceTest.cs | 79 +++++++++++++++++++ .../Assets/TestBackgroundService.cs | 17 ++-- .../{TestFixture.cs => TestHostFixture.cs} | 4 +- .../BootstrapperLifetimeTest.cs | 28 +++---- ...debelt.Bootstrapper.FunctionalTests.csproj | 8 -- .../HostApplicationBuilderExtensionsTest.cs | 6 +- .../HostBuilderExtensionsTest.cs | 12 +-- .../HostedServiceExtensionsTest.cs | 14 ++-- testenvironments.json | 2 +- 19 files changed, 326 insertions(+), 55 deletions(-) create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualGenericHostFixture.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualMinimalHostFixture.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestConsoleStartup.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestHostFixture.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestMinimalConsoleProgram.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/Codebelt.Bootstrapper.Console.FunctionalTests.csproj create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/ConsoleHostedServiceTest.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/LoggerExtensions.cs create mode 100644 test/Codebelt.Bootstrapper.Console.FunctionalTests/MinimalConsoleHostedServiceTest.cs rename test/Codebelt.Bootstrapper.FunctionalTests/Assets/{TestFixture.cs => TestHostFixture.cs} (91%) diff --git a/Codebelt.Bootstrapper.sln b/Codebelt.Bootstrapper.sln index e4684f1..23dde0f 100644 --- a/Codebelt.Bootstrapper.sln +++ b/Codebelt.Bootstrapper.sln @@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.Tests EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.FunctionalTests", "test\Codebelt.Bootstrapper.FunctionalTests\Codebelt.Bootstrapper.FunctionalTests.csproj", "{8970491F-E0BD-489D-AA7E-617A67C2E39F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codebelt.Bootstrapper.Console.FunctionalTests", "test\Codebelt.Bootstrapper.Console.FunctionalTests\Codebelt.Bootstrapper.Console.FunctionalTests.csproj", "{585A199C-4675-4A8A-89B7-EFF824377456}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,6 +125,10 @@ Global {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Debug|Any CPU.Build.0 = Debug|Any CPU {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Release|Any CPU.ActiveCfg = Release|Any CPU {8970491F-E0BD-489D-AA7E-617A67C2E39F}.Release|Any CPU.Build.0 = Release|Any CPU + {585A199C-4675-4A8A-89B7-EFF824377456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {585A199C-4675-4A8A-89B7-EFF824377456}.Debug|Any CPU.Build.0 = Debug|Any CPU + {585A199C-4675-4A8A-89B7-EFF824377456}.Release|Any CPU.ActiveCfg = Release|Any CPU + {585A199C-4675-4A8A-89B7-EFF824377456}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -146,6 +152,7 @@ Global {E87D127B-4CD1-4009-962E-EEF70A522C13} = {28409021-5670-4008-9061-DBAAA0FC4DA6} {CC656409-7479-4553-8E9B-0854801E1590} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {8970491F-E0BD-489D-AA7E-617A67C2E39F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {585A199C-4675-4A8A-89B7-EFF824377456} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {72FB037E-3629-4CDB-812E-D577A3D4FD26} diff --git a/Directory.Packages.props b/Directory.Packages.props index a26b3ba..6de61c3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,9 +4,9 @@ - - - + + + @@ -15,15 +15,15 @@ - - - - - + + + + + - - + + diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualGenericHostFixture.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualGenericHostFixture.cs new file mode 100644 index 0000000..3ff99f5 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualGenericHostFixture.cs @@ -0,0 +1,12 @@ +using Codebelt.Extensions.Xunit.Hosting; + +namespace Codebelt.Bootstrapper.Console.Assets +{ + public class ManualGenericHostFixture : GenericHostFixture + { + public ManualGenericHostFixture() + { + HostRunnerCallback = host => { }; + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualMinimalHostFixture.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualMinimalHostFixture.cs new file mode 100644 index 0000000..6bcb555 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/ManualMinimalHostFixture.cs @@ -0,0 +1,12 @@ +using Codebelt.Extensions.Xunit.Hosting; + +namespace Codebelt.Bootstrapper.Console.Assets +{ + public class ManualMinimalHostFixture : MinimalHostFixture + { + public ManualMinimalHostFixture() + { + HostRunnerCallback = host => { }; + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestConsoleStartup.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestConsoleStartup.cs new file mode 100644 index 0000000..df3c4df --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestConsoleStartup.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Codebelt.Bootstrapper.Console.Assets +{ + public class TestConsoleStartup : ConsoleStartup + { + public TestConsoleStartup(IConfiguration configuration, IHostEnvironment environment) : base(configuration, environment) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + } + + public override Task RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var logger = serviceProvider.GetRequiredService>(); + logger.LogTrace($"Inside {nameof(RunAsync)} of {nameof(TestConsoleStartup)}."); + return Task.CompletedTask; + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestHostFixture.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestHostFixture.cs new file mode 100644 index 0000000..6dac83b --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestHostFixture.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Bootstrapper.Console.Assets +{ + public class TestHostFixture : GenericHostFixture + { + public override void ConfigureHost(Test hostTest) + { + var hb = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((context, config) => + { + ConfigureCallback(config.Build(), context.HostingEnvironment); + }) + .ConfigureServices((context, services) => + { + Configuration = context.Configuration; + Environment = context.HostingEnvironment; + ConfigureServicesCallback(services); + }) + .ConfigureHostConfiguration(builder => + { + builder.AddInMemoryCollection(new Dictionary + { + { HostDefaults.ApplicationKey, hostTest.CallerType.Assembly.GetName().Name } + }); + }); + ConfigureHostCallback(hb); + Host = hb.Build(); + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestMinimalConsoleProgram.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestMinimalConsoleProgram.cs new file mode 100644 index 0000000..34bdcc6 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Assets/TestMinimalConsoleProgram.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Codebelt.Bootstrapper.Console.Assets +{ + public class TestMinimalConsoleProgram : MinimalConsoleProgram + { + public override Task RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var logger = serviceProvider.GetRequiredService>(); + logger.LogTrace($"Inside {nameof(RunAsync)} of {nameof(TestMinimalConsoleProgram)}."); + return Task.CompletedTask; + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/Codebelt.Bootstrapper.Console.FunctionalTests.csproj b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Codebelt.Bootstrapper.Console.FunctionalTests.csproj new file mode 100644 index 0000000..680c1d8 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/Codebelt.Bootstrapper.Console.FunctionalTests.csproj @@ -0,0 +1,11 @@ + + + + Codebelt.Bootstrapper.Console + + + + + + + diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/ConsoleHostedServiceTest.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/ConsoleHostedServiceTest.cs new file mode 100644 index 0000000..33aae0c --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/ConsoleHostedServiceTest.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using Codebelt.Bootstrapper.Console.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Bootstrapper.Console +{ + public class ConsoleHostedServiceTest : Test + { + public ConsoleHostedServiceTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task StartAsync_ShouldInvokeRunAsyncInTestConsoleStartup() + { + await using var test = HostTestFactory.Create(services => + { + services.AddXunitTestLogging(TestOutput); + }, hb => + { + hb.UseBootstrapperLifetime() + .UseBootstrapperStartup() + .UseConsoleStartup(); + }); + + await test.Host.WaitForShutdownAsync(); + + var loggerStore = test.Host.Services.GetRequiredService>().GetTestStore(); + Assert.Collection(loggerStore.Query(), + entry => Assert.Equal("Information: RunAsync started.", entry.Message), + entry => Assert.Equal("Trace: Inside RunAsync of TestConsoleStartup.", entry.Message), + entry => Assert.Equal("Information: RunAsync completed successfully.", entry.Message) + ); + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/LoggerExtensions.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/LoggerExtensions.cs new file mode 100644 index 0000000..a1295f9 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/LoggerExtensions.cs @@ -0,0 +1,26 @@ +using System; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Logging; + +namespace Codebelt.Bootstrapper.Console +{ + public static class LoggerExtensions + { + /// + /// Returns the associated that is provided when settings up services from . + /// + /// The from which to retrieve the . + /// Returns an implementation of with all logged entries expressed as . + /// + /// cannot be null. + /// + /// + /// does not contain a test store. + /// + public static ITestStore GetTestStore(this ILogger logger, Type loggerType) + { + return logger.GetTestStore(loggerType.FullName); + } + } +} diff --git a/test/Codebelt.Bootstrapper.Console.FunctionalTests/MinimalConsoleHostedServiceTest.cs b/test/Codebelt.Bootstrapper.Console.FunctionalTests/MinimalConsoleHostedServiceTest.cs new file mode 100644 index 0000000..456b5c7 --- /dev/null +++ b/test/Codebelt.Bootstrapper.Console.FunctionalTests/MinimalConsoleHostedServiceTest.cs @@ -0,0 +1,79 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Codebelt.Bootstrapper.Console.Assets; +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Bootstrapper.Console +{ + public class MinimalConsoleHostedServiceTest : Test + { + public MinimalConsoleHostedServiceTest(ITestOutputHelper output) : base(output) + { + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + { + var exception = e.ExceptionObject as Exception; + Debug.WriteLine($"Unhandled exception: {exception?.Message}"); + }; + + TaskScheduler.UnobservedTaskException += (sender, e) => + { + Debug.WriteLine($"Unobserved task exception: {e.Exception.Message}"); + e.SetObserved(); + }; + } + + [Fact] + public async Task StartAsync_ShouldInvokeRunAsyncInTestConsoleStartup() + { + await using var test = MinimalHostTestFactory.Create(services => + { + services.AddXunitTestLogging(TestOutput); + }, hb => + { + hb.UseBootstrapperLifetime() + .UseBootstrapperProgram(typeof(TestMinimalConsoleProgram)) + .UseMinimalConsoleProgram(); + }); + + //var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + + //if (SynchronizationContext.Current == null) + //{ + // // normal ASP.Net Core environment does not have a synchronization context, + // // no problem with await here, it will be executed on the thread pool + // await test.Host.StartAsync(); + // await test.Host.WaitForShutdownAsync(); + //} + //else + //{ + // // xunit uses it's own SynchronizationContext that allows a maximum thread count + // // equal to the logical cpu count (that is 1 on our single cpu build agents). So + // // when we're trying to await something here, the task get's scheduled to xunit's + // // synchronization context, which is already at it's limit running the test thread + // // so we end up in a deadlock here. + // // solution is to run the await explicitly on the thread pool by using Task.Run + // Task.Run(async () => + // { + // await test.Host.StartAsync(); + // await test.Host.WaitForShutdownAsync(); + // }).Wait(); + //} + + await test.Host.WaitForShutdownAsync(); + + var loggerStore = test.Host.Services.GetRequiredService>().GetTestStore(); + Assert.Collection(loggerStore.Query(), + entry => Assert.Equal("Information: RunAsync started.", entry.Message), + entry => Assert.Equal("Trace: Inside RunAsync of TestMinimalConsoleProgram.", entry.Message), + entry => Assert.Equal("Information: RunAsync completed successfully.", entry.Message) + ); + } + } +} diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs index ceb5176..c040a1b 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestBackgroundService.cs @@ -10,10 +10,15 @@ namespace Codebelt.Bootstrapper.Assets public class TestBackgroundService : BackgroundService { private readonly ILogger _logger; + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly IHostLifetime _hostLifetime; + private IHostLifetimeEvents _events; - public TestBackgroundService(ILogger logger) + public TestBackgroundService(ILogger logger, IHostApplicationLifetime applicationLifetime, IHostLifetimeEvents events) { _logger = logger; + _applicationLifetime = applicationLifetime; + _events = events; } public TimeSpan Elapsed { get; private set; } = TimeSpan.Zero; @@ -21,10 +26,12 @@ public TestBackgroundService(ILogger logger) protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var sw = Stopwatch.StartNew(); - await this.WaitForApplicationStartedAnnouncementAsync(stoppingToken).ConfigureAwait(false); - sw.Stop(); - Elapsed = sw.Elapsed; - _logger.LogInformation("TestBackgroundService started after {Elapsed}", Elapsed); + _events.OnApplicationStartedCallback += () => + { + sw.Stop(); + Elapsed = sw.Elapsed; + _logger.LogInformation("TestBackgroundService started after {Elapsed}", Elapsed); + }; } } } diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestHostFixture.cs similarity index 91% rename from test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs rename to test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestHostFixture.cs index 52f0c27..82901e6 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestFixture.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Assets/TestHostFixture.cs @@ -6,7 +6,7 @@ namespace Codebelt.Bootstrapper.Assets { - public class TestHostFixture : HostFixture + public class TestHostFixture : GenericHostFixture { public override void ConfigureHost(Test hostTest) { @@ -18,7 +18,7 @@ public override void ConfigureHost(Test hostTest) .ConfigureServices((context, services) => { Configuration = context.Configuration; - HostingEnvironment = context.HostingEnvironment; + Environment = context.HostingEnvironment; ConfigureServicesCallback(services); }) .ConfigureHostConfiguration(builder => diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs index 79d35fb..991b907 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/BootstrapperLifetimeTest.cs @@ -1,8 +1,7 @@ -using System; -using System.Threading; -using Codebelt.Bootstrapper.Assets; +using Codebelt.Bootstrapper.Assets; using Codebelt.Extensions.Xunit; using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; using Xunit.Abstractions; @@ -11,21 +10,15 @@ namespace Codebelt.Bootstrapper { public class BootstrapperLifetimeTest : Test { - private bool _started = false; - private bool _stopping = false; - private bool _stopped = false; - public BootstrapperLifetimeTest(ITestOutputHelper output) : base(output) { - BootstrapperLifetime.OnApplicationStartedCallback = () => { _started = true; }; - BootstrapperLifetime.OnApplicationStoppingCallback = () => { _stopping = true; }; - BootstrapperLifetime.OnApplicationStoppedCallback = () => { _stopped = true; }; } [Fact] public void OnApplicationStartedCallback_ShouldBeInvokedWhenStartingHost() { - using var test = GenericHostTestFactory.Create(services => + var started = false; + using var test = HostTestFactory.Create(services => { services.AddXunitTestLoggingOutputHelperAccessor(); services.AddXunitTestLogging(TestOutput); @@ -35,15 +28,19 @@ public void OnApplicationStartedCallback_ShouldBeInvokedWhenStartingHost() hb.UseBootstrapperStartup(); }, new TestHostFixture()); + test.Host.Services.GetRequiredService().OnApplicationStartedCallback = () => { started = true; }; + test.Host.Start(); - Assert.True(_started); + Assert.True(started); } [Fact] public void OnApplicationStoppingCallback_OnApplicationStoppedCallback_ShouldBeInvokedWhenStoppingHost() { - using var test = GenericHostTestFactory.Create(services => + var stopping = false; + var stopped = false; + using var test = HostTestFactory.Create(services => { services.AddXunitTestLoggingOutputHelperAccessor(); services.AddXunitTestLogging(TestOutput); @@ -53,10 +50,13 @@ public void OnApplicationStoppingCallback_OnApplicationStoppedCallback_ShouldBeI hb.UseBootstrapperStartup(); }, new TestHostFixture()); + test.Host.Services.GetRequiredService().OnApplicationStoppingCallback = () => { stopping = true; }; + test.Host.Services.GetRequiredService().OnApplicationStoppedCallback = () => { stopped = true; }; + test.Host.Start(); test.Host.StopAsync().GetAwaiter().GetResult(); - Assert.True(_stopping && _stopped); + Assert.True(stopping && stopped); } } } diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj index 5e9152a..dc95b98 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj +++ b/test/Codebelt.Bootstrapper.FunctionalTests/Codebelt.Bootstrapper.FunctionalTests.csproj @@ -4,14 +4,6 @@ Codebelt.Bootstrapper - - - - <_Parameter1>DisableTestParallelization = true - <_Parameter1_IsLiteral>true - - - diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs index 0b7c598..59d0f3a 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostApplicationBuilderExtensionsTest.cs @@ -23,9 +23,9 @@ public HostApplicationBuilderExtensionsTest(ITestOutputHelper output) : base(out [Fact] public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime() { - var host = Host.CreateApplicationBuilder() - .UseBootstrapperLifetime() - .Build(); + var hb = Host.CreateApplicationBuilder(); + hb.UseBootstrapperLifetime(); + var host = hb.Build(); var bootstrapperLifetime = host.Services.GetService(); diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs index defe9b4..abda99d 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostBuilderExtensionsTest.cs @@ -17,14 +17,14 @@ public HostBuilderExtensionsTest(ITestOutputHelper output) : base(output) [Fact] public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime() { - using var test = GenericHostTestFactory.Create(services => + using var test = HostTestFactory.Create(services => { }, hb => { hb.UseBootstrapperLifetime(); - }, new TestHostFixture()); + }); - var bootstrapperLifetime = test.ServiceProvider.GetService(); + var bootstrapperLifetime = test.Host.Services.GetService(); Assert.NotNull(bootstrapperLifetime); Assert.IsType(bootstrapperLifetime); @@ -33,14 +33,14 @@ public void UseBootstrapperLifetime_ShouldRegisterBootstrapperLifetime() [Fact] public void UseBootstrapperStartup_ShouldRegisterStartupFactory() { - using var test = GenericHostTestFactory.Create(services => + using var test = HostTestFactory.Create(services => { }, hb => { hb.UseBootstrapperStartup(); - }, new TestHostFixture()); + }); - var startupFactory = test.ServiceProvider.GetService>(); + var startupFactory = test.Host.Services.GetService>(); Assert.NotNull(startupFactory); Assert.IsType>(startupFactory); diff --git a/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs index a830336..37d4a96 100644 --- a/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs +++ b/test/Codebelt.Bootstrapper.FunctionalTests/HostedServiceExtensionsTest.cs @@ -22,12 +22,8 @@ public async Task WaitForApplicationStartedAnnouncementAsync_MustWaitForApplicat { var timeToWait = TimeSpan.FromMilliseconds(50); var started = false; - BootstrapperLifetime.OnApplicationStartedCallback = () => - { - Thread.Sleep(timeToWait); - }; - using var test = GenericHostTestFactory.Create(services => + using var test = HostTestFactory.Create(services => { services.AddXunitTestLoggingOutputHelperAccessor(); services.AddXunitTestLogging(TestOutput); @@ -37,7 +33,13 @@ public async Task WaitForApplicationStartedAnnouncementAsync_MustWaitForApplicat hb.UseBootstrapperLifetime(); }, new TestHostFixture()); - var bgs = test.ServiceProvider.GetRequiredService() as TestBackgroundService; + test.Host.Services.GetRequiredService().OnApplicationStartedCallback += () => + { + Thread.Sleep(timeToWait); + }; + + + var bgs = test.Host.Services.GetRequiredService() as TestBackgroundService; await test.Host.StartAsync().ConfigureAwait(false); diff --git a/testenvironments.json b/testenvironments.json index 328e82b..095d438 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -9,7 +9,7 @@ { "name": "Docker-Ubuntu", "type": "docker", - "dockerImage": "gimlichael/ubuntu-testrunner:net8.0.407-9.0.202" + "dockerImage": "gimlichael/ubuntu-testrunner:net8.0.408-9.0.203" } ] } From 6bc5bca18b5a4a605e91e23eb0a8b3ad267404c3 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 21:21:46 +0200 Subject: [PATCH 12/13] :package: updated NuGet package definition --- .../PackageReleaseNotes.txt | 12 +++++++++++- .../PackageReleaseNotes.txt | 8 +++++++- .../PackageReleaseNotes.txt | 8 +++++++- .../PackageReleaseNotes.txt | 19 ++++++++++++++++++- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.nuget/Codebelt.Bootstrapper.Console/PackageReleaseNotes.txt b/.nuget/Codebelt.Bootstrapper.Console/PackageReleaseNotes.txt index e102d9c..8d82edb 100644 --- a/.nuget/Codebelt.Bootstrapper.Console/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Bootstrapper.Console/PackageReleaseNotes.txt @@ -1,4 +1,14 @@ -Version 3.0.1 +Version 4.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +# Breaking Changes +- CHANGED UseBootstrapperProgram method on the HostApplicationBuilderExtensions class in the Codebelt.Bootstrapper.Console namespace to extend IHostApplicationBuilder instead of HostApplicationBuilder +- CHANGED UseMinimalConsoleProgram method on the HostApplicationBuilderExtensions class in the Codebelt.Bootstrapper.Console namespace to extend IHostApplicationBuilder instead of HostApplicationBuilder +  +Version 3.0.1 Availability: .NET 9 and .NET 8   # ALM diff --git a/.nuget/Codebelt.Bootstrapper.Web/PackageReleaseNotes.txt b/.nuget/Codebelt.Bootstrapper.Web/PackageReleaseNotes.txt index 10dcafe..3c78178 100644 --- a/.nuget/Codebelt.Bootstrapper.Web/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Bootstrapper.Web/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version 3.0.1 +Version 4.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 3.0.1 Availability: .NET 9 and .NET 8   # ALM diff --git a/.nuget/Codebelt.Bootstrapper.Worker/PackageReleaseNotes.txt b/.nuget/Codebelt.Bootstrapper.Worker/PackageReleaseNotes.txt index d84a2fc..b63b882 100644 --- a/.nuget/Codebelt.Bootstrapper.Worker/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Bootstrapper.Worker/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version 3.0.1 +Version 4.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 3.0.1 Availability: .NET 9 and .NET 8   # ALM diff --git a/.nuget/Codebelt.Bootstrapper/PackageReleaseNotes.txt b/.nuget/Codebelt.Bootstrapper/PackageReleaseNotes.txt index 944a7e1..266b80e 100644 --- a/.nuget/Codebelt.Bootstrapper/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Bootstrapper/PackageReleaseNotes.txt @@ -1,4 +1,21 @@ -Version 3.0.1 +Version 4.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +# Breaking Changes +- REMOVED HostedServiceExtensions class in the Codebelt.Bootstrapper namespace (WaitForApplicationStartedAnnouncementAsync extension method) +- CHANGED BootstrapperLifetime class in the Codebelt.Bootstrapper namespace to implement IHostLifetimeEvents (hereby removing static equivalents) +- CHANGED UseBootstrapperStartup method on the HostApplicationBuilderExtensions class in the Codebelt.Bootstrapper namespace to extend IHostApplicationBuilder instead of HostApplicationBuilder +  +# New Features +- ADDED IHostLifetimeEvents interface in the Codebelt.Bootstrapper namespace that provides a convenient way to be notified of host lifetime events +  +# Bug Fixes +- FIXED BootstrapperLifetime class in the Codebelt.Bootstrapper namespace to disregard SuppressStatusMessages and always assign callbacks to members of IHostLifetimeEvents +  +Version 3.0.1 Availability: .NET 9 and .NET 8   # ALM From 693f16479327536bd6d93b29fc61071bfed78f5a Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Fri, 11 Apr 2025 21:21:58 +0200 Subject: [PATCH 13/13] :speech_balloon: updated community health pages --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8de4e17..958302b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. +## [4.0.0] - TBD + +This major release revisits and refines some of the earlier design decisions to offer a more consistent and flexible API. It also brings forward improvements to reliability and maintainability. + +### Added + +- IHostLifetimeEvents interface in the Codebelt.Bootstrapper namespace that provides a convenient way to be notified of host lifetime events + +### Changed + +- BootstrapperLifetime class in the Codebelt.Bootstrapper namespace to implement IHostLifetimeEvents and hereby removing static equivalents (breaking change) +- UseBootstrapperStartup method on the HostApplicationBuilderExtensions class in the Codebelt.Bootstrapper namespace to extend IHostApplicationBuilder instead of HostApplicationBuilder (breaking change) +- UseBootstrapperProgram method on the HostApplicationBuilderExtensions class in the Codebelt.Bootstrapper.Console namespace to extend IHostApplicationBuilder instead of HostApplicationBuilder (breaking change) +- UseMinimalConsoleProgram method on the HostApplicationBuilderExtensions class in the Codebelt.Bootstrapper.Console namespace to extend IHostApplicationBuilder instead of HostApplicationBuilder (breaking change) + +### Removed + +- HostedServiceExtensions class in the Codebelt.Bootstrapper namespace (breaking change) + +### Fixed + +- BootstrapperLifetime class in the Codebelt.Bootstrapper namespace to disregard SuppressStatusMessages and always assign callbacks to members of IHostLifetimeEvents + ## [3.0.1] - 2025-01-31 This is a service update that primarily focuses on package dependencies and minor improvements.