diff --git a/README.md b/README.md index 5797025..b5e0642 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,11 @@ And if we had to do the same for every new functionality that is coming, that wo The main goals that have been accomplished are: -- Automatic generation of test cases using all proper variations of registered `clean utilities` -- Every `clean utility` can define external demands that represent conditions about what other `clean utilities` should be present within a variation in order to generate a test case with it +- Automatic generation of test cases using all proper combinations of registered `clean utilities` +- Every `clean utility` can define external demands that represent conditions about what other `clean utilities` should be present within a combination in order to generate a test case with it - Every `clean utility` can depend internally on other `clean utilities` - Every `clean utility` can define internal demands that represent conditions about what `clean utilities` should be injected upon initialization +- Every `clean utility` can define outer demands that represent conditions oriented towards the superior utilities - Global and local `clean utilities` - local `clean utilities` are instantiated for every test case; global `clean utilities` are instantiated only once and can be used to share common context between similar test cases - Parallel execution of tests cases @@ -125,7 +126,9 @@ _Enabling test metadata presentation methods often has performance impact over t ## What are the `clean utilities`? The `clean utility` is a key component for our library. Every `clean utility` has a `category` and a `name` that are required. -One test may require many `clean utilities` and whenever there are two or more utilities from the same category that can be used for its execution, then a test case will be generated for each possible variation of utilities. + +> One test may require utilities from many categories. +> The corresponding test cases will be generated using **unique** combination of utilities from the required categories. Every `clean utility` can me marked as `local` or `global`. `Local clean utilities` will be instantiated at least once for every test case requiring their participation. @@ -165,11 +168,18 @@ If this is not possible, the test assembly should be explicitly decorated with a ```C# [assembly: TryAtSoftware.CleanTests.Core.Attributes.SharesUtilitiesWith("Assembly.With.Shared.CleanUtilities")] ``` +### Dependencies + +Every `clean utility` can depend on other `clean utiliites`. +This relationship can be modelled throughout the `WithRequirements` attribute. + +When generating test cases, each unique instantiation procedure (i.e. the resolution of dependencies) for a given `clean utility` will be presumed as a separate member of the combinatorial set. +For example, if the dependencies of a given utility can be resolved in **N** different ways, the generation process will use all **N** different instantiation procedures as if they were different utilities. ### External demands -Every `clean utility` can define `external demands` throughout the `ExternalDemands` attribute. With its help for each `category` a set of demanded `characteristics` can be defined. -These demanded characteristics will alter the way variations of `clean utilities` are generated - all external demands should be satisfied for all utilities participating in the variation. +Every `clean utility` can define `external demands` throughout the `ExternalDemands` attribute. +These demanded characteristics will alter the way combinations of `clean utilities` are generated - all external demands should be satisfied for all utilities participating in the combination. Example: ```C# @@ -181,12 +191,10 @@ public class ConsoleReader : IReader } ``` -### Internal requirements and demands - -Every `clean utility` can depend on other `clean utiliites`. This relationship can be modelled throughout the `WithRequirements` attribute. -When such `clean utility` participates in a variation, that same variation will be reused as many times as the number of possible instantiation procedures there are (according to the registered `clean utilities` of the required categories). +### Internal demands -Moreover, internal `demands` can be applied (throughout the `InternalDemands` attribute) to filter out the dependent `clean utilities` according to a predefined set of characteristics. +Every `clean utility` can define `internal demands` throughout the `InternalDemands` attribute. +This type of demanded characteristics can be used to filter out the dependent `clean utilities`. Example: ```C# @@ -207,6 +215,12 @@ public class Engine : IEngine /* further implementation of the `IEngine` interface... */ } ``` + +### Outer demands + +Every `clean utility` can define `outer demands` throughout the `OuterDemands` attribute. +This type of demanded characteristics can be used to model conditions oriented towards utilities in the outer scope (also known as _superior_ utilities). + ## How to use clean tests? This library is built atop [XUnit](https://xunit.net/) so if you are familiar with the way this framework operates, you are most likely ready to use `clean tests`. diff --git a/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj b/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj index 72487f5..a34d933 100644 --- a/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj +++ b/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj @@ -14,8 +14,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj b/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj index 6887d1d..3ab482d 100644 --- a/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj +++ b/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj @@ -12,7 +12,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj b/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj index 8f6f023..7760e34 100644 --- a/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj +++ b/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj @@ -11,7 +11,7 @@ - + diff --git a/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj b/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj index 39060f1..7d0b9db 100644 --- a/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj +++ b/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj @@ -6,7 +6,7 @@ - + diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs new file mode 100644 index 0000000..8073fc9 --- /dev/null +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs @@ -0,0 +1,198 @@ +namespace TryAtSoftware.CleanTests.UnitTests; + +using System.Text.Json; +using TryAtSoftware.CleanTests.Core.Construction; +using TryAtSoftware.CleanTests.UnitTests.Constants; +using TryAtSoftware.CleanTests.UnitTests.Extensions; +using TryAtSoftware.CleanTests.UnitTests.Parametrization; +using TryAtSoftware.Randomizer.Core.Helpers; + +public class ConstructionManagerTests +{ + [Theory(Timeout = UnitTestConstants.Timeout)] + [MemberData(nameof(GetDependenciesManagerSetups))] + public void DependencyGraphsShouldBeConstructedSuccessfully(EnvironmentSetup setup, string pathToExpectedResult) + { + var assemblyTestData = setup.MaterializeAsAssemblyData(); + var manager = new ConstructionManager(assemblyTestData); + + var allUtilities = assemblyTestData.CleanUtilities.GetAllValues().ToArray(); + var expectedOutput = File.ReadLines(pathToExpectedResult).ToArray(); + Assert.Equal(allUtilities.Length, expectedOutput.Length); + + for (var i = 0; i < allUtilities.Length; i++) + { + var constructionPaths = manager.BuildIndividualConstructionGraphs(new[] { allUtilities[i].Id }); + var output = JsonSerializer.Serialize(constructionPaths); + Assert.Equal(expectedOutput[i], output); + } + } + + [Fact] + public void OuterDemandsShouldBeAppliedCorrectlyAtRootLevel() + { + var utilitiesCount = RandomizationHelper.RandomInteger(3, 10); + var setup = new EnvironmentSetup("outer_demands_env"); + setup.WithCategory("FL1", 1).WithCategory("FL2", utilitiesCount).WithCategory("SL1", utilitiesCount); + setup.WithRequirements("FL1", 1, "SL1"); + + for (var i = 1; i <= utilitiesCount; i++) + { + var characteristic = $"C-{i}"; + setup.WithCharacteristics("FL2", i, characteristic); + setup.WithOuterDemands("SL1", i, "FL2", characteristic); + } + + var assemblyTestData = setup.MaterializeAsAssemblyData(); + var manager = new ConstructionManager(assemblyTestData); + + var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray(); + Assert.Single(fl1Utilities); + + var fl2Utilities = assemblyTestData.CleanUtilities.Get("FL2").ToArray(); + Assert.Equal(utilitiesCount, fl2Utilities.Length); + + var sl1Utilities = assemblyTestData.CleanUtilities.Get("SL1").ToArray(); + Assert.Equal(utilitiesCount, sl1Utilities.Length); + + for (var i = 0; i < utilitiesCount; i++) + { + var utilityIds = new[] { fl1Utilities[0].Id, fl2Utilities[i].Id }; + var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds); + + Assert.Single(constructionGraphs); + var fl1ConstructionGraph = constructionGraphs[0][0]; + + Assert.NotNull(fl1Utilities); + Assert.Equal(fl1Utilities[0].Id, fl1ConstructionGraph.Id); + + var sl1ConstructionGraph = Assert.Single(fl1ConstructionGraph.Dependencies); + Assert.NotNull(sl1ConstructionGraph); + Assert.Equal(sl1Utilities[i].Id, sl1ConstructionGraph.Id); + + var fl2ConstructionGraph = constructionGraphs[0][1]; + Assert.NotNull(fl2ConstructionGraph); + Assert.Equal(fl2Utilities[i].Id, fl2ConstructionGraph.Id); + + Assert.Empty(fl2ConstructionGraph.Dependencies); + } + } + + [Fact] + public void UnsatisfiableOuterDemandsShouldBeHandledCorrectlyAtRootLevel() + { + var utilitiesCount = RandomizationHelper.RandomInteger(3, 10); + var setup = new EnvironmentSetup("outer_demands_env"); + setup.WithCategory("FL1", 1).WithCategory("FL2", utilitiesCount).WithCategory("SL1", utilitiesCount); + setup.WithRequirements("FL1", 1, "SL1"); + + for (var i = 1; i <= utilitiesCount; i++) + { + setup.WithCharacteristics("FL2", i, $"C{i}"); + setup.WithOuterDemands("SL1", i, "FL2", $"D{i}"); + } + + var assemblyTestData = setup.MaterializeAsAssemblyData(); + var manager = new ConstructionManager(assemblyTestData); + + var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray(); + Assert.Single(fl1Utilities); + + var fl2Utilities = assemblyTestData.CleanUtilities.Get("FL2").ToArray(); + Assert.Equal(utilitiesCount, fl2Utilities.Length); + + for (var i = 0; i < utilitiesCount; i++) + { + var utilityIds = new[] { fl1Utilities[0].Id, fl2Utilities[i].Id }; + var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds); + + Assert.Empty(constructionGraphs); + } + } + + [Fact] + public void OuterDemandsShouldBeAppliedCorrectlyAtDeeperLevels() + { + var utilitiesCount = RandomizationHelper.RandomInteger(3, 10); + var setup = new EnvironmentSetup("outer_demands_env"); + setup.WithCategory("FL1", 1).WithCategory("SL1", 1).WithCategory("SL2", utilitiesCount).WithCategory("TL1", utilitiesCount); + setup.WithRequirements("FL1", 1, "SL1", "SL2").WithRequirements("SL1", 1, "TL1"); + + for (var i = 1; i <= utilitiesCount; i++) + { + var characteristic = $"C-{i}"; + setup.WithCharacteristics("SL2", i, characteristic); + setup.WithOuterDemands("TL1", i, "SL2", characteristic); + } + + var assemblyTestData = setup.MaterializeAsAssemblyData(); + var manager = new ConstructionManager(assemblyTestData); + + var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray(); + Assert.Single(fl1Utilities); + + var sl1Utilities = assemblyTestData.CleanUtilities.Get("SL1").ToArray(); + Assert.Single(sl1Utilities); + + var sl2Utilities = assemblyTestData.CleanUtilities.Get("SL2").ToArray(); + Assert.Equal(utilitiesCount, sl2Utilities.Length); + + var tl1Utilities = assemblyTestData.CleanUtilities.Get("TL1").ToArray(); + Assert.Equal(utilitiesCount, tl1Utilities.Length); + + var utilityIds = new[] { fl1Utilities[0].Id }; + var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds); + + Assert.Equal(utilitiesCount, constructionGraphs.Length); + for (var i = 0; i < utilitiesCount; i++) + { + var fl1ConstructionGraph = constructionGraphs[i][0]; + + Assert.NotNull(fl1Utilities); + Assert.Equal(fl1Utilities[0].Id, fl1ConstructionGraph.Id); + Assert.Equal(2, fl1ConstructionGraph.Dependencies.Count); + + var sl1ConstructionGraph = fl1ConstructionGraph.Dependencies[0]; + Assert.NotNull(sl1ConstructionGraph); + Assert.Equal(sl1Utilities[0].Id, sl1ConstructionGraph.Id); + + var sl2ConstructionGraph = fl1ConstructionGraph.Dependencies[1]; + Assert.NotNull(sl2ConstructionGraph); + Assert.Equal(sl2Utilities[i].Id, sl2ConstructionGraph.Id); + Assert.Empty(sl2ConstructionGraph.Dependencies); + + var tl1ConstructionGraph = Assert.Single(sl1ConstructionGraph.Dependencies); + Assert.NotNull(tl1ConstructionGraph); + Assert.Equal(tl1Utilities[i].Id, tl1ConstructionGraph.Id); + Assert.Empty(tl1ConstructionGraph.Dependencies); + } + } + + [Fact] + public void UnsatisfiableOuterDemandsShouldBeHandledCorrectlyAtDeeperLevels() + { + var utilitiesCount = RandomizationHelper.RandomInteger(3, 10); + var setup = new EnvironmentSetup("outer_demands_env"); + setup.WithCategory("FL1", 1).WithCategory("SL1", 1).WithCategory("SL2", utilitiesCount).WithCategory("TL1", utilitiesCount); + setup.WithRequirements("FL1", 1, "SL1", "SL2").WithRequirements("SL1", 1, "TL1"); + + for (var i = 1; i <= utilitiesCount; i++) + { + setup.WithCharacteristics("SL2", i, $"C{i}"); + setup.WithOuterDemands("TL1", i, "SL2", $"D{i}"); + } + + var assemblyTestData = setup.MaterializeAsAssemblyData(); + var manager = new ConstructionManager(assemblyTestData); + + var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray(); + Assert.Single(fl1Utilities); + + var utilityIds = new[] { fl1Utilities[0].Id }; + var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds); + + Assert.Empty(constructionGraphs); + } + + public static IEnumerable GetDependenciesManagerSetups() => TestParameters.ConstructObservableConstructionManagerSetups().Select(dependenciesManagerSetup => new object[] { dependenciesManagerSetup.EnvironmentSetup, dependenciesManagerSetup.PathToExpectedResult }); +} \ No newline at end of file diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs deleted file mode 100644 index ca120ca..0000000 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace TryAtSoftware.CleanTests.UnitTests; - -using System.Text.Json; -using TryAtSoftware.CleanTests.Core.Construction; -using TryAtSoftware.CleanTests.Core.Dependencies; -using TryAtSoftware.CleanTests.UnitTests.Constants; -using TryAtSoftware.CleanTests.UnitTests.Extensions; -using TryAtSoftware.CleanTests.UnitTests.Parametrization; - -public class DependenciesManagerTests -{ - [Theory(Timeout = UnitTestConstants.Timeout)] - [MemberData(nameof(GetDependenciesManagerSetups))] - public void DependencyGraphsShouldBeConstructedSuccessfully(EnvironmentSetup setup, string pathToExpectedResult) - { - var assemblyTestData = setup.MaterializeAsAssemblyData(); - var manager = new ConstructionManager(assemblyTestData); - - var allUtilities = assemblyTestData.CleanUtilities.GetAllValues().ToArray(); - var expectedOutput = File.ReadLines(pathToExpectedResult).ToArray(); - Assert.Equal(allUtilities.Length, expectedOutput.Length); - - for (var i = 0; i < allUtilities.Length; i++) - { - var constructionPaths = manager.BuildIndividualConstructionGraphs(new[] { allUtilities[i].Id }); - var output = JsonSerializer.Serialize(constructionPaths); - Assert.Equal(expectedOutput[i], output); - } - } - - public static IEnumerable GetDependenciesManagerSetups() - => TestParameters.ConstructObservableConstructionManagerSetups() - .Select(dependenciesManagerSetup => new object[] { dependenciesManagerSetup.EnvironmentSetup, dependenciesManagerSetup.PathToExpectedResult }); -} \ No newline at end of file diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/EnvironmentSetup.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/EnvironmentSetup.cs index 2c951ff..996a14a 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/EnvironmentSetup.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/EnvironmentSetup.cs @@ -7,7 +7,8 @@ public class EnvironmentSetup { private readonly Dictionary _numberOfUtilitiesPerCategory = new (); - private readonly Dictionary>> _demandsPerUtility = new (); + private readonly Dictionary>> _externalDemandsPerUtility = new (); + private readonly Dictionary>> _outerDemandsPerUtility = new (); private readonly Dictionary> _characteristics = new (); private readonly Dictionary> _requirements = new (); @@ -52,14 +53,26 @@ public EnvironmentSetup WithRequirements(string category, int utilityId, params return this; } - public EnvironmentSetup WithDemands(string utilityCategory, int utilityId, string demandsCategory, params string[] demands) + public EnvironmentSetup WithExternalDemands(string utilityCategory, int utilityId, string demandsCategory, params string[] demands) { this.ValidateUtilityExists(utilityCategory, utilityId); var universalId = ComposeUniversalUtilityId(utilityCategory, utilityId); - if (!this._demandsPerUtility.ContainsKey(universalId)) this._demandsPerUtility[universalId] = new Dictionary>(); - if (!this._demandsPerUtility[universalId].ContainsKey(demandsCategory)) this._demandsPerUtility[universalId][demandsCategory] = new List(); - foreach (var demand in demands.OrEmptyIfNull().IgnoreNullOrWhitespaceValues()) this._demandsPerUtility[universalId][demandsCategory].Add(demand); + if (!this._externalDemandsPerUtility.ContainsKey(universalId)) this._externalDemandsPerUtility[universalId] = new Dictionary>(); + if (!this._externalDemandsPerUtility[universalId].ContainsKey(demandsCategory)) this._externalDemandsPerUtility[universalId][demandsCategory] = new List(); + foreach (var demand in demands.OrEmptyIfNull().IgnoreNullOrWhitespaceValues()) this._externalDemandsPerUtility[universalId][demandsCategory].Add(demand); + + return this; + } + + public EnvironmentSetup WithOuterDemands(string utilityCategory, int utilityId, string demandsCategory, params string[] demands) + { + this.ValidateUtilityExists(utilityCategory, utilityId); + + var universalId = ComposeUniversalUtilityId(utilityCategory, utilityId); + if (!this._outerDemandsPerUtility.ContainsKey(universalId)) this._outerDemandsPerUtility[universalId] = new Dictionary>(); + if (!this._outerDemandsPerUtility[universalId].ContainsKey(demandsCategory)) this._outerDemandsPerUtility[universalId][demandsCategory] = new List(); + foreach (var demand in demands.OrEmptyIfNull().IgnoreNullOrWhitespaceValues()) this._outerDemandsPerUtility[universalId][demandsCategory].Add(demand); return this; } @@ -78,11 +91,17 @@ public ICleanTestInitializationCollection Materialize() this._requirements.TryGetValue(universalId, out var requirements); ICleanUtilityDescriptor utility = new CleanUtilityDescriptor(category, typeof(int), universalId, isGlobal: false, characteristics, requirements); - this._demandsPerUtility.TryGetValue(universalId, out var demandsByCategory); - foreach (var (demandCategory, demands) in demandsByCategory.OrEmptyIfNull()) + this._externalDemandsPerUtility.TryGetValue(universalId, out var externalDemandsByCategory); + foreach (var (demandCategory, demands) in externalDemandsByCategory.OrEmptyIfNull()) { foreach (var demand in demands) utility.ExternalDemands.Register(demandCategory, demand); } + + this._outerDemandsPerUtility.TryGetValue(universalId, out var outerDemandsByCategory); + foreach (var (demandCategory, demands) in outerDemandsByCategory.OrEmptyIfNull()) + { + foreach (var demand in demands) utility.OuterDemands.Register(demandCategory, demand); + } utilitiesCollection.Register(category, utility); } diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs index 0f5b93a..fd3a725 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs @@ -15,12 +15,12 @@ public static class TestParameters { var setup1 = new EnvironmentSetup("Setup #1").WithCategory("A", 3).WithCategory("B", 2).WithCategory("C", 3).WithCategory("D", 3); setup1.WithCharacteristics("C", 1, "exec_A2").WithCharacteristics("C", 3, "exec_A2").WithCharacteristics("D", 1, "exec_C3").WithCharacteristics("D", 2, "exec_B1", "exec_B2", "exec_C3").WithCharacteristics("D", 3, "exec_B1", "exec_B2"); - setup1.WithDemands("A", 2, "C", "exec_A2").WithDemands("B", 1, "D", "exec_B1").WithDemands("B", 2, "D", "exec_B2").WithDemands("C", 3, "D", "exec_C3"); + setup1.WithExternalDemands("A", 2, "C", "exec_A2").WithExternalDemands("B", 1, "D", "exec_B1").WithExternalDemands("B", 2, "D", "exec_B2").WithExternalDemands("C", 3, "D", "exec_C3"); yield return (setup1, 26); var setup2 = new EnvironmentSetup("Setup #2").WithCategory("A", 2).WithCategory("B", 2).WithCategory("C", 2).WithCategory("D", 2).WithCategory("E", 2); setup2.WithCharacteristics("B", 2, "exec_A1").WithCharacteristics("C", 1, "exec_B2").WithCharacteristics("D", 2, "exec_C1").WithCharacteristics("E", 1, "exec_D2"); - setup2.WithDemands("A", 1, "B", "exec_A1").WithDemands("B", 2, "C", "exec_B2").WithDemands("C", 1, "D", "exec_C1").WithDemands("D", 2, "E", "exec_D2"); + setup2.WithExternalDemands("A", 1, "B", "exec_A1").WithExternalDemands("B", 2, "C", "exec_B2").WithExternalDemands("C", 1, "D", "exec_C1").WithExternalDemands("D", 2, "E", "exec_D2"); yield return (setup2, 6); // 5 categories; 10 utilities in each; no demands @@ -47,7 +47,7 @@ public static class TestParameters for (var i = 0; i < 9; i++) { - for (var j = 1; j <= 5; j++) setup5.WithDemands(ConstructCategoryName(i), j, ConstructCategoryName(i + 1), $"exec_{ConstructCategoryName(i)}{j}"); + for (var j = 1; j <= 5; j++) setup5.WithExternalDemands(ConstructCategoryName(i), j, ConstructCategoryName(i + 1), $"exec_{ConstructCategoryName(i)}{j}"); } yield return (setup5, 98415); @@ -62,7 +62,7 @@ public static class TestParameters for (var i = 0; i < 99; i++) { - for (var j = 1; j <= 10; j++) setup6.WithDemands(ConstructCategoryName(i), j, ConstructCategoryName(i + 1), $"exec_{ConstructCategoryName(i)}{j}"); + for (var j = 1; j <= 10; j++) setup6.WithExternalDemands(ConstructCategoryName(i), j, ConstructCategoryName(i + 1), $"exec_{ConstructCategoryName(i)}{j}"); } yield return (setup6, 10); @@ -70,19 +70,19 @@ public static class TestParameters // 1000 categories; 100 utilities in each; all utilities in the first category are incompatible with all utilities in the last category. var setup7 = new EnvironmentSetup("Setup #7"); for (var i = 0; i < 1000; i++) setup7.WithCategory(ConstructCategoryName(i), 100); - for (var i = 1; i <= 100; i++) setup7.WithDemands(ConstructCategoryName(0), i, ConstructCategoryName(999), "q"); + for (var i = 1; i <= 100; i++) setup7.WithExternalDemands(ConstructCategoryName(0), i, ConstructCategoryName(999), "q"); yield return (setup7, 0); // 3 categories; 2 utilities in each; Inapplicable demands var setup8 = new EnvironmentSetup("Setup #8"); for (var i = 0; i < 5; i++) setup8.WithCategory(ConstructCategoryName(i), 2); for (var i = 0; i < 5; i++) - setup8.WithDemands(ConstructCategoryName(i), 1, "non-existing demand category", "demand1", "demand2"); + setup8.WithExternalDemands(ConstructCategoryName(i), 1, "non-existing demand category", "demand1", "demand2"); yield return (setup8, 32); var setup9 = new EnvironmentSetup("Setup #9"); for (var i = 0; i < 5; i++) setup9.WithCategory(ConstructCategoryName(i), 3); - for (var i = 0; i < 4; i++) setup9.WithDemands(ConstructCategoryName(i), 1, ConstructCategoryName(4), $"exec_{ConstructCategoryName(i)}{1}"); + for (var i = 0; i < 4; i++) setup9.WithExternalDemands(ConstructCategoryName(i), 1, ConstructCategoryName(4), $"exec_{ConstructCategoryName(i)}{1}"); yield return (setup9, 48); } diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/TryAtSoftware.CleanTests.UnitTests.csproj b/Tests/TryAtSoftware.CleanTests.UnitTests/TryAtSoftware.CleanTests.UnitTests.csproj index dca0c02..4deba27 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/TryAtSoftware.CleanTests.UnitTests.csproj +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/TryAtSoftware.CleanTests.UnitTests.csproj @@ -13,7 +13,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/TryAtSoftware.CleanTests.Core/Attributes/OuterDemandsAttribute.cs b/TryAtSoftware.CleanTests.Core/Attributes/OuterDemandsAttribute.cs new file mode 100644 index 0000000..a87e416 --- /dev/null +++ b/TryAtSoftware.CleanTests.Core/Attributes/OuterDemandsAttribute.cs @@ -0,0 +1,12 @@ +namespace TryAtSoftware.CleanTests.Core.Attributes; + +using System; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class OuterDemandsAttribute : BaseDemandsAttribute +{ + public OuterDemandsAttribute(string category, params string[] demands) + : base(category, demands) + { + } +} \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs b/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs index c765062..11fcade 100644 --- a/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs +++ b/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs @@ -41,6 +41,9 @@ public void Register(string category, TValue value) /// public IEnumerable GetAllValues() => this._data.Values.SelectMany(x => x.OrEmptyIfNull().IgnoreNullValues()); + /// + public void Clear() => this._data.Clear(); + /// public IEnumerator>> GetEnumerator() { diff --git a/TryAtSoftware.CleanTests.Core/CleanUtilityDescriptor.cs b/TryAtSoftware.CleanTests.Core/CleanUtilityDescriptor.cs index c307058..561a1bb 100644 --- a/TryAtSoftware.CleanTests.Core/CleanUtilityDescriptor.cs +++ b/TryAtSoftware.CleanTests.Core/CleanUtilityDescriptor.cs @@ -31,6 +31,9 @@ public class CleanUtilityDescriptor : ICleanUtilityDescriptor /// public ICleanTestInitializationCollection InternalDemands { get; } = new CleanTestInitializationCollection(); + /// + public ICleanTestInitializationCollection OuterDemands { get; } = new CleanTestInitializationCollection(); + /// public IReadOnlyCollection InternalRequirements => this._internalRequirements.AsReadOnlyCollection(); diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionGraphs.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionGraphs.cs index 7a8f4ea..afd4a92 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionGraphs.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionGraphs.cs @@ -1,4 +1,4 @@ -namespace TryAtSoftware.CleanTests.Core.Dependencies; +namespace TryAtSoftware.CleanTests.Core.Construction; using System.Collections.Generic; @@ -13,7 +13,7 @@ public record FullCleanUtilityConstructionGraph(string Id) { public string Id { get; } = Id; - public List> ConstructionDescriptors { get; } = new (); + public List ConstructionDescriptors { get; } = new (); } diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index d031ec3..e9b5ee7 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using TryAtSoftware.CleanTests.Core.Dependencies; using TryAtSoftware.CleanTests.Core.Extensions; using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.CleanTests.Core.Utilities; @@ -23,16 +22,19 @@ public ConstructionManager(CleanTestAssemblyData cleanTestAssemblyData) public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGraphs(IEnumerable utilityIds) { - var dependenciesConstructionGraphs = new List(); + var constructionGraphs = new List(); + var utilitiesByCategory = new Dictionary(); foreach (var utilityId in utilityIds) { var constructionGraph = this.AccessConstructionGraph(utilityId); if (constructionGraph is null) return Array.Empty(); - dependenciesConstructionGraphs.Add(constructionGraph); + constructionGraphs.Add(constructionGraph); + this.ExtractUtilityByCategory(utilityId, utilitiesByCategory); } - var individualRepresentationsOfConstructionGraphs = dependenciesConstructionGraphs.Select(FlattenConstructionGraph).ToArray(); + var individualRepresentationsOfConstructionGraphs = new IndividualCleanUtilityConstructionGraph[constructionGraphs.Count][]; + for (var i = 0; i < constructionGraphs.Count; i++) individualRepresentationsOfConstructionGraphs[i] = FlattenConstructionGraph(constructionGraphs[i], x => this.OuterDemandsAreFulfilled(x, utilitiesByCategory)); return Merge(individualRepresentationsOfConstructionGraphs); } @@ -57,48 +59,120 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr var dependencyGraphsById = new Dictionary(); foreach (var requirement in utility.InternalRequirements) { - var currentDependencies = this.ExtractDependencies(utility, requirement); + this.ExtractDependencies(utility, requirement, usedUtilities, dependenciesCollection, dependencyGraphsById); - foreach (var dependency in currentDependencies) - { - if (usedUtilities.Contains(dependency.Id)) continue; + // Construction graph for the current utility should not be built if none of the dependencies can be constructed successfully within the given context. + if (dependenciesCollection.GetCount(requirement) == 0) return null; + } + + usedUtilities.Remove(utilityId); + + var combinatorialMachine = new CombinatorialMachine(dependenciesCollection); + var dependenciesCombinations = combinatorialMachine.GenerateAllCombinations(); + + var constructionGraphsPerCombination = new FullCleanUtilityConstructionGraph[utility.InternalRequirements.Count]; + var utilitiesByCategoryPerCombination = new Dictionary(); + foreach (var combination in dependenciesCombinations.Select(x => x.Values)) + { + this.ExtractUtilitiesByCategory(combination, utilitiesByCategoryPerCombination); + ExtractConstructionGraphs(combination, dependencyGraphsById, constructionGraphsPerCombination); + + var dependenciesConstructionGraphs = this.NormalizeDependenciesConstructionGraphs(constructionGraphsPerCombination, utilitiesByCategoryPerCombination); + if (dependenciesConstructionGraphs is not null) graph.ConstructionDescriptors.Add(dependenciesConstructionGraphs); + } + + // Construction graph for the current utility should not be built if there are no construction descriptors left after the normalization stage. + if (graph.ConstructionDescriptors.Count == 0) return null; + return graph; + } + + private FullCleanUtilityConstructionGraph[]? NormalizeDependenciesConstructionGraphs(FullCleanUtilityConstructionGraph[] constructionGraphs, IDictionary outerUtilitiesByCategory) + { + var result = new FullCleanUtilityConstructionGraph[constructionGraphs.Length]; - var dependencyGraph = this.BuildConstructionGraph(dependency.Id, usedUtilities); - if (dependencyGraph is null) continue; + for (var i = 0; i < constructionGraphs.Length; i++) + { + var graphCopy = constructionGraphs[i].Copy(); + if (graphCopy.ConstructionDescriptors.Count > 0) + { + var useAny = false; + for (var j = graphCopy.ConstructionDescriptors.Count - 1; j >= 0; j--) + { + if (this.OuterDemandsAreFulfilled(graphCopy.ConstructionDescriptors[j], outerUtilitiesByCategory)) useAny = true; + else graphCopy.ConstructionDescriptors.RemoveAt(j); + } - dependenciesCollection.Register(requirement, dependency); - dependencyGraphsById[dependency.Id] = dependencyGraph; + if (!useAny) return null; } - if (dependenciesCollection.GetCount(requirement) == 0) return null; + result[i] = graphCopy; } - usedUtilities.Remove(utilityId); + return result; + } - var graphIterator = new CombinatorialMachine(dependenciesCollection); - var dependenciesVariations = graphIterator.GenerateAllCombinations(); - foreach (var variation in dependenciesVariations) + private bool OuterDemandsAreFulfilled(IEnumerable constructionDescriptor, IDictionary outerUtilitiesByCategory) + { + foreach (var dependencyConstructionGraph in constructionDescriptor) { - var variationDependenciesConstructionGraphs = variation.Values.Select(x => dependencyGraphsById[x]).ToList(); - graph.ConstructionDescriptors.Add(variationDependenciesConstructionGraphs); + var dependencyUtility = this._cleanTestAssemblyData.CleanUtilitiesById[dependencyConstructionGraph.Id]; + foreach (var (category, demands) in dependencyUtility.OuterDemands) + { + if (outerUtilitiesByCategory.TryGetValue(category, out var outerUtilityForCategory) && !outerUtilityForCategory.FulfillsAllDemands(demands)) + return false; + } } - return graph; + return true; + } + + private void ExtractUtilitiesByCategory(IEnumerable utilityIds, IDictionary destination) + { + foreach (var utilityId in utilityIds) + this.ExtractUtilityByCategory(utilityId, destination); } - private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, string requirement) + private void ExtractUtilityByCategory(string utilityId, IDictionary destination) + { + var utility = this._cleanTestAssemblyData.CleanUtilitiesById[utilityId]; + destination[utility.Category] = utility; + } + + private static void ExtractConstructionGraphs(IEnumerable utilityIds, IDictionary constructionGraphsById, FullCleanUtilityConstructionGraph[] destination) + { + var index = 0; + foreach (var utilityId in utilityIds) + { + destination[index] = constructionGraphsById[utilityId]; + index++; + } + } + + private void ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, string requirement, ISet usedUtilities, ICleanTestInitializationCollection dependenciesCollection, IDictionary dependencyGraphsById) { var localDemands = utilityDescriptor.InternalDemands.Get(requirement); Func? predicate = null; if (utilityDescriptor.IsGlobal) predicate = x => x.IsGlobal; - return this._cleanTestAssemblyData.CleanUtilities.Get(requirement, localDemands, predicate); + var dependencies = this._cleanTestAssemblyData.CleanUtilities.Get(requirement, localDemands, predicate); + + foreach (var dependency in dependencies) + { + if (usedUtilities.Contains(dependency.Id)) continue; + + var dependencyGraph = this.BuildConstructionGraph(dependency.Id, usedUtilities); + if (dependencyGraph is null) continue; + + dependenciesCollection.Register(requirement, dependency); + dependencyGraphsById[dependency.Id] = dependencyGraph; + } } /// /// Use this method to transform a to a collection of instances. /// /// The construction graph that should be transformed. + /// A filter that should be applied to the root construction descriptors. /// Returns the collection of subsequently built instances. /// /// Let's assume that we have the following construction graph: @@ -134,7 +208,7 @@ private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor ut /// | Service 2.1B | /// | Service 3A | /// - private static IndividualCleanUtilityConstructionGraph[] FlattenConstructionGraph(FullCleanUtilityConstructionGraph constructionGraph) + private static IndividualCleanUtilityConstructionGraph[] FlattenConstructionGraph(FullCleanUtilityConstructionGraph constructionGraph, Func, bool>? rootConstructionDescriptorPredicate = null) { if (constructionGraph.ConstructionDescriptors.Count == 0) { @@ -145,8 +219,10 @@ private static IndividualCleanUtilityConstructionGraph[] FlattenConstructionGrap var ans = new List(); foreach (var constructionDescriptor in constructionGraph.ConstructionDescriptors) { - var current = new IndividualCleanUtilityConstructionGraph[constructionDescriptor.Count][]; - for (var i = 0; i < constructionDescriptor.Count; i++) + if (rootConstructionDescriptorPredicate is not null && !rootConstructionDescriptorPredicate(constructionDescriptor)) continue; + + var current = new IndividualCleanUtilityConstructionGraph[constructionDescriptor.Length][]; + for (var i = 0; i < constructionDescriptor.Length; i++) current[i] = FlattenConstructionGraph(constructionDescriptor[i]); var nodes = IterateAllSequences(current, x => Union(constructionGraph.Id, x)).Select(x => Union(constructionGraph.Id, x.Dependencies)); diff --git a/TryAtSoftware.CleanTests.Core/Extensions/ConstructionGraphExtensions.cs b/TryAtSoftware.CleanTests.Core/Extensions/ConstructionGraphExtensions.cs new file mode 100644 index 0000000..8a7fead --- /dev/null +++ b/TryAtSoftware.CleanTests.Core/Extensions/ConstructionGraphExtensions.cs @@ -0,0 +1,20 @@ +namespace TryAtSoftware.CleanTests.Core.Extensions; + +using TryAtSoftware.CleanTests.Core.Construction; + +public static class ConstructionGraphExtensions +{ + public static FullCleanUtilityConstructionGraph Copy(this FullCleanUtilityConstructionGraph graph) + { + var graphCopy = new FullCleanUtilityConstructionGraph(graph.Id); + foreach (var descriptor in graph.ConstructionDescriptors) + { + var descriptorCopy = new FullCleanUtilityConstructionGraph[descriptor.Length]; + for (var i = 0; i < descriptor.Length; i++) descriptorCopy[i] = descriptor[i].Copy(); + + graphCopy.ConstructionDescriptors.Add(descriptorCopy); + } + + return graphCopy; + } +} \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs index 5218ac5..94adaeb 100644 --- a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs +++ b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs @@ -10,4 +10,5 @@ public interface ICleanTestInitializationCollection : IEnumerable GetAllValues(); + void Clear(); } \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanUtilityDescriptor.cs b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanUtilityDescriptor.cs index f8c1151..b93ead9 100644 --- a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanUtilityDescriptor.cs +++ b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanUtilityDescriptor.cs @@ -27,6 +27,11 @@ public interface ICleanUtilityDescriptor /// Gets a collection of demands defining conditions towards utilities the represented one internally depends on in order to be instantiated. /// ICleanTestInitializationCollection InternalDemands { get; } + + /// + /// Gets a collection of demands defining conditions towards utilities that depend on the represented one in order to be instantiated. + /// + ICleanTestInitializationCollection OuterDemands { get; } /// /// Gets a collection of categories the represented utility depends on in order to be instantiated. diff --git a/TryAtSoftware.CleanTests.Core/Interfaces/IConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Interfaces/IConstructionManager.cs index 1938e51..531747f 100644 --- a/TryAtSoftware.CleanTests.Core/Interfaces/IConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Interfaces/IConstructionManager.cs @@ -1,7 +1,7 @@ namespace TryAtSoftware.CleanTests.Core.Interfaces; using System.Collections.Generic; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; public interface IConstructionManager { diff --git a/TryAtSoftware.CleanTests.Core/TryAtSoftware.CleanTests.Core.csproj b/TryAtSoftware.CleanTests.Core/TryAtSoftware.CleanTests.Core.csproj index e26691c..3beb028 100644 --- a/TryAtSoftware.CleanTests.Core/TryAtSoftware.CleanTests.Core.csproj +++ b/TryAtSoftware.CleanTests.Core/TryAtSoftware.CleanTests.Core.csproj @@ -21,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/TryAtSoftware.CleanTests.Core/XUnit/CleanTestCaseData.cs b/TryAtSoftware.CleanTests.Core/XUnit/CleanTestCaseData.cs index 09738fa..2ab951d 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/CleanTestCaseData.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/CleanTestCaseData.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; using TryAtSoftware.Extensions.Collections; public class CleanTestCaseData diff --git a/TryAtSoftware.CleanTests.Core/XUnit/CleanTestFramework.cs b/TryAtSoftware.CleanTests.Core/XUnit/CleanTestFramework.cs index af45091..183d9b5 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/CleanTestFramework.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/CleanTestFramework.cs @@ -76,8 +76,9 @@ private static void RegisterUtilitiesFromAssembly(IAssemblyInfo assemblyInfo, IC if (initializationUtilityAttributes.Length == 0) continue; var decoratedType = new DecoratedType(type); - var internalDemands = decoratedType.ExtractDemands(); var externalDemands = decoratedType.ExtractDemands(); + var internalDemands = decoratedType.ExtractDemands(); + var outerDemands = decoratedType.ExtractDemands(); var requirements = ExtractRequirements(type); foreach (var utilityAttribute in initializationUtilityAttributes.OrEmptyIfNull().IgnoreNullValues()) @@ -88,8 +89,9 @@ private static void RegisterUtilitiesFromAssembly(IAssemblyInfo assemblyInfo, IC var characteristicsArgument = utilityAttribute.GetNamedArgument>(nameof(CleanUtilityAttribute.Characteristics)); var initializationUtility = new CleanUtilityDescriptor(categoryArgument, type.ToRuntimeType(), nameArgument, isGlobalArgument, characteristicsArgument, requirements); - internalDemands.CopyTo(initializationUtility.InternalDemands); externalDemands.CopyTo(initializationUtility.ExternalDemands); + internalDemands.CopyTo(initializationUtility.InternalDemands); + outerDemands.CopyTo(initializationUtility.OuterDemands); utilitiesCollection.Add(initializationUtility); } diff --git a/TryAtSoftware.CleanTests.Core/XUnit/ConstructionCache.cs b/TryAtSoftware.CleanTests.Core/XUnit/ConstructionCache.cs index a60cbd9..7c53f41 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/ConstructionCache.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/ConstructionCache.cs @@ -1,7 +1,7 @@ namespace TryAtSoftware.CleanTests.Core.XUnit; using System.Collections.Generic; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; /// /// This class has a significant role for optimizing the discovery phase. diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs index 8140b93..9136f77 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; using TryAtSoftware.CleanTests.Core.Enums; using TryAtSoftware.CleanTests.Core.Extensions; using TryAtSoftware.CleanTests.Core.Interfaces; @@ -34,14 +34,14 @@ protected BaseTestCaseDiscoverer(IMessageSink diagnosticMessageSink, TestCaseDis public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) { var graphIterator = new CombinatorialMachine(this._initializationUtilitiesCollection); - var variations = graphIterator.GenerateAllCombinations(); + var combinations = graphIterator.GenerateAllCombinations(); var argumentsCollection = this.GetTestMethodArguments(this._diagnosticMessageSink, testMethod).ToArray(); var testCases = new List(); - foreach (var variation in variations) + foreach (var combination in combinations) { - var dependenciesSet = this._constructionManager.BuildIndividualConstructionGraphs(variation.Values); + var dependenciesSet = this._constructionManager.BuildIndividualConstructionGraphs(combination.Values); foreach (var dependencies in dependenciesSet) { diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs index 0e694dc..dc51704 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs @@ -4,7 +4,6 @@ namespace TryAtSoftware.CleanTests.Core.XUnit.Discovery; using System.Collections.Generic; using TryAtSoftware.CleanTests.Core.Attributes; using TryAtSoftware.CleanTests.Core.Construction; -using TryAtSoftware.CleanTests.Core.Dependencies; using TryAtSoftware.CleanTests.Core.Extensions; using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.CleanTests.Core.XUnit.Extensions; diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestCollectionRunner.cs b/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestCollectionRunner.cs index 095755a..c4764d1 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestCollectionRunner.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestCollectionRunner.cs @@ -5,7 +5,7 @@ namespace TryAtSoftware.CleanTests.Core.XUnit.Execution; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; using TryAtSoftware.CleanTests.Core.Extensions; using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.CleanTests.Core.Utilities; diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestInvoker.cs b/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestInvoker.cs index 893e8d2..6b8d6a4 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestInvoker.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Execution/CleanTestInvoker.cs @@ -5,7 +5,7 @@ namespace TryAtSoftware.CleanTests.Core.XUnit.Execution; using System.Reflection; using System.Threading; using Microsoft.Extensions.DependencyInjection; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.CleanTests.Core.XUnit.Extensions; using TryAtSoftware.CleanTests.Core.XUnit.Interfaces; diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Extensions/XUnitFrameworkExtensions.cs b/TryAtSoftware.CleanTests.Core/XUnit/Extensions/XUnitFrameworkExtensions.cs index b3fd9a1..aa06cd0 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Extensions/XUnitFrameworkExtensions.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Extensions/XUnitFrameworkExtensions.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.CleanTests.Core.XUnit.Interfaces; using TryAtSoftware.Extensions.Collections; diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableIndividualDependencyNode.cs b/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableIndividualDependencyNode.cs index 44c8f6c..3e0b5d0 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableIndividualDependencyNode.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableIndividualDependencyNode.cs @@ -2,7 +2,7 @@ using System; using System.Linq; -using TryAtSoftware.CleanTests.Core.Dependencies; +using TryAtSoftware.CleanTests.Core.Construction; using TryAtSoftware.CleanTests.Core.Extensions; using TryAtSoftware.Extensions.Collections; using Xunit.Abstractions; diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs b/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs index 218a2d6..4fd1ae5 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs @@ -43,16 +43,18 @@ public void Deserialize(IXunitSerializationInfo info) var characteristics = info.GetValue("ch"); - var deserializedGlobalDemands = info.GetValue("gd"); - var deserializedLocalDemands = info.GetValue("ld"); + var deserializedExternalDemands = info.GetValue("gd"); + var deserializedInternalDemands = info.GetValue("ld"); + var deserializedOuterDemands = info.GetValue("od"); var deserializedRequirements = info.GetValue("r"); var requirements = new HashSet(); foreach (var requirement in deserializedRequirements) requirements.Add(requirement); this.CleanUtilityDescriptor = new CleanUtilityDescriptor(initializationCategory, deserializedUtilityType, utilityName, isGlobal, characteristics, requirements); - DeserializeDemands(deserializedGlobalDemands, this.CleanUtilityDescriptor.ExternalDemands); - DeserializeDemands(deserializedLocalDemands, this.CleanUtilityDescriptor.InternalDemands); + DeserializeDemands(deserializedExternalDemands, this.CleanUtilityDescriptor.ExternalDemands); + DeserializeDemands(deserializedInternalDemands, this.CleanUtilityDescriptor.InternalDemands); + DeserializeDemands(deserializedOuterDemands, this.CleanUtilityDescriptor.OuterDemands); } /// @@ -61,13 +63,14 @@ public void Serialize(IXunitSerializationInfo info) if (info is null) throw new ArgumentNullException(nameof(info)); info.AddValue("c", this.CleanUtilityDescriptor.Category); - info.AddValue("id", this.CleanUtilityDescriptor.Id.ToString()); + info.AddValue("id", this.CleanUtilityDescriptor.Id); info.AddValue("ut", this.CleanUtilityDescriptor.Type); info.AddValue("un", this.CleanUtilityDescriptor.Name); info.AddValue("ig", this.CleanUtilityDescriptor.IsGlobal); info.AddValue("gd", Serialize(this.CleanUtilityDescriptor.ExternalDemands)); info.AddValue("ld", Serialize(this.CleanUtilityDescriptor.InternalDemands)); + info.AddValue("od", Serialize(this.CleanUtilityDescriptor.OuterDemands)); var characteristics = this.CleanUtilityDescriptor.Characteristics.ToArray(); info.AddValue("ch", characteristics); diff --git a/TryAtSoftware.CleanTests.Sample/SampleCleanTest.cs b/TryAtSoftware.CleanTests.Sample/SampleCleanTest.cs index 4b67507..c590005 100644 --- a/TryAtSoftware.CleanTests.Sample/SampleCleanTest.cs +++ b/TryAtSoftware.CleanTests.Sample/SampleCleanTest.cs @@ -73,7 +73,7 @@ public void TestComplexUtilityDistribution() [CleanFact] [WithRequirements(Categories.People)] - [TestDemands(Categories.People, Characteristics.KnownPerson)] + [TestDemands(Categories.People, Characteristics.People.KnownPerson)] public void TestUtilityDistributionWithDemands() { var person = this.GetService(); diff --git a/TryAtSoftware.CleanTests.Sample/TryAtSoftware.CleanTests.Sample.csproj b/TryAtSoftware.CleanTests.Sample/TryAtSoftware.CleanTests.Sample.csproj index 119a701..807a820 100644 --- a/TryAtSoftware.CleanTests.Sample/TryAtSoftware.CleanTests.Sample.csproj +++ b/TryAtSoftware.CleanTests.Sample/TryAtSoftware.CleanTests.Sample.csproj @@ -13,7 +13,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/Characteristics.cs b/TryAtSoftware.CleanTests.Sample/Utilities/Characteristics.cs index d470d59..50bf183 100644 --- a/TryAtSoftware.CleanTests.Sample/Utilities/Characteristics.cs +++ b/TryAtSoftware.CleanTests.Sample/Utilities/Characteristics.cs @@ -1,7 +1,15 @@ namespace TryAtSoftware.CleanTests.Sample.Utilities; -public class Characteristics +public static class Characteristics { - public const string KnownPerson = "known_person"; - public const string LiteraryWorkAuthor = "literary_work_author"; + public static class Creations + { + public const string Art = "art"; + } + + public static class People + { + public const string KnownPerson = "known_person"; + public const string LiteraryWorkAuthor = "literary_work_author"; + } } \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Book.cs b/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Book.cs index 0c86cd1..0669c36 100644 --- a/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Book.cs +++ b/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Book.cs @@ -5,7 +5,7 @@ [CleanUtility(Categories.Creations, "Book")] [WithRequirements(Categories.People)] -[InternalDemands(Categories.People, Characteristics.LiteraryWorkAuthor)] +[InternalDemands(Categories.People, Characteristics.People.LiteraryWorkAuthor)] public class Book : ICreation { private readonly IPerson _author; diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Picture.cs b/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Picture.cs index 772dd3b..787d122 100644 --- a/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Picture.cs +++ b/TryAtSoftware.CleanTests.Sample/Utilities/Creations/Picture.cs @@ -1,11 +1,20 @@ namespace TryAtSoftware.CleanTests.Sample.Utilities.Creations; using TryAtSoftware.CleanTests.Core.Attributes; +using TryAtSoftware.CleanTests.Sample.Utilities.People; -[CleanUtility(Categories.Creations, "Picture")] -[ExternalDemands(Categories.People, Characteristics.KnownPerson)] +[CleanUtility(Categories.Creations, "Picture", Characteristics.Creations.Art)] +[WithRequirements(Categories.People)] +[ExternalDemands(Categories.People, Characteristics.People.KnownPerson)] public class Picture : ICreation { - public string Name => "Composition with Red, Blue and Yellow - Mondrian, Piet"; + private readonly IPerson _author; + + public Picture(IPerson author) + { + this._author = author ?? throw new ArgumentNullException(nameof(author)); + } + + public string Name => $"Composition with Red, Blue and Yellow - {this._author.LastName}, {this._author.FirstName}"; public string Type => "Picture"; } \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/People/Artist.cs b/TryAtSoftware.CleanTests.Sample/Utilities/People/Artist.cs new file mode 100644 index 0000000..6a29cf4 --- /dev/null +++ b/TryAtSoftware.CleanTests.Sample/Utilities/People/Artist.cs @@ -0,0 +1,11 @@ +namespace TryAtSoftware.CleanTests.Sample.Utilities.People; + +using TryAtSoftware.CleanTests.Core.Attributes; + +[CleanUtility(Categories.People, "Artist", Characteristics.People.KnownPerson)] +[OuterDemands(Categories.Creations, Characteristics.Creations.Art)] +public class Artist : IPerson +{ + public string FirstName => "Piet"; + public string LastName => "Mondrian"; +} \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/People/Poet.cs b/TryAtSoftware.CleanTests.Sample/Utilities/People/Poet.cs index d396a14..9bf92ef 100644 --- a/TryAtSoftware.CleanTests.Sample/Utilities/People/Poet.cs +++ b/TryAtSoftware.CleanTests.Sample/Utilities/People/Poet.cs @@ -2,7 +2,7 @@ using TryAtSoftware.CleanTests.Core.Attributes; -[CleanUtility(Categories.People, "Poet", Characteristics.KnownPerson, Characteristics.LiteraryWorkAuthor)] +[CleanUtility(Categories.People, "Poet", Characteristics.People.KnownPerson, Characteristics.People.LiteraryWorkAuthor)] public class Poet : IPerson { public string FirstName => "William"; diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/People/SoftwareDeveloper.cs b/TryAtSoftware.CleanTests.Sample/Utilities/People/SoftwareDeveloper.cs index 1c951cf..c830801 100644 --- a/TryAtSoftware.CleanTests.Sample/Utilities/People/SoftwareDeveloper.cs +++ b/TryAtSoftware.CleanTests.Sample/Utilities/People/SoftwareDeveloper.cs @@ -2,7 +2,7 @@ using TryAtSoftware.CleanTests.Core.Attributes; -[CleanUtility(Categories.People, "Software developer", Characteristics.KnownPerson)] +[CleanUtility(Categories.People, "Software developer", Characteristics.People.KnownPerson)] public class SoftwareDeveloper : IPerson { public string FirstName => "Tony"; diff --git a/TryAtSoftware.CleanTests.Sample/Utilities/People/Writer.cs b/TryAtSoftware.CleanTests.Sample/Utilities/People/Writer.cs index 0bba396..c4ae0b8 100644 --- a/TryAtSoftware.CleanTests.Sample/Utilities/People/Writer.cs +++ b/TryAtSoftware.CleanTests.Sample/Utilities/People/Writer.cs @@ -2,7 +2,7 @@ using TryAtSoftware.CleanTests.Core.Attributes; -[CleanUtility(Categories.People, "Writer", Characteristics.KnownPerson, Characteristics.LiteraryWorkAuthor)] +[CleanUtility(Categories.People, "Writer", Characteristics.People.KnownPerson, Characteristics.People.LiteraryWorkAuthor)] public class Writer : IPerson { public string FirstName => "Charles";