From 309e4f58a3e15464917433dbfcc9f642c3b383c3 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Tue, 1 Aug 2023 22:48:55 +0300 Subject: [PATCH 01/15] Added the `Outer demands` attribute. --- .../Attributes/OuterDemandsAttribute.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 TryAtSoftware.CleanTests.Core/Attributes/OuterDemandsAttribute.cs 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 From 98240257c5baf3b1f53443b378fafa39acd9d07c Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Tue, 1 Aug 2023 22:52:24 +0300 Subject: [PATCH 02/15] Added the `Outer demands` property to the clean utility descriptor. --- TryAtSoftware.CleanTests.Core/CleanUtilityDescriptor.cs | 3 +++ .../Interfaces/ICleanUtilityDescriptor.cs | 5 +++++ 2 files changed, 8 insertions(+) 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/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. From d5cd5f5315f4efb6027ff4f09458fc53c7cb3e2f Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Tue, 1 Aug 2023 22:52:39 +0300 Subject: [PATCH 03/15] Outer demands are successfully set for each clean utility descriptor instance --- TryAtSoftware.CleanTests.Core/XUnit/CleanTestFramework.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); } From 8000e243fb876ea1eeb5371b99b5a6d2bbbbef20 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Tue, 1 Aug 2023 22:54:13 +0300 Subject: [PATCH 04/15] Outer demands should be successfully serialized and deserialized along with the clean utility descriptor. --- .../SerializableInitializationUtility.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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); From 7a060fc2b0db8f10a19aa6cdd818a5f533758e91 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Sat, 5 Aug 2023 22:26:55 +0300 Subject: [PATCH 05/15] Added a function that should copy full construction graph instances. --- .../Construction/ConstructionManager.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index d031ec3..5f770c9 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -77,6 +77,7 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr var graphIterator = new CombinatorialMachine(dependenciesCollection); var dependenciesVariations = graphIterator.GenerateAllCombinations(); + foreach (var variation in dependenciesVariations) { var variationDependenciesConstructionGraphs = variation.Values.Select(x => dependencyGraphsById[x]).ToList(); @@ -95,6 +96,24 @@ private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor ut return this._cleanTestAssemblyData.CleanUtilities.Get(requirement, localDemands, predicate); } + private static FullCleanUtilityConstructionGraph Copy(FullCleanUtilityConstructionGraph graph) + { + var graphCopy = new FullCleanUtilityConstructionGraph(graph.Id); + foreach (var descriptor in graph.ConstructionDescriptors) + { + var descriptorCopy = new List(capacity: descriptor.Count); + foreach (var dependencyGraph in descriptor) + { + var dependencyGraphCopy = Copy(dependencyGraph); + descriptorCopy.Add(dependencyGraphCopy); + } + + graphCopy.ConstructionDescriptors.Add(descriptorCopy); + } + + return graphCopy; + } + /// /// Use this method to transform a to a collection of instances. /// From 6e167297ce33a87c8806060faff7c6dbb08c0894 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Sat, 5 Aug 2023 23:11:46 +0300 Subject: [PATCH 06/15] Added a simple function that should help normalizing the full construction graph in terms of the outer demands that are applied. Integrated it within the `BuildConstructionGraph` method --- .../ConstructionManagerBenchmark.cs | 1 - .../DependenciesManagerTests.cs | 1 - .../CleanTestInitializationCollection.cs | 3 + .../Construction/ConstructionGraphs.cs | 4 +- .../Construction/ConstructionManager.cs | 89 ++++++++++++++----- .../Extensions/ConstructionGraphExtensions.cs | 20 +++++ .../ICleanTestInitializationCollection.cs | 1 + .../Interfaces/IConstructionManager.cs | 2 +- .../XUnit/CleanTestCaseData.cs | 2 +- .../XUnit/ConstructionCache.cs | 2 +- .../XUnit/Discovery/BaseTestCaseDiscoverer.cs | 2 +- .../Discovery/CleanTestFrameworkDiscoverer.cs | 1 - .../Execution/CleanTestCollectionRunner.cs | 2 +- .../XUnit/Execution/CleanTestInvoker.cs | 2 +- .../Extensions/XUnitFrameworkExtensions.cs | 2 +- .../SerializableIndividualDependencyNode.cs | 2 +- 16 files changed, 100 insertions(+), 36 deletions(-) create mode 100644 TryAtSoftware.CleanTests.Core/Extensions/ConstructionGraphExtensions.cs diff --git a/Tests/TryAtSoftware.CleanTests.Benchmark/ConstructionManagerBenchmark.cs b/Tests/TryAtSoftware.CleanTests.Benchmark/ConstructionManagerBenchmark.cs index 25fd6c0..2236037 100644 --- a/Tests/TryAtSoftware.CleanTests.Benchmark/ConstructionManagerBenchmark.cs +++ b/Tests/TryAtSoftware.CleanTests.Benchmark/ConstructionManagerBenchmark.cs @@ -4,7 +4,6 @@ using System.Linq; using BenchmarkDotNet.Attributes; using TryAtSoftware.CleanTests.Core.Construction; -using TryAtSoftware.CleanTests.Core.Dependencies; using TryAtSoftware.CleanTests.Core.XUnit; using TryAtSoftware.CleanTests.UnitTests.Extensions; using TryAtSoftware.CleanTests.UnitTests.Parametrization; diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs index ca120ca..db14a52 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs @@ -2,7 +2,6 @@ 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; 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/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 5f770c9..2499b69 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; @@ -75,43 +74,87 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr usedUtilities.Remove(utilityId); - var graphIterator = new CombinatorialMachine(dependenciesCollection); - var dependenciesVariations = graphIterator.GenerateAllCombinations(); + var combinatorialMachine = new CombinatorialMachine(dependenciesCollection); + var dependenciesCombinations = combinatorialMachine.GenerateAllCombinations(); - foreach (var variation in dependenciesVariations) + var constructionGraphsPerCombination = new FullCleanUtilityConstructionGraph[utility.InternalRequirements.Count]; + var utilitiesByCategoryPerCombination = new Dictionary(); + foreach (var combination in dependenciesCombinations) { - var variationDependenciesConstructionGraphs = variation.Values.Select(x => dependencyGraphsById[x]).ToList(); - graph.ConstructionDescriptors.Add(variationDependenciesConstructionGraphs); + var index = 0; + var outerDemandsArePresent = false; + foreach (var dependencyId in combination.Values) + { + constructionGraphsPerCombination[index] = dependencyGraphsById[dependencyId]; + + var dependency = this._cleanTestAssemblyData.CleanUtilitiesById[dependencyId]; + utilitiesByCategoryPerCombination[dependency.Category] = dependency; + + outerDemandsArePresent = outerDemandsArePresent || dependency.OuterDemands.Categories.Count > 0; + index++; + } + + FullCleanUtilityConstructionGraph[]? dependenciesConstructionGraphs; + if (outerDemandsArePresent) dependenciesConstructionGraphs = this.NormalizeDependenciesConstructionGraphs(constructionGraphsPerCombination, utilitiesByCategoryPerCombination); + else + { + dependenciesConstructionGraphs = new FullCleanUtilityConstructionGraph[constructionGraphsPerCombination.Length]; + constructionGraphsPerCombination.CopyTo(dependenciesConstructionGraphs.AsSpan()); + } + + if (dependenciesConstructionGraphs is not null) graph.ConstructionDescriptors.Add(dependenciesConstructionGraphs); } return graph; } - private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, string requirement) + private FullCleanUtilityConstructionGraph[]? NormalizeDependenciesConstructionGraphs(FullCleanUtilityConstructionGraph[] constructionGraphs, IDictionary outerUtilitiesByCategory) { - var localDemands = utilityDescriptor.InternalDemands.Get(requirement); + var result = new FullCleanUtilityConstructionGraph[constructionGraphs.Length]; + + 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); + } + + if (!useAny) return null; + } + + result[i] = graphCopy; + } - Func? predicate = null; - if (utilityDescriptor.IsGlobal) predicate = x => x.IsGlobal; - return this._cleanTestAssemblyData.CleanUtilities.Get(requirement, localDemands, predicate); + return result; } - private static FullCleanUtilityConstructionGraph Copy(FullCleanUtilityConstructionGraph graph) + private bool OuterDemandsAreFulfilled(IEnumerable constructionDescriptor, IDictionary outerUtilitiesByCategory) { - var graphCopy = new FullCleanUtilityConstructionGraph(graph.Id); - foreach (var descriptor in graph.ConstructionDescriptors) + foreach (var dependencyConstructionGraph in constructionDescriptor) { - var descriptorCopy = new List(capacity: descriptor.Count); - foreach (var dependencyGraph in descriptor) + var dependencyUtility = this._cleanTestAssemblyData.CleanUtilitiesById[dependencyConstructionGraph.Id]; + foreach (var (category, demands) in dependencyUtility.OuterDemands) { - var dependencyGraphCopy = Copy(dependencyGraph); - descriptorCopy.Add(dependencyGraphCopy); + if (outerUtilitiesByCategory.TryGetValue(category, out var outerUtilityForCategory) && !outerUtilityForCategory.FulfillsAllDemands(demands)) + return false; } - - graphCopy.ConstructionDescriptors.Add(descriptorCopy); } - return graphCopy; + return true; + } + + private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, string requirement) + { + var localDemands = utilityDescriptor.InternalDemands.Get(requirement); + + Func? predicate = null; + if (utilityDescriptor.IsGlobal) predicate = x => x.IsGlobal; + return this._cleanTestAssemblyData.CleanUtilities.Get(requirement, localDemands, predicate); } /// @@ -164,8 +207,8 @@ 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++) + 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/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/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/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..a05586f 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; 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; From 5b31c845a902384b7fd4bd01a7b2a9aaad1adbeb Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Sat, 5 Aug 2023 23:35:31 +0300 Subject: [PATCH 07/15] Simplified the logic for normalizing the construction graphs of the dependencies. --- .../Construction/ConstructionManager.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index 2499b69..7a3e568 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -82,7 +82,6 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr foreach (var combination in dependenciesCombinations) { var index = 0; - var outerDemandsArePresent = false; foreach (var dependencyId in combination.Values) { constructionGraphsPerCombination[index] = dependencyGraphsById[dependencyId]; @@ -90,18 +89,10 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr var dependency = this._cleanTestAssemblyData.CleanUtilitiesById[dependencyId]; utilitiesByCategoryPerCombination[dependency.Category] = dependency; - outerDemandsArePresent = outerDemandsArePresent || dependency.OuterDemands.Categories.Count > 0; index++; } - FullCleanUtilityConstructionGraph[]? dependenciesConstructionGraphs; - if (outerDemandsArePresent) dependenciesConstructionGraphs = this.NormalizeDependenciesConstructionGraphs(constructionGraphsPerCombination, utilitiesByCategoryPerCombination); - else - { - dependenciesConstructionGraphs = new FullCleanUtilityConstructionGraph[constructionGraphsPerCombination.Length]; - constructionGraphsPerCombination.CopyTo(dependenciesConstructionGraphs.AsSpan()); - } - + var dependenciesConstructionGraphs = this.NormalizeDependenciesConstructionGraphs(constructionGraphsPerCombination, utilitiesByCategoryPerCombination); if (dependenciesConstructionGraphs is not null) graph.ConstructionDescriptors.Add(dependenciesConstructionGraphs); } From 6b9298fea7ae152f483e75a9725e66919b49da78 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 08:49:30 +0300 Subject: [PATCH 08/15] Updating some packages. Trying to fix code smells. --- .../Calculator.CleanTests.csproj | 4 +-- .../JobAgency.CleanTests.csproj | 2 +- .../JobAgency.Data/JobAgency.Data.csproj | 2 +- .../TryAtSoftware.CleanTests.Benchmark.csproj | 2 +- .../TryAtSoftware.CleanTests.UnitTests.csproj | 2 +- .../Construction/ConstructionManager.cs | 31 +++++++++++++------ .../TryAtSoftware.CleanTests.Core.csproj | 2 +- .../TryAtSoftware.CleanTests.Sample.csproj | 2 +- 8 files changed, 29 insertions(+), 18 deletions(-) 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/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/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index 7a3e568..08294cf 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -81,16 +81,8 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr var utilitiesByCategoryPerCombination = new Dictionary(); foreach (var combination in dependenciesCombinations) { - var index = 0; - foreach (var dependencyId in combination.Values) - { - constructionGraphsPerCombination[index] = dependencyGraphsById[dependencyId]; - - var dependency = this._cleanTestAssemblyData.CleanUtilitiesById[dependencyId]; - utilitiesByCategoryPerCombination[dependency.Category] = dependency; - - index++; - } + this.ExtractUtilitiesByCategory(combination.Values, utilitiesByCategoryPerCombination); + this.ExtractConstructionGraphs(combination.Values, dependencyGraphsById, constructionGraphsPerCombination); var dependenciesConstructionGraphs = this.NormalizeDependenciesConstructionGraphs(constructionGraphsPerCombination, utilitiesByCategoryPerCombination); if (dependenciesConstructionGraphs is not null) graph.ConstructionDescriptors.Add(dependenciesConstructionGraphs); @@ -139,6 +131,25 @@ private bool OuterDemandsAreFulfilled(IEnumerable utilityIds, IDictionary destination) + { + foreach (var utilityId in utilityIds) + { + var utility = this._cleanTestAssemblyData.CleanUtilitiesById[utilityId]; + destination[utility.Category] = utility; + } + } + + private void ExtractConstructionGraphs(IEnumerable utilityIds, IDictionary constructionGraphsById, FullCleanUtilityConstructionGraph[] destination) + { + var index = 0; + foreach (var utilityId in utilityIds) + { + destination[index] = constructionGraphsById[utilityId]; + index++; + } + } + private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, string requirement) { var localDemands = utilityDescriptor.InternalDemands.Get(requirement); 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.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 From 1e20a6808d37b2459f8e2dfc1af0c463abfcf9db Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 20:34:22 +0300 Subject: [PATCH 09/15] Finalizing the outer demands implementation --- .../Construction/ConstructionManager.cs | 37 ++++++++++++------- .../XUnit/Discovery/BaseTestCaseDiscoverer.cs | 6 +-- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index 08294cf..b8d6e61 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -22,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); } @@ -79,10 +82,10 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr var constructionGraphsPerCombination = new FullCleanUtilityConstructionGraph[utility.InternalRequirements.Count]; var utilitiesByCategoryPerCombination = new Dictionary(); - foreach (var combination in dependenciesCombinations) + foreach (var combination in dependenciesCombinations.Select(x => x.Values)) { - this.ExtractUtilitiesByCategory(combination.Values, utilitiesByCategoryPerCombination); - this.ExtractConstructionGraphs(combination.Values, dependencyGraphsById, constructionGraphsPerCombination); + this.ExtractUtilitiesByCategory(combination, utilitiesByCategoryPerCombination); + ExtractConstructionGraphs(combination, dependencyGraphsById, constructionGraphsPerCombination); var dependenciesConstructionGraphs = this.NormalizeDependenciesConstructionGraphs(constructionGraphsPerCombination, utilitiesByCategoryPerCombination); if (dependenciesConstructionGraphs is not null) graph.ConstructionDescriptors.Add(dependenciesConstructionGraphs); @@ -94,7 +97,7 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr private FullCleanUtilityConstructionGraph[]? NormalizeDependenciesConstructionGraphs(FullCleanUtilityConstructionGraph[] constructionGraphs, IDictionary outerUtilitiesByCategory) { var result = new FullCleanUtilityConstructionGraph[constructionGraphs.Length]; - + for (var i = 0; i < constructionGraphs.Length; i++) { var graphCopy = constructionGraphs[i].Copy(); @@ -109,7 +112,7 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr if (!useAny) return null; } - + result[i] = graphCopy; } @@ -134,13 +137,16 @@ private bool OuterDemandsAreFulfilled(IEnumerable utilityIds, IDictionary destination) { foreach (var utilityId in utilityIds) - { - var utility = this._cleanTestAssemblyData.CleanUtilitiesById[utilityId]; - destination[utility.Category] = utility; - } + this.ExtractUtilityByCategory(utilityId, destination); } - private void ExtractConstructionGraphs(IEnumerable utilityIds, IDictionary constructionGraphsById, FullCleanUtilityConstructionGraph[] destination) + 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) @@ -163,6 +169,7 @@ private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor ut /// 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: @@ -198,7 +205,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) { @@ -209,6 +216,8 @@ private static IndividualCleanUtilityConstructionGraph[] FlattenConstructionGrap var ans = new List(); foreach (var constructionDescriptor in constructionGraph.ConstructionDescriptors) { + 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]); diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs index a05586f..9136f77 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/BaseTestCaseDiscoverer.cs @@ -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) { From 75fbed51232615b822f95e8c81df8b30a2e48d14 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 21:18:39 +0300 Subject: [PATCH 10/15] Started writing some simple tests covering the new functionality --- .../ConstructionManagerTests.cs | 85 +++++++++++++++++++ .../DependenciesManagerTests.cs | 33 ------- .../Parametrization/EnvironmentSetup.cs | 33 +++++-- .../Parametrization/TestParameters.cs | 14 +-- 4 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs delete mode 100644 Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs new file mode 100644 index 0000000..59d2aad --- /dev/null +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs @@ -0,0 +1,85 @@ +namespace TryAtSoftware.CleanTests.UnitTests; + +using System.Text.Json; +using TryAtSoftware.CleanTests.Core.Construction; +using TryAtSoftware.CleanTests.Core.Interfaces; +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); + } + } + + 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 db14a52..0000000 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/DependenciesManagerTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -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; - -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); } From 1a127bd9bff32c1c62ba8a37aa8eb16813383b4d Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 21:40:41 +0300 Subject: [PATCH 11/15] Wrote one more test covering that outer utilities are successfully applied at non-root levels as well --- .../ConstructionManagerTests.cs | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs index 59d2aad..c00c122 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs @@ -2,7 +2,6 @@ using System.Text.Json; using TryAtSoftware.CleanTests.Core.Construction; -using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.CleanTests.UnitTests.Constants; using TryAtSoftware.CleanTests.UnitTests.Extensions; using TryAtSoftware.CleanTests.UnitTests.Parametrization; @@ -79,6 +78,64 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtRootLevel() } } + [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); + } + } + public static IEnumerable GetDependenciesManagerSetups() => TestParameters.ConstructObservableConstructionManagerSetups() .Select(dependenciesManagerSetup => new object[] { dependenciesManagerSetup.EnvironmentSetup, dependenciesManagerSetup.PathToExpectedResult }); From 8a4db0c3eefac2f5d15360d280f6dca6d07f1d3e Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 22:43:53 +0300 Subject: [PATCH 12/15] Experimenting with the samples. --- .../SampleCleanTest.cs | 2 +- .../Utilities/Characteristics.cs | 14 +++++++++++--- .../Utilities/Creations/Book.cs | 2 +- .../Utilities/Creations/Picture.cs | 15 ++++++++++++--- .../Utilities/People/Artist.cs | 11 +++++++++++ .../Utilities/People/Poet.cs | 2 +- .../Utilities/People/SoftwareDeveloper.cs | 2 +- .../Utilities/People/Writer.cs | 2 +- 8 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 TryAtSoftware.CleanTests.Sample/Utilities/People/Artist.cs 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/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"; From 014b09196a9361eb965e9ddab831445023179447 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 22:55:55 +0300 Subject: [PATCH 13/15] Added tests covering what happens when none of the outer demands can be satisified. Applid a minor fix supporting the expected behavior. --- .../ConstructionManagerTests.cs | 80 ++++++++++++++++--- .../Construction/ConstructionManager.cs | 3 + 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs index c00c122..8073fc9 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs @@ -42,13 +42,13 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtRootLevel() 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); @@ -59,10 +59,10 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtRootLevel() { 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); @@ -73,11 +73,43 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtRootLevel() 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() { @@ -92,13 +124,13 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtDeeperLevels() 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); @@ -110,12 +142,12 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtDeeperLevels() 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); @@ -135,8 +167,32 @@ public void OuterDemandsShouldBeAppliedCorrectlyAtDeeperLevels() 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 }); + public static IEnumerable GetDependenciesManagerSetups() => TestParameters.ConstructObservableConstructionManagerSetups().Select(dependenciesManagerSetup => new object[] { dependenciesManagerSetup.EnvironmentSetup, dependenciesManagerSetup.PathToExpectedResult }); } \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index b8d6e61..c37ede3 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -72,6 +72,7 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr dependencyGraphsById[dependency.Id] = dependencyGraph; } + // 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; } @@ -91,6 +92,8 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr 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; } From 94046fc2398d1ef6b9c30300089c7cfbd8920216 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Mon, 7 Aug 2023 23:04:04 +0300 Subject: [PATCH 14/15] Trying to reduce cognitive complexity within the construction manager. --- .../Construction/ConstructionManager.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index c37ede3..e9b5ee7 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -59,18 +59,7 @@ public IndividualCleanUtilityConstructionGraph[][] BuildIndividualConstructionGr var dependencyGraphsById = new Dictionary(); foreach (var requirement in utility.InternalRequirements) { - var currentDependencies = this.ExtractDependencies(utility, requirement); - - foreach (var dependency in currentDependencies) - { - 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; - } + this.ExtractDependencies(utility, requirement, usedUtilities, dependenciesCollection, dependencyGraphsById); // 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; @@ -159,13 +148,24 @@ private static void ExtractConstructionGraphs(IEnumerable utilityIds, ID } } - private ICleanUtilityDescriptor[] ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, string requirement) + 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; + } } /// From 5964761f9f0152462b60291849bcc726f46bad80 Mon Sep 17 00:00:00 2001 From: Tony Troeff Date: Tue, 8 Aug 2023 07:46:35 +0300 Subject: [PATCH 15/15] Made imporvements to the documentation. Added some basic information about the outer demands. --- README.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) 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`.