Skip to content
Open
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 @@ -12,14 +12,14 @@

public class CleanUtilitiesDistributionTests
{
private const string Category = "_";
private const string DefaultCategory = "_";

[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 cleanUtilityDescriptor = new CleanUtilityDescriptor(DefaultCategory, typeof(StandardUtility), "Matching utility", isGlobal: false, characteristics: ["available"]);
var assemblyData = new CleanTestAssemblyData([cleanUtilityDescriptor]);

var testCases = await reflectionMocks.AssemblyInfo.DiscoverTestCasesAsync(assemblyData, testComponentMocks);
Expand All @@ -28,7 +28,7 @@ public async Task TestCasesShouldNotBeDiscoveredWhenDemandsFilterOutAllRequiredU
}

[Fact(Timeout = UnitTestConstants.Timeout)]
public async Task TestCasesShouldBeDiscoveredOnceWhenNoUtilitiesAreRequired()
public async Task TestCasesShouldBeDiscoveredEvenIfTheyDoNotRequireAnyUtilities()
{
var reflectionMocks = ReflectionMocks.MockReflectionSuite(Assembly.GetExecutingAssembly(), typeof(TestConsumingNoUtilities));
var testComponentMocks = reflectionMocks.MockTestComponentsSuite();
Expand All @@ -46,7 +46,7 @@ public async Task UtilitiesDistributionShouldHaveProperErrorHandling(bool isGlob
var reflectionMocks = ReflectionMocks.MockReflectionSuite(Assembly.GetExecutingAssembly(), testClass);
var testComponentMocks = reflectionMocks.MockTestComponentsSuite();

var cleanUtilityDescriptor = new CleanUtilityDescriptor(Category, typeof(InconclusiveUtility), "Inconclusive utility", isGlobal);
var cleanUtilityDescriptor = new CleanUtilityDescriptor(DefaultCategory, typeof(InconclusiveUtility), "Inconclusive utility", isGlobal);
var assemblyData = new CleanTestAssemblyData(new[] { cleanUtilityDescriptor });

var testCases = await reflectionMocks.AssemblyInfo.DiscoverTestCasesAsync(assemblyData, testComponentMocks);
Expand All @@ -59,6 +59,33 @@ public async Task UtilitiesDistributionShouldHaveProperErrorHandling(bool isGlob
Assert.Equal(1, executionResult.Total);
}

[Fact(Timeout = UnitTestConstants.Timeout)]
public async Task FrameworkDiscoveryShouldProduceATestCaseForEachMatchingUtilityAttribute()
{
var typesMap = new Dictionary<string, IReflectionTypeInfo>();
var assemblyInfo = Assembly.GetExecutingAssembly().MockReflectionAssemblyInfo(typesMap);

var cleanTestTypeInfo = typeof(TestConsumingMultiUtilities).MockReflectionTypeInfo(assemblyInfo);
var utilityTypeInfo = typeof(MultiUseCleanUtility).MockReflectionTypeInfo(assemblyInfo);

typesMap[typeof(TestConsumingMultiUtilities).AssemblyQualifiedName!] = cleanTestTypeInfo;
typesMap[typeof(MultiUseCleanUtility).AssemblyQualifiedName!] = utilityTypeInfo;

var reflectionMocks = new ReflectionMocksSuite(assemblyInfo, cleanTestTypeInfo);
var testComponentMocks = reflectionMocks.MockTestComponentsSuite();

var messageSink = testComponentMocks.DiagnosticMessageSink;
var framework = new CleanTestFramework(messageSink);
var discoverer = framework.GetDiscoverer(assemblyInfo);

var testCases = await discoverer.DiscoverTestCasesAsync(testComponentMocks);
var testCase = Assert.IsType<CleanTestCase>(Assert.Single(testCases));

Assert.Equal(2, testCase.CleanTestCaseData.CleanUtilities.Count);
Assert.Single(testCase.CleanTestCaseData.CleanUtilities, x => x.Id == "c:Multi-Category #1|n:Utility A");
Assert.Single(testCase.CleanTestCaseData.CleanUtilities, x => x.Id == "c:Multi-Category #2|n:Utility A");
}

private class InconclusiveUtility(string unresolvableParameter)
{
public string UnresolvableParameter { get; } = unresolvableParameter;
Expand All @@ -68,9 +95,15 @@ private class StandardUtility
{
}

[CleanUtility("Multi-Category #1", "Utility A")]
[CleanUtility("Multi-Category #2", "Utility A")]
private class MultiUseCleanUtility
{
}

private class TestConsumingGlobalUtilities(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper)
{
[CleanFact, WithRequirements(Category)]
[CleanFact, WithRequirements(DefaultCategory)]
public void Test()
{
_ = this.GetGlobalService<InconclusiveUtility>();
Expand All @@ -80,7 +113,7 @@ public void Test()

private class TestConsumingLocalUtilities(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper)
{
[CleanFact, WithRequirements(Category)]
[CleanFact, WithRequirements(DefaultCategory)]
public void Test()
{
_ = this.GetService<InconclusiveUtility>();
Expand All @@ -90,7 +123,7 @@ public void Test()

private class TestDefiningUnfulfillableDemands(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper)
{
[CleanFact, WithRequirements(Category), TestDemands(Category, "missing")]
[CleanFact, WithRequirements(DefaultCategory), TestDemands(DefaultCategory, "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.");
}

Expand All @@ -99,4 +132,11 @@ private class TestConsumingNoUtilities(ITestOutputHelper testOutputHelper) : Cle
[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.");
}

private class TestConsumingMultiUtilities(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper)
{
[CleanFact]
[WithRequirements("Multi-Category #1", "Multi-Category #2")]
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 @@ -11,17 +11,26 @@ internal static class DiscoveryExtensions
{
private const int DelayBetweenDiscoveryRetries = 50;
private const int MaxDiscoveryRetries = 20;
internal static async Task<IEnumerable<IXunitTestCase>> DiscoverTestCasesAsync(this IAssemblyInfo assembly, CleanTestAssemblyData assemblyData, TestComponentMocksSuite testComponentMocks)

internal static Task<IEnumerable<IXunitTestCase>> DiscoverTestCasesAsync(this IAssemblyInfo assembly, CleanTestAssemblyData assemblyData, TestComponentMocksSuite testComponentMocks)
{
Assert.NotNull(assembly);
Assert.NotNull(assemblyData);
Assert.NotNull(testComponentMocks);


var testFrameworkDiscoverer = new CleanTestFrameworkDiscoverer(assembly, testComponentMocks.SourceInformationProvider, testComponentMocks.DiagnosticMessageSink, assemblyData);
return testFrameworkDiscoverer.DiscoverTestCasesAsync(testComponentMocks);
}

internal static async Task<IEnumerable<IXunitTestCase>> DiscoverTestCasesAsync(this ITestFrameworkDiscoverer testFrameworkDiscoverer, TestComponentMocksSuite testComponentMocks)
{
Assert.NotNull(testFrameworkDiscoverer);
Assert.NotNull(testComponentMocks);

var discoveryIsOver = false;
var discoveredTestCases = new List<IXunitTestCase>();
var discoveryMessageSink = Substitute.For<IMessageSink>();

var discoveryMessageSink = TestComponentMocks.MockMessageSink();
discoveryMessageSink.OnMessage(Arg.Any<IMessageSinkMessage>()).Returns(true);
discoveryMessageSink.OnMessage(Arg.Any<ITestCaseDiscoveryMessage>()).Returns(true)
.AndDoes(x =>
Expand All @@ -32,7 +41,6 @@ internal static async Task<IEnumerable<IXunitTestCase>> DiscoverTestCasesAsync(t
discoveryMessageSink.OnMessage(Arg.Any<IDiscoveryCompleteMessage>()).Returns(true)
.AndDoes(_ => discoveryIsOver = true);

var testFrameworkDiscoverer = new CleanTestFrameworkDiscoverer(assembly, testComponentMocks.SourceInformationProvider, testComponentMocks.DiagnosticMessageSink, assemblyData);
testFrameworkDiscoverer.Find(includeSourceInformation: true, discoveryMessageSink, testComponentMocks.TestFrameworkDiscoveryOptions);

var retryId = 0;
Expand All @@ -41,7 +49,7 @@ internal static async Task<IEnumerable<IXunitTestCase>> DiscoverTestCasesAsync(t
await Task.Delay(DelayBetweenDiscoveryRetries);
retryId++;
}

Assert.True(discoveryIsOver, $"The discovery process did not finish in time after {MaxDiscoveryRetries} retries.");
return discoveredTestCases;
}
Expand Down
22 changes: 6 additions & 16 deletions Tests/TryAtSoftware.CleanTests.UnitTests/GenericAutomationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async Task TestCasesShouldFailForIncompleteGenericTestClasses()
var testCases = await reflectionMocks.AssemblyInfo.DiscoverTestCasesAsync(assemblyData, testComponentMocks);
Assert.Empty(testCases);
}

[Fact(Timeout = UnitTestConstants.Timeout)]
public async Task TestCasesShouldPassForCompleteGenericTestClasses()
{
Expand All @@ -40,14 +40,9 @@ public async Task TestCasesShouldPassForCompleteGenericTestClasses()
Assert.Equal(4, executionResult.Total);
}

private class IncompleteGenericTestClass<T> : CleanTest
private class IncompleteGenericTestClass<T>(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper)
where T : new()
{
public IncompleteGenericTestClass(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}

[CleanFact]
public static void Test() => Assert.NotNull(new T());
}
Expand All @@ -56,21 +51,16 @@ public IncompleteGenericTestClass(ITestOutputHelper testOutputHelper)
private class TestGenericParameterAttribute : Attribute
{
}
// To be removed when upgrading `TryAtSoftware.Extensions.Reflection`.

// To be removed when upgrading `TryAtSoftware.Extensions.Reflection`.
#nullable disable
[TestSuiteGenericTypeMapping(typeof(TestGenericParameterAttribute), typeof(object))]
private class CompleteGenericTestClass<[TestGenericParameter] T> : CleanTest
private class CompleteGenericTestClass<[TestGenericParameter] T>(ITestOutputHelper testOutputHelper) : CleanTest(testOutputHelper)
where T : new()
{
public CompleteGenericTestClass(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}

[Fact]
public static void Fact() => Assert.NotNull(new T());

[Theory]
[MemberData(nameof(GetTheoryData))]
public static void Theory(int iterations)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Collections.Generic;
using TryAtSoftware.Extensions.Collections;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
Comment thread
TonyTroeff marked this conversation as resolved.
Comment thread
TonyTroeff marked this conversation as resolved.
public class CleanUtilityAttribute : Attribute
{
public string Name { get; }
Expand Down
28 changes: 14 additions & 14 deletions TryAtSoftware.CleanTests.Core/XUnit/CleanTestFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

public class CleanTestFramework(IMessageSink messageSink) : XunitTestFramework(messageSink)
{
private readonly Dictionary<string, CleanTestAssemblyData> _utilityDescriptorsByAssembly = new ();
private readonly Dictionary<string, CleanTestAssemblyData> _utilityDescriptorsByAssembly = new();

protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
{
Expand All @@ -35,9 +35,9 @@ protected override ITestFrameworkDiscoverer CreateDiscoverer(IAssemblyInfo assem
private CleanTestAssemblyData ExtractAssemblyData(IAssemblyInfo assemblyInfo)
{
if (this._utilityDescriptorsByAssembly.TryGetValue(assemblyInfo.Name, out var memoizedResult)) return memoizedResult;

var utilitiesCollection = new List<ICleanUtilityDescriptor>();

RegisterUtilitiesFromAssembly(assemblyInfo, utilitiesCollection);
var sharedUtilitiesAttributes = assemblyInfo.GetCustomAttributes(typeof(SharesUtilitiesWithAttribute));
foreach (var sharedUtilitiesAttribute in sharedUtilitiesAttributes)
Expand All @@ -48,47 +48,47 @@ private CleanTestAssemblyData ExtractAssemblyData(IAssemblyInfo assemblyInfo)
}

var assemblyData = new CleanTestAssemblyData(utilitiesCollection);

var configurationAttribute = assemblyInfo.GetCustomAttributes(typeof(ConfigureCleanTestsFrameworkAttribute)).FirstOrDefault();
if (configurationAttribute is not null)
{
assemblyData.MaxDegreeOfParallelism = configurationAttribute.GetNamedArgument<int>(nameof(ConfigureCleanTestsFrameworkAttribute.MaxDegreeOfParallelism));
assemblyData.UtilitiesPresentations = configurationAttribute.GetNamedArgument<CleanTestMetadataPresentations>(nameof(ConfigureCleanTestsFrameworkAttribute.UtilitiesPresentations));
assemblyData.GenericTypeMappingPresentations = configurationAttribute.GetNamedArgument<CleanTestMetadataPresentations>(nameof(ConfigureCleanTestsFrameworkAttribute.GenericTypeMappingPresentations));
}

this._utilityDescriptorsByAssembly[assemblyInfo.Name] = assemblyData;
return assemblyData;
}
}

private static void RegisterUtilitiesFromAssembly(IAssemblyInfo assemblyInfo, List<ICleanUtilityDescriptor> utilitiesCollection)
{
foreach (var type in assemblyInfo.GetTypes(includePrivateTypes: false).OrEmptyIfNull().IgnoreNullValues())
{
if (type.IsAbstract) continue;

var initializationUtilityAttributes = type.GetCustomAttributes(typeof(CleanUtilityAttribute)).ToArray();
if (initializationUtilityAttributes.Length == 0) continue;
var cleanUtilityAttributes = type.GetCustomAttributes(typeof(CleanUtilityAttribute)).ToArray();
Comment thread
TonyTroeff marked this conversation as resolved.
if (cleanUtilityAttributes.Length == 0) continue;

var decoratedType = new DecoratedType(type);
var externalDemands = decoratedType.ExtractDemands<ExternalDemandsAttribute>();
var internalDemands = decoratedType.ExtractDemands<InternalDemandsAttribute>();
var outerDemands = decoratedType.ExtractDemands<OuterDemandsAttribute>();
var requirements = ExtractRequirements(type);

foreach (var utilityAttribute in initializationUtilityAttributes.OrEmptyIfNull().IgnoreNullValues())
foreach (var utilityAttribute in cleanUtilityAttributes.OrEmptyIfNull().IgnoreNullValues())
{
var categoryArgument = utilityAttribute.GetNamedArgument<string>(nameof(CleanUtilityAttribute.Category));
var nameArgument = utilityAttribute.GetNamedArgument<string>(nameof(CleanUtilityAttribute.Name));
var isGlobalArgument = utilityAttribute.GetNamedArgument<bool>(nameof(CleanUtilityAttribute.IsGlobal));
var characteristicsArgument = utilityAttribute.GetNamedArgument<IEnumerable<string>>(nameof(CleanUtilityAttribute.Characteristics));

var initializationUtility = new CleanUtilityDescriptor(categoryArgument, type.ToRuntimeType(), nameArgument, isGlobalArgument, characteristicsArgument, requirements);
externalDemands.CopyTo(initializationUtility.ExternalDemands);
internalDemands.CopyTo(initializationUtility.InternalDemands);
outerDemands.CopyTo(initializationUtility.OuterDemands);
var cleanUtility = new CleanUtilityDescriptor(categoryArgument, type.ToRuntimeType(), nameArgument, isGlobalArgument, characteristicsArgument, requirements);
externalDemands.CopyTo(cleanUtility.ExternalDemands);
internalDemands.CopyTo(cleanUtility.InternalDemands);
outerDemands.CopyTo(cleanUtility.OuterDemands);

utilitiesCollection.Add(initializationUtility);
utilitiesCollection.Add(cleanUtility);
}
}
}
Expand Down
Loading