From 0978394d9c00abcd7ec21da14aa5912627af335c Mon Sep 17 00:00:00 2001 From: rlittlesii <6969701+RLittlesII@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:53:02 -0600 Subject: [PATCH 1/3] enhancement: add concurrency to ApplicationStartup add Microsoft Logging Extensions to Startup --- Airframe.sln | 8 +- directory.packages.props | 164 +++++++++--------- src/Core/AppStart/ApplicationStartup.cs | 24 +-- .../AppStart/ApplicationStartupExtensions.cs | 17 ++ src/Core/AppStart/IApplicationStartup.cs | 12 +- .../AppStart/LoggableApplicationStartup.cs | 56 ++++++ src/Core/AppStart/LoggableStartupOperation.cs | 50 ++++++ src/Core/Core.csproj | 4 +- .../Core/AppStart/ApplicationStartupTest.cs | 59 ------- .../Core/AppStart/TestOperation.cs | 25 --- .../Core/Geofence/GeofenceServiceTests.cs | 17 -- .../AppStart/ApplicationStartupFixture.cs | 11 +- .../AppStart/ApplicationStartupTest.cs | 64 +++++++ test/Core.Tests/AppStart/TestOperation.cs | 37 ++++ test/Core.Tests/Core.Tests.csproj | 28 +++ .../Geofence/GeofenceServiceFixture.cs | 10 ++ .../Geofence/GeofenceServiceTests.cs | 7 + test/Core.Tests/LoggerMock.cs | 21 +++ 18 files changed, 405 insertions(+), 209 deletions(-) create mode 100644 src/Core/AppStart/ApplicationStartupExtensions.cs create mode 100644 src/Core/AppStart/LoggableApplicationStartup.cs create mode 100644 src/Core/AppStart/LoggableStartupOperation.cs delete mode 100644 test/Airframe.Tests/Core/AppStart/ApplicationStartupTest.cs delete mode 100644 test/Airframe.Tests/Core/AppStart/TestOperation.cs delete mode 100644 test/Airframe.Tests/Core/Geofence/GeofenceServiceTests.cs rename test/{Airframe.Tests/Core => Core.Tests}/AppStart/ApplicationStartupFixture.cs (71%) create mode 100644 test/Core.Tests/AppStart/ApplicationStartupTest.cs create mode 100644 test/Core.Tests/AppStart/TestOperation.cs create mode 100644 test/Core.Tests/Core.Tests.csproj create mode 100644 test/Core.Tests/Geofence/GeofenceServiceFixture.cs create mode 100644 test/Core.Tests/Geofence/GeofenceServiceTests.cs create mode 100644 test/Core.Tests/LoggerMock.cs diff --git a/Airframe.sln b/Airframe.sln index 819fe481e..cb4a52577 100644 --- a/Airframe.sln +++ b/Airframe.sln @@ -21,7 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solution", ".solution", "{ NuGet.config = NuGet.config README.md = README.md stylecop.json = stylecop.json - packages.props = packages.props .gitignore = .gitignore .editorconfig = .editorconfig .codecov.yml = .codecov.yml @@ -90,6 +89,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Airframe.ViewModels.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicrosoftDependencyInjection.Tests", "test\MicrosoftDependencyInjection.Tests\MicrosoftDependencyInjection.Tests.csproj", "{6C3C3C73-C46B-4BF2-8F78-F417135D5D3E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Tests", "test\Core.Tests\Core.Tests.csproj", "{A250A99B-0225-4B5D-9753-ED1DF770D61D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -178,6 +179,10 @@ Global {6C3C3C73-C46B-4BF2-8F78-F417135D5D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C3C3C73-C46B-4BF2-8F78-F417135D5D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C3C3C73-C46B-4BF2-8F78-F417135D5D3E}.Release|Any CPU.Build.0 = Release|Any CPU + {A250A99B-0225-4B5D-9753-ED1DF770D61D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A250A99B-0225-4B5D-9753-ED1DF770D61D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A250A99B-0225-4B5D-9753-ED1DF770D61D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A250A99B-0225-4B5D-9753-ED1DF770D61D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -204,6 +209,7 @@ Global {903C1B9D-F4F8-4540-8F46-E8EB8F15EAA5} = {843C3844-BA79-40AE-B102-F5AFA0F4AD77} {64AEB8AB-619C-4AF3-BA3F-407A4FA9A305} = {843C3844-BA79-40AE-B102-F5AFA0F4AD77} {6C3C3C73-C46B-4BF2-8F78-F417135D5D3E} = {843C3844-BA79-40AE-B102-F5AFA0F4AD77} + {A250A99B-0225-4B5D-9753-ED1DF770D61D} = {843C3844-BA79-40AE-B102-F5AFA0F4AD77} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8F0DF64A-C8D5-41DF-B4C9-5C70526644DF} diff --git a/directory.packages.props b/directory.packages.props index fab708b03..a351b8037 100644 --- a/directory.packages.props +++ b/directory.packages.props @@ -1,86 +1,82 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Core/AppStart/ApplicationStartup.cs b/src/Core/AppStart/ApplicationStartup.cs index 364375101..11941eb69 100644 --- a/src/Core/AppStart/ApplicationStartup.cs +++ b/src/Core/AppStart/ApplicationStartup.cs @@ -1,29 +1,21 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; +using Microsoft.Extensions.Logging; namespace Rocket.Surgery.Airframe { /// /// Represents the application startup sequence. /// - public sealed class ApplicationStartup : IApplicationStartup + public sealed class ApplicationStartup : LoggableApplicationStartup, IApplicationStartup { - private readonly IEnumerable _startupTasks; - /// /// Initializes a new instance of the class. /// - /// The startup tasks. - public ApplicationStartup(IEnumerable startupTasks) => - _startupTasks = startupTasks; - - /// - public IObservable Startup() => _startupTasks - .Where(x => x.CanExecute()) - .Select(x => x.Start()) - .Concat(); + /// The logger factory. + /// The startup operations. + public ApplicationStartup(ILoggerFactory loggerFactory, IEnumerable startupOperations) + : base(loggerFactory, startupOperations) + { + } } } \ No newline at end of file diff --git a/src/Core/AppStart/ApplicationStartupExtensions.cs b/src/Core/AppStart/ApplicationStartupExtensions.cs new file mode 100644 index 000000000..e6f7cba0a --- /dev/null +++ b/src/Core/AppStart/ApplicationStartupExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Reactive; + +namespace Rocket.Surgery.Airframe; + +/// +/// Future default interface implementations. Some call them mixins. +/// +public static class ApplicationStartupExtensions +{ + /// + /// Starts the application life cycle. + /// + /// The application startup. + /// A completion notification. + public static IObservable Startup(this IApplicationStartup startup) => startup.Startup(1); +} \ No newline at end of file diff --git a/src/Core/AppStart/IApplicationStartup.cs b/src/Core/AppStart/IApplicationStartup.cs index c844c7d06..112d1a7b1 100644 --- a/src/Core/AppStart/IApplicationStartup.cs +++ b/src/Core/AppStart/IApplicationStartup.cs @@ -1,5 +1,6 @@ using System; using System.Reactive; +using System.Reactive.Concurrency; namespace Rocket.Surgery.Airframe { @@ -11,7 +12,16 @@ public interface IApplicationStartup /// /// Starts the application life cycle. /// + /// The maximum concurrent operations. /// A completion notification. - IObservable Startup(); + IObservable Startup(int concurrentOperations); + + /// + /// Starts the application life cycle. + /// + /// The maximum concurrent operations. + /// The scheduler. + /// A completion notification. + IObservable Startup(int concurrentOperations, IScheduler scheduler); } } \ No newline at end of file diff --git a/src/Core/AppStart/LoggableApplicationStartup.cs b/src/Core/AppStart/LoggableApplicationStartup.cs new file mode 100644 index 000000000..2a2be0c0c --- /dev/null +++ b/src/Core/AppStart/LoggableApplicationStartup.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using Microsoft.Extensions.Logging; + +namespace Rocket.Surgery.Airframe; + +/// +/// Represents the application startup sequence. +/// +public abstract class LoggableApplicationStartup : IApplicationStartup +{ + private readonly ILogger _logger; + private readonly IEnumerable _startupOperations; + + /// + /// Initializes a new instance of the class. + /// + /// The logger factory. + /// The startup tasks. + protected LoggableApplicationStartup(ILoggerFactory loggerFactory, IEnumerable startupOperations) + { + _logger = loggerFactory.CreateLogger(GetType()); + _startupOperations = startupOperations; + } + + /// + IObservable IApplicationStartup.Startup(int concurrentOperations) => Startup(concurrentOperations) + .Finally(() => _logger.LogTrace("{Startup} completed", GetType().Name)); + + /// + IObservable IApplicationStartup.Startup(int concurrentOperations, IScheduler scheduler) => null; + + /// + /// Executes the provided . + /// + /// The maximum concurrent operations. + /// The scheduler. + /// A completion notification of the startup operation execution. + protected virtual IObservable Startup(int concurrentOperations, IScheduler scheduler) => _startupOperations + .Where(operation => operation.CanExecute()) + .Select(operation => operation.Start()) + .Merge(concurrentOperations, scheduler) + .PublishLast() + .RefCount(); + + /// + /// Executes the provided . + /// + /// The maximum concurrent operations. + /// A completion notification of the startup operation execution. + protected virtual IObservable Startup(int concurrentOperations = 1) => Startup(concurrentOperations, CurrentThreadScheduler.Instance); +} \ No newline at end of file diff --git a/src/Core/AppStart/LoggableStartupOperation.cs b/src/Core/AppStart/LoggableStartupOperation.cs new file mode 100644 index 000000000..7791e1218 --- /dev/null +++ b/src/Core/AppStart/LoggableStartupOperation.cs @@ -0,0 +1,50 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using Microsoft.Extensions.Logging; + +namespace Rocket.Surgery.Airframe +{ + /// + /// Represents a that logs using . + /// + public abstract class LoggableStartupOperation : IStartupOperation + { + /// + /// Initializes a new instance of the class. + /// + /// The logger factory. + protected LoggableStartupOperation(ILoggerFactory factory) => Logger = factory.CreateLogger(GetType()); + + /// + /// Gets the logger. + /// + protected ILogger Logger { get; } + + /// + IObservable IStartupOperation.Start() => + + // Add logging. + Start().Finally(() => Logger.LogTrace("Completed {Operation}", GetType().Name)); + + /// + bool IStartupOperation.CanExecute() + { + var canExecute = CanExecute(); + Logger.LogTrace("Can Execute: {CanExecute}", canExecute); + return canExecute; + } + + /// + /// Template method for the startup operation. + /// + /// A completion notification. + protected abstract IObservable Start(); + + /// + /// Template method for whether or not the startup operation will execute. + /// + /// A completion notification. + protected virtual bool CanExecute() => true; + } +} \ No newline at end of file diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 33e43b42f..deb95ea20 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -8,5 +8,7 @@ latest Abstractions and base implementations for Airframe. - + + + diff --git a/test/Airframe.Tests/Core/AppStart/ApplicationStartupTest.cs b/test/Airframe.Tests/Core/AppStart/ApplicationStartupTest.cs deleted file mode 100644 index b2adfb59f..000000000 --- a/test/Airframe.Tests/Core/AppStart/ApplicationStartupTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -using FluentAssertions; -using Microsoft.Reactive.Testing; -using ReactiveUI.Testing; -using Rocket.Surgery.Airframe; -using System; -using System.Reactive.Linq; -using Xunit; - -namespace Airframe.Tests.Core.AppStart -{ - public class ApplicationStartupTest : TestBase - { - [Fact] - public void Should_Notify_When_Operations_Complete() - { - // Given - var result = false; - var testScheduler = new TestScheduler(); - ApplicationStartup sut = new ApplicationStartupFixture().WithStartupOperations(new TestOperation(testScheduler, TimeSpan.FromSeconds(3))); - - // When - sut.Startup() - .Select(_ => true) - .Subscribe( - x => - { - // Then - result = x; - } - ); - testScheduler.AdvanceByMs(1000); - - result.Should().BeFalse(); - - testScheduler.AdvanceByMs(2001); - result.Should().BeTrue(); - } - - - [Fact] - public void Should_Skip_If_Cannot_Execute() - { - // Given - var testScheduler = new TestScheduler(); - ApplicationStartup sut = new ApplicationStartupFixture().WithStartupOperations(new TestOperation(testScheduler, TimeSpan.FromSeconds(3), false)); - - // When - sut.Startup() - .Select(_ => true) - .Subscribe( - x => - { - // Then - x.Should().BeTrue(); - } - ); - } - } -} \ No newline at end of file diff --git a/test/Airframe.Tests/Core/AppStart/TestOperation.cs b/test/Airframe.Tests/Core/AppStart/TestOperation.cs deleted file mode 100644 index 8fab38c8d..000000000 --- a/test/Airframe.Tests/Core/AppStart/TestOperation.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Rocket.Surgery.Airframe; -using System; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - -namespace Airframe.Tests.Core.AppStart -{ - internal class TestOperation : StartupOperationBase - { - private readonly IScheduler _scheduler; - private readonly TimeSpan _delay; - private readonly bool _canExecute; - - public TestOperation(IScheduler scheduler, TimeSpan delay, bool canExecute = true) - { - _scheduler = scheduler; - _delay = delay; - _canExecute = canExecute; - } - - protected override IObservable Start() => Observable.Return(Unit.Default).Delay(_delay, _scheduler); - protected override bool CanExecute() => _canExecute; - } -} \ No newline at end of file diff --git a/test/Airframe.Tests/Core/Geofence/GeofenceServiceTests.cs b/test/Airframe.Tests/Core/Geofence/GeofenceServiceTests.cs deleted file mode 100644 index c238235e6..000000000 --- a/test/Airframe.Tests/Core/Geofence/GeofenceServiceTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Rocket.Surgery.Airframe; -using Rocket.Surgery.Extensions.Testing.Fixtures; - -namespace Airframe.Tests.Core.Geofence -{ - public class GeofenceServiceTests - { - - } - - internal class GeofenceServiceFixture : ITestFixtureBuilder - { - public static implicit operator GeofenceService(GeofenceServiceFixture fixture) => fixture.Build(); - - private GeofenceService Build() => new GeofenceService(); - } -} \ No newline at end of file diff --git a/test/Airframe.Tests/Core/AppStart/ApplicationStartupFixture.cs b/test/Core.Tests/AppStart/ApplicationStartupFixture.cs similarity index 71% rename from test/Airframe.Tests/Core/AppStart/ApplicationStartupFixture.cs rename to test/Core.Tests/AppStart/ApplicationStartupFixture.cs index 979edfb9c..defddaa64 100644 --- a/test/Airframe.Tests/Core/AppStart/ApplicationStartupFixture.cs +++ b/test/Core.Tests/AppStart/ApplicationStartupFixture.cs @@ -1,18 +1,19 @@ -using Rocket.Surgery.Airframe; +using Microsoft.Extensions.Logging; +using NSubstitute; using Rocket.Surgery.Extensions.Testing.Fixtures; -using System.Collections.Generic; -using System.Linq; -namespace Airframe.Tests.Core.AppStart +namespace Rocket.Surgery.Airframe.Core.Tests.AppStart { internal class ApplicationStartupFixture : ITestFixtureBuilder { private IEnumerable _startupOperations = Enumerable.Empty(); + private ILoggerFactory _loggerFactory = Substitute.For(); + public static implicit operator ApplicationStartup(ApplicationStartupFixture fixture) => fixture.Build(); public ApplicationStartupFixture WithStartupOperations(params StartupOperationBase[] startupOperations) => this.With(ref _startupOperations, startupOperations); - private ApplicationStartup Build() => new ApplicationStartup(_startupOperations); + private ApplicationStartup Build() => new ApplicationStartup(_loggerFactory, _startupOperations); } } \ No newline at end of file diff --git a/test/Core.Tests/AppStart/ApplicationStartupTest.cs b/test/Core.Tests/AppStart/ApplicationStartupTest.cs new file mode 100644 index 000000000..997488bba --- /dev/null +++ b/test/Core.Tests/AppStart/ApplicationStartupTest.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using Microsoft.Reactive.Testing; +using ReactiveUI.Testing; +using System.Reactive; +using Xunit; + +namespace Rocket.Surgery.Airframe.Core.Tests.AppStart +{ + public class ApplicationStartupTest + { + [Fact] + public void GivenOperation_WhenStartupComplete_ThenCompletionRecieved() + { + // Given + Unit? result = null; + var testScheduler = new TestScheduler(); + ApplicationStartup sut = new ApplicationStartupFixture().WithStartupOperations(new ScheduledTestOperation(testScheduler, TimeSpan.FromSeconds(3))); + + // When + sut.Startup() + .Subscribe( + x => + { + // Then + result = x; + } + ); + testScheduler.AdvanceByMs(1000); + + result.Should().BeNull(); + + testScheduler.AdvanceByMs(2001); + result.Should().NotBeNull(); + } + + [Fact] + public void GivenOperationCannotExecute_WhenStartup_ThenNotExecuted() + { + // Given + var testOperation = new TestOperation(false); + ApplicationStartup sut = new ApplicationStartupFixture().WithStartupOperations(testOperation); + + // When + using var _ = sut.Startup().Subscribe(); + + // Then + testOperation.Executed.Should().BeFalse(); + } + + [Fact] + public void GivenOperationCanExecute_WhenStartup_ThenExecuted() + { + // Given + var testOperation = new TestOperation(); + ApplicationStartup sut = new ApplicationStartupFixture().WithStartupOperations(testOperation); + + // When + using var _ = sut.Startup().Subscribe(); + + // Then + testOperation.Executed.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/test/Core.Tests/AppStart/TestOperation.cs b/test/Core.Tests/AppStart/TestOperation.cs new file mode 100644 index 000000000..871c9fdd8 --- /dev/null +++ b/test/Core.Tests/AppStart/TestOperation.cs @@ -0,0 +1,37 @@ +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +namespace Rocket.Surgery.Airframe.Core.Tests.AppStart +{ + internal class ScheduledTestOperation : TestOperation + { + private readonly IScheduler _scheduler; + private readonly TimeSpan _delay; + + public ScheduledTestOperation(IScheduler scheduler, TimeSpan delay, bool willExecute = true) + : base(willExecute) + { + _scheduler = scheduler; + _delay = delay; + } + + /// + protected override IObservable Start() => Observable.Return(Unit.Default).Delay(_delay, _scheduler).Finally(() => Executed = true); + } + + internal class TestOperation : StartupOperationBase + { + private readonly bool _canExecute; + + public TestOperation(bool canExecute = true) => _canExecute = canExecute; + + public bool Executed { get; protected set; } + + /// + protected override IObservable Start() => Observable.Return(Unit.Default).Finally(() => Executed = true); + + /// + protected override bool CanExecute() => _canExecute; + } +} \ No newline at end of file diff --git a/test/Core.Tests/Core.Tests.csproj b/test/Core.Tests/Core.Tests.csproj new file mode 100644 index 000000000..795e38d7d --- /dev/null +++ b/test/Core.Tests/Core.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + false + Rocket.Surgery.Airframe.Core.Tests + Rocket.Surgery.Airframe.Core.Tests + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/test/Core.Tests/Geofence/GeofenceServiceFixture.cs b/test/Core.Tests/Geofence/GeofenceServiceFixture.cs new file mode 100644 index 000000000..a636ddd94 --- /dev/null +++ b/test/Core.Tests/Geofence/GeofenceServiceFixture.cs @@ -0,0 +1,10 @@ +using Rocket.Surgery.Extensions.Testing.Fixtures; + +namespace Rocket.Surgery.Airframe.Core.Tests.Geofence; + +internal class GeofenceServiceFixture : ITestFixtureBuilder +{ + public static implicit operator GeofenceService(GeofenceServiceFixture fixture) => fixture.Build(); + + private GeofenceService Build() => new GeofenceService(); +} \ No newline at end of file diff --git a/test/Core.Tests/Geofence/GeofenceServiceTests.cs b/test/Core.Tests/Geofence/GeofenceServiceTests.cs new file mode 100644 index 000000000..13299d119 --- /dev/null +++ b/test/Core.Tests/Geofence/GeofenceServiceTests.cs @@ -0,0 +1,7 @@ +namespace Rocket.Surgery.Airframe.Core.Tests.Geofence +{ + public class GeofenceServiceTests + { + + } +} \ No newline at end of file diff --git a/test/Core.Tests/LoggerMock.cs b/test/Core.Tests/LoggerMock.cs new file mode 100644 index 000000000..65ed7127e --- /dev/null +++ b/test/Core.Tests/LoggerMock.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Logging; +using System.Reactive.Disposables; +using Xunit.Abstractions; + +namespace Rocket.Surgery.Airframe.Core.Tests; + +internal class LoggerMock : ILogger +{ + private readonly ITestOutputHelper _testOutputHelper; + + public LoggerMock(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => LogToOutputHelper(eventId, state, exception, formatter); + + private void LogToOutputHelper(EventId eventId, TState state, Exception exception, Func formatter) => + _testOutputHelper.WriteLine(formatter(state, exception)); + + public bool IsEnabled(LogLevel logLevel) => true; + + public IDisposable BeginScope(TState state) => Disposable.Empty; +} \ No newline at end of file From 4866f87bb1e4597b40ed20e8120a1da7ea48724e Mon Sep 17 00:00:00 2001 From: rlittlesii <6969701+RLittlesII@users.noreply.github.com> Date: Fri, 23 Dec 2022 16:09:56 -0600 Subject: [PATCH 2/3] block scope because --- .../AppStart/ApplicationStartupExtensions.cs | 21 ++--- .../AppStart/LoggableApplicationStartup.cs | 79 ++++++++++--------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/src/Core/AppStart/ApplicationStartupExtensions.cs b/src/Core/AppStart/ApplicationStartupExtensions.cs index e6f7cba0a..0e87598bb 100644 --- a/src/Core/AppStart/ApplicationStartupExtensions.cs +++ b/src/Core/AppStart/ApplicationStartupExtensions.cs @@ -1,17 +1,18 @@ using System; using System.Reactive; -namespace Rocket.Surgery.Airframe; - -/// -/// Future default interface implementations. Some call them mixins. -/// -public static class ApplicationStartupExtensions +namespace Rocket.Surgery.Airframe { /// - /// Starts the application life cycle. + /// Future default interface implementations. Some call them mixins. /// - /// The application startup. - /// A completion notification. - public static IObservable Startup(this IApplicationStartup startup) => startup.Startup(1); + public static class ApplicationStartupExtensions + { + /// + /// Starts the application life cycle. + /// + /// The application startup. + /// A completion notification. + public static IObservable Startup(this IApplicationStartup startup) => startup.Startup(1); + } } \ No newline at end of file diff --git a/src/Core/AppStart/LoggableApplicationStartup.cs b/src/Core/AppStart/LoggableApplicationStartup.cs index 2a2be0c0c..7242df8f6 100644 --- a/src/Core/AppStart/LoggableApplicationStartup.cs +++ b/src/Core/AppStart/LoggableApplicationStartup.cs @@ -6,51 +6,52 @@ using System.Reactive.Linq; using Microsoft.Extensions.Logging; -namespace Rocket.Surgery.Airframe; - -/// -/// Represents the application startup sequence. -/// -public abstract class LoggableApplicationStartup : IApplicationStartup +namespace Rocket.Surgery.Airframe { - private readonly ILogger _logger; - private readonly IEnumerable _startupOperations; - /// - /// Initializes a new instance of the class. + /// Represents the application startup sequence. /// - /// The logger factory. - /// The startup tasks. - protected LoggableApplicationStartup(ILoggerFactory loggerFactory, IEnumerable startupOperations) + public abstract class LoggableApplicationStartup : IApplicationStartup { - _logger = loggerFactory.CreateLogger(GetType()); - _startupOperations = startupOperations; - } + private readonly ILogger _logger; + private readonly IEnumerable _startupOperations; - /// - IObservable IApplicationStartup.Startup(int concurrentOperations) => Startup(concurrentOperations) - .Finally(() => _logger.LogTrace("{Startup} completed", GetType().Name)); + /// + /// Initializes a new instance of the class. + /// + /// The logger factory. + /// The startup tasks. + protected LoggableApplicationStartup(ILoggerFactory loggerFactory, IEnumerable startupOperations) + { + _logger = loggerFactory.CreateLogger(GetType()); + _startupOperations = startupOperations; + } - /// - IObservable IApplicationStartup.Startup(int concurrentOperations, IScheduler scheduler) => null; + /// + IObservable IApplicationStartup.Startup(int concurrentOperations) => Startup(concurrentOperations) + .Finally(() => _logger.LogTrace("{Startup} completed", GetType().Name)); - /// - /// Executes the provided . - /// - /// The maximum concurrent operations. - /// The scheduler. - /// A completion notification of the startup operation execution. - protected virtual IObservable Startup(int concurrentOperations, IScheduler scheduler) => _startupOperations - .Where(operation => operation.CanExecute()) - .Select(operation => operation.Start()) - .Merge(concurrentOperations, scheduler) - .PublishLast() - .RefCount(); + /// + IObservable IApplicationStartup.Startup(int concurrentOperations, IScheduler scheduler) => null; - /// - /// Executes the provided . - /// - /// The maximum concurrent operations. - /// A completion notification of the startup operation execution. - protected virtual IObservable Startup(int concurrentOperations = 1) => Startup(concurrentOperations, CurrentThreadScheduler.Instance); + /// + /// Executes the provided . + /// + /// The maximum concurrent operations. + /// The scheduler. + /// A completion notification of the startup operation execution. + protected virtual IObservable Startup(int concurrentOperations, IScheduler scheduler) => _startupOperations + .Where(operation => operation.CanExecute()) + .Select(operation => operation.Start()) + .Merge(concurrentOperations, scheduler) + .PublishLast() + .RefCount(); + + /// + /// Executes the provided . + /// + /// The maximum concurrent operations. + /// A completion notification of the startup operation execution. + protected virtual IObservable Startup(int concurrentOperations = 1) => Startup(concurrentOperations, CurrentThreadScheduler.Instance); + } } \ No newline at end of file From 2006ac8de9f326a5f505cdb7243a40ee95fd7f7d Mon Sep 17 00:00:00 2001 From: rlittlesii <6969701+RLittlesII@users.noreply.github.com> Date: Fri, 23 Dec 2022 16:21:31 -0600 Subject: [PATCH 3/3] fixup --- .../AppStart/ApplicationStartupFixture.cs | 2 ++ .../AppStart/ApplicationStartupTest.cs | 1 + .../AppStart/ScheduledTestOperation.cs | 23 +++++++++++++++++++ test/Core.Tests/AppStart/TestOperation.cs | 18 +-------------- test/Core.Tests/Core.Tests.csproj | 1 - .../Geofence/GeofenceServiceFixture.cs | 10 -------- .../Geofence/GeofenceServiceTests.cs | 7 ------ test/Core.Tests/LoggerMock.cs | 22 ++++++++++-------- 8 files changed, 39 insertions(+), 45 deletions(-) create mode 100644 test/Core.Tests/AppStart/ScheduledTestOperation.cs delete mode 100644 test/Core.Tests/Geofence/GeofenceServiceFixture.cs delete mode 100644 test/Core.Tests/Geofence/GeofenceServiceTests.cs diff --git a/test/Core.Tests/AppStart/ApplicationStartupFixture.cs b/test/Core.Tests/AppStart/ApplicationStartupFixture.cs index defddaa64..b54006d38 100644 --- a/test/Core.Tests/AppStart/ApplicationStartupFixture.cs +++ b/test/Core.Tests/AppStart/ApplicationStartupFixture.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Logging; using NSubstitute; using Rocket.Surgery.Extensions.Testing.Fixtures; +using System.Collections.Generic; +using System.Linq; namespace Rocket.Surgery.Airframe.Core.Tests.AppStart { diff --git a/test/Core.Tests/AppStart/ApplicationStartupTest.cs b/test/Core.Tests/AppStart/ApplicationStartupTest.cs index 997488bba..4a6e1261b 100644 --- a/test/Core.Tests/AppStart/ApplicationStartupTest.cs +++ b/test/Core.Tests/AppStart/ApplicationStartupTest.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Microsoft.Reactive.Testing; using ReactiveUI.Testing; +using System; using System.Reactive; using Xunit; diff --git a/test/Core.Tests/AppStart/ScheduledTestOperation.cs b/test/Core.Tests/AppStart/ScheduledTestOperation.cs new file mode 100644 index 000000000..c918d3749 --- /dev/null +++ b/test/Core.Tests/AppStart/ScheduledTestOperation.cs @@ -0,0 +1,23 @@ +using System; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +namespace Rocket.Surgery.Airframe.Core.Tests.AppStart +{ + internal class ScheduledTestOperation : TestOperation + { + private readonly IScheduler _scheduler; + private readonly TimeSpan _delay; + + public ScheduledTestOperation(IScheduler scheduler, TimeSpan delay, bool willExecute = true) + : base(willExecute) + { + _scheduler = scheduler; + _delay = delay; + } + + /// + protected override IObservable Start() => Observable.Return(Unit.Default).Delay(_delay, _scheduler).Finally(() => Executed = true); + } +} \ No newline at end of file diff --git a/test/Core.Tests/AppStart/TestOperation.cs b/test/Core.Tests/AppStart/TestOperation.cs index 871c9fdd8..c0cdaa82e 100644 --- a/test/Core.Tests/AppStart/TestOperation.cs +++ b/test/Core.Tests/AppStart/TestOperation.cs @@ -1,25 +1,9 @@ +using System; using System.Reactive; -using System.Reactive.Concurrency; using System.Reactive.Linq; namespace Rocket.Surgery.Airframe.Core.Tests.AppStart { - internal class ScheduledTestOperation : TestOperation - { - private readonly IScheduler _scheduler; - private readonly TimeSpan _delay; - - public ScheduledTestOperation(IScheduler scheduler, TimeSpan delay, bool willExecute = true) - : base(willExecute) - { - _scheduler = scheduler; - _delay = delay; - } - - /// - protected override IObservable Start() => Observable.Return(Unit.Default).Delay(_delay, _scheduler).Finally(() => Executed = true); - } - internal class TestOperation : StartupOperationBase { private readonly bool _canExecute; diff --git a/test/Core.Tests/Core.Tests.csproj b/test/Core.Tests/Core.Tests.csproj index 795e38d7d..3f5743d33 100644 --- a/test/Core.Tests/Core.Tests.csproj +++ b/test/Core.Tests/Core.Tests.csproj @@ -2,7 +2,6 @@ net6.0 - enable false Rocket.Surgery.Airframe.Core.Tests Rocket.Surgery.Airframe.Core.Tests diff --git a/test/Core.Tests/Geofence/GeofenceServiceFixture.cs b/test/Core.Tests/Geofence/GeofenceServiceFixture.cs deleted file mode 100644 index a636ddd94..000000000 --- a/test/Core.Tests/Geofence/GeofenceServiceFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Rocket.Surgery.Extensions.Testing.Fixtures; - -namespace Rocket.Surgery.Airframe.Core.Tests.Geofence; - -internal class GeofenceServiceFixture : ITestFixtureBuilder -{ - public static implicit operator GeofenceService(GeofenceServiceFixture fixture) => fixture.Build(); - - private GeofenceService Build() => new GeofenceService(); -} \ No newline at end of file diff --git a/test/Core.Tests/Geofence/GeofenceServiceTests.cs b/test/Core.Tests/Geofence/GeofenceServiceTests.cs deleted file mode 100644 index 13299d119..000000000 --- a/test/Core.Tests/Geofence/GeofenceServiceTests.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Rocket.Surgery.Airframe.Core.Tests.Geofence -{ - public class GeofenceServiceTests - { - - } -} \ No newline at end of file diff --git a/test/Core.Tests/LoggerMock.cs b/test/Core.Tests/LoggerMock.cs index 65ed7127e..eeb60db6f 100644 --- a/test/Core.Tests/LoggerMock.cs +++ b/test/Core.Tests/LoggerMock.cs @@ -1,21 +1,23 @@ using Microsoft.Extensions.Logging; +using System; using System.Reactive.Disposables; using Xunit.Abstractions; -namespace Rocket.Surgery.Airframe.Core.Tests; - -internal class LoggerMock : ILogger +namespace Rocket.Surgery.Airframe.Core.Tests { - private readonly ITestOutputHelper _testOutputHelper; + internal class LoggerMock : ILogger + { + private readonly ITestOutputHelper _testOutputHelper; - public LoggerMock(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; + public LoggerMock(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => LogToOutputHelper(eventId, state, exception, formatter); + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => LogToOutputHelper(eventId, state, exception, formatter); - private void LogToOutputHelper(EventId eventId, TState state, Exception exception, Func formatter) => - _testOutputHelper.WriteLine(formatter(state, exception)); + private void LogToOutputHelper(EventId eventId, TState state, Exception exception, Func formatter) => + _testOutputHelper.WriteLine(formatter(state, exception)); - public bool IsEnabled(LogLevel logLevel) => true; + public bool IsEnabled(LogLevel logLevel) => true; - public IDisposable BeginScope(TState state) => Disposable.Empty; + public IDisposable BeginScope(TState state) => Disposable.Empty; + } } \ No newline at end of file