Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,42 @@
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);
var testComponentMocks = reflectionMocks.MockTestComponentsSuite();

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);

Expand All @@ -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()
{
Expand All @@ -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()
{
Expand All @@ -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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

public class EnvironmentSetup
{
private readonly Dictionary<string, int> _numberOfUtilitiesPerCategory = new ();
private readonly Dictionary<string, Dictionary<string, List<string>>> _externalDemandsPerUtility = new ();
private readonly Dictionary<string, Dictionary<string, List<string>>> _outerDemandsPerUtility = new ();
private readonly Dictionary<string, List<string>> _characteristics = new ();
private readonly Dictionary<string, List<string>> _requirements = new ();
private readonly Dictionary<string, int> _numberOfUtilitiesPerCategory = new();
private readonly Dictionary<string, Dictionary<string, List<string>>> _externalDemandsPerUtility = new();
private readonly Dictionary<string, Dictionary<string, List<string>>> _outerDemandsPerUtility = new();
private readonly Dictionary<string, List<string>> _characteristics = new();
private readonly Dictionary<string, List<string>> _requirements = new();

public EnvironmentSetup(string name)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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<string, List<string>>();
if (!this._externalDemandsPerUtility[universalId].ContainsKey(demandsCategory)) this._externalDemandsPerUtility[universalId][demandsCategory] = new List<string>();
Expand All @@ -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<string, List<string>>();
if (!this._outerDemandsPerUtility[universalId].ContainsKey(demandsCategory)) this._outerDemandsPerUtility[universalId][demandsCategory] = new List<string>();
Expand All @@ -94,15 +94,18 @@ internal ICleanTestInitializationCollection<ICleanUtilityDescriptor> 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);
}
}
Expand All @@ -113,7 +116,7 @@ internal ICleanTestInitializationCollection<ICleanUtilityDescriptor> 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,10 @@ public int GetCount(string category)
public bool ContainsCategory(string category) => this._data.ContainsKey(category);

/// <inheritdoc />
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);
}
/// <inheritdoc />
public void Register(string category, TValue value) => this._data[category].Add(value);

Comment thread
TonyTroeff marked this conversation as resolved.
/// <inheritdoc />
public IEnumerable<TValue> GetAllValues() => this._data.Values.SelectMany(x => x.OrEmptyIfNull().IgnoreNullValues());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,15 @@ private void ExtractDependencies(ICleanUtilityDescriptor utilityDescriptor, stri
Func<ICleanUtilityDescriptor, bool>? 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;

var dependencyGraph = this.BuildConstructionGraph(dependency.Id, usedUtilities);
if (dependencyGraph is null) continue;

dependenciesCollection.Register(requirement);
dependenciesCollection.Register(requirement, dependency);
dependencyGraphsById[dependency.Id] = dependencyGraph;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static void CopyTo<T>(this ICleanTestInitializationCollection<T> source,

foreach (var (category, values) in source)
{
target.Register(category);
foreach (var value in values) target.Register(category, value);
}
}
Expand Down Expand Up @@ -49,6 +50,8 @@ internal static ICleanTestInitializationCollection<string> ExtractDemands<TAttri
{
var demandsArgument = attribute.GetNamedArgument<IEnumerable<string>>(nameof(BaseDemandsAttribute.Demands));
var categoryArgument = attribute.GetNamedArgument<string>(nameof(BaseDemandsAttribute.Category));

demands.Register(categoryArgument);
foreach (var demand in demandsArgument) demands.Register(categoryArgument, demand);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal interface ICleanTestInitializationCollection<TValue> : IEnumerable<KeyV
IEnumerable<TValue> Get(string category);
int GetCount(string category);
bool ContainsCategory(string category);
void Register(string category);
void Register(string category, TValue value);
IEnumerable<TValue> GetAllValues();
void Clear();
Expand Down
4 changes: 3 additions & 1 deletion TryAtSoftware.CleanTests.Core/XUnit/CleanTestAssemblyData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
internal class CleanTestAssemblyData
{
private IHierarchyScanner _hierarchyScanner = new HierarchyScanner();

public ICleanTestInitializationCollection<ICleanUtilityDescriptor> CleanUtilities { get; } = new CleanTestInitializationCollection<ICleanUtilityDescriptor>();
public IDictionary<string, ICleanUtilityDescriptor> CleanUtilitiesById { get; } = new Dictionary<string, ICleanUtilityDescriptor>();

Expand All @@ -31,6 +31,8 @@ public CleanTestAssemblyData(IEnumerable<ICleanUtilityDescriptor>? 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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -122,6 +122,8 @@ private CleanTestInitializationCollection<ICleanUtilityDescriptor> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>("c");

var deserializedUtilityType = info.GetValue<Type>("ut");
var utilityName = info.GetValue<string>("un");
var isGlobal = info.GetValue<bool>("ig");

var characteristics = info.GetValue<string[]>("ch");

var deserializedExternalDemands = info.GetValue<SerializableDemand[]>("gd");
Expand Down Expand Up @@ -82,7 +82,7 @@ public void Serialize(IXunitSerializationInfo info)
private static SerializableDemand[] Serialize(ICleanTestInitializationCollection<string> demands)
{
if (demands is null) throw new ArgumentNullException(nameof(demands));

var serializableDemands = new List<SerializableDemand>();
foreach (var (category, categoryDemands) in demands) serializableDemands.AddRange(categoryDemands.Select(categoryDemand => new SerializableDemand(category, categoryDemand)));
return serializableDemands.ToArray();
Expand All @@ -92,6 +92,11 @@ private static void DeserializeDemands(IEnumerable<SerializableDemand> 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);
}
}
}
Loading