From fa50786df3dea99ec4d3b675d60976344d812cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Morgan?= Date: Thu, 23 Nov 2023 23:37:45 -0600 Subject: [PATCH 1/5] [skip ci] WIP: v2 rewrite --- .github/workflows/build.yml | 10 +- .github/workflows/publish.yml | 14 +- BuildTargets/CompileOptions.targets | 2 +- .../CommonExtensionsTests.cs | 6 +- .../{ServicePoolTests.cs => FlexPoolTests.cs} | 106 ++-- .../ServicePool.Tests.csproj | 16 +- src/ServicePool/DefaultDiscoveryEngine.cs | 4 +- src/ServicePool/Extensions/Common.cs | 71 ++- .../Extensions/DiscoverySupportExtensions.cs | 127 +++++ src/ServicePool/FlexPool.cs | 235 ++++++++ src/ServicePool/Pool.cs | 220 ++++++++ src/ServicePool/PoolBase.cs | 228 ++++++++ src/ServicePool/Resources/Errors.cs | 29 +- .../Resources/Strings/Errors.Designer.cs | 115 ++-- src/ServicePool/ServicePool.cs | 505 ------------------ src/ServicePool/ServicePool.csproj | 4 +- 16 files changed, 977 insertions(+), 715 deletions(-) rename src/ServicePool.Tests/{ServicePoolTests.cs => FlexPoolTests.cs} (78%) create mode 100644 src/ServicePool/Extensions/DiscoverySupportExtensions.cs create mode 100644 src/ServicePool/FlexPool.cs create mode 100644 src/ServicePool/Pool.cs create mode 100644 src/ServicePool/PoolBase.cs delete mode 100644 src/ServicePool/ServicePool.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42be89c..01b927d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,18 +21,18 @@ jobs: - name: Install .NET Core uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Install Codecov run: choco install codecov -y - name: Build and test ServicePool - run: dotnet test $env:Solution_Name --configuration=$env:Configuration --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover + run: dotnet test $env:Solution_Name --configuration=$env:Configuration --collect:"XPlat Code Coverage" --results-directory .\Build\tests -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover - name: Upload code coverage report - run: codecov -f "$((ls .\src\ServicePool.Tests\TestResults -Directory)[0].Fullname)\coverage.opencover.xml" + run: foreach ($j in (ls .\Build\tests\ -Directory)) { codecov -f "$($j.FullName)\coverage.opencover.xml" } env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Create NuGet Packages - run: dotnet pack $env:Solution_Name --configuration=$env:Configuration --version-suffix=$env:GITHUB_SHA --include-source -p:RepositoryBranch=${{ github.ref }} -p:ContinuousIntegrationBuild=true + run: dotnet pack $env:Solution_Name --configuration=$env:Configuration --version-suffix=$env:GITHUB_SHA --include-source -p:RepositoryBranch=${{ github.ref }} -p:RepositoryCommit=${{ github.sha }} -p:ContinuousIntegrationBuild=true - uses: actions/upload-artifact@v3 with: name: ServicePool-nuget-packages - path: Build/Bin/**/*.nupkg + path: Build/Bin/**/*.nupkg \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cbe00a7..ff6dcfb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,16 +15,10 @@ jobs: - name: Install .NET Core uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x - - name: Get tag name - uses: olegtarasov/get-tag@v2.1.2 + dotnet-version: 8.0.x - name: Create NuGet Packages - run: dotnet pack $env:Solution_Name --configuration=$env:Configuration -p:Version=$env:GIT_TAG_NAME -p:RepositoryBranch=${{ github.ref }} -p:ContinuousIntegrationBuild=true + run: dotnet pack $env:Solution_Name --configuration=${{ env.Configuration }} -p:Version=${{ github.ref_name }} -p:RepositoryBranch=${{ github.ref }} -p:RepositoryCommit=${{ github.sha }} -p:ContinuousIntegrationBuild=true - name: Push packages to GitHub - run: dotnet nuget push "Build/**/*.nupkg" -k $env:GhToken --skip-duplicate --source "https://nuget.pkg.github.com/TheXDS/" - env: - GhToken: ${{ secrets.GITHUB_TOKEN }} + run: dotnet nuget push "Build/**/*.nupkg" -k ${{ secrets.GITHUB_TOKEN }} --skip-duplicate --source "https://nuget.pkg.github.com/TheXDS/" - name: Push packages to NuGet - run: dotnet nuget push "Build/**/*.nupkg" -k $env:NgToken --skip-duplicate --source "https://api.nuget.org/v3/index.json" - env: - NgToken: ${{ secrets.NUGET_TOKEN }} + run: dotnet nuget push "Build/**/*.nupkg" -k ${{ secrets.NUGET_TOKEN }} --skip-duplicate --source "https://api.nuget.org/v3/index.json" \ No newline at end of file diff --git a/BuildTargets/CompileOptions.targets b/BuildTargets/CompileOptions.targets index 2d8034d..fb53b09 100644 --- a/BuildTargets/CompileOptions.targets +++ b/BuildTargets/CompileOptions.targets @@ -19,6 +19,6 @@ false - + \ No newline at end of file diff --git a/src/ServicePool.Tests/CommonExtensionsTests.cs b/src/ServicePool.Tests/CommonExtensionsTests.cs index 34b4afa..b85092a 100644 --- a/src/ServicePool.Tests/CommonExtensionsTests.cs +++ b/src/ServicePool.Tests/CommonExtensionsTests.cs @@ -39,7 +39,7 @@ public class CommonExtensionsTests [Test] public void RegisterInto_registers_singleton() { - var pool = new ServicePool(); + var pool = new FlexPool(); var x = new Random().RegisterInto(pool); Assert.IsInstanceOf(x); Assert.AreSame(x, pool.Resolve()); @@ -48,7 +48,7 @@ public void RegisterInto_registers_singleton() [Test] public void RegisterIntoIf_registers_if_true() { - var pool = new ServicePool(); + var pool = new FlexPool(); var x = new Random().RegisterIntoIf(pool, true); Assert.IsInstanceOf(x); Assert.AreSame(x, pool.Resolve()); @@ -57,7 +57,7 @@ public void RegisterIntoIf_registers_if_true() [Test] public void RegisterIntoIf_returns_null_if_false() { - var pool = new ServicePool(); + var pool = new FlexPool(); var x = new Random().RegisterIntoIf(pool, false); Assert.IsNull(x); Assert.IsNull(pool.Resolve()); diff --git a/src/ServicePool.Tests/ServicePoolTests.cs b/src/ServicePool.Tests/FlexPoolTests.cs similarity index 78% rename from src/ServicePool.Tests/ServicePoolTests.cs rename to src/ServicePool.Tests/FlexPoolTests.cs index 4edc578..0f4a920 100644 --- a/src/ServicePool.Tests/ServicePoolTests.cs +++ b/src/ServicePool.Tests/FlexPoolTests.cs @@ -1,4 +1,4 @@ -// ServicePoolTests.cs +// FlexPoolTests.cs // // This file is part of ServicePool // @@ -36,46 +36,16 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using TheXDS.ServicePool.Extensions; namespace TheXDS.ServicePool.Tests; -public class ServicePoolTests +public class FlexPoolTests { - [Test] - public void Registration_api_is_fluent() - { - ServicePool? pool = new(); - Assert.AreSame(pool, pool.Register()); - Assert.AreSame(pool, pool.Register(DummyFactory)); - Assert.AreSame(pool, pool.InitNow()); - Assert.AreSame(pool, pool.RegisterNow()); - Assert.AreSame(pool, pool.RegisterNow(new object())); - Assert.AreSame(pool, pool.RegisterIf(false)); - Assert.AreSame(pool, pool.RegisterIf(false, DummyFactory)); - Assert.AreSame(pool, pool.RegisterNowIf(false)); - Assert.AreSame(pool, pool.RegisterNowIf(false, new object())); - } - - [Test] - public void ActiveCount_gets_correct_count() - { - ServicePool? pool = new(); - Assert.Zero(pool.ActiveCount); - - pool.RegisterNow(); - Assert.AreEqual(1, pool.ActiveCount); - - pool.RegisterNow(); - Assert.AreEqual(2, pool.ActiveCount); - - pool.Register(); - Assert.AreEqual(2, pool.ActiveCount); - } - [Test] public void Count_gets_total_service_count() { - ServicePool? pool = new(); + FlexPool? pool = new(); Assert.Zero(pool.Count); pool.RegisterNow(); @@ -91,14 +61,16 @@ public void Count_gets_total_service_count() [Test] public void Pool_registers_and_resolves_cached_services() { - ServicePool? pool = new ServicePool().RegisterNow(); + FlexPool? pool = new FlexPool(); + pool.RegisterNow(); Assert.IsInstanceOf(pool.Resolve()); } [Test] public void Pool_resolves_persistent_services() { - ServicePool? pool = new ServicePool().Register(true); + FlexPool? pool = new FlexPool(); + pool.Register(true); Random? r1 = pool.Resolve(); Random? r2 = pool.Resolve(); @@ -110,7 +82,8 @@ public void Pool_resolves_persistent_services() [Test] public void Pool_resolves_non_persistent_services() { - ServicePool? pool = new ServicePool().Register(false); + FlexPool? pool = new FlexPool(); + pool.Register(false); Random? r1 = pool.Resolve(); Random? r2 = pool.Resolve(); @@ -122,14 +95,14 @@ public void Pool_resolves_non_persistent_services() [Test] public void Resolve_returns_null_if_not_registered() { - ServicePool? pool = new(); + FlexPool? pool = new(); Assert.IsNull(pool.Resolve()); } [Test] public void Resolve_resolves_for_interfaces() { - ServicePool? pool = new(); + FlexPool? pool = new(); pool.Register(); Assert.IsNotNull(pool.Resolve()); } @@ -137,7 +110,7 @@ public void Resolve_resolves_for_interfaces() [Test] public void Resolve_resolves_for_base_class() { - ServicePool? pool = new(); + FlexPool? pool = new(); pool.Register(); Assert.IsNotNull(pool.Resolve()); } @@ -145,7 +118,8 @@ public void Resolve_resolves_for_base_class() [Test] public void Pool_inits_lazy_services_on_InitNow() { - ServicePool? pool = new ServicePool().Register(); + FlexPool? pool = new FlexPool(); + pool.Register(); Assert.Zero(pool.ActiveCount); pool.InitNow(); Assert.AreEqual(1, pool.ActiveCount); @@ -154,7 +128,8 @@ public void Pool_inits_lazy_services_on_InitNow() [Test] public void InitNow_skips_non_persistent_services() { - ServicePool? pool = new ServicePool().Register(false); + FlexPool? pool = new FlexPool(); + pool.Register(false); Assert.Zero(pool.ActiveCount); pool.InitNow(); Assert.Zero(pool.ActiveCount); @@ -163,7 +138,8 @@ public void InitNow_skips_non_persistent_services() [Test] public void RegisterIf_skips_if_false() { - ServicePool? pool = new ServicePool().RegisterIf(false); + FlexPool? pool = new FlexPool(); + pool.RegisterIf(false); Assert.Zero(pool.Count); pool.RegisterIf(true); Assert.AreEqual(1, pool.Count); @@ -176,7 +152,8 @@ public void RegisterIf_skips_if_false() [Test] public void RegisterNowIf_skips_if_false() { - ServicePool? pool = new ServicePool().RegisterNowIf(false); + FlexPool? pool = new FlexPool(); + pool.RegisterNowIf(false); Assert.Zero(pool.Count); pool.RegisterNowIf(true); Assert.AreEqual(1, pool.Count); @@ -189,7 +166,7 @@ public void RegisterNowIf_skips_if_false() [Test] public void Discover_searches_for_service() { - ServicePool? pool = new(); + FlexPool? pool = new(); Assert.IsInstanceOf(pool.Discover()); Assert.AreEqual(1, pool.Count); } @@ -197,7 +174,7 @@ public void Discover_searches_for_service() [Test] public void Discover_returns_from_active_services() { - ServicePool? pool = new(); + FlexPool? pool = new(); pool.Register(); var r = pool.Resolve(); Assert.AreSame(r, pool.Discover()); @@ -206,7 +183,7 @@ public void Discover_returns_from_active_services() [Test] public void ResolveAll_returns_collection() { - ServicePool pool = new(); + FlexPool pool = new(); pool.RegisterNow(new List()); pool.RegisterNow(new Collection()); pool.RegisterNow(Array.Empty()); @@ -216,14 +193,14 @@ public void ResolveAll_returns_collection() [Test] public void DiscoverAll_enumerates_all_types_that_implement_base_type() { - ServicePool pool = new(); + FlexPool pool = new(); Assert.AreEqual(3, pool.DiscoverAll().ToArray().Length); } [Test] public void DiscoverAll_skips_existing_services() { - ServicePool pool = new(); + FlexPool pool = new(); pool.RegisterNow(); var t1 = pool.Resolve(); ITest[] c = pool.DiscoverAll().ToArray(); @@ -236,7 +213,7 @@ public void DiscoverAll_skips_existing_services() [Test] public void Pool_supports_removal() { - ServicePool pool = new(); + FlexPool pool = new(); pool.RegisterNow(); pool.Register(); Assert.IsTrue(pool.Remove()); @@ -250,7 +227,7 @@ public void Pool_supports_removal() [Test] public void Consume_removes_service() { - ServicePool pool = new(); + FlexPool pool = new(); pool.RegisterNow(); Assert.IsInstanceOf(pool.Consume()); Assert.Zero(pool.Count); @@ -264,8 +241,9 @@ public void Consume_removes_service() [Test] public void Enumerator_includes_lazy_and_eager_items() { - ServicePool pool = new(); - pool.RegisterNow().Register(); + FlexPool pool = new(); + pool.RegisterNow(); + pool.Register(); IEnumerator e = pool.GetEnumerator(); Assert.IsInstanceOf(e); @@ -278,18 +256,10 @@ public void Enumerator_includes_lazy_and_eager_items() Assert.AreEqual(2, c); } - [Test] - public void Common_ServicePool_exists() - { - Assert.IsAssignableFrom(ServicePool.CommonPool); - ServicePool pool = ServicePool.CommonPool; - Assert.AreSame(pool, ServicePool.CommonPool); - } - [Test] public void ServicePool_throws_error_on_invalid_registrations() { - ServicePool pool = new(); + FlexPool pool = new(); Assert.Catch(() => pool.RegisterNow()); Assert.Catch(() => pool.RegisterNow()); } @@ -297,7 +267,7 @@ public void ServicePool_throws_error_on_invalid_registrations() [Test] public void ServicePool_errors_have_messages() { - ServicePool pool = new(); + FlexPool pool = new(); var ex = Assert.Catch(() => pool.RegisterNow()); Assert.IsNotNull(ex); Assert.IsNotEmpty(ex!.Message); @@ -320,7 +290,7 @@ public void ServicePool_strings_have_localized_messages(string locale) [Test] public void ServicePool_throws_error_on_uninstantiable_class() { - ServicePool pool = new(); + FlexPool pool = new(); pool.Register(() => 3); Assert.Catch(() => pool.RegisterNow()); } @@ -328,10 +298,10 @@ public void ServicePool_throws_error_on_uninstantiable_class() [Test] public void ServicePool_injects_dependencies() { - ServicePool pool = new ServicePool() - .Register() - .Register() - .Register(); + FlexPool pool = new FlexPool(); + pool.Register(); + pool.Register(); + pool.Register(); var t4 = pool.Resolve()!; Assert.IsInstanceOf(t4); diff --git a/src/ServicePool.Tests/ServicePool.Tests.csproj b/src/ServicePool.Tests/ServicePool.Tests.csproj index 725627a..f463229 100644 --- a/src/ServicePool.Tests/ServicePool.Tests.csproj +++ b/src/ServicePool.Tests/ServicePool.Tests.csproj @@ -1,17 +1,17 @@ - + - net6.0 + net8.0 enable false - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -21,8 +21,4 @@ - - - - diff --git a/src/ServicePool/DefaultDiscoveryEngine.cs b/src/ServicePool/DefaultDiscoveryEngine.cs index 10e0a78..4607f2e 100644 --- a/src/ServicePool/DefaultDiscoveryEngine.cs +++ b/src/ServicePool/DefaultDiscoveryEngine.cs @@ -37,8 +37,8 @@ namespace TheXDS.ServicePool; /// /// /// This is used by default by -/// and -/// . +/// and +/// . /// public class DefaultDiscoveryEngine : IDiscoveryEngine { diff --git a/src/ServicePool/Extensions/Common.cs b/src/ServicePool/Extensions/Common.cs index d7f77c8..4c7b686 100644 --- a/src/ServicePool/Extensions/Common.cs +++ b/src/ServicePool/Extensions/Common.cs @@ -26,46 +26,45 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace TheXDS.ServicePool.Extensions +namespace TheXDS.ServicePool.Extensions; + +/// +/// Includes a set of useful extension methods to work with ServicePool. +/// +public static class Common { /// - /// Includes a set of useful extension methods to work with ServicePool. + /// Adds support for Fluent singleton registration on any object type. /// - public static class Common + /// + /// Type of object to register into the pool. + /// + /// Instance to register. + /// Pool to register the singleton into. + /// The same instance as . + public static T RegisterInto(this T obj, FlexPool pool) where T : notnull { - /// - /// Adds support for Fluent singleton registration on any object type. - /// - /// - /// Type of object to register into the pool. - /// - /// Instance to register. - /// Pool to register the singleton into. - /// The same instance as . - public static T RegisterInto(this T obj, ServicePool pool) where T : notnull - { - pool.RegisterNow(obj); - return obj; - } + pool.RegisterNow(obj); + return obj; + } - /// - /// Adds support for Fluent singleton registration on any object type. - /// - /// - /// Type of object to register into the pool. - /// - /// Instance to register. - /// Pool to register the singleton into. - /// - /// Value that determines if the singleton should be registered or not. - /// - /// - /// The same instance as if - /// is equal to , - /// otherwise. - public static T? RegisterIntoIf(this T obj, ServicePool pool, bool condition) where T : notnull - { - return condition ? obj.RegisterInto(pool) : default; - } + /// + /// Adds support for Fluent singleton registration on any object type. + /// + /// + /// Type of object to register into the pool. + /// + /// Instance to register. + /// Pool to register the singleton into. + /// + /// Value that determines if the singleton should be registered or not. + /// + /// + /// The same instance as if + /// is equal to , + /// otherwise. + public static T? RegisterIntoIf(this T obj, FlexPool pool, bool condition) where T : notnull + { + return condition ? obj.RegisterInto(pool) : default; } } diff --git a/src/ServicePool/Extensions/DiscoverySupportExtensions.cs b/src/ServicePool/Extensions/DiscoverySupportExtensions.cs new file mode 100644 index 0000000..8e05f84 --- /dev/null +++ b/src/ServicePool/Extensions/DiscoverySupportExtensions.cs @@ -0,0 +1,127 @@ +// DiscoverySupportExtensions.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace TheXDS.ServicePool.Extensions; + +public static class DiscoverySupportExtensions +{ + /// + /// Tries to resolve a registered service of type + /// , and if not found, searches for any type + /// in the app domain that can be instantiated and returned as the + /// requested service. + /// + /// Type of service to get. + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , the + /// discovered service will not be added to the pool. + /// + /// + /// A registered service or a newly discovered one if it implements the + /// requested type, or in case that no + /// discoverable service for the requested type exists. + /// + /// + /// When discovering new services, if a service of a specific type is + /// found inside the pool, it will be gracefully skipped and not + /// instantiated again. + /// + public static T? Discover(this PoolBase pool, bool persistent = true) where T : notnull + { + return (T?)Discover(pool, typeof(T), persistent); + } + + public static object? Discover(this PoolBase pool, Type objectType, bool persistent = true) + { + return Discover(pool, objectType, new DefaultDiscoveryEngine(), persistent); + } + + /// + /// Tries to resolve a registered service of type + /// , and if not found, searches for any type + /// that can be instantiated and returned as the requested service. + /// + /// Type of service to get. + /// + /// Discovery engine to use while searching for new instantiable types. + /// + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , the + /// discovered service will not be added to the pool. + /// + /// + /// A registered service or a newly discovered one if it implements the + /// requested type, or in case that no + /// discoverable service for the requested type exists. + /// + /// + /// When discovering new services, if a service of a specific type is + /// found inside the pool, it will be gracefully skipped and not + /// instantiated again. + /// + public static T? Discover(this PoolBase pool, IDiscoveryEngine discoveryEngine, bool persistent = true) where T : notnull + { + return (T?)Discover(pool, typeof(T), discoveryEngine, persistent); + } + + public static object? Discover(this PoolBase pool, Type objectType, IDiscoveryEngine discoveryEngine, bool persistent = true) + { + return pool.Resolve(objectType) ?? DiscoverAll(pool, discoveryEngine, objectType, persistent).FirstOrDefault(); + } + + public static IEnumerable DiscoverAll(this PoolBase pool, IDiscoveryEngine discoveryEngine, Type t, bool persistent) + { + foreach (Type dt in discoveryEngine.Discover(t)) + { + if (pool.Resolve(dt) is null && pool.CreateInstanceOrNull(dt) is { } obj) + { + if (persistent) pool.RegisterNow(obj); + yield return obj; + } + } + } +} + +public static class SugarExtensions +{ + public static void RegisterIf(this PoolBase pool, bool condition, Func factory, bool persistent = true) where T : notnull + { + if (condition) { pool.Register(factory, persistent); } + } +} \ No newline at end of file diff --git a/src/ServicePool/FlexPool.cs b/src/ServicePool/FlexPool.cs new file mode 100644 index 0000000..6936040 --- /dev/null +++ b/src/ServicePool/FlexPool.cs @@ -0,0 +1,235 @@ +// FlexPool.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using TheXDS.ServicePool.Extensions; + +namespace TheXDS.ServicePool; + +/// +/// Represents a collection of hosted services that can be instantiated and +/// resolved using dependency injection, resolving objects to any implemented +/// interface/base type defined in their declaration without explicit +/// registration. +/// +public class FlexPool : PoolBase +{ + private record FlexFactoryEntry(in Type Type, in bool Persistent, in Func Factory) : FactoryEntry(Persistent, Factory); + + private List _factories = []; + private List _instances = []; + + /// + public override int Count => _instances.Count + _factories.Count; + + /// + public override void Register(Type objectType, bool persistent = true) + { + _factories.Add(new(objectType, persistent, () => CreateInstance(objectType))); + } + + /// + public override void Register(Func factory, bool persistent = true) + { + _factories.Add(new(typeof(T), persistent, () => factory())); + } + + /// + public override void InitNow() + { + var entries = _factories.Where(p => p.Persistent).ToArray(); + foreach (var entry in entries) + { + RegisterNow(entry.Factory()); + _factories.Remove(entry); + } + } + + /// + public override IEnumerator GetEnumerator() + { + return _instances.Concat(_factories.Select(CreateFromLazy)).GetEnumerator(); + } + + /// + /// Instances and registers a new service of type + /// . + /// + /// Type of service to register. + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void RegisterNow() where T : notnull + { + RegisterNow(() => CreateInstance(typeof(T))); + } + + /// + public override void RegisterNow(object singleton) + { + _instances.Add(singleton); + } + + /// + /// Removes a singleton from this service pool. + /// + /// Service instance to remove. + /// + /// if the service instance was found and + /// removed from the pool, otherwise. + /// + public bool Remove(object service) + { + return _instances.Remove(service); + } + + /// + /// Removes a registered service from this service pool. + /// + /// Type of service to remove. + /// + /// if a registered service matching the + /// requested type was found and removed from the pool, + /// otherwise. + /// + public override bool Remove(Type objectType) + { + return ResolveActive(objectType) is { } o ? Remove(o) : (GetLazyFactory(objectType).FirstOrDefault() is { } f && _factories.Remove(f)); + } + + /// + /// Resolves all services that match the requested type. + /// + /// Type fo service to resolve. + /// + /// An enumeration of all the services (both eagerly and lazily + /// initialized) registered in the pool that implement the specified + /// type. + /// + public IEnumerable ResolveAll() where T : notnull + { + return _instances.OfType() + .Concat(GetLazyFactory(typeof(T)) + .Select(CreateFromLazy).Cast()); + } + + /// + /// Tries to resolve and register all services of type + /// found in the current app domain, returning + /// the resulting enumeration of all services found. + /// + /// Type of service to get. + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , any + /// discovered service will not be added to the pool. + /// + /// + /// A collection of all the services found in the current app domain, + /// or an empty enumeration in case that no discoverable service for + /// the requested type exists. + /// + /// + /// The resulting enumeration will contain all registered services, and + /// the discovery will skip any discoverable service for which there's + /// a singleton with the same type or a compatible lazy factory + /// registered. + /// + public IEnumerable DiscoverAll(bool persistent = true) where T : notnull + { + return DiscoverAll(new DefaultDiscoveryEngine(), persistent); + } + + /// + /// Tries to resolve and register all services of type + /// found using the specified + /// , returning the resulting enumeration + /// of all services found. + /// + /// Type of service to get. + /// + /// Discovery engine to use while searching for new instantiable types. + /// + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , any + /// discovered service will not be added to the pool. + /// + /// + /// A collection of all the services found in the current app domain, + /// or an empty enumeration in case that no discoverable service for + /// the requested type exists. + /// + /// + /// The resulting enumeration will contain all registered services, and + /// the discovery will skip any discoverable service for which there's + /// a singleton with the same type or a compatible lazy factory + /// registered. + /// + public IEnumerable DiscoverAll(IDiscoveryEngine discoveryEngine, bool persistent = true) where T : notnull + { + return ResolveAll().Concat(this.DiscoverAll(discoveryEngine, typeof(T), persistent).Cast()); + } + + /// + protected override object? ResolveActive(Type serviceType) + { + return _instances.FirstOrDefault(p => serviceType.IsAssignableFrom(p.GetType())); + } + + /// + protected override object? ResolveLazy(Type objectType) + { + return GetLazyFactory(objectType).FirstOrDefault() is { } f + ? CreateFromLazy(f) + : null; + } + + private object CreateFromLazy(FlexFactoryEntry factory) + { + var obj = factory.Factory.Invoke(); + if (factory.Persistent) + { + _factories.Remove(factory); + _instances.Add(obj); + } + return obj; + } + + private IEnumerable GetLazyFactory(Type serviceType) + { + return _factories.Where(p => serviceType.IsAssignableFrom(p.Type)); + } +} diff --git a/src/ServicePool/Pool.cs b/src/ServicePool/Pool.cs new file mode 100644 index 0000000..e319169 --- /dev/null +++ b/src/ServicePool/Pool.cs @@ -0,0 +1,220 @@ +// Pool.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using TheXDS.ServicePool.Resources; + +namespace TheXDS.ServicePool; + +/// +/// Represents a collection of hosted services that can be instantiated and +/// resolved using dependency injection, resolving objects to whichever +/// resolution type they have registered, and only allowing a single type per +/// pool to be registered. +/// +public class Pool : PoolBase +{ + /// + /// Gets a dictionary of all factories registered to generate + /// lazily-initialized services. + /// + protected Dictionary Factories { get; } = []; + + /// + /// Gets a dictionary of all persistent singletons currently active on this + /// pool instance. + /// + protected Dictionary Instances { get; } = []; + + /// + public override int Count => Instances.Count + Factories.Count; + + /// + public override void Register(Type objectType, bool persistent = true) + { + Register(objectType, [objectType], persistent); + } + + /// + public override void Register(Func factory, bool persistent = true) + { + Register(() => factory(), [typeof(T)], persistent); + } + + /// + public override void RegisterNow(object singleton) + { + RegisterNow(singleton, [singleton.GetType()]); + } + + /// + public override void InitNow() + { + var entries = Factories.Where(p => p.Value.Persistent).ToArray(); + foreach (var entry in entries) + { + RegisterNow(entry.Value.Factory()); + Factories.Remove(entry.Key); + } + } + + /// + public override bool Remove(Type objectType) + { + return Instances.Remove(objectType) || Factories.Remove(objectType); + } + + /// + /// Registers a new instance of the specified service. + /// + /// Instance of the service. + /// + /// Types to be registered as resolvable for the object to generate using + /// the specified factory. + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void RegisterNow(object singleton, Type[] typeRegistrations) + { + CheckUniqueType(typeRegistrations); + foreach (var j in typeRegistrations) + { + Instances.Add(j, singleton); + } + } + + /// + /// Registers a lazily-instantiated service. + /// + /// Object factory to use. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + /// + /// Types to be registered as resolvable for the object to generate using + /// the specified factory. + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void Register(Func factory, Type[] typeRegistrations, bool persistent = true) + { + CheckUniqueType(typeRegistrations); + foreach (var j in typeRegistrations) + { + Factories.Add(j, new(persistent, factory)); + } + } + + /// + /// Registers a lazily-instantiated service. + /// + /// Type of object to register. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + /// + /// Types to be registered as resolvable for the object to generate using + /// the specified factory. + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void Register(Type objectType, Type[] typeRegistrations, bool persistent = true) + { + Register(() => CreateInstance(objectType), typeRegistrations, persistent); + } + + /// + /// Registers a lazily-instantiated service. + /// + /// Type of object to register. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + /// + /// Types to be registered as resolvable for the object to generate using + /// the specified factory. + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void Register(Type[] typeRegistrations, bool persistent = true) where T : notnull + { + Register(typeof(T), typeRegistrations, persistent); + } + + /// + public override IEnumerator GetEnumerator() => Instances.Values.Concat(Factories.Select(CreateFromLazy)).GetEnumerator(); + + /// + protected override object? ResolveLazy(Type objectType) + { + return Factories.TryGetValue(objectType, out FactoryEntry? factory) ? CreateFromLazy(objectType, factory) : null; + } + + /// + protected override object? ResolveActive(Type objectType) + { + return Instances.TryGetValue(objectType, out var service) ? service : null; + } + + private object CreateFromLazy(KeyValuePair entry) => CreateFromLazy(entry.Key, entry.Value); + + private object CreateFromLazy(Type objectType, FactoryEntry factory) + { + var obj = factory.Factory.Invoke(); + if (factory.Persistent) + { + Factories.Remove(objectType); + Instances.Add(objectType, obj); + } + return obj; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckUniqueType(IEnumerable newKeys) + { + var existingKeys = Instances.Keys.Concat(Factories.Keys).ToArray(); + if (newKeys.Any(existingKeys.Contains)) throw new InvalidOperationException(); + } +} diff --git a/src/ServicePool/PoolBase.cs b/src/ServicePool/PoolBase.cs new file mode 100644 index 0000000..49c864f --- /dev/null +++ b/src/ServicePool/PoolBase.cs @@ -0,0 +1,228 @@ +// FlexPoolBase.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using TheXDS.ServicePool.Resources; + +namespace TheXDS.ServicePool; + +/// +/// Base class for all service pools that share dependency resolution logic +/// using object factories. +/// +public abstract class PoolBase : IEnumerable +{ + /// + /// Represents a single lazily-initialized object factory item. + /// + /// If set to , once the object instance is created, the new object will be persisted inside the pool and its factory removed. + /// + protected record FactoryEntry(in bool Persistent, in Func Factory); + + /// + /// Gets a count of all the registered services in this pool. + /// + public abstract int Count { get; } + + /// + /// Resolves and then removes a registered service from this service + /// pool. + /// + /// Type of service to consume. + /// + /// The first service that implements found on + /// the pool, or if no such service has been + /// registered. + /// + public T? Consume() where T : notnull => (T?)Consume(typeof(T)); + + /// + /// Resolves and then removes a registered service from this service + /// pool. + /// + /// Type of service to consume. + /// + /// The first service that implements found + /// on the pool, or if no such service has been + /// registered. + /// + public object? Consume(Type objectType) + { + object? obj = Resolve(objectType); + if (obj is not null) _ = Remove(objectType); + return obj; + } + + /// + /// Registers a lazily-instantiated service. + /// + /// Type of object to register. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void Register(bool persistent = true) where T : notnull => Register(typeof(T), persistent); + + /// + /// Instances and registers a new service of type + /// . + /// + /// Type of service to register. + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void RegisterNow(T singleton) where T : notnull => RegisterNow(singleton); + + public abstract void Register(Type objectType, bool persistent = true); + + /// + /// Registers a lazily-instantiated service. + /// + /// Type of service to register. + /// Object factory to use. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public abstract void Register(Func factory, bool persistent = true) where T : notnull; + + public abstract void RegisterNow(object singleton); + + /// + /// Initializes all registered services marked as persistent using + /// their respective registered factories. + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public abstract void InitNow(); + + /// + /// Tries to resolve a service that implements the specified interface + /// or base type. + /// + /// Type of service to get. + /// + /// The first service that implements found on + /// the pool, or if no such service has been + /// registered. + /// + public T? Resolve() where T : notnull => (T?)Resolve(typeof(T)); + + /// + /// Tries to resolve a service that implements the specified interface + /// or base type. + /// + /// Type of service to get. + /// + /// The first service that implements found + /// on the pool, or if no such service has been + /// registered. + /// + public object? Resolve(Type objectType) + { + return ResolveActive(objectType) ?? ResolveLazy(objectType); + } + + /// + /// Removes a registered service from this service pool. + /// + /// Type of service to remove. + /// + /// if a registered service matching the + /// requested type was found and removed from the pool, + /// otherwise. + /// + public bool Remove() => Remove(typeof(T)); + + /// + /// Removes a registered service from this service pool. + /// + /// Type of service to remove. + /// + /// if a registered service matching the + /// requested type was found and removed from the pool, + /// otherwise. + /// + public abstract bool Remove(Type objectType); + + /// + public abstract IEnumerator GetEnumerator(); + + public object? CreateInstanceOrNull(Type t) + { + if (t.IsAbstract || t.IsInterface) return null; + ConstructorInfo[] ctors = [.. t.GetConstructors().OrderByDescending(p => p.GetParameters().Length)]; + foreach (ConstructorInfo ctor in ctors) + { + if (IsValidCtor(ctor, t, out var args)) + { + return ctor.Invoke(args); + } + } + return null; + } + + public object CreateInstance(Type t) + { + return CreateInstanceOrNull(t) ?? throw Errors.CantInstantiate(); + } + + protected abstract object? ResolveLazy(Type objectType); + + protected abstract object? ResolveActive(Type objectType); + + private bool IsValidCtor(ConstructorInfo ctor, Type targetType, out object[]? args) + { + ParameterInfo[] pars = ctor.GetParameters(); + List a = []; + foreach (ParameterInfo arg in pars) + { + var value = + (targetType.IsAssignableFrom(arg.ParameterType) ? ResolveActive(arg.ParameterType) : Resolve(arg.ParameterType)) ?? + (arg.IsOptional ? Type.Missing : null); + if (value is null) break; + a.Add(value); + } + return (args = a.Count == pars.Length ? [.. a] : null) is not null; + } +} \ No newline at end of file diff --git a/src/ServicePool/Resources/Errors.cs b/src/ServicePool/Resources/Errors.cs index 8a0120e..19d102c 100644 --- a/src/ServicePool/Resources/Errors.cs +++ b/src/ServicePool/Resources/Errors.cs @@ -29,24 +29,23 @@ using System; using Ers = TheXDS.ServicePool.Resources.Strings.Errors; -namespace TheXDS.ServicePool.Resources +namespace TheXDS.ServicePool.Resources; + +/// +/// Exposes a set of common errors that can be thrown by ServicePool. +/// +public static class Errors { /// - /// Exposes a set of common errors that can be thrown by ServicePool. + /// Gets an that is normally + /// thrown when a class inside the pool cannot be instantiated. /// - public static class Errors + /// + /// An with a custom error + /// message. + /// + public static InvalidOperationException CantInstantiate() { - /// - /// Gets an that is normally - /// thrown when a class inside the pool cannot be instantiated. - /// - /// - /// An with a custom error - /// message. - /// - public static InvalidOperationException CantInstantiate() - { - return new InvalidOperationException(Ers.CantInstantiate); - } + return new InvalidOperationException(Ers.CantInstantiate); } } diff --git a/src/ServicePool/Resources/Strings/Errors.Designer.cs b/src/ServicePool/Resources/Strings/Errors.Designer.cs index a900cf9..60268eb 100644 --- a/src/ServicePool/Resources/Strings/Errors.Designer.cs +++ b/src/ServicePool/Resources/Strings/Errors.Designer.cs @@ -8,71 +8,70 @@ // //------------------------------------------------------------------------------ -namespace TheXDS.ServicePool.Resources.Strings { - using System; +namespace TheXDS.ServicePool.Resources.Strings; +using System; + + +/// +/// Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. +/// +// StronglyTypedResourceBuilder generó automáticamente esta clase +// a través de una herramienta como ResGen o Visual Studio. +// Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen +// con la opción /str o recompile su proyecto de VS. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +public class Errors { + + private static global::System.Resources.ResourceManager resourceMan; + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Errors() { + } /// - /// Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. + /// Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. /// - // StronglyTypedResourceBuilder generó automáticamente esta clase - // a través de una herramienta como ResGen o Visual Studio. - // Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen - // con la opción /str o recompile su proyecto de VS. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Errors { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Errors() { - } - - /// - /// Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TheXDS.ServicePool.Resources.Strings.Errors", typeof(Errors).Assembly); - resourceMan = temp; - } - return resourceMan; + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TheXDS.ServicePool.Resources.Strings.Errors", typeof(Errors).Assembly); + resourceMan = temp; } + return resourceMan; } - - /// - /// Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las - /// búsquedas de recursos mediante esta clase de recurso fuertemente tipado. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } + } + + /// + /// Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las + /// búsquedas de recursos mediante esta clase de recurso fuertemente tipado. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; } - - /// - /// Busca una cadena traducida similar a Cannot create a new instance of the requested service. Either: - ///- The type's constructor requied unavailable services. - ///- The type doesn't have any valid public constructors. - ///- The type is not instantiable (static, abstract). - ///- A constructor was found, but it required arguments that caused a circular service reference. - /// - ///Try registering the service using an explicit factory, or with the RegisterNow(object?, bool) method.. - /// - public static string CantInstantiate { - get { - return ResourceManager.GetString("CantInstantiate", resourceCulture); - } + set { + resourceCulture = value; + } + } + + /// + /// Busca una cadena traducida similar a Cannot create a new instance of the requested service. Either: + ///- The type's constructor requied unavailable services. + ///- The type doesn't have any valid public constructors. + ///- The type is not instantiable (static, abstract). + ///- A constructor was found, but it required arguments that caused a circular service reference. + /// + ///Try registering the service using an explicit factory, or with the RegisterNow(object?, bool) method.. + /// + public static string CantInstantiate { + get { + return ResourceManager.GetString("CantInstantiate", resourceCulture); } } } diff --git a/src/ServicePool/ServicePool.cs b/src/ServicePool/ServicePool.cs deleted file mode 100644 index edf4782..0000000 --- a/src/ServicePool/ServicePool.cs +++ /dev/null @@ -1,505 +0,0 @@ -// ServicePool.cs -// -// This file is part of ServicePool -// -// Author(s): -// César Andrés Morgan -// -// Released under the MIT License (MIT) -// Copyright © 2011 - 2022 César Andrés Morgan -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the “Software”), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using TheXDS.ServicePool.Resources; - -namespace TheXDS.ServicePool; - -/// -/// Represents a collection of hosted services that can be instantiated and -/// resolved using dependency injection. -/// -public class ServicePool : IEnumerable -{ - private static ServicePool? _commonPool; - - /// - /// Gets a static reference to a common service pool. - /// - /// - /// This property will instantiate the static pool upon it being accessed - /// for the first time, and the pool will live throughout the lifespan - /// of the application. - /// - public static ServicePool CommonPool - { - get => _commonPool ??= new(); - } - - private record FactoryEntry(Type Key, bool Persistent, Func Factory); - - private readonly ICollection _factories = new HashSet(); - private readonly ICollection _singletons = new HashSet(); - - /// - /// Gets the number of actively instantiated singletons registered in the - /// pool. - /// - public int ActiveCount => _singletons.Count; - - /// - /// Gets a count of all the registered services in this pool. - /// - public int Count => ActiveCount + _factories.Count; - - /// - /// Registers a lazily-instantiated singleton. - /// - /// Type of service to register. - /// - /// If set to , the resolved singleton is going - /// to be persisted in the service pool. When , - /// the registered service will be instantiated and initialized each time - /// it is requested. - /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool Register(bool persistent = true) where T : notnull - { - return Register(CreateNewInstance, persistent); - } - - /// - /// Registers a lazily-instantiated singleton. - /// - /// Type of service to register. - /// Singleton factory to use. - /// - /// If set to , the resolved singleton is going - /// to be persisted in the service pool. When , - /// the registered service will be instantiated and initialized each time - /// it is requested. - /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool Register(Func factory, bool persistent = true) where T : notnull - { - _factories.Add(new(typeof(T), persistent, () => factory())); - return this; - } - - /// - /// Initializes all registered services marked as persistent using - /// their respective registered factories. - /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool InitNow() - { - FactoryEntry[] entries = _factories.Where(p => p.Persistent).ToArray(); - foreach (FactoryEntry entry in entries) - { - RegisterNow(entry.Factory()); - _factories.Remove(entry); - } - return this; - } - - /// - /// Instances and registers a new service of type - /// . - /// - /// Type of service to register. - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool RegisterNow() where T : notnull - { - return RegisterNow(CreateNewInstance()); - } - - /// - /// Registers a new instance of the specified service. - /// - /// Instance of the service. - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool RegisterNow(object singleton) - { - _singletons.Add(singleton); - return this; - } - - /// - /// Registers a lazily-instantiated singleton of type - /// if the condition is true. - /// - /// Type of service to register. - /// - /// Determines if the service will be added to this pool. - /// - /// - /// If set to , the resolved singleton is going - /// to be persisted in the service pool. When , - /// the registered service will be instantiated and initialized each time - /// it is requested. - /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool RegisterIf(bool condition, bool persistent = true) where T : notnull - { - return condition ? Register(persistent) : this; - } - - /// - /// Registers a lazily-instantiated singleton of type - /// if the condition is true. - /// - /// - /// Determines if the service will be added to this pool. - /// - /// Singleton factory to use. - /// - /// If set to , the resolved singleton is going - /// to be persisted in the service pool. When , - /// the registered service will be instantiated and initialized each time - /// it is requested. - /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool RegisterIf(bool condition, Func factory, bool persistent = true) where T : notnull - { - return condition ? Register(factory, persistent) : this; - } - - /// - /// Instantiates and registers a new service of type - /// if the condition is true. - /// - /// Type of service to register. - /// - /// Determines if the service will be added to this pool. - /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool RegisterNowIf(bool condition) where T : notnull - { - return condition ? RegisterNow() : this; - } - - /// - /// Instantiates and registers a new service if the condition is true. - /// - /// - /// Determines if the service will be added to this pool. - /// - /// Instance of the service. - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public ServicePool RegisterNowIf(bool condition, object singleton) - { - return condition ? RegisterNow(singleton) : this; - } - - /// - /// Tries to resolve a service that implements the specified interface - /// or base type. - /// - /// Type of service to get. - /// - /// The first service that implements found on - /// the pool, or if no such service has been - /// registered. - /// - public T? Resolve() where T : notnull => (T?)Resolve(typeof(T)); - - /// - /// Resolves all services that match the requested type. - /// - /// Type fo service to resolve. - /// - /// An enumeration of all the services (both eagerly and lazily - /// initialized) registered in the pool that implement the specified - /// type. - /// - public IEnumerable ResolveAll() where T : notnull - { - return _singletons.OfType() - .Concat(GetLazyFactory(typeof(T)) - .Select(CreateFromLazy).Cast()); - } - - /// - /// Tries to resolve a registered service of type - /// , and if not found, searches for any type - /// in the app domain that can be instantiated and returned as the - /// requested service. - /// - /// Type of service to get. - /// - /// If set to , in case a service of the - /// specified type hasn't been registered and a compatible type has - /// been discovered, the newly created instance will be registered - /// persistently in the pool. If set to , the - /// discovered service will not be added to the pool. - /// - /// - /// A registered service or a newly discovered one if it implements the - /// requested type, or in case that no - /// discoverable service for the requested type exists. - /// - /// - /// When discovering new services, if a service of a specific type is - /// found inside the pool, it will be gracefully skipped and not - /// instantiated again. - /// - public T? Discover(bool persistent = true) where T : notnull - { - return Discover(new DefaultDiscoveryEngine(), persistent); - } - - /// - /// Tries to resolve a registered service of type - /// , and if not found, searches for any type - /// that can be instantiated and returned as the requested service. - /// - /// Type of service to get. - /// - /// Discovery engine to use while searching for new instantiable types. - /// - /// - /// If set to , in case a service of the - /// specified type hasn't been registered and a compatible type has - /// been discovered, the newly created instance will be registered - /// persistently in the pool. If set to , the - /// discovered service will not be added to the pool. - /// - /// - /// A registered service or a newly discovered one if it implements the - /// requested type, or in case that no - /// discoverable service for the requested type exists. - /// - /// - /// When discovering new services, if a service of a specific type is - /// found inside the pool, it will be gracefully skipped and not - /// instantiated again. - /// - public T? Discover(IDiscoveryEngine discoveryEngine, bool persistent = true) where T : notnull - { - return Resolve(typeof(T)) is T o ? o : (T?)Discover(discoveryEngine, typeof(T), persistent).FirstOrDefault(); - } - - /// - /// Tries to resolve and register all services of type - /// found in the current app domain, returning - /// the resulting enumeration of all services found. - /// - /// Type of service to get. - /// - /// If set to , in case a service of the - /// specified type hasn't been registered and a compatible type has - /// been discovered, the newly created instance will be registered - /// persistently in the pool. If set to , any - /// discovered service will not be added to the pool. - /// - /// - /// A collection of all the services found in the current app domain, - /// or an empty enumeration in case that no discoverable service for - /// the requested type exists. - /// - /// - /// The resulting enumeration will contain all registered services, and - /// the discovery will skip any discoverable service for which there's - /// a singleton with the same type or a compatible lazy factory - /// registered. - /// - public IEnumerable DiscoverAll(bool persistent = true) where T : notnull - { - return DiscoverAll(new DefaultDiscoveryEngine(), persistent); - } - - /// - /// Tries to resolve and register all services of type - /// found using the specified - /// , returning the resulting enumeration - /// of all services found. - /// - /// Type of service to get. - /// - /// Discovery engine to use while searching for new instantiable types. - /// - /// - /// If set to , in case a service of the - /// specified type hasn't been registered and a compatible type has - /// been discovered, the newly created instance will be registered - /// persistently in the pool. If set to , any - /// discovered service will not be added to the pool. - /// - /// - /// A collection of all the services found in the current app domain, - /// or an empty enumeration in case that no discoverable service for - /// the requested type exists. - /// - /// - /// The resulting enumeration will contain all registered services, and - /// the discovery will skip any discoverable service for which there's - /// a singleton with the same type or a compatible lazy factory - /// registered. - /// - public IEnumerable DiscoverAll(IDiscoveryEngine discoveryEngine, bool persistent = true) where T : notnull - { - return ResolveAll().Concat(Discover(discoveryEngine, typeof(T), persistent).Cast()); - } - - /// - /// Removes a singleton from this service pool. - /// - /// Service instance to remove. - /// - /// if the service instance was found and - /// removed from the pool, otherwise. - /// - public bool Remove(object service) - { - return _singletons.Remove(service); - } - - /// - /// Removes a registered service from this service pool. - /// - /// Type of service to remove. - /// - /// if a registered service matching the - /// requested type was found and removed from the pool, - /// otherwise. - /// - public bool Remove() - { - return ResolveActive(typeof(T)) is { } o ? Remove(o) : (GetLazyFactory(typeof(T)).FirstOrDefault() is { } f && _factories.Remove(f)); - } - - /// - /// Resolves and then removes a registered service from this service - /// pool. - /// - /// Type of service to consume. - /// - /// The first service that implements found on - /// the pool, or if no such service has been - /// registered. - /// - public T? Consume() where T : notnull - { - T? obj = Resolve(); - if (obj is not null) _ = Remove(obj) || Remove(); - return obj; - } - - /// - public IEnumerator GetEnumerator() - { - return ((IEnumerable)_singletons.Concat(_factories.Select(CreateFromLazy))).GetEnumerator(); - } - - private IEnumerable Discover(IDiscoveryEngine discoveryEngine, Type t, bool persistent) - { - foreach (Type dt in discoveryEngine.Discover(t)) - { - if (Resolve(dt) is null && CreateNewInstance(dt) is { } obj) - { - if (persistent) RegisterNow(obj); - yield return obj; - } - } - } - - private object? CreateNewInstance(Type t) - { - if (t.IsAbstract || t.IsInterface) return null; - ConstructorInfo[] ctors = t.GetConstructors().OrderByDescending(p => p.GetParameters().Length).ToArray(); - foreach (ConstructorInfo ctor in ctors) - { - ParameterInfo[] pars = ctor.GetParameters(); - List args = new(); - foreach (ParameterInfo arg in pars) - { - var value = - (t.IsAssignableFrom(arg.ParameterType) ? ResolveActive(arg.ParameterType) : Resolve(arg.ParameterType)) ?? - (arg.IsOptional ? Type.Missing : null); - if (value is null) break; - args.Add(value); - } - if (args.Count == pars.Length) - { - return ctor.Invoke(args.ToArray()); - } - } - return null; - } - - private T CreateNewInstance() - { - return (T)(CreateNewInstance(typeof(T)) ?? throw Errors.CantInstantiate()); - } - - private object? Resolve(Type serviceType) - { - return ResolveActive(serviceType) ?? ResolveLazy(serviceType); - } - - private object? ResolveActive(Type serviceType) - { - return _singletons.FirstOrDefault(p => serviceType.IsAssignableFrom(p.GetType())); - } - - private object? ResolveLazy(Type serviceType) - { - return GetLazyFactory(serviceType).FirstOrDefault() is { } factory - ? CreateFromLazy(factory) - : null; - } - - private object CreateFromLazy(FactoryEntry factory) - { - var obj = factory.Factory.Invoke(); - if (factory.Persistent) - { - _factories.Remove(factory); - RegisterNow(obj); - } - return obj; - } - - private IEnumerable GetLazyFactory(Type serviceType) - { - return _factories.Where(p => serviceType.IsAssignableFrom(p.Key)); - } -} diff --git a/src/ServicePool/ServicePool.csproj b/src/ServicePool/ServicePool.csproj index ee2f39a..7a264aa 100644 --- a/src/ServicePool/ServicePool.csproj +++ b/src/ServicePool/ServicePool.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable @@ -21,7 +21,7 @@ - + From 6918227735b300b1c81929e8aa7f91c31ab5639f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Morgan?= Date: Fri, 15 Dec 2023 10:46:23 -0600 Subject: [PATCH 2/5] WIP: ServicePool V2 --- README.md | 19 +++ src/ServicePool.Tests/FlexPoolTests.cs | 63 +------- src/ServicePool/DefaultDiscoveryEngine.cs | 5 +- .../Extensions/DiscoverySupportExtensions.cs | 148 +++++++++++++++++- src/ServicePool/Extensions/SugarExtensions.cs | 112 +++++++++++++ src/ServicePool/FlexPool.cs | 84 +--------- src/ServicePool/Pool.cs | 1 - src/ServicePool/PoolBase.cs | 89 +++++++++-- src/ServicePool/ServicePool.csproj | 3 +- 9 files changed, 359 insertions(+), 165 deletions(-) create mode 100644 src/ServicePool/Extensions/SugarExtensions.cs diff --git a/README.md b/README.md index 4339571..6a5ab32 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,22 @@ The resulting binaries will be in the `./Build/bin` directory. ```sh dotnet test ./src/ServicePool.sln ``` +#### Coverage reports +It is possible to generate a coverage report locally.For that, it is necessary to install [`ReportGenerator`](https://github.com/danielpalme/ReportGenerator), which will read the test results after execution, and will generate a web page with the coverage results. + +To install `ReportGenerator` execute: +```sh +dotnet tool install -g dotnet-reportgenerator-globaltool +``` +After installing `ReportGenerator`, it will be possible to execute the following command: +```sh +dotnet test ./src/ServicePool.sln --collect:"XPlat Code Coverage" --results-directory:./Build/Tests ; reportgenerator -reports:./Build/Tests/*/coverage.cobertura.xml -targetdir:./Build/Coverage/ +``` +The coverage reports will be stored in `./Build/Coverage` + +## Contribute +[![Buy Me A Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://www.buymeacoffee.com/xdsxpsivx) + +If `ServicePool` is useful to you, or if you're interested in donating to sponsor the project, feel free to to a donation via [PayPal](https://paypal.me/thexds), [BuyMeACoffee](https://www.buymeacoffee.com/xdsxpsivx) or just contact me directly. + +Sadly, I cannot offer other means of sending donations as of right now due to my country (Honduras) not being supported by almost any platform. diff --git a/src/ServicePool.Tests/FlexPoolTests.cs b/src/ServicePool.Tests/FlexPoolTests.cs index 0f4a920..a3d3a6f 100644 --- a/src/ServicePool.Tests/FlexPoolTests.cs +++ b/src/ServicePool.Tests/FlexPoolTests.cs @@ -27,6 +27,7 @@ // SOFTWARE. #pragma warning disable CS1591 +#pragma warning disable IDE0290 using NUnit.Framework; using System; @@ -61,7 +62,7 @@ public void Count_gets_total_service_count() [Test] public void Pool_registers_and_resolves_cached_services() { - FlexPool? pool = new FlexPool(); + FlexPool? pool = new(); pool.RegisterNow(); Assert.IsInstanceOf(pool.Resolve()); } @@ -69,7 +70,7 @@ public void Pool_registers_and_resolves_cached_services() [Test] public void Pool_resolves_persistent_services() { - FlexPool? pool = new FlexPool(); + FlexPool? pool = new(); pool.Register(true); Random? r1 = pool.Resolve(); Random? r2 = pool.Resolve(); @@ -82,7 +83,7 @@ public void Pool_resolves_persistent_services() [Test] public void Pool_resolves_non_persistent_services() { - FlexPool? pool = new FlexPool(); + FlexPool? pool = new(); pool.Register(false); Random? r1 = pool.Resolve(); Random? r2 = pool.Resolve(); @@ -115,54 +116,6 @@ public void Resolve_resolves_for_base_class() Assert.IsNotNull(pool.Resolve()); } - [Test] - public void Pool_inits_lazy_services_on_InitNow() - { - FlexPool? pool = new FlexPool(); - pool.Register(); - Assert.Zero(pool.ActiveCount); - pool.InitNow(); - Assert.AreEqual(1, pool.ActiveCount); - } - - [Test] - public void InitNow_skips_non_persistent_services() - { - FlexPool? pool = new FlexPool(); - pool.Register(false); - Assert.Zero(pool.ActiveCount); - pool.InitNow(); - Assert.Zero(pool.ActiveCount); - } - - [Test] - public void RegisterIf_skips_if_false() - { - FlexPool? pool = new FlexPool(); - pool.RegisterIf(false); - Assert.Zero(pool.Count); - pool.RegisterIf(true); - Assert.AreEqual(1, pool.Count); - pool.RegisterIf(false, DummyFactory); - Assert.AreEqual(1, pool.Count); - pool.RegisterIf(true, DummyFactory); - Assert.AreEqual(2, pool.Count); - } - - [Test] - public void RegisterNowIf_skips_if_false() - { - FlexPool? pool = new FlexPool(); - pool.RegisterNowIf(false); - Assert.Zero(pool.Count); - pool.RegisterNowIf(true); - Assert.AreEqual(1, pool.Count); - pool.RegisterNowIf(false, new Exception()); - Assert.AreEqual(1, pool.Count); - pool.RegisterNowIf(true, new Exception()); - Assert.AreEqual(2, pool.Count); - } - [Test] public void Discover_searches_for_service() { @@ -298,7 +251,7 @@ public void ServicePool_throws_error_on_uninstantiable_class() [Test] public void ServicePool_injects_dependencies() { - FlexPool pool = new FlexPool(); + FlexPool pool = new(); pool.Register(); pool.Register(); pool.Register(); @@ -315,12 +268,6 @@ private interface ITest void Test(); } - [ExcludeFromCodeCoverage] - private static object DummyFactory() - { - return new object(); - } - [ExcludeFromCodeCoverage] private abstract class AbstractTest : ITest { diff --git a/src/ServicePool/DefaultDiscoveryEngine.cs b/src/ServicePool/DefaultDiscoveryEngine.cs index 4607f2e..eccd636 100644 --- a/src/ServicePool/DefaultDiscoveryEngine.cs +++ b/src/ServicePool/DefaultDiscoveryEngine.cs @@ -37,8 +37,9 @@ namespace TheXDS.ServicePool; /// /// /// This is used by default by -/// and -/// . +/// , +/// +/// and other related overloads of these methods. /// public class DefaultDiscoveryEngine : IDiscoveryEngine { diff --git a/src/ServicePool/Extensions/DiscoverySupportExtensions.cs b/src/ServicePool/Extensions/DiscoverySupportExtensions.cs index 8e05f84..4ced7dd 100644 --- a/src/ServicePool/Extensions/DiscoverySupportExtensions.cs +++ b/src/ServicePool/Extensions/DiscoverySupportExtensions.cs @@ -33,6 +33,10 @@ namespace TheXDS.ServicePool.Extensions; +/// +/// Includes a set of extensions to extend the functionality of service pools +/// to include service discovery functionality. +/// public static class DiscoverySupportExtensions { /// @@ -42,6 +46,9 @@ public static class DiscoverySupportExtensions /// requested service. /// /// Type of service to get. + /// + /// Pool instance to discover the services from/onto. + /// /// /// If set to , in case a service of the /// specified type hasn't been registered and a compatible type has @@ -64,6 +71,27 @@ public static class DiscoverySupportExtensions return (T?)Discover(pool, typeof(T), persistent); } + /// + /// Tries to resolve a registered service of the specified type, and if not + /// found, searches for any type that can be instantiated and returned as + /// the requested service. + /// + /// + /// Pool instance to discover the service from/onto. + /// + /// Type fo service to discover. + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , the + /// discovered service will not be added to the pool. + /// + /// + /// A registered service or a newly discovered one if it implements the + /// requested type, or in case that no + /// discoverable service for the requested type exists. + /// public static object? Discover(this PoolBase pool, Type objectType, bool persistent = true) { return Discover(pool, objectType, new DefaultDiscoveryEngine(), persistent); @@ -75,6 +103,9 @@ public static class DiscoverySupportExtensions /// that can be instantiated and returned as the requested service. /// /// Type of service to get. + /// + /// Pool instance to discover the service from/onto. + /// /// /// Discovery engine to use while searching for new instantiable types. /// @@ -100,11 +131,55 @@ public static class DiscoverySupportExtensions return (T?)Discover(pool, typeof(T), discoveryEngine, persistent); } + /// + /// Tries to resolve a registered service of the specified type, and if not + /// found, searches for any type that can be instantiated and returned as + /// the requested service. + /// + /// + /// Pool instance to discover the service from/onto. + /// + /// Type fo service to discover. + /// + /// Discovery engine to use while searching for new instantiable types. + /// + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , the + /// discovered service will not be added to the pool. + /// + /// + /// A registered service or a newly discovered one if it implements the + /// requested type, or in case that no + /// discoverable service for the requested type exists. + /// public static object? Discover(this PoolBase pool, Type objectType, IDiscoveryEngine discoveryEngine, bool persistent = true) { return pool.Resolve(objectType) ?? DiscoverAll(pool, discoveryEngine, objectType, persistent).FirstOrDefault(); } + /// + /// Discovers all the available service instances that implement the specified service type. + /// + /// + /// Pool instance to discover the services from/onto. + /// + /// + /// Discovery engine to use while searching for new instantiable types. + /// + /// Type of service to discover. + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instances will be registered + /// persistently in the pool. If set to , the + /// discovered services will not be added to the pool. + /// + /// + /// An enumeration of all discovered services. + /// public static IEnumerable DiscoverAll(this PoolBase pool, IDiscoveryEngine discoveryEngine, Type t, bool persistent) { foreach (Type dt in discoveryEngine.Discover(t)) @@ -116,12 +191,73 @@ public static class DiscoverySupportExtensions } } } -} -public static class SugarExtensions -{ - public static void RegisterIf(this PoolBase pool, bool condition, Func factory, bool persistent = true) where T : notnull + /// + /// Tries to resolve and register all services of type + /// found in the current app domain, returning + /// the resulting enumeration of all services found. + /// + /// Type of service to get. + /// + /// Pool instance to discover the services from/onto. + /// + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , any + /// discovered service will not be added to the pool. + /// + /// + /// A collection of all the services found in the current app domain, + /// or an empty enumeration in case that no discoverable service for + /// the requested type exists. + /// + /// + /// The resulting enumeration will contain all registered services, and + /// the discovery will skip any discoverable service for which there's + /// a singleton with the same type or a compatible lazy factory + /// registered. + /// + public static IEnumerable DiscoverAll(this FlexPool pool, bool persistent = true) where T : notnull { - if (condition) { pool.Register(factory, persistent); } + return DiscoverAll(pool, new DefaultDiscoveryEngine(), persistent); } -} \ No newline at end of file + + + /// + /// Tries to resolve and register all services of type + /// found using the specified + /// , returning the resulting enumeration + /// of all services found. + /// + /// Type of service to get. + /// + /// Pool instance to discover the services from/onto. + /// + /// + /// Discovery engine to use while searching for new instantiable types. + /// + /// + /// If set to , in case a service of the + /// specified type hasn't been registered and a compatible type has + /// been discovered, the newly created instance will be registered + /// persistently in the pool. If set to , any + /// discovered service will not be added to the pool. + /// + /// + /// A collection of all the services found in the current app domain, + /// or an empty enumeration in case that no discoverable service for + /// the requested type exists. + /// + /// + /// The resulting enumeration will contain all registered services, and + /// the discovery will skip any discoverable service for which there's + /// a singleton with the same type or a compatible lazy factory + /// registered. + /// + public static IEnumerable DiscoverAll(this FlexPool pool, IDiscoveryEngine discoveryEngine, bool persistent = true) where T : notnull + { + return pool.ResolveAll().Concat(DiscoverAll(pool, discoveryEngine, typeof(T), persistent).Cast()); + } +} diff --git a/src/ServicePool/Extensions/SugarExtensions.cs b/src/ServicePool/Extensions/SugarExtensions.cs new file mode 100644 index 0000000..c6302b0 --- /dev/null +++ b/src/ServicePool/Extensions/SugarExtensions.cs @@ -0,0 +1,112 @@ +// DiscoverySupportExtensions.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +namespace TheXDS.ServicePool.Extensions; + +/// +/// Includes a set of extensions to extend the functionality of service pools +/// to include quick registration and/or consumption methods. +/// +public static class SugarExtensions +{ + /// + /// Registers a lazily-instantiated service if the specified condition + /// resolves to . + /// + /// Type of service to register. + /// Pool to register the service into. + /// + /// If this value evals to , the service will be + /// registered; otherwise no action is taken. + /// + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + public static void RegisterIf(this PoolBase pool, bool condition, bool persistent = true) where T : notnull + { + if (condition) pool.Register(persistent); + } + + /// + /// Registers a lazily-instantiated service if the specified condition + /// resolves to . + /// + /// Type of service to register. + /// Pool to register the service into. + /// + /// If this value evals to , the service will be + /// registered; otherwise no action is taken. + /// + /// Object factory to use. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + public static void RegisterIf(this PoolBase pool, bool condition, Func factory, bool persistent = true) where T : notnull + { + if (condition) pool.Register(factory, persistent); + } + + /// + /// Registers a singleton instance if the specified condition resolves to + /// . + /// + /// Type of service to register. + /// Pool to register the service into. + /// + /// If this value evals to , the service will be + /// registered; otherwise no action is taken. + /// + /// Object instance to register. + public static void RegisterNowIf(this PoolBase pool, bool condition, T singleton) where T : notnull + { + if (condition) pool.RegisterNow(singleton); + } + + /// + /// Registers a singleton instance if the specified condition resolves to + /// . + /// + /// Type of service to register. + /// Pool to register the service into. + /// + /// If this value evals to , the service will be + /// registered; otherwise no action is taken. + /// + public static void RegisterNowIf(this PoolBase pool, bool condition) where T : notnull + { + if (condition) pool.RegisterNow(); + } +} \ No newline at end of file diff --git a/src/ServicePool/FlexPool.cs b/src/ServicePool/FlexPool.cs index 6936040..83a0b47 100644 --- a/src/ServicePool/FlexPool.cs +++ b/src/ServicePool/FlexPool.cs @@ -30,7 +30,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using TheXDS.ServicePool.Extensions; namespace TheXDS.ServicePool; @@ -44,8 +43,8 @@ public class FlexPool : PoolBase { private record FlexFactoryEntry(in Type Type, in bool Persistent, in Func Factory) : FactoryEntry(Persistent, Factory); - private List _factories = []; - private List _instances = []; + private readonly List _factories = []; + private readonly List _instances = []; /// public override int Count => _instances.Count + _factories.Count; @@ -76,20 +75,7 @@ public override void InitNow() /// public override IEnumerator GetEnumerator() { - return _instances.Concat(_factories.Select(CreateFromLazy)).GetEnumerator(); - } - - /// - /// Instances and registers a new service of type - /// . - /// - /// Type of service to register. - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public void RegisterNow() where T : notnull - { - RegisterNow(() => CreateInstance(typeof(T))); + return _instances.ToArray().Concat(_factories.Select(CreateFromLazy).ToArray()).GetEnumerator(); } /// @@ -114,7 +100,7 @@ public bool Remove(object service) /// /// Removes a registered service from this service pool. /// - /// Type of service to remove. + /// Type of service to remove. /// /// if a registered service matching the /// requested type was found and removed from the pool, @@ -141,68 +127,6 @@ public IEnumerable ResolveAll() where T : notnull .Select(CreateFromLazy).Cast()); } - /// - /// Tries to resolve and register all services of type - /// found in the current app domain, returning - /// the resulting enumeration of all services found. - /// - /// Type of service to get. - /// - /// If set to , in case a service of the - /// specified type hasn't been registered and a compatible type has - /// been discovered, the newly created instance will be registered - /// persistently in the pool. If set to , any - /// discovered service will not be added to the pool. - /// - /// - /// A collection of all the services found in the current app domain, - /// or an empty enumeration in case that no discoverable service for - /// the requested type exists. - /// - /// - /// The resulting enumeration will contain all registered services, and - /// the discovery will skip any discoverable service for which there's - /// a singleton with the same type or a compatible lazy factory - /// registered. - /// - public IEnumerable DiscoverAll(bool persistent = true) where T : notnull - { - return DiscoverAll(new DefaultDiscoveryEngine(), persistent); - } - - /// - /// Tries to resolve and register all services of type - /// found using the specified - /// , returning the resulting enumeration - /// of all services found. - /// - /// Type of service to get. - /// - /// Discovery engine to use while searching for new instantiable types. - /// - /// - /// If set to , in case a service of the - /// specified type hasn't been registered and a compatible type has - /// been discovered, the newly created instance will be registered - /// persistently in the pool. If set to , any - /// discovered service will not be added to the pool. - /// - /// - /// A collection of all the services found in the current app domain, - /// or an empty enumeration in case that no discoverable service for - /// the requested type exists. - /// - /// - /// The resulting enumeration will contain all registered services, and - /// the discovery will skip any discoverable service for which there's - /// a singleton with the same type or a compatible lazy factory - /// registered. - /// - public IEnumerable DiscoverAll(IDiscoveryEngine discoveryEngine, bool persistent = true) where T : notnull - { - return ResolveAll().Concat(this.DiscoverAll(discoveryEngine, typeof(T), persistent).Cast()); - } - /// protected override object? ResolveActive(Type serviceType) { diff --git a/src/ServicePool/Pool.cs b/src/ServicePool/Pool.cs index e319169..05681ac 100644 --- a/src/ServicePool/Pool.cs +++ b/src/ServicePool/Pool.cs @@ -31,7 +31,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using TheXDS.ServicePool.Resources; namespace TheXDS.ServicePool; diff --git a/src/ServicePool/PoolBase.cs b/src/ServicePool/PoolBase.cs index 49c864f..582cd7c 100644 --- a/src/ServicePool/PoolBase.cs +++ b/src/ServicePool/PoolBase.cs @@ -1,4 +1,4 @@ -// FlexPoolBase.cs +// PoolBase.cs // // This file is part of ServicePool // @@ -44,8 +44,14 @@ public abstract class PoolBase : IEnumerable /// /// Represents a single lazily-initialized object factory item. /// - /// If set to , once the object instance is created, the new object will be persisted inside the pool and its factory removed. - /// + /// + /// If set to , once the object instance is created, + /// the new object will be persisted inside the pool and its factory + /// removed. + /// + /// + /// Factory to use when creating a new instance of the requested service. + /// protected record FactoryEntry(in bool Persistent, in Func Factory); /// @@ -92,21 +98,33 @@ protected record FactoryEntry(in bool Persistent, in Func Factory); /// , the registered service will be instantiated /// and initialized each time it is requested (it will be transient). /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// public void Register(bool persistent = true) where T : notnull => Register(typeof(T), persistent); + /// + /// Instances and registers a service of type + /// . + /// + /// Singleton instance to register. + /// Type of service to register. + public void RegisterNow(T singleton) where T : notnull => RegisterNow((object)singleton); + /// /// Instances and registers a new service of type /// . /// /// Type of service to register. - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// - public void RegisterNow(T singleton) where T : notnull => RegisterNow(singleton); + public void RegisterNow() where T : notnull => RegisterNow(CreateInstance(typeof(T))); + /// + /// Registers a lazily-instantiated service. + /// + /// Type of object to register. + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// public abstract void Register(Type objectType, bool persistent = true); /// @@ -120,20 +138,20 @@ protected record FactoryEntry(in bool Persistent, in Func Factory); /// , the registered service will be instantiated /// and initialized each time it is requested (it will be transient). /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// public abstract void Register(Func factory, bool persistent = true) where T : notnull; - + + /// + /// Registers an active sigleton instance as a service on this pool. + /// + /// + /// Active instance to register. + /// public abstract void RegisterNow(object singleton); /// /// Initializes all registered services marked as persistent using /// their respective registered factories. /// - /// - /// This same service pool instance, allowing the use of Fluent syntax. - /// public abstract void InitNow(); /// @@ -188,6 +206,15 @@ protected record FactoryEntry(in bool Persistent, in Func Factory); /// public abstract IEnumerator GetEnumerator(); + /// + /// Creates a new instance of the specified type, resolving its required + /// dependencies from the registered services on this instance. + /// + /// Type of object to instance. + /// + /// A new instance of the specified type, or if the + /// specified type cannot be instantiated given its required dependencies. + /// public object? CreateInstanceOrNull(Type t) { if (t.IsAbstract || t.IsInterface) return null; @@ -202,13 +229,41 @@ protected record FactoryEntry(in bool Persistent, in Func Factory); return null; } + /// + /// Creates a new instance of the specified type, resolving its required + /// dependencies from the registered services on this instance. + /// + /// Type of object to instance. + /// A new instance of the specified type. + /// + /// Thrown if the requested service could not be created given its required + /// dependencies. + /// public object CreateInstance(Type t) { return CreateInstanceOrNull(t) ?? throw Errors.CantInstantiate(); } + /// + /// Defines the resolution logic to be used when requesting a service that + /// should be initialized from a factory. + /// + /// Requested service type. + /// + /// The resolved object instance, or if the service + /// could not be lazily resolved. + /// protected abstract object? ResolveLazy(Type objectType); + /// + /// Defines the resolution logic to be used when requesting a service that + /// should be returned from a collection of active object instances. + /// + /// Requested service type. + /// + /// The resolved object instance, or if there is no + /// oject that could be mapped to the requested type. + /// protected abstract object? ResolveActive(Type objectType); private bool IsValidCtor(ConstructorInfo ctor, Type targetType, out object[]? args) diff --git a/src/ServicePool/ServicePool.csproj b/src/ServicePool/ServicePool.csproj index 7a264aa..7d321d3 100644 --- a/src/ServicePool/ServicePool.csproj +++ b/src/ServicePool/ServicePool.csproj @@ -1,8 +1,9 @@ - net8.0 + net8.0;net6.0 enable + 12 From 36d3bf0c14bd7264b0b0c8d61d7f24017fac21bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Morgan?= Date: Tue, 19 Dec 2023 15:28:17 -0600 Subject: [PATCH 3/5] V2 rewrite --- .../AssemblyListDiscoveryEngineTests.cs | 8 +- .../CommonExtensionsTests.cs | 24 +- .../DefaultDiscoveryEngineTests.cs | 47 ++++ src/ServicePool.Tests/FlexPoolTests.cs | 237 ++---------------- src/ServicePool.Tests/PoolBaseTests.cs | 235 +++++++++++++++++ src/ServicePool.Tests/PoolTests.cs | 80 ++++++ .../ServicePool.Tests.csproj | 2 +- .../TestTypes/AbstractTest.cs | 39 +++ src/ServicePool.Tests/TestTypes/ITest.cs | 41 +++ src/ServicePool.Tests/TestTypes/Test1.cs | 43 ++++ src/ServicePool.Tests/TestTypes/Test2.cs | 43 ++++ src/ServicePool.Tests/TestTypes/Test3.cs | 38 +++ src/ServicePool.Tests/TestTypes/Test4.cs | 48 ++++ .../TestTypes/UninstantiableTest.cs | 45 ++++ src/ServicePool/Extensions/Common.cs | 10 +- src/ServicePool/Pool.cs | 25 +- 16 files changed, 725 insertions(+), 240 deletions(-) create mode 100644 src/ServicePool.Tests/DefaultDiscoveryEngineTests.cs create mode 100644 src/ServicePool.Tests/PoolBaseTests.cs create mode 100644 src/ServicePool.Tests/PoolTests.cs create mode 100644 src/ServicePool.Tests/TestTypes/AbstractTest.cs create mode 100644 src/ServicePool.Tests/TestTypes/ITest.cs create mode 100644 src/ServicePool.Tests/TestTypes/Test1.cs create mode 100644 src/ServicePool.Tests/TestTypes/Test2.cs create mode 100644 src/ServicePool.Tests/TestTypes/Test3.cs create mode 100644 src/ServicePool.Tests/TestTypes/Test4.cs create mode 100644 src/ServicePool.Tests/TestTypes/UninstantiableTest.cs diff --git a/src/ServicePool.Tests/AssemblyListDiscoveryEngineTests.cs b/src/ServicePool.Tests/AssemblyListDiscoveryEngineTests.cs index fa6dba9..f255b73 100644 --- a/src/ServicePool.Tests/AssemblyListDiscoveryEngineTests.cs +++ b/src/ServicePool.Tests/AssemblyListDiscoveryEngineTests.cs @@ -44,7 +44,7 @@ public void Engine_supports_add() { a }; - Assert.Contains(a, e); + Assert.That(e, Contains.Item(a)); } [Test] @@ -53,7 +53,7 @@ public void Engine_supports_insert() var a = Assembly.GetExecutingAssembly(); var e = new AssemblyListDiscoveryEngine(); e.Insert(0, a); - Assert.Contains(a, e); + Assert.That(e, Contains.Item(a)); } [Test] @@ -63,8 +63,8 @@ public void Engine_supports_set() var b = typeof(object).Assembly; var e = new AssemblyListDiscoveryEngine { a }; e[0] = b; - Assert.False(e.Contains(a)); - Assert.Contains(b, e); + Assert.That(e, Does.Not.Contain(a)); + Assert.That(e, Contains.Item(b)); } [Test] diff --git a/src/ServicePool.Tests/CommonExtensionsTests.cs b/src/ServicePool.Tests/CommonExtensionsTests.cs index b85092a..f946134 100644 --- a/src/ServicePool.Tests/CommonExtensionsTests.cs +++ b/src/ServicePool.Tests/CommonExtensionsTests.cs @@ -34,32 +34,36 @@ namespace TheXDS.ServicePool.Tests; -public class CommonExtensionsTests +public class PoolExtensionsTests : CommonExtensionsTests { } + +public class FlexPoolExtensionsTests : CommonExtensionsTests { } + +public abstract class CommonExtensionsTests where T : PoolBase, new() { [Test] public void RegisterInto_registers_singleton() { - var pool = new FlexPool(); + var pool = new T(); var x = new Random().RegisterInto(pool); - Assert.IsInstanceOf(x); - Assert.AreSame(x, pool.Resolve()); + Assert.That(x, Is.InstanceOf()); + Assert.That(pool.Resolve(), Is.SameAs(x)); } [Test] public void RegisterIntoIf_registers_if_true() { - var pool = new FlexPool(); + var pool = new Pool(); var x = new Random().RegisterIntoIf(pool, true); - Assert.IsInstanceOf(x); - Assert.AreSame(x, pool.Resolve()); + Assert.That(x, Is.InstanceOf()); + Assert.That(pool.Resolve(), Is.SameAs(x)); } [Test] - public void RegisterIntoIf_returns_null_if_false() + public void RegisterIntoIf_returns_object_if_false() { var pool = new FlexPool(); var x = new Random().RegisterIntoIf(pool, false); - Assert.IsNull(x); - Assert.IsNull(pool.Resolve()); + Assert.That(x, Is.InstanceOf()); + Assert.That(pool.Resolve(), Is.Null); } } diff --git a/src/ServicePool.Tests/DefaultDiscoveryEngineTests.cs b/src/ServicePool.Tests/DefaultDiscoveryEngineTests.cs new file mode 100644 index 0000000..abad2fc --- /dev/null +++ b/src/ServicePool.Tests/DefaultDiscoveryEngineTests.cs @@ -0,0 +1,47 @@ +// AssemblyListDiscoveryEngineTests.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2023 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using NUnit.Framework; +using System; +using System.Collections; +using System.Linq; + +namespace TheXDS.ServicePool.Tests; + +public class DefaultDiscoveryEngineTests +{ + [Test] + public void Engine_resolves_base_type() + { + var engine = new DefaultDiscoveryEngine(); + var types = engine.Discover(typeof(Exception)).ToArray(); + Assert.That(types.Length, Is.GreaterThan(50)); + } +} diff --git a/src/ServicePool.Tests/FlexPoolTests.cs b/src/ServicePool.Tests/FlexPoolTests.cs index a3d3a6f..829953c 100644 --- a/src/ServicePool.Tests/FlexPoolTests.cs +++ b/src/ServicePool.Tests/FlexPoolTests.cs @@ -27,85 +27,25 @@ // SOFTWARE. #pragma warning disable CS1591 -#pragma warning disable IDE0290 using NUnit.Framework; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; using TheXDS.ServicePool.Extensions; +using TheXDS.ServicePool.TestTypes; namespace TheXDS.ServicePool.Tests; -public class FlexPoolTests +public class FlexPoolTests : PoolBaseTests { - [Test] - public void Count_gets_total_service_count() - { - FlexPool? pool = new(); - Assert.Zero(pool.Count); - - pool.RegisterNow(); - Assert.AreEqual(1, pool.Count); - - pool.RegisterNow(); - Assert.AreEqual(2, pool.Count); - - pool.Register(); - Assert.AreEqual(3, pool.Count); - } - - [Test] - public void Pool_registers_and_resolves_cached_services() - { - FlexPool? pool = new(); - pool.RegisterNow(); - Assert.IsInstanceOf(pool.Resolve()); - } - - [Test] - public void Pool_resolves_persistent_services() - { - FlexPool? pool = new(); - pool.Register(true); - Random? r1 = pool.Resolve(); - Random? r2 = pool.Resolve(); - - Assert.IsInstanceOf(r1); - Assert.IsInstanceOf(r2); - Assert.AreSame(r1, r2); - } - - [Test] - public void Pool_resolves_non_persistent_services() - { - FlexPool? pool = new(); - pool.Register(false); - Random? r1 = pool.Resolve(); - Random? r2 = pool.Resolve(); - - Assert.IsInstanceOf(r1); - Assert.IsInstanceOf(r2); - Assert.AreNotSame(r1, r2); - } - - [Test] - public void Resolve_returns_null_if_not_registered() - { - FlexPool? pool = new(); - Assert.IsNull(pool.Resolve()); - } - [Test] public void Resolve_resolves_for_interfaces() { FlexPool? pool = new(); pool.Register(); - Assert.IsNotNull(pool.Resolve()); + Assert.That(pool.Resolve(), Is.Not.Null); } [Test] @@ -113,15 +53,15 @@ public void Resolve_resolves_for_base_class() { FlexPool? pool = new(); pool.Register(); - Assert.IsNotNull(pool.Resolve()); + Assert.That(pool.Resolve(), Is.Not.Null); } [Test] public void Discover_searches_for_service() { FlexPool? pool = new(); - Assert.IsInstanceOf(pool.Discover()); - Assert.AreEqual(1, pool.Count); + Assert.That(pool.Discover(), Is.InstanceOf()); + Assert.That(pool.Count, Is.EqualTo(1)); } [Test] @@ -130,7 +70,7 @@ public void Discover_returns_from_active_services() FlexPool? pool = new(); pool.Register(); var r = pool.Resolve(); - Assert.AreSame(r, pool.Discover()); + Assert.That(pool.Discover(), Is.SameAs(r)); } [Test] @@ -140,14 +80,14 @@ public void ResolveAll_returns_collection() pool.RegisterNow(new List()); pool.RegisterNow(new Collection()); pool.RegisterNow(Array.Empty()); - Assert.AreEqual(3, pool.ResolveAll>().Count()); + Assert.That(pool.ResolveAll>().Count(), Is.EqualTo(3)); } [Test] public void DiscoverAll_enumerates_all_types_that_implement_base_type() { FlexPool pool = new(); - Assert.AreEqual(3, pool.DiscoverAll().ToArray().Length); + Assert.That(pool.DiscoverAll().ToArray().Length, Is.EqualTo(3)); } [Test] @@ -157,164 +97,25 @@ public void DiscoverAll_skips_existing_services() pool.RegisterNow(); var t1 = pool.Resolve(); ITest[] c = pool.DiscoverAll().ToArray(); - Assert.AreEqual(3, c.Length); - Assert.AreSame(t1, c[0]); - Assert.IsInstanceOf(c[1]); - Assert.IsInstanceOf(c[2]); - } - - [Test] - public void Pool_supports_removal() - { - FlexPool pool = new(); - pool.RegisterNow(); - pool.Register(); - Assert.IsTrue(pool.Remove()); - Assert.AreEqual(1, pool.Count); - Assert.IsFalse(pool.Remove()); - Assert.IsTrue(pool.Remove()); - Assert.Zero(pool.Count); - Assert.IsFalse(pool.Remove()); + Assert.That(c.Length, Is.EqualTo(3)); + Assert.That(c[0], Is.SameAs(t1)); + Assert.That(c[1], Is.InstanceOf()); + Assert.That(c[2], Is.InstanceOf()); } [Test] - public void Consume_removes_service() + public void DiscoverAll_allows_specifying_engine() { FlexPool pool = new(); - pool.RegisterNow(); - Assert.IsInstanceOf(pool.Consume()); - Assert.Zero(pool.Count); - Assert.Null(pool.Consume()); - pool.Register(); - Assert.IsInstanceOf(pool.Consume()); - Assert.Zero(pool.Count); - Assert.Null(pool.Consume()); + ITest[] c = pool.DiscoverAll(new DefaultDiscoveryEngine()).ToArray(); + Assert.That(c.Length, Is.EqualTo(3)); } [Test] - public void Enumerator_includes_lazy_and_eager_items() + public void Discover_allows_specifying_engine() { FlexPool pool = new(); - pool.RegisterNow(); - pool.Register(); - - IEnumerator e = pool.GetEnumerator(); - Assert.IsInstanceOf(e); - int c = 0; - while (e.MoveNext()) - { - Assert.IsInstanceOf(e.Current); - c++; - } - Assert.AreEqual(2, c); - } - - [Test] - public void ServicePool_throws_error_on_invalid_registrations() - { - FlexPool pool = new(); - Assert.Catch(() => pool.RegisterNow()); - Assert.Catch(() => pool.RegisterNow()); - } - - [Test] - public void ServicePool_errors_have_messages() - { - FlexPool pool = new(); - var ex = Assert.Catch(() => pool.RegisterNow()); - Assert.IsNotNull(ex); - Assert.IsNotEmpty(ex!.Message); - } - - [TestCase("en_US")] - [TestCase("es_MX")] - public void ServicePool_strings_have_localized_messages(string locale) - { - Resources.Strings.Errors.Culture = new(locale); - foreach (var prop in typeof(Resources.Strings.Errors) - .GetProperties(BindingFlags.Static | BindingFlags.Public) - .Where(p => p.PropertyType == typeof(string)) - .Select(p => p.GetValue(null)).Cast()) - { - Assert.IsFalse(string.IsNullOrWhiteSpace(prop)); - } - } - - [Test] - public void ServicePool_throws_error_on_uninstantiable_class() - { - FlexPool pool = new(); - pool.Register(() => 3); - Assert.Catch(() => pool.RegisterNow()); - } - - [Test] - public void ServicePool_injects_dependencies() - { - FlexPool pool = new(); - pool.Register(); - pool.Register(); - pool.Register(); - - var t4 = pool.Resolve()!; - Assert.IsInstanceOf(t4); - Assert.IsInstanceOf(t4.Random); - Assert.IsInstanceOf(t4.Test1); - } - - private interface ITest - { - [ExcludeFromCodeCoverage] - void Test(); - } - - [ExcludeFromCodeCoverage] - private abstract class AbstractTest : ITest - { - public abstract void Test(); - } - - [ExcludeFromCodeCoverage] - private class UninstantiableTest - { - public UninstantiableTest(int x, Exception y, Guid z, IEnumerator a) - { - } - } - - [ExcludeFromCodeCoverage] - private class Test1 : ITest - { - public void Test() - { - Assert.Pass(); - } - } - - [ExcludeFromCodeCoverage] - private class Test2 : ITest - { - void ITest.Test() - { - Assert.Pass(); - } - } - - [ExcludeFromCodeCoverage] - private class Test3 : Test1 - { - } - - [ExcludeFromCodeCoverage] - private class Test4 - { - public Test4(Random random, Test1 test1) - { - Random = random; - Test1 = test1; - } - - public Random Random { get; } - public Test1 Test1 { get; } + var c = pool.Discover(new DefaultDiscoveryEngine()); + Assert.That(c, Is.Not.Null); } } diff --git a/src/ServicePool.Tests/PoolBaseTests.cs b/src/ServicePool.Tests/PoolBaseTests.cs new file mode 100644 index 0000000..13f56bc --- /dev/null +++ b/src/ServicePool.Tests/PoolBaseTests.cs @@ -0,0 +1,235 @@ +// TTests.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 +#pragma warning disable IDE0290 +#pragma warning disable IDE0060 + +using NUnit.Framework; +using System; +using System.Collections; +using System.Linq; +using System.Reflection; +using TheXDS.ServicePool.TestTypes; + +namespace TheXDS.ServicePool.Tests; + +public abstract class PoolBaseTests where T : PoolBase, new() +{ + [Test] + public void Count_gets_total_service_count() + { + T pool = new(); + Assert.That(pool.Count, Is.Zero); + + pool.RegisterNow(); + Assert.That(pool.Count, Is.EqualTo(1)); + + pool.RegisterNow(); + Assert.That(pool.Count, Is.EqualTo(2)); + + pool.Register(); + Assert.That(pool.Count, Is.EqualTo(3)); + } + + [Test] + public void Pool_registers_and_resolves_cached_services() + { + T pool = new(); + pool.RegisterNow(); + Assert.That(pool.Resolve(), Is.InstanceOf()); + } + + [Test] + public void Pool_resolves_persistent_services() + { + T pool = new(); + pool.Register(true); + Random? r1 = pool.Resolve(); + Random? r2 = pool.Resolve(); + + Assert.That(r1, Is.InstanceOf()); + Assert.That(r2, Is.InstanceOf()); + Assert.That(r1, Is.SameAs(r2)); + } + + [Test] + public void InitNow_initializes_services() + { + T pool = new(); + pool.Register(true); + pool.InitNow(); + Assert.That(pool.Count, Is.Not.Zero); + } + + [Test] + public void Pool_resolves_non_persistent_services() + { + T pool = new(); + pool.Register(false); + Random? r1 = pool.Resolve(); + Random? r2 = pool.Resolve(); + + Assert.That(r1, Is.InstanceOf()); + Assert.That(r2, Is.InstanceOf()); + Assert.That(r1, Is.Not.SameAs(r2)); + } + + [Test] + public void Resolve_returns_null_if_not_registered() + { + T pool = new(); + Assert.That(pool.Resolve(), Is.Null); + } + + [Test] + public void Pool_supports_removal_of_lazy_services() + { + T pool = new(); + pool.Register(); + + Assert.That(pool.Remove(), Is.True); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void Pool_supports_removal_of_eager_services() + { + T pool = new(); + pool.RegisterNow(); + + Assert.That(pool.Remove(), Is.True); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void Remove_returns_false_if_service_is_not_registered() + { + T pool = new(); + Assert.That(pool.Remove(), Is.False); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void Consume_removes_service() + { + T pool = new(); + pool.RegisterNow(); + Assert.That(pool.Consume(), Is.InstanceOf()); + Assert.That(pool.Count, Is.Zero); + Assert.That(pool.Consume(), Is.Null); + } + + [Test] + public void Consume_returns_null_if_no_service_is_available() + { + T pool = new(); + Assert.That(pool.Consume(), Is.Null); + } + + [Test] + public void Consume_returns_null_if_service_was_consumed() + { + T pool = new(); + pool.RegisterNow(); + pool.Consume(); + + Assert.That(pool.Consume(), Is.Null); + } + + [Test] + public void Enumerator_includes_lazy_and_eager_items() + { + T pool = new(); + pool.RegisterNow(); + pool.Register(); + + IEnumerator e = pool.GetEnumerator(); + Assert.That(e, Is.InstanceOf()); + int c = 0; + while (e.MoveNext()) + { + Assert.That(e.Current, Is.InstanceOf()); + c++; + } + Assert.That(c, Is.EqualTo(2)); + } + + [Test] + public void ServicePool_throws_error_on_invalid_registrations() + { + T pool = new(); + Assert.Catch(pool.RegisterNow); + Assert.Catch(pool.RegisterNow); + } + + [Test] + public void ServicePool_errors_have_messages() + { + T pool = new(); + var ex = Assert.Catch(pool.RegisterNow); + Assert.That(ex, Is.Not.Null); + Assert.That(ex!.Message, Is.Not.Empty); + } + + [TestCase("en_US")] + [TestCase("es_MX")] + public void ServicePool_strings_have_localized_messages(string locale) + { + Resources.Strings.Errors.Culture = new(locale); + foreach (var prop in typeof(Resources.Strings.Errors) + .GetProperties(BindingFlags.Static | BindingFlags.Public) + .Where(p => p.PropertyType == typeof(string)) + .Select(p => p.GetValue(null)).Cast()) + { + Assert.That(string.IsNullOrWhiteSpace(prop), Is.False); + } + } + + [Test] + public void ServicePool_throws_error_on_uninstantiable_class() + { + T pool = new(); + pool.Register(() => 3); + Assert.Catch(pool.RegisterNow); + } + + [Test] + public void ServicePool_injects_dependencies() + { + T pool = new(); + pool.Register(); + pool.Register(); + pool.Register(); + + var t4 = pool.Resolve()!; + Assert.That(t4, Is.InstanceOf()); + Assert.That(t4.Random, Is.InstanceOf()); + Assert.That(t4.Test1, Is.InstanceOf()); + } +} diff --git a/src/ServicePool.Tests/PoolTests.cs b/src/ServicePool.Tests/PoolTests.cs new file mode 100644 index 0000000..5c85224 --- /dev/null +++ b/src/ServicePool.Tests/PoolTests.cs @@ -0,0 +1,80 @@ +// FlexPoolTests.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using NUnit.Framework; +using System; +using TheXDS.ServicePool.TestTypes; + +namespace TheXDS.ServicePool.Tests; + +public class PoolTests : PoolBaseTests +{ + [Test] + public void Resolve_resolves_for_interfaces() + { + Pool? pool = new(); + pool.Register(); + Assert.That(pool.Resolve(), Is.Not.Null); + } + + [Test] + public void Resolve_resolves_for_base_class() + { + Pool? pool = new(); + pool.Register(); + Assert.That(pool.Resolve(), Is.Not.Null); + } + + [TestCase(typeof(ITest))] + [TestCase(typeof(Test1))] + [TestCase(typeof(Test3))] + public void Resolve_resolves_for_various_types(Type resolvableType) + { + Pool? pool = new(); + pool.Register([typeof(ITest), typeof(Test1), typeof(Test3)]); + Assert.That(pool.Resolve(resolvableType), Is.Not.Null); + } + + [Test] + public void Resolve_returns_null_if_not_explicitly_registered_for_interface() + { + Pool? pool = new(); + pool.Register(); + Assert.That(pool.Resolve(), Is.Null); + } + + [Test] + public void Resolve_returns_null_if_not_explicitly_registered_for_own_type() + { + Pool? pool = new(); + pool.Register(); + Assert.That(pool.Resolve(), Is.Null); + } +} diff --git a/src/ServicePool.Tests/ServicePool.Tests.csproj b/src/ServicePool.Tests/ServicePool.Tests.csproj index f463229..8cd3ecd 100644 --- a/src/ServicePool.Tests/ServicePool.Tests.csproj +++ b/src/ServicePool.Tests/ServicePool.Tests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/src/ServicePool.Tests/TestTypes/AbstractTest.cs b/src/ServicePool.Tests/TestTypes/AbstractTest.cs new file mode 100644 index 0000000..c2790ca --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/AbstractTest.cs @@ -0,0 +1,39 @@ +// AbstractTest.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +[ExcludeFromCodeCoverage] +public abstract class AbstractTest : ITest +{ + public abstract void Test(); +} diff --git a/src/ServicePool.Tests/TestTypes/ITest.cs b/src/ServicePool.Tests/TestTypes/ITest.cs new file mode 100644 index 0000000..88e382b --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/ITest.cs @@ -0,0 +1,41 @@ +// ITest.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 +#pragma warning disable IDE0290 +#pragma warning disable IDE0060 + +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +public interface ITest +{ + [ExcludeFromCodeCoverage] + void Test(); +} diff --git a/src/ServicePool.Tests/TestTypes/Test1.cs b/src/ServicePool.Tests/TestTypes/Test1.cs new file mode 100644 index 0000000..a01d99e --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/Test1.cs @@ -0,0 +1,43 @@ +// Test1.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using NUnit.Framework; +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +[ExcludeFromCodeCoverage] +public class Test1 : ITest +{ + public void Test() + { + Assert.Pass(); + } +} diff --git a/src/ServicePool.Tests/TestTypes/Test2.cs b/src/ServicePool.Tests/TestTypes/Test2.cs new file mode 100644 index 0000000..3ba21ed --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/Test2.cs @@ -0,0 +1,43 @@ +// Test2.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using NUnit.Framework; +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +[ExcludeFromCodeCoverage] +public class Test2 : ITest +{ + void ITest.Test() + { + Assert.Pass(); + } +} diff --git a/src/ServicePool.Tests/TestTypes/Test3.cs b/src/ServicePool.Tests/TestTypes/Test3.cs new file mode 100644 index 0000000..fd84add --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/Test3.cs @@ -0,0 +1,38 @@ +// Test3.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +[ExcludeFromCodeCoverage] +public class Test3 : Test1 +{ +} diff --git a/src/ServicePool.Tests/TestTypes/Test4.cs b/src/ServicePool.Tests/TestTypes/Test4.cs new file mode 100644 index 0000000..88acf31 --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/Test4.cs @@ -0,0 +1,48 @@ +// Test4.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 +#pragma warning disable IDE0290 + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +[ExcludeFromCodeCoverage] +public class Test4 +{ + public Test4(Random random, Test1 test1) + { + Random = random; + Test1 = test1; + } + + public Random Random { get; } + public Test1 Test1 { get; } +} diff --git a/src/ServicePool.Tests/TestTypes/UninstantiableTest.cs b/src/ServicePool.Tests/TestTypes/UninstantiableTest.cs new file mode 100644 index 0000000..9be86c0 --- /dev/null +++ b/src/ServicePool.Tests/TestTypes/UninstantiableTest.cs @@ -0,0 +1,45 @@ +// UninstantiableTest.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 +#pragma warning disable IDE0290 +#pragma warning disable IDE0060 + +using System; +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace TheXDS.ServicePool.TestTypes; + +[ExcludeFromCodeCoverage] +public class UninstantiableTest +{ + public UninstantiableTest(int x, Exception y, Guid z, IEnumerator a) + { + } +} diff --git a/src/ServicePool/Extensions/Common.cs b/src/ServicePool/Extensions/Common.cs index 4c7b686..0675136 100644 --- a/src/ServicePool/Extensions/Common.cs +++ b/src/ServicePool/Extensions/Common.cs @@ -42,7 +42,7 @@ public static class Common /// Instance to register. /// Pool to register the singleton into. /// The same instance as . - public static T RegisterInto(this T obj, FlexPool pool) where T : notnull + public static T RegisterInto(this T obj, PoolBase pool) where T : notnull { pool.RegisterNow(obj); return obj; @@ -60,11 +60,9 @@ public static T RegisterInto(this T obj, FlexPool pool) where T : notnull /// Value that determines if the singleton should be registered or not. /// /// - /// The same instance as if - /// is equal to , - /// otherwise. - public static T? RegisterIntoIf(this T obj, FlexPool pool, bool condition) where T : notnull + /// The same instance as . + public static T RegisterIntoIf(this T obj, PoolBase pool, bool condition) where T : notnull { - return condition ? obj.RegisterInto(pool) : default; + return condition ? obj.RegisterInto(pool) : obj; } } diff --git a/src/ServicePool/Pool.cs b/src/ServicePool/Pool.cs index 05681ac..1d9314a 100644 --- a/src/ServicePool/Pool.cs +++ b/src/ServicePool/Pool.cs @@ -81,8 +81,8 @@ public override void InitNow() var entries = Factories.Where(p => p.Value.Persistent).ToArray(); foreach (var entry in entries) { - RegisterNow(entry.Value.Factory()); Factories.Remove(entry.Key); + RegisterNow(entry.Value.Factory()); } } @@ -159,6 +159,29 @@ public void Register(Type objectType, Type[] typeRegistrations, bool persistent { Register(() => CreateInstance(objectType), typeRegistrations, persistent); } + + /// + /// Registers a lazily-instantiated service. + /// + /// Type of object to register. + /// + /// Type of the object to be instantiated upon service type request. + /// + /// + /// If set to , the resolved object is going to be + /// persisted in the service pool (it will be a Singleton). When + /// , the registered service will be instantiated + /// and initialized each time it is requested (it will be transient). + /// + /// + /// This same service pool instance, allowing the use of Fluent syntax. + /// + public void Register(bool persistent = true) + where TReg : notnull + where TService : notnull + { + Register([typeof(TReg)], persistent); + } /// /// Registers a lazily-instantiated service. From 381e5427ed8402bf5a23562ec02e9bffbd28b1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Morgan?= Date: Tue, 19 Dec 2023 15:34:57 -0600 Subject: [PATCH 4/5] Cleanup --- .../CommonExtensionsTests.cs | 4 --- .../FlexPoolExtensionsTests.cs | 33 +++++++++++++++++++ src/ServicePool.Tests/PoolExtensionsTests.cs | 33 +++++++++++++++++++ .../Extensions/DiscoverySupportExtensions.cs | 1 - 4 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/ServicePool.Tests/FlexPoolExtensionsTests.cs create mode 100644 src/ServicePool.Tests/PoolExtensionsTests.cs diff --git a/src/ServicePool.Tests/CommonExtensionsTests.cs b/src/ServicePool.Tests/CommonExtensionsTests.cs index f946134..974a640 100644 --- a/src/ServicePool.Tests/CommonExtensionsTests.cs +++ b/src/ServicePool.Tests/CommonExtensionsTests.cs @@ -34,10 +34,6 @@ namespace TheXDS.ServicePool.Tests; -public class PoolExtensionsTests : CommonExtensionsTests { } - -public class FlexPoolExtensionsTests : CommonExtensionsTests { } - public abstract class CommonExtensionsTests where T : PoolBase, new() { [Test] diff --git a/src/ServicePool.Tests/FlexPoolExtensionsTests.cs b/src/ServicePool.Tests/FlexPoolExtensionsTests.cs new file mode 100644 index 0000000..e23811e --- /dev/null +++ b/src/ServicePool.Tests/FlexPoolExtensionsTests.cs @@ -0,0 +1,33 @@ +// FlexPoolExtensionsTests.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +namespace TheXDS.ServicePool.Tests; + +public class FlexPoolExtensionsTests : CommonExtensionsTests { } diff --git a/src/ServicePool.Tests/PoolExtensionsTests.cs b/src/ServicePool.Tests/PoolExtensionsTests.cs new file mode 100644 index 0000000..585fa8d --- /dev/null +++ b/src/ServicePool.Tests/PoolExtensionsTests.cs @@ -0,0 +1,33 @@ +// PoolExtensionsTests.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +namespace TheXDS.ServicePool.Tests; + +public class PoolExtensionsTests : CommonExtensionsTests { } diff --git a/src/ServicePool/Extensions/DiscoverySupportExtensions.cs b/src/ServicePool/Extensions/DiscoverySupportExtensions.cs index 4ced7dd..83e0d25 100644 --- a/src/ServicePool/Extensions/DiscoverySupportExtensions.cs +++ b/src/ServicePool/Extensions/DiscoverySupportExtensions.cs @@ -224,7 +224,6 @@ public static IEnumerable DiscoverAll(this FlexPool pool, bool persistent return DiscoverAll(pool, new DefaultDiscoveryEngine(), persistent); } - /// /// Tries to resolve and register all services of type /// found using the specified From 97f900c77fea36387f06f7a87a1fafe37daed94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Morgan?= Date: Tue, 19 Dec 2023 15:47:55 -0600 Subject: [PATCH 5/5] Added missing tests --- src/ServicePool.Tests/SugarExtensionsTests.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/ServicePool.Tests/SugarExtensionsTests.cs diff --git a/src/ServicePool.Tests/SugarExtensionsTests.cs b/src/ServicePool.Tests/SugarExtensionsTests.cs new file mode 100644 index 0000000..7dfce60 --- /dev/null +++ b/src/ServicePool.Tests/SugarExtensionsTests.cs @@ -0,0 +1,109 @@ +// SugarExtensionsTests.cs +// +// This file is part of ServicePool +// +// Author(s): +// César Andrés Morgan +// +// Released under the MIT License (MIT) +// Copyright © 2011 - 2022 César Andrés Morgan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the “Software”), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma warning disable CS1591 + +using NUnit.Framework; +using System; +using System.Diagnostics.CodeAnalysis; +using TheXDS.ServicePool.Extensions; + +namespace TheXDS.ServicePool.Tests; + +public class SugarExtensionsTests +{ + [Test] + public void RegisterIf_T_skips_if_false() + { + FlexPool? pool = new(); + pool.RegisterIf(false); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void RegisterIf_T_registers_if_true() + { + FlexPool? pool = new(); + pool.RegisterIf(true); + Assert.That(pool.Count, Is.EqualTo(1)); + } + + [Test] + public void RegisterIf_with_factory_skips_if_false() + { + FlexPool? pool = new(); + pool.RegisterIf(false, DummyFactory); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void RegisterIf_with_factory_registers_if_true() + { + FlexPool? pool = new(); + pool.RegisterIf(true, DummyFactory); + Assert.That(pool.Count, Is.EqualTo(1)); + } + + [Test] + public void RegisterNowIf_T_skips_if_false() + { + FlexPool? pool = new(); + pool.RegisterNowIf(false); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void RegisterNowIf_T_registers_if_true() + { + FlexPool? pool = new(); + pool.RegisterNowIf(true); + Assert.That(pool.Count, Is.EqualTo(1)); + } + + [Test] + public void RegisterNowIf_with_singleton_skips_if_false() + { + FlexPool? pool = new(); + pool.RegisterNowIf(false, new Exception()); + Assert.That(pool.Count, Is.Zero); + } + + [Test] + public void RegisterNowIf_with_singleton_registers_if_true() + { + FlexPool? pool = new(); + pool.RegisterNowIf(true, new Exception()); + Assert.That(pool.Count, Is.EqualTo(1)); + } + + [ExcludeFromCodeCoverage] + private static object DummyFactory() + { + return new object(); + } +}