diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/CleanUtilitiesDistributionTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/CleanUtilitiesDistributionTests.cs index c9a86d9..296c3f3 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/CleanUtilitiesDistributionTests.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/CleanUtilitiesDistributionTests.cs @@ -13,10 +13,34 @@ public class CleanUtilitiesDistributionTests { private const string Category = "_"; - + + [Fact(Timeout = UnitTestConstants.Timeout)] + public async Task TestCasesShouldNotBeDiscoveredWhenDemandsFilterOutAllRequiredUtilities() + { + var reflectionMocks = ReflectionMocks.MockReflectionSuite(Assembly.GetExecutingAssembly(), typeof(TestDefiningUnfulfillableDemands)); + var testComponentMocks = reflectionMocks.MockTestComponentsSuite(); + var cleanUtilityDescriptor = new CleanUtilityDescriptor(Category, typeof(StandardUtility), "Matching utility", isGlobal: false, characteristics: ["available"]); + var assemblyData = new CleanTestAssemblyData([cleanUtilityDescriptor]); + + var testCases = await reflectionMocks.AssemblyInfo.DiscoverTestCasesAsync(assemblyData, testComponentMocks); + + Assert.Empty(testCases); + } + + [Fact(Timeout = UnitTestConstants.Timeout)] + public async Task TestCasesShouldBeDiscoveredOnceWhenNoUtilitiesAreRequired() + { + var reflectionMocks = ReflectionMocks.MockReflectionSuite(Assembly.GetExecutingAssembly(), typeof(TestConsumingNoUtilities)); + var testComponentMocks = reflectionMocks.MockTestComponentsSuite(); + var assemblyData = new CleanTestAssemblyData(); + + var testCases = await reflectionMocks.AssemblyInfo.DiscoverTestCasesAsync(assemblyData, testComponentMocks); + Assert.Single(testCases); + } + [Theory(Timeout = UnitTestConstants.Timeout)] - [InlineData(true, typeof(TestClassConsumingGlobalUtilities))] - [InlineData(false, typeof(TestClassConsumingLocalUtilities))] + [InlineData(true, typeof(TestConsumingGlobalUtilities))] + [InlineData(false, typeof(TestConsumingLocalUtilities))] public async Task UtilitiesDistributionShouldHaveProperErrorHandling(bool isGlobal, Type testClass) { var reflectionMocks = ReflectionMocks.MockReflectionSuite(Assembly.GetExecutingAssembly(), testClass); @@ -24,7 +48,7 @@ public async Task UtilitiesDistributionShouldHaveProperErrorHandling(bool isGlob var cleanUtilityDescriptor = new CleanUtilityDescriptor(Category, typeof(InconclusiveUtility), "Inconclusive utility", isGlobal); var assemblyData = new CleanTestAssemblyData(new[] { cleanUtilityDescriptor }); - + var testCases = await reflectionMocks.AssemblyInfo.DiscoverTestCasesAsync(assemblyData, testComponentMocks); var cleanTestAssemblyRunner = new CleanTestAssemblyRunner(testComponentMocks.TestAssembly, testCases, testComponentMocks.DiagnosticMessageSink, testComponentMocks.ExecutionMessageSink, testComponentMocks.TestFrameworkExecutionOptions, assemblyData); @@ -35,13 +59,17 @@ public async Task UtilitiesDistributionShouldHaveProperErrorHandling(bool isGlob Assert.Equal(1, executionResult.Total); } - private class TestClassConsumingGlobalUtilities : CleanTest + private class InconclusiveUtility(string unresolvableParameter) + { + public string UnresolvableParameter { get; } = unresolvableParameter; + } + + private class StandardUtility + { + } + + private class TestConsumingGlobalUtilities(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper) { - public TestClassConsumingGlobalUtilities(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - [CleanFact, WithRequirements(Category)] public void Test() { @@ -50,13 +78,8 @@ public void Test() } } - private class TestClassConsumingLocalUtilities : CleanTest + private class TestConsumingLocalUtilities(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper) { - public TestClassConsumingLocalUtilities(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - [CleanFact, WithRequirements(Category)] public void Test() { @@ -65,13 +88,15 @@ public void Test() } } - private class InconclusiveUtility + private class TestDefiningUnfulfillableDemands(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper) { - public InconclusiveUtility(string unresolvableParameter) - { - this.UnresolvableParameter = unresolvableParameter; - } - - public string UnresolvableParameter { get; } + [CleanFact, WithRequirements(Category), TestDemands(Category, "missing")] + public void Test() => Assert.Fail("It is not expected that this test will be executed. It is just a mean to validate correct test case discovery."); + } + + private class TestConsumingNoUtilities(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper) + { + [CleanFact] + public void Test() => Assert.Fail("It is not expected that this test will be executed. It is just a mean to validate correct test case discovery."); } } \ 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 31a1d5c..4ac76eb 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/EnvironmentSetup.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/EnvironmentSetup.cs @@ -6,11 +6,11 @@ public class EnvironmentSetup { - private readonly Dictionary _numberOfUtilitiesPerCategory = new (); - private readonly Dictionary>> _externalDemandsPerUtility = new (); - private readonly Dictionary>> _outerDemandsPerUtility = new (); - private readonly Dictionary> _characteristics = new (); - private readonly Dictionary> _requirements = new (); + private readonly Dictionary _numberOfUtilitiesPerCategory = new(); + private readonly Dictionary>> _externalDemandsPerUtility = new(); + private readonly Dictionary>> _outerDemandsPerUtility = new(); + private readonly Dictionary> _characteristics = new(); + private readonly Dictionary> _requirements = new(); public EnvironmentSetup(string name) { @@ -20,12 +20,12 @@ public EnvironmentSetup(string name) public string Name { get; } public int CategoriesCount => this._numberOfUtilitiesPerCategory.Count; - + public EnvironmentSetup WithCategory(string category, int utilitiesCount) { if (string.IsNullOrWhiteSpace(category)) throw new ArgumentNullException(nameof(category)); if (utilitiesCount <= 0) throw new ArgumentException("The number of utilities for each category must be at least 1", nameof(utilitiesCount)); - + if (!this._numberOfUtilitiesPerCategory.TryAdd(category, utilitiesCount)) throw new InvalidOperationException("Category with that name has already been registered."); return this; @@ -56,7 +56,7 @@ public EnvironmentSetup WithRequirements(string category, int utilityId, params public EnvironmentSetup WithExternalDemands(string utilityCategory, int utilityId, string demandsCategory, params string[] demands) { this.ValidateUtilityExists(utilityCategory, utilityId); - + var universalId = ComposeUniversalUtilityId(utilityCategory, utilityId); if (!this._externalDemandsPerUtility.ContainsKey(universalId)) this._externalDemandsPerUtility[universalId] = new Dictionary>(); if (!this._externalDemandsPerUtility[universalId].ContainsKey(demandsCategory)) this._externalDemandsPerUtility[universalId][demandsCategory] = new List(); @@ -68,7 +68,7 @@ public EnvironmentSetup WithExternalDemands(string utilityCategory, int utilityI 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(); @@ -94,15 +94,18 @@ internal ICleanTestInitializationCollection Materialize this._externalDemandsPerUtility.TryGetValue(universalId, out var externalDemandsByCategory); foreach (var (demandCategory, demands) in externalDemandsByCategory.OrEmptyIfNull()) { + utility.ExternalDemands.Register(demandCategory); foreach (var demand in demands) utility.ExternalDemands.Register(demandCategory, demand); } - + this._outerDemandsPerUtility.TryGetValue(universalId, out var outerDemandsByCategory); foreach (var (demandCategory, demands) in outerDemandsByCategory.OrEmptyIfNull()) { + utility.OuterDemands.Register(demandCategory); foreach (var demand in demands) utility.OuterDemands.Register(demandCategory, demand); } + utilitiesCollection.Register(category); utilitiesCollection.Register(category, utility); } } @@ -113,7 +116,7 @@ internal ICleanTestInitializationCollection Materialize private void ValidateUtilityExists(string category, int utilityId) { this.ValidateCategoryExists(category); - + var upperBound = this._numberOfUtilitiesPerCategory[category]; if (utilityId <= 0 || utilityId > upperBound) throw new ArgumentException($"Invalid utility id. It should be in the range [1, {upperBound}]", nameof(utilityId)); } diff --git a/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs b/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs index cebecf0..180a215 100644 --- a/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs +++ b/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs @@ -30,13 +30,10 @@ public int GetCount(string category) public bool ContainsCategory(string category) => this._data.ContainsKey(category); /// - public void Register(string category, TValue value) - { - if (value is null) return; + public void Register(string category) => this._data.EnsureValue(category); - var utilities = this._data.EnsureValue(category); - utilities.Add(value); - } + /// + public void Register(string category, TValue value) => this._data[category].Add(value); /// public IEnumerable GetAllValues() => this._data.Values.SelectMany(x => x.OrEmptyIfNull().IgnoreNullValues()); diff --git a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs index fd6fe8d..687f0f5 100644 --- a/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs +++ b/TryAtSoftware.CleanTests.Core/Construction/ConstructionManager.cs @@ -149,7 +149,7 @@ private void ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, stri Func? predicate = null; if (utilityDescriptor.IsGlobal) predicate = x => x.IsGlobal; var dependencies = this._cleanTestAssemblyData.CleanUtilities.Get(requirement, localDemands, predicate); - + foreach (var dependency in dependencies) { if (usedUtilities.Contains(dependency.Id)) continue; @@ -157,6 +157,7 @@ private void ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, stri var dependencyGraph = this.BuildConstructionGraph(dependency.Id, usedUtilities); if (dependencyGraph is null) continue; + dependenciesCollection.Register(requirement); dependenciesCollection.Register(requirement, dependency); dependencyGraphsById[dependency.Id] = dependencyGraph; } diff --git a/TryAtSoftware.CleanTests.Core/Extensions/CleanTestsFrameworkExtensions.cs b/TryAtSoftware.CleanTests.Core/Extensions/CleanTestsFrameworkExtensions.cs index 3df3ae5..c487832 100644 --- a/TryAtSoftware.CleanTests.Core/Extensions/CleanTestsFrameworkExtensions.cs +++ b/TryAtSoftware.CleanTests.Core/Extensions/CleanTestsFrameworkExtensions.cs @@ -20,6 +20,7 @@ public static void CopyTo(this ICleanTestInitializationCollection source, foreach (var (category, values) in source) { + target.Register(category); foreach (var value in values) target.Register(category, value); } } @@ -49,6 +50,8 @@ internal static ICleanTestInitializationCollection ExtractDemands>(nameof(BaseDemandsAttribute.Demands)); var categoryArgument = attribute.GetNamedArgument(nameof(BaseDemandsAttribute.Category)); + + demands.Register(categoryArgument); foreach (var demand in demandsArgument) demands.Register(categoryArgument, demand); } diff --git a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs index fea22c3..8224c0d 100644 --- a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs +++ b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs @@ -8,6 +8,7 @@ internal interface ICleanTestInitializationCollection : IEnumerable Get(string category); int GetCount(string category); bool ContainsCategory(string category); + void Register(string category); void Register(string category, TValue value); IEnumerable GetAllValues(); void Clear(); diff --git a/TryAtSoftware.CleanTests.Core/XUnit/CleanTestAssemblyData.cs b/TryAtSoftware.CleanTests.Core/XUnit/CleanTestAssemblyData.cs index a6f3790..c0da311 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/CleanTestAssemblyData.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/CleanTestAssemblyData.cs @@ -12,7 +12,7 @@ internal class CleanTestAssemblyData { private IHierarchyScanner _hierarchyScanner = new HierarchyScanner(); - + public ICleanTestInitializationCollection CleanUtilities { get; } = new CleanTestInitializationCollection(); public IDictionary CleanUtilitiesById { get; } = new Dictionary(); @@ -31,6 +31,8 @@ public CleanTestAssemblyData(IEnumerable? cleanUtilitie foreach (var cleanUtility in cleanUtilities.OrEmptyIfNull().IgnoreNullValues()) { if (!this.CleanUtilitiesById.TryAdd(cleanUtility.Id, cleanUtility)) throw new InvalidOperationException("Two clean utilities with the same identifier cannot co-exist."); + + this.CleanUtilities.Register(cleanUtility.Category); this.CleanUtilities.Register(cleanUtility.Category, cleanUtility); } } diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs index 0f14f23..aa79f81 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Discovery/CleanTestFrameworkDiscoverer.cs @@ -34,7 +34,7 @@ public CleanTestFrameworkDiscoverer(IAssemblyInfo assemblyInfo, ISourceInformati protected override ITestClass CreateTestClass(ITypeInfo @class) { var collection = this._fallbackTestFrameworkDiscoverer.TestCollectionFactory.Get(@class); - + if (@class.IsCleanTest() && @class.IsGenericType) { var runtimeClass = @class.ToRuntimeType(); @@ -54,9 +54,9 @@ protected override ITestClass CreateTestClass(ITypeInfo @class) @class = Reflector.Wrap(runtimeClass); } - + var wrappedXUnitTypeInfo = new CleanTestReflectionTypeInfoWrapper(@class); - + // @class.Name -> Fully qualified type name // The subsequently created wrapper's `Name` property should expose a readable value for generic types. return new CleanTestClassWrapper(collection, wrappedXUnitTypeInfo, @class.Name); @@ -122,6 +122,8 @@ private CleanTestInitializationCollection GetInitializa var allRequirementSources = new[] { initializationRequirements, globalRequirements }; foreach (var category in allRequirementSources.Union()) { + customInitializationUtilitiesCollection.Register(category); + var categoryDemands = demands.Get(category); foreach (var initializationUtility in this._cleanTestAssemblyData.CleanUtilities.Get(category, categoryDemands)) customInitializationUtilitiesCollection.Register(category, initializationUtility); } diff --git a/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs b/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs index 3424fc8..c149111 100644 --- a/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs +++ b/TryAtSoftware.CleanTests.Core/XUnit/Serialization/SerializableInitializationUtility.cs @@ -34,13 +34,13 @@ public SerializableInitializationUtility(ICleanUtilityDescriptor cleanUtilityDes public void Deserialize(IXunitSerializationInfo info) { if (info is null) throw new ArgumentNullException(nameof(info)); - + var initializationCategory = info.GetValue("c"); var deserializedUtilityType = info.GetValue("ut"); var utilityName = info.GetValue("un"); var isGlobal = info.GetValue("ig"); - + var characteristics = info.GetValue("ch"); var deserializedExternalDemands = info.GetValue("gd"); @@ -82,7 +82,7 @@ public void Serialize(IXunitSerializationInfo info) private static SerializableDemand[] Serialize(ICleanTestInitializationCollection demands) { if (demands is null) throw new ArgumentNullException(nameof(demands)); - + var serializableDemands = new List(); foreach (var (category, categoryDemands) in demands) serializableDemands.AddRange(categoryDemands.Select(categoryDemand => new SerializableDemand(category, categoryDemand))); return serializableDemands.ToArray(); @@ -92,6 +92,11 @@ private static void DeserializeDemands(IEnumerable deseriali { if (deserializedGlobalDemands is null) throw new ArgumentNullException(nameof(deserializedGlobalDemands)); if (demandsCollection is null) throw new ArgumentNullException(nameof(demandsCollection)); - foreach (var deserializedDemand in deserializedGlobalDemands) demandsCollection.Register(deserializedDemand.InitializationCategory, deserializedDemand.Demand); + + foreach (var deserializedDemand in deserializedGlobalDemands) + { + demandsCollection.Register(deserializedDemand.InitializationCategory); + demandsCollection.Register(deserializedDemand.InitializationCategory, deserializedDemand.Demand); + } } } \ No newline at end of file