From 525b9ef93699624163a6ac158de5349c3acaa1b5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 14:43:12 +0000 Subject: [PATCH 001/124] First code commit --- .idea/.idea.TUnit/.idea/.gitignore | 13 + .idea/.idea.TUnit/.idea/encodings.xml | 4 + .idea/.idea.TUnit/.idea/indexLayout.xml | 8 + TUnit.Assertions/Assert.cs | 22 ++ TUnit.Assertions/AssertCondition.cs | 22 ++ .../AssertConditions/FailureLocation.cs | 8 + .../Generic/EqualsAssertCondition.cs | 19 ++ .../Generic/SameReferenceAssertCondition.cs | 18 ++ .../String/StringCompareAssertionSettings.cs | 7 + .../String/StringEqualsAssertCondition.cs | 19 ++ .../Exceptions/AssertionException.cs | 8 + TUnit.Assertions/Exceptions/TUnitException.cs | 22 ++ TUnit.Assertions/IAssertCondition.cs | 10 + TUnit.Assertions/Is.cs | 16 + TUnit.Assertions/Is_Strings.cs | 11 + TUnit.Assertions/TUnit.Assertions.csproj | 9 + .../Attributes/OneTimeSetUpAttribute.cs | 4 + .../Attributes/OneTimeTearDownAttribute.cs | 4 + TUnit.Core/Attributes/SetUpAttribute.cs | 4 + TUnit.Core/Attributes/SkipAttribute.cs | 4 + TUnit.Core/Attributes/TUnitAttribute.cs | 3 + TUnit.Core/Attributes/TearDownAttribute.cs | 4 + TUnit.Core/Attributes/TestAttribute.cs | 4 + .../Attributes/TestWithDataAttribute.cs | 12 + TUnit.Core/SourceLocation.cs | 3 + TUnit.Core/TUnit.Core.csproj | 10 + TUnit.Core/Test.cs | 81 +++++ TUnit.Core/TestCollection.cs | 35 +++ TUnit.Core/TypeInformation.cs | 8 + .../Modules/AddLocalNuGetDirectoryModule.cs | 20 ++ .../Modules/AddReferencesToTestProject.cs | 36 +++ .../CreateLocalNuGetDirectoryModule.cs | 17 + .../Modules/GetPackageProjectsModule.cs | 15 + .../MoveNuGetPackagesToLocalSourceModule.cs | 24 ++ .../Modules/PackTUnitFilesModule.cs | 27 ++ TUnit.Pipeline/PackedProject.cs | 3 + TUnit.Pipeline/Program.cs | 9 + TUnit.Pipeline/TUnit.Pipeline.csproj | 16 + TUnit.TestAdapter/AssemblyLoader.cs | 35 +++ TUnit.TestAdapter/AsyncTestExecutor.cs | 294 ++++++++++++++++++ .../Constants/TestAdapterConstants.cs | 7 + .../Extensions/TestExtensions.cs | 19 ++ TUnit.TestAdapter/ProcessingTest.cs | 5 + TUnit.TestAdapter/Properties/AssemblyInfo.cs | 2 + TUnit.TestAdapter/SourceLocationHelper.cs | 40 +++ TUnit.TestAdapter/TUnit.TestAdapter.csproj | 21 ++ TUnit.TestAdapter/TestAndClass.cs | 5 + TUnit.TestAdapter/TestCollector.cs | 36 +++ TUnit.TestAdapter/TestDiscoverer.cs | 28 ++ TUnit.TestAdapter/TestExecutor.cs | 52 ++++ TUnit.TestAdapter/TestsLoader.cs | 61 ++++ TUnit.TestProject/GlobalUsings.cs | 0 TUnit.TestProject/TUnit.TestProject.csproj | 21 ++ TUnit.TestProject/Tests.cs | 17 + TUnit.sln | 46 +++ TUnit/TUnit.csproj | 14 + 56 files changed, 1262 insertions(+) create mode 100644 .idea/.idea.TUnit/.idea/.gitignore create mode 100644 .idea/.idea.TUnit/.idea/encodings.xml create mode 100644 .idea/.idea.TUnit/.idea/indexLayout.xml create mode 100644 TUnit.Assertions/Assert.cs create mode 100644 TUnit.Assertions/AssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/FailureLocation.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs create mode 100644 TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs create mode 100644 TUnit.Assertions/Exceptions/AssertionException.cs create mode 100644 TUnit.Assertions/Exceptions/TUnitException.cs create mode 100644 TUnit.Assertions/IAssertCondition.cs create mode 100644 TUnit.Assertions/Is.cs create mode 100644 TUnit.Assertions/Is_Strings.cs create mode 100644 TUnit.Assertions/TUnit.Assertions.csproj create mode 100644 TUnit.Core/Attributes/OneTimeSetUpAttribute.cs create mode 100644 TUnit.Core/Attributes/OneTimeTearDownAttribute.cs create mode 100644 TUnit.Core/Attributes/SetUpAttribute.cs create mode 100644 TUnit.Core/Attributes/SkipAttribute.cs create mode 100644 TUnit.Core/Attributes/TUnitAttribute.cs create mode 100644 TUnit.Core/Attributes/TearDownAttribute.cs create mode 100644 TUnit.Core/Attributes/TestAttribute.cs create mode 100644 TUnit.Core/Attributes/TestWithDataAttribute.cs create mode 100644 TUnit.Core/SourceLocation.cs create mode 100644 TUnit.Core/TUnit.Core.csproj create mode 100644 TUnit.Core/Test.cs create mode 100644 TUnit.Core/TestCollection.cs create mode 100644 TUnit.Core/TypeInformation.cs create mode 100644 TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs create mode 100644 TUnit.Pipeline/Modules/AddReferencesToTestProject.cs create mode 100644 TUnit.Pipeline/Modules/CreateLocalNuGetDirectoryModule.cs create mode 100644 TUnit.Pipeline/Modules/GetPackageProjectsModule.cs create mode 100644 TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs create mode 100644 TUnit.Pipeline/Modules/PackTUnitFilesModule.cs create mode 100644 TUnit.Pipeline/PackedProject.cs create mode 100644 TUnit.Pipeline/Program.cs create mode 100644 TUnit.Pipeline/TUnit.Pipeline.csproj create mode 100644 TUnit.TestAdapter/AssemblyLoader.cs create mode 100644 TUnit.TestAdapter/AsyncTestExecutor.cs create mode 100644 TUnit.TestAdapter/Constants/TestAdapterConstants.cs create mode 100644 TUnit.TestAdapter/Extensions/TestExtensions.cs create mode 100644 TUnit.TestAdapter/ProcessingTest.cs create mode 100644 TUnit.TestAdapter/Properties/AssemblyInfo.cs create mode 100644 TUnit.TestAdapter/SourceLocationHelper.cs create mode 100644 TUnit.TestAdapter/TUnit.TestAdapter.csproj create mode 100644 TUnit.TestAdapter/TestAndClass.cs create mode 100644 TUnit.TestAdapter/TestCollector.cs create mode 100644 TUnit.TestAdapter/TestDiscoverer.cs create mode 100644 TUnit.TestAdapter/TestExecutor.cs create mode 100644 TUnit.TestAdapter/TestsLoader.cs create mode 100644 TUnit.TestProject/GlobalUsings.cs create mode 100644 TUnit.TestProject/TUnit.TestProject.csproj create mode 100644 TUnit.TestProject/Tests.cs create mode 100644 TUnit.sln create mode 100644 TUnit/TUnit.csproj diff --git a/.idea/.idea.TUnit/.idea/.gitignore b/.idea/.idea.TUnit/.idea/.gitignore new file mode 100644 index 0000000000..0be7a56a1e --- /dev/null +++ b/.idea/.idea.TUnit/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +/.idea.TUnit.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.TUnit/.idea/encodings.xml b/.idea/.idea.TUnit/.idea/encodings.xml new file mode 100644 index 0000000000..df87cf951f --- /dev/null +++ b/.idea/.idea.TUnit/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.TUnit/.idea/indexLayout.xml b/.idea/.idea.TUnit/.idea/indexLayout.xml new file mode 100644 index 0000000000..7b08163ceb --- /dev/null +++ b/.idea/.idea.TUnit/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs new file mode 100644 index 0000000000..609b4a54bc --- /dev/null +++ b/TUnit.Assertions/Assert.cs @@ -0,0 +1,22 @@ +using TUnit.Assertions.Exceptions; + +namespace TUnit.Assertions; + +public static class Assert +{ + public static void That(T value, AssertCondition assertCondition) + { + if (!assertCondition.Matches(value)) + { + var message = GetMessage(assertCondition, value); + throw new AssertionException(message); + } + } + + private static string GetMessage(AssertCondition assertCondition, T actualValue) + { + return assertCondition.MessageFactory != null + ? assertCondition.MessageFactory((assertCondition.ExpectedValue, actualValue)) + : assertCondition.Message; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertCondition.cs b/TUnit.Assertions/AssertCondition.cs new file mode 100644 index 0000000000..b56e622ce3 --- /dev/null +++ b/TUnit.Assertions/AssertCondition.cs @@ -0,0 +1,22 @@ +namespace TUnit.Assertions; + +public abstract class AssertCondition : IAssertCondition +{ + internal AssertCondition(T expected) + { + ExpectedValue = expected; + } + + internal Func<(T expectedValue, T actualValue), string>? MessageFactory { get; private set; } + + public T ExpectedValue { get; } + public abstract bool Matches(T actualValue); + + public abstract string Message { get; protected set; } + + public IAssertCondition WithMessage(Func<(T expectedValue, T actualValue), string> messageFactory) + { + MessageFactory = messageFactory; + return this; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/FailureLocation.cs b/TUnit.Assertions/AssertConditions/FailureLocation.cs new file mode 100644 index 0000000000..b9bd39ac5b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/FailureLocation.cs @@ -0,0 +1,8 @@ +namespace TUnit.Assertions.AssertConditions; + +public record FailureLocation +{ + public long Position { get; } + public object? ExpectedValue { get; } + public object? ActualValue { get; } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs new file mode 100644 index 0000000000..5ea7757dc3 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -0,0 +1,19 @@ +namespace TUnit.Assertions.AssertConditions.Generic; + +public class EqualsAssertCondition : AssertCondition +{ + private readonly T _expected; + + public EqualsAssertCondition(T expected) : base(expected) + { + _expected = expected; + } + + public override bool Matches(T actualValue) + { + Message = $"Expected {_expected} but received {actualValue}"; + return Equals(actualValue, _expected); + } + + public override string Message { get; protected set; } = string.Empty; +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs new file mode 100644 index 0000000000..f31a6b8ea0 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -0,0 +1,18 @@ +namespace TUnit.Assertions.AssertConditions.Generic; + +public class SameReferenceAssertCondition : AssertCondition +{ + private readonly T _expected; + + public SameReferenceAssertCondition(T expected) : base(expected) + { + _expected = expected; + } + + public override bool Matches(T actualValue) + { + return ReferenceEquals(actualValue, _expected); + } + + public override string Message { get; protected set; } = string.Empty; +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs b/TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs new file mode 100644 index 0000000000..398c05b6f1 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs @@ -0,0 +1,7 @@ +namespace TUnit.Assertions.AssertConditions.String; + +public record StringCompareAssertionSettings +{ + public StringComparison StringComparison { get; init; } = StringComparison.Ordinal; + public bool Trim { get; init; } = false; +}; \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs new file mode 100644 index 0000000000..783edef8ab --- /dev/null +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -0,0 +1,19 @@ +namespace TUnit.Assertions.AssertConditions.String; + +public class StringEqualsAssertCondition : AssertCondition +{ + private readonly StringComparison _stringComparison; + + public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : base(expected) + { + _stringComparison = stringComparison; + } + + public override bool Matches(string actualValue) + { + Message = $"Expected {ExpectedValue} but received {actualValue}"; + return string.Equals(actualValue, ExpectedValue, _stringComparison); + } + + public override string Message { get; protected set; } = string.Empty; +} \ No newline at end of file diff --git a/TUnit.Assertions/Exceptions/AssertionException.cs b/TUnit.Assertions/Exceptions/AssertionException.cs new file mode 100644 index 0000000000..d5bfe8360a --- /dev/null +++ b/TUnit.Assertions/Exceptions/AssertionException.cs @@ -0,0 +1,8 @@ +namespace TUnit.Assertions.Exceptions; + +public class AssertionException : TUnitException +{ + public AssertionException(string? message) : base(message) + { + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Exceptions/TUnitException.cs b/TUnit.Assertions/Exceptions/TUnitException.cs new file mode 100644 index 0000000000..e448e3472a --- /dev/null +++ b/TUnit.Assertions/Exceptions/TUnitException.cs @@ -0,0 +1,22 @@ +using System.Runtime.Serialization; + +namespace TUnit.Assertions.Exceptions; + +public class TUnitException : Exception +{ + public TUnitException() + { + } + + protected TUnitException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public TUnitException(string? message) : base(message) + { + } + + public TUnitException(string? message, Exception? innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/TUnit.Assertions/IAssertCondition.cs b/TUnit.Assertions/IAssertCondition.cs new file mode 100644 index 0000000000..f4d510cb4e --- /dev/null +++ b/TUnit.Assertions/IAssertCondition.cs @@ -0,0 +1,10 @@ +namespace TUnit.Assertions; + +public interface IAssertCondition +{ + internal T ExpectedValue { get; } + + public bool Matches(T actualValue); + + internal string Message { get; } +} \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs new file mode 100644 index 0000000000..c3d437ed41 --- /dev/null +++ b/TUnit.Assertions/Is.cs @@ -0,0 +1,16 @@ +using TUnit.Assertions.AssertConditions.Generic; + +namespace TUnit.Assertions; + +public static partial class Is +{ + public static IAssertCondition EqualTo(T expected) + { + return new EqualsAssertCondition(expected); + } + + public static IAssertCondition SameReference(T expected) + { + return new SameReferenceAssertCondition(expected); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Is_Strings.cs b/TUnit.Assertions/Is_Strings.cs new file mode 100644 index 0000000000..c553cc9b70 --- /dev/null +++ b/TUnit.Assertions/Is_Strings.cs @@ -0,0 +1,11 @@ +using TUnit.Assertions.AssertConditions.String; + +namespace TUnit.Assertions; + +public static partial class Is +{ + public static IAssertCondition EqualTo(string expected, StringComparison stringComparison = StringComparison.Ordinal) + { + return new StringEqualsAssertCondition(expected, stringComparison); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj new file mode 100644 index 0000000000..ca62071d4f --- /dev/null +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs b/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs new file mode 100644 index 0000000000..40c13ba781 --- /dev/null +++ b/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs @@ -0,0 +1,4 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class OneTimeSetUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/OneTimeTearDownAttribute.cs b/TUnit.Core/Attributes/OneTimeTearDownAttribute.cs new file mode 100644 index 0000000000..9a828fc5ef --- /dev/null +++ b/TUnit.Core/Attributes/OneTimeTearDownAttribute.cs @@ -0,0 +1,4 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class OneTimeTearDownAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/SetUpAttribute.cs b/TUnit.Core/Attributes/SetUpAttribute.cs new file mode 100644 index 0000000000..96c1c4b5c9 --- /dev/null +++ b/TUnit.Core/Attributes/SetUpAttribute.cs @@ -0,0 +1,4 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class SetUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/SkipAttribute.cs b/TUnit.Core/Attributes/SkipAttribute.cs new file mode 100644 index 0000000000..22bb734105 --- /dev/null +++ b/TUnit.Core/Attributes/SkipAttribute.cs @@ -0,0 +1,4 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] +public class SkipAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/TUnitAttribute.cs b/TUnit.Core/Attributes/TUnitAttribute.cs new file mode 100644 index 0000000000..d8eb96163a --- /dev/null +++ b/TUnit.Core/Attributes/TUnitAttribute.cs @@ -0,0 +1,3 @@ +namespace TUnit.Core.Attributes; + +public class TUnitAttribute : Attribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/TearDownAttribute.cs b/TUnit.Core/Attributes/TearDownAttribute.cs new file mode 100644 index 0000000000..7032514a65 --- /dev/null +++ b/TUnit.Core/Attributes/TearDownAttribute.cs @@ -0,0 +1,4 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class TearDownAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/TestAttribute.cs b/TUnit.Core/Attributes/TestAttribute.cs new file mode 100644 index 0000000000..48c90ae33d --- /dev/null +++ b/TUnit.Core/Attributes/TestAttribute.cs @@ -0,0 +1,4 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class TestAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/TestWithDataAttribute.cs b/TUnit.Core/Attributes/TestWithDataAttribute.cs new file mode 100644 index 0000000000..64f8a17825 --- /dev/null +++ b/TUnit.Core/Attributes/TestWithDataAttribute.cs @@ -0,0 +1,12 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class TestWithDataAttribute : TUnitAttribute +{ + public object[] Values { get; } + + public TestWithDataAttribute(params object[] values) + { + Values = values; + } +} \ No newline at end of file diff --git a/TUnit.Core/SourceLocation.cs b/TUnit.Core/SourceLocation.cs new file mode 100644 index 0000000000..c98e2e3bc9 --- /dev/null +++ b/TUnit.Core/SourceLocation.cs @@ -0,0 +1,3 @@ +namespace TUnit.Core; + +public record SourceLocation(string? FileName, int MinLineNumber, int MaxLineNumber); \ No newline at end of file diff --git a/TUnit.Core/TUnit.Core.csproj b/TUnit.Core/TUnit.Core.csproj new file mode 100644 index 0000000000..c81818ddbf --- /dev/null +++ b/TUnit.Core/TUnit.Core.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + enable + enable + default + + + diff --git a/TUnit.Core/Test.cs b/TUnit.Core/Test.cs new file mode 100644 index 0000000000..acf720e459 --- /dev/null +++ b/TUnit.Core/Test.cs @@ -0,0 +1,81 @@ +using System.Reflection; +using TUnit.Core.Attributes; + +namespace TUnit.Core; + +public record Test +{ + public Test(MethodInfo MethodInfo, + SourceLocation SourceLocation, + object?[]? Arguments) + { + var classType = MethodInfo.DeclaringType!; + + this.MethodInfo = MethodInfo; + this.Arguments = Arguments; + this.SourceLocation = SourceLocation; + + TestName = MethodInfo.Name; + ClassName = classType.Name; + FullyQualifiedClassName = classType.FullName!; + Assembly = classType.Assembly; + Source = classType.Assembly.Location; + FullName = $"{classType.FullName}.{MethodInfo.Name}{GetArguments(Arguments)}"; + IsSkipped = MethodInfo.CustomAttributes + .Concat(classType.CustomAttributes) + .Any(x => x.AttributeType == typeof(SkipAttribute)); + + FileName = SourceLocation.FileName; + MinLineNumber = SourceLocation.MinLineNumber; + MaxLineNumber = SourceLocation.MaxLineNumber; + } + + public Guid Id { get; } = Guid.NewGuid(); + + public string TestName { get; } + + public string ClassName { get; } + + public string FullyQualifiedClassName { get; set; } + + public Assembly Assembly { get; } + + public string Source { get; } + public string FullName { get; } + public MethodInfo MethodInfo { get; init; } + public string? FileName { get; set; } + public int MinLineNumber { get; set; } + public int MaxLineNumber { get; set; } + public object?[]? Arguments { get; init; } + public SourceLocation SourceLocation { get; } + + public bool IsSkipped { get; } + + public void Deconstruct(out MethodInfo methodInfo, out object?[]? arguments) + { + methodInfo = MethodInfo; + arguments = Arguments; + } + + private static string GetArguments(object?[]? arguments) + { + if (arguments is null) + { + return string.Empty; + } + + var argsAsString = arguments.Select(StringifyArgument); + + return $"({string.Join(", ", argsAsString)})"; + } + + private static string StringifyArgument(object? obj) + { + return obj switch + { + null => "null", + string stringValue => $"\"{stringValue}\"", + _ => obj.ToString() ?? string.Empty + }; + } +} \ No newline at end of file diff --git a/TUnit.Core/TestCollection.cs b/TUnit.Core/TestCollection.cs new file mode 100644 index 0000000000..06f2ccf2cc --- /dev/null +++ b/TUnit.Core/TestCollection.cs @@ -0,0 +1,35 @@ +using System.Collections.Immutable; + +namespace TUnit.Core; + +/// +/// Contains a collection of tests that can be run. Should be disposed to ensure that the +/// temporary containing the test assemblies is unloaded. +/// +public sealed class TestCollection +{ + /// + /// The test sources (assembly file names). + /// + public IReadOnlyList Sources { get; } + + /// + /// The tests that were discovered. + /// + public IReadOnlyList Tests { get; private set; } + + public TestCollection(IEnumerable sources, IEnumerable tests) + { + Sources = ImmutableArray.CreateRange(sources); + Tests = ImmutableArray.CreateRange(tests); + } + + /// + /// Filters the tests in the test collection. This is used for partial test runs. + /// + /// The filter to apply. + public void Filter(Func filter) + { + Tests = ImmutableArray.CreateRange(Tests.Where(filter)); + } +} \ No newline at end of file diff --git a/TUnit.Core/TypeInformation.cs b/TUnit.Core/TypeInformation.cs new file mode 100644 index 0000000000..f8244c6e59 --- /dev/null +++ b/TUnit.Core/TypeInformation.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace TUnit.Core; + +public record TypeInformation(Assembly Assembly) +{ + public Type[] Types { get; } = Assembly.GetTypes(); +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs b/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs new file mode 100644 index 0000000000..fbd3b7a9da --- /dev/null +++ b/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs @@ -0,0 +1,20 @@ +using ModularPipelines.Attributes; +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Models; +using ModularPipelines.Modules; + +namespace TUnit.Pipeline.Modules; + +[DependsOn] +public class AddLocalNuGetDirectoryModule : Module +{ + protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var directoryResult = await GetModule(); + + return await context.DotNet().Nuget.Add + .Source(new DotNetNugetAddSourceOptions(directoryResult.Value!), cancellationToken); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs new file mode 100644 index 0000000000..19f7effa90 --- /dev/null +++ b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs @@ -0,0 +1,36 @@ +using EnumerableAsyncProcessor.Extensions; +using ModularPipelines.Attributes; +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Extensions; +using ModularPipelines.Git.Extensions; +using ModularPipelines.Models; +using ModularPipelines.Modules; + +namespace TUnit.Pipeline.Modules; +[DependsOn] +[DependsOn] +[DependsOn] +public class AddReferencesToTestProject : Module +{ + protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var testProject = context.Git().RootDirectory.FindFile(x => x.Name == "TUnit.TestProject.csproj").AssertExists(); + var projects = await GetModule(); + var localNugetDirectory = await GetModule(); + + await projects.Value! + .ForEachAsync( + x => context.DotNet().Remove.Package(new DotNetRemovePackageOptions(x.Name), cancellationToken), + cancellationToken: cancellationToken) + .ProcessOneAtATime(); + + return await projects.Value! + .SelectAsync(async x => await context.DotNet().Add.Package(new DotNetAddPackageOptions(testProject, x.Name) + { + Version = x.Version, + Source = localNugetDirectory.Value! + }, cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/CreateLocalNuGetDirectoryModule.cs b/TUnit.Pipeline/Modules/CreateLocalNuGetDirectoryModule.cs new file mode 100644 index 0000000000..2f2ae5d93c --- /dev/null +++ b/TUnit.Pipeline/Modules/CreateLocalNuGetDirectoryModule.cs @@ -0,0 +1,17 @@ +using ModularPipelines.Context; +using ModularPipelines.Extensions; +using ModularPipelines.FileSystem; +using ModularPipelines.Modules; + +namespace TUnit.Pipeline.Modules; + +public class CreateLocalNuGetDirectoryModule : Module +{ + protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + return context.FileSystem.GetFolder(Environment.SpecialFolder.UserProfile) + .GetFolder("LocalNuGet") + .Create() + .AsTask(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs b/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs new file mode 100644 index 0000000000..7a78526f8f --- /dev/null +++ b/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs @@ -0,0 +1,15 @@ +using ModularPipelines.Context; +using ModularPipelines.Extensions; +using ModularPipelines.Git.Extensions; +using ModularPipelines.Modules; +using File = ModularPipelines.FileSystem.File; + +namespace TUnit.Pipeline.Modules; + +public class GetPackageProjectsModule : Module> +{ + protected override Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + return context.Git().RootDirectory.GetFiles(x => x.Extension == ".csproj").Where(x => !x.Name.Contains("Pipeline")).Where(x => !x.Name.Contains("TestProject")).ToList().AsTask?>(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs b/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs new file mode 100644 index 0000000000..bbffe9f141 --- /dev/null +++ b/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs @@ -0,0 +1,24 @@ +using ModularPipelines.Attributes; +using ModularPipelines.Context; +using ModularPipelines.Extensions; +using ModularPipelines.Git.Extensions; +using ModularPipelines.Modules; +using File = ModularPipelines.FileSystem.File; + +namespace TUnit.Pipeline.Modules; + +[DependsOn] +public class MoveNuGetPackagesToLocalSourceModule : Module> +{ + protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var localNugetDirectory = await GetModule(); + + var nugetPackages = context.Git().RootDirectory + .GetFiles(x => x.Extension is ".nupkg" or ".snupkg"); + + return nugetPackages + .Select(x => x.MoveTo(localNugetDirectory.Value.AssertExists())) + .ToList(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs new file mode 100644 index 0000000000..26395b55f3 --- /dev/null +++ b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs @@ -0,0 +1,27 @@ +using EnumerableAsyncProcessor.Extensions; +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Models; +using ModularPipelines.Modules; +using ModularPipelines.Attributes; + +namespace TUnit.Pipeline.Modules; +[DependsOn] +public class PackTUnitFilesModule : Module> +{ + protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var projects = await GetModule(); + + var guid = Guid.NewGuid(); + var version = $"0.0.1-alpha{guid}"; + + var packedProjects = await projects.Value!.SelectAsync(async project => + { + return await context.DotNet().Pack(new DotNetPackOptions(project) { Properties = new[] { new KeyValue("Version", version), new KeyValue("PackageVersion", version) } }, cancellationToken); + }, cancellationToken: cancellationToken).ProcessOneAtATime(); + + return projects.Value!.Select(x => new PackedProject(x.Name, version)).ToList(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/PackedProject.cs b/TUnit.Pipeline/PackedProject.cs new file mode 100644 index 0000000000..34cb1938b7 --- /dev/null +++ b/TUnit.Pipeline/PackedProject.cs @@ -0,0 +1,3 @@ +namespace TUnit.Pipeline.Modules; + +public record PackedProject(string Name, string Version); \ No newline at end of file diff --git a/TUnit.Pipeline/Program.cs b/TUnit.Pipeline/Program.cs new file mode 100644 index 0000000000..982b27198f --- /dev/null +++ b/TUnit.Pipeline/Program.cs @@ -0,0 +1,9 @@ +using ModularPipelines.Extensions; +using ModularPipelines.Host; + +await PipelineHostBuilder.Create() + .ConfigureServices((context, collection) => + { + collection.AddModulesFromAssembly(typeof(Program).Assembly); + }) + .ExecutePipelineAsync(); \ No newline at end of file diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj new file mode 100644 index 0000000000..3590d330fc --- /dev/null +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + default + + + + + + + + diff --git a/TUnit.TestAdapter/AssemblyLoader.cs b/TUnit.TestAdapter/AssemblyLoader.cs new file mode 100644 index 0000000000..b4bdeaf457 --- /dev/null +++ b/TUnit.TestAdapter/AssemblyLoader.cs @@ -0,0 +1,35 @@ +using System.Reflection; + +namespace TUnit.TestAdapter; + +internal class AssemblyLoader +{ + internal Assembly? LoadByPath(string assemblyPath) + { + if (!File.Exists(assemblyPath)) + { + return null; + } + + try + { + return Assembly.LoadFrom(assemblyPath); + } + catch + { + return null; + } + } + + internal Assembly? LoadByName(AssemblyName assemblyName) + { + try + { + return Assembly.Load(assemblyName); + } + catch + { + return null; + } + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs new file mode 100644 index 0000000000..12455e55c4 --- /dev/null +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -0,0 +1,294 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using TUnit.Core; +using TUnit.Core.Attributes; +using TUnit.TestAdapter.Extensions; + +namespace TUnit.TestAdapter; + +public class AsyncTestExecutor(CancellationTokenSource cancellationTokenSource) +{ + private bool _canRunAnotherTest = true; + + private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); + private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); + + public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + var allTestsOrderedByClass = tests + .GroupBy(x => x.FullyQualifiedClassName) + .SelectMany(x => x.ToList()) + .ToList(); + + var queue = new Queue(allTestsOrderedByClass); + + if (queue.Count is 0) + { + return; + } + + MonitorSystemResources(); + + var executingTests = new List(); + + await foreach (var testCase in ProcessQueue(queue, frameworkHandle)) + { + executingTests.Add(testCase); + + SetupRunOneTimeTearDownForClass(testCase, allTestsOrderedByClass, executingTests); + + executingTests.RemoveAll(x => x.Task.IsCompletedSuccessfully); + } + + executingTests.RemoveAll(x => x.Task.IsCompletedSuccessfully); + + await Task.WhenAll(executingTests.Select(x => x.Task)); + await Task.WhenAll(_oneTimeTearDownRegistry.Values); + } + + private void SetupRunOneTimeTearDownForClass(ProcessingTest processingTest, + IEnumerable allTestsOrderedByClass, + IEnumerable executingTests) + { + var lastTestForClass = allTestsOrderedByClass.Last(x => + x.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName); + + if (processingTest.Test.FullName != lastTestForClass.FullName) + { + return; + } + + var executingTestsForThisClass = executingTests + .Where(x => x.Test.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName) + .Select(x => x.Task) + .ToArray(); + + Task.WhenAll(executingTestsForThisClass).ContinueWith(x => + { + _ = _oneTimeTearDownRegistry.GetOrAdd(processingTest.Test.FullyQualifiedClassName, + ExecuteOneTimeTearDowns(processingTest.Class)); + + return Task.CompletedTask; + }); + } + + private async IAsyncEnumerable ProcessQueue(Queue queue, ITestExecutionRecorder? frameworkHandle) + { + while (queue.Count > 0) + { + if (_canRunAnotherTest) + { + var test = queue.Dequeue(); + + var @class = CreateTestClass(test); + + yield return new ProcessingTest(test, @class, ProcessTest(test, @class, frameworkHandle)); + } + else + { + await Task.Delay(100); + } + } + } + + private async Task ProcessTest(Test test, object @class, ITestExecutionRecorder? frameworkHandle) + { + await ExecuteTestMethod(test, @class, frameworkHandle); + } + + private static object CreateTestClass(Test test) + { + return Activator.CreateInstance(test.MethodInfo.DeclaringType!)!; + } + + private async ValueTask ExecuteTestMethod(Test test, object @class, ITestExecutionRecorder? frameworkHandle) + { + var testCase = test.ToTestCase(); + + if (test.IsSkipped) + { + frameworkHandle?.RecordEnd(testCase, TestOutcome.Skipped); + return; + } + + frameworkHandle?.RecordStart(testCase); + + var start = DateTimeOffset.Now; + var stopwatch = Stopwatch.StartNew(); + + try + { + await ExecuteTest(test, @class); + + frameworkHandle?.RecordEnd(testCase, TestOutcome.Passed); + + frameworkHandle?.RecordResult(new TestResult(testCase) + { + Outcome = TestOutcome.Passed, + + DisplayName = test.TestName, + StartTime = start, + EndTime = DateTimeOffset.Now, + Duration = stopwatch.Elapsed, + ComputerName = Environment.MachineName, + }); + } + catch (Exception e) + { + frameworkHandle?.RecordEnd(testCase, TestOutcome.Failed); + + frameworkHandle?.RecordResult(new TestResult(testCase) + { + Outcome = TestOutcome.Failed, + + DisplayName = test.TestName, + ErrorMessage = e.Message, + ErrorStackTrace = e.StackTrace, + StartTime = start, + EndTime = DateTimeOffset.Now, + Duration = stopwatch.Elapsed, + ComputerName = Environment.MachineName, + }); + } + } + + private async Task ExecuteTest(Test test, object @class) + { + await ExecuteSetUps(@class); + + var methodExecutionObject = test.MethodInfo.Invoke(@class, test.Arguments); + + if (methodExecutionObject is Task task) + { + await task; + } + + await ExecuteTearDowns(@class); + } + + private async Task ExecuteSetUps(object @class) + { + await _oneTimeSetUpRegistry.GetOrAdd(@class.GetType().FullName!, _ => ExecuteOneTimeSetUps(@class)); + + var setUpMethods = @class.GetType() + .GetMethods() + .Where(x => !x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(SetUpAttribute))); + + foreach (var setUpMethod in setUpMethods) + { + var result = setUpMethod.Invoke(@class, null); + + if (result is Task task) + { + await task; + } + } + } + + private async Task ExecuteTearDowns(object @class) + { + var tearDownMethods = @class.GetType() + .GetMethods() + .Where(x => !x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(TearDownAttribute))); + + var exceptions = new List(); + + foreach (var tearDownMethod in tearDownMethods) + { + try + { + var result = tearDownMethod.Invoke(@class, null); + + if (result is Task task) + { + await task; + } + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + if (exceptions.Any()) + { + throw new AggregateException(exceptions); + } + } + + private async Task ExecuteOneTimeSetUps(object @class) + { + var oneTimeSetUpMethods = @class.GetType() + .GetMethods() + .Where(x => x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeSetUpAttribute))); + + foreach (var oneTimeSetUpMethod in oneTimeSetUpMethods) + { + var result = oneTimeSetUpMethod.Invoke(null, BindingFlags.Static | BindingFlags.Public, null, null, null); + + if (result is Task task) + { + await task; + } + } + } + + private async Task ExecuteOneTimeTearDowns(object @class) + { + var oneTimeTearDownMethods = @class.GetType() + .GetMethods() + .Where(x => x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeTearDownAttribute))); + + foreach (var oneTimeTearDownMethod in oneTimeTearDownMethods) + { + var result = oneTimeTearDownMethod.Invoke(null, BindingFlags.Static | BindingFlags.Public, null, null, null); + + if (result is Task task) + { + await task; + } + } + } + + private void MonitorSystemResources() + { + Task.Factory.StartNew(async _ => + { + while (!cancellationTokenSource.IsCancellationRequested) + { + await Task.Delay(500); + + var cpuUsage = await GetCpuUsageForProcess(); + + _canRunAnotherTest = cpuUsage < 80; + } + }, null, TaskCreationOptions.LongRunning); + } + + private async Task GetCpuUsageForProcess() + { + var startTime = DateTime.UtcNow; + + var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + await Task.Delay(500); + + var endTime = DateTime.UtcNow; + + var endCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + + var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; + + var totalMsPassed = (endTime - startTime).TotalMilliseconds; + + var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); + + return cpuUsageTotal * 100; + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs new file mode 100644 index 0000000000..5c7d052d29 --- /dev/null +++ b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs @@ -0,0 +1,7 @@ +namespace TUnit.TestAdapter.Constants; + +internal static class TestAdapterConstants +{ + internal const string ExecutorUriString = "executor://tunit/TestRunner/net"; + internal static readonly Uri ExecutorUri = new(ExecutorUriString); +} \ No newline at end of file diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs new file mode 100644 index 0000000000..6a2da9ac39 --- /dev/null +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TUnit.Core; +using TUnit.TestAdapter.Constants; + +namespace TUnit.TestAdapter.Extensions; + +public static class TestExtensions +{ + public static TestCase ToTestCase(this Test test) + { + return new TestCase(test.FullName, TestAdapterConstants.ExecutorUri, test.Source) + { + DisplayName = test.TestName, + Id = test.Id, + CodeFilePath = test.FileName, + LineNumber = test.MinLineNumber + }; + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/ProcessingTest.cs b/TUnit.TestAdapter/ProcessingTest.cs new file mode 100644 index 0000000000..1ea3b1f5af --- /dev/null +++ b/TUnit.TestAdapter/ProcessingTest.cs @@ -0,0 +1,5 @@ +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public record ProcessingTest(Test Test, object Class, Task Task); \ No newline at end of file diff --git a/TUnit.TestAdapter/Properties/AssemblyInfo.cs b/TUnit.TestAdapter/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c65238fb38 --- /dev/null +++ b/TUnit.TestAdapter/Properties/AssemblyInfo.cs @@ -0,0 +1,2 @@ + + diff --git a/TUnit.TestAdapter/SourceLocationHelper.cs b/TUnit.TestAdapter/SourceLocationHelper.cs new file mode 100644 index 0000000000..46e8bb4a01 --- /dev/null +++ b/TUnit.TestAdapter/SourceLocationHelper.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public class SourceLocationHelper +{ + private readonly IMessageLogger? _logger; + private static readonly SourceLocation EmptySourceLocation = new(null, 0, 0); + + public SourceLocationHelper(IMessageLogger? logger) + { + _logger = logger; + } + + public SourceLocation GetSourceLocation(DiaSession diaSession, string className, string methodName) + { + try + { + var navigationData = diaSession.GetNavigationDataForMethod(className, methodName); + + if (navigationData is null) + { + _logger?.SendMessage(TestMessageLevel.Error, $"No navigation data found for {className}.{methodName}"); + + return EmptySourceLocation; + } + + return new SourceLocation(navigationData.FileName, navigationData.MinLineNumber, navigationData.MaxLineNumber); + } + catch (Exception e) + { + _logger?.SendMessage(TestMessageLevel.Error, $"Error retrieving source location for {className}.{methodName}"); + _logger?.SendMessage(TestMessageLevel.Error, e.ToString()); + + return EmptySourceLocation; + } + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj new file mode 100644 index 0000000000..e01dcca5d4 --- /dev/null +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + default + Library + true + + + + + + + + + + + + diff --git a/TUnit.TestAdapter/TestAndClass.cs b/TUnit.TestAdapter/TestAndClass.cs new file mode 100644 index 0000000000..fa1d944872 --- /dev/null +++ b/TUnit.TestAdapter/TestAndClass.cs @@ -0,0 +1,5 @@ +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public record TestAndClass(Test Test, object Class); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs new file mode 100644 index 0000000000..dcadf7c83c --- /dev/null +++ b/TUnit.TestAdapter/TestCollector.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public class TestCollector +{ + private readonly IMessageLogger? _messageLogger; + + public TestCollector(IMessageLogger? messageLogger) + { + _messageLogger = messageLogger; + } + + public TestCollection CollectionFromSources(IEnumerable sources) + { + var sourcesAsList = sources.ToList(); + + return new TestCollection(sourcesAsList, TestsFromSources(sourcesAsList)); + } + + public IEnumerable TestsFromSources(IEnumerable sources) + { + var assemblyLoader = new AssemblyLoader(); + var testsLoader = new TestsLoader(_messageLogger); + + var tests = sources + .Select(assemblyLoader.LoadByPath) + .OfType() + .Select(x => new TypeInformation(x)) + .SelectMany(x => testsLoader.GetTests(x)); + + return tests; + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs new file mode 100644 index 0000000000..cebbe8170c --- /dev/null +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.TestAdapter.Constants; +using TUnit.TestAdapter.Extensions; + +namespace TUnit.TestAdapter; + +[FileExtension(".dll")] +[FileExtension(".exe")] +[DefaultExecutorUri(TestAdapterConstants.ExecutorUriString)] +[ExtensionUri(TestAdapterConstants.ExecutorUriString)] +public class TestDiscoverer : ITestDiscoverer +{ + public void DiscoverTests(IEnumerable sources, + IDiscoveryContext discoveryContext, + IMessageLogger logger, + ITestCaseDiscoverySink discoverySink) + { + var testCollector = new TestCollector(logger); + + foreach (var test in testCollector.TestsFromSources(sources)) + { + logger.SendMessage(TestMessageLevel.Informational, "Test found: " + test.FullName); + discoverySink.SendTestCase(test.ToTestCase()); + } + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs new file mode 100644 index 0000000000..9bb266b7c8 --- /dev/null +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -0,0 +1,52 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using TUnit.TestAdapter.Constants; + +namespace TUnit.TestAdapter; + +[FileExtension(".dll")] +[FileExtension(".exe")] +[DefaultExecutorUri(TestAdapterConstants.ExecutorUriString)] +[ExtensionUri(TestAdapterConstants.ExecutorUriString)] +public class TestExecutor : ITestExecutor2 +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly AsyncTestExecutor _asyncTestExecutor; + + public TestExecutor() + { + _asyncTestExecutor = new AsyncTestExecutor(_cancellationTokenSource); + } + + public void RunTests(IEnumerable? tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + RunTests(tests?.Select(x => x.Source), runContext, frameworkHandle); + } + + public void RunTests(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + if (sources is null) + { + return; + } + + var tests = new TestCollector(frameworkHandle).TestsFromSources(sources); + + _asyncTestExecutor.RunInAsyncContext(tests, runContext, frameworkHandle).GetAwaiter().GetResult(); + } + + public void Cancel() + { + _cancellationTokenSource.Cancel(); + } + + public bool ShouldAttachToTestHost(IEnumerable? tests, IRunContext runContext) + { + return ShouldAttachToTestHost(tests?.Select(x => x.Source), runContext); + } + + public bool ShouldAttachToTestHost(IEnumerable? sources, IRunContext runContext) + { + return runContext.IsBeingDebugged; + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs new file mode 100644 index 0000000000..0a3af6f7f6 --- /dev/null +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -0,0 +1,61 @@ +using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Core; +using TUnit.Core.Attributes; + +namespace TUnit.TestAdapter; + +public class TestsLoader +{ + private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute)]; + private readonly SourceLocationHelper _sourceLocationHelper; + + public TestsLoader(IMessageLogger? messageLogger) + { + _sourceLocationHelper = new SourceLocationHelper(messageLogger); + } + + public IEnumerable GetTests(TypeInformation typeInformation) + { + using var diaSession = new DiaSession(typeInformation.Assembly.Location); + + var methods = typeInformation.Types.SelectMany(x => x.GetMethods()); + + foreach (var methodInfo in methods) + { + if (!HasTestAttributes(methodInfo)) + { + continue; + } + + var sourceLocation = _sourceLocationHelper + .GetSourceLocation(diaSession, methodInfo.DeclaringType!.Name, methodInfo.Name); + + foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) + { + var arguments = testWithDataAttribute.ConstructorArguments.Select(x => x.Value); + + yield return new Test( + MethodInfo: methodInfo, + SourceLocation: sourceLocation, + Arguments: arguments.ToArray() + ); + } + + if(methodInfo.CustomAttributes.Any(x => x.AttributeType == typeof(TestAttribute))) + { + yield return new Test( + MethodInfo: methodInfo, + SourceLocation: sourceLocation, + Arguments: null + ); + } + } + } + + private static bool HasTestAttributes(MethodInfo methodInfo) + { + return methodInfo.CustomAttributes.Select(x => x.AttributeType).Intersect(TestAttributes).Any(); + } +} \ No newline at end of file diff --git a/TUnit.TestProject/GlobalUsings.cs b/TUnit.TestProject/GlobalUsings.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj new file mode 100644 index 0000000000..d89af0898a --- /dev/null +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + + + + + diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs new file mode 100644 index 0000000000..02465296fa --- /dev/null +++ b/TUnit.TestProject/Tests.cs @@ -0,0 +1,17 @@ +using TUnit.Core.Attributes; + +namespace TUnit.TestProject; + +public class Tests +{ + // [SetUp] + // public void Setup() + // { + // } + + [Test] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/TUnit.sln b/TUnit.sln new file mode 100644 index 0000000000..8e93b5d74c --- /dev/null +++ b/TUnit.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit", "TUnit\TUnit.csproj", "{45A310AD-151B-4E0F-8A2C-FC55D31B16BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.TestAdapter", "TUnit.TestAdapter\TUnit.TestAdapter.csproj", "{37BF09F2-8CDA-4388-A13A-AB24572AACD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Core", "TUnit.Core\TUnit.Core.csproj", "{252CD110-7923-403F-9CCA-827E7352BF54}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.TestProject", "TUnit.TestProject\TUnit.TestProject.csproj", "{2F9038D3-96AD-4D86-B06B-9E59C2B941A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Assertions", "TUnit.Assertions\TUnit.Assertions.csproj", "{1F1276E0-0DB5-4CD4-BF4B-74760E50B923}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Pipeline", "TUnit.Pipeline\TUnit.Pipeline.csproj", "{527337F5-0F78-4141-901C-72EA81222AB1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {45A310AD-151B-4E0F-8A2C-FC55D31B16BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45A310AD-151B-4E0F-8A2C-FC55D31B16BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45A310AD-151B-4E0F-8A2C-FC55D31B16BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45A310AD-151B-4E0F-8A2C-FC55D31B16BD}.Release|Any CPU.Build.0 = Release|Any CPU + {37BF09F2-8CDA-4388-A13A-AB24572AACD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37BF09F2-8CDA-4388-A13A-AB24572AACD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37BF09F2-8CDA-4388-A13A-AB24572AACD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37BF09F2-8CDA-4388-A13A-AB24572AACD5}.Release|Any CPU.Build.0 = Release|Any CPU + {252CD110-7923-403F-9CCA-827E7352BF54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {252CD110-7923-403F-9CCA-827E7352BF54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {252CD110-7923-403F-9CCA-827E7352BF54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {252CD110-7923-403F-9CCA-827E7352BF54}.Release|Any CPU.Build.0 = Release|Any CPU + {2F9038D3-96AD-4D86-B06B-9E59C2B941A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F9038D3-96AD-4D86-B06B-9E59C2B941A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F9038D3-96AD-4D86-B06B-9E59C2B941A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F9038D3-96AD-4D86-B06B-9E59C2B941A8}.Release|Any CPU.Build.0 = Release|Any CPU + {1F1276E0-0DB5-4CD4-BF4B-74760E50B923}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F1276E0-0DB5-4CD4-BF4B-74760E50B923}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F1276E0-0DB5-4CD4-BF4B-74760E50B923}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F1276E0-0DB5-4CD4-BF4B-74760E50B923}.Release|Any CPU.Build.0 = Release|Any CPU + {527337F5-0F78-4141-901C-72EA81222AB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {527337F5-0F78-4141-901C-72EA81222AB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {527337F5-0F78-4141-901C-72EA81222AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {527337F5-0F78-4141-901C-72EA81222AB1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj new file mode 100644 index 0000000000..d75a0cf9c9 --- /dev/null +++ b/TUnit/TUnit.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + From ecdda9d76c3882689316ba5f8ffc7bdae997d578 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:31:12 +0000 Subject: [PATCH 002/124] Pipeline fix --- TUnit.Assertions/Is.cs | 4 +-- TUnit.Assertions/Is_Strings.cs | 2 +- .../Modules/AddLocalNuGetDirectoryModule.cs | 10 +++++++ .../Modules/AddReferencesToTestProject.cs | 16 +++++++++- TUnit.Pipeline/Modules/CleanProjectsModule.cs | 29 +++++++++++++++++++ .../MoveNuGetPackagesToLocalSourceModule.cs | 6 ++++ .../Modules/PackTUnitFilesModule.cs | 16 +++++++--- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 13 ++++++--- TUnit.TestProject/TUnit.TestProject.csproj | 6 +++- TUnit.TestProject/Tests.cs | 4 ++- TUnit/TUnit.csproj | 1 + 11 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 TUnit.Pipeline/Modules/CleanProjectsModule.cs diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index c3d437ed41..004b56d802 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -4,12 +4,12 @@ namespace TUnit.Assertions; public static partial class Is { - public static IAssertCondition EqualTo(T expected) + public static AssertCondition EqualTo(T expected) { return new EqualsAssertCondition(expected); } - public static IAssertCondition SameReference(T expected) + public static AssertCondition SameReference(T expected) { return new SameReferenceAssertCondition(expected); } diff --git a/TUnit.Assertions/Is_Strings.cs b/TUnit.Assertions/Is_Strings.cs index c553cc9b70..5ecf030d19 100644 --- a/TUnit.Assertions/Is_Strings.cs +++ b/TUnit.Assertions/Is_Strings.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions; public static partial class Is { - public static IAssertCondition EqualTo(string expected, StringComparison stringComparison = StringComparison.Ordinal) + public static AssertCondition EqualTo(string expected, StringComparison stringComparison = StringComparison.Ordinal) { return new StringEqualsAssertCondition(expected, stringComparison); } diff --git a/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs b/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs index fbd3b7a9da..1ca62e5dbf 100644 --- a/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs +++ b/TUnit.Pipeline/Modules/AddLocalNuGetDirectoryModule.cs @@ -14,6 +14,16 @@ public class AddLocalNuGetDirectoryModule : Module { var directoryResult = await GetModule(); + var currentNuGetSources = await context.DotNet() + .Nuget + .List + .Source(token: cancellationToken); + + if (currentNuGetSources.StandardOutput.Contains(directoryResult.Value!)) + { + return currentNuGetSources; + } + return await context.DotNet().Nuget.Add .Source(new DotNetNugetAddSourceOptions(directoryResult.Value!), cancellationToken); } diff --git a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs index 19f7effa90..303a46f414 100644 --- a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs +++ b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs @@ -7,6 +7,7 @@ using ModularPipelines.Git.Extensions; using ModularPipelines.Models; using ModularPipelines.Modules; +using File = ModularPipelines.FileSystem.File; namespace TUnit.Pipeline.Modules; [DependsOn] @@ -22,15 +23,28 @@ public class AddReferencesToTestProject : Module await projects.Value! .ForEachAsync( - x => context.DotNet().Remove.Package(new DotNetRemovePackageOptions(x.Name), cancellationToken), + x => RemovePackage(context, cancellationToken, testProject, x), cancellationToken: cancellationToken) .ProcessOneAtATime(); return await projects.Value! + .Where(x => x.Name == "TUnit.TestAdapter") .SelectAsync(async x => await context.DotNet().Add.Package(new DotNetAddPackageOptions(testProject, x.Name) { Version = x.Version, Source = localNugetDirectory.Value! }, cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); } + + private static async Task RemovePackage(IPipelineContext context, CancellationToken cancellationToken, File testProject, PackedProject x) + { + var existingPackages = await context.DotNet().List.Package(new DotNetListPackageOptions(testProject), cancellationToken); + + if (!existingPackages.StandardOutput.Contains($" > {x.Name} ")) + { + return; + } + + await context.DotNet().Remove.Package(new DotNetRemovePackageOptions(testProject, x.Name), cancellationToken); + } } \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/CleanProjectsModule.cs b/TUnit.Pipeline/Modules/CleanProjectsModule.cs new file mode 100644 index 0000000000..370a5fdd9d --- /dev/null +++ b/TUnit.Pipeline/Modules/CleanProjectsModule.cs @@ -0,0 +1,29 @@ +using EnumerableAsyncProcessor.Extensions; +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Models; +using ModularPipelines.Modules; +using ModularPipelines.Attributes; + +namespace TUnit.Pipeline.Modules; +[DependsOn] +public class CleanProjectsModule : Module +{ + protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var projects = await GetModule(); + return await projects.Value!.SelectAsync(x => context.DotNet().Clean(new DotNetCleanOptions(x), cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); + } +} + +[DependsOn] +[DependsOn] +public class BuildProjectsModule : Module +{ + protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var projects = await GetModule(); + return await projects.Value!.SelectAsync(x => context.DotNet().Build(new DotNetBuildOptions(x), cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs b/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs index bbffe9f141..abfc7cb521 100644 --- a/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs +++ b/TUnit.Pipeline/Modules/MoveNuGetPackagesToLocalSourceModule.cs @@ -8,12 +8,18 @@ namespace TUnit.Pipeline.Modules; [DependsOn] +[DependsOn] public class MoveNuGetPackagesToLocalSourceModule : Module> { protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) { var localNugetDirectory = await GetModule(); + foreach (var file in localNugetDirectory.Value!.ListFiles().Where(x => x.Name.Contains("TUnit"))) + { + file.Delete(); + } + var nugetPackages = context.Git().RootDirectory .GetFiles(x => x.Extension is ".nupkg" or ".snupkg"); diff --git a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs index 26395b55f3..d7ecb1d172 100644 --- a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs +++ b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs @@ -8,6 +8,7 @@ namespace TUnit.Pipeline.Modules; [DependsOn] +[DependsOn] public class PackTUnitFilesModule : Module> { protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) @@ -18,10 +19,17 @@ public class PackTUnitFilesModule : Module> var version = $"0.0.1-alpha{guid}"; var packedProjects = await projects.Value!.SelectAsync(async project => - { - return await context.DotNet().Pack(new DotNetPackOptions(project) { Properties = new[] { new KeyValue("Version", version), new KeyValue("PackageVersion", version) } }, cancellationToken); - }, cancellationToken: cancellationToken).ProcessOneAtATime(); + { + return await context.DotNet() + .Pack( + new DotNetPackOptions(project) + { + Properties = new[] + { new KeyValue("Version", version), new KeyValue("PackageVersion", version) } + }, cancellationToken); + }, cancellationToken: cancellationToken) + .ProcessOneAtATime(); - return projects.Value!.Select(x => new PackedProject(x.Name, version)).ToList(); + return projects.Value!.Select(x => new PackedProject(x.NameWithoutExtension, version)).ToList(); } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index e01dcca5d4..fa55d26432 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -5,17 +5,22 @@ enable enable default + true Library - true - - + - + + + + + + + diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index d89af0898a..1c10300534 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,7 +11,11 @@ - + + + all + runtime; build; native; contentfiles; analyzers + diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 02465296fa..fcb3f98e50 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,3 +1,4 @@ +using TUnit.Assertions; using TUnit.Core.Attributes; namespace TUnit.TestProject; @@ -12,6 +13,7 @@ public class Tests [Test] public void Test1() { - + var one = "1"; + Assert.That(one, Is.EqualTo("1")); } } \ No newline at end of file diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index d75a0cf9c9..73ce60e529 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -7,6 +7,7 @@ + From 1222d7c4db372ada89d4cf791e30cdaccfd91e69 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:31:59 +0000 Subject: [PATCH 003/124] Fixes --- .../Modules/AddReferencesToTestProject.cs | 2 +- TUnit.Pipeline/TUnit.Pipeline.csproj | 4 +- TUnit.TestAdapter/AssemblyLoader.cs | 3 +- TUnit.TestAdapter/AsyncTestExecutor.cs | 54 +++++++---- .../ReflectionMetadataProvider.cs | 88 +++++++++++++++++ TUnit.TestAdapter/SourceLocationHelper.cs | 95 +++++++++++++++++-- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 18 +++- TUnit.TestAdapter/TUnit.TestAdapter.props | 10 ++ TUnit.TestAdapter/TestCollector.cs | 1 + TUnit.TestAdapter/TestDiscoverer.cs | 8 +- TUnit.TestAdapter/TestExecutor.cs | 1 + TUnit.TestAdapter/TestsLoader.cs | 5 +- TUnit.TestProject/TUnit.TestProject.csproj | 10 +- TUnit.TestProject/Tests.cs | 23 +++++ 14 files changed, 281 insertions(+), 41 deletions(-) create mode 100644 TUnit.TestAdapter/ReflectionMetadataProvider.cs create mode 100644 TUnit.TestAdapter/TUnit.TestAdapter.props diff --git a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs index 303a46f414..da7f0da703 100644 --- a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs +++ b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs @@ -32,7 +32,7 @@ await projects.Value! .SelectAsync(async x => await context.DotNet().Add.Package(new DotNetAddPackageOptions(testProject, x.Name) { Version = x.Version, - Source = localNugetDirectory.Value! + Source = localNugetDirectory.Value!, }, cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); } diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj index 3590d330fc..7579ccbe99 100644 --- a/TUnit.Pipeline/TUnit.Pipeline.csproj +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/TUnit.TestAdapter/AssemblyLoader.cs b/TUnit.TestAdapter/AssemblyLoader.cs index b4bdeaf457..529d0d08af 100644 --- a/TUnit.TestAdapter/AssemblyLoader.cs +++ b/TUnit.TestAdapter/AssemblyLoader.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.Loader; namespace TUnit.TestAdapter; @@ -13,7 +14,7 @@ internal class AssemblyLoader try { - return Assembly.LoadFrom(assemblyPath); + return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); } catch { diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 12455e55c4..cc8bbd3e7e 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -36,6 +36,11 @@ public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runCon await foreach (var testCase in ProcessQueue(queue, frameworkHandle)) { + if (cancellationTokenSource.IsCancellationRequested) + { + break; + } + executingTests.Add(testCase); SetupRunOneTimeTearDownForClass(testCase, allTestsOrderedByClass, executingTests); @@ -79,7 +84,7 @@ private async IAsyncEnumerable ProcessQueue(Queue queue, I { while (queue.Count > 0) { - if (_canRunAnotherTest) + if (_canRunAnotherTest && !cancellationTokenSource.IsCancellationRequested) { var test = queue.Dequeue(); @@ -87,6 +92,10 @@ private async IAsyncEnumerable ProcessQueue(Queue queue, I yield return new ProcessingTest(test, @class, ProcessTest(test, @class, frameworkHandle)); } + else if (cancellationTokenSource.IsCancellationRequested) + { + break; + } else { await Task.Delay(100); @@ -159,12 +168,7 @@ private async Task ExecuteTest(Test test, object @class) { await ExecuteSetUps(@class); - var methodExecutionObject = test.MethodInfo.Invoke(@class, test.Arguments); - - if (methodExecutionObject is Task task) - { - await task; - } + await InvokeMethod(@class, test.MethodInfo, test.Arguments); await ExecuteTearDowns(@class); } @@ -180,12 +184,7 @@ private async Task ExecuteSetUps(object @class) foreach (var setUpMethod in setUpMethods) { - var result = setUpMethod.Invoke(@class, null); - - if (result is Task task) - { - await task; - } + await InvokeMethod(@class, setUpMethod, null); } } @@ -202,12 +201,7 @@ private async Task ExecuteTearDowns(object @class) { try { - var result = tearDownMethod.Invoke(@class, null); - - if (result is Task task) - { - await task; - } + await InvokeMethod(@class, tearDownMethod, null); } catch (Exception e) { @@ -291,4 +285,26 @@ private async Task GetCpuUsageForProcess() return cpuUsageTotal * 100; } + + public async Task InvokeMethod(object @class, MethodInfo methodInfo, object?[]? arguments) + { + try + { + var result = methodInfo.Invoke(@class, arguments); + + if (result is Task task) + { + await task; + } + } + catch (TargetInvocationException e) + { + if (e.InnerException is null) + { + throw; + } + + throw e.InnerException; + } + } } \ No newline at end of file diff --git a/TUnit.TestAdapter/ReflectionMetadataProvider.cs b/TUnit.TestAdapter/ReflectionMetadataProvider.cs new file mode 100644 index 0000000000..24fa4b2aa2 --- /dev/null +++ b/TUnit.TestAdapter/ReflectionMetadataProvider.cs @@ -0,0 +1,88 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace TUnit.TestAdapter +{ + internal sealed class ReflectionMetadataProvider + { + public Type? GetDeclaringType(string assemblyPath, string reflectedTypeName, string methodName) + { + var type = TryGetSingleMethod(assemblyPath, reflectedTypeName, methodName)?.DeclaringType; + if (type == null) + { + return null; + } + + if (type.IsConstructedGenericType) + { + type = type.GetGenericTypeDefinition(); + } + + return type; + } + + public Type? GetStateMachineType(string assemblyPath, string reflectedTypeName, string methodName) + { + var method = TryGetSingleMethod(assemblyPath, reflectedTypeName, methodName); + if (method == null) + { + return null; + } + + var candidate = null as Type; + + foreach (var attributeData in CustomAttributeData.GetCustomAttributes(method)) + { + for (var current = attributeData.Constructor.DeclaringType; current != null; current = current.GetTypeInfo().BaseType) + { + if (current.FullName != "System.Runtime.CompilerServices.StateMachineAttribute") + { + continue; + } + + var parameters = attributeData.Constructor.GetParameters(); + for (var i = 0; i < parameters.Length; i++) + { + if (parameters[i].Name != "stateMachineType") + { + continue; + } + + if (attributeData.ConstructorArguments[i].Value is Type argument) + { + if (candidate != null) + { + return null; + } + + candidate = argument; + } + } + } + } + + return candidate; + } + + private static MethodInfo? TryGetSingleMethod(string assemblyPath, string reflectedTypeName, string methodName) + { + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); + + var type = assembly.GetType(reflectedTypeName, throwOnError: false); + + var methods = type?.GetMethods().Where(m => m.Name == methodName).Take(2).ToList(); + return methods?.Count == 1 ? methods[0] : null; + } + catch (FileNotFoundException) + { + return null; + } + } + + public void Dispose() + { + } + } +} diff --git a/TUnit.TestAdapter/SourceLocationHelper.cs b/TUnit.TestAdapter/SourceLocationHelper.cs index 46e8bb4a01..1bdb79429b 100644 --- a/TUnit.TestAdapter/SourceLocationHelper.cs +++ b/TUnit.TestAdapter/SourceLocationHelper.cs @@ -1,32 +1,40 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; namespace TUnit.TestAdapter; -public class SourceLocationHelper +public class SourceLocationHelper : IDisposable { private readonly IMessageLogger? _logger; private static readonly SourceLocation EmptySourceLocation = new(null, 0, 0); + private readonly ReflectionMetadataProvider _metadataProvider; + + private readonly Dictionary _sessionsByAssemblyPath = new (StringComparer.OrdinalIgnoreCase); public SourceLocationHelper(IMessageLogger? logger) { _logger = logger; + _metadataProvider = new ReflectionMetadataProvider(); } - public SourceLocation GetSourceLocation(DiaSession diaSession, string className, string methodName) + public SourceLocation GetSourceLocation(string assemblyLocation, string className, string methodName) { try { - var navigationData = diaSession.GetNavigationDataForMethod(className, methodName); + var navigationData = TryGetNavigationData(assemblyLocation, className, methodName); if (navigationData is null) { _logger?.SendMessage(TestMessageLevel.Error, $"No navigation data found for {className}.{methodName}"); - + _logger?.SendMessage(TestMessageLevel.Error, $"Assembly: {assemblyLocation}"); + return EmptySourceLocation; } - + + _logger?.SendMessage(TestMessageLevel.Informational, $"Navigation data found for {className}.{methodName}"); + return new SourceLocation(navigationData.FileName, navigationData.MinLineNumber, navigationData.MaxLineNumber); } catch (Exception e) @@ -37,4 +45,79 @@ public SourceLocation GetSourceLocation(DiaSession diaSession, string className, return EmptySourceLocation; } } + + private SourceLocation? TryGetNavigationData(string assemblyLocation, string className, string methodName) + { + var sessionData = TryGetSessionData(assemblyLocation, className, methodName); + + if (sessionData != null) + { + return sessionData; + } + + var stateMachine = + _metadataProvider.GetStateMachineType(assemblyLocation, className, methodName); + + if (stateMachine != null) + { + sessionData = TryGetSessionData(stateMachine.Assembly.Location, stateMachine.FullName!, "MoveNext"); + + if (sessionData != null) + { + return sessionData; + } + } + + var declaringType2 = + _metadataProvider.GetStateMachineType(assemblyLocation, className, methodName); + + if (declaringType2 != null) + { + sessionData = TryGetSessionData(declaringType2.Assembly.Location, declaringType2.FullName!, methodName); + + if (sessionData != null) + { + return sessionData; + } + } + + return null; + } + + private Type? DoWithBreaker(string assemblyLocation, Func method, string declaringTypeName, string methodName) + { + try + { + return method.Invoke(assemblyLocation, declaringTypeName, methodName); + } + catch + { + // Ignored + } + + return null; + } + + private SourceLocation? TryGetSessionData(string assemblyPath, string declaringTypeFullName, string methodName) + { + if (!_sessionsByAssemblyPath.TryGetValue(assemblyPath, out var session)) + { + session = new DiaSession(assemblyPath); + _sessionsByAssemblyPath.Add(assemblyPath, session); + } + + var data = session.GetNavigationData(declaringTypeFullName, methodName); + + return string.IsNullOrEmpty(data?.FileName) + ? null + : new SourceLocation(data.FileName, data.MinLineNumber, data.MaxLineNumber); + } + + public void Dispose() + { + foreach (var diaSession in _sessionsByAssemblyPath.Values) + { + diaSession.Dispose(); + } + } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index fa55d26432..de159ad477 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -17,10 +17,24 @@ - + + $(TargetsForTfmSpecificContentInPackage);_AddToOutput + + + - + + + + + + + + PreserveNewest + + + diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.props b/TUnit.TestAdapter/TUnit.TestAdapter.props new file mode 100644 index 0000000000..815f953031 --- /dev/null +++ b/TUnit.TestAdapter/TUnit.TestAdapter.props @@ -0,0 +1,10 @@ + + + + + TUnit.TestAdapter.dll + PreserveNewest + true + + + \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index dcadf7c83c..ba26cb590c 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -26,6 +26,7 @@ public IEnumerable TestsFromSources(IEnumerable sources) var testsLoader = new TestsLoader(_messageLogger); var tests = sources + .Select(source => Path.IsPathRooted(source) ? source : Path.Combine(Directory.GetCurrentDirectory(), source)) .Select(assemblyLoader.LoadByPath) .OfType() .Select(x => new TypeInformation(x)) diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index cebbe8170c..e68c77a891 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using System.Diagnostics; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.TestAdapter.Constants; @@ -17,6 +18,11 @@ public void DiscoverTests(IEnumerable sources, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) { + if (!Debugger.IsAttached) + { + Debugger.Launch(); + } + var testCollector = new TestCollector(logger); foreach (var test in testCollector.TestsFromSources(sources)) diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index 9bb266b7c8..6c4870509b 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -15,6 +15,7 @@ public class TestExecutor : ITestExecutor2 public TestExecutor() { + Console.CancelKeyPress += (_, _) => _cancellationTokenSource.Cancel(); _asyncTestExecutor = new AsyncTestExecutor(_cancellationTokenSource); } diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 0a3af6f7f6..56df6f5b98 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -1,5 +1,4 @@ using System.Reflection; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; using TUnit.Core.Attributes; @@ -18,8 +17,6 @@ public TestsLoader(IMessageLogger? messageLogger) public IEnumerable GetTests(TypeInformation typeInformation) { - using var diaSession = new DiaSession(typeInformation.Assembly.Location); - var methods = typeInformation.Types.SelectMany(x => x.GetMethods()); foreach (var methodInfo in methods) @@ -30,7 +27,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) } var sourceLocation = _sourceLocationHelper - .GetSourceLocation(diaSession, methodInfo.DeclaringType!.Name, methodInfo.Name); + .GetSourceLocation(typeInformation.Assembly.Location, methodInfo.DeclaringType!.FullName!, methodInfo.Name); foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) { diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 1c10300534..82ce5f0830 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -4,7 +4,6 @@ net6.0 enable enable - false true @@ -12,13 +11,14 @@ - - all - runtime; build; native; contentfiles; analyzers - + + + + + diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index fcb3f98e50..51d7657552 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -16,4 +16,27 @@ public void Test1() var one = "1"; Assert.That(one, Is.EqualTo("1")); } + + [Test] + public void Test2() + { + var one = "2"; + Assert.That(one, Is.EqualTo("1")); + } + + [Test] + public async Task Test3() + { + await Task.Yield(); + var one = "1"; + Assert.That(one, Is.EqualTo("1")); + } + + [Test] + public async Task Test4() + { + await Task.Yield(); + var one = "2"; + Assert.That(one, Is.EqualTo("1")); + } } \ No newline at end of file From ff1adb3b712d537bfcde5cab5b3c542f81dc6a71 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:41:18 +0000 Subject: [PATCH 004/124] Fix --- TUnit.TestAdapter/AsyncTestExecutor.cs | 21 +++++++++------------ TUnit.TestAdapter/SourceLocationHelper.cs | 4 +--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index cc8bbd3e7e..fad46e1213 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -1,6 +1,8 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Globalization; using System.Reflection; +using System.Runtime.ExceptionServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using TUnit.Core; @@ -168,7 +170,7 @@ private async Task ExecuteTest(Test test, object @class) { await ExecuteSetUps(@class); - await InvokeMethod(@class, test.MethodInfo, test.Arguments); + await InvokeMethod(@class, test.MethodInfo, BindingFlags.Default, test.Arguments); await ExecuteTearDowns(@class); } @@ -184,7 +186,7 @@ private async Task ExecuteSetUps(object @class) foreach (var setUpMethod in setUpMethods) { - await InvokeMethod(@class, setUpMethod, null); + await InvokeMethod(@class, setUpMethod, BindingFlags.Default, null); } } @@ -201,7 +203,7 @@ private async Task ExecuteTearDowns(object @class) { try { - await InvokeMethod(@class, tearDownMethod, null); + await InvokeMethod(@class, tearDownMethod, BindingFlags.Default, null); } catch (Exception e) { @@ -224,12 +226,7 @@ private async Task ExecuteOneTimeSetUps(object @class) foreach (var oneTimeSetUpMethod in oneTimeSetUpMethods) { - var result = oneTimeSetUpMethod.Invoke(null, BindingFlags.Static | BindingFlags.Public, null, null, null); - - if (result is Task task) - { - await task; - } + await InvokeMethod(@class, oneTimeSetUpMethod, BindingFlags.Static | BindingFlags.Public, null); } } @@ -286,11 +283,11 @@ private async Task GetCpuUsageForProcess() return cpuUsageTotal * 100; } - public async Task InvokeMethod(object @class, MethodInfo methodInfo, object?[]? arguments) + public async Task InvokeMethod(object @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) { try { - var result = methodInfo.Invoke(@class, arguments); + var result = methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture); if (result is Task task) { @@ -304,7 +301,7 @@ public async Task InvokeMethod(object @class, MethodInfo methodInfo, object?[]? throw; } - throw e.InnerException; + ExceptionDispatchInfo.Capture(e.InnerException).Throw(); } } } \ No newline at end of file diff --git a/TUnit.TestAdapter/SourceLocationHelper.cs b/TUnit.TestAdapter/SourceLocationHelper.cs index 1bdb79429b..31c07c271a 100644 --- a/TUnit.TestAdapter/SourceLocationHelper.cs +++ b/TUnit.TestAdapter/SourceLocationHelper.cs @@ -32,9 +32,7 @@ public SourceLocation GetSourceLocation(string assemblyLocation, string classNam return EmptySourceLocation; } - - _logger?.SendMessage(TestMessageLevel.Informational, $"Navigation data found for {className}.{methodName}"); - + return new SourceLocation(navigationData.FileName, navigationData.MinLineNumber, navigationData.MaxLineNumber); } catch (Exception e) From e0256ed7061cc280de51696efa547bc7c0b9049a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:23:42 +0000 Subject: [PATCH 005/124] Fix --- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 4 ++-- TUnit.TestAdapter/TUnit.TestAdapter.props | 10 ---------- TUnit.TestAdapter/TUnit.TestAdapter.targets | 9 +++++++++ TUnit.TestProject/TUnit.TestProject.csproj | 9 ++++----- TUnit/TUnit.csproj | 1 - 5 files changed, 15 insertions(+), 18 deletions(-) delete mode 100644 TUnit.TestAdapter/TUnit.TestAdapter.props create mode 100644 TUnit.TestAdapter/TUnit.TestAdapter.targets diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index de159ad477..2885b31d71 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -23,7 +23,7 @@ - + @@ -32,7 +32,7 @@ - + PreserveNewest diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.props b/TUnit.TestAdapter/TUnit.TestAdapter.props deleted file mode 100644 index 815f953031..0000000000 --- a/TUnit.TestAdapter/TUnit.TestAdapter.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - TUnit.TestAdapter.dll - PreserveNewest - true - - - \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.targets b/TUnit.TestAdapter/TUnit.TestAdapter.targets new file mode 100644 index 0000000000..3f46f4825c --- /dev/null +++ b/TUnit.TestAdapter/TUnit.TestAdapter.targets @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 82ce5f0830..9fe0f45764 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,14 +11,13 @@ - - - - + + all + runtime; build; native; contentfiles; analyzers + - diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index 73ce60e529..45f6ff7b23 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -9,7 +9,6 @@ - From 4ba0d5c6c1986db882d50b550e95ce42721cdb86 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:30:19 +0000 Subject: [PATCH 006/124] Redundant --- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 16 ---------------- TUnit.TestAdapter/TUnit.TestAdapter.targets | 9 --------- TUnit.TestProject/TUnit.TestProject.csproj | 6 +++--- 3 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 TUnit.TestAdapter/TUnit.TestAdapter.targets diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index 2885b31d71..794c4b1b65 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -17,24 +17,8 @@ - - $(TargetsForTfmSpecificContentInPackage);_AddToOutput - - - - - - - - - - - - PreserveNewest - - diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.targets b/TUnit.TestAdapter/TUnit.TestAdapter.targets deleted file mode 100644 index 3f46f4825c..0000000000 --- a/TUnit.TestAdapter/TUnit.TestAdapter.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 9fe0f45764..07556ec8e6 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,9 +11,9 @@ - - all - runtime; build; native; contentfiles; analyzers + + runtime; build; native; contentfiles; analyzers; buildtransitive + all From 67a94ff9d43e5a8a5559dd04b87d230d4f8a9c22 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:36:54 +0000 Subject: [PATCH 007/124] Remove debugger call --- TUnit.TestAdapter/TestDiscoverer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index e68c77a891..00aa9446c8 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -18,11 +18,6 @@ public void DiscoverTests(IEnumerable sources, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) { - if (!Debugger.IsAttached) - { - Debugger.Launch(); - } - var testCollector = new TestCollector(logger); foreach (var test in testCollector.TestsFromSources(sources)) From 94ffc9d072d66254b536ef158ac5d3e1ea8df3d5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:24:17 +0000 Subject: [PATCH 008/124] Fix --- TUnit.Core/ParameterArgument.cs | 3 ++ TUnit.Core/Test.cs | 14 ++++---- TUnit.TestAdapter/AsyncTestExecutor.cs | 2 +- .../Extensions/TestExtensions.cs | 15 ++++++-- TUnit.TestAdapter/SourceLocationHelper.cs | 36 ++++--------------- TUnit.TestAdapter/TestCollector.cs | 11 ++---- TUnit.TestAdapter/TestDiscoverer.cs | 2 +- TUnit.TestAdapter/TestsLoader.cs | 11 ++---- TUnit.TestProject/TUnit.TestProject.csproj | 2 +- 9 files changed, 38 insertions(+), 58 deletions(-) create mode 100644 TUnit.Core/ParameterArgument.cs diff --git a/TUnit.Core/ParameterArgument.cs b/TUnit.Core/ParameterArgument.cs new file mode 100644 index 0000000000..32b782aba1 --- /dev/null +++ b/TUnit.Core/ParameterArgument.cs @@ -0,0 +1,3 @@ +namespace TUnit.Core; + +public record ParameterArgument(Type Type, object? Value); \ No newline at end of file diff --git a/TUnit.Core/Test.cs b/TUnit.Core/Test.cs index acf720e459..718a28b08b 100644 --- a/TUnit.Core/Test.cs +++ b/TUnit.Core/Test.cs @@ -7,7 +7,7 @@ public record Test { public Test(MethodInfo MethodInfo, SourceLocation SourceLocation, - object?[]? Arguments) + ParameterArgument[]? Arguments) { var classType = MethodInfo.DeclaringType!; @@ -20,7 +20,7 @@ public Test(MethodInfo MethodInfo, FullyQualifiedClassName = classType.FullName!; Assembly = classType.Assembly; Source = classType.Assembly.Location; - FullName = $"{classType.FullName}.{MethodInfo.Name}{GetArguments(Arguments)}"; + FullyQualifiedName = $"{classType.FullName}.{MethodInfo.Name}{GetParameterTypes(Arguments)}"; IsSkipped = MethodInfo.CustomAttributes .Concat(classType.CustomAttributes) .Any(x => x.AttributeType == typeof(SkipAttribute)); @@ -41,12 +41,12 @@ public Test(MethodInfo MethodInfo, public Assembly Assembly { get; } public string Source { get; } - public string FullName { get; } + public string FullyQualifiedName { get; } public MethodInfo MethodInfo { get; init; } public string? FileName { get; set; } public int MinLineNumber { get; set; } public int MaxLineNumber { get; set; } - public object?[]? Arguments { get; init; } + public ParameterArgument[]? Arguments { get; init; } public SourceLocation SourceLocation { get; } public bool IsSkipped { get; } @@ -57,16 +57,16 @@ public void Deconstruct(out MethodInfo methodInfo, out object?[]? arguments) arguments = Arguments; } - private static string GetArguments(object?[]? arguments) + public static string GetParameterTypes(ParameterArgument[]? arguments) { if (arguments is null) { return string.Empty; } - var argsAsString = arguments.Select(StringifyArgument); + var argsAsString = arguments.Select(arg => arg.Type.FullName!); - return $"({string.Join(", ", argsAsString)})"; + return $"({string.Join(',', argsAsString)})"; } private static string StringifyArgument(object? obj) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index fad46e1213..5a1e581754 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -63,7 +63,7 @@ private void SetupRunOneTimeTearDownForClass(ProcessingTest processingTest, var lastTestForClass = allTestsOrderedByClass.Last(x => x.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName); - if (processingTest.Test.FullName != lastTestForClass.FullName) + if (processingTest.Test.FullyQualifiedName != lastTestForClass.FullyQualifiedName) { return; } diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index 6a2da9ac39..c07b4056bd 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -8,12 +8,23 @@ public static class TestExtensions { public static TestCase ToTestCase(this Test test) { - return new TestCase(test.FullName, TestAdapterConstants.ExecutorUri, test.Source) + var testCase = new TestCase(test.FullyQualifiedName, TestAdapterConstants.ExecutorUri, test.Source) { DisplayName = test.TestName, Id = test.Id, CodeFilePath = test.FileName, - LineNumber = test.MinLineNumber + LineNumber = test.MinLineNumber, }; + + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), test.FullyQualifiedClassName); + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), test.MethodInfo.Name + Test.GetParameterTypes(test.Arguments)); + + return testCase; + } + + private static TestProperty GetOrRegisterTestProperty(string name) + { + return TestProperty.Find(name) + ?? TestProperty.Register(name, name, typeof(string), typeof(TestCase)); } } \ No newline at end of file diff --git a/TUnit.TestAdapter/SourceLocationHelper.cs b/TUnit.TestAdapter/SourceLocationHelper.cs index 31c07c271a..d9c16563d3 100644 --- a/TUnit.TestAdapter/SourceLocationHelper.cs +++ b/TUnit.TestAdapter/SourceLocationHelper.cs @@ -1,24 +1,16 @@ -using System.Reflection; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; namespace TUnit.TestAdapter; -public class SourceLocationHelper : IDisposable +public class SourceLocationHelper(IMessageLogger? logger) : IDisposable { - private readonly IMessageLogger? _logger; private static readonly SourceLocation EmptySourceLocation = new(null, 0, 0); - private readonly ReflectionMetadataProvider _metadataProvider; + private readonly ReflectionMetadataProvider _metadataProvider = new(); private readonly Dictionary _sessionsByAssemblyPath = new (StringComparer.OrdinalIgnoreCase); - public SourceLocationHelper(IMessageLogger? logger) - { - _logger = logger; - _metadataProvider = new ReflectionMetadataProvider(); - } - public SourceLocation GetSourceLocation(string assemblyLocation, string className, string methodName) { try @@ -27,8 +19,8 @@ public SourceLocation GetSourceLocation(string assemblyLocation, string classNam if (navigationData is null) { - _logger?.SendMessage(TestMessageLevel.Error, $"No navigation data found for {className}.{methodName}"); - _logger?.SendMessage(TestMessageLevel.Error, $"Assembly: {assemblyLocation}"); + logger?.SendMessage(TestMessageLevel.Error, $"No navigation data found for {className}.{methodName}"); + logger?.SendMessage(TestMessageLevel.Error, $"Assembly: {assemblyLocation}"); return EmptySourceLocation; } @@ -37,8 +29,8 @@ public SourceLocation GetSourceLocation(string assemblyLocation, string classNam } catch (Exception e) { - _logger?.SendMessage(TestMessageLevel.Error, $"Error retrieving source location for {className}.{methodName}"); - _logger?.SendMessage(TestMessageLevel.Error, e.ToString()); + logger?.SendMessage(TestMessageLevel.Error, $"Error retrieving source location for {className}.{methodName}"); + logger?.SendMessage(TestMessageLevel.Error, e.ToString()); return EmptySourceLocation; } @@ -81,20 +73,6 @@ public SourceLocation GetSourceLocation(string assemblyLocation, string classNam return null; } - - private Type? DoWithBreaker(string assemblyLocation, Func method, string declaringTypeName, string methodName) - { - try - { - return method.Invoke(assemblyLocation, declaringTypeName, methodName); - } - catch - { - // Ignored - } - - return null; - } private SourceLocation? TryGetSessionData(string assemblyPath, string declaringTypeFullName, string methodName) { diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index ba26cb590c..1e8cda95be 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -4,15 +4,8 @@ namespace TUnit.TestAdapter; -public class TestCollector +public class TestCollector(IMessageLogger? messageLogger) { - private readonly IMessageLogger? _messageLogger; - - public TestCollector(IMessageLogger? messageLogger) - { - _messageLogger = messageLogger; - } - public TestCollection CollectionFromSources(IEnumerable sources) { var sourcesAsList = sources.ToList(); @@ -23,7 +16,7 @@ public TestCollection CollectionFromSources(IEnumerable sources) public IEnumerable TestsFromSources(IEnumerable sources) { var assemblyLoader = new AssemblyLoader(); - var testsLoader = new TestsLoader(_messageLogger); + var testsLoader = new TestsLoader(messageLogger); var tests = sources .Select(source => Path.IsPathRooted(source) ? source : Path.Combine(Directory.GetCurrentDirectory(), source)) diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index 00aa9446c8..2e5b18a971 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -22,7 +22,7 @@ public void DiscoverTests(IEnumerable sources, foreach (var test in testCollector.TestsFromSources(sources)) { - logger.SendMessage(TestMessageLevel.Informational, "Test found: " + test.FullName); + logger.SendMessage(TestMessageLevel.Informational, "Test found: " + test.FullyQualifiedName); discoverySink.SendTestCase(test.ToTestCase()); } } diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 56df6f5b98..44b013589b 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -5,15 +5,10 @@ namespace TUnit.TestAdapter; -public class TestsLoader +public class TestsLoader(IMessageLogger? messageLogger) { private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute)]; - private readonly SourceLocationHelper _sourceLocationHelper; - - public TestsLoader(IMessageLogger? messageLogger) - { - _sourceLocationHelper = new SourceLocationHelper(messageLogger); - } + private readonly SourceLocationHelper _sourceLocationHelper = new(messageLogger); public IEnumerable GetTests(TypeInformation typeInformation) { @@ -31,7 +26,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) { - var arguments = testWithDataAttribute.ConstructorArguments.Select(x => x.Value); + var arguments = testWithDataAttribute.ConstructorArguments.Select(x => new ParameterArgument(x.ArgumentType, x.Value)); yield return new Test( MethodInfo: methodInfo, diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 07556ec8e6..b60af5d4e6 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,7 +11,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 7065bf3ec38abb533ebda34a73a4c446e37f7c09 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:38:27 +0000 Subject: [PATCH 009/124] Fix --- TUnit.TestAdapter/AsyncTestExecutor.cs | 4 +-- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 1 + TUnit.TestProject/TUnit.TestProject.csproj | 9 ++++--- TUnit.TestProject/Tests.cs | 31 ++++++++++++++++------ 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 5a1e581754..4668e2177c 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -170,7 +170,7 @@ private async Task ExecuteTest(Test test, object @class) { await ExecuteSetUps(@class); - await InvokeMethod(@class, test.MethodInfo, BindingFlags.Default, test.Arguments); + await InvokeMethod(@class, test.MethodInfo, BindingFlags.Default, test.Arguments?.Select(x => x.Value).ToArray()); await ExecuteTearDowns(@class); } @@ -283,7 +283,7 @@ private async Task GetCpuUsageForProcess() return cpuUsageTotal * 100; } - public async Task InvokeMethod(object @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) + private async Task InvokeMethod(object @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) { try { diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index 794c4b1b65..785d3f930e 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -10,6 +10,7 @@ + diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index b60af5d4e6..ecdf62100c 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,13 +11,14 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + + + + diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 51d7657552..6e6f21b708 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -13,30 +13,45 @@ public class Tests [Test] public void Test1() { - var one = "1"; - Assert.That(one, Is.EqualTo("1")); + var value = "1"; + Assert.That(value, Is.EqualTo("1")); } [Test] public void Test2() { - var one = "2"; - Assert.That(one, Is.EqualTo("1")); + var value = "2"; + Assert.That(value, Is.EqualTo("1")); } [Test] public async Task Test3() { await Task.Yield(); - var one = "1"; - Assert.That(one, Is.EqualTo("1")); + var value = "1"; + Assert.That(value, Is.EqualTo("1")); } [Test] public async Task Test4() { await Task.Yield(); - var one = "2"; - Assert.That(one, Is.EqualTo("1")); + var value = "2"; + Assert.That(value, Is.EqualTo("1")); + } + + [TestWithData("1")] + [TestWithData("2")] + public void ParameterisedTests1(string value) + { + Assert.That(value, Is.EqualTo("1")); + } + + [TestWithData("1")] + [TestWithData("2")] + public async Task ParameterisedTests2(string value) + { + await Task.Yield(); + Assert.That(value, Is.EqualTo("1")); } } \ No newline at end of file From b92d1261333fb0e4bd3b5dd96c77264a5bb5af99 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:43:35 +0000 Subject: [PATCH 010/124] Tweak --- TUnit.Core/Test.cs | 23 ++++++++----------- TUnit.TestAdapter/AsyncTestExecutor.cs | 2 +- .../Extensions/TestExtensions.cs | 2 +- TUnit.TestAdapter/TestsLoader.cs | 4 ++-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/TUnit.Core/Test.cs b/TUnit.Core/Test.cs index 718a28b08b..60b196ed6c 100644 --- a/TUnit.Core/Test.cs +++ b/TUnit.Core/Test.cs @@ -7,20 +7,22 @@ public record Test { public Test(MethodInfo MethodInfo, SourceLocation SourceLocation, - ParameterArgument[]? Arguments) + ParameterArgument[]? arguments) { var classType = MethodInfo.DeclaringType!; this.MethodInfo = MethodInfo; - this.Arguments = Arguments; this.SourceLocation = SourceLocation; + ParameterTypes = arguments?.Select(x => x.Type).ToArray(); + ArgumentValues = arguments?.Select(x => x.Value).ToArray(); + TestName = MethodInfo.Name; ClassName = classType.Name; FullyQualifiedClassName = classType.FullName!; Assembly = classType.Assembly; Source = classType.Assembly.Location; - FullyQualifiedName = $"{classType.FullName}.{MethodInfo.Name}{GetParameterTypes(Arguments)}"; + FullyQualifiedName = $"{classType.FullName}.{MethodInfo.Name}{GetParameterTypes(ParameterTypes)}"; IsSkipped = MethodInfo.CustomAttributes .Concat(classType.CustomAttributes) .Any(x => x.AttributeType == typeof(SkipAttribute)); @@ -46,25 +48,20 @@ public Test(MethodInfo MethodInfo, public string? FileName { get; set; } public int MinLineNumber { get; set; } public int MaxLineNumber { get; set; } - public ParameterArgument[]? Arguments { get; init; } + public Type[]? ParameterTypes { get; init; } + public object?[]? ArgumentValues { get; init; } public SourceLocation SourceLocation { get; } public bool IsSkipped { get; } - public void Deconstruct(out MethodInfo methodInfo, out object?[]? arguments) - { - methodInfo = MethodInfo; - arguments = Arguments; - } - - public static string GetParameterTypes(ParameterArgument[]? arguments) + public static string GetParameterTypes(Type[]? types) { - if (arguments is null) + if (types is null) { return string.Empty; } - var argsAsString = arguments.Select(arg => arg.Type.FullName!); + var argsAsString = types.Select(arg => arg.FullName!); return $"({string.Join(',', argsAsString)})"; } diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 4668e2177c..e0476c8dd6 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -170,7 +170,7 @@ private async Task ExecuteTest(Test test, object @class) { await ExecuteSetUps(@class); - await InvokeMethod(@class, test.MethodInfo, BindingFlags.Default, test.Arguments?.Select(x => x.Value).ToArray()); + await InvokeMethod(@class, test.MethodInfo, BindingFlags.Default, test.ArgumentValues?.ToArray()); await ExecuteTearDowns(@class); } diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index c07b4056bd..c7a25ca531 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -17,7 +17,7 @@ public static TestCase ToTestCase(this Test test) }; testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), test.FullyQualifiedClassName); - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), test.MethodInfo.Name + Test.GetParameterTypes(test.Arguments)); + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), test.MethodInfo.Name + Test.GetParameterTypes(test.ParameterTypes)); return testCase; } diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 44b013589b..b90b4ebf5a 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -31,7 +31,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) yield return new Test( MethodInfo: methodInfo, SourceLocation: sourceLocation, - Arguments: arguments.ToArray() + arguments: arguments.ToArray() ); } @@ -40,7 +40,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) yield return new Test( MethodInfo: methodInfo, SourceLocation: sourceLocation, - Arguments: null + arguments: null ); } } From 1109f69e4e8d2a7245ff7510c571f639f9a0a26e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:56:13 +0000 Subject: [PATCH 011/124] Fix --- TUnit.TestAdapter/TestsLoader.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index b90b4ebf5a..ea96745e5d 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Reflection.Metadata; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; using TUnit.Core.Attributes; @@ -26,13 +27,19 @@ public IEnumerable GetTests(TypeInformation typeInformation) foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) { - var arguments = testWithDataAttribute.ConstructorArguments.Select(x => new ParameterArgument(x.ArgumentType, x.Value)); + foreach (var customAttributeTypedArgument in testWithDataAttribute.ConstructorArguments) + { + var arguments = + (customAttributeTypedArgument.Value as IEnumerable) + ?.Select(x => new ParameterArgument(x.Value?.GetType()!, x.Value)) + .ToArray(); - yield return new Test( - MethodInfo: methodInfo, - SourceLocation: sourceLocation, - arguments: arguments.ToArray() - ); + yield return new Test( + MethodInfo: methodInfo, + SourceLocation: sourceLocation, + arguments: arguments + ); + } } if(methodInfo.CustomAttributes.Any(x => x.AttributeType == typeof(TestAttribute))) From 65af1207d2219201a3d36956a1b52f47d1a4df61 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:59:54 +0000 Subject: [PATCH 012/124] Fix --- TUnit.Core/Test.cs | 12 ++++++++++++ TUnit.TestAdapter/Extensions/TestExtensions.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/TUnit.Core/Test.cs b/TUnit.Core/Test.cs index 60b196ed6c..105b4626c7 100644 --- a/TUnit.Core/Test.cs +++ b/TUnit.Core/Test.cs @@ -18,6 +18,7 @@ public Test(MethodInfo MethodInfo, ArgumentValues = arguments?.Select(x => x.Value).ToArray(); TestName = MethodInfo.Name; + DisplayName = MethodInfo.Name + GetArgumentValues(); ClassName = classType.Name; FullyQualifiedClassName = classType.FullName!; Assembly = classType.Assembly; @@ -32,6 +33,16 @@ public Test(MethodInfo MethodInfo, MaxLineNumber = SourceLocation.MaxLineNumber; } + private string GetArgumentValues() + { + if (ArgumentValues == null) + { + return string.Empty; + } + + return $"({string.Join(',', ArgumentValues.Select(StringifyArgument))})"; + } + public Guid Id { get; } = Guid.NewGuid(); public string TestName { get; } @@ -53,6 +64,7 @@ public Test(MethodInfo MethodInfo, public SourceLocation SourceLocation { get; } public bool IsSkipped { get; } + public string DisplayName { get; } public static string GetParameterTypes(Type[]? types) { diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index c7a25ca531..c298dd3fc3 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -10,7 +10,7 @@ public static TestCase ToTestCase(this Test test) { var testCase = new TestCase(test.FullyQualifiedName, TestAdapterConstants.ExecutorUri, test.Source) { - DisplayName = test.TestName, + DisplayName = test.DisplayName, Id = test.Id, CodeFilePath = test.FileName, LineNumber = test.MinLineNumber, From 09ab4d9f43a44c5e55804ed3a02f755ba6194945 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:04:42 +0000 Subject: [PATCH 013/124] Fix --- TUnit.TestAdapter/AsyncTestExecutor.cs | 29 ++++++++++--------- .../Extensions/TestExtensions.cs | 1 + TUnit.TestAdapter/TestCollector.cs | 28 +++++++++++++++++- TUnit.TestAdapter/TestExecutor.cs | 17 ++++++++--- TUnit.TestAdapter/TestWithTestCase.cs | 6 ++++ 5 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 TUnit.TestAdapter/TestWithTestCase.cs diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index e0476c8dd6..4994c80590 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -18,14 +18,14 @@ public class AsyncTestExecutor(CancellationTokenSource cancellationTokenSource) private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); - public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) { var allTestsOrderedByClass = tests - .GroupBy(x => x.FullyQualifiedClassName) + .GroupBy(x => x.Test.FullyQualifiedClassName) .SelectMany(x => x.ToList()) .ToList(); - var queue = new Queue(allTestsOrderedByClass); + var queue = new Queue(allTestsOrderedByClass); if (queue.Count is 0) { @@ -57,13 +57,13 @@ public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runCon } private void SetupRunOneTimeTearDownForClass(ProcessingTest processingTest, - IEnumerable allTestsOrderedByClass, + IEnumerable allTestsOrderedByClass, IEnumerable executingTests) { var lastTestForClass = allTestsOrderedByClass.Last(x => - x.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName); + x.Test.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName); - if (processingTest.Test.FullyQualifiedName != lastTestForClass.FullyQualifiedName) + if (processingTest.Test.FullyQualifiedName != lastTestForClass.Test.FullyQualifiedName) { return; } @@ -82,7 +82,7 @@ private void SetupRunOneTimeTearDownForClass(ProcessingTest processingTest, }); } - private async IAsyncEnumerable ProcessQueue(Queue queue, ITestExecutionRecorder? frameworkHandle) + private async IAsyncEnumerable ProcessQueue(Queue queue, ITestExecutionRecorder? frameworkHandle) { while (queue.Count > 0) { @@ -90,9 +90,9 @@ private async IAsyncEnumerable ProcessQueue(Queue queue, I { var test = queue.Dequeue(); - var @class = CreateTestClass(test); + var @class = CreateTestClass(test.Test); - yield return new ProcessingTest(test, @class, ProcessTest(test, @class, frameworkHandle)); + yield return new ProcessingTest(test.Test, @class, ProcessTest(test, @class, frameworkHandle)); } else if (cancellationTokenSource.IsCancellationRequested) { @@ -105,9 +105,9 @@ private async IAsyncEnumerable ProcessQueue(Queue queue, I } } - private async Task ProcessTest(Test test, object @class, ITestExecutionRecorder? frameworkHandle) + private async Task ProcessTest(TestWithTestCase testWithTestCase, object @class, ITestExecutionRecorder? frameworkHandle) { - await ExecuteTestMethod(test, @class, frameworkHandle); + await ExecuteTestMethod(testWithTestCase, @class, frameworkHandle); } private static object CreateTestClass(Test test) @@ -115,10 +115,11 @@ private static object CreateTestClass(Test test) return Activator.CreateInstance(test.MethodInfo.DeclaringType!)!; } - private async ValueTask ExecuteTestMethod(Test test, object @class, ITestExecutionRecorder? frameworkHandle) + private async ValueTask ExecuteTestMethod(TestWithTestCase testWithTestCase, object @class, ITestExecutionRecorder? frameworkHandle) { - var testCase = test.ToTestCase(); - + var test = testWithTestCase.Test; + var testCase = testWithTestCase.TestCase; + if (test.IsSkipped) { frameworkHandle?.RecordEnd(testCase, TestOutcome.Skipped); diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index c298dd3fc3..17d249f1f4 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -14,6 +14,7 @@ public static TestCase ToTestCase(this Test test) Id = test.Id, CodeFilePath = test.FileName, LineNumber = test.MinLineNumber, + LocalExtensionData = test }; testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), test.FullyQualifiedClassName); diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 1e8cda95be..164415c9f1 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -1,6 +1,8 @@ using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; +using TUnit.TestAdapter.Extensions; namespace TUnit.TestAdapter; @@ -12,7 +14,31 @@ public TestCollection CollectionFromSources(IEnumerable sources) return new TestCollection(sourcesAsList, TestsFromSources(sourcesAsList)); } - + + public IEnumerable TestsFromTestCases(IEnumerable testCases) + { + var assemblyLoader = new AssemblyLoader(); + var testsLoader = new TestsLoader(messageLogger); + + foreach (var testCase in testCases) + { + var source = testCase.Source; + var assembly = assemblyLoader.LoadByPath(source); + + if (assembly is null) + { + continue; + } + + var tests = testsLoader.GetTests(new TypeInformation(assembly)); + + var matchingTest = tests.First(x => x.FullyQualifiedName == testCase.FullyQualifiedName + && x.DisplayName == testCase.DisplayName); + + yield return new TestWithTestCase(matchingTest, testCase); + } + } + public IEnumerable TestsFromSources(IEnumerable sources) { var assemblyLoader = new AssemblyLoader(); diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index 6c4870509b..74ef602d53 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -1,6 +1,8 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using TUnit.Core; using TUnit.TestAdapter.Constants; +using TUnit.TestAdapter.Extensions; namespace TUnit.TestAdapter; @@ -19,10 +21,16 @@ public TestExecutor() _asyncTestExecutor = new AsyncTestExecutor(_cancellationTokenSource); } - public void RunTests(IEnumerable? tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + public void RunTests(IEnumerable? testCases, IRunContext? runContext, IFrameworkHandle? frameworkHandle) { - RunTests(tests?.Select(x => x.Source), runContext, frameworkHandle); - } + if (testCases is null) + { + return; + } + + var testsWithTestCases = new TestCollector(frameworkHandle).TestsFromTestCases(testCases); + + _asyncTestExecutor.RunInAsyncContext(testsWithTestCases, runContext, frameworkHandle).GetAwaiter().GetResult(); } public void RunTests(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle) { @@ -31,7 +39,8 @@ public void RunTests(IEnumerable? sources, IRunContext? runContext, IFra return; } - var tests = new TestCollector(frameworkHandle).TestsFromSources(sources); + var tests = new TestCollector(frameworkHandle).TestsFromSources(sources) + .Select(x => new TestWithTestCase(x, x.ToTestCase())); _asyncTestExecutor.RunInAsyncContext(tests, runContext, frameworkHandle).GetAwaiter().GetResult(); } diff --git a/TUnit.TestAdapter/TestWithTestCase.cs b/TUnit.TestAdapter/TestWithTestCase.cs new file mode 100644 index 0000000000..5fe8dbf9dd --- /dev/null +++ b/TUnit.TestAdapter/TestWithTestCase.cs @@ -0,0 +1,6 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public record TestWithTestCase(Test Test, TestCase TestCase); \ No newline at end of file From 0c34995102b0fc50336b519a45ca766cae7b0600 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:13:41 +0000 Subject: [PATCH 014/124] Tweaks --- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 3 ++- TUnit.TestProject/TUnit.TestProject.csproj | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index 785d3f930e..52cea0b729 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -7,10 +7,11 @@ default true Library + true - + diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index ecdf62100c..2da8dd7aca 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,14 +11,14 @@ - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - + From c6edd6c4c48c039fceebbd959f85c02afa5af1e4 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:36:29 +0000 Subject: [PATCH 015/124] Fix queuing tests logic --- TUnit.TestAdapter/AsyncTestExecutor.cs | 11 ++++++++--- TUnit.TestProject/TUnit.TestProject.csproj | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 4994c80590..3dcd23582e 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -288,11 +288,11 @@ private async Task InvokeMethod(object @class, MethodInfo methodInfo, BindingFla { try { - var result = methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture); + var result = await Task.Run(() => methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture) as Task); - if (result is Task task) + if (result != null) { - await task; + await result; } } catch (TargetInvocationException e) @@ -305,4 +305,9 @@ private async Task InvokeMethod(object @class, MethodInfo methodInfo, BindingFla ExceptionDispatchInfo.Capture(e.InnerException).Throw(); } } + + private bool IsTask(Type type) + { + return type.IsAssignableTo(typeof(Task)); + } } \ No newline at end of file diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 2da8dd7aca..6354dae533 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -11,14 +11,14 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + + + - + From 2347fababcf37aa52ca16e13103091513959b94b Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:41:25 +0000 Subject: [PATCH 016/124] Skip logic fix --- TUnit.TestAdapter/AsyncTestExecutor.cs | 10 ++++++++++ TUnit.TestProject/Tests.cs | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 3dcd23582e..2f5a4b6912 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -122,7 +122,17 @@ private async ValueTask ExecuteTestMethod(TestWithTestCase testWithTestCase, obj if (test.IsSkipped) { + var skipTime = DateTimeOffset.Now; frameworkHandle?.RecordEnd(testCase, TestOutcome.Skipped); + frameworkHandle?.RecordResult(new TestResult(testCase) + { + Outcome = TestOutcome.Skipped, + DisplayName = test.TestName, + StartTime = skipTime, + EndTime = skipTime, + Duration = TimeSpan.Zero, + ComputerName = Environment.MachineName, + }); return; } diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 6e6f21b708..2a2b83431c 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -54,4 +54,19 @@ public async Task ParameterisedTests2(string value) await Task.Yield(); Assert.That(value, Is.EqualTo("1")); } + + [Test, Skip] + public void Skip1() + { + var value = "1"; + Assert.That(value, Is.EqualTo("1")); + } + + [Test, Skip] + public async Task Skip2() + { + await Task.Yield(); + var value = "1"; + Assert.That(value, Is.EqualTo("1")); + } } \ No newline at end of file From aad383fbdfde3c75aca5ce1847acc7ce0344f9a9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:48:28 +0000 Subject: [PATCH 017/124] Tweaks --- TUnit.TestAdapter/AsyncTestExecutor.cs | 1 - TUnit.TestAdapter/TestCollector.cs | 28 +++++++++++++++++++++++--- TUnit.TestAdapter/TestDiscoverer.cs | 3 +-- TUnit.TestAdapter/TestExecutor.cs | 10 +++++---- TUnit.TestAdapter/TestsLoader.cs | 1 - 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 2f5a4b6912..0c13f454ad 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -7,7 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using TUnit.Core; using TUnit.Core.Attributes; -using TUnit.TestAdapter.Extensions; namespace TUnit.TestAdapter; diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 164415c9f1..b63a41e54c 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -1,8 +1,8 @@ using System.Reflection; using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; -using TUnit.TestAdapter.Extensions; namespace TUnit.TestAdapter; @@ -15,7 +15,7 @@ public TestCollection CollectionFromSources(IEnumerable sources) return new TestCollection(sourcesAsList, TestsFromSources(sourcesAsList)); } - public IEnumerable TestsFromTestCases(IEnumerable testCases) + public IEnumerable TestsFromTestCases(IEnumerable testCases, ITestExecutionRecorder? testExecutionRecorder) { var assemblyLoader = new AssemblyLoader(); var testsLoader = new TestsLoader(messageLogger); @@ -27,18 +27,40 @@ public IEnumerable TestsFromTestCases(IEnumerable te if (assembly is null) { + MarkNotFound(testCase, testExecutionRecorder); continue; } var tests = testsLoader.GetTests(new TypeInformation(assembly)); - var matchingTest = tests.First(x => x.FullyQualifiedName == testCase.FullyQualifiedName + var matchingTest = tests.FirstOrDefault(x => x.FullyQualifiedName == testCase.FullyQualifiedName && x.DisplayName == testCase.DisplayName); + if (matchingTest is null) + { + MarkNotFound(testCase, testExecutionRecorder); + continue; + } + yield return new TestWithTestCase(matchingTest, testCase); } } + private void MarkNotFound(TestCase testCase, ITestExecutionRecorder? testExecutionRecorder) + { + var now = DateTimeOffset.Now; + + testExecutionRecorder?.RecordResult(new TestResult(testCase) + { + DisplayName = testCase.DisplayName, + Outcome = TestOutcome.NotFound, + Duration = TimeSpan.Zero, + StartTime = now, + EndTime = now, + ComputerName = Environment.MachineName + }); + } + public IEnumerable TestsFromSources(IEnumerable sources) { var assemblyLoader = new AssemblyLoader(); diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index 2e5b18a971..d08bac4ec3 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.TestAdapter.Constants; diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index 74ef602d53..857e5bc705 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -1,6 +1,5 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using TUnit.Core; using TUnit.TestAdapter.Constants; using TUnit.TestAdapter.Extensions; @@ -17,7 +16,6 @@ public class TestExecutor : ITestExecutor2 public TestExecutor() { - Console.CancelKeyPress += (_, _) => _cancellationTokenSource.Cancel(); _asyncTestExecutor = new AsyncTestExecutor(_cancellationTokenSource); } @@ -28,9 +26,13 @@ public void RunTests(IEnumerable? testCases, IRunContext? runContext, return; } - var testsWithTestCases = new TestCollector(frameworkHandle).TestsFromTestCases(testCases); + var testsWithTestCases = new TestCollector(frameworkHandle) + .TestsFromTestCases(testCases, frameworkHandle); - _asyncTestExecutor.RunInAsyncContext(testsWithTestCases, runContext, frameworkHandle).GetAwaiter().GetResult(); } + _asyncTestExecutor.RunInAsyncContext(testsWithTestCases, runContext, frameworkHandle) + .GetAwaiter() + .GetResult(); + } public void RunTests(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle) { diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index ea96745e5d..4bd2f97811 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Reflection.Metadata; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; using TUnit.Core.Attributes; From 86b006d12cb913535990289f04d463a96792dc3c Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:56:57 +0000 Subject: [PATCH 018/124] Fixes --- TUnit.Core/Attributes/SkipAttribute.cs | 10 +++++++++- TUnit.Core/Test.cs | 10 ++++++---- TUnit.TestAdapter/AsyncTestExecutor.cs | 2 ++ TUnit.TestProject/Tests.cs | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/TUnit.Core/Attributes/SkipAttribute.cs b/TUnit.Core/Attributes/SkipAttribute.cs index 22bb734105..311a6cbfd0 100644 --- a/TUnit.Core/Attributes/SkipAttribute.cs +++ b/TUnit.Core/Attributes/SkipAttribute.cs @@ -1,4 +1,12 @@ namespace TUnit.Core.Attributes; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public class SkipAttribute : TUnitAttribute; \ No newline at end of file +public class SkipAttribute : TUnitAttribute +{ + public string Reason { get; } + + public SkipAttribute(string reason) + { + Reason = reason; + } +} \ No newline at end of file diff --git a/TUnit.Core/Test.cs b/TUnit.Core/Test.cs index 105b4626c7..cc33ef8d27 100644 --- a/TUnit.Core/Test.cs +++ b/TUnit.Core/Test.cs @@ -24,15 +24,16 @@ public Test(MethodInfo MethodInfo, Assembly = classType.Assembly; Source = classType.Assembly.Location; FullyQualifiedName = $"{classType.FullName}.{MethodInfo.Name}{GetParameterTypes(ParameterTypes)}"; - IsSkipped = MethodInfo.CustomAttributes + SkipReason = MethodInfo.CustomAttributes .Concat(classType.CustomAttributes) - .Any(x => x.AttributeType == typeof(SkipAttribute)); + .FirstOrDefault(x => x.AttributeType == typeof(SkipAttribute)) + ?.ConstructorArguments.FirstOrDefault().Value as string; FileName = SourceLocation.FileName; MinLineNumber = SourceLocation.MinLineNumber; MaxLineNumber = SourceLocation.MaxLineNumber; } - + private string GetArgumentValues() { if (ArgumentValues == null) @@ -63,7 +64,8 @@ private string GetArgumentValues() public object?[]? ArgumentValues { get; init; } public SourceLocation SourceLocation { get; } - public bool IsSkipped { get; } + public string? SkipReason { get; } + public bool IsSkipped => !string.IsNullOrEmpty(SkipReason); public string DisplayName { get; } public static string GetParameterTypes(Type[]? types) diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs index 0c13f454ad..fdd9dd45fd 100644 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestExecutor.cs @@ -131,7 +131,9 @@ private async ValueTask ExecuteTestMethod(TestWithTestCase testWithTestCase, obj EndTime = skipTime, Duration = TimeSpan.Zero, ComputerName = Environment.MachineName, + ErrorMessage = $"Skipped due to: {test.SkipReason}", }); + return; } diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 2a2b83431c..e9ebad549d 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -55,14 +55,14 @@ public async Task ParameterisedTests2(string value) Assert.That(value, Is.EqualTo("1")); } - [Test, Skip] + [Test, Skip("Reason1")] public void Skip1() { var value = "1"; Assert.That(value, Is.EqualTo("1")); } - [Test, Skip] + [Test, Skip("Reason2")] public async Task Skip2() { await Task.Yield(); From 1cfd42a3ea31a22743e5147ccddeffb364bf4d41 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:27:57 +0000 Subject: [PATCH 019/124] Fix --- .../AssertConditions/String/StringEqualsAssertCondition.cs | 4 +++- TUnit.Assertions/Is_Strings.cs | 7 ++++++- TUnit.Assertions/TUnit.Assertions.csproj | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 783edef8ab..8f2937ffd3 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -11,7 +11,9 @@ public StringEqualsAssertCondition(string expected, StringComparison stringCompa public override bool Matches(string actualValue) { - Message = $"Expected {ExpectedValue} but received {actualValue}"; + Message = $""" + Expected "{ExpectedValue}" but received "{actualValue}" + """; return string.Equals(actualValue, ExpectedValue, _stringComparison); } diff --git a/TUnit.Assertions/Is_Strings.cs b/TUnit.Assertions/Is_Strings.cs index 5ecf030d19..b929918a5e 100644 --- a/TUnit.Assertions/Is_Strings.cs +++ b/TUnit.Assertions/Is_Strings.cs @@ -4,7 +4,12 @@ namespace TUnit.Assertions; public static partial class Is { - public static AssertCondition EqualTo(string expected, StringComparison stringComparison = StringComparison.Ordinal) + public static AssertCondition EqualTo(string expected) + { + return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); + } + + public static AssertCondition EqualTo(string expected, StringComparison stringComparison) { return new StringEqualsAssertCondition(expected, stringComparison); } diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index ca62071d4f..9b799c2645 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -4,6 +4,7 @@ net6.0 enable enable + latest From 8d35c92cb197370998ef37103b636b642b34c5a3 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:58:40 +0000 Subject: [PATCH 020/124] Fixes --- TUnit.Assertions/Assert.cs | 12 ++---------- TUnit.Assertions/AssertCondition.cs | 18 +++++++++++++----- .../Generic/EqualsAssertCondition.cs | 10 +++++----- .../Generic/SameReferenceAssertCondition.cs | 7 ++++--- .../Numbers/GreaterThanAssertCondition.cs | 18 ++++++++++++++++++ .../GreaterThanOrEqualToAssertCondition.cs | 18 ++++++++++++++++++ .../Numbers/LessThanAssertCondition.cs | 18 ++++++++++++++++++ .../LessThanOrEqualToAssertCondition.cs | 18 ++++++++++++++++++ .../String/StringEqualsAssertCondition.cs | 12 ++++++------ TUnit.Assertions/IAssertCondition.cs | 6 ++---- TUnit.Assertions/Is_Numbers.cs | 11 +++++++++++ TUnit.Assertions/TUnit.Assertions.csproj | 2 +- 12 files changed, 116 insertions(+), 34 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs create mode 100644 TUnit.Assertions/Is_Numbers.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 609b4a54bc..d5b2f6ffe7 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -6,17 +6,9 @@ public static class Assert { public static void That(T value, AssertCondition assertCondition) { - if (!assertCondition.Matches(value)) + if (!assertCondition.Assert(value)) { - var message = GetMessage(assertCondition, value); - throw new AssertionException(message); + throw new AssertionException(assertCondition.Message); } } - - private static string GetMessage(AssertCondition assertCondition, T actualValue) - { - return assertCondition.MessageFactory != null - ? assertCondition.MessageFactory((assertCondition.ExpectedValue, actualValue)) - : assertCondition.Message; - } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertCondition.cs b/TUnit.Assertions/AssertCondition.cs index b56e622ce3..cf880160b4 100644 --- a/TUnit.Assertions/AssertCondition.cs +++ b/TUnit.Assertions/AssertCondition.cs @@ -7,14 +7,22 @@ internal AssertCondition(T expected) ExpectedValue = expected; } - internal Func<(T expectedValue, T actualValue), string>? MessageFactory { get; private set; } + internal abstract Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - public T ExpectedValue { get; } - public abstract bool Matches(T actualValue); + internal T ExpectedValue { get; } + private T ActualValue { get; set; } = default!; + + public bool Assert(T actualValue) + { + ActualValue = actualValue; + return Passes(actualValue); + } - public abstract string Message { get; protected set; } + protected abstract bool Passes(T actualValue); + + public string Message => MessageFactory.Invoke((ExpectedValue, ActualValue)); - public IAssertCondition WithMessage(Func<(T expectedValue, T actualValue), string> messageFactory) + public IAssertCondition WithMessage(Func<(T ExpectedValue, T ActualValue), string> messageFactory) { MessageFactory = messageFactory; return this; diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 5ea7757dc3..6490c8687d 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -8,12 +8,12 @@ public EqualsAssertCondition(T expected) : base(expected) { _expected = expected; } - - public override bool Matches(T actualValue) + + internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } + = tuple => $"Expected {tuple.ExpectedValue} but received {tuple.ActualValue}"; + + protected override bool Passes(T actualValue) { - Message = $"Expected {_expected} but received {actualValue}"; return Equals(actualValue, _expected); } - - public override string Message { get; protected set; } = string.Empty; } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index f31a6b8ea0..61105be483 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -9,10 +9,11 @@ public SameReferenceAssertCondition(T expected) : base(expected) _expected = expected; } - public override bool Matches(T actualValue) + internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } + = tuple => $"The two objects are different references."; + + protected override bool Passes(T actualValue) { return ReferenceEquals(actualValue, _expected); } - - public override string Message { get; protected set; } = string.Empty; } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs new file mode 100644 index 0000000000..651f1e21d3 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace TUnit.Assertions; + +public class GreaterThanAssertCondition : AssertCondition where T : INumber +{ + public GreaterThanAssertCondition(T expected) : base(expected) + { + } + + internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } + = tuple => $"{tuple.ActualValue} is not greater than {tuple.ExpectedValue}"; + + protected override bool Passes(T actualValue) + { + return actualValue > ExpectedValue; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs new file mode 100644 index 0000000000..4c09dd36be --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace TUnit.Assertions; + +public class GreaterThanOrEqualToAssertCondition : AssertCondition where T : INumber +{ + public GreaterThanOrEqualToAssertCondition(T expected) : base(expected) + { + } + + internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } + = tuple => $"{tuple.ActualValue} is not greater than or equal to {tuple.ExpectedValue}"; + + protected override bool Passes(T actualValue) + { + return actualValue >= ExpectedValue; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs new file mode 100644 index 0000000000..411786edaf --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace TUnit.Assertions; + +public class LessThanAssertCondition : AssertCondition where T : INumber +{ + public LessThanAssertCondition(T expected) : base(expected) + { + } + + internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } + = tuple => $"{tuple.ActualValue} is not less than {tuple.ExpectedValue}"; + + protected override bool Passes(T actualValue) + { + return actualValue < ExpectedValue; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs new file mode 100644 index 0000000000..0bed6eaae8 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace TUnit.Assertions; + +public class LessThanOrEqualToAssertCondition : AssertCondition where T : INumber +{ + public LessThanOrEqualToAssertCondition(T expected) : base(expected) + { + } + + internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } + = tuple => $"{tuple.ActualValue} is not less than or equal to {tuple.ExpectedValue}"; + + protected override bool Passes(T actualValue) + { + return actualValue <= ExpectedValue; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 8f2937ffd3..eb6064252b 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -9,13 +9,13 @@ public StringEqualsAssertCondition(string expected, StringComparison stringCompa _stringComparison = stringComparison; } - public override bool Matches(string actualValue) + protected override bool Passes(string actualValue) { - Message = $""" - Expected "{ExpectedValue}" but received "{actualValue}" - """; return string.Equals(actualValue, ExpectedValue, _stringComparison); } - - public override string Message { get; protected set; } = string.Empty; + + internal override Func<(string ExpectedValue, string ActualValue), string> MessageFactory { get; set; } + = tuple => $""" + Expected "{tuple.ExpectedValue}" but received "{tuple.ActualValue}" + """; } \ No newline at end of file diff --git a/TUnit.Assertions/IAssertCondition.cs b/TUnit.Assertions/IAssertCondition.cs index f4d510cb4e..7790086834 100644 --- a/TUnit.Assertions/IAssertCondition.cs +++ b/TUnit.Assertions/IAssertCondition.cs @@ -1,10 +1,8 @@ namespace TUnit.Assertions; -public interface IAssertCondition +public interface IAssertCondition { - internal T ExpectedValue { get; } - - public bool Matches(T actualValue); + public bool Assert(T actualValue); internal string Message { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/Is_Numbers.cs b/TUnit.Assertions/Is_Numbers.cs new file mode 100644 index 0000000000..96ad17f808 --- /dev/null +++ b/TUnit.Assertions/Is_Numbers.cs @@ -0,0 +1,11 @@ +using System.Numerics; + +namespace TUnit.Assertions; + +public static partial class Is +{ + public static AssertCondition GreaterThan(T expected) where T : INumber + { + return new GreaterThanAssertCondition(expected); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index 9b799c2645..d537ffdf6c 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable latest From e488bcda0476c403fee8dfae5a01448f2f4f529d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:35:45 +0000 Subject: [PATCH 021/124] Fixes --- TUnit.Assertions/AssertCondition.cs | 18 ++++++++-------- .../Generic/EqualsAssertCondition.cs | 13 +++++------- .../Generic/SameReferenceAssertCondition.cs | 13 +++++------- .../Numbers/GreaterThanAssertCondition.cs | 13 ++++++------ .../GreaterThanOrEqualToAssertCondition.cs | 13 ++++++------ .../Numbers/IsEvenAssertCondition.cs | 19 +++++++++++++++++ .../Numbers/LessThanAssertCondition.cs | 13 ++++++------ .../LessThanOrEqualToAssertCondition.cs | 13 ++++++------ .../String/StringEqualsAssertCondition.cs | 11 +++++----- .../ExpectedValueAssertCondition.cs | 21 +++++++++++++++++++ TUnit.Assertions/IAssertCondition.cs | 2 +- TUnit.Assertions/Is.cs | 4 ++-- TUnit.Assertions/Is_Numbers.cs | 3 ++- TUnit.Assertions/TUnit.Assertions.csproj | 2 +- TUnit.Core/TUnit.Core.csproj | 2 +- TUnit.Pipeline/Modules/BuildProjectsModule.cs | 20 ++++++++++++++++++ TUnit.Pipeline/Modules/CleanProjectsModule.cs | 11 ---------- TUnit.Pipeline/PackedProject.cs | 2 +- TUnit.Pipeline/TUnit.Pipeline.csproj | 2 +- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 2 +- TUnit.TestProject/TUnit.TestProject.csproj | 2 +- TUnit/TUnit.csproj | 2 +- 22 files changed, 124 insertions(+), 77 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs create mode 100644 TUnit.Assertions/ExpectedValueAssertCondition.cs create mode 100644 TUnit.Pipeline/Modules/BuildProjectsModule.cs diff --git a/TUnit.Assertions/AssertCondition.cs b/TUnit.Assertions/AssertCondition.cs index cf880160b4..7b32a954ab 100644 --- a/TUnit.Assertions/AssertCondition.cs +++ b/TUnit.Assertions/AssertCondition.cs @@ -2,27 +2,27 @@ public abstract class AssertCondition : IAssertCondition { - internal AssertCondition(T expected) + internal AssertCondition() { - ExpectedValue = expected; } - - internal abstract Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - internal T ExpectedValue { get; } - private T ActualValue { get; set; } = default!; + private Func? MessageFactory { get; set; } + + protected T ActualValue { get; private set; } = default!; public bool Assert(T actualValue) { ActualValue = actualValue; return Passes(actualValue); } - + + public abstract string DefaultMessage { get; } + protected abstract bool Passes(T actualValue); - public string Message => MessageFactory.Invoke((ExpectedValue, ActualValue)); + public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; - public IAssertCondition WithMessage(Func<(T ExpectedValue, T ActualValue), string> messageFactory) + public IAssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 6490c8687d..1f4efbb591 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,19 +1,16 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class EqualsAssertCondition : AssertCondition +public class EqualsAssertCondition : ExpectedValueAssertCondition { - private readonly T _expected; - public EqualsAssertCondition(T expected) : base(expected) + public EqualsAssertCondition(TExpected expected) : base(expected) { - _expected = expected; } - internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - = tuple => $"Expected {tuple.ExpectedValue} but received {tuple.ActualValue}"; + public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected override bool Passes(T actualValue) + protected override bool Passes(TActual actualValue) { - return Equals(actualValue, _expected); + return Equals(actualValue, ExpectedValue); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index 61105be483..937a072b15 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -1,19 +1,16 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class SameReferenceAssertCondition : AssertCondition +public class SameReferenceAssertCondition : ExpectedValueAssertCondition { - private readonly T _expected; - public SameReferenceAssertCondition(T expected) : base(expected) + public SameReferenceAssertCondition(TExpected expected) : base(expected) { - _expected = expected; } - internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - = tuple => $"The two objects are different references."; + public override string DefaultMessage => "The two objects are different references."; - protected override bool Passes(T actualValue) + protected override bool Passes(TActual actualValue) { - return ReferenceEquals(actualValue, _expected); + return ReferenceEquals(actualValue, ExpectedValue); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index 651f1e21d3..6af5196336 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -1,17 +1,18 @@ using System.Numerics; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Numbers; -public class GreaterThanAssertCondition : AssertCondition where T : INumber +public class GreaterThanAssertCondition : ExpectedValueAssertCondition + where TExpected : INumber + where TActual : INumber, TExpected { - public GreaterThanAssertCondition(T expected) : base(expected) + public GreaterThanAssertCondition(TExpected expected) : base(expected) { } - internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - = tuple => $"{tuple.ActualValue} is not greater than {tuple.ExpectedValue}"; + public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; - protected override bool Passes(T actualValue) + protected override bool Passes(TActual actualValue) { return actualValue > ExpectedValue; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 4c09dd36be..4a2d65edf7 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -1,17 +1,18 @@ using System.Numerics; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Numbers; -public class GreaterThanOrEqualToAssertCondition : AssertCondition where T : INumber +public class GreaterThanOrEqualToAssertCondition : ExpectedValueAssertCondition + where TExpected : INumber + where TActual : INumber, TExpected { - public GreaterThanOrEqualToAssertCondition(T expected) : base(expected) + public GreaterThanOrEqualToAssertCondition(TExpected expected) : base(expected) { } - internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - = tuple => $"{tuple.ActualValue} is not greater than or equal to {tuple.ExpectedValue}"; + public override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; - protected override bool Passes(T actualValue) + protected override bool Passes(TActual actualValue) { return actualValue >= ExpectedValue; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs new file mode 100644 index 0000000000..7b29d0e2b6 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -0,0 +1,19 @@ +using System.Numerics; + +namespace TUnit.Assertions.AssertConditions.Numbers; + +public class IsEvenAssertCondition : ExpectedValueAssertCondition + where TExpected : INumber + where TActual : INumber, TExpected +{ + public IsEvenAssertCondition(TExpected expected) : base(expected) + { + } + + public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; + + protected override bool Passes(TActual actualValue) + { + return actualValue > ExpectedValue; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index 411786edaf..bfc5a595c7 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -1,17 +1,18 @@ using System.Numerics; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Numbers; -public class LessThanAssertCondition : AssertCondition where T : INumber +public class LessThanAssertCondition : ExpectedValueAssertCondition + where TExpected : INumber + where TActual : INumber, TExpected { - public LessThanAssertCondition(T expected) : base(expected) + public LessThanAssertCondition(TExpected expected) : base(expected) { } - internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - = tuple => $"{tuple.ActualValue} is not less than {tuple.ExpectedValue}"; + public override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; - protected override bool Passes(T actualValue) + protected override bool Passes(TActual actualValue) { return actualValue < ExpectedValue; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index 0bed6eaae8..4d27b9a86e 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -1,17 +1,18 @@ using System.Numerics; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Numbers; -public class LessThanOrEqualToAssertCondition : AssertCondition where T : INumber +public class LessThanOrEqualToAssertCondition : ExpectedValueAssertCondition + where TExpected : INumber + where TActual : INumber, TExpected { - public LessThanOrEqualToAssertCondition(T expected) : base(expected) + public LessThanOrEqualToAssertCondition(TExpected expected) : base(expected) { } - internal override Func<(T ExpectedValue, T ActualValue), string> MessageFactory { get; set; } - = tuple => $"{tuple.ActualValue} is not less than or equal to {tuple.ExpectedValue}"; + public override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; - protected override bool Passes(T actualValue) + protected override bool Passes(TActual actualValue) { return actualValue <= ExpectedValue; } diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index eb6064252b..887266c104 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions.AssertConditions.String; -public class StringEqualsAssertCondition : AssertCondition +public class StringEqualsAssertCondition : ExpectedValueAssertCondition { private readonly StringComparison _stringComparison; @@ -13,9 +13,8 @@ protected override bool Passes(string actualValue) { return string.Equals(actualValue, ExpectedValue, _stringComparison); } - - internal override Func<(string ExpectedValue, string ActualValue), string> MessageFactory { get; set; } - = tuple => $""" - Expected "{tuple.ExpectedValue}" but received "{tuple.ActualValue}" - """; + + public override string DefaultMessage => $""" + Expected "{ExpectedValue}" but received "{ActualValue}" + """; } \ No newline at end of file diff --git a/TUnit.Assertions/ExpectedValueAssertCondition.cs b/TUnit.Assertions/ExpectedValueAssertCondition.cs new file mode 100644 index 0000000000..bdff7ea179 --- /dev/null +++ b/TUnit.Assertions/ExpectedValueAssertCondition.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions; + +public abstract class ExpectedValueAssertCondition : AssertCondition +{ + internal TExpected ExpectedValue { get; } + + internal ExpectedValueAssertCondition(TExpected expected) + { + ExpectedValue = expected; + } + + public new string Message => MessageFactory?.Invoke((ExpectedValue, ActualValue)) ?? DefaultMessage; + + private Func<(TExpected ExpectedValue, TActual ActualValue), string>? MessageFactory { get; set; } + + public IAssertCondition WithMessage(Func<(TExpected ExpectedValue, TActual ActualValue), string> messageFactory) + { + MessageFactory = messageFactory!; + return this; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/IAssertCondition.cs b/TUnit.Assertions/IAssertCondition.cs index 7790086834..1da65b17cf 100644 --- a/TUnit.Assertions/IAssertCondition.cs +++ b/TUnit.Assertions/IAssertCondition.cs @@ -4,5 +4,5 @@ public interface IAssertCondition { public bool Assert(T actualValue); - internal string Message { get; } + internal string DefaultMessage { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 004b56d802..0a204f0845 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -6,11 +6,11 @@ public static partial class Is { public static AssertCondition EqualTo(T expected) { - return new EqualsAssertCondition(expected); + return new EqualsAssertCondition(expected); } public static AssertCondition SameReference(T expected) { - return new SameReferenceAssertCondition(expected); + return new SameReferenceAssertCondition(expected); } } \ No newline at end of file diff --git a/TUnit.Assertions/Is_Numbers.cs b/TUnit.Assertions/Is_Numbers.cs index 96ad17f808..fcd366f98b 100644 --- a/TUnit.Assertions/Is_Numbers.cs +++ b/TUnit.Assertions/Is_Numbers.cs @@ -1,4 +1,5 @@ using System.Numerics; +using TUnit.Assertions.AssertConditions.Numbers; namespace TUnit.Assertions; @@ -6,6 +7,6 @@ public static partial class Is { public static AssertCondition GreaterThan(T expected) where T : INumber { - return new GreaterThanAssertCondition(expected); + return new GreaterThanAssertCondition(expected); } } \ No newline at end of file diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index d537ffdf6c..97b7c882fc 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -1,7 +1,7 @@  - net8.0 + net7.0 enable enable latest diff --git a/TUnit.Core/TUnit.Core.csproj b/TUnit.Core/TUnit.Core.csproj index c81818ddbf..87ff03f1d0 100644 --- a/TUnit.Core/TUnit.Core.csproj +++ b/TUnit.Core/TUnit.Core.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 enable enable default diff --git a/TUnit.Pipeline/Modules/BuildProjectsModule.cs b/TUnit.Pipeline/Modules/BuildProjectsModule.cs new file mode 100644 index 0000000000..55c6108314 --- /dev/null +++ b/TUnit.Pipeline/Modules/BuildProjectsModule.cs @@ -0,0 +1,20 @@ +using EnumerableAsyncProcessor.Extensions; +using ModularPipelines.Attributes; +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Models; +using ModularPipelines.Modules; + +namespace TUnit.Pipeline.Modules; + +[DependsOn] +[DependsOn] +public class BuildProjectsModule : Module +{ + protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var projects = await GetModule(); + return await projects.Value!.SelectAsync(x => context.DotNet().Build(new DotNetBuildOptions(x), cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/CleanProjectsModule.cs b/TUnit.Pipeline/Modules/CleanProjectsModule.cs index 370a5fdd9d..aa4fa793b1 100644 --- a/TUnit.Pipeline/Modules/CleanProjectsModule.cs +++ b/TUnit.Pipeline/Modules/CleanProjectsModule.cs @@ -15,15 +15,4 @@ public class CleanProjectsModule : Module var projects = await GetModule(); return await projects.Value!.SelectAsync(x => context.DotNet().Clean(new DotNetCleanOptions(x), cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); } -} - -[DependsOn] -[DependsOn] -public class BuildProjectsModule : Module -{ - protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var projects = await GetModule(); - return await projects.Value!.SelectAsync(x => context.DotNet().Build(new DotNetBuildOptions(x), cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); - } } \ No newline at end of file diff --git a/TUnit.Pipeline/PackedProject.cs b/TUnit.Pipeline/PackedProject.cs index 34cb1938b7..aeb3aa1a74 100644 --- a/TUnit.Pipeline/PackedProject.cs +++ b/TUnit.Pipeline/PackedProject.cs @@ -1,3 +1,3 @@ -namespace TUnit.Pipeline.Modules; +namespace TUnit.Pipeline; public record PackedProject(string Name, string Version); \ No newline at end of file diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj index 7579ccbe99..19136d3fb2 100644 --- a/TUnit.Pipeline/TUnit.Pipeline.csproj +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 enable enable default diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index 52cea0b729..baffa533eb 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 enable enable default diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 6354dae533..1e847d0523 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 enable enable false diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index 45f6ff7b23..eb2daaa6d7 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 enable enable From 047693bb797c967cd1a9b1eb251c0cf764225af5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:42:55 +0000 Subject: [PATCH 022/124] Even Odd Assertions --- .../Numbers/IsEvenAssertCondition.cs | 13 +++------- .../Numbers/IsOddAssertCondition.cs | 14 +++++++++++ TUnit.Assertions/Is_Numbers.cs | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index 7b29d0e2b6..9cffeb80fb 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -2,18 +2,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class IsEvenAssertCondition : ExpectedValueAssertCondition - where TExpected : INumber - where TActual : INumber, TExpected +public class IsEvenAssertCondition : AssertCondition + where TActual : INumber, IModulusOperators { - public IsEvenAssertCondition(TExpected expected) : base(expected) - { - } - - public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; + public override string DefaultMessage => $"{ActualValue} is not even"; protected override bool Passes(TActual actualValue) { - return actualValue > ExpectedValue; + return actualValue % 2 == 0; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs new file mode 100644 index 0000000000..d339f0e854 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -0,0 +1,14 @@ +using System.Numerics; + +namespace TUnit.Assertions.AssertConditions.Numbers; + +public class IsOddAssertCondition : AssertCondition + where TActual : INumber, IModulusOperators +{ + public override string DefaultMessage => $"{ActualValue} is not odd"; + + protected override bool Passes(TActual actualValue) + { + return actualValue % 2 != 0; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Is_Numbers.cs b/TUnit.Assertions/Is_Numbers.cs index fcd366f98b..1b4902edcf 100644 --- a/TUnit.Assertions/Is_Numbers.cs +++ b/TUnit.Assertions/Is_Numbers.cs @@ -9,4 +9,29 @@ public static AssertCondition GreaterThan(T expected) where T : INumber { return new GreaterThanAssertCondition(expected); } + + public static AssertCondition GreaterThanOrEqualTo(T expected) where T : INumber + { + return new GreaterThanOrEqualToAssertCondition(expected); + } + + public static AssertCondition LessThan(T expected) where T : INumber + { + return new GreaterThanAssertCondition(expected); + } + + public static AssertCondition LessThanOrEqualTo(T expected) where T : INumber + { + return new LessThanOrEqualToAssertCondition(expected); + } + + public static AssertCondition Even() where T : INumber, IModulusOperators + { + return new IsEvenAssertCondition(); + } + + public static AssertCondition Odd() where T : INumber, IModulusOperators + { + return new IsOddAssertCondition(); + } } \ No newline at end of file From b4e5a1da44283779247d8b20efa691a9e33b2d7f Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:58:07 +0000 Subject: [PATCH 023/124] Assertion tests --- .idea/.idea.TUnit/.idea/vcs.xml | 6 +++ TUnit.Assertions.UnitTests/GlobalUsings.cs | 2 + .../TUnit.Assertions.UnitTests.csproj | 24 ++++++++++ .../ZeroAssertionTests.cs | 47 +++++++++++++++++++ .../Numbers/ZeroAssertCondition.cs | 14 ++++++ TUnit.Assertions/Is_Numbers.cs | 3 ++ TUnit.sln | 6 +++ 7 files changed, 102 insertions(+) create mode 100644 .idea/.idea.TUnit/.idea/vcs.xml create mode 100644 TUnit.Assertions.UnitTests/GlobalUsings.cs create mode 100644 TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj create mode 100644 TUnit.Assertions.UnitTests/ZeroAssertionTests.cs create mode 100644 TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs diff --git a/.idea/.idea.TUnit/.idea/vcs.xml b/.idea/.idea.TUnit/.idea/vcs.xml new file mode 100644 index 0000000000..35eb1ddfbb --- /dev/null +++ b/.idea/.idea.TUnit/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TUnit.Assertions.UnitTests/GlobalUsings.cs b/TUnit.Assertions.UnitTests/GlobalUsings.cs new file mode 100644 index 0000000000..5e3c1307f3 --- /dev/null +++ b/TUnit.Assertions.UnitTests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using NUnit.Framework; +global using Assert = TUnit.Assertions.Assert; \ No newline at end of file diff --git a/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj b/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj new file mode 100644 index 0000000000..b3a4a45760 --- /dev/null +++ b/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs new file mode 100644 index 0000000000..61de8c1b42 --- /dev/null +++ b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs @@ -0,0 +1,47 @@ +namespace TUnit.Assertions.UnitTests; + + +public class ZeroAssertionTests +{ + [Test] + public void Int() + { + int zero = 0; + Assert.That(zero, Is.Zero); + } + + [Test] + public void Long() + { + long zero = 0; + Assert.That(zero, Is.Zero); + } + + [Test] + public void Short() + { + short zero = 0; + Assert.That(zero, Is.Zero); + } + + [Test] + public void Int_Bad() + { + int zero = 1; + Assert.That(zero, Is.Zero); + } + + [Test] + public void Long_Bad() + { + long zero = 1; + Assert.That(zero, Is.Zero); + } + + [Test] + public void Short_Bad() + { + short zero = 1; + Assert.That(zero, Is.Zero); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs new file mode 100644 index 0000000000..b91814004d --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -0,0 +1,14 @@ +using System.Numerics; + +namespace TUnit.Assertions.AssertConditions.Numbers; + +public class ZeroAssertCondition : AssertCondition + where TActual : INumber, IEqualityOperators +{ + public override string DefaultMessage => $"{ActualValue} is not equal to 0"; + + protected override bool Passes(TActual actualValue) + { + return actualValue == TActual.Zero; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Is_Numbers.cs b/TUnit.Assertions/Is_Numbers.cs index 1b4902edcf..41cdafa69b 100644 --- a/TUnit.Assertions/Is_Numbers.cs +++ b/TUnit.Assertions/Is_Numbers.cs @@ -1,10 +1,13 @@ using System.Numerics; +using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; namespace TUnit.Assertions; public static partial class Is { + public static AssertCondition Zero => new EqualsAssertCondition(0); + public static AssertCondition GreaterThan(T expected) where T : INumber { return new GreaterThanAssertCondition(expected); diff --git a/TUnit.sln b/TUnit.sln index 8e93b5d74c..b55340e5e8 100644 --- a/TUnit.sln +++ b/TUnit.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Assertions", "TUnit.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Pipeline", "TUnit.Pipeline\TUnit.Pipeline.csproj", "{527337F5-0F78-4141-901C-72EA81222AB1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Assertions.UnitTests", "TUnit.Assertions.UnitTests\TUnit.Assertions.UnitTests.csproj", "{2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {527337F5-0F78-4141-901C-72EA81222AB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {527337F5-0F78-4141-901C-72EA81222AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU {527337F5-0F78-4141-901C-72EA81222AB1}.Release|Any CPU.Build.0 = Release|Any CPU + {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 7e7dee727f0338f8fd09ceace912706fba7e275f Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 14:43:20 +0000 Subject: [PATCH 024/124] Engine project --- TUnit.Core/TUnitTestResult.cs | 20 ++ TUnit.Core/TestCollection.cs | 6 +- TUnit.Core/{Test.cs => TestDetails.cs} | 13 +- TUnit.Engine/MethodInvoker.cs | 34 ++ TUnit.Engine/SingleTestExecutor.cs | 141 ++++++++ TUnit.Engine/TUnit.Engine.csproj | 17 + TUnit.TestAdapter/AsyncTestExecutor.cs | 324 ------------------ TUnit.TestAdapter/AsyncTestRunExecutor.cs | 195 +++++++++++ .../Extensions/TestExtensions.cs | 18 +- TUnit.TestAdapter/NoOpRunContext.cs | 11 + TUnit.TestAdapter/ProcessingTest.cs | 5 - TUnit.TestAdapter/TUnit.TestAdapter.csproj | 2 +- TUnit.TestAdapter/TestAndClass.cs | 2 +- TUnit.TestAdapter/TestCollector.cs | 2 +- TUnit.TestAdapter/TestExecutor.cs | 8 +- TUnit.TestAdapter/TestWithTestCase.cs | 3 +- TUnit.TestAdapter/TestsLoader.cs | 6 +- TUnit.sln | 6 + TUnit/TUnit.csproj | 2 +- 19 files changed, 460 insertions(+), 355 deletions(-) create mode 100644 TUnit.Core/TUnitTestResult.cs rename TUnit.Core/{Test.cs => TestDetails.cs} (87%) create mode 100644 TUnit.Engine/MethodInvoker.cs create mode 100644 TUnit.Engine/SingleTestExecutor.cs create mode 100644 TUnit.Engine/TUnit.Engine.csproj delete mode 100644 TUnit.TestAdapter/AsyncTestExecutor.cs create mode 100644 TUnit.TestAdapter/AsyncTestRunExecutor.cs create mode 100644 TUnit.TestAdapter/NoOpRunContext.cs delete mode 100644 TUnit.TestAdapter/ProcessingTest.cs diff --git a/TUnit.Core/TUnitTestResult.cs b/TUnit.Core/TUnitTestResult.cs new file mode 100644 index 0000000000..c198e86469 --- /dev/null +++ b/TUnit.Core/TUnitTestResult.cs @@ -0,0 +1,20 @@ +namespace TUnit.Core; + +public record TUnitTestResult +{ + public required TestDetails TestDetails { get; init; } + public required Status Status { get; init; } + public required DateTimeOffset Start { get; init; } + public required DateTimeOffset End { get; init; } + public required TimeSpan Duration { get; init; } + public required Exception? Exception { get; init; } + public required string ComputerName { get; init; } +}; + +public enum Status +{ + None, + Passed, + Failed, + Skipped +} \ No newline at end of file diff --git a/TUnit.Core/TestCollection.cs b/TUnit.Core/TestCollection.cs index 06f2ccf2cc..245bebf2d8 100644 --- a/TUnit.Core/TestCollection.cs +++ b/TUnit.Core/TestCollection.cs @@ -16,9 +16,9 @@ public sealed class TestCollection /// /// The tests that were discovered. /// - public IReadOnlyList Tests { get; private set; } + public IReadOnlyList Tests { get; private set; } - public TestCollection(IEnumerable sources, IEnumerable tests) + public TestCollection(IEnumerable sources, IEnumerable tests) { Sources = ImmutableArray.CreateRange(sources); Tests = ImmutableArray.CreateRange(tests); @@ -28,7 +28,7 @@ public TestCollection(IEnumerable sources, IEnumerable tests) /// Filters the tests in the test collection. This is used for partial test runs. /// /// The filter to apply. - public void Filter(Func filter) + public void Filter(Func filter) { Tests = ImmutableArray.CreateRange(Tests.Where(filter)); } diff --git a/TUnit.Core/Test.cs b/TUnit.Core/TestDetails.cs similarity index 87% rename from TUnit.Core/Test.cs rename to TUnit.Core/TestDetails.cs index cc33ef8d27..f97e7f50f5 100644 --- a/TUnit.Core/Test.cs +++ b/TUnit.Core/TestDetails.cs @@ -3,9 +3,9 @@ namespace TUnit.Core; -public record Test +public record TestDetails { - public Test(MethodInfo MethodInfo, + public TestDetails(MethodInfo MethodInfo, SourceLocation SourceLocation, ParameterArgument[]? arguments) { @@ -68,6 +68,15 @@ private string GetArgumentValues() public bool IsSkipped => !string.IsNullOrEmpty(SkipReason); public string DisplayName { get; } + private readonly TaskCompletionSource _completionSource = new(); + public Task GetResultAsync() => _completionSource.Task; + + public TUnitTestResult SetResult(TUnitTestResult unitTestResult) + { + _completionSource.SetResult(unitTestResult); + return unitTestResult; + } + public static string GetParameterTypes(Type[]? types) { if (types is null) diff --git a/TUnit.Engine/MethodInvoker.cs b/TUnit.Engine/MethodInvoker.cs new file mode 100644 index 0000000000..4ccecde44f --- /dev/null +++ b/TUnit.Engine/MethodInvoker.cs @@ -0,0 +1,34 @@ +using System.Globalization; +using System.Reflection; +using System.Runtime.ExceptionServices; + +namespace TUnit.Engine; + +public class MethodInvoker +{ + public async Task InvokeMethod(object? @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) + { + try + { + var result = await Task.Run(() => methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture)); + + if (result is ValueTask valueTask) + { + await valueTask; + } + else if (result is Task task) + { + await task; + } + } + catch (TargetInvocationException e) + { + if (e.InnerException is null) + { + throw; + } + + ExceptionDispatchInfo.Capture(e.InnerException).Throw(); + } + } +} \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs new file mode 100644 index 0000000000..6d11352da3 --- /dev/null +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -0,0 +1,141 @@ +using System.Collections.Concurrent; +using System.Reflection; +using TUnit.Core; +using TUnit.Core.Attributes; + +namespace TUnit.Engine; + +public class SingleTestExecutor +{ + private readonly MethodInvoker _methodInvoker; + + public SingleTestExecutor(MethodInvoker methodInvoker) + { + _methodInvoker = methodInvoker; + } + + private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); + + public async Task ExecuteTest(TestDetails testDetails) + { + var start = DateTimeOffset.Now; + + if (testDetails.IsSkipped) + { + return testDetails.SetResult(new TUnitTestResult + { + TestDetails = testDetails, + Duration = TimeSpan.Zero, + Start = start, + End = start, + ComputerName = Environment.MachineName, + Exception = null, + Status = Status.Skipped + }); + } + + try + { + await ExecuteCore(testDetails); + + var end = DateTimeOffset.Now; + + return testDetails.SetResult(new TUnitTestResult + { + TestDetails = testDetails, + Duration = end - start, + Start = start, + End = end, + ComputerName = Environment.MachineName, + Exception = null, + Status = Status.Passed + }); + } + catch (Exception e) + { + var end = DateTimeOffset.Now; + + return testDetails.SetResult(new TUnitTestResult + { + TestDetails = testDetails, + Duration = end - start, + Start = start, + End = end, + ComputerName = Environment.MachineName, + Exception = e, + Status = Status.Failed + }); + } + } + + private async Task ExecuteCore(TestDetails testDetails) + { + var @class = CreateTestClass(testDetails); + + await ExecuteSetUps(@class); + + await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, testDetails.ArgumentValues?.ToArray()); + + await ExecuteTearDowns(@class); + } + + private async Task ExecuteSetUps(object @class) + { + await _oneTimeSetUpRegistry.GetOrAdd(@class.GetType().FullName!, _ => ExecuteOneTimeSetUps(@class)); + + var setUpMethods = @class.GetType() + .GetMethods() + .Where(x => !x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(SetUpAttribute))); + + foreach (var setUpMethod in setUpMethods) + { + await _methodInvoker.InvokeMethod(@class, setUpMethod, BindingFlags.Default, null); + } + } + + private async Task ExecuteTearDowns(object @class) + { + var tearDownMethods = @class.GetType() + .GetMethods() + .Where(x => !x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(TearDownAttribute))); + + var exceptions = new List(); + + foreach (var tearDownMethod in tearDownMethods) + { + try + { + await _methodInvoker.InvokeMethod(@class, tearDownMethod, BindingFlags.Default, null); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + if (exceptions.Any()) + { + throw new AggregateException(exceptions); + } + } + + private async Task ExecuteOneTimeSetUps(object @class) + { + var oneTimeSetUpMethods = @class.GetType() + .GetMethods() + .Where(x => x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeSetUpAttribute))); + + foreach (var oneTimeSetUpMethod in oneTimeSetUpMethods) + { + await _methodInvoker.InvokeMethod(@class, oneTimeSetUpMethod, BindingFlags.Static | BindingFlags.Public, null); + } + } + + private static object CreateTestClass(TestDetails testDetails) + { + return Activator.CreateInstance(testDetails.MethodInfo.DeclaringType!)!; + } +} \ No newline at end of file diff --git a/TUnit.Engine/TUnit.Engine.csproj b/TUnit.Engine/TUnit.Engine.csproj new file mode 100644 index 0000000000..2579f4e532 --- /dev/null +++ b/TUnit.Engine/TUnit.Engine.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + diff --git a/TUnit.TestAdapter/AsyncTestExecutor.cs b/TUnit.TestAdapter/AsyncTestExecutor.cs deleted file mode 100644 index fdd9dd45fd..0000000000 --- a/TUnit.TestAdapter/AsyncTestExecutor.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Globalization; -using System.Reflection; -using System.Runtime.ExceptionServices; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using TUnit.Core; -using TUnit.Core.Attributes; - -namespace TUnit.TestAdapter; - -public class AsyncTestExecutor(CancellationTokenSource cancellationTokenSource) -{ - private bool _canRunAnotherTest = true; - - private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); - private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); - - public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) - { - var allTestsOrderedByClass = tests - .GroupBy(x => x.Test.FullyQualifiedClassName) - .SelectMany(x => x.ToList()) - .ToList(); - - var queue = new Queue(allTestsOrderedByClass); - - if (queue.Count is 0) - { - return; - } - - MonitorSystemResources(); - - var executingTests = new List(); - - await foreach (var testCase in ProcessQueue(queue, frameworkHandle)) - { - if (cancellationTokenSource.IsCancellationRequested) - { - break; - } - - executingTests.Add(testCase); - - SetupRunOneTimeTearDownForClass(testCase, allTestsOrderedByClass, executingTests); - - executingTests.RemoveAll(x => x.Task.IsCompletedSuccessfully); - } - - executingTests.RemoveAll(x => x.Task.IsCompletedSuccessfully); - - await Task.WhenAll(executingTests.Select(x => x.Task)); - await Task.WhenAll(_oneTimeTearDownRegistry.Values); - } - - private void SetupRunOneTimeTearDownForClass(ProcessingTest processingTest, - IEnumerable allTestsOrderedByClass, - IEnumerable executingTests) - { - var lastTestForClass = allTestsOrderedByClass.Last(x => - x.Test.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName); - - if (processingTest.Test.FullyQualifiedName != lastTestForClass.Test.FullyQualifiedName) - { - return; - } - - var executingTestsForThisClass = executingTests - .Where(x => x.Test.FullyQualifiedClassName == processingTest.Test.FullyQualifiedClassName) - .Select(x => x.Task) - .ToArray(); - - Task.WhenAll(executingTestsForThisClass).ContinueWith(x => - { - _ = _oneTimeTearDownRegistry.GetOrAdd(processingTest.Test.FullyQualifiedClassName, - ExecuteOneTimeTearDowns(processingTest.Class)); - - return Task.CompletedTask; - }); - } - - private async IAsyncEnumerable ProcessQueue(Queue queue, ITestExecutionRecorder? frameworkHandle) - { - while (queue.Count > 0) - { - if (_canRunAnotherTest && !cancellationTokenSource.IsCancellationRequested) - { - var test = queue.Dequeue(); - - var @class = CreateTestClass(test.Test); - - yield return new ProcessingTest(test.Test, @class, ProcessTest(test, @class, frameworkHandle)); - } - else if (cancellationTokenSource.IsCancellationRequested) - { - break; - } - else - { - await Task.Delay(100); - } - } - } - - private async Task ProcessTest(TestWithTestCase testWithTestCase, object @class, ITestExecutionRecorder? frameworkHandle) - { - await ExecuteTestMethod(testWithTestCase, @class, frameworkHandle); - } - - private static object CreateTestClass(Test test) - { - return Activator.CreateInstance(test.MethodInfo.DeclaringType!)!; - } - - private async ValueTask ExecuteTestMethod(TestWithTestCase testWithTestCase, object @class, ITestExecutionRecorder? frameworkHandle) - { - var test = testWithTestCase.Test; - var testCase = testWithTestCase.TestCase; - - if (test.IsSkipped) - { - var skipTime = DateTimeOffset.Now; - frameworkHandle?.RecordEnd(testCase, TestOutcome.Skipped); - frameworkHandle?.RecordResult(new TestResult(testCase) - { - Outcome = TestOutcome.Skipped, - DisplayName = test.TestName, - StartTime = skipTime, - EndTime = skipTime, - Duration = TimeSpan.Zero, - ComputerName = Environment.MachineName, - ErrorMessage = $"Skipped due to: {test.SkipReason}", - }); - - return; - } - - frameworkHandle?.RecordStart(testCase); - - var start = DateTimeOffset.Now; - var stopwatch = Stopwatch.StartNew(); - - try - { - await ExecuteTest(test, @class); - - frameworkHandle?.RecordEnd(testCase, TestOutcome.Passed); - - frameworkHandle?.RecordResult(new TestResult(testCase) - { - Outcome = TestOutcome.Passed, - - DisplayName = test.TestName, - StartTime = start, - EndTime = DateTimeOffset.Now, - Duration = stopwatch.Elapsed, - ComputerName = Environment.MachineName, - }); - } - catch (Exception e) - { - frameworkHandle?.RecordEnd(testCase, TestOutcome.Failed); - - frameworkHandle?.RecordResult(new TestResult(testCase) - { - Outcome = TestOutcome.Failed, - - DisplayName = test.TestName, - ErrorMessage = e.Message, - ErrorStackTrace = e.StackTrace, - StartTime = start, - EndTime = DateTimeOffset.Now, - Duration = stopwatch.Elapsed, - ComputerName = Environment.MachineName, - }); - } - } - - private async Task ExecuteTest(Test test, object @class) - { - await ExecuteSetUps(@class); - - await InvokeMethod(@class, test.MethodInfo, BindingFlags.Default, test.ArgumentValues?.ToArray()); - - await ExecuteTearDowns(@class); - } - - private async Task ExecuteSetUps(object @class) - { - await _oneTimeSetUpRegistry.GetOrAdd(@class.GetType().FullName!, _ => ExecuteOneTimeSetUps(@class)); - - var setUpMethods = @class.GetType() - .GetMethods() - .Where(x => !x.IsStatic) - .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(SetUpAttribute))); - - foreach (var setUpMethod in setUpMethods) - { - await InvokeMethod(@class, setUpMethod, BindingFlags.Default, null); - } - } - - private async Task ExecuteTearDowns(object @class) - { - var tearDownMethods = @class.GetType() - .GetMethods() - .Where(x => !x.IsStatic) - .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(TearDownAttribute))); - - var exceptions = new List(); - - foreach (var tearDownMethod in tearDownMethods) - { - try - { - await InvokeMethod(@class, tearDownMethod, BindingFlags.Default, null); - } - catch (Exception e) - { - exceptions.Add(e); - } - } - - if (exceptions.Any()) - { - throw new AggregateException(exceptions); - } - } - - private async Task ExecuteOneTimeSetUps(object @class) - { - var oneTimeSetUpMethods = @class.GetType() - .GetMethods() - .Where(x => x.IsStatic) - .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeSetUpAttribute))); - - foreach (var oneTimeSetUpMethod in oneTimeSetUpMethods) - { - await InvokeMethod(@class, oneTimeSetUpMethod, BindingFlags.Static | BindingFlags.Public, null); - } - } - - private async Task ExecuteOneTimeTearDowns(object @class) - { - var oneTimeTearDownMethods = @class.GetType() - .GetMethods() - .Where(x => x.IsStatic) - .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeTearDownAttribute))); - - foreach (var oneTimeTearDownMethod in oneTimeTearDownMethods) - { - var result = oneTimeTearDownMethod.Invoke(null, BindingFlags.Static | BindingFlags.Public, null, null, null); - - if (result is Task task) - { - await task; - } - } - } - - private void MonitorSystemResources() - { - Task.Factory.StartNew(async _ => - { - while (!cancellationTokenSource.IsCancellationRequested) - { - await Task.Delay(500); - - var cpuUsage = await GetCpuUsageForProcess(); - - _canRunAnotherTest = cpuUsage < 80; - } - }, null, TaskCreationOptions.LongRunning); - } - - private async Task GetCpuUsageForProcess() - { - var startTime = DateTime.UtcNow; - - var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; - await Task.Delay(500); - - var endTime = DateTime.UtcNow; - - var endCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; - - var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; - - var totalMsPassed = (endTime - startTime).TotalMilliseconds; - - var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); - - return cpuUsageTotal * 100; - } - - private async Task InvokeMethod(object @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) - { - try - { - var result = await Task.Run(() => methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture) as Task); - - if (result != null) - { - await result; - } - } - catch (TargetInvocationException e) - { - if (e.InnerException is null) - { - throw; - } - - ExceptionDispatchInfo.Capture(e.InnerException).Throw(); - } - } - - private bool IsTask(Type type) - { - return type.IsAssignableTo(typeof(Task)); - } -} \ No newline at end of file diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs new file mode 100644 index 0000000000..91f9947073 --- /dev/null +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -0,0 +1,195 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Core; +using TUnit.Core.Attributes; +using TUnit.Engine; + +namespace TUnit.TestAdapter; + +public class AsyncTestRunExecutor(SingleTestExecutor singleTestExecutor, MethodInvoker methodInvoker, CancellationTokenSource cancellationTokenSource) +{ + private bool _canRunAnotherTest = true; + + private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); + private readonly List _setResultsTasks = new(); + + public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + var allTestsOrderedByClass = tests + .GroupBy(x => x.Details.FullyQualifiedClassName) + .SelectMany(x => x.ToList()) + .ToList(); + + var queue = new Queue(allTestsOrderedByClass); + + if (queue.Count is 0) + { + return; + } + + MonitorSystemResources(); + + var executingTests = new List(); + + await foreach (var testWithResult in ProcessQueue(queue)) + { + if (cancellationTokenSource.IsCancellationRequested) + { + break; + } + + executingTests.Add(testWithResult); + + SetupRunOneTimeTearDownForClass(testWithResult.Test.Details, allTestsOrderedByClass, executingTests); + + executingTests.RemoveAll(x => x.Result.IsCompletedSuccessfully); + + _setResultsTasks.Add(testWithResult.Result.ContinueWith(t => + { + var result = t.Result; + var testDetails = testWithResult.Test.Details; + + frameworkHandle?.RecordResult(new TestResult(testWithResult.Test.TestCase) + { + DisplayName = testDetails.DisplayName, + Outcome = GetOutcome(result.Status), + ComputerName = result.ComputerName, + Duration = result.Duration, + StartTime = result.Start, + EndTime = result.End, + ErrorMessage = result.Exception?.Message, + ErrorStackTrace = result.Exception?.StackTrace, + }); + })); + } + + executingTests.RemoveAll(x => x.Result.IsCompletedSuccessfully); + + await WhenAllSafely(executingTests.Select(x => x.Result), frameworkHandle); + await WhenAllSafely(_oneTimeTearDownRegistry.Values, frameworkHandle); + await Task.WhenAll(_setResultsTasks); + } + + private TestOutcome GetOutcome(Status resultStatus) + { + return resultStatus switch + { + Status.None => TestOutcome.None, + Status.Passed => TestOutcome.Passed, + Status.Failed => TestOutcome.Failed, + Status.Skipped => TestOutcome.Skipped, + _ => throw new ArgumentOutOfRangeException(nameof(resultStatus), resultStatus, null) + }; + } + + private void SetupRunOneTimeTearDownForClass(TestDetails processingTestDetails, + IEnumerable allTestsOrderedByClass, + IEnumerable executingTests) + { + var lastTestForClass = allTestsOrderedByClass.Last(x => + x.Details.FullyQualifiedClassName == processingTestDetails.FullyQualifiedClassName); + + if (processingTestDetails.FullyQualifiedName != lastTestForClass.Details.FullyQualifiedName) + { + return; + } + + var executingTestsForThisClass = executingTests + .Where(x => x.Test.Details.FullyQualifiedClassName == processingTestDetails.FullyQualifiedClassName) + .Select(x => x.Result) + .ToArray(); + + Task.WhenAll(executingTestsForThisClass).ContinueWith(x => + { + _ = _oneTimeTearDownRegistry.GetOrAdd(processingTestDetails.FullyQualifiedClassName, + ExecuteOneTimeTearDowns(processingTestDetails)); + + return Task.CompletedTask; + }); + } + + private async IAsyncEnumerable ProcessQueue(Queue queue) + { + while (queue.Count > 0) + { + if (_canRunAnotherTest && !cancellationTokenSource.IsCancellationRequested) + { + var test = queue.Dequeue(); + + yield return new TestWithResult(test, singleTestExecutor.ExecuteTest(test.Details)); + } + else if (cancellationTokenSource.IsCancellationRequested) + { + break; + } + else + { + await Task.Delay(100); + } + } + } + + private void MonitorSystemResources() + { + Task.Factory.StartNew(async _ => + { + while (!cancellationTokenSource.IsCancellationRequested) + { + await Task.Delay(500); + + var cpuUsage = await GetCpuUsageForProcess(); + + _canRunAnotherTest = cpuUsage < 80; + } + }, null, TaskCreationOptions.LongRunning); + } + + private async Task GetCpuUsageForProcess() + { + var startTime = DateTime.UtcNow; + + var startCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + await Task.Delay(500); + + var endTime = DateTime.UtcNow; + + var endCpuUsage = Process.GetCurrentProcess().TotalProcessorTime; + + var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds; + + var totalMsPassed = (endTime - startTime).TotalMilliseconds; + + var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed); + + return cpuUsageTotal * 100; + } + + private async Task ExecuteOneTimeTearDowns(TestDetails testDetails) + { + var oneTimeTearDownMethods = testDetails.MethodInfo.DeclaringType! + .GetMethods() + .Where(x => x.IsStatic) + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeTearDownAttribute))); + + foreach (var oneTimeTearDownMethod in oneTimeTearDownMethods) + { + await methodInvoker.InvokeMethod(null, oneTimeTearDownMethod, BindingFlags.Static | BindingFlags.Public, null); + } + } + + private async Task WhenAllSafely(IEnumerable tasks, IMessageLogger? messageLogger) + { + try + { + await Task.WhenAll(tasks); + } + catch (Exception e) + { + messageLogger?.SendMessage(TestMessageLevel.Error, e.ToString()); + } + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index 17d249f1f4..643a59d1a5 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -6,19 +6,19 @@ namespace TUnit.TestAdapter.Extensions; public static class TestExtensions { - public static TestCase ToTestCase(this Test test) + public static TestCase ToTestCase(this TestDetails testDetails) { - var testCase = new TestCase(test.FullyQualifiedName, TestAdapterConstants.ExecutorUri, test.Source) + var testCase = new TestCase(testDetails.FullyQualifiedName, TestAdapterConstants.ExecutorUri, testDetails.Source) { - DisplayName = test.DisplayName, - Id = test.Id, - CodeFilePath = test.FileName, - LineNumber = test.MinLineNumber, - LocalExtensionData = test + DisplayName = testDetails.DisplayName, + Id = testDetails.Id, + CodeFilePath = testDetails.FileName, + LineNumber = testDetails.MinLineNumber, + LocalExtensionData = testDetails }; - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), test.FullyQualifiedClassName); - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), test.MethodInfo.Name + Test.GetParameterTypes(test.ParameterTypes)); + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), testDetails.FullyQualifiedClassName); + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), testDetails.MethodInfo.Name + TestDetails.GetParameterTypes(testDetails.ParameterTypes)); return testCase; } diff --git a/TUnit.TestAdapter/NoOpRunContext.cs b/TUnit.TestAdapter/NoOpRunContext.cs new file mode 100644 index 0000000000..f532ae40f7 --- /dev/null +++ b/TUnit.TestAdapter/NoOpRunContext.cs @@ -0,0 +1,11 @@ +namespace TUnit.TestAdapter; + +public class NoOpRunContext +{ + +} + +public class NoOpFrameworkHandle +{ + +} \ No newline at end of file diff --git a/TUnit.TestAdapter/ProcessingTest.cs b/TUnit.TestAdapter/ProcessingTest.cs deleted file mode 100644 index 1ea3b1f5af..0000000000 --- a/TUnit.TestAdapter/ProcessingTest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using TUnit.Core; - -namespace TUnit.TestAdapter; - -public record ProcessingTest(Test Test, object Class, Task Task); \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnit.TestAdapter.csproj b/TUnit.TestAdapter/TUnit.TestAdapter.csproj index baffa533eb..e7f045eae4 100644 --- a/TUnit.TestAdapter/TUnit.TestAdapter.csproj +++ b/TUnit.TestAdapter/TUnit.TestAdapter.csproj @@ -16,7 +16,7 @@ - + diff --git a/TUnit.TestAdapter/TestAndClass.cs b/TUnit.TestAdapter/TestAndClass.cs index fa1d944872..15231f981f 100644 --- a/TUnit.TestAdapter/TestAndClass.cs +++ b/TUnit.TestAdapter/TestAndClass.cs @@ -2,4 +2,4 @@ namespace TUnit.TestAdapter; -public record TestAndClass(Test Test, object Class); \ No newline at end of file +public record TestAndClass(TestDetails TestDetails, object Class); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index b63a41e54c..4f6e18144e 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -61,7 +61,7 @@ private void MarkNotFound(TestCase testCase, ITestExecutionRecorder? testExecuti }); } - public IEnumerable TestsFromSources(IEnumerable sources) + public IEnumerable TestsFromSources(IEnumerable sources) { var assemblyLoader = new AssemblyLoader(); var testsLoader = new TestsLoader(messageLogger); diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index 857e5bc705..dfb856f16c 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -12,11 +12,11 @@ namespace TUnit.TestAdapter; public class TestExecutor : ITestExecutor2 { private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly AsyncTestExecutor _asyncTestExecutor; + private readonly AsyncTestRunExecutor _asyncTestRunExecutor; public TestExecutor() { - _asyncTestExecutor = new AsyncTestExecutor(_cancellationTokenSource); + _asyncTestRunExecutor = new AsyncTestRunExecutor(_cancellationTokenSource); } public void RunTests(IEnumerable? testCases, IRunContext? runContext, IFrameworkHandle? frameworkHandle) @@ -29,7 +29,7 @@ public void RunTests(IEnumerable? testCases, IRunContext? runContext, var testsWithTestCases = new TestCollector(frameworkHandle) .TestsFromTestCases(testCases, frameworkHandle); - _asyncTestExecutor.RunInAsyncContext(testsWithTestCases, runContext, frameworkHandle) + _asyncTestRunExecutor.RunInAsyncContext(testsWithTestCases, runContext, frameworkHandle) .GetAwaiter() .GetResult(); } @@ -44,7 +44,7 @@ public void RunTests(IEnumerable? sources, IRunContext? runContext, IFra var tests = new TestCollector(frameworkHandle).TestsFromSources(sources) .Select(x => new TestWithTestCase(x, x.ToTestCase())); - _asyncTestExecutor.RunInAsyncContext(tests, runContext, frameworkHandle).GetAwaiter().GetResult(); + _asyncTestRunExecutor.RunInAsyncContext(tests, runContext, frameworkHandle).GetAwaiter().GetResult(); } public void Cancel() diff --git a/TUnit.TestAdapter/TestWithTestCase.cs b/TUnit.TestAdapter/TestWithTestCase.cs index 5fe8dbf9dd..462a6cb3f0 100644 --- a/TUnit.TestAdapter/TestWithTestCase.cs +++ b/TUnit.TestAdapter/TestWithTestCase.cs @@ -3,4 +3,5 @@ namespace TUnit.TestAdapter; -public record TestWithTestCase(Test Test, TestCase TestCase); \ No newline at end of file +public record TestWithTestCase(TestDetails Details, TestCase TestCase); +public record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 4bd2f97811..817742ddc8 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -10,7 +10,7 @@ public class TestsLoader(IMessageLogger? messageLogger) private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute)]; private readonly SourceLocationHelper _sourceLocationHelper = new(messageLogger); - public IEnumerable GetTests(TypeInformation typeInformation) + public IEnumerable GetTests(TypeInformation typeInformation) { var methods = typeInformation.Types.SelectMany(x => x.GetMethods()); @@ -33,7 +33,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) ?.Select(x => new ParameterArgument(x.Value?.GetType()!, x.Value)) .ToArray(); - yield return new Test( + yield return new TestDetails( MethodInfo: methodInfo, SourceLocation: sourceLocation, arguments: arguments @@ -43,7 +43,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) if(methodInfo.CustomAttributes.Any(x => x.AttributeType == typeof(TestAttribute))) { - yield return new Test( + yield return new TestDetails( MethodInfo: methodInfo, SourceLocation: sourceLocation, arguments: null diff --git a/TUnit.sln b/TUnit.sln index b55340e5e8..c9f4a58317 100644 --- a/TUnit.sln +++ b/TUnit.sln @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Pipeline", "TUnit.Pip EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Assertions.UnitTests", "TUnit.Assertions.UnitTests\TUnit.Assertions.UnitTests.csproj", "{2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Engine", "TUnit.Engine\TUnit.Engine.csproj", "{6C960AFF-E533-4B61-A559-107CA9AA5E76}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,5 +50,9 @@ Global {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D9CBBB5-2FF5-44F4-8358-D39CD0BD19EA}.Release|Any CPU.Build.0 = Release|Any CPU + {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index eb2daaa6d7..dfb481ac37 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -8,7 +8,7 @@ - + From 86c1ce0d17d34854a096249adf1339436940b889 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:10:15 +0000 Subject: [PATCH 025/124] Dependency Injection --- TUnit.TestAdapter/AssemblyLoader.cs | 2 +- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 16 ++++--- .../Extensions/ServiceCollectionExtensions.cs | 18 ++++++++ TUnit.TestAdapter/NoOpRunContext.cs | 11 ----- .../ReflectionMetadataProvider.cs | 20 --------- TUnit.TestAdapter/SourceLocationHelper.cs | 10 ++--- .../Stubs/NoOpFrameworkHandle.cs | 36 ++++++++++++++++ TUnit.TestAdapter/Stubs/NoOpRunContext.cs | 23 ++++++++++ TUnit.TestAdapter/TestCollector.cs | 14 ++----- TUnit.TestAdapter/TestDiscoverer.cs | 16 ++++++- TUnit.TestAdapter/TestExecutor.cs | 42 +++++++++++++------ TUnit.TestAdapter/TestsLoader.cs | 6 +-- 12 files changed, 144 insertions(+), 70 deletions(-) create mode 100644 TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs delete mode 100644 TUnit.TestAdapter/NoOpRunContext.cs create mode 100644 TUnit.TestAdapter/Stubs/NoOpFrameworkHandle.cs create mode 100644 TUnit.TestAdapter/Stubs/NoOpRunContext.cs diff --git a/TUnit.TestAdapter/AssemblyLoader.cs b/TUnit.TestAdapter/AssemblyLoader.cs index 529d0d08af..047537a84a 100644 --- a/TUnit.TestAdapter/AssemblyLoader.cs +++ b/TUnit.TestAdapter/AssemblyLoader.cs @@ -3,7 +3,7 @@ namespace TUnit.TestAdapter; -internal class AssemblyLoader +public class AssemblyLoader { internal Assembly? LoadByPath(string assemblyPath) { diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index 91f9947073..50554322b9 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -10,14 +10,20 @@ namespace TUnit.TestAdapter; -public class AsyncTestRunExecutor(SingleTestExecutor singleTestExecutor, MethodInvoker methodInvoker, CancellationTokenSource cancellationTokenSource) +public class AsyncTestRunExecutor + ( + SingleTestExecutor singleTestExecutor, + MethodInvoker methodInvoker, + ITestExecutionRecorder testExecutionRecorder, + CancellationTokenSource cancellationTokenSource + ) { private bool _canRunAnotherTest = true; private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); private readonly List _setResultsTasks = new(); - public async Task RunInAsyncContext(IEnumerable tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + public async Task RunInAsyncContext(IEnumerable tests) { var allTestsOrderedByClass = tests .GroupBy(x => x.Details.FullyQualifiedClassName) @@ -53,7 +59,7 @@ public async Task RunInAsyncContext(IEnumerable tests, IRunCon var result = t.Result; var testDetails = testWithResult.Test.Details; - frameworkHandle?.RecordResult(new TestResult(testWithResult.Test.TestCase) + testExecutionRecorder?.RecordResult(new TestResult(testWithResult.Test.TestCase) { DisplayName = testDetails.DisplayName, Outcome = GetOutcome(result.Status), @@ -69,8 +75,8 @@ public async Task RunInAsyncContext(IEnumerable tests, IRunCon executingTests.RemoveAll(x => x.Result.IsCompletedSuccessfully); - await WhenAllSafely(executingTests.Select(x => x.Result), frameworkHandle); - await WhenAllSafely(_oneTimeTearDownRegistry.Values, frameworkHandle); + await WhenAllSafely(executingTests.Select(x => x.Result), testExecutionRecorder); + await WhenAllSafely(_oneTimeTearDownRegistry.Values, testExecutionRecorder); await Task.WhenAll(_setResultsTasks); } diff --git a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..a911b89d6c --- /dev/null +++ b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using TUnit.Engine; + +namespace TUnit.TestAdapter.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddTestAdapterServices(this IServiceCollection services) + { + return services.AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/NoOpRunContext.cs b/TUnit.TestAdapter/NoOpRunContext.cs deleted file mode 100644 index f532ae40f7..0000000000 --- a/TUnit.TestAdapter/NoOpRunContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace TUnit.TestAdapter; - -public class NoOpRunContext -{ - -} - -public class NoOpFrameworkHandle -{ - -} \ No newline at end of file diff --git a/TUnit.TestAdapter/ReflectionMetadataProvider.cs b/TUnit.TestAdapter/ReflectionMetadataProvider.cs index 24fa4b2aa2..3118fa33b1 100644 --- a/TUnit.TestAdapter/ReflectionMetadataProvider.cs +++ b/TUnit.TestAdapter/ReflectionMetadataProvider.cs @@ -5,22 +5,6 @@ namespace TUnit.TestAdapter { internal sealed class ReflectionMetadataProvider { - public Type? GetDeclaringType(string assemblyPath, string reflectedTypeName, string methodName) - { - var type = TryGetSingleMethod(assemblyPath, reflectedTypeName, methodName)?.DeclaringType; - if (type == null) - { - return null; - } - - if (type.IsConstructedGenericType) - { - type = type.GetGenericTypeDefinition(); - } - - return type; - } - public Type? GetStateMachineType(string assemblyPath, string reflectedTypeName, string methodName) { var method = TryGetSingleMethod(assemblyPath, reflectedTypeName, methodName); @@ -80,9 +64,5 @@ internal sealed class ReflectionMetadataProvider return null; } } - - public void Dispose() - { - } } } diff --git a/TUnit.TestAdapter/SourceLocationHelper.cs b/TUnit.TestAdapter/SourceLocationHelper.cs index d9c16563d3..46e4bfe62c 100644 --- a/TUnit.TestAdapter/SourceLocationHelper.cs +++ b/TUnit.TestAdapter/SourceLocationHelper.cs @@ -4,7 +4,7 @@ namespace TUnit.TestAdapter; -public class SourceLocationHelper(IMessageLogger? logger) : IDisposable +public class SourceLocationHelper(IMessageLogger logger) : IDisposable { private static readonly SourceLocation EmptySourceLocation = new(null, 0, 0); @@ -19,8 +19,8 @@ public SourceLocation GetSourceLocation(string assemblyLocation, string classNam if (navigationData is null) { - logger?.SendMessage(TestMessageLevel.Error, $"No navigation data found for {className}.{methodName}"); - logger?.SendMessage(TestMessageLevel.Error, $"Assembly: {assemblyLocation}"); + logger.SendMessage(TestMessageLevel.Error, $"No navigation data found for {className}.{methodName}"); + logger.SendMessage(TestMessageLevel.Error, $"Assembly: {assemblyLocation}"); return EmptySourceLocation; } @@ -29,8 +29,8 @@ public SourceLocation GetSourceLocation(string assemblyLocation, string classNam } catch (Exception e) { - logger?.SendMessage(TestMessageLevel.Error, $"Error retrieving source location for {className}.{methodName}"); - logger?.SendMessage(TestMessageLevel.Error, e.ToString()); + logger.SendMessage(TestMessageLevel.Error, $"Error retrieving source location for {className}.{methodName}"); + logger.SendMessage(TestMessageLevel.Error, e.ToString()); return EmptySourceLocation; } diff --git a/TUnit.TestAdapter/Stubs/NoOpFrameworkHandle.cs b/TUnit.TestAdapter/Stubs/NoOpFrameworkHandle.cs new file mode 100644 index 0000000000..d1f180252e --- /dev/null +++ b/TUnit.TestAdapter/Stubs/NoOpFrameworkHandle.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace TUnit.TestAdapter.Stubs; + +public class NoOpFrameworkHandle : IFrameworkHandle +{ + public void SendMessage(TestMessageLevel testMessageLevel, string message) + { + } + + public void RecordResult(TestResult testResult) + { + } + + public void RecordStart(TestCase testCase) + { + } + + public void RecordEnd(TestCase testCase, TestOutcome outcome) + { + } + + public void RecordAttachments(IList attachmentSets) + { + } + + public int LaunchProcessWithDebuggerAttached(string filePath, string? workingDirectory, string? arguments, + IDictionary? environmentVariables) + { + return 0; + } + + public bool EnableShutdownAfterTestRun { get; set; } = true; +} \ No newline at end of file diff --git a/TUnit.TestAdapter/Stubs/NoOpRunContext.cs b/TUnit.TestAdapter/Stubs/NoOpRunContext.cs new file mode 100644 index 0000000000..936dba2aa8 --- /dev/null +++ b/TUnit.TestAdapter/Stubs/NoOpRunContext.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.TestPlatform.Common; +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +namespace TUnit.TestAdapter.Stubs; + +public class NoOpRunContext : IRunContext +{ + public IRunSettings? RunSettings { get; } = new RunSettings(); + + public ITestCaseFilterExpression? GetTestCaseFilter(IEnumerable? supportedProperties, Func propertyProvider) + { + return new TestCaseFilterExpression(new FilterExpressionWrapper(string.Empty)); + } + + public bool KeepAlive => false; + public bool InIsolation => false; + public bool IsDataCollectionEnabled => false; + public bool IsBeingDebugged => false; + public string? TestRunDirectory { get; } = Directory.GetCurrentDirectory(); + public string? SolutionDirectory { get; } = Directory.GetCurrentDirectory(); +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 4f6e18144e..7e66f0967f 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -6,7 +6,7 @@ namespace TUnit.TestAdapter; -public class TestCollector(IMessageLogger? messageLogger) +public class TestCollector(AssemblyLoader assemblyLoader, TestsLoader testsLoader, ITestExecutionRecorder testExecutionRecorder) { public TestCollection CollectionFromSources(IEnumerable sources) { @@ -15,11 +15,8 @@ public TestCollection CollectionFromSources(IEnumerable sources) return new TestCollection(sourcesAsList, TestsFromSources(sourcesAsList)); } - public IEnumerable TestsFromTestCases(IEnumerable testCases, ITestExecutionRecorder? testExecutionRecorder) + public IEnumerable TestsFromTestCases(IEnumerable testCases) { - var assemblyLoader = new AssemblyLoader(); - var testsLoader = new TestsLoader(messageLogger); - foreach (var testCase in testCases) { var source = testCase.Source; @@ -46,11 +43,11 @@ public IEnumerable TestsFromTestCases(IEnumerable te } } - private void MarkNotFound(TestCase testCase, ITestExecutionRecorder? testExecutionRecorder) + private void MarkNotFound(TestCase testCase, ITestExecutionRecorder testExecutionRecorder) { var now = DateTimeOffset.Now; - testExecutionRecorder?.RecordResult(new TestResult(testCase) + testExecutionRecorder.RecordResult(new TestResult(testCase) { DisplayName = testCase.DisplayName, Outcome = TestOutcome.NotFound, @@ -63,9 +60,6 @@ private void MarkNotFound(TestCase testCase, ITestExecutionRecorder? testExecuti public IEnumerable TestsFromSources(IEnumerable sources) { - var assemblyLoader = new AssemblyLoader(); - var testsLoader = new TestsLoader(messageLogger); - var tests = sources .Select(source => Path.IsPathRooted(source) ? source : Path.Combine(Directory.GetCurrentDirectory(), source)) .Select(assemblyLoader.LoadByPath) diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index d08bac4ec3..577037ea88 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -1,8 +1,10 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.TestAdapter.Constants; using TUnit.TestAdapter.Extensions; +using TUnit.TestAdapter.Stubs; namespace TUnit.TestAdapter; @@ -17,7 +19,8 @@ public void DiscoverTests(IEnumerable sources, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) { - var testCollector = new TestCollector(logger); + var testCollector = BuildServices(discoveryContext, logger) + .GetRequiredService(); foreach (var test in testCollector.TestsFromSources(sources)) { @@ -25,4 +28,13 @@ public void DiscoverTests(IEnumerable sources, discoverySink.SendTestCase(test.ToTestCase()); } } + + private IServiceProvider BuildServices(IDiscoveryContext discoveryContext, IMessageLogger messageLogger) + { + return new ServiceCollection() + .AddSingleton(discoveryContext) + .AddSingleton(messageLogger) + .AddTestAdapterServices() + .BuildServiceProvider(); + } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index dfb856f16c..26b71aaa3d 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -1,7 +1,9 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using TUnit.TestAdapter.Constants; using TUnit.TestAdapter.Extensions; +using TUnit.TestAdapter.Stubs; namespace TUnit.TestAdapter; @@ -12,13 +14,7 @@ namespace TUnit.TestAdapter; public class TestExecutor : ITestExecutor2 { private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly AsyncTestRunExecutor _asyncTestRunExecutor; - public TestExecutor() - { - _asyncTestRunExecutor = new AsyncTestRunExecutor(_cancellationTokenSource); - } - public void RunTests(IEnumerable? testCases, IRunContext? runContext, IFrameworkHandle? frameworkHandle) { if (testCases is null) @@ -26,10 +22,14 @@ public void RunTests(IEnumerable? testCases, IRunContext? runContext, return; } - var testsWithTestCases = new TestCollector(frameworkHandle) - .TestsFromTestCases(testCases, frameworkHandle); + var serviceProvider = BuildServices(runContext, frameworkHandle); + + var testsWithTestCases = + serviceProvider.GetRequiredService() + .TestsFromTestCases(testCases); - _asyncTestRunExecutor.RunInAsyncContext(testsWithTestCases, runContext, frameworkHandle) + serviceProvider.GetRequiredService() + .RunInAsyncContext(testsWithTestCases) .GetAwaiter() .GetResult(); } @@ -40,11 +40,18 @@ public void RunTests(IEnumerable? sources, IRunContext? runContext, IFra { return; } + + var serviceProvider = BuildServices(runContext, frameworkHandle); - var tests = new TestCollector(frameworkHandle).TestsFromSources(sources) + var tests = serviceProvider.GetRequiredService() + .TestsFromSources(sources) .Select(x => new TestWithTestCase(x, x.ToTestCase())); + + serviceProvider.GetRequiredService() + .RunInAsyncContext(tests) + .GetAwaiter() + .GetResult(); - _asyncTestRunExecutor.RunInAsyncContext(tests, runContext, frameworkHandle).GetAwaiter().GetResult(); } public void Cancel() @@ -61,4 +68,15 @@ public bool ShouldAttachToTestHost(IEnumerable? sources, IRunContext run { return runContext.IsBeingDebugged; } + + private IServiceProvider BuildServices(IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + return new ServiceCollection() + .AddSingleton(runContext ?? new NoOpRunContext()) + .AddSingleton(frameworkHandle ?? new NoOpFrameworkHandle()) + .AddSingleton(x => x.GetRequiredService()) + .AddSingleton(_cancellationTokenSource) + .AddTestAdapterServices() + .BuildServiceProvider(); + } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 817742ddc8..cca86415b4 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -1,14 +1,12 @@ using System.Reflection; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; using TUnit.Core.Attributes; namespace TUnit.TestAdapter; -public class TestsLoader(IMessageLogger? messageLogger) +public class TestsLoader(SourceLocationHelper sourceLocationHelper) { private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute)]; - private readonly SourceLocationHelper _sourceLocationHelper = new(messageLogger); public IEnumerable GetTests(TypeInformation typeInformation) { @@ -21,7 +19,7 @@ public IEnumerable GetTests(TypeInformation typeInformation) continue; } - var sourceLocation = _sourceLocationHelper + var sourceLocation = sourceLocationHelper .GetSourceLocation(typeInformation.Assembly.Location, methodInfo.DeclaringType!.FullName!, methodInfo.Name); foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) From 02cdc3e23cde5f420be90ab7e288082b01b5e792 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:12:33 +0000 Subject: [PATCH 026/124] Fix --- TUnit.TestAdapter/TestExecutor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index 26b71aaa3d..df3b5d8727 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.TestAdapter.Constants; using TUnit.TestAdapter.Extensions; using TUnit.TestAdapter.Stubs; @@ -75,6 +76,7 @@ private IServiceProvider BuildServices(IRunContext? runContext, IFrameworkHandle .AddSingleton(runContext ?? new NoOpRunContext()) .AddSingleton(frameworkHandle ?? new NoOpFrameworkHandle()) .AddSingleton(x => x.GetRequiredService()) + .AddSingleton(x => x.GetRequiredService()) .AddSingleton(_cancellationTokenSource) .AddTestAdapterServices() .BuildServiceProvider(); From 415553d55b38e7ccdeea3e3859df105a1a0cac41 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:14:15 +0000 Subject: [PATCH 027/124] Fix --- .../Extensions/ServiceCollectionExtensions.cs | 13 +++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 1 - TUnit.TestAdapter/TestDiscoverer.cs | 1 + TUnit.TestAdapter/TestExecutor.cs | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 TUnit.Engine/Extensions/ServiceCollectionExtensions.cs diff --git a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..860eb36980 --- /dev/null +++ b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; +using TUnit.Engine; + +namespace TUnit.TestAdapter.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddTestEngineServices(this IServiceCollection services) + { + return services.AddSingleton() + .AddSingleton(); + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs index a911b89d6c..d4924f55cb 100644 --- a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs @@ -9,7 +9,6 @@ public static IServiceCollection AddTestAdapterServices(this IServiceCollection { return services.AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index 577037ea88..b13d6db957 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -35,6 +35,7 @@ private IServiceProvider BuildServices(IDiscoveryContext discoveryContext, IMess .AddSingleton(discoveryContext) .AddSingleton(messageLogger) .AddTestAdapterServices() + .AddTestEngineServices() .BuildServiceProvider(); } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index df3b5d8727..ed4bd2127c 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -79,6 +79,7 @@ private IServiceProvider BuildServices(IRunContext? runContext, IFrameworkHandle .AddSingleton(x => x.GetRequiredService()) .AddSingleton(_cancellationTokenSource) .AddTestAdapterServices() + .AddTestEngineServices() .BuildServiceProvider(); } } \ No newline at end of file From 075c996d925e23f30d314af44a09665c2520cd7e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:37:01 +0000 Subject: [PATCH 028/124] Retry and Repeat Attributes --- TUnit.Core/Attributes/RepeatAttribute.cs | 17 ++++++++++++++ TUnit.Core/Attributes/RetryAttribute.cs | 17 ++++++++++++++ TUnit.Core/TestDetails.cs | 20 ++++++++++++++-- TUnit.Engine/SingleTestExecutor.cs | 30 ++++++++++++++++++++---- 4 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 TUnit.Core/Attributes/RepeatAttribute.cs create mode 100644 TUnit.Core/Attributes/RetryAttribute.cs diff --git a/TUnit.Core/Attributes/RepeatAttribute.cs b/TUnit.Core/Attributes/RepeatAttribute.cs new file mode 100644 index 0000000000..c59dc96a83 --- /dev/null +++ b/TUnit.Core/Attributes/RepeatAttribute.cs @@ -0,0 +1,17 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] +public class RepeatAttribute : TUnitAttribute +{ + public int Times { get; } + + public RepeatAttribute(int times) + { + if (times < 0) + { + throw new ArgumentOutOfRangeException(nameof(times), "Repeat times must be positive"); + } + + Times = times; + } +} \ No newline at end of file diff --git a/TUnit.Core/Attributes/RetryAttribute.cs b/TUnit.Core/Attributes/RetryAttribute.cs new file mode 100644 index 0000000000..d9d262512c --- /dev/null +++ b/TUnit.Core/Attributes/RetryAttribute.cs @@ -0,0 +1,17 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] +public class RetryAttribute : TUnitAttribute +{ + public int Times { get; } + + public RetryAttribute(int times) + { + if (times < 0) + { + throw new ArgumentOutOfRangeException(nameof(times), "Retry times must be positive"); + } + + Times = times; + } +} \ No newline at end of file diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index f97e7f50f5..d8e56ce2ad 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -24,16 +24,32 @@ public TestDetails(MethodInfo MethodInfo, Assembly = classType.Assembly; Source = classType.Assembly.Location; FullyQualifiedName = $"{classType.FullName}.{MethodInfo.Name}{GetParameterTypes(ParameterTypes)}"; - SkipReason = MethodInfo.CustomAttributes + + var methodAndClassAttributes = MethodInfo.CustomAttributes .Concat(classType.CustomAttributes) + .ToArray(); + + SkipReason = methodAndClassAttributes .FirstOrDefault(x => x.AttributeType == typeof(SkipAttribute)) ?.ConstructorArguments.FirstOrDefault().Value as string; + + RetryCount = methodAndClassAttributes + .FirstOrDefault(x => x.AttributeType == typeof(RetryAttribute)) + ?.ConstructorArguments.FirstOrDefault().Value as int? ?? 0; + + RepeatCount = methodAndClassAttributes + .FirstOrDefault(x => x.AttributeType == typeof(RepeatAttribute)) + ?.ConstructorArguments.FirstOrDefault().Value as int? ?? 0; FileName = SourceLocation.FileName; MinLineNumber = SourceLocation.MinLineNumber; MaxLineNumber = SourceLocation.MaxLineNumber; } - + + + public int RetryCount { get; set; } + public int RepeatCount { get; set; } + private string GetArgumentValues() { if (ArgumentValues == null) diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 6d11352da3..bee6954e90 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -72,11 +72,33 @@ private async Task ExecuteCore(TestDetails testDetails) { var @class = CreateTestClass(testDetails); - await ExecuteSetUps(@class); + var isRetry = testDetails.RetryCount > 0; + var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, testDetails.ArgumentValues?.ToArray()); - - await ExecuteTearDowns(@class); + for (var i = 0; i < executionCount + 1; i++) + { + try + { + await ExecuteSetUps(@class); + + await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, + testDetails.ArgumentValues?.ToArray()); + + await ExecuteTearDowns(@class); + + if (isRetry) + { + break; + } + } + catch + { + if (!isRetry || i == executionCount) + { + throw; + } + } + } } private async Task ExecuteSetUps(object @class) From aa7d4847c1901546ca21f4df67fc6c522ffd8c5b Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:44:28 +0000 Subject: [PATCH 029/124] TestClassCreator --- .../Extensions/ServiceCollectionExtensions.cs | 4 ++-- TUnit.Engine/SingleTestExecutor.cs | 11 ++++------- TUnit.Engine/TestClassCreator.cs | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 TUnit.Engine/TestClassCreator.cs diff --git a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs index 860eb36980..eb124fd992 100644 --- a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs @@ -1,13 +1,13 @@ using Microsoft.Extensions.DependencyInjection; -using TUnit.Engine; -namespace TUnit.TestAdapter.Extensions; +namespace TUnit.Engine.Extensions; public static class ServiceCollectionExtensions { public static IServiceCollection AddTestEngineServices(this IServiceCollection services) { return services.AddSingleton() + .AddSingleton() .AddSingleton(); } } \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index bee6954e90..e8142f1478 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -8,10 +8,12 @@ namespace TUnit.Engine; public class SingleTestExecutor { private readonly MethodInvoker _methodInvoker; + private readonly TestClassCreator _testClassCreator; - public SingleTestExecutor(MethodInvoker methodInvoker) + public SingleTestExecutor(MethodInvoker methodInvoker, TestClassCreator testClassCreator) { _methodInvoker = methodInvoker; + _testClassCreator = testClassCreator; } private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); @@ -70,7 +72,7 @@ public async Task ExecuteTest(TestDetails testDetails) private async Task ExecuteCore(TestDetails testDetails) { - var @class = CreateTestClass(testDetails); + var @class = _testClassCreator.CreateTestClass(testDetails); var isRetry = testDetails.RetryCount > 0; var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; @@ -155,9 +157,4 @@ private async Task ExecuteOneTimeSetUps(object @class) await _methodInvoker.InvokeMethod(@class, oneTimeSetUpMethod, BindingFlags.Static | BindingFlags.Public, null); } } - - private static object CreateTestClass(TestDetails testDetails) - { - return Activator.CreateInstance(testDetails.MethodInfo.DeclaringType!)!; - } } \ No newline at end of file diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs new file mode 100644 index 0000000000..d1f3a83a71 --- /dev/null +++ b/TUnit.Engine/TestClassCreator.cs @@ -0,0 +1,18 @@ +using TUnit.Core; + +namespace TUnit.Engine; + +public class TestClassCreator +{ + public object CreateTestClass(TestDetails testDetails) + { + try + { + return Activator.CreateInstance(testDetails.MethodInfo.DeclaringType!)!; + } + catch (Exception e) + { + throw new Exception("Cannot create an instance of the test class. Is there a public parameterless constructor?", e); + } + } +} \ No newline at end of file From 11d38095b8ed1b47e096d96a5f54985f1d7aed94 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:34:54 +0000 Subject: [PATCH 030/124] Refactor --- .../TUnit.Assertions.UnitTests.csproj | 1 + TUnit.Core/AssembliesAnd.cs | 5 ++ .../Attributes/TestDataSourceAttribute.cs | 20 +++++++ TUnit.Core/Status.cs | 9 +++ TUnit.Core/TUnit.Core.csproj | 2 +- TUnit.Core/TUnitTestResult.cs | 10 +--- TUnit.Core/TestCollection.cs | 7 ++- TUnit.Core/TestDetails.cs | 37 ++++++------ TUnit.Engine/ClassLoader.cs | 27 +++++++++ .../Extensions/ServiceCollectionExtensions.cs | 2 + TUnit.Engine/Extensions/TypeExtensions.cs | 13 +++++ TUnit.Engine/MethodInvoker.cs | 32 ++++++++--- TUnit.Engine/SingleTestExecutor.cs | 8 +-- TUnit.Engine/TUnit.Engine.csproj | 1 + TUnit.Engine/TestClassCreator.cs | 57 +++++++++++++++++-- TUnit.Engine/TestDataSourceRetriever.cs | 57 +++++++++++++++++++ TUnit.Pipeline/TUnit.Pipeline.csproj | 2 +- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 16 ++++-- TUnit.TestAdapter/TUnit.TestAdapter.csproj | 2 +- TUnit.TestAdapter/TestCollector.cs | 30 +++++++--- TUnit.TestAdapter/TestDiscoverer.cs | 5 +- TUnit.TestAdapter/TestExecutor.cs | 11 +++- TUnit.TestAdapter/TestWithResult.cs | 5 ++ TUnit.TestAdapter/TestWithTestCase.cs | 3 +- TUnit.TestAdapter/TestsLoader.cs | 52 +++++++++++++---- TUnit.TestProject/TUnit.TestProject.csproj | 1 + TUnit/TUnit.csproj | 1 + 27 files changed, 338 insertions(+), 78 deletions(-) create mode 100644 TUnit.Core/AssembliesAnd.cs create mode 100644 TUnit.Core/Attributes/TestDataSourceAttribute.cs create mode 100644 TUnit.Core/Status.cs create mode 100644 TUnit.Engine/ClassLoader.cs create mode 100644 TUnit.Engine/Extensions/TypeExtensions.cs create mode 100644 TUnit.Engine/TestDataSourceRetriever.cs create mode 100644 TUnit.TestAdapter/TestWithResult.cs diff --git a/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj b/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj index b3a4a45760..091cca0874 100644 --- a/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj +++ b/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj @@ -7,6 +7,7 @@ false true + latest diff --git a/TUnit.Core/AssembliesAnd.cs b/TUnit.Core/AssembliesAnd.cs new file mode 100644 index 0000000000..4695272b08 --- /dev/null +++ b/TUnit.Core/AssembliesAnd.cs @@ -0,0 +1,5 @@ +using System.Reflection; + +namespace TUnit.Core; + +public record AssembliesAnd(Assembly[] Assemblies, IEnumerable Values); \ No newline at end of file diff --git a/TUnit.Core/Attributes/TestDataSourceAttribute.cs b/TUnit.Core/Attributes/TestDataSourceAttribute.cs new file mode 100644 index 0000000000..e6421151bc --- /dev/null +++ b/TUnit.Core/Attributes/TestDataSourceAttribute.cs @@ -0,0 +1,20 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +public class TestDataSourceAttribute : TUnitAttribute +{ + public string? ClassNameProvidingDataSource { get; } + public string MethodNameProvidingDataSource { get; } + + + public TestDataSourceAttribute(string methodNameProvidingDataSource) + { + MethodNameProvidingDataSource = methodNameProvidingDataSource; + } + + public TestDataSourceAttribute(string classNameProvidingDataSource, string methodNameProvidingDataSource) + { + ClassNameProvidingDataSource = classNameProvidingDataSource; + MethodNameProvidingDataSource = methodNameProvidingDataSource; + } +} \ No newline at end of file diff --git a/TUnit.Core/Status.cs b/TUnit.Core/Status.cs new file mode 100644 index 0000000000..6d69ae01ca --- /dev/null +++ b/TUnit.Core/Status.cs @@ -0,0 +1,9 @@ +namespace TUnit.Core; + +public enum Status +{ + None, + Passed, + Failed, + Skipped +} \ No newline at end of file diff --git a/TUnit.Core/TUnit.Core.csproj b/TUnit.Core/TUnit.Core.csproj index 87ff03f1d0..97b7c882fc 100644 --- a/TUnit.Core/TUnit.Core.csproj +++ b/TUnit.Core/TUnit.Core.csproj @@ -4,7 +4,7 @@ net7.0 enable enable - default + latest diff --git a/TUnit.Core/TUnitTestResult.cs b/TUnit.Core/TUnitTestResult.cs index c198e86469..538b7c8b3c 100644 --- a/TUnit.Core/TUnitTestResult.cs +++ b/TUnit.Core/TUnitTestResult.cs @@ -9,12 +9,4 @@ public record TUnitTestResult public required TimeSpan Duration { get; init; } public required Exception? Exception { get; init; } public required string ComputerName { get; init; } -}; - -public enum Status -{ - None, - Passed, - Failed, - Skipped -} \ No newline at end of file +}; \ No newline at end of file diff --git a/TUnit.Core/TestCollection.cs b/TUnit.Core/TestCollection.cs index 245bebf2d8..b7a4acfa1a 100644 --- a/TUnit.Core/TestCollection.cs +++ b/TUnit.Core/TestCollection.cs @@ -8,6 +8,8 @@ namespace TUnit.Core; /// public sealed class TestCollection { + private readonly AssembliesAnd _assembliesAndTestDetails; + /// /// The test sources (assembly file names). /// @@ -18,10 +20,11 @@ public sealed class TestCollection /// public IReadOnlyList Tests { get; private set; } - public TestCollection(IEnumerable sources, IEnumerable tests) + public TestCollection(IEnumerable sources, AssembliesAnd assembliesAndTestDetails) { + _assembliesAndTestDetails = assembliesAndTestDetails; Sources = ImmutableArray.CreateRange(sources); - Tests = ImmutableArray.CreateRange(tests); + Tests = ImmutableArray.CreateRange(assembliesAndTestDetails.Values); } /// diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index d8e56ce2ad..5c1279694e 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -6,12 +6,12 @@ namespace TUnit.Core; public record TestDetails { public TestDetails(MethodInfo MethodInfo, + Type ClassType, SourceLocation SourceLocation, ParameterArgument[]? arguments) { - var classType = MethodInfo.DeclaringType!; - this.MethodInfo = MethodInfo; + this.ClassType = ClassType; this.SourceLocation = SourceLocation; ParameterTypes = arguments?.Select(x => x.Type).ToArray(); @@ -19,14 +19,14 @@ public TestDetails(MethodInfo MethodInfo, TestName = MethodInfo.Name; DisplayName = MethodInfo.Name + GetArgumentValues(); - ClassName = classType.Name; - FullyQualifiedClassName = classType.FullName!; - Assembly = classType.Assembly; - Source = classType.Assembly.Location; - FullyQualifiedName = $"{classType.FullName}.{MethodInfo.Name}{GetParameterTypes(ParameterTypes)}"; + ClassName = this.ClassType.Name; + FullyQualifiedClassName = this.ClassType.FullName!; + Assembly = this.ClassType.Assembly; + Source = this.ClassType.Assembly.Location; + FullyQualifiedName = $"{this.ClassType.FullName}.{MethodInfo.Name}{GetParameterTypes(ParameterTypes)}"; var methodAndClassAttributes = MethodInfo.CustomAttributes - .Concat(classType.CustomAttributes) + .Concat(this.ClassType.CustomAttributes) .ToArray(); SkipReason = methodAndClassAttributes @@ -45,10 +45,10 @@ public TestDetails(MethodInfo MethodInfo, MinLineNumber = SourceLocation.MinLineNumber; MaxLineNumber = SourceLocation.MaxLineNumber; } + - - public int RetryCount { get; set; } - public int RepeatCount { get; set; } + public int RetryCount { get; } + public int RepeatCount { get; } private string GetArgumentValues() { @@ -66,18 +66,19 @@ private string GetArgumentValues() public string ClassName { get; } - public string FullyQualifiedClassName { get; set; } + public string FullyQualifiedClassName { get; } public Assembly Assembly { get; } public string Source { get; } public string FullyQualifiedName { get; } - public MethodInfo MethodInfo { get; init; } - public string? FileName { get; set; } - public int MinLineNumber { get; set; } - public int MaxLineNumber { get; set; } - public Type[]? ParameterTypes { get; init; } - public object?[]? ArgumentValues { get; init; } + public MethodInfo MethodInfo { get; } + public Type ClassType { get; } + public string? FileName { get; } + public int MinLineNumber { get; } + public int MaxLineNumber { get; } + public Type[]? ParameterTypes { get; } + public object?[]? ArgumentValues { get; } public SourceLocation SourceLocation { get; } public string? SkipReason { get; } diff --git a/TUnit.Engine/ClassLoader.cs b/TUnit.Engine/ClassLoader.cs new file mode 100644 index 0000000000..142f494c8d --- /dev/null +++ b/TUnit.Engine/ClassLoader.cs @@ -0,0 +1,27 @@ +using System.Reflection; + +namespace TUnit.Engine; + +public class ClassLoader +{ + public IEnumerable GetAllTypes(Assembly[] assemblies) + { + return assemblies.SelectMany(LoadTypes); + } + + private static IEnumerable LoadTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException reflectionTypeLoadException) + { + return reflectionTypeLoadException.Types.OfType(); + } + catch + { + return Array.Empty(); + } + } +} \ No newline at end of file diff --git a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs index eb124fd992..d199dad532 100644 --- a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs @@ -8,6 +8,8 @@ public static IServiceCollection AddTestEngineServices(this IServiceCollection s { return services.AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton(); } } \ No newline at end of file diff --git a/TUnit.Engine/Extensions/TypeExtensions.cs b/TUnit.Engine/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..76e2bcb487 --- /dev/null +++ b/TUnit.Engine/Extensions/TypeExtensions.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace TUnit.Engine.Extensions; + +public static class TypeExtensions +{ + public static bool HasAttribute(this Type type, [NotNullWhen(true)] out T[]? attributes) where T : Attribute + { + attributes = type.GetCustomAttributes().ToArray(); + return attributes.Any(); + } +} \ No newline at end of file diff --git a/TUnit.Engine/MethodInvoker.cs b/TUnit.Engine/MethodInvoker.cs index 4ccecde44f..d00ba85fea 100644 --- a/TUnit.Engine/MethodInvoker.cs +++ b/TUnit.Engine/MethodInvoker.cs @@ -6,7 +6,7 @@ namespace TUnit.Engine; public class MethodInvoker { - public async Task InvokeMethod(object? @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) + public async Task InvokeMethod(object? @class, MethodInfo methodInfo, BindingFlags bindingFlags, object?[]? arguments) { try { @@ -15,20 +15,38 @@ public async Task InvokeMethod(object? @class, MethodInfo methodInfo, BindingFla if (result is ValueTask valueTask) { await valueTask; + + if (valueTask.GetType().IsGenericType) + { + return valueTask.GetType() + .GetProperty("Result", BindingFlags.Instance | BindingFlags.Public) + ?.GetValue(valueTask); + } + + return null; } - else if (result is Task task) + + if (result is Task task) { await task; + + if (task.GetType().IsGenericType) + { + return task.GetType() + .GetProperty("Result", BindingFlags.Instance | BindingFlags.Public) + ?.GetValue(task); + } + + return null; } + + return result; } catch (TargetInvocationException e) { - if (e.InnerException is null) - { - throw; - } + ExceptionDispatchInfo.Capture(e.InnerException ?? e).Throw(); - ExceptionDispatchInfo.Capture(e.InnerException).Throw(); + return null; } } } \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index e8142f1478..7c5bfab430 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -18,7 +18,7 @@ public SingleTestExecutor(MethodInvoker methodInvoker, TestClassCreator testClas private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); - public async Task ExecuteTest(TestDetails testDetails) + public async Task ExecuteTest(TestDetails testDetails, Type[] allClasses) { var start = DateTimeOffset.Now; @@ -38,7 +38,7 @@ public async Task ExecuteTest(TestDetails testDetails) try { - await ExecuteCore(testDetails); + await ExecuteCore(testDetails, allClasses); var end = DateTimeOffset.Now; @@ -70,9 +70,9 @@ public async Task ExecuteTest(TestDetails testDetails) } } - private async Task ExecuteCore(TestDetails testDetails) + private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) { - var @class = _testClassCreator.CreateTestClass(testDetails); + var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); var isRetry = testDetails.RetryCount > 0; var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; diff --git a/TUnit.Engine/TUnit.Engine.csproj b/TUnit.Engine/TUnit.Engine.csproj index 2579f4e532..5d75f2b180 100644 --- a/TUnit.Engine/TUnit.Engine.csproj +++ b/TUnit.Engine/TUnit.Engine.csproj @@ -4,6 +4,7 @@ net7.0 enable enable + latest diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs index d1f3a83a71..09710a8a58 100644 --- a/TUnit.Engine/TestClassCreator.cs +++ b/TUnit.Engine/TestClassCreator.cs @@ -1,10 +1,59 @@ -using TUnit.Core; +using System.Reflection; +using TUnit.Core; +using TUnit.Core.Attributes; +using TUnit.Engine.Extensions; namespace TUnit.Engine; -public class TestClassCreator +public class TestClassCreator(TestDataSourceRetriever testDataSourceRetriever) { - public object CreateTestClass(TestDetails testDetails) + public object CreateTestClass(TestDetails testDetails, Type[] allClasses) + { + if (testDetails.ClassType.HasAttribute(out var testDataSourceAttributes)) + { + return CreateWithTestDataSources(testDetails, testDataSourceAttributes, allClasses); + } + + return CreateBasicClass(testDetails); + } + + private IEnumerable CreateWithTestDataSources(TestDetails testDetails, + IEnumerable testDataSourceAttributes, + Type[] allClasses) + { + foreach (var testDataSourceAttribute in testDataSourceAttributes) + { + var className = testDataSourceAttribute.ClassNameProvidingDataSource; + + ParameterArgument[]? testData; + if (string.IsNullOrEmpty(className)) + { + var @class = testDetails.MethodInfo.DeclaringType!; + + testData = testDataSourceRetriever.GetTestDataSourceArguments( + @class, + testDataSourceAttribute.MethodNameProvidingDataSource + ); + } + else + { + var @class = allClasses.FirstOrDefault(x => x.FullName == className) + ?? allClasses.First(x => x.Name == className); + + testData = testDataSourceRetriever.GetTestDataSourceArguments( + @class, + testDataSourceAttribute.MethodNameProvidingDataSource + ); + } + + yield return Activator.CreateInstance( + testDetails.ClassType, + testData?.Select(x => x.Value).ToArray() + )!; + } + } + + private static object CreateBasicClass(TestDetails testDetails) { try { @@ -14,5 +63,5 @@ public object CreateTestClass(TestDetails testDetails) { throw new Exception("Cannot create an instance of the test class. Is there a public parameterless constructor?", e); } - } + } } \ No newline at end of file diff --git a/TUnit.Engine/TestDataSourceRetriever.cs b/TUnit.Engine/TestDataSourceRetriever.cs new file mode 100644 index 0000000000..8bf0965000 --- /dev/null +++ b/TUnit.Engine/TestDataSourceRetriever.cs @@ -0,0 +1,57 @@ +using System.Reflection; +using TUnit.Core; + +namespace TUnit.Engine; + +public class TestDataSourceRetriever(MethodInvoker methodInvoker) +{ + public ParameterArgument[]? GetTestDataSourceArguments(MethodInfo methodInfo, + CustomAttributeData testDataSourceAttribute, Type[] allClasses) + { + if (testDataSourceAttribute.ConstructorArguments.Count == 1) + { + // 1 argument means only method name supplied - Implies method is in same class + var methodName = (string) testDataSourceAttribute.ConstructorArguments[0].Value!; + var @class = methodInfo.DeclaringType!; + + return GetTestDataSourceArguments(@class, methodName); + } + else + { + // Class name and method name + var className = (string) testDataSourceAttribute.ConstructorArguments[0].Value!; + var methodName = (string) testDataSourceAttribute.ConstructorArguments[1].Value!; + + return GetTestDataSourceArguments(className, methodName, allClasses); + } + } + + public ParameterArgument[]? GetTestDataSourceArguments( + string className, + string methodName, + Type[] allClasses + ) + { + var @class = allClasses.FirstOrDefault(x => x.FullName == className) + ?? allClasses.First(x => x.Name == className); + + return GetTestDataSourceArguments(@class, methodName); + } + + public ParameterArgument[]? GetTestDataSourceArguments( + Type @class, + string methodName + ) + { + var method = @class.GetMethods().First(x => x.IsStatic && x.Name == methodName); + + var result = methodInvoker.InvokeMethod(null, method, BindingFlags.Static | BindingFlags.Public, null).Result; + + if (result is null) + { + return null; + } + + return [new ParameterArgument(result.GetType(), result)]; + } +} \ No newline at end of file diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj index 19136d3fb2..39af739300 100644 --- a/TUnit.Pipeline/TUnit.Pipeline.csproj +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -5,7 +5,7 @@ net7.0 enable enable - default + latest diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index 50554322b9..5028b8680c 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -15,17 +15,19 @@ public class AsyncTestRunExecutor SingleTestExecutor singleTestExecutor, MethodInvoker methodInvoker, ITestExecutionRecorder testExecutionRecorder, + ClassLoader classLoader, CancellationTokenSource cancellationTokenSource ) { private bool _canRunAnotherTest = true; private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); - private readonly List _setResultsTasks = new(); + private readonly List _setResultsTasks = []; - public async Task RunInAsyncContext(IEnumerable tests) + public async Task RunInAsyncContext(AssembliesAnd assembliesAndTests) { - var allTestsOrderedByClass = tests + var allTestsOrderedByClass = assembliesAndTests + .Values .GroupBy(x => x.Details.FullyQualifiedClassName) .SelectMany(x => x.ToList()) .ToList(); @@ -40,8 +42,10 @@ public async Task RunInAsyncContext(IEnumerable tests) MonitorSystemResources(); var executingTests = new List(); + + var allClasses = classLoader.GetAllTypes(assembliesAndTests.Assemblies).ToArray(); - await foreach (var testWithResult in ProcessQueue(queue)) + await foreach (var testWithResult in ProcessQueue(queue, allClasses)) { if (cancellationTokenSource.IsCancellationRequested) { @@ -118,7 +122,7 @@ private void SetupRunOneTimeTearDownForClass(TestDetails processingTestDetails, }); } - private async IAsyncEnumerable ProcessQueue(Queue queue) + private async IAsyncEnumerable ProcessQueue(Queue queue, Type[] allClasses) { while (queue.Count > 0) { @@ -126,7 +130,7 @@ private async IAsyncEnumerable ProcessQueue(Queuenet7.0 enable enable - default + latest true Library true diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 7e66f0967f..3da0855f1c 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -15,9 +15,20 @@ public TestCollection CollectionFromSources(IEnumerable sources) return new TestCollection(sourcesAsList, TestsFromSources(sourcesAsList)); } - public IEnumerable TestsFromTestCases(IEnumerable testCases) + public AssembliesAnd TestsFromTestCases(IEnumerable testCases) { - foreach (var testCase in testCases) + var testCasesArray = testCases.ToArray(); + var allAssemblies = testCasesArray + .Select(x => assemblyLoader.LoadByPath(x.Source)) + .OfType() + .ToArray(); + + return new AssembliesAnd(allAssemblies, TestWithTestCaseCore(testCasesArray, allAssemblies)); + } + + private IEnumerable TestWithTestCaseCore(TestCase[] testCasesArray, Assembly[] allAssemblies) + { + foreach (var testCase in testCasesArray) { var source = testCase.Source; var assembly = assemblyLoader.LoadByPath(source); @@ -28,10 +39,10 @@ public IEnumerable TestsFromTestCases(IEnumerable te continue; } - var tests = testsLoader.GetTests(new TypeInformation(assembly)); + var tests = testsLoader.GetTests(new TypeInformation(assembly), allAssemblies); var matchingTest = tests.FirstOrDefault(x => x.FullyQualifiedName == testCase.FullyQualifiedName - && x.DisplayName == testCase.DisplayName); + && x.DisplayName == testCase.DisplayName); if (matchingTest is null) { @@ -58,15 +69,18 @@ private void MarkNotFound(TestCase testCase, ITestExecutionRecorder testExecutio }); } - public IEnumerable TestsFromSources(IEnumerable sources) + public AssembliesAnd TestsFromSources(IEnumerable sources) { - var tests = sources + var allAssemblies = sources .Select(source => Path.IsPathRooted(source) ? source : Path.Combine(Directory.GetCurrentDirectory(), source)) .Select(assemblyLoader.LoadByPath) .OfType() + .ToArray(); + + var tests = allAssemblies .Select(x => new TypeInformation(x)) - .SelectMany(x => testsLoader.GetTests(x)); + .SelectMany(x => testsLoader.GetTests(x, allAssemblies)); - return tests; + return new AssembliesAnd(allAssemblies, tests); } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index b13d6db957..e7b79807db 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Engine.Extensions; using TUnit.TestAdapter.Constants; using TUnit.TestAdapter.Extensions; using TUnit.TestAdapter.Stubs; @@ -21,8 +22,10 @@ public void DiscoverTests(IEnumerable sources, { var testCollector = BuildServices(discoveryContext, logger) .GetRequiredService(); + + var assembliesAndTests = testCollector.TestsFromSources(sources); - foreach (var test in testCollector.TestsFromSources(sources)) + foreach (var test in assembliesAndTests.Values) { logger.SendMessage(TestMessageLevel.Informational, "Test found: " + test.FullyQualifiedName); discoverySink.SendTestCase(test.ToTestCase()); diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index ed4bd2127c..fd805e6241 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -2,6 +2,8 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Core; +using TUnit.Engine.Extensions; using TUnit.TestAdapter.Constants; using TUnit.TestAdapter.Extensions; using TUnit.TestAdapter.Stubs; @@ -44,12 +46,15 @@ public void RunTests(IEnumerable? sources, IRunContext? runContext, IFra var serviceProvider = BuildServices(runContext, frameworkHandle); - var tests = serviceProvider.GetRequiredService() - .TestsFromSources(sources) + var assembliesAndTestsFromSources = serviceProvider.GetRequiredService() + .TestsFromSources(sources); + + var tests = assembliesAndTestsFromSources + .Values .Select(x => new TestWithTestCase(x, x.ToTestCase())); serviceProvider.GetRequiredService() - .RunInAsyncContext(tests) + .RunInAsyncContext(new AssembliesAnd(assembliesAndTestsFromSources.Assemblies, tests)) .GetAwaiter() .GetResult(); diff --git a/TUnit.TestAdapter/TestWithResult.cs b/TUnit.TestAdapter/TestWithResult.cs new file mode 100644 index 0000000000..444091c88f --- /dev/null +++ b/TUnit.TestAdapter/TestWithResult.cs @@ -0,0 +1,5 @@ +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestWithTestCase.cs b/TUnit.TestAdapter/TestWithTestCase.cs index 462a6cb3f0..411781a35a 100644 --- a/TUnit.TestAdapter/TestWithTestCase.cs +++ b/TUnit.TestAdapter/TestWithTestCase.cs @@ -3,5 +3,4 @@ namespace TUnit.TestAdapter; -public record TestWithTestCase(TestDetails Details, TestCase TestCase); -public record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file +public record TestWithTestCase(TestDetails Details, TestCase TestCase); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index cca86415b4..371f34235f 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -1,14 +1,15 @@ using System.Reflection; using TUnit.Core; using TUnit.Core.Attributes; +using TUnit.Engine; namespace TUnit.TestAdapter; -public class TestsLoader(SourceLocationHelper sourceLocationHelper) +public class TestsLoader(SourceLocationHelper sourceLocationHelper, ClassLoader classLoader, TestDataSourceRetriever testDataSourceRetriever) { - private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute)]; + private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute), typeof(TestDataSourceAttribute)]; - public IEnumerable GetTests(TypeInformation typeInformation) + public IEnumerable GetTests(TypeInformation typeInformation, Assembly[] allAssemblies) { var methods = typeInformation.Types.SelectMany(x => x.GetMethods()); @@ -21,6 +22,11 @@ public IEnumerable GetTests(TypeInformation typeInformation) var sourceLocation = sourceLocationHelper .GetSourceLocation(typeInformation.Assembly.Location, methodInfo.DeclaringType!.FullName!, methodInfo.Name); + + var allClasses = classLoader.GetAllTypes(allAssemblies).ToArray(); + var nonAbstractClassesContainingTest = allClasses + .Where(t => t.IsAssignableTo(methodInfo.DeclaringType!) && !t.IsAbstract) + .ToArray(); foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) { @@ -31,27 +37,51 @@ public IEnumerable GetTests(TypeInformation typeInformation) ?.Select(x => new ParameterArgument(x.Value?.GetType()!, x.Value)) .ToArray(); - yield return new TestDetails( + foreach (var classType in nonAbstractClassesContainingTest) + { + yield return new TestDetails( MethodInfo: methodInfo, + ClassType: classType, SourceLocation: sourceLocation, arguments: arguments - ); + ); + } } } if(methodInfo.CustomAttributes.Any(x => x.AttributeType == typeof(TestAttribute))) { - yield return new TestDetails( - MethodInfo: methodInfo, - SourceLocation: sourceLocation, - arguments: null - ); + foreach (var classType in nonAbstractClassesContainingTest) + { + yield return new TestDetails( + MethodInfo: methodInfo, + ClassType: classType, + SourceLocation: sourceLocation, + arguments: null + ); + } + } + + foreach (var testDataSourceAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestDataSourceAttribute))) + { + foreach (var classType in nonAbstractClassesContainingTest) + { + yield return new TestDetails( + MethodInfo: methodInfo, + ClassType: classType, + SourceLocation: sourceLocation, + arguments: testDataSourceRetriever.GetTestDataSourceArguments(methodInfo, testDataSourceAttribute, allClasses) + ); + } } } } private static bool HasTestAttributes(MethodInfo methodInfo) { - return methodInfo.CustomAttributes.Select(x => x.AttributeType).Intersect(TestAttributes).Any(); + return methodInfo.CustomAttributes + .Select(x => x.AttributeType) + .Intersect(TestAttributes) + .Any(); } } \ No newline at end of file diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index 1e847d0523..ddd1ea0e4e 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -6,6 +6,7 @@ enable false true + latest diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index dfb481ac37..9c1efe93f5 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -4,6 +4,7 @@ net7.0 enable enable + latest From ad02d8ad00e8dfea9a2f27e11e930b755b46d811 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:46:06 +0000 Subject: [PATCH 031/124] TestDataSource Tests --- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 2 +- TUnit.TestProject/TestDataSources.cs | 7 +++ TUnit.TestProject/Tests.cs | 55 +++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 TUnit.TestProject/TestDataSources.cs diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index 5028b8680c..eb1214aa34 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -63,7 +63,7 @@ public async Task RunInAsyncContext(AssembliesAnd assembliesAn var result = t.Result; var testDetails = testWithResult.Test.Details; - testExecutionRecorder?.RecordResult(new TestResult(testWithResult.Test.TestCase) + testExecutionRecorder.RecordResult(new TestResult(testWithResult.Test.TestCase) { DisplayName = testDetails.DisplayName, Outcome = GetOutcome(result.Status), diff --git a/TUnit.TestProject/TestDataSources.cs b/TUnit.TestProject/TestDataSources.cs new file mode 100644 index 0000000000..21c3a1d78d --- /dev/null +++ b/TUnit.TestProject/TestDataSources.cs @@ -0,0 +1,7 @@ +namespace TUnit.TestProject; + +public class TestDataSources +{ + public static int One() => 1; + public static int Two() => 2; +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index e9ebad549d..cc007929c6 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -69,4 +69,59 @@ public async Task Skip2() var value = "1"; Assert.That(value, Is.EqualTo("1")); } + + [TestDataSource(nameof(One))] + public void TestDataSource1(int value) + { + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(One))] + public async Task TestDataSource2(int value) + { + await Task.Yield(); + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(Two))] + public void TestDataSource3(int value) + { + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(Two))] + public async Task TestDataSource4(int value) + { + await Task.Yield(); + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(TestDataSources), nameof(One))] + public void TestDataSource5(int value) + { + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(TestDataSources), nameof(One))] + public async Task TestDataSource6(int value) + { + await Task.Yield(); + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(TestDataSources), nameof(Two))] + public void TestDataSource7(int value) + { + Assert.That(value, Is.EqualTo(1)); + } + + [TestDataSource(nameof(TestDataSources), nameof(Two))] + public async Task TestDataSource8(int value) + { + await Task.Yield(); + Assert.That(value, Is.EqualTo(1)); + } + + public static int One() => 1; + public static int Two() => 2; } \ No newline at end of file From c736d0e32ac191f43e41a4d70ebfe53a53673380 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 17:52:08 +0000 Subject: [PATCH 032/124] Disposer.cs --- TUnit.Engine/Disposer.cs | 19 ++++++++ .../Extensions/ServiceCollectionExtensions.cs | 1 + TUnit.Engine/SingleTestExecutor.cs | 47 ++++++++++++------- 3 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 TUnit.Engine/Disposer.cs diff --git a/TUnit.Engine/Disposer.cs b/TUnit.Engine/Disposer.cs new file mode 100644 index 0000000000..74c243b008 --- /dev/null +++ b/TUnit.Engine/Disposer.cs @@ -0,0 +1,19 @@ +namespace TUnit.Engine; + +public class Disposer +{ + public ValueTask DisposeAsync(object obj) + { + if (obj is IAsyncDisposable asyncDisposable) + { + return asyncDisposable.DisposeAsync(); + } + + if (obj is IDisposable disposable) + { + disposable.Dispose(); + } + + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs index d199dad532..c77b09681b 100644 --- a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ public static IServiceCollection AddTestEngineServices(this IServiceCollection s .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(); } } \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 7c5bfab430..4935689b80 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -9,11 +9,15 @@ public class SingleTestExecutor { private readonly MethodInvoker _methodInvoker; private readonly TestClassCreator _testClassCreator; + private readonly Disposer _disposer; - public SingleTestExecutor(MethodInvoker methodInvoker, TestClassCreator testClassCreator) + public SingleTestExecutor(MethodInvoker methodInvoker, + TestClassCreator testClassCreator, + Disposer disposer) { _methodInvoker = methodInvoker; _testClassCreator = testClassCreator; + _disposer = disposer; } private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); @@ -74,33 +78,40 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) { var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); - var isRetry = testDetails.RetryCount > 0; - var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - - for (var i = 0; i < executionCount + 1; i++) + try { - try + var isRetry = testDetails.RetryCount > 0; + var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; + + for (var i = 0; i < executionCount + 1; i++) { - await ExecuteSetUps(@class); + try + { + await ExecuteSetUps(@class); - await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, - testDetails.ArgumentValues?.ToArray()); + await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, + testDetails.ArgumentValues?.ToArray()); - await ExecuteTearDowns(@class); + await ExecuteTearDowns(@class); - if (isRetry) - { - break; + if (isRetry) + { + break; + } } - } - catch - { - if (!isRetry || i == executionCount) + catch { - throw; + if (!isRetry || i == executionCount) + { + throw; + } } } } + finally + { + await _disposer.DisposeAsync(@class); + } } private async Task ExecuteSetUps(object @class) From e8ea409440ff5ccfd318cc3c4c7c13a48065098e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:07:33 +0000 Subject: [PATCH 033/124] Use ID --- TUnit.Core/TestDetails.cs | 19 +++++++++++-- .../Stubs/NoOpExecutionRecorder.cs | 28 +++++++++++++++++++ TUnit.TestAdapter/TestCollector.cs | 10 +++---- TUnit.TestAdapter/TestDiscoverer.cs | 1 + 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 TUnit.TestAdapter/Stubs/NoOpExecutionRecorder.cs diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 5c1279694e..05772e7e3e 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -1,4 +1,6 @@ using System.Reflection; +using System.Security.Cryptography; +using System.Text; using TUnit.Core.Attributes; namespace TUnit.Core; @@ -44,8 +46,21 @@ public TestDetails(MethodInfo MethodInfo, FileName = SourceLocation.FileName; MinLineNumber = SourceLocation.MinLineNumber; MaxLineNumber = SourceLocation.MaxLineNumber; + + Id = GenerateGuid(); } - + + private Guid GenerateGuid() + { + var bytes = Encoding.UTF8.GetBytes(DisplayName + MinLineNumber + new string(FullyQualifiedName.Reverse().ToArray())); + + var hashedBytes = SHA1.HashData(bytes); + + Array.Resize(ref hashedBytes, 16); + + return new Guid(hashedBytes); + } + public int RetryCount { get; } public int RepeatCount { get; } @@ -60,7 +75,7 @@ private string GetArgumentValues() return $"({string.Join(',', ArgumentValues.Select(StringifyArgument))})"; } - public Guid Id { get; } = Guid.NewGuid(); + public Guid Id { get; } public string TestName { get; } diff --git a/TUnit.TestAdapter/Stubs/NoOpExecutionRecorder.cs b/TUnit.TestAdapter/Stubs/NoOpExecutionRecorder.cs new file mode 100644 index 0000000000..fa8cb7acc5 --- /dev/null +++ b/TUnit.TestAdapter/Stubs/NoOpExecutionRecorder.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace TUnit.TestAdapter.Stubs; + +public class NoOpExecutionRecorder : ITestExecutionRecorder +{ + public void SendMessage(TestMessageLevel testMessageLevel, string message) + { + } + + public void RecordResult(TestResult testResult) + { + } + + public void RecordStart(TestCase testCase) + { + } + + public void RecordEnd(TestCase testCase, TestOutcome outcome) + { + } + + public void RecordAttachments(IList attachmentSets) + { + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 3da0855f1c..f4e89f0d5e 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -1,7 +1,6 @@ using System.Reflection; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; namespace TUnit.TestAdapter; @@ -35,18 +34,17 @@ private IEnumerable TestWithTestCaseCore(TestCase[] testCasesA if (assembly is null) { - MarkNotFound(testCase, testExecutionRecorder); + MarkNotFound(testCase); continue; } var tests = testsLoader.GetTests(new TypeInformation(assembly), allAssemblies); - var matchingTest = tests.FirstOrDefault(x => x.FullyQualifiedName == testCase.FullyQualifiedName - && x.DisplayName == testCase.DisplayName); + var matchingTest = tests.FirstOrDefault(x => x.Id == testCase.Id); if (matchingTest is null) { - MarkNotFound(testCase, testExecutionRecorder); + MarkNotFound(testCase); continue; } @@ -54,7 +52,7 @@ private IEnumerable TestWithTestCaseCore(TestCase[] testCasesA } } - private void MarkNotFound(TestCase testCase, ITestExecutionRecorder testExecutionRecorder) + private void MarkNotFound(TestCase testCase) { var now = DateTimeOffset.Now; diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index e7b79807db..8a9a1bce6c 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -37,6 +37,7 @@ private IServiceProvider BuildServices(IDiscoveryContext discoveryContext, IMess return new ServiceCollection() .AddSingleton(discoveryContext) .AddSingleton(messageLogger) + .AddSingleton() .AddTestAdapterServices() .AddTestEngineServices() .BuildServiceProvider(); From f4ae6f78be7a9b2d09363c46a7bb31ffd35f0d0a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 19:53:42 +0000 Subject: [PATCH 034/124] TUnitTestFilterProvider --- .../Constants/TestAdapterConstants.cs | 5 ++ .../Extensions/ServiceCollectionExtensions.cs | 1 + TUnit.TestAdapter/TUnitProperties.cs | 14 +++++ TUnit.TestAdapter/TUnitTestFilterProvider.cs | 57 +++++++++++++++++++ TUnit.TestAdapter/TestExecutor.cs | 17 +++++- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 TUnit.TestAdapter/TUnitProperties.cs create mode 100644 TUnit.TestAdapter/TUnitTestFilterProvider.cs diff --git a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs index 5c7d052d29..16ae0eef6d 100644 --- a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs +++ b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs @@ -4,4 +4,9 @@ internal static class TestAdapterConstants { internal const string ExecutorUriString = "executor://tunit/TestRunner/net"; internal static readonly Uri ExecutorUri = new(ExecutorUriString); + + public const string FullyQualifiedName = "TUnit.FullyQualifiedName"; + public const string Name = "TUnit.Name"; + public const string TestCategory = "TUnit.TestCategory"; + } \ No newline at end of file diff --git a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs index d4924f55cb..ccd859098f 100644 --- a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs @@ -12,6 +12,7 @@ public static IServiceCollection AddTestAdapterServices(this IServiceCollection .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(); } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnitProperties.cs b/TUnit.TestAdapter/TUnitProperties.cs new file mode 100644 index 0000000000..7ea517cffd --- /dev/null +++ b/TUnit.TestAdapter/TUnitProperties.cs @@ -0,0 +1,14 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TUnit.TestAdapter.Constants; + +namespace TUnit.TestAdapter; + +public class TUnitProperties +{ + internal static readonly TestProperty TestCategory = TestProperty.Register( + id: TestAdapterConstants.TestCategory, + label: "TestCategory", + valueType: typeof(string[]), + TestPropertyAttributes.Hidden, + owner: typeof(TestCase)); +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnitTestFilterProvider.cs b/TUnit.TestAdapter/TUnitTestFilterProvider.cs new file mode 100644 index 0000000000..5f59a522e6 --- /dev/null +++ b/TUnit.TestAdapter/TUnitTestFilterProvider.cs @@ -0,0 +1,57 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +namespace TUnit.TestAdapter; + +public class TUnitTestFilterProvider(IRunContext runContext) +{ + private static readonly Dictionary _supportedPropertiesCache; + private static readonly List SupportedProperties = []; + + static TUnitTestFilterProvider() + { + // Initialize the property cache + _supportedPropertiesCache = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["FullyQualifiedName"] = TestCaseProperties.FullyQualifiedName, + ["Name"] = TestCaseProperties.DisplayName, + ["TestCategory"] = TUnitProperties.TestCategory, + }; + + SupportedProperties.AddRange(_supportedPropertiesCache.Keys); + } + + public ITestCaseFilterExpression? GetFilter() + { + return runContext.GetTestCaseFilter(SupportedProperties, name => + _supportedPropertiesCache.TryGetValue(name, out var property) + ? property + : TestProperty.Find(name) + ); + } + + public IEnumerable FilterTests(IEnumerable tests) + { + var filter = GetFilter(); + + if (filter is null) + { + foreach (var testWithTestCase in tests) + { + yield return testWithTestCase; + } + + yield break; + } + + foreach (var testWithTestCase in tests) + { + var (testDetails, testCase) = testWithTestCase; + + if (filter.MatchTestCase(testCase, TestProperty.Find)) + { + yield return testWithTestCase; + } + } + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestExecutor.cs b/TUnit.TestAdapter/TestExecutor.cs index fd805e6241..0a91e19dc2 100644 --- a/TUnit.TestAdapter/TestExecutor.cs +++ b/TUnit.TestAdapter/TestExecutor.cs @@ -32,7 +32,7 @@ public void RunTests(IEnumerable? testCases, IRunContext? runContext, .TestsFromTestCases(testCases); serviceProvider.GetRequiredService() - .RunInAsyncContext(testsWithTestCases) + .RunInAsyncContext(Filter(testsWithTestCases, serviceProvider)) .GetAwaiter() .GetResult(); } @@ -52,14 +52,25 @@ public void RunTests(IEnumerable? sources, IRunContext? runContext, IFra var tests = assembliesAndTestsFromSources .Values .Select(x => new TestWithTestCase(x, x.ToTestCase())); - + serviceProvider.GetRequiredService() - .RunInAsyncContext(new AssembliesAnd(assembliesAndTestsFromSources.Assemblies, tests)) + .RunInAsyncContext(Filter(new AssembliesAnd(assembliesAndTestsFromSources.Assemblies, tests), serviceProvider)) .GetAwaiter() .GetResult(); } + private AssembliesAnd Filter(AssembliesAnd assembliesAnd, IServiceProvider serviceProvider) + { + var testFilterProvider = serviceProvider.GetRequiredService(); + + var tests = assembliesAnd.Values; + + var filteredTests = testFilterProvider.FilterTests(tests); + + return assembliesAnd with { Values = filteredTests }; + } + public void Cancel() { _cancellationTokenSource.Cancel(); From 8a4854a7bdd1ece8d06ae57e1f8c675a1b4027f9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 22:56:27 +0000 Subject: [PATCH 035/124] TestContext.cs --- TUnit.Core/TestContext.cs | 14 ++++++++++++++ TUnit.Engine/MethodInvoker.cs | 3 ++- TUnit.Engine/SingleTestExecutor.cs | 6 +++++- TUnit.TestProject/Tests.cs | 13 +++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 TUnit.Core/TestContext.cs diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs new file mode 100644 index 0000000000..ccdfcd222c --- /dev/null +++ b/TUnit.Core/TestContext.cs @@ -0,0 +1,14 @@ +using System.Collections.Concurrent; + +namespace TUnit.Core; + +public class TestContext +{ + private static readonly AsyncLocal _asyncLocal = new(); + private static readonly ConcurrentDictionary _contexts = new(); + public static TestDetails Current + { + get => _asyncLocal.Value!; + set => _asyncLocal.Value = value; + } +} \ No newline at end of file diff --git a/TUnit.Engine/MethodInvoker.cs b/TUnit.Engine/MethodInvoker.cs index d00ba85fea..c1ae76f7f1 100644 --- a/TUnit.Engine/MethodInvoker.cs +++ b/TUnit.Engine/MethodInvoker.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.ExceptionServices; +using TUnit.Core; namespace TUnit.Engine; @@ -10,7 +11,7 @@ public class MethodInvoker { try { - var result = await Task.Run(() => methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture)); + var result = methodInfo.Invoke(@class, bindingFlags, null, arguments, CultureInfo.InvariantCulture); if (result is ValueTask valueTask) { diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 4935689b80..030252ded2 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -42,7 +42,11 @@ public async Task ExecuteTest(TestDetails testDetails, Type[] a try { - await ExecuteCore(testDetails, allClasses); + await Task.Run(async () => + { + TestContext.Current = testDetails; + await ExecuteCore(testDetails, allClasses); + }); var end = DateTimeOffset.Now; diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index cc007929c6..581116611a 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,4 +1,5 @@ using TUnit.Assertions; +using TUnit.Core; using TUnit.Core.Attributes; namespace TUnit.TestProject; @@ -121,6 +122,18 @@ public async Task TestDataSource8(int value) await Task.Yield(); Assert.That(value, Is.EqualTo(1)); } + + [Test] + public void TestContext1() + { + Assert.That(TestContext.Current.TestName, Is.EqualTo(nameof(TestContext1))); + } + + [Test] + public void TestContext2() + { + Assert.That(TestContext.Current.TestName, Is.EqualTo(nameof(TestContext1))); + } public static int One() => 1; public static int Two() => 2; From e4d6c928c33f6da1107b886bb7a3efea89f7b3d5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:15:20 +0000 Subject: [PATCH 036/124] Throws conditions --- TUnit.Assertions/Assert.cs | 8 +++++ .../{ => AssertConditions}/AssertCondition.cs | 6 ++-- .../AssertConditions/AsyncAssertCondition.cs | 30 +++++++++++++++++++ .../AsynchronousDelegateAssertCondition.cs | 5 ++++ .../{ => AssertConditions/Conditions}/Is.cs | 0 .../Conditions}/Is_Numbers.cs | 0 .../Conditions}/Is_Strings.cs | 0 .../SynchronousDelegateAssertCondition.cs | 5 ++++ .../AssertConditions/Conditions/Throws.cs | 6 ++++ .../ThrowsNothingAssertCondition.cs | 22 ++++++++++++++ .../ThrowsNothingAsyncAssertCondition.cs | 22 ++++++++++++++ .../ExpectedValueAssertCondition.cs | 4 +-- TUnit.Assertions/IAssertCondition.cs | 8 ----- 13 files changed, 103 insertions(+), 13 deletions(-) rename TUnit.Assertions/{ => AssertConditions}/AssertCondition.cs (75%) create mode 100644 TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs rename TUnit.Assertions/{ => AssertConditions/Conditions}/Is.cs (100%) rename TUnit.Assertions/{ => AssertConditions/Conditions}/Is_Numbers.cs (100%) rename TUnit.Assertions/{ => AssertConditions/Conditions}/Is_Strings.cs (100%) create mode 100644 TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Conditions/Throws.cs create mode 100644 TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs rename TUnit.Assertions/{ => AssertConditions}/ExpectedValueAssertCondition.cs (75%) delete mode 100644 TUnit.Assertions/IAssertCondition.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index d5b2f6ffe7..783184cec9 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -11,4 +11,12 @@ public static void That(T value, AssertCondition assertCondition) throw new AssertionException(assertCondition.Message); } } + + public static async Task ThatAsync(T value, AsyncAssertCondition assertCondition) + { + if (!await assertCondition.Assert(value)) + { + throw new AssertionException(assertCondition.Message); + } + } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs similarity index 75% rename from TUnit.Assertions/AssertCondition.cs rename to TUnit.Assertions/AssertConditions/AssertCondition.cs index 7b32a954ab..0366f18b01 100644 --- a/TUnit.Assertions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions; -public abstract class AssertCondition : IAssertCondition +public abstract class AssertCondition { internal AssertCondition() { @@ -10,7 +10,7 @@ internal AssertCondition() protected T ActualValue { get; private set; } = default!; - public bool Assert(T actualValue) + public virtual bool Assert(T actualValue) { ActualValue = actualValue; return Passes(actualValue); @@ -22,7 +22,7 @@ public bool Assert(T actualValue) public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; - public IAssertCondition WithMessage(Func messageFactory) + public AssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; diff --git a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs new file mode 100644 index 0000000000..937de6de51 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs @@ -0,0 +1,30 @@ +namespace TUnit.Assertions; + +public abstract class AsyncAssertCondition +{ + internal AsyncAssertCondition() + { + } + + private Func? MessageFactory { get; set; } + + protected T ActualValue { get; private set; } = default!; + + public async Task Assert(T actualValue) + { + ActualValue = actualValue; + return await Passes(actualValue); + } + + public abstract string DefaultMessage { get; } + + protected abstract Task Passes(T actualValue); + + public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; + + public AsyncAssertCondition WithMessage(Func messageFactory) + { + MessageFactory = messageFactory; + return this; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs new file mode 100644 index 0000000000..82cf6d33dc --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs @@ -0,0 +1,5 @@ +namespace TUnit.Assertions; + +public abstract class AsynchronousDelegateAssertCondition : AsyncAssertCondition> +{ +} \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/AssertConditions/Conditions/Is.cs similarity index 100% rename from TUnit.Assertions/Is.cs rename to TUnit.Assertions/AssertConditions/Conditions/Is.cs diff --git a/TUnit.Assertions/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs similarity index 100% rename from TUnit.Assertions/Is_Numbers.cs rename to TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs diff --git a/TUnit.Assertions/Is_Strings.cs b/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs similarity index 100% rename from TUnit.Assertions/Is_Strings.cs rename to TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs diff --git a/TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs new file mode 100644 index 0000000000..deca5306bc --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs @@ -0,0 +1,5 @@ +namespace TUnit.Assertions; + +public abstract class SynchronousDelegateAssertCondition : AssertCondition +{ +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/Throws.cs b/TUnit.Assertions/AssertConditions/Conditions/Throws.cs new file mode 100644 index 0000000000..23befdb3f0 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Conditions/Throws.cs @@ -0,0 +1,6 @@ +namespace TUnit.Assertions; + +public static class Throws +{ + public static ThrowsNothingAssertCondition Nothing => new ThrowsNothingAssertCondition(); +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs new file mode 100644 index 0000000000..2c81c423e4 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs @@ -0,0 +1,22 @@ +namespace TUnit.Assertions; + +public class ThrowsNothingAssertCondition : SynchronousDelegateAssertCondition +{ + private Exception? _exception; + + public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; + + protected override bool Passes(Action actualValue) + { + try + { + actualValue(); + } + catch (Exception e) + { + _exception = e; + } + + return _exception == null; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs new file mode 100644 index 0000000000..e18007483c --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs @@ -0,0 +1,22 @@ +namespace TUnit.Assertions; + +public class ThrowsNothingAsyncAssertCondition : AsynchronousDelegateAssertCondition +{ + private Exception? _exception; + + public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; + + protected override async Task Passes(Func actualValue) + { + try + { + await actualValue(); + } + catch (Exception e) + { + _exception = e; + } + + return _exception == null; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/ExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs similarity index 75% rename from TUnit.Assertions/ExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs index bdff7ea179..8829a2103d 100644 --- a/TUnit.Assertions/ExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions; public abstract class ExpectedValueAssertCondition : AssertCondition { @@ -13,7 +13,7 @@ internal ExpectedValueAssertCondition(TExpected expected) private Func<(TExpected ExpectedValue, TActual ActualValue), string>? MessageFactory { get; set; } - public IAssertCondition WithMessage(Func<(TExpected ExpectedValue, TActual ActualValue), string> messageFactory) + public AssertCondition WithMessage(Func<(TExpected ExpectedValue, TActual ActualValue), string> messageFactory) { MessageFactory = messageFactory!; return this; diff --git a/TUnit.Assertions/IAssertCondition.cs b/TUnit.Assertions/IAssertCondition.cs deleted file mode 100644 index 1da65b17cf..0000000000 --- a/TUnit.Assertions/IAssertCondition.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace TUnit.Assertions; - -public interface IAssertCondition -{ - public bool Assert(T actualValue); - - internal string DefaultMessage { get; } -} \ No newline at end of file From 9f99080e3788c071afec1db43a0d31d5b3f26acd Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:21:14 +0000 Subject: [PATCH 037/124] Tweaks --- TUnit.Assertions/Assert.cs | 16 ++++++++++++++++ TUnit.TestProject/Tests.cs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 783184cec9..e01e8748c7 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -19,4 +19,20 @@ public static async Task ThatAsync(T value, AsyncAssertCondition assertCon throw new AssertionException(assertCondition.Message); } } + + public static void That(Action value, SynchronousDelegateAssertCondition assertCondition) + { + if (!assertCondition.Assert(value)) + { + throw new AssertionException(assertCondition.Message); + } + } + + public static async Task ThatAsync(Func value, AsynchronousDelegateAssertCondition assertCondition) + { + if (!await assertCondition.Assert(value)) + { + throw new AssertionException(assertCondition.Message); + } + } } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 581116611a..109457175c 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -135,6 +135,22 @@ public void TestContext2() Assert.That(TestContext.Current.TestName, Is.EqualTo(nameof(TestContext1))); } + [Test] + public void Throws1() + { + Assert.That(() => new string(Array.Empty()), Throws.Nothing); + } + + [Test] + public async Task Throws2() + { + await Assert.ThatAsync(async () => + { + await Task.Yield(); + new string(Array.Empty()); + }, Throws.Nothing); + } + public static int One() => 1; public static int Two() => 2; } \ No newline at end of file From adbf995cb39349f491fea64ddeb76ec0357dcb14 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:37:20 +0000 Subject: [PATCH 038/124] Throws assertions --- .../ZeroAssertionTests.cs | 2 + TUnit.Assertions/Assert.cs | 37 +++++++++-- .../AssertConditions/AssertCondition.cs | 2 +- .../AssertConditions/AsyncAssertCondition.cs | 2 +- .../AsynchronousDelegateAssertCondition.cs | 2 +- .../Conditions/DelegateAssertCondition.cs | 61 +++++++++++++++++++ .../AssertConditions/Conditions/Is.cs | 2 +- .../AssertConditions/Conditions/Is_Numbers.cs | 2 +- .../AssertConditions/Conditions/Is_Strings.cs | 2 +- .../SynchronousDelegateAssertCondition.cs | 5 -- .../AssertConditions/Conditions/Throws.cs | 4 +- .../ThrowsNothingAssertCondition.cs | 18 ++---- .../ThrowsNothingAsyncAssertCondition.cs | 2 +- .../Extensions/DelegateExtensions.cs | 54 ++++++++++++++++ TUnit.TestProject/Tests.cs | 17 ++++++ 15 files changed, 180 insertions(+), 32 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs create mode 100644 TUnit.Assertions/Extensions/DelegateExtensions.cs diff --git a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs index 61de8c1b42..e45700bdc1 100644 --- a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs @@ -1,3 +1,5 @@ +using Is = TUnit.Assertions.AssertConditions.Conditions.Is; + namespace TUnit.Assertions.UnitTests; diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index e01e8748c7..23980af17d 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,4 +1,7 @@ -using TUnit.Assertions.Exceptions; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Conditions; +using TUnit.Assertions.Exceptions; +using TUnit.Assertions.Extensions; namespace TUnit.Assertions; @@ -20,17 +23,41 @@ public static async Task ThatAsync(T value, AsyncAssertCondition assertCon } } - public static void That(Action value, SynchronousDelegateAssertCondition assertCondition) + public static void That(Action value, DelegateAssertCondition assertCondition) { - if (!assertCondition.Assert(value)) + var exception = value.InvokeAndGetException(); + + if (!assertCondition.Assert(exception)) { throw new AssertionException(assertCondition.Message); } } - public static async Task ThatAsync(Func value, AsynchronousDelegateAssertCondition assertCondition) + public static void That(Func value, DelegateAssertCondition assertCondition) { - if (!await assertCondition.Assert(value)) + var result = value.InvokeAndGetException(); + + if (!assertCondition.Assert(result.Item1, result.Item2)) + { + throw new AssertionException(assertCondition.Message); + } + } + + public static async Task ThatAsync(Func value, DelegateAssertCondition assertCondition) + { + var exception = await value.InvokeAndGetExceptionAsync(); + + if (!assertCondition.Assert(exception)) + { + throw new AssertionException(assertCondition.Message); + } + } + + public static async Task ThatAsync(Func> value, DelegateAssertCondition assertCondition) + { + var result = await value.InvokeAndGetExceptionAsync(); + + if (!assertCondition.Assert(result.Item1, result.Item2)) { throw new AssertionException(assertCondition.Message); } diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 0366f18b01..2a7b691114 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions; public abstract class AssertCondition { diff --git a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs index 937de6de51..2f08cfada8 100644 --- a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions; public abstract class AsyncAssertCondition { diff --git a/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs index 82cf6d33dc..f2d0b38c28 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; public abstract class AsynchronousDelegateAssertCondition : AsyncAssertCondition> { diff --git a/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs new file mode 100644 index 0000000000..69d1eb73ce --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs @@ -0,0 +1,61 @@ +namespace TUnit.Assertions.AssertConditions.Conditions; + +public abstract class DelegateAssertCondition +{ + internal DelegateAssertCondition() + { + } + + private Func? MessageFactory { get; set; } + + protected Exception? Exception { get; private set; } + protected T? ActualValue { get; private set; } = default!; + + public virtual bool Assert(T? actualValue, Exception? exception) + { + Exception = exception; + ActualValue = actualValue; + return Passes(actualValue, exception); + } + + public abstract string DefaultMessage { get; } + + protected abstract bool Passes(T? actualValue, Exception? exception); + + public string Message => MessageFactory?.Invoke(ActualValue, Exception) ?? DefaultMessage; + + public DelegateAssertCondition WithMessage(Func messageFactory) + { + MessageFactory = messageFactory; + return this; + } +} + +public abstract class DelegateAssertCondition +{ + internal DelegateAssertCondition() + { + } + + private Func? MessageFactory { get; set; } + + protected Exception? Exception { get; private set; } + + public virtual bool Assert(Exception? exception) + { + Exception = exception; + return Passes(exception); + } + + public abstract string DefaultMessage { get; } + + protected abstract bool Passes(Exception? exception); + + public string Message => MessageFactory?.Invoke(Exception) ?? DefaultMessage; + + public DelegateAssertCondition WithMessage(Func messageFactory) + { + MessageFactory = messageFactory; + return this; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/Is.cs b/TUnit.Assertions/AssertConditions/Conditions/Is.cs index 0a204f0845..fbe2814833 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Is.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/Is.cs @@ -1,6 +1,6 @@ using TUnit.Assertions.AssertConditions.Generic; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; public static partial class Is { diff --git a/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs index 41cdafa69b..df94f101a4 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs @@ -2,7 +2,7 @@ using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; public static partial class Is { diff --git a/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs b/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs index b929918a5e..10a4408bf2 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs @@ -1,6 +1,6 @@ using TUnit.Assertions.AssertConditions.String; -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; public static partial class Is { diff --git a/TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs deleted file mode 100644 index deca5306bc..0000000000 --- a/TUnit.Assertions/AssertConditions/Conditions/SynchronousDelegateAssertCondition.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TUnit.Assertions; - -public abstract class SynchronousDelegateAssertCondition : AssertCondition -{ -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/Throws.cs b/TUnit.Assertions/AssertConditions/Conditions/Throws.cs index 23befdb3f0..0f41eda3fc 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Throws.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/Throws.cs @@ -1,6 +1,6 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; public static class Throws { - public static ThrowsNothingAssertCondition Nothing => new ThrowsNothingAssertCondition(); + public static ThrowsNothingAssertCondition Nothing => new(); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs index 2c81c423e4..20adc12aa0 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs @@ -1,22 +1,14 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; -public class ThrowsNothingAssertCondition : SynchronousDelegateAssertCondition +public class ThrowsNothingAssertCondition : DelegateAssertCondition { private Exception? _exception; public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; - protected override bool Passes(Action actualValue) + protected override bool Passes(Exception? exception) { - try - { - actualValue(); - } - catch (Exception e) - { - _exception = e; - } - - return _exception == null; + _exception = exception; + return exception == null; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs index e18007483c..ee975b1788 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions.Conditions; public class ThrowsNothingAsyncAssertCondition : AsynchronousDelegateAssertCondition { diff --git a/TUnit.Assertions/Extensions/DelegateExtensions.cs b/TUnit.Assertions/Extensions/DelegateExtensions.cs new file mode 100644 index 0000000000..c0262c3e18 --- /dev/null +++ b/TUnit.Assertions/Extensions/DelegateExtensions.cs @@ -0,0 +1,54 @@ +namespace TUnit.Assertions.Extensions; + +internal static class DelegateExtensions +{ + public static Exception? InvokeAndGetException(this Action action) + { + try + { + action(); + return null; + } + catch (Exception e) + { + return e; + } + } + + public static async Task InvokeAndGetExceptionAsync(this Func action) + { + try + { + await action(); + return null; + } + catch (Exception e) + { + return e; + } + } + + public static async Task<(T?, Exception?)> InvokeAndGetExceptionAsync(this Func> action) + { + try + { + return (await action(), null); + } + catch (Exception e) + { + return (default, e); + } + } + + public static (T?, Exception?) InvokeAndGetException(this Func action) + { + try + { + return (action(), null); + } + catch (Exception e) + { + return (default, e); + } + } +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 109457175c..85fb90aa8c 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,4 +1,5 @@ using TUnit.Assertions; +using TUnit.Assertions.AssertConditions.Conditions; using TUnit.Core; using TUnit.Core.Attributes; @@ -151,6 +152,22 @@ await Assert.ThatAsync(async () => }, Throws.Nothing); } + [Test] + public void Throws3() + { + Assert.That(() => throw new ApplicationException(), Throws.Nothing); + } + + [Test] + public async Task Throws4() + { + await Assert.ThatAsync(async () => + { + await Task.Yield(); + throw new ApplicationException(); + }, Throws.Nothing); + } + public static int One() => 1; public static int Two() => 2; } \ No newline at end of file From b56e5342d6fd2151815934facf5a078bfce8ede2 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:01:56 +0000 Subject: [PATCH 039/124] TimeoutAttribute.cs --- TUnit.Core/Attributes/TimeoutAttribute.cs | 11 ++++++++ TUnit.Core/TestDetails.cs | 19 +++++++++++++ TUnit.Engine/SingleTestExecutor.cs | 34 +++++++++++++++++++++-- TUnit.TestProject/Tests.cs | 6 ++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 TUnit.Core/Attributes/TimeoutAttribute.cs diff --git a/TUnit.Core/Attributes/TimeoutAttribute.cs b/TUnit.Core/Attributes/TimeoutAttribute.cs new file mode 100644 index 0000000000..ce77a920f5 --- /dev/null +++ b/TUnit.Core/Attributes/TimeoutAttribute.cs @@ -0,0 +1,11 @@ +namespace TUnit.Core.Attributes; + +[AttributeUsage(AttributeTargets.Method)] +public class TimeoutAttribute : TUnitAttribute +{ + public TimeSpan Timeout { get; } + public TimeoutAttribute(int timeoutInMilliseconds) + { + Timeout = TimeSpan.FromMilliseconds(timeoutInMilliseconds); + } +} \ No newline at end of file diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 05772e7e3e..9ac33d0bf5 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -43,6 +43,8 @@ public TestDetails(MethodInfo MethodInfo, .FirstOrDefault(x => x.AttributeType == typeof(RepeatAttribute)) ?.ConstructorArguments.FirstOrDefault().Value as int? ?? 0; + Timeout = GetTimeout(methodAndClassAttributes); + FileName = SourceLocation.FileName; MinLineNumber = SourceLocation.MinLineNumber; MaxLineNumber = SourceLocation.MaxLineNumber; @@ -50,6 +52,20 @@ public TestDetails(MethodInfo MethodInfo, Id = GenerateGuid(); } + private static TimeSpan GetTimeout(CustomAttributeData[] methodAndClassAttributes) + { + var timeoutMilliseconds = methodAndClassAttributes + .FirstOrDefault(x => x.AttributeType == typeof(TimeoutAttribute)) + ?.ConstructorArguments.FirstOrDefault().Value as int?; + + if (timeoutMilliseconds is 0 or null) + { + return default; + } + + return TimeSpan.FromMilliseconds(timeoutMilliseconds.Value); + } + private Guid GenerateGuid() { var bytes = Encoding.UTF8.GetBytes(DisplayName + MinLineNumber + new string(FullyQualifiedName.Reverse().ToArray())); @@ -90,6 +106,9 @@ private string GetArgumentValues() public MethodInfo MethodInfo { get; } public Type ClassType { get; } public string? FileName { get; } + + public TimeSpan Timeout { get; } + public int MinLineNumber { get; } public int MaxLineNumber { get; } public Type[]? ParameterTypes { get; } diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 030252ded2..537efcf7fa 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -10,14 +10,17 @@ public class SingleTestExecutor private readonly MethodInvoker _methodInvoker; private readonly TestClassCreator _testClassCreator; private readonly Disposer _disposer; + private readonly CancellationTokenSource _cancellationTokenSource; public SingleTestExecutor(MethodInvoker methodInvoker, TestClassCreator testClassCreator, - Disposer disposer) + Disposer disposer, + CancellationTokenSource cancellationTokenSource) { _methodInvoker = methodInvoker; _testClassCreator = testClassCreator; _disposer = disposer; + _cancellationTokenSource = cancellationTokenSource; } private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); @@ -92,9 +95,16 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) try { await ExecuteSetUps(@class); + + var testLevelCancellationTokenSource = + CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); + + if(testDetails.Timeout != default) + { + testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); + } - await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, - testDetails.ArgumentValues?.ToArray()); + await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); await ExecuteTearDowns(@class); @@ -118,6 +128,24 @@ await _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.D } } + private async Task ExecuteTestMethodWithTimeout(TestDetails testDetails, object @class, + CancellationTokenSource cancellationTokenSource) + { + var methodResult = _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, + testDetails.ArgumentValues?.ToArray()); + + if (testDetails.Timeout == default) + { + await methodResult; + return; + } + + var timeoutTask = Task.Delay(testDetails.Timeout, cancellationTokenSource.Token) + .ContinueWith(t => throw new OperationCanceledException()); + + await await Task.WhenAny(timeoutTask, methodResult); + } + private async Task ExecuteSetUps(object @class) { await _oneTimeSetUpRegistry.GetOrAdd(@class.GetType().FullName!, _ => ExecuteOneTimeSetUps(@class)); diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 85fb90aa8c..b28f9e83ea 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -168,6 +168,12 @@ await Assert.ThatAsync(async () => }, Throws.Nothing); } + [Test, Timeout(500)] + public async Task Timeout1() + { + await Task.Delay(TimeSpan.FromSeconds(5)); + } + public static int One() => 1; public static int Two() => 2; } \ No newline at end of file From 97c9784e6e5a2e68f9465b4a89bf2086b27b0473 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:07:17 +0000 Subject: [PATCH 040/124] TimeoutException.cs --- TUnit.Core/Exceptions/TUnitException.cs | 22 ++++++++++++++++++++++ TUnit.Core/Exceptions/TimeoutException.cs | 16 ++++++++++++++++ TUnit.Engine/SingleTestExecutor.cs | 3 ++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 TUnit.Core/Exceptions/TUnitException.cs create mode 100644 TUnit.Core/Exceptions/TimeoutException.cs diff --git a/TUnit.Core/Exceptions/TUnitException.cs b/TUnit.Core/Exceptions/TUnitException.cs new file mode 100644 index 0000000000..e2d5084f7d --- /dev/null +++ b/TUnit.Core/Exceptions/TUnitException.cs @@ -0,0 +1,22 @@ +using System.Runtime.Serialization; + +namespace TUnit.Core.Exceptions; + +public class TUnitException : Exception +{ + public TUnitException() + { + } + + protected TUnitException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public TUnitException(string? message) : base(message) + { + } + + public TUnitException(string? message, Exception? innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/TUnit.Core/Exceptions/TimeoutException.cs b/TUnit.Core/Exceptions/TimeoutException.cs new file mode 100644 index 0000000000..4c52044206 --- /dev/null +++ b/TUnit.Core/Exceptions/TimeoutException.cs @@ -0,0 +1,16 @@ +using System.Runtime.Serialization; + +namespace TUnit.Core.Exceptions; + +public class TimeoutException : TUnitException +{ + public TimeoutException(TestDetails testDetails) : base(GetMessage(testDetails)) + { + + } + + private static string GetMessage(TestDetails testDetails) + { + return $"The test timed out after {testDetails.Timeout.Milliseconds} milliseconds"; + } +} \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 537efcf7fa..72b90eaa7a 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -2,6 +2,7 @@ using System.Reflection; using TUnit.Core; using TUnit.Core.Attributes; +using TimeoutException = TUnit.Core.Exceptions.TimeoutException; namespace TUnit.Engine; @@ -141,7 +142,7 @@ private async Task ExecuteTestMethodWithTimeout(TestDetails testDetails, object } var timeoutTask = Task.Delay(testDetails.Timeout, cancellationTokenSource.Token) - .ContinueWith(t => throw new OperationCanceledException()); + .ContinueWith(t => throw new TimeoutException(testDetails)); await await Task.WhenAny(timeoutTask, methodResult); } From 8a89f21e0c1c6cc2388f93af324cca8550bc3e1c Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:39:25 +0000 Subject: [PATCH 041/124] Tweak to constructing class --- TUnit.Engine/SingleTestExecutor.cs | 65 ++++++++++++++---------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 72b90eaa7a..a54007ed43 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -84,48 +84,45 @@ await Task.Run(async () => private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) { - var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); + var isRetry = testDetails.RetryCount > 0; + var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - try + for (var i = 0; i < executionCount + 1; i++) { - var isRetry = testDetails.RetryCount > 0; - var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - - for (var i = 0; i < executionCount + 1; i++) + var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); + + try { - try + await ExecuteSetUps(@class); + + var testLevelCancellationTokenSource = + CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); + + if (testDetails.Timeout != default) { - await ExecuteSetUps(@class); - - var testLevelCancellationTokenSource = - CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); - - if(testDetails.Timeout != default) - { - testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); - } - - await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); - - await ExecuteTearDowns(@class); - - if (isRetry) - { - break; - } + testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); } - catch + + await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); + + await ExecuteTearDowns(@class); + + if (isRetry) { - if (!isRetry || i == executionCount) - { - throw; - } + break; } } - } - finally - { - await _disposer.DisposeAsync(@class); + catch + { + if (!isRetry || i == executionCount) + { + throw; + } + } + finally + { + await _disposer.DisposeAsync(@class); + } } } From 144bac123aa1c165684dee21c7204cae349331b5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:43:30 +0000 Subject: [PATCH 042/124] InternalsVisibleTo --- TUnit.Core/Properties/AssemblyInfo.cs | 5 +++++ TUnit.Core/TestDetails.cs | 2 ++ TUnit.Engine/Properties/AssemblyInfo.cs | 5 +++++ TUnit.Engine/SingleTestExecutor.cs | 1 + TUnit.TestAdapter/Properties/AssemblyInfo.cs | 6 +++++- 5 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 TUnit.Core/Properties/AssemblyInfo.cs create mode 100644 TUnit.Engine/Properties/AssemblyInfo.cs diff --git a/TUnit.Core/Properties/AssemblyInfo.cs b/TUnit.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ebd2d1a856 --- /dev/null +++ b/TUnit.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TUnit.Core")] +[assembly: InternalsVisibleTo("TUnit.Engine")] +[assembly: InternalsVisibleTo("TUnit.TestAdapter")] \ No newline at end of file diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 9ac33d0bf5..1fc0ebdaf4 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -109,6 +109,8 @@ private string GetArgumentValues() public TimeSpan Timeout { get; } + public int Count { get; internal set; } + public int MinLineNumber { get; } public int MaxLineNumber { get; } public Type[]? ParameterTypes { get; } diff --git a/TUnit.Engine/Properties/AssemblyInfo.cs b/TUnit.Engine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ebd2d1a856 --- /dev/null +++ b/TUnit.Engine/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TUnit.Core")] +[assembly: InternalsVisibleTo("TUnit.Engine")] +[assembly: InternalsVisibleTo("TUnit.TestAdapter")] \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index a54007ed43..d57e087d1e 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -89,6 +89,7 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) for (var i = 0; i < executionCount + 1; i++) { + TestContext.Current.Count++; var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); try diff --git a/TUnit.TestAdapter/Properties/AssemblyInfo.cs b/TUnit.TestAdapter/Properties/AssemblyInfo.cs index c65238fb38..66b03104d8 100644 --- a/TUnit.TestAdapter/Properties/AssemblyInfo.cs +++ b/TUnit.TestAdapter/Properties/AssemblyInfo.cs @@ -1,2 +1,6 @@ - +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TUnit.Core")] +[assembly: InternalsVisibleTo("TUnit.Engine")] +[assembly: InternalsVisibleTo("TUnit.TestAdapter")] From 29dea9b1b98a327950d37cbbce7856cfc93b3936 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:44:11 +0000 Subject: [PATCH 043/124] CurrentExecutionCount --- TUnit.Core/TestDetails.cs | 2 +- TUnit.Engine/SingleTestExecutor.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 1fc0ebdaf4..7d717af00a 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -109,7 +109,7 @@ private string GetArgumentValues() public TimeSpan Timeout { get; } - public int Count { get; internal set; } + public int CurrentExecutionCount { get; internal set; } public int MinLineNumber { get; } public int MaxLineNumber { get; } diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index d57e087d1e..65f7cdd0b6 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -89,7 +89,8 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) for (var i = 0; i < executionCount + 1; i++) { - TestContext.Current.Count++; + TestContext.Current.CurrentExecutionCount++; + var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); try From b084cd23f109a6f51be5233c5bca338d36a834ba Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:50:46 +0000 Subject: [PATCH 044/124] Rename to Clean Ups --- ...arDownAttribute.cs => CleanUpAttribute.cs} | 2 +- ...ttribute.cs => OneTimeCleanUpAttribute.cs} | 2 +- TUnit.Engine/SingleTestExecutor.cs | 12 +++++----- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 22 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) rename TUnit.Core/Attributes/{TearDownAttribute.cs => CleanUpAttribute.cs} (67%) rename TUnit.Core/Attributes/{OneTimeTearDownAttribute.cs => OneTimeCleanUpAttribute.cs} (64%) diff --git a/TUnit.Core/Attributes/TearDownAttribute.cs b/TUnit.Core/Attributes/CleanUpAttribute.cs similarity index 67% rename from TUnit.Core/Attributes/TearDownAttribute.cs rename to TUnit.Core/Attributes/CleanUpAttribute.cs index 7032514a65..4db2f2456b 100644 --- a/TUnit.Core/Attributes/TearDownAttribute.cs +++ b/TUnit.Core/Attributes/CleanUpAttribute.cs @@ -1,4 +1,4 @@ namespace TUnit.Core.Attributes; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class TearDownAttribute : TUnitAttribute; \ No newline at end of file +public class CleanUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/OneTimeTearDownAttribute.cs b/TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs similarity index 64% rename from TUnit.Core/Attributes/OneTimeTearDownAttribute.cs rename to TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs index 9a828fc5ef..155d570b55 100644 --- a/TUnit.Core/Attributes/OneTimeTearDownAttribute.cs +++ b/TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs @@ -1,4 +1,4 @@ namespace TUnit.Core.Attributes; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class OneTimeTearDownAttribute : TUnitAttribute; \ No newline at end of file +public class OneTimeCleanUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 65f7cdd0b6..763ddb94e6 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -107,7 +107,7 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); - await ExecuteTearDowns(@class); + await ExecuteCleanUps(@class); if (isRetry) { @@ -161,20 +161,20 @@ private async Task ExecuteSetUps(object @class) } } - private async Task ExecuteTearDowns(object @class) + private async Task ExecuteCleanUps(object @class) { - var tearDownMethods = @class.GetType() + var cleanUpMethods = @class.GetType() .GetMethods() .Where(x => !x.IsStatic) - .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(TearDownAttribute))); + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(CleanUpAttribute))); var exceptions = new List(); - foreach (var tearDownMethod in tearDownMethods) + foreach (var cleanUpMethod in cleanUpMethods) { try { - await _methodInvoker.InvokeMethod(@class, tearDownMethod, BindingFlags.Default, null); + await _methodInvoker.InvokeMethod(@class, cleanUpMethod, BindingFlags.Default, null); } catch (Exception e) { diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index eb1214aa34..af35c3319e 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -21,7 +21,7 @@ CancellationTokenSource cancellationTokenSource { private bool _canRunAnotherTest = true; - private readonly ConcurrentDictionary _oneTimeTearDownRegistry = new(); + private readonly ConcurrentDictionary _oneTimeCleanUpRegistry = new(); private readonly List _setResultsTasks = []; public async Task RunInAsyncContext(AssembliesAnd assembliesAndTests) @@ -54,7 +54,7 @@ public async Task RunInAsyncContext(AssembliesAnd assembliesAn executingTests.Add(testWithResult); - SetupRunOneTimeTearDownForClass(testWithResult.Test.Details, allTestsOrderedByClass, executingTests); + SetupRunOneTimeCleanUpForClass(testWithResult.Test.Details, allTestsOrderedByClass, executingTests); executingTests.RemoveAll(x => x.Result.IsCompletedSuccessfully); @@ -80,7 +80,7 @@ public async Task RunInAsyncContext(AssembliesAnd assembliesAn executingTests.RemoveAll(x => x.Result.IsCompletedSuccessfully); await WhenAllSafely(executingTests.Select(x => x.Result), testExecutionRecorder); - await WhenAllSafely(_oneTimeTearDownRegistry.Values, testExecutionRecorder); + await WhenAllSafely(_oneTimeCleanUpRegistry.Values, testExecutionRecorder); await Task.WhenAll(_setResultsTasks); } @@ -96,7 +96,7 @@ private TestOutcome GetOutcome(Status resultStatus) }; } - private void SetupRunOneTimeTearDownForClass(TestDetails processingTestDetails, + private void SetupRunOneTimeCleanUpForClass(TestDetails processingTestDetails, IEnumerable allTestsOrderedByClass, IEnumerable executingTests) { @@ -115,8 +115,8 @@ private void SetupRunOneTimeTearDownForClass(TestDetails processingTestDetails, Task.WhenAll(executingTestsForThisClass).ContinueWith(x => { - _ = _oneTimeTearDownRegistry.GetOrAdd(processingTestDetails.FullyQualifiedClassName, - ExecuteOneTimeTearDowns(processingTestDetails)); + _ = _oneTimeCleanUpRegistry.GetOrAdd(processingTestDetails.FullyQualifiedClassName, + ExecuteOneTimeCleanUps(processingTestDetails)); return Task.CompletedTask; }); @@ -178,16 +178,16 @@ private async Task GetCpuUsageForProcess() return cpuUsageTotal * 100; } - private async Task ExecuteOneTimeTearDowns(TestDetails testDetails) + private async Task ExecuteOneTimeCleanUps(TestDetails testDetails) { - var oneTimeTearDownMethods = testDetails.MethodInfo.DeclaringType! + var oneTimeCleanUpMethods = testDetails.MethodInfo.DeclaringType! .GetMethods() .Where(x => x.IsStatic) - .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeTearDownAttribute))); + .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeCleanUpAttribute))); - foreach (var oneTimeTearDownMethod in oneTimeTearDownMethods) + foreach (var oneTimeCleanUpMethod in oneTimeCleanUpMethods) { - await methodInvoker.InvokeMethod(null, oneTimeTearDownMethod, BindingFlags.Static | BindingFlags.Public, null); + await methodInvoker.InvokeMethod(null, oneTimeCleanUpMethod, BindingFlags.Static | BindingFlags.Public, null); } } From 30cbf09b02d131dac4d29cde36501122a3424d42 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 28 Jan 2024 14:12:17 +0000 Subject: [PATCH 045/124] HasCountAssertCondition --- .../Collections/HasCountAssertCondition.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs new file mode 100644 index 0000000000..63a331ca68 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -0,0 +1,32 @@ +using System.Collections; + +namespace TUnit.Assertions.AssertConditions.Collections; + +public class HasCountAssertCondition : ExpectedValueAssertCondition, int> +{ + public HasCountAssertCondition(int expected) : base(expected) + { + } + + public override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; + + protected override bool Passes(IEnumerable actualValue) + { + return GetCount(actualValue) == ExpectedValue; + } + + private int GetCount(IEnumerable actualValue) + { + if (actualValue is ICollection collection) + { + return collection.Count; + } + + if (actualValue is TActual[] array) + { + return array.Length; + } + + return actualValue.Count(); + } +} \ No newline at end of file From cc258da10c525716d67cef40c32dd8f675e8404d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:35:28 +0000 Subject: [PATCH 046/124] Rework assertion generics --- .../ZeroAssertionTests.cs | 2 +- TUnit.Assertions/Assert.cs | 29 ++----- TUnit.Assertions/AssertConditions/And.cs | 21 +++++ .../AssertConditions/AssertCondition.cs | 7 +- TUnit.Assertions/AssertConditions/AsyncAnd.cs | 21 +++++ .../AssertConditions/AsyncAssertCondition.cs | 81 +++++++++++++++++-- TUnit.Assertions/AssertConditions/AsyncOr.cs | 21 +++++ .../AssertConditions/Collections/Count.cs | 6 ++ .../ConditionEntries/Instance/HasInstance.cs | 20 +++++ .../ConditionEntries/Static/Has.cs | 6 ++ .../Static}/Is.cs | 4 +- .../Static}/Is_Numbers.cs | 2 +- .../Static}/Is_Strings.cs | 2 +- .../ConditionEntries/Static/Throws.cs | 9 +++ .../AsynchronousDelegateAssertCondition.cs | 5 -- .../Conditions/DelegateAssertCondition.cs | 29 +++++-- .../AssertConditions/Conditions/Throws.cs | 6 -- .../ThrowsNothingAsyncAssertCondition.cs | 19 +---- .../ExpectedValueAssertCondition.cs | 5 ++ TUnit.Assertions/AssertConditions/Or.cs | 21 +++++ TUnit.TestProject/Tests.cs | 9 ++- 21 files changed, 252 insertions(+), 73 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/And.cs create mode 100644 TUnit.Assertions/AssertConditions/AsyncAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/AsyncOr.cs create mode 100644 TUnit.Assertions/AssertConditions/Collections/Count.cs create mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs create mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs rename TUnit.Assertions/AssertConditions/{Conditions => ConditionEntries/Static}/Is.cs (78%) rename TUnit.Assertions/AssertConditions/{Conditions => ConditionEntries/Static}/Is_Numbers.cs (94%) rename TUnit.Assertions/AssertConditions/{Conditions => ConditionEntries/Static}/Is_Strings.cs (86%) create mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs delete mode 100644 TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Conditions/Throws.cs create mode 100644 TUnit.Assertions/AssertConditions/Or.cs diff --git a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs index e45700bdc1..d44ea51915 100644 --- a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs @@ -1,4 +1,4 @@ -using Is = TUnit.Assertions.AssertConditions.Conditions.Is; +using Is = TUnit.Assertions.AssertConditions.ConditionEntries.Static.Is; namespace TUnit.Assertions.UnitTests; diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 23980af17d..71facd800c 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,7 +1,6 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Conditions; using TUnit.Assertions.Exceptions; -using TUnit.Assertions.Extensions; namespace TUnit.Assertions; @@ -15,19 +14,9 @@ public static void That(T value, AssertCondition assertCondition) } } - public static async Task ThatAsync(T value, AsyncAssertCondition assertCondition) - { - if (!await assertCondition.Assert(value)) - { - throw new AssertionException(assertCondition.Message); - } - } - public static void That(Action value, DelegateAssertCondition assertCondition) { - var exception = value.InvokeAndGetException(); - - if (!assertCondition.Assert(exception)) + if (!assertCondition.Assert(value)) { throw new AssertionException(assertCondition.Message); } @@ -35,29 +24,23 @@ public static void That(Action value, DelegateAssertCondition assertCondition) public static void That(Func value, DelegateAssertCondition assertCondition) { - var result = value.InvokeAndGetException(); - - if (!assertCondition.Assert(result.Item1, result.Item2)) + if (!assertCondition.Assert(value)) { throw new AssertionException(assertCondition.Message); } } - public static async Task ThatAsync(Func value, DelegateAssertCondition assertCondition) + public static async Task That(Func value, AsyncAssertCondition assertCondition) { - var exception = await value.InvokeAndGetExceptionAsync(); - - if (!assertCondition.Assert(exception)) + if (!await assertCondition.Assert(value)) { throw new AssertionException(assertCondition.Message); } } - public static async Task ThatAsync(Func> value, DelegateAssertCondition assertCondition) + public static async Task That(Func> value, AsyncAssertCondition assertCondition) { - var result = await value.InvokeAndGetExceptionAsync(); - - if (!assertCondition.Assert(result.Item1, result.Item2)) + if (!await assertCondition.Assert(value)) { throw new AssertionException(assertCondition.Message); } diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/And.cs new file mode 100644 index 0000000000..e56edf98be --- /dev/null +++ b/TUnit.Assertions/AssertConditions/And.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions; + +public class And +{ + private readonly IReadOnlyCollection> _assertConditions; + + public And(IReadOnlyCollection> assertConditions) + { + _assertConditions = assertConditions; + } +} + +public class And +{ + private readonly IReadOnlyCollection> _assertConditions; + + public And(IReadOnlyCollection> assertConditions) + { + _assertConditions = assertConditions; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 2a7b691114..dcf41ad381 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -4,13 +4,15 @@ public abstract class AssertCondition { internal AssertCondition() { + And = new And([this]); + Or = new Or([this]); } private Func? MessageFactory { get; set; } protected T ActualValue { get; private set; } = default!; - public virtual bool Assert(T actualValue) + public bool Assert(T actualValue) { ActualValue = actualValue; return Passes(actualValue); @@ -27,4 +29,7 @@ public AssertCondition WithMessage(Func messageFactory) MessageFactory = messageFactory; return this; } + + public And And { get; } + public Or Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AsyncAnd.cs b/TUnit.Assertions/AssertConditions/AsyncAnd.cs new file mode 100644 index 0000000000..1534ca7316 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/AsyncAnd.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions; + +public class AsyncAnd +{ + private readonly AsyncAssertCondition _assertCondition; + + public AsyncAnd(AsyncAssertCondition assertCondition) + { + _assertCondition = assertCondition; + } +} + +public class AsyncAnd +{ + private readonly AsyncAssertCondition _assertCondition; + + public AsyncAnd(AsyncAssertCondition assertCondition) + { + _assertCondition = assertCondition; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs index 2f08cfada8..0a49ffa963 100644 --- a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs @@ -1,30 +1,97 @@ namespace TUnit.Assertions.AssertConditions; +public abstract class AsyncAssertCondition +{ + internal AsyncAssertCondition() + { + And = new AsyncAnd(this); + Or = new AsyncOr(this); + } + + private Func? MessageFactory { get; set; } + + protected Exception? Exception { get; private set; } + + public async Task Assert(Func @delegate) + { + try + { + await @delegate(); + } + catch (Exception e) + { + Exception = e; + } + + return Passes(Exception); + } + + internal void SetActual(Exception? exception) + { + Exception = exception; + } + + public abstract string DefaultMessage { get; } + + protected abstract bool Passes(Exception? exception); + + public string Message => MessageFactory?.Invoke(Exception) ?? DefaultMessage; + + public AsyncAssertCondition WithMessage(Func messageFactory) + { + MessageFactory = messageFactory; + return this; + } + + public AsyncAnd And { get; } + public AsyncOr Or { get; } +} + public abstract class AsyncAssertCondition { internal AsyncAssertCondition() { + And = new AsyncAnd(this); + Or = new AsyncOr(this); } - private Func? MessageFactory { get; set; } + private Func? MessageFactory { get; set; } + + protected T? ActualValue { get; private set; } + protected Exception? Exception { get; private set; } - protected T ActualValue { get; private set; } = default!; + public async Task Assert(Func> @delegate) + { + try + { + ActualValue = await @delegate(); + } + catch (Exception e) + { + Exception = e; + } + + return Passes(ActualValue, Exception); + } - public async Task Assert(T actualValue) + internal void SetActual(T? actualValue, Exception? exception) { ActualValue = actualValue; - return await Passes(actualValue); + Exception = exception; } public abstract string DefaultMessage { get; } - protected abstract Task Passes(T actualValue); + protected abstract bool Passes(T? actualValue, Exception? exception); - public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; + public string Message => MessageFactory?.Invoke(ActualValue, Exception) ?? DefaultMessage; - public AsyncAssertCondition WithMessage(Func messageFactory) + public AsyncAssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; } + + public AsyncAnd And { get; } + public AsyncOr Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AsyncOr.cs b/TUnit.Assertions/AssertConditions/AsyncOr.cs new file mode 100644 index 0000000000..7e9fb1f84c --- /dev/null +++ b/TUnit.Assertions/AssertConditions/AsyncOr.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions; + +public class AsyncOr +{ + private readonly AsyncAssertCondition _assertCondition; + + public AsyncOr(AsyncAssertCondition assertCondition) + { + _assertCondition = assertCondition; + } +} + +public class AsyncOr +{ + private readonly AsyncAssertCondition _assertCondition; + + public AsyncOr(AsyncAssertCondition assertCondition) + { + _assertCondition = assertCondition; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Collections/Count.cs b/TUnit.Assertions/AssertConditions/Collections/Count.cs new file mode 100644 index 0000000000..d904ed6c2d --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Collections/Count.cs @@ -0,0 +1,6 @@ +namespace TUnit.Assertions.AssertConditions.Collections; + +public class Count +{ + +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs new file mode 100644 index 0000000000..609e3742aa --- /dev/null +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs @@ -0,0 +1,20 @@ +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Instance; + +public class HasInstance +{ + private readonly IReadOnlyCollection> _assertConditions; + + public HasInstance(AssertCondition assertConditions) : this([assertConditions]) + { + } + + public HasInstance(IReadOnlyCollection> assertConditions) + { + _assertConditions = assertConditions; + And = new And(assertConditions); + Or = new Or(assertConditions); + } + + public And And { get; } + public Or Or { get; } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs new file mode 100644 index 0000000000..208693025d --- /dev/null +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs @@ -0,0 +1,6 @@ +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; + +public static class Has +{ + +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/Is.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs similarity index 78% rename from TUnit.Assertions/AssertConditions/Conditions/Is.cs rename to TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs index fbe2814833..979380c06e 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Is.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs @@ -1,8 +1,8 @@ using TUnit.Assertions.AssertConditions.Generic; -namespace TUnit.Assertions.AssertConditions.Conditions; +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; -public static partial class Is +public partial class Is { public static AssertCondition EqualTo(T expected) { diff --git a/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs similarity index 94% rename from TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs rename to TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs index df94f101a4..97d131f75c 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs @@ -2,7 +2,7 @@ using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; -namespace TUnit.Assertions.AssertConditions.Conditions; +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { diff --git a/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs similarity index 86% rename from TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs rename to TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index 10a4408bf2..760143af89 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -1,6 +1,6 @@ using TUnit.Assertions.AssertConditions.String; -namespace TUnit.Assertions.AssertConditions.Conditions; +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs new file mode 100644 index 0000000000..dc6046bae7 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs @@ -0,0 +1,9 @@ +using TUnit.Assertions.AssertConditions.Conditions; + +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; + +public static class Throws +{ + public static ThrowsNothingAssertCondition Nothing => new(); + public static ThrowsNothingAsyncAssertCondition NothingAsync => new(); +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs deleted file mode 100644 index f2d0b38c28..0000000000 --- a/TUnit.Assertions/AssertConditions/Conditions/AsynchronousDelegateAssertCondition.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.Conditions; - -public abstract class AsynchronousDelegateAssertCondition : AsyncAssertCondition> -{ -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs index 69d1eb73ce..b62d79ccc6 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs @@ -11,11 +11,18 @@ internal DelegateAssertCondition() protected Exception? Exception { get; private set; } protected T? ActualValue { get; private set; } = default!; - public virtual bool Assert(T? actualValue, Exception? exception) + public bool Assert(Func action) { - Exception = exception; - ActualValue = actualValue; - return Passes(actualValue, exception); + try + { + ActualValue = action(); + } + catch (Exception e) + { + Exception = e; + } + + return Passes(ActualValue, Exception); } public abstract string DefaultMessage { get; } @@ -41,10 +48,18 @@ internal DelegateAssertCondition() protected Exception? Exception { get; private set; } - public virtual bool Assert(Exception? exception) + public bool Assert(Action action) { - Exception = exception; - return Passes(exception); + try + { + action(); + } + catch (Exception e) + { + Exception = e; + } + + return Passes(Exception); } public abstract string DefaultMessage { get; } diff --git a/TUnit.Assertions/AssertConditions/Conditions/Throws.cs b/TUnit.Assertions/AssertConditions/Conditions/Throws.cs deleted file mode 100644 index 0f41eda3fc..0000000000 --- a/TUnit.Assertions/AssertConditions/Conditions/Throws.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.Conditions; - -public static class Throws -{ - public static ThrowsNothingAssertCondition Nothing => new(); -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs index ee975b1788..9f099382de 100644 --- a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs @@ -1,22 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Conditions; -public class ThrowsNothingAsyncAssertCondition : AsynchronousDelegateAssertCondition +public class ThrowsNothingAsyncAssertCondition : AsyncAssertCondition { - private Exception? _exception; + public override string DefaultMessage => $"A {Exception?.GetType().Name} was thrown"; - public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; - - protected override async Task Passes(Func actualValue) + protected override bool Passes(Exception? exception) { - try - { - await actualValue(); - } - catch (Exception e) - { - _exception = e; - } - - return _exception == null; + return Exception == null; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs index 8829a2103d..5ed83a76e0 100644 --- a/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs @@ -7,6 +7,8 @@ public abstract class ExpectedValueAssertCondition : AssertC internal ExpectedValueAssertCondition(TExpected expected) { ExpectedValue = expected; + And = new And([this]); + Or = new Or([this]); } public new string Message => MessageFactory?.Invoke((ExpectedValue, ActualValue)) ?? DefaultMessage; @@ -18,4 +20,7 @@ public AssertCondition WithMessage(Func<(TExpected ExpectedValue, TActu MessageFactory = messageFactory!; return this; } + + public new And And { get; } + public new Or Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Or.cs new file mode 100644 index 0000000000..4e55da107c --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Or.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions; + +public class Or +{ + private readonly IReadOnlyCollection> _assertConditions; + + public Or(IReadOnlyCollection> assertConditions) + { + _assertConditions = assertConditions; + } +} + +public class Or +{ + private readonly IReadOnlyCollection> _assertConditions; + + public Or(IReadOnlyCollection> assertConditions) + { + _assertConditions = assertConditions; + } +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index b28f9e83ea..2ee3d2ed12 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,4 +1,5 @@ using TUnit.Assertions; +using TUnit.Assertions.AssertConditions.ConditionEntries.Static; using TUnit.Assertions.AssertConditions.Conditions; using TUnit.Core; using TUnit.Core.Attributes; @@ -145,11 +146,11 @@ public void Throws1() [Test] public async Task Throws2() { - await Assert.ThatAsync(async () => + await Assert.That(async () => { await Task.Yield(); new string(Array.Empty()); - }, Throws.Nothing); + }, Throws.NothingAsync); } [Test] @@ -161,11 +162,11 @@ public void Throws3() [Test] public async Task Throws4() { - await Assert.ThatAsync(async () => + await Assert.That(async () => { await Task.Yield(); throw new ApplicationException(); - }, Throws.Nothing); + }, Throws.NothingAsync); } [Test, Timeout(500)] From c7541c510d0b6a0b65fa10fd4a864b468854c3a9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:59:22 +0000 Subject: [PATCH 047/124] Rework --- TUnit.Assertions/AssertConditions/And.cs | 13 ++++++++++--- .../AssertConditions/AssertCondition.cs | 11 ++++++++--- .../ConditionEntries/Static/Is_Strings.cs | 10 ++++++++++ .../ExpectedValueAssertCondition.cs | 11 +++++++---- .../Generic/EqualsAssertCondition.cs | 8 +++++--- .../String/StringEqualsAssertCondition.cs | 6 +++++- .../DelegateAssertCondition.cs | 0 .../ThrowsNothingAssertCondition.cs | 0 .../ThrowsNothingAsyncAssertCondition.cs | 0 TUnit.TestProject/Tests.cs | 8 ++++++++ 10 files changed, 53 insertions(+), 14 deletions(-) rename TUnit.Assertions/AssertConditions/{Conditions => Throws}/DelegateAssertCondition.cs (100%) rename TUnit.Assertions/AssertConditions/{Conditions => Throws}/ThrowsNothingAssertCondition.cs (100%) rename TUnit.Assertions/AssertConditions/{Conditions => Throws}/ThrowsNothingAsyncAssertCondition.cs (100%) diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/And.cs index e56edf98be..a66af1a564 100644 --- a/TUnit.Assertions/AssertConditions/And.cs +++ b/TUnit.Assertions/AssertConditions/And.cs @@ -1,13 +1,20 @@ +using TUnit.Assertions.AssertConditions.ConditionEntries.Static; + namespace TUnit.Assertions.AssertConditions; -public class And +public class And { - private readonly IReadOnlyCollection> _assertConditions; + private readonly IReadOnlyCollection> _assertConditions; - public And(IReadOnlyCollection> assertConditions) + public And(IReadOnlyCollection> assertConditions) { _assertConditions = assertConditions; } + + public AssertCondition EqualTo(TExpected expected) + { + return Is.EqualTo(_assertConditions, expected); + } } public class And diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index dcf41ad381..01ec7f1c92 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -2,10 +2,15 @@ public abstract class AssertCondition { - internal AssertCondition() + internal readonly IReadOnlyCollection> PreviousAssertConditions; + + internal AssertCondition(IReadOnlyCollection> previousAssertConditions) { - And = new And([this]); - Or = new Or([this]); + IReadOnlyCollection> conditionsUntilNow = [..previousAssertConditions, this]; + PreviousAssertConditions = conditionsUntilNow; + + And = new And(conditionsUntilNow); + Or = new Or(conditionsUntilNow); } private Func? MessageFactory { get; set; } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index 760143af89..b5f247bc14 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -9,8 +9,18 @@ public static AssertCondition EqualTo(string expected) return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); } + public static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected) + { + return new StringEqualsAssertCondition(previousConditions, expected, StringComparison.Ordinal); + } + public static AssertCondition EqualTo(string expected, StringComparison stringComparison) { return new StringEqualsAssertCondition(expected, stringComparison); } + + public static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) + { + return new StringEqualsAssertCondition(previousConditions, expected, stringComparison); + } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs index 5ed83a76e0..abd977c951 100644 --- a/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs @@ -2,13 +2,16 @@ public abstract class ExpectedValueAssertCondition : AssertCondition { + internal readonly IReadOnlyCollection> PreviousConditions; internal TExpected ExpectedValue { get; } - - internal ExpectedValueAssertCondition(TExpected expected) + + internal ExpectedValueAssertCondition(IReadOnlyCollection> previousConditions, TExpected expected) : base(previousConditions) { + IReadOnlyCollection> conditionsUntilNow = [..previousConditions, this]; + PreviousConditions = conditionsUntilNow; ExpectedValue = expected; - And = new And([this]); - Or = new Or([this]); + And = new And(conditionsUntilNow); + Or = new Or(conditionsUntilNow); } public new string Message => MessageFactory?.Invoke((ExpectedValue, ActualValue)) ?? DefaultMessage; diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 1f4efbb591..5cead5ddd5 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,9 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class EqualsAssertCondition : ExpectedValueAssertCondition +public class EqualsAssertCondition( + IReadOnlyCollection> previousConditions, + TExpected expected) + : ExpectedValueAssertCondition(previousConditions, expected) { - - public EqualsAssertCondition(TExpected expected) : base(expected) + public EqualsAssertCondition(TExpected expected) : this(Array.Empty>(), expected) { } diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 887266c104..f51fc5d4ef 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -4,7 +4,11 @@ public class StringEqualsAssertCondition : ExpectedValueAssertCondition>(), expected, stringComparison) + { + } + + public StringEqualsAssertCondition(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) : base(previousConditions, expected) { _stringComparison = stringComparison; } diff --git a/TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs similarity index 100% rename from TUnit.Assertions/AssertConditions/Conditions/DelegateAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs similarity index 100% rename from TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAsyncAssertCondition.cs similarity index 100% rename from TUnit.Assertions/AssertConditions/Conditions/ThrowsNothingAsyncAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAsyncAssertCondition.cs diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 2ee3d2ed12..400455c14e 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -174,6 +174,14 @@ public async Task Timeout1() { await Task.Delay(TimeSpan.FromSeconds(5)); } + + [Test] + public void String_And_Condition() + { + var str = "My string"; + + Assert.That(str, Is.EqualTo("My string").And.EqualTo("This")); + } public static int One() => 1; public static int Two() => 2; From 0b389c17b52fec8175175b4f8ffc3286b42fea22 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:52:50 +0000 Subject: [PATCH 048/124] Rework again --- TUnit.Assertions/Assert.cs | 10 +- TUnit.Assertions/AssertConditions/AsyncAnd.cs | 21 ---- .../AssertConditions/AsyncAssertCondition.cs | 97 ------------------- TUnit.Assertions/AssertConditions/AsyncOr.cs | 21 ---- .../Collections/HasCountAssertCondition.cs | 2 +- .../ConditionEntries/Instance/HasInstance.cs | 5 +- .../ConditionEntries/Static/Is.cs | 14 ++- .../ConditionEntries/Static/Is_Numbers.cs | 4 +- .../ConditionEntries/Static/Is_Strings.cs | 4 +- .../ConditionEntries/Static/Throws.cs | 2 +- .../Generic/EqualsAssertCondition.cs | 4 - .../Generic/SameReferenceAssertCondition.cs | 2 +- TUnit.Assertions/AssertConditions/Or.cs | 6 +- .../String/StringEqualsAssertCondition.cs | 2 +- .../Throws/DelegateAssertCondition.cs | 29 ++---- .../Throws/ThrowsNothingAssertCondition.cs | 2 +- .../ThrowsNothingAsyncAssertCondition.cs | 11 --- TUnit.Assertions/DelegateInvocationResult.cs | 7 ++ .../Extensions/DelegateExtensions.cs | 4 +- TUnit.TestProject/Tests.cs | 1 - 20 files changed, 49 insertions(+), 199 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/AsyncAnd.cs delete mode 100644 TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/AsyncOr.cs delete mode 100644 TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAsyncAssertCondition.cs create mode 100644 TUnit.Assertions/DelegateInvocationResult.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 71facd800c..b54ac45e60 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,6 +1,7 @@ using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Conditions; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Exceptions; +using TUnit.Assertions.Extensions; namespace TUnit.Assertions; @@ -30,7 +31,7 @@ public static void That(Func value, DelegateAssertCondition assertCondi } } - public static async Task That(Func value, AsyncAssertCondition assertCondition) + public static async Task That(Func value, AssertCondition assertCondition) { if (!await assertCondition.Assert(value)) { @@ -38,9 +39,10 @@ public static async Task That(Func value, AsyncAssertCondition assertCondi } } - public static async Task That(Func> value, AsyncAssertCondition assertCondition) + public static async Task That(Func> value, DelegateAssertCondition assertCondition) { - if (!await assertCondition.Assert(value)) + var invocationResult = await value.InvokeAndGetExceptionAsync(); + if (!assertCondition.Assert(invocationResult.Result, invocationResult.Exception)) { throw new AssertionException(assertCondition.Message); } diff --git a/TUnit.Assertions/AssertConditions/AsyncAnd.cs b/TUnit.Assertions/AssertConditions/AsyncAnd.cs deleted file mode 100644 index 1534ca7316..0000000000 --- a/TUnit.Assertions/AssertConditions/AsyncAnd.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace TUnit.Assertions.AssertConditions; - -public class AsyncAnd -{ - private readonly AsyncAssertCondition _assertCondition; - - public AsyncAnd(AsyncAssertCondition assertCondition) - { - _assertCondition = assertCondition; - } -} - -public class AsyncAnd -{ - private readonly AsyncAssertCondition _assertCondition; - - public AsyncAnd(AsyncAssertCondition assertCondition) - { - _assertCondition = assertCondition; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs deleted file mode 100644 index 0a49ffa963..0000000000 --- a/TUnit.Assertions/AssertConditions/AsyncAssertCondition.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace TUnit.Assertions.AssertConditions; - -public abstract class AsyncAssertCondition -{ - internal AsyncAssertCondition() - { - And = new AsyncAnd(this); - Or = new AsyncOr(this); - } - - private Func? MessageFactory { get; set; } - - protected Exception? Exception { get; private set; } - - public async Task Assert(Func @delegate) - { - try - { - await @delegate(); - } - catch (Exception e) - { - Exception = e; - } - - return Passes(Exception); - } - - internal void SetActual(Exception? exception) - { - Exception = exception; - } - - public abstract string DefaultMessage { get; } - - protected abstract bool Passes(Exception? exception); - - public string Message => MessageFactory?.Invoke(Exception) ?? DefaultMessage; - - public AsyncAssertCondition WithMessage(Func messageFactory) - { - MessageFactory = messageFactory; - return this; - } - - public AsyncAnd And { get; } - public AsyncOr Or { get; } -} - -public abstract class AsyncAssertCondition -{ - internal AsyncAssertCondition() - { - And = new AsyncAnd(this); - Or = new AsyncOr(this); - } - - private Func? MessageFactory { get; set; } - - protected T? ActualValue { get; private set; } - protected Exception? Exception { get; private set; } - - public async Task Assert(Func> @delegate) - { - try - { - ActualValue = await @delegate(); - } - catch (Exception e) - { - Exception = e; - } - - return Passes(ActualValue, Exception); - } - - internal void SetActual(T? actualValue, Exception? exception) - { - ActualValue = actualValue; - Exception = exception; - } - - public abstract string DefaultMessage { get; } - - protected abstract bool Passes(T? actualValue, Exception? exception); - - public string Message => MessageFactory?.Invoke(ActualValue, Exception) ?? DefaultMessage; - - public AsyncAssertCondition WithMessage(Func messageFactory) - { - MessageFactory = messageFactory; - return this; - } - - public AsyncAnd And { get; } - public AsyncOr Or { get; } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AsyncOr.cs b/TUnit.Assertions/AssertConditions/AsyncOr.cs deleted file mode 100644 index 7e9fb1f84c..0000000000 --- a/TUnit.Assertions/AssertConditions/AsyncOr.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace TUnit.Assertions.AssertConditions; - -public class AsyncOr -{ - private readonly AsyncAssertCondition _assertCondition; - - public AsyncOr(AsyncAssertCondition assertCondition) - { - _assertCondition = assertCondition; - } -} - -public class AsyncOr -{ - private readonly AsyncAssertCondition _assertCondition; - - public AsyncOr(AsyncAssertCondition assertCondition) - { - _assertCondition = assertCondition; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index 63a331ca68..694d492a68 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions.AssertConditions.Collections; public class HasCountAssertCondition : ExpectedValueAssertCondition, int> { - public HasCountAssertCondition(int expected) : base(expected) + public HasCountAssertCondition(AsyncAssertCondition>? IReadOnlyCollection, int>> previousConditions, int expected) : base(previousConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs index 609e3742aa..2403ce2ebf 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs @@ -2,11 +2,8 @@ public class HasInstance { + private readonly AsyncAssertCondition? _asyncAssertCondition; private readonly IReadOnlyCollection> _assertConditions; - - public HasInstance(AssertCondition assertConditions) : this([assertConditions]) - { - } public HasInstance(IReadOnlyCollection> assertConditions) { diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs index 979380c06e..5f108347ba 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs @@ -6,11 +6,21 @@ public partial class Is { public static AssertCondition EqualTo(T expected) { - return new EqualsAssertCondition(expected); + return new EqualsAssertCondition([], expected); } + internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, T expected) + { + return new EqualsAssertCondition(previousConditions, expected); + } + public static AssertCondition SameReference(T expected) { - return new SameReferenceAssertCondition(expected); + return new SameReferenceAssertCondition([], expected); + } + + internal static AssertCondition SameReference(IReadOnlyCollection> previousConditions, T expected) + { + return new SameReferenceAssertCondition(previousConditions, expected); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs index 97d131f75c..71ade23877 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs @@ -6,7 +6,9 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { - public static AssertCondition Zero => new EqualsAssertCondition(0); + public static AssertCondition Zero => new EqualsAssertCondition([], 0); + internal static AssertCondition ZeroInternal(IReadOnlyCollection> previousConditions) + => new EqualsAssertCondition(previousConditions, 0); public static AssertCondition GreaterThan(T expected) where T : INumber { diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index b5f247bc14..76efbe35e4 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -9,7 +9,7 @@ public static AssertCondition EqualTo(string expected) return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); } - public static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected) + internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected) { return new StringEqualsAssertCondition(previousConditions, expected, StringComparison.Ordinal); } @@ -19,7 +19,7 @@ public static AssertCondition EqualTo(string expected, StringComparison return new StringEqualsAssertCondition(expected, stringComparison); } - public static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) + internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) { return new StringEqualsAssertCondition(previousConditions, expected, stringComparison); } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs index dc6046bae7..a94bbf4982 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs @@ -1,4 +1,4 @@ -using TUnit.Assertions.AssertConditions.Conditions; +using TUnit.Assertions.AssertConditions.Throws; namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 5cead5ddd5..ceb4e97b33 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -5,10 +5,6 @@ public class EqualsAssertCondition( TExpected expected) : ExpectedValueAssertCondition(previousConditions, expected) { - public EqualsAssertCondition(TExpected expected) : this(Array.Empty>(), expected) - { - } - public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; protected override bool Passes(TActual actualValue) diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index 937a072b15..b9892cbf23 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -3,7 +3,7 @@ public class SameReferenceAssertCondition : ExpectedValueAssertCondition { - public SameReferenceAssertCondition(TExpected expected) : base(expected) + public SameReferenceAssertCondition(IReadOnlyCollection> previousConditions, TExpected expected) : base(previousConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Or.cs index 4e55da107c..32f4745cf4 100644 --- a/TUnit.Assertions/AssertConditions/Or.cs +++ b/TUnit.Assertions/AssertConditions/Or.cs @@ -1,10 +1,10 @@ namespace TUnit.Assertions.AssertConditions; -public class Or +public class Or { - private readonly IReadOnlyCollection> _assertConditions; + private readonly IReadOnlyCollection> _assertConditions; - public Or(IReadOnlyCollection> assertConditions) + public Or(IReadOnlyCollection> assertConditions) { _assertConditions = assertConditions; } diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index f51fc5d4ef..090c98f513 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -4,7 +4,7 @@ public class StringEqualsAssertCondition : ExpectedValueAssertCondition>(), expected, stringComparison) + public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : this(null, Array.Empty>(), expected, stringComparison) { } diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs index b62d79ccc6..6be8069990 100644 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.AssertConditions.Conditions; +namespace TUnit.Assertions.AssertConditions.Throws; public abstract class DelegateAssertCondition { @@ -11,17 +11,11 @@ internal DelegateAssertCondition() protected Exception? Exception { get; private set; } protected T? ActualValue { get; private set; } = default!; - public bool Assert(Func action) + public bool Assert(DelegateInvocationResult delegateInvocationResult) { - try - { - ActualValue = action(); - } - catch (Exception e) - { - Exception = e; - } - + ActualValue = delegateInvocationResult.Result; + Exception = delegateInvocationResult.Exception; + return Passes(ActualValue, Exception); } @@ -48,17 +42,10 @@ internal DelegateAssertCondition() protected Exception? Exception { get; private set; } - public bool Assert(Action action) + public bool Assert(Exception? exception) { - try - { - action(); - } - catch (Exception e) - { - Exception = e; - } - + Exception = exception; + return Passes(Exception); } diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index 20adc12aa0..43082d2d26 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.AssertConditions.Conditions; +namespace TUnit.Assertions.AssertConditions.Throws; public class ThrowsNothingAssertCondition : DelegateAssertCondition { diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAsyncAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAsyncAssertCondition.cs deleted file mode 100644 index 9f099382de..0000000000 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAsyncAssertCondition.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.Conditions; - -public class ThrowsNothingAsyncAssertCondition : AsyncAssertCondition -{ - public override string DefaultMessage => $"A {Exception?.GetType().Name} was thrown"; - - protected override bool Passes(Exception? exception) - { - return Exception == null; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/DelegateInvocationResult.cs b/TUnit.Assertions/DelegateInvocationResult.cs new file mode 100644 index 0000000000..059eb50ae4 --- /dev/null +++ b/TUnit.Assertions/DelegateInvocationResult.cs @@ -0,0 +1,7 @@ +namespace TUnit.Assertions; + +public record DelegateInvocationResult(T? Result, Exception? Exception) +{ + public static implicit operator DelegateInvocationResult((T?, Exception?) tuple) => + new(tuple.Item1, tuple.Item2); +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/DelegateExtensions.cs b/TUnit.Assertions/Extensions/DelegateExtensions.cs index c0262c3e18..bf4c3abe2e 100644 --- a/TUnit.Assertions/Extensions/DelegateExtensions.cs +++ b/TUnit.Assertions/Extensions/DelegateExtensions.cs @@ -28,7 +28,7 @@ internal static class DelegateExtensions } } - public static async Task<(T?, Exception?)> InvokeAndGetExceptionAsync(this Func> action) + public static async Task> InvokeAndGetExceptionAsync(this Func> action) { try { @@ -40,7 +40,7 @@ internal static class DelegateExtensions } } - public static (T?, Exception?) InvokeAndGetException(this Func action) + public static DelegateInvocationResult InvokeAndGetException(this Func action) { try { diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 400455c14e..89a4c43203 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,6 +1,5 @@ using TUnit.Assertions; using TUnit.Assertions.AssertConditions.ConditionEntries.Static; -using TUnit.Assertions.AssertConditions.Conditions; using TUnit.Core; using TUnit.Core.Attributes; From 835c19e3cee023c6d247b6d94900caeeebc52517 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:15:59 +0000 Subject: [PATCH 049/124] Rework --- TUnit.Assertions/Assert.cs | 19 ++++++++---- TUnit.Assertions/AssertConditions/And.cs | 20 +++--------- .../AssertConditions/AssertCondition.cs | 31 ++++++++++--------- .../Collections/HasCountAssertCondition.cs | 4 +-- .../ConditionEntries/Instance/HasInstance.cs | 13 ++++---- .../ConditionEntries/Static/Is.cs | 12 +++---- .../ConditionEntries/Static/Is_Numbers.cs | 28 ++++++++--------- .../ConditionEntries/Static/Is_Strings.cs | 8 ++--- .../ConditionEntries/Static/Throws.cs | 1 - .../ExpectedValueAssertCondition.cs | 29 ----------------- .../Generic/EqualsAssertCondition.cs | 4 +-- .../Generic/SameReferenceAssertCondition.cs | 4 +-- .../Numbers/GreaterThanAssertCondition.cs | 8 ++--- .../GreaterThanOrEqualToAssertCondition.cs | 8 ++--- .../Numbers/IsEvenAssertCondition.cs | 8 +++-- .../Numbers/IsOddAssertCondition.cs | 8 +++-- .../Numbers/LessThanAssertCondition.cs | 8 ++--- .../LessThanOrEqualToAssertCondition.cs | 8 ++--- .../Numbers/ZeroAssertCondition.cs | 8 +++-- TUnit.Assertions/AssertConditions/Or.cs | 14 ++------- .../String/StringEqualsAssertCondition.cs | 6 ++-- TUnit.Engine/ClassLoader.cs | 2 +- TUnit.TestProject/Tests.cs | 8 ++--- 23 files changed, 115 insertions(+), 144 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index b54ac45e60..60ba68cbd2 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -7,7 +7,7 @@ namespace TUnit.Assertions; public static class Assert { - public static void That(T value, AssertCondition assertCondition) + public static void That(T value, AssertCondition assertCondition) { if (!assertCondition.Assert(value)) { @@ -17,7 +17,9 @@ public static void That(T value, AssertCondition assertCondition) public static void That(Action value, DelegateAssertCondition assertCondition) { - if (!assertCondition.Assert(value)) + var exception = value.InvokeAndGetException(); + + if (!assertCondition.Assert(exception)) { throw new AssertionException(assertCondition.Message); } @@ -25,15 +27,19 @@ public static void That(Action value, DelegateAssertCondition assertCondition) public static void That(Func value, DelegateAssertCondition assertCondition) { - if (!assertCondition.Assert(value)) + var delegateInvocationResult = value.InvokeAndGetException(); + + if (!assertCondition.Assert(delegateInvocationResult)) { throw new AssertionException(assertCondition.Message); } } - public static async Task That(Func value, AssertCondition assertCondition) + public static async Task That(Func value, DelegateAssertCondition assertCondition) { - if (!await assertCondition.Assert(value)) + var exception = await value.InvokeAndGetExceptionAsync(); + + if (!assertCondition.Assert(exception)) { throw new AssertionException(assertCondition.Message); } @@ -42,7 +48,8 @@ public static async Task That(Func value, AssertCondition assertCondition) public static async Task That(Func> value, DelegateAssertCondition assertCondition) { var invocationResult = await value.InvokeAndGetExceptionAsync(); - if (!assertCondition.Assert(invocationResult.Result, invocationResult.Exception)) + + if (!assertCondition.Assert(invocationResult)) { throw new AssertionException(assertCondition.Message); } diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/And.cs index a66af1a564..472d3c8929 100644 --- a/TUnit.Assertions/AssertConditions/And.cs +++ b/TUnit.Assertions/AssertConditions/And.cs @@ -2,27 +2,17 @@ namespace TUnit.Assertions.AssertConditions; -public class And +public class And { - private readonly IReadOnlyCollection> _assertConditions; + private readonly IReadOnlyCollection> _assertConditions; - public And(IReadOnlyCollection> assertConditions) + public And(IReadOnlyCollection> assertConditions) { _assertConditions = assertConditions; } - - public AssertCondition EqualTo(TExpected expected) + + public AssertCondition EqualTo(TExpected expected) { return Is.EqualTo(_assertConditions, expected); } -} - -public class And -{ - private readonly IReadOnlyCollection> _assertConditions; - - public And(IReadOnlyCollection> assertConditions) - { - _assertConditions = assertConditions; - } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 01ec7f1c92..19612d3251 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,23 +1,26 @@ namespace TUnit.Assertions.AssertConditions; -public abstract class AssertCondition +public abstract class AssertCondition { - internal readonly IReadOnlyCollection> PreviousAssertConditions; + internal readonly IReadOnlyCollection> AllAssertConditions; + internal TExpected? ExpectedValue { get; } - internal AssertCondition(IReadOnlyCollection> previousAssertConditions) + internal AssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) { - IReadOnlyCollection> conditionsUntilNow = [..previousAssertConditions, this]; - PreviousAssertConditions = conditionsUntilNow; + IReadOnlyCollection> conditionsUntilNow = [..previousAssertConditions, this]; + AllAssertConditions = conditionsUntilNow; + + ExpectedValue = expected; - And = new And(conditionsUntilNow); - Or = new Or(conditionsUntilNow); + And = new And(conditionsUntilNow); + Or = new Or(conditionsUntilNow); } - private Func? MessageFactory { get; set; } + private Func? MessageFactory { get; set; } - protected T ActualValue { get; private set; } = default!; + protected TActual ActualValue { get; private set; } = default!; - public bool Assert(T actualValue) + public bool Assert(TActual actualValue) { ActualValue = actualValue; return Passes(actualValue); @@ -25,16 +28,16 @@ public bool Assert(T actualValue) public abstract string DefaultMessage { get; } - protected abstract bool Passes(T actualValue); + protected abstract bool Passes(TActual actualValue); public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; - public AssertCondition WithMessage(Func messageFactory) + public AssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; } - public And And { get; } - public Or Or { get; } + public And And { get; } + public Or Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index 694d492a68..52b2fec406 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -2,9 +2,9 @@ namespace TUnit.Assertions.AssertConditions.Collections; -public class HasCountAssertCondition : ExpectedValueAssertCondition, int> +public class HasCountAssertCondition : AssertCondition, int> { - public HasCountAssertCondition(AsyncAssertCondition>? IReadOnlyCollection, int>> previousConditions, int expected) : base(previousConditions, expected) + public HasCountAssertCondition(IReadOnlyCollection, int>> previousConditions, int expected) : base(previousConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs index 2403ce2ebf..3d4c2e0cca 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs @@ -2,16 +2,15 @@ public class HasInstance { - private readonly AsyncAssertCondition? _asyncAssertCondition; - private readonly IReadOnlyCollection> _assertConditions; + private readonly IReadOnlyCollection> _assertConditions; - public HasInstance(IReadOnlyCollection> assertConditions) + public HasInstance(IReadOnlyCollection> assertConditions) { _assertConditions = assertConditions; - And = new And(assertConditions); - Or = new Or(assertConditions); + And = new And(assertConditions); + Or = new Or(assertConditions); } - public And And { get; } - public Or Or { get; } + public And And { get; } + public Or Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs index 5f108347ba..bcae9c2625 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs @@ -4,22 +4,22 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public partial class Is { - public static AssertCondition EqualTo(T expected) + public static AssertCondition EqualTo(TExpected expected) { - return new EqualsAssertCondition([], expected); + return new EqualsAssertCondition([], expected); } - internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, T expected) + internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, TExpected expected) { - return new EqualsAssertCondition(previousConditions, expected); + return new EqualsAssertCondition(previousConditions, expected); } - public static AssertCondition SameReference(T expected) + public static AssertCondition SameReference(T expected) { return new SameReferenceAssertCondition([], expected); } - internal static AssertCondition SameReference(IReadOnlyCollection> previousConditions, T expected) + internal static AssertCondition SameReference(IReadOnlyCollection> previousConditions, T expected) { return new SameReferenceAssertCondition(previousConditions, expected); } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs index 71ade23877..75bd9a72da 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs @@ -6,37 +6,37 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { - public static AssertCondition Zero => new EqualsAssertCondition([], 0); - internal static AssertCondition ZeroInternal(IReadOnlyCollection> previousConditions) + public static AssertCondition Zero => new EqualsAssertCondition([], 0); + internal static AssertCondition ZeroInternal(IReadOnlyCollection> previousConditions) => new EqualsAssertCondition(previousConditions, 0); - public static AssertCondition GreaterThan(T expected) where T : INumber + public static AssertCondition GreaterThan(T expected) where T : INumber { - return new GreaterThanAssertCondition(expected); + return new GreaterThanAssertCondition([], expected); } - public static AssertCondition GreaterThanOrEqualTo(T expected) where T : INumber + public static AssertCondition GreaterThanOrEqualTo(T expected) where T : INumber { - return new GreaterThanOrEqualToAssertCondition(expected); + return new GreaterThanOrEqualToAssertCondition([], expected); } - public static AssertCondition LessThan(T expected) where T : INumber + public static AssertCondition LessThan(T expected) where T : INumber { - return new GreaterThanAssertCondition(expected); + return new GreaterThanAssertCondition([], expected); } - public static AssertCondition LessThanOrEqualTo(T expected) where T : INumber + public static AssertCondition LessThanOrEqualTo(T expected) where T : INumber { - return new LessThanOrEqualToAssertCondition(expected); + return new LessThanOrEqualToAssertCondition([], expected); } - public static AssertCondition Even() where T : INumber, IModulusOperators + public static AssertCondition Even() where T : INumber, IModulusOperators { - return new IsEvenAssertCondition(); + return new IsEvenAssertCondition([], default); } - public static AssertCondition Odd() where T : INumber, IModulusOperators + public static AssertCondition Odd() where T : INumber, IModulusOperators { - return new IsOddAssertCondition(); + return new IsOddAssertCondition([], default); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index 76efbe35e4..6d7523616c 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -4,22 +4,22 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { - public static AssertCondition EqualTo(string expected) + public static AssertCondition EqualTo(string expected) { return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); } - internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected) + internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected) { return new StringEqualsAssertCondition(previousConditions, expected, StringComparison.Ordinal); } - public static AssertCondition EqualTo(string expected, StringComparison stringComparison) + public static AssertCondition EqualTo(string expected, StringComparison stringComparison) { return new StringEqualsAssertCondition(expected, stringComparison); } - internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) + internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) { return new StringEqualsAssertCondition(previousConditions, expected, stringComparison); } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs index a94bbf4982..2f1b7f1435 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs @@ -5,5 +5,4 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static class Throws { public static ThrowsNothingAssertCondition Nothing => new(); - public static ThrowsNothingAsyncAssertCondition NothingAsync => new(); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs deleted file mode 100644 index abd977c951..0000000000 --- a/TUnit.Assertions/AssertConditions/ExpectedValueAssertCondition.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace TUnit.Assertions.AssertConditions; - -public abstract class ExpectedValueAssertCondition : AssertCondition -{ - internal readonly IReadOnlyCollection> PreviousConditions; - internal TExpected ExpectedValue { get; } - - internal ExpectedValueAssertCondition(IReadOnlyCollection> previousConditions, TExpected expected) : base(previousConditions) - { - IReadOnlyCollection> conditionsUntilNow = [..previousConditions, this]; - PreviousConditions = conditionsUntilNow; - ExpectedValue = expected; - And = new And(conditionsUntilNow); - Or = new Or(conditionsUntilNow); - } - - public new string Message => MessageFactory?.Invoke((ExpectedValue, ActualValue)) ?? DefaultMessage; - - private Func<(TExpected ExpectedValue, TActual ActualValue), string>? MessageFactory { get; set; } - - public AssertCondition WithMessage(Func<(TExpected ExpectedValue, TActual ActualValue), string> messageFactory) - { - MessageFactory = messageFactory!; - return this; - } - - public new And And { get; } - public new Or Or { get; } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index ceb4e97b33..80a4dfc858 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,9 +1,9 @@ namespace TUnit.Assertions.AssertConditions.Generic; public class EqualsAssertCondition( - IReadOnlyCollection> previousConditions, + IReadOnlyCollection> previousConditions, TExpected expected) - : ExpectedValueAssertCondition(previousConditions, expected) + : AssertCondition(previousConditions, expected) { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index b9892cbf23..589544a81a 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -1,9 +1,9 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class SameReferenceAssertCondition : ExpectedValueAssertCondition +public class SameReferenceAssertCondition : AssertCondition { - public SameReferenceAssertCondition(IReadOnlyCollection> previousConditions, TExpected expected) : base(previousConditions, expected) + public SameReferenceAssertCondition(IReadOnlyCollection> previousConditions, TExpected expected) : base(previousConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index 6af5196336..472d56ba2e 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -2,18 +2,18 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class GreaterThanAssertCondition : ExpectedValueAssertCondition +public class GreaterThanAssertCondition : AssertCondition where TExpected : INumber where TActual : INumber, TExpected { - public GreaterThanAssertCondition(TExpected expected) : base(expected) + public GreaterThanAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) { } public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; - + protected override bool Passes(TActual actualValue) { - return actualValue > ExpectedValue; + return actualValue > ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 4a2d65edf7..8821b2eb38 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -2,18 +2,18 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class GreaterThanOrEqualToAssertCondition : ExpectedValueAssertCondition +public class GreaterThanOrEqualToAssertCondition : AssertCondition where TExpected : INumber where TActual : INumber, TExpected { - public GreaterThanOrEqualToAssertCondition(TExpected expected) : base(expected) + public GreaterThanOrEqualToAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) { } public override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; - + protected override bool Passes(TActual actualValue) { - return actualValue >= ExpectedValue; + return actualValue >= ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index 9cffeb80fb..36c0be40a2 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -2,11 +2,15 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class IsEvenAssertCondition : AssertCondition +public class IsEvenAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { + public IsEvenAssertCondition(IReadOnlyCollection> previousAssertConditions, TActual? expected) : base(previousAssertConditions, expected) + { + } + public override string DefaultMessage => $"{ActualValue} is not even"; - + protected override bool Passes(TActual actualValue) { return actualValue % 2 == 0; diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index d339f0e854..68dfc446c1 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -2,11 +2,15 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class IsOddAssertCondition : AssertCondition +public class IsOddAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { + public IsOddAssertCondition(IReadOnlyCollection> previousAssertConditions, TActual? expected) : base(previousAssertConditions, expected) + { + } + public override string DefaultMessage => $"{ActualValue} is not odd"; - + protected override bool Passes(TActual actualValue) { return actualValue % 2 != 0; diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index bfc5a595c7..f54cddf768 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -2,18 +2,18 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class LessThanAssertCondition : ExpectedValueAssertCondition +public class LessThanAssertCondition : AssertCondition where TExpected : INumber where TActual : INumber, TExpected { - public LessThanAssertCondition(TExpected expected) : base(expected) + public LessThanAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) { } public override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; - + protected override bool Passes(TActual actualValue) { - return actualValue < ExpectedValue; + return actualValue < ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index 4d27b9a86e..2309554edf 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -2,18 +2,18 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class LessThanOrEqualToAssertCondition : ExpectedValueAssertCondition +public class LessThanOrEqualToAssertCondition : AssertCondition where TExpected : INumber where TActual : INumber, TExpected { - public LessThanOrEqualToAssertCondition(TExpected expected) : base(expected) + public LessThanOrEqualToAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) { } public override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; - + protected override bool Passes(TActual actualValue) { - return actualValue <= ExpectedValue; + return actualValue <= ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index b91814004d..e3a7a12842 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -2,11 +2,15 @@ namespace TUnit.Assertions.AssertConditions.Numbers; -public class ZeroAssertCondition : AssertCondition +public class ZeroAssertCondition : AssertCondition where TActual : INumber, IEqualityOperators { + public ZeroAssertCondition(IReadOnlyCollection> previousAssertConditions, TActual? expected) : base(previousAssertConditions, expected) + { + } + public override string DefaultMessage => $"{ActualValue} is not equal to 0"; - + protected override bool Passes(TActual actualValue) { return actualValue == TActual.Zero; diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Or.cs index 32f4745cf4..9adce24dd0 100644 --- a/TUnit.Assertions/AssertConditions/Or.cs +++ b/TUnit.Assertions/AssertConditions/Or.cs @@ -1,20 +1,10 @@ namespace TUnit.Assertions.AssertConditions; -public class Or -{ - private readonly IReadOnlyCollection> _assertConditions; - - public Or(IReadOnlyCollection> assertConditions) - { - _assertConditions = assertConditions; - } -} - public class Or { - private readonly IReadOnlyCollection> _assertConditions; + private readonly IReadOnlyCollection> _assertConditions; - public Or(IReadOnlyCollection> assertConditions) + public Or(IReadOnlyCollection> assertConditions) { _assertConditions = assertConditions; } diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 090c98f513..3bf6f2d14f 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -1,14 +1,14 @@ namespace TUnit.Assertions.AssertConditions.String; -public class StringEqualsAssertCondition : ExpectedValueAssertCondition +public class StringEqualsAssertCondition : AssertCondition { private readonly StringComparison _stringComparison; - public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : this(null, Array.Empty>(), expected, stringComparison) + public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : this([], expected, stringComparison) { } - public StringEqualsAssertCondition(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) : base(previousConditions, expected) + public StringEqualsAssertCondition(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) : base(previousConditions, expected) { _stringComparison = stringComparison; } diff --git a/TUnit.Engine/ClassLoader.cs b/TUnit.Engine/ClassLoader.cs index 142f494c8d..7ec680a467 100644 --- a/TUnit.Engine/ClassLoader.cs +++ b/TUnit.Engine/ClassLoader.cs @@ -21,7 +21,7 @@ private static IEnumerable LoadTypes(Assembly assembly) } catch { - return Array.Empty(); + return []; } } } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 89a4c43203..59d0b2448e 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -139,7 +139,7 @@ public void TestContext2() [Test] public void Throws1() { - Assert.That(() => new string(Array.Empty()), Throws.Nothing); + Assert.That(() => new string([]), Throws.Nothing); } [Test] @@ -148,8 +148,8 @@ public async Task Throws2() await Assert.That(async () => { await Task.Yield(); - new string(Array.Empty()); - }, Throws.NothingAsync); + new string([]); + }, Throws.Nothing); } [Test] @@ -165,7 +165,7 @@ await Assert.That(async () => { await Task.Yield(); throw new ApplicationException(); - }, Throws.NothingAsync); + }, Throws.Nothing); } [Test, Timeout(500)] From 00408f2a1d978323c70d34f77e6ce8e4c85c3d6f Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:24:40 +0000 Subject: [PATCH 050/124] Improvements --- TUnit.Assertions/Assert.cs | 43 +++++++++++++------ .../AssertConditions/AssertCondition.cs | 4 +- .../ConditionEntries/Static/Throws.cs | 2 +- .../Throws/DelegateAssertCondition.cs | 10 ++++- .../Throws/ThrowsNothingAssertCondition.cs | 8 +++- TUnit.TestProject/Tests.cs | 8 ++++ 6 files changed, 54 insertions(+), 21 deletions(-) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 60ba68cbd2..f64f6824b4 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -9,49 +9,64 @@ public static class Assert { public static void That(T value, AssertCondition assertCondition) { - if (!assertCondition.Assert(value)) + foreach (var condition in assertCondition.NestedAssertConditions) { - throw new AssertionException(assertCondition.Message); + if (!condition.Assert(value)) + { + throw new AssertionException(condition.Message); + } } } public static void That(Action value, DelegateAssertCondition assertCondition) { var exception = value.InvokeAndGetException(); - - if (!assertCondition.Assert(exception)) + + foreach (var condition in assertCondition.NestedAssertConditions) { - throw new AssertionException(assertCondition.Message); + if (!condition.Assert(exception)) + { + throw new AssertionException(condition.Message); + } } } public static void That(Func value, DelegateAssertCondition assertCondition) { var delegateInvocationResult = value.InvokeAndGetException(); - - if (!assertCondition.Assert(delegateInvocationResult)) + + foreach (var condition in assertCondition.NestedAssertConditions) { - throw new AssertionException(assertCondition.Message); + if (!condition.Assert(delegateInvocationResult)) + { + throw new AssertionException(condition.Message); + } } } public static async Task That(Func value, DelegateAssertCondition assertCondition) { var exception = await value.InvokeAndGetExceptionAsync(); - - if (!assertCondition.Assert(exception)) + + foreach (var condition in assertCondition.NestedAssertConditions) { - throw new AssertionException(assertCondition.Message); + if (!condition.Assert(exception)) + { + throw new AssertionException(condition.Message); + } } } public static async Task That(Func> value, DelegateAssertCondition assertCondition) { var invocationResult = await value.InvokeAndGetExceptionAsync(); - - if (!assertCondition.Assert(invocationResult)) + + foreach (var condition in assertCondition.NestedAssertConditions) { - throw new AssertionException(assertCondition.Message); + if (!condition.Assert(invocationResult)) + { + throw new AssertionException(condition.Message); + } } } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 19612d3251..60ce55879b 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -2,13 +2,13 @@ public abstract class AssertCondition { - internal readonly IReadOnlyCollection> AllAssertConditions; + internal readonly IReadOnlyCollection> NestedAssertConditions; internal TExpected? ExpectedValue { get; } internal AssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) { IReadOnlyCollection> conditionsUntilNow = [..previousAssertConditions, this]; - AllAssertConditions = conditionsUntilNow; + NestedAssertConditions = conditionsUntilNow; ExpectedValue = expected; diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs index 2f1b7f1435..b3b284ee96 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs @@ -4,5 +4,5 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static class Throws { - public static ThrowsNothingAssertCondition Nothing => new(); + public static ThrowsNothingAssertCondition Nothing => new([]); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs index 6be8069990..f39eee949c 100644 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs @@ -2,8 +2,11 @@ public abstract class DelegateAssertCondition { - internal DelegateAssertCondition() + internal readonly IReadOnlyCollection> NestedAssertConditions; + + internal DelegateAssertCondition(IReadOnlyCollection> nestedAssertConditions) { + NestedAssertConditions = nestedAssertConditions; } private Func? MessageFactory { get; set; } @@ -34,8 +37,11 @@ public DelegateAssertCondition WithMessage(Func messa public abstract class DelegateAssertCondition { - internal DelegateAssertCondition() + internal readonly IReadOnlyCollection NestedAssertConditions; + + internal DelegateAssertCondition(IReadOnlyCollection nestedAssertConditions) { + NestedAssertConditions = nestedAssertConditions; } private Func? MessageFactory { get; set; } diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index 43082d2d26..ae6d666049 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -2,10 +2,14 @@ public class ThrowsNothingAssertCondition : DelegateAssertCondition { + public ThrowsNothingAssertCondition(IReadOnlyCollection nestedAssertConditions) : base(nestedAssertConditions) + { + } + private Exception? _exception; - + public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; - + protected override bool Passes(Exception? exception) { _exception = exception; diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 59d0b2448e..0fc291fa4d 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -182,6 +182,14 @@ public void String_And_Condition() Assert.That(str, Is.EqualTo("My string").And.EqualTo("This")); } + [Test] + public void String_And_Condition2() + { + var str = "My string"; + + Assert.That(str, Is.EqualTo("This").And.EqualTo("My string")); + } + public static int One() => 1; public static int Two() => 2; } \ No newline at end of file From cb8a765888595c1041be9280e0d919252255f422 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:27:06 +0000 Subject: [PATCH 051/124] Improvements --- TUnit.Assertions/AssertConditions/AssertCondition.cs | 9 ++++----- .../Numbers/GreaterThanAssertCondition.cs | 2 +- .../Numbers/GreaterThanOrEqualToAssertCondition.cs | 2 +- .../AssertConditions/Numbers/IsEvenAssertCondition.cs | 2 +- .../AssertConditions/Numbers/IsOddAssertCondition.cs | 2 +- .../AssertConditions/Numbers/LessThanAssertCondition.cs | 2 +- .../Numbers/LessThanOrEqualToAssertCondition.cs | 2 +- .../AssertConditions/Numbers/ZeroAssertCondition.cs | 2 +- .../AssertConditions/Throws/DelegateAssertCondition.cs | 4 ++-- 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 60ce55879b..94e1be64ac 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -5,15 +5,14 @@ public abstract class AssertCondition internal readonly IReadOnlyCollection> NestedAssertConditions; internal TExpected? ExpectedValue { get; } - internal AssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) + internal AssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) { - IReadOnlyCollection> conditionsUntilNow = [..previousAssertConditions, this]; - NestedAssertConditions = conditionsUntilNow; + NestedAssertConditions = [..nestedAssertConditions, this]; ExpectedValue = expected; - And = new And(conditionsUntilNow); - Or = new Or(conditionsUntilNow); + And = new And(NestedAssertConditions); + Or = new Or(NestedAssertConditions); } private Func? MessageFactory { get; set; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index 472d56ba2e..8988cb560c 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -6,7 +6,7 @@ public class GreaterThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public GreaterThanAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) + public GreaterThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 8821b2eb38..24190b5668 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -6,7 +6,7 @@ public class GreaterThanOrEqualToAssertCondition : AssertCon where TExpected : INumber where TActual : INumber, TExpected { - public GreaterThanOrEqualToAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) + public GreaterThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index 36c0be40a2..f7243911e5 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsEvenAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsEvenAssertCondition(IReadOnlyCollection> previousAssertConditions, TActual? expected) : base(previousAssertConditions, expected) + public IsEvenAssertCondition(IReadOnlyCollection> nestedAssertConditions, TActual? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index 68dfc446c1..38d1993717 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsOddAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsOddAssertCondition(IReadOnlyCollection> previousAssertConditions, TActual? expected) : base(previousAssertConditions, expected) + public IsOddAssertCondition(IReadOnlyCollection> nestedAssertConditions, TActual? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index f54cddf768..20ed37bb7b 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -6,7 +6,7 @@ public class LessThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public LessThanAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) + public LessThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index 2309554edf..2ae317b8ef 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -6,7 +6,7 @@ public class LessThanOrEqualToAssertCondition : AssertCondit where TExpected : INumber where TActual : INumber, TExpected { - public LessThanOrEqualToAssertCondition(IReadOnlyCollection> previousAssertConditions, TExpected? expected) : base(previousAssertConditions, expected) + public LessThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index e3a7a12842..2d985a0c52 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class ZeroAssertCondition : AssertCondition where TActual : INumber, IEqualityOperators { - public ZeroAssertCondition(IReadOnlyCollection> previousAssertConditions, TActual? expected) : base(previousAssertConditions, expected) + public ZeroAssertCondition(IReadOnlyCollection> nestedAssertConditions, TActual? expected) : base(nestedAssertConditions, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs index f39eee949c..e1cdec42f6 100644 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs @@ -6,7 +6,7 @@ public abstract class DelegateAssertCondition internal DelegateAssertCondition(IReadOnlyCollection> nestedAssertConditions) { - NestedAssertConditions = nestedAssertConditions; + NestedAssertConditions = [..nestedAssertConditions, this]; } private Func? MessageFactory { get; set; } @@ -41,7 +41,7 @@ public abstract class DelegateAssertCondition internal DelegateAssertCondition(IReadOnlyCollection nestedAssertConditions) { - NestedAssertConditions = nestedAssertConditions; + NestedAssertConditions = [..nestedAssertConditions, this]; } private Func? MessageFactory { get; set; } From b28b1fbfc0a4d623ab7a53ed8daaf82c104448dd Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:57:03 +0000 Subject: [PATCH 052/124] Rework --- TUnit.Assertions/Assert.cs | 16 ++++++++++++---- TUnit.Assertions/AssertConditions/And.cs | 2 +- .../AssertConditions/AssertCondition.cs | 4 +++- .../Collections/HasCountAssertCondition.cs | 2 +- .../ConditionEntries/Static/Is.cs | 12 ++++++------ .../ConditionEntries/Static/Is_Numbers.cs | 18 +++++++++--------- .../ConditionEntries/Static/Is_Strings.cs | 8 ++++---- .../Generic/EqualsAssertCondition.cs | 4 ++-- .../Generic/SameReferenceAssertCondition.cs | 2 +- .../Numbers/GreaterThanAssertCondition.cs | 2 +- .../GreaterThanOrEqualToAssertCondition.cs | 2 +- .../Numbers/IsEvenAssertCondition.cs | 2 +- .../Numbers/IsOddAssertCondition.cs | 2 +- .../Numbers/LessThanAssertCondition.cs | 2 +- .../LessThanOrEqualToAssertCondition.cs | 2 +- .../Numbers/ZeroAssertCondition.cs | 2 +- TUnit.Assertions/AssertConditions/Or.cs | 7 +++++++ .../String/StringEqualsAssertCondition.cs | 4 ++-- TUnit.Assertions/NestedConditionsOperator.cs | 7 +++++++ TUnit.sln | 18 ++++++++++++++++++ 20 files changed, 80 insertions(+), 38 deletions(-) create mode 100644 TUnit.Assertions/NestedConditionsOperator.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index f64f6824b4..9c9c81d93e 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -18,7 +18,7 @@ public static void That(T value, AssertCondition assertCondition) } } - public static void That(Action value, DelegateAssertCondition assertCondition) + public static Exception? That(Action value, DelegateAssertCondition assertCondition) { var exception = value.InvokeAndGetException(); @@ -29,9 +29,11 @@ public static void That(Action value, DelegateAssertCondition assertCondition) throw new AssertionException(condition.Message); } } + + return exception; } - public static void That(Func value, DelegateAssertCondition assertCondition) + public static DelegateInvocationResult That(Func value, DelegateAssertCondition assertCondition) { var delegateInvocationResult = value.InvokeAndGetException(); @@ -42,9 +44,11 @@ public static void That(Func value, DelegateAssertCondition assertCondi throw new AssertionException(condition.Message); } } + + return delegateInvocationResult; } - public static async Task That(Func value, DelegateAssertCondition assertCondition) + public static async Task That(Func value, DelegateAssertCondition assertCondition) { var exception = await value.InvokeAndGetExceptionAsync(); @@ -55,9 +59,11 @@ public static async Task That(Func value, DelegateAssertCondition assertCo throw new AssertionException(condition.Message); } } + + return exception; } - public static async Task That(Func> value, DelegateAssertCondition assertCondition) + public static async Task> That(Func> value, DelegateAssertCondition assertCondition) { var invocationResult = await value.InvokeAndGetExceptionAsync(); @@ -68,5 +74,7 @@ public static async Task That(Func> value, DelegateAssertCondition throw new AssertionException(condition.Message); } } + + return invocationResult; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/And.cs index 472d3c8929..b038319e87 100644 --- a/TUnit.Assertions/AssertConditions/And.cs +++ b/TUnit.Assertions/AssertConditions/And.cs @@ -13,6 +13,6 @@ public And(IReadOnlyCollection> assertCondit public AssertCondition EqualTo(TExpected expected) { - return Is.EqualTo(_assertConditions, expected); + return Is.EqualTo(_assertConditions, NestedConditionsOperator.And, expected); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 94e1be64ac..593e14d986 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -3,12 +3,14 @@ public abstract class AssertCondition { internal readonly IReadOnlyCollection> NestedAssertConditions; + public NestedConditionsOperator? NestedConditionsOperator { get; } internal TExpected? ExpectedValue { get; } - internal AssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) + internal AssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) { NestedAssertConditions = [..nestedAssertConditions, this]; + NestedConditionsOperator = nestedConditionsOperator; ExpectedValue = expected; And = new And(NestedAssertConditions); diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index 52b2fec406..7791770b0f 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions.AssertConditions.Collections; public class HasCountAssertCondition : AssertCondition, int> { - public HasCountAssertCondition(IReadOnlyCollection, int>> previousConditions, int expected) : base(previousConditions, expected) + public HasCountAssertCondition(IReadOnlyCollection, int>> nestedConditions, NestedConditionsOperator? @operator, int expected) : base(nestedConditions, @operator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs index bcae9c2625..e5a65da294 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs @@ -6,21 +6,21 @@ public partial class Is { public static AssertCondition EqualTo(TExpected expected) { - return new EqualsAssertCondition([], expected); + return new EqualsAssertCondition([], null, expected); } - internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, TExpected expected) + internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, TExpected expected) { - return new EqualsAssertCondition(previousConditions, expected); + return new EqualsAssertCondition(nestedConditions, @operator, expected); } public static AssertCondition SameReference(T expected) { - return new SameReferenceAssertCondition([], expected); + return new SameReferenceAssertCondition([], null, expected); } - internal static AssertCondition SameReference(IReadOnlyCollection> previousConditions, T expected) + internal static AssertCondition SameReference(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, T expected) { - return new SameReferenceAssertCondition(previousConditions, expected); + return new SameReferenceAssertCondition(nestedConditions, @operator, expected); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs index 75bd9a72da..5063489fc2 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs @@ -6,37 +6,37 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { - public static AssertCondition Zero => new EqualsAssertCondition([], 0); - internal static AssertCondition ZeroInternal(IReadOnlyCollection> previousConditions) - => new EqualsAssertCondition(previousConditions, 0); + public static AssertCondition Zero => new EqualsAssertCondition([], null, 0); + internal static AssertCondition ZeroInternal(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator) + => new EqualsAssertCondition(nestedConditions, @operator, 0); public static AssertCondition GreaterThan(T expected) where T : INumber { - return new GreaterThanAssertCondition([], expected); + return new GreaterThanAssertCondition([], null, expected); } public static AssertCondition GreaterThanOrEqualTo(T expected) where T : INumber { - return new GreaterThanOrEqualToAssertCondition([], expected); + return new GreaterThanOrEqualToAssertCondition([], null, expected); } public static AssertCondition LessThan(T expected) where T : INumber { - return new GreaterThanAssertCondition([], expected); + return new GreaterThanAssertCondition([], null, expected); } public static AssertCondition LessThanOrEqualTo(T expected) where T : INumber { - return new LessThanOrEqualToAssertCondition([], expected); + return new LessThanOrEqualToAssertCondition([], null, expected); } public static AssertCondition Even() where T : INumber, IModulusOperators { - return new IsEvenAssertCondition([], default); + return new IsEvenAssertCondition([], null, default); } public static AssertCondition Odd() where T : INumber, IModulusOperators { - return new IsOddAssertCondition([], default); + return new IsOddAssertCondition([], null, default); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index 6d7523616c..2f091b0d85 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -9,9 +9,9 @@ public static AssertCondition EqualTo(string expected) return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); } - internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected) + internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected) { - return new StringEqualsAssertCondition(previousConditions, expected, StringComparison.Ordinal); + return new StringEqualsAssertCondition(nestedConditions, @operator, expected, StringComparison.Ordinal); } public static AssertCondition EqualTo(string expected, StringComparison stringComparison) @@ -19,8 +19,8 @@ public static AssertCondition EqualTo(string expected, StringCom return new StringEqualsAssertCondition(expected, stringComparison); } - internal static AssertCondition EqualTo(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) + internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected, StringComparison stringComparison) { - return new StringEqualsAssertCondition(previousConditions, expected, stringComparison); + return new StringEqualsAssertCondition(nestedConditions, @operator, expected, stringComparison); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 80a4dfc858..ec76693a45 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,9 +1,9 @@ namespace TUnit.Assertions.AssertConditions.Generic; public class EqualsAssertCondition( - IReadOnlyCollection> previousConditions, + IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, TExpected expected) - : AssertCondition(previousConditions, expected) + : AssertCondition(nestedConditions, @operator, expected) { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index 589544a81a..ba894c2249 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -3,7 +3,7 @@ public class SameReferenceAssertCondition : AssertCondition { - public SameReferenceAssertCondition(IReadOnlyCollection> previousConditions, TExpected expected) : base(previousConditions, expected) + public SameReferenceAssertCondition(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, TExpected expected) : base(nestedConditions, @operator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index 8988cb560c..3144235371 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -6,7 +6,7 @@ public class GreaterThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public GreaterThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) + public GreaterThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 24190b5668..433d15089a 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -6,7 +6,7 @@ public class GreaterThanOrEqualToAssertCondition : AssertCon where TExpected : INumber where TActual : INumber, TExpected { - public GreaterThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) + public GreaterThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index f7243911e5..b3227903b0 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsEvenAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsEvenAssertCondition(IReadOnlyCollection> nestedAssertConditions, TActual? expected) : base(nestedAssertConditions, expected) + public IsEvenAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TActual? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index 38d1993717..4e75bd4610 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsOddAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsOddAssertCondition(IReadOnlyCollection> nestedAssertConditions, TActual? expected) : base(nestedAssertConditions, expected) + public IsOddAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TActual? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index 20ed37bb7b..00db21ded2 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -6,7 +6,7 @@ public class LessThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public LessThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) + public LessThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index 2ae317b8ef..e999dd86bf 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -6,7 +6,7 @@ public class LessThanOrEqualToAssertCondition : AssertCondit where TExpected : INumber where TActual : INumber, TExpected { - public LessThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, TExpected? expected) : base(nestedAssertConditions, expected) + public LessThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index 2d985a0c52..b88c9276cf 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class ZeroAssertCondition : AssertCondition where TActual : INumber, IEqualityOperators { - public ZeroAssertCondition(IReadOnlyCollection> nestedAssertConditions, TActual? expected) : base(nestedAssertConditions, expected) + public ZeroAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TActual? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) { } diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Or.cs index 9adce24dd0..ed176f987d 100644 --- a/TUnit.Assertions/AssertConditions/Or.cs +++ b/TUnit.Assertions/AssertConditions/Or.cs @@ -1,3 +1,5 @@ +using TUnit.Assertions.AssertConditions.ConditionEntries.Static; + namespace TUnit.Assertions.AssertConditions; public class Or @@ -8,4 +10,9 @@ public Or(IReadOnlyCollection> assertConditi { _assertConditions = assertConditions; } + + public AssertCondition EqualTo(TExpected expected) + { + return Is.EqualTo(_assertConditions, NestedConditionsOperator.Or, expected); + } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 3bf6f2d14f..f5225bb11f 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -4,11 +4,11 @@ public class StringEqualsAssertCondition : AssertCondition { private readonly StringComparison _stringComparison; - public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : this([], expected, stringComparison) + public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : this([], null, expected, stringComparison) { } - public StringEqualsAssertCondition(IReadOnlyCollection> previousConditions, string expected, StringComparison stringComparison) : base(previousConditions, expected) + public StringEqualsAssertCondition(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected, StringComparison stringComparison) : base(nestedConditions, @operator, expected) { _stringComparison = stringComparison; } diff --git a/TUnit.Assertions/NestedConditionsOperator.cs b/TUnit.Assertions/NestedConditionsOperator.cs new file mode 100644 index 0000000000..b01de33ccf --- /dev/null +++ b/TUnit.Assertions/NestedConditionsOperator.cs @@ -0,0 +1,7 @@ +namespace TUnit.Assertions; + +public enum NestedConditionsOperator +{ + And, + Or +} \ No newline at end of file diff --git a/TUnit.sln b/TUnit.sln index c9f4a58317..ea39d6f612 100644 --- a/TUnit.sln +++ b/TUnit.sln @@ -16,6 +16,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Assertions.UnitTests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Engine", "TUnit.Engine\TUnit.Engine.csproj", "{6C960AFF-E533-4B61-A559-107CA9AA5E76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Analyzers", "TUnit.Analyzers\TUnit.Analyzers\TUnit.Analyzers.csproj", "{68EE0A31-F949-445D-80D7-CD7160510655}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Analyzers.Sample", "TUnit.Analyzers\TUnit.Analyzers.Sample\TUnit.Analyzers.Sample.csproj", "{175A7375-E1B4-4BED-98FC-988A46263DE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Analyzers.Tests", "TUnit.Analyzers\TUnit.Analyzers.Tests\TUnit.Analyzers.Tests.csproj", "{EE52CB5D-86DF-47E6-B105-F453A0B6FE66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,5 +60,17 @@ Global {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C960AFF-E533-4B61-A559-107CA9AA5E76}.Release|Any CPU.Build.0 = Release|Any CPU + {68EE0A31-F949-445D-80D7-CD7160510655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68EE0A31-F949-445D-80D7-CD7160510655}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68EE0A31-F949-445D-80D7-CD7160510655}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68EE0A31-F949-445D-80D7-CD7160510655}.Release|Any CPU.Build.0 = Release|Any CPU + {175A7375-E1B4-4BED-98FC-988A46263DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {175A7375-E1B4-4BED-98FC-988A46263DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {175A7375-E1B4-4BED-98FC-988A46263DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {175A7375-E1B4-4BED-98FC-988A46263DE4}.Release|Any CPU.Build.0 = Release|Any CPU + {EE52CB5D-86DF-47E6-B105-F453A0B6FE66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE52CB5D-86DF-47E6-B105-F453A0B6FE66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE52CB5D-86DF-47E6-B105-F453A0B6FE66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE52CB5D-86DF-47E6-B105-F453A0B6FE66}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 531f2c5f65ef6661e7776326ec81663dfa9f287f Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:56:12 +0000 Subject: [PATCH 053/124] Rework --- TUnit.Assertions/Assert.cs | 39 ++++++------------- TUnit.Assertions/AssertConditions/And.cs | 11 +++--- .../AssertConditions/AssertCondition.cs | 31 ++++----------- .../AssertConditions/BaseAssertCondition.cs | 19 +++++++++ .../Collections/HasCountAssertCondition.cs | 4 +- .../Combiners/AssertConditionAnd.cs | 23 +++++++++++ .../Combiners/AssertConditionOr.cs | 21 ++++++++++ .../Combiners/DelegateAssertConditionAnd.cs | 26 +++++++++++++ .../Combiners/DelegateAssertConditionOr.cs | 24 ++++++++++++ .../ConditionEntries/Instance/HasInstance.cs | 9 ++--- .../ConditionEntries/Static/Is.cs | 10 ++--- .../ConditionEntries/Static/Is_Numbers.cs | 16 ++++---- .../ConditionEntries/Static/Is_Strings.cs | 4 +- .../ConditionEntries/Static/Throws.cs | 2 +- .../Generic/EqualsAssertCondition.cs | 8 ++-- .../Generic/SameReferenceAssertCondition.cs | 4 +- .../Numbers/GreaterThanAssertCondition.cs | 4 +- .../GreaterThanOrEqualToAssertCondition.cs | 4 +- .../Numbers/IsEvenAssertCondition.cs | 4 +- .../Numbers/IsOddAssertCondition.cs | 4 +- .../Numbers/LessThanAssertCondition.cs | 4 +- .../LessThanOrEqualToAssertCondition.cs | 4 +- .../Numbers/ZeroAssertCondition.cs | 4 +- TUnit.Assertions/AssertConditions/Or.cs | 11 +++--- .../String/StringEqualsAssertCondition.cs | 8 +--- .../Throws/DelegateAssertCondition.cs | 22 ++--------- .../Throws/ThrowsNothingAssertCondition.cs | 6 +-- TUnit.Assertions/TUnit.Assertions.csproj | 4 ++ TUnit/GlobalUsings.cs | 4 ++ 29 files changed, 200 insertions(+), 134 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/BaseAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs create mode 100644 TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs create mode 100644 TUnit/GlobalUsings.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 9c9c81d93e..450840ed73 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -9,12 +9,9 @@ public static class Assert { public static void That(T value, AssertCondition assertCondition) { - foreach (var condition in assertCondition.NestedAssertConditions) + if (!assertCondition.Assert(value)) { - if (!condition.Assert(value)) - { - throw new AssertionException(condition.Message); - } + throw new AssertionException(assertCondition.Message); } } @@ -22,12 +19,9 @@ public static void That(T value, AssertCondition assertCondition) { var exception = value.InvokeAndGetException(); - foreach (var condition in assertCondition.NestedAssertConditions) + if (!assertCondition.Assert(exception)) { - if (!condition.Assert(exception)) - { - throw new AssertionException(condition.Message); - } + throw new AssertionException(assertCondition.Message); } return exception; @@ -37,12 +31,9 @@ public static DelegateInvocationResult That(Func value, DelegateAssertC { var delegateInvocationResult = value.InvokeAndGetException(); - foreach (var condition in assertCondition.NestedAssertConditions) + if (!assertCondition.Assert(delegateInvocationResult)) { - if (!condition.Assert(delegateInvocationResult)) - { - throw new AssertionException(condition.Message); - } + throw new AssertionException(assertCondition.Message); } return delegateInvocationResult; @@ -52,12 +43,9 @@ public static DelegateInvocationResult That(Func value, DelegateAssertC { var exception = await value.InvokeAndGetExceptionAsync(); - foreach (var condition in assertCondition.NestedAssertConditions) + if (!assertCondition.Assert(exception)) { - if (!condition.Assert(exception)) - { - throw new AssertionException(condition.Message); - } + throw new AssertionException(assertCondition.Message); } return exception; @@ -65,16 +53,13 @@ public static DelegateInvocationResult That(Func value, DelegateAssertC public static async Task> That(Func> value, DelegateAssertCondition assertCondition) { - var invocationResult = await value.InvokeAndGetExceptionAsync(); + var delegateInvocationResult = await value.InvokeAndGetExceptionAsync(); - foreach (var condition in assertCondition.NestedAssertConditions) + if (!assertCondition.Assert(delegateInvocationResult)) { - if (!condition.Assert(invocationResult)) - { - throw new AssertionException(condition.Message); - } + throw new AssertionException(assertCondition.Message); } - return invocationResult; + return delegateInvocationResult; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/And.cs index b038319e87..91ecbe0c2a 100644 --- a/TUnit.Assertions/AssertConditions/And.cs +++ b/TUnit.Assertions/AssertConditions/And.cs @@ -1,18 +1,19 @@ +using TUnit.Assertions.AssertConditions.Combiners; using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Assertions.AssertConditions; public class And { - private readonly IReadOnlyCollection> _assertConditions; + private readonly BaseAssertCondition _otherAssertCondition; - public And(IReadOnlyCollection> assertConditions) + public And(BaseAssertCondition otherAssertCondition) { - _assertConditions = assertConditions; + _otherAssertCondition = otherAssertCondition; } - public AssertCondition EqualTo(TExpected expected) + public AssertConditionAnd EqualTo(TExpected expected) { - return Is.EqualTo(_assertConditions, NestedConditionsOperator.And, expected); + return new AssertConditionAnd(_otherAssertCondition, Is.EqualTo(expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 593e14d986..e50181ac79 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,35 +1,23 @@ -namespace TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Combiners; -public abstract class AssertCondition +namespace TUnit.Assertions.AssertConditions; + +public abstract class AssertCondition : BaseAssertCondition { - internal readonly IReadOnlyCollection> NestedAssertConditions; - public NestedConditionsOperator? NestedConditionsOperator { get; } internal TExpected? ExpectedValue { get; } - internal AssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) + internal AssertCondition(TExpected? expected) { - NestedAssertConditions = [..nestedAssertConditions, this]; - - NestedConditionsOperator = nestedConditionsOperator; ExpectedValue = expected; - - And = new And(NestedAssertConditions); - Or = new Or(NestedAssertConditions); } - - private Func? MessageFactory { get; set; } - - protected TActual ActualValue { get; private set; } = default!; - + public bool Assert(TActual actualValue) { ActualValue = actualValue; return Passes(actualValue); } - - public abstract string DefaultMessage { get; } - - protected abstract bool Passes(TActual actualValue); + + private Func? MessageFactory { get; set; } public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; @@ -38,7 +26,4 @@ public AssertCondition WithMessage(Func mes MessageFactory = messageFactory; return this; } - - public And And { get; } - public Or Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs new file mode 100644 index 0000000000..487b87d8d3 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -0,0 +1,19 @@ +namespace TUnit.Assertions.AssertConditions; + +public abstract class BaseAssertCondition +{ + internal BaseAssertCondition() + { + And = new And(this); + Or = new Or(this); + } + + protected TActual ActualValue { get; set; } = default!; + + public abstract string DefaultMessage { get; } + + protected internal abstract bool Passes(TActual actualValue); + + public And And { get; } + public Or Or { get; } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index 7791770b0f..fcd518ab44 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -4,13 +4,13 @@ namespace TUnit.Assertions.AssertConditions.Collections; public class HasCountAssertCondition : AssertCondition, int> { - public HasCountAssertCondition(IReadOnlyCollection, int>> nestedConditions, NestedConditionsOperator? @operator, int expected) : base(nestedConditions, @operator, expected) + public HasCountAssertCondition(IReadOnlyCollection, int>> nestedConditions, NestedConditionsOperator? @operator, int expected) : base(expected) { } public override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; - protected override bool Passes(IEnumerable actualValue) + protected internal override bool Passes(IEnumerable actualValue) { return GetCount(actualValue) == ExpectedValue; } diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs new file mode 100644 index 0000000000..9c3965cbdb --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs @@ -0,0 +1,23 @@ +namespace TUnit.Assertions.AssertConditions.Combiners; + +public class AssertConditionAnd : BaseAssertCondition +{ + private readonly BaseAssertCondition _condition1; + private readonly BaseAssertCondition _condition2; + + public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) + { + _condition1 = condition1; + _condition2 = condition2; + } + + public override string DefaultMessage => + !_condition1.Passes(ActualValue) ? _condition1.DefaultMessage : + !_condition2.Passes(ActualValue) ? _condition2.DefaultMessage : string.Empty; + + protected internal override bool Passes(TActual actualValue) + { + ActualValue = actualValue; + return _condition1.Passes(actualValue) && _condition2.Passes(actualValue); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs new file mode 100644 index 0000000000..76a4f5b89a --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions.Combiners; + +public class AssertConditionOr : BaseAssertCondition +{ + private readonly BaseAssertCondition _condition1; + private readonly BaseAssertCondition _condition2; + + public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) + { + _condition1 = condition1; + _condition2 = condition2; + } + + public override string DefaultMessage => $"{_condition1.DefaultMessage} & {_condition2.DefaultMessage}"; + + protected internal override bool Passes(TActual actualValue) + { + ActualValue = actualValue; + return _condition1.Passes(actualValue) || _condition2.Passes(actualValue); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs new file mode 100644 index 0000000000..3d0f232544 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs @@ -0,0 +1,26 @@ +using TUnit.Assertions.AssertConditions.Throws; + +namespace TUnit.Assertions.AssertConditions.Combiners; + +public class DelegateAssertConditionAnd : DelegateAssertCondition +{ + private readonly DelegateAssertCondition _condition1; + private readonly DelegateAssertCondition _condition2; + + public DelegateAssertConditionAnd(DelegateAssertCondition condition1, DelegateAssertCondition condition2) + { + _condition1 = condition1; + _condition2 = condition2; + } + + public override string DefaultMessage => + !_condition1.Passes(ActualValue, Exception) ? _condition1.DefaultMessage : + !_condition2.Passes(ActualValue, Exception) ? _condition2.DefaultMessage : string.Empty; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + ActualValue = actualValue; + Exception = exception; + return _condition1.Passes(actualValue, exception) && _condition2.Passes(actualValue, exception); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs new file mode 100644 index 0000000000..8d095a97ae --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs @@ -0,0 +1,24 @@ +using TUnit.Assertions.AssertConditions.Throws; + +namespace TUnit.Assertions.AssertConditions.Combiners; + +public class DelegateAssertConditionOr : DelegateAssertCondition +{ + private readonly DelegateAssertCondition _condition1; + private readonly DelegateAssertCondition _condition2; + + public DelegateAssertConditionOr(DelegateAssertCondition condition1, DelegateAssertCondition condition2) + { + _condition1 = condition1; + _condition2 = condition2; + } + + public override string DefaultMessage => $"{_condition1.DefaultMessage} & {_condition2.DefaultMessage}"; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + ActualValue = actualValue; + Exception = exception; + return _condition1.Passes(actualValue, exception) || _condition2.Passes(actualValue, exception); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs index 3d4c2e0cca..7e81272eda 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs @@ -2,13 +2,10 @@ public class HasInstance { - private readonly IReadOnlyCollection> _assertConditions; - - public HasInstance(IReadOnlyCollection> assertConditions) + public HasInstance(AssertCondition otherAssertConditions) { - _assertConditions = assertConditions; - And = new And(assertConditions); - Or = new Or(assertConditions); + And = new And(otherAssertConditions); + Or = new Or(otherAssertConditions); } public And And { get; } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs index e5a65da294..737da0b76b 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs @@ -6,21 +6,21 @@ public partial class Is { public static AssertCondition EqualTo(TExpected expected) { - return new EqualsAssertCondition([], null, expected); + return new EqualsAssertCondition(expected); } - internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, TExpected expected) + internal static AssertCondition EqualTo(TExpected expected) { - return new EqualsAssertCondition(nestedConditions, @operator, expected); + return new EqualsAssertCondition(expected); } public static AssertCondition SameReference(T expected) { - return new SameReferenceAssertCondition([], null, expected); + return new SameReferenceAssertCondition(expected); } internal static AssertCondition SameReference(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, T expected) { - return new SameReferenceAssertCondition(nestedConditions, @operator, expected); + return new SameReferenceAssertCondition(expected); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs index 5063489fc2..90efa865d3 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs @@ -6,37 +6,37 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { - public static AssertCondition Zero => new EqualsAssertCondition([], null, 0); + public static AssertCondition Zero => new EqualsAssertCondition(0); internal static AssertCondition ZeroInternal(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator) - => new EqualsAssertCondition(nestedConditions, @operator, 0); + => new EqualsAssertCondition(0); public static AssertCondition GreaterThan(T expected) where T : INumber { - return new GreaterThanAssertCondition([], null, expected); + return new GreaterThanAssertCondition(expected); } public static AssertCondition GreaterThanOrEqualTo(T expected) where T : INumber { - return new GreaterThanOrEqualToAssertCondition([], null, expected); + return new GreaterThanOrEqualToAssertCondition(expected); } public static AssertCondition LessThan(T expected) where T : INumber { - return new GreaterThanAssertCondition([], null, expected); + return new GreaterThanAssertCondition(expected); } public static AssertCondition LessThanOrEqualTo(T expected) where T : INumber { - return new LessThanOrEqualToAssertCondition([], null, expected); + return new LessThanOrEqualToAssertCondition(expected); } public static AssertCondition Even() where T : INumber, IModulusOperators { - return new IsEvenAssertCondition([], null, default); + return new IsEvenAssertCondition(); } public static AssertCondition Odd() where T : INumber, IModulusOperators { - return new IsOddAssertCondition([], null, default); + return new IsOddAssertCondition(); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index 2f091b0d85..acc52ca20d 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -11,7 +11,7 @@ public static AssertCondition EqualTo(string expected) internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected) { - return new StringEqualsAssertCondition(nestedConditions, @operator, expected, StringComparison.Ordinal); + return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); } public static AssertCondition EqualTo(string expected, StringComparison stringComparison) @@ -21,6 +21,6 @@ public static AssertCondition EqualTo(string expected, StringCom internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected, StringComparison stringComparison) { - return new StringEqualsAssertCondition(nestedConditions, @operator, expected, stringComparison); + return new StringEqualsAssertCondition(expected, stringComparison); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs index b3b284ee96..2f1b7f1435 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs @@ -4,5 +4,5 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static class Throws { - public static ThrowsNothingAssertCondition Nothing => new([]); + public static ThrowsNothingAssertCondition Nothing => new(); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index ec76693a45..db2baf40e6 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,13 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class EqualsAssertCondition( - IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, - TExpected expected) - : AssertCondition(nestedConditions, @operator, expected) +public class EqualsAssertCondition(TExpected expected) + : AssertCondition(expected) { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return Equals(actualValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index ba894c2249..f0406d6863 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -3,13 +3,13 @@ public class SameReferenceAssertCondition : AssertCondition { - public SameReferenceAssertCondition(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, TExpected expected) : base(nestedConditions, @operator, expected) + public SameReferenceAssertCondition(TExpected expected) : base(expected) { } public override string DefaultMessage => "The two objects are different references."; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return ReferenceEquals(actualValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index 3144235371..fab8d2220d 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -6,13 +6,13 @@ public class GreaterThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public GreaterThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public GreaterThanAssertCondition(TExpected? expected) : base(expected) { } public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue > ExpectedValue!; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 433d15089a..4fde0c7a40 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -6,13 +6,13 @@ public class GreaterThanOrEqualToAssertCondition : AssertCon where TExpected : INumber where TActual : INumber, TExpected { - public GreaterThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public GreaterThanOrEqualToAssertCondition(TExpected? expected) : base(expected) { } public override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue >= ExpectedValue!; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index b3227903b0..9d6a87d2bd 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -5,13 +5,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsEvenAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsEvenAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TActual? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public IsEvenAssertCondition() : base(default) { } public override string DefaultMessage => $"{ActualValue} is not even"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue % 2 == 0; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index 4e75bd4610..09651a967d 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -5,13 +5,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsOddAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsOddAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TActual? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public IsOddAssertCondition() : base(default) { } public override string DefaultMessage => $"{ActualValue} is not odd"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue % 2 != 0; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index 00db21ded2..b714f4e746 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -6,13 +6,13 @@ public class LessThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public LessThanAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public LessThanAssertCondition(TExpected? expected) : base(expected) { } public override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue < ExpectedValue!; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index e999dd86bf..3ef0358c46 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -6,13 +6,13 @@ public class LessThanOrEqualToAssertCondition : AssertCondit where TExpected : INumber where TActual : INumber, TExpected { - public LessThanOrEqualToAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TExpected? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public LessThanOrEqualToAssertCondition(TExpected? expected) : base(expected) { } public override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue <= ExpectedValue!; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index b88c9276cf..6d40e27fc0 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -5,13 +5,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class ZeroAssertCondition : AssertCondition where TActual : INumber, IEqualityOperators { - public ZeroAssertCondition(IReadOnlyCollection> nestedAssertConditions, NestedConditionsOperator? nestedConditionsOperator, TActual? expected) : base(nestedAssertConditions, nestedConditionsOperator, expected) + public ZeroAssertCondition(TActual? expected) : base(expected) { } public override string DefaultMessage => $"{ActualValue} is not equal to 0"; - protected override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual actualValue) { return actualValue == TActual.Zero; } diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Or.cs index ed176f987d..639151fdbb 100644 --- a/TUnit.Assertions/AssertConditions/Or.cs +++ b/TUnit.Assertions/AssertConditions/Or.cs @@ -1,18 +1,19 @@ +using TUnit.Assertions.AssertConditions.Combiners; using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Assertions.AssertConditions; public class Or { - private readonly IReadOnlyCollection> _assertConditions; + private readonly BaseAssertCondition _otherAssertCondition; - public Or(IReadOnlyCollection> assertConditions) + public Or(BaseAssertCondition otherAssertCondition) { - _assertConditions = assertConditions; + _otherAssertCondition = otherAssertCondition; } - public AssertCondition EqualTo(TExpected expected) + public AssertConditionOr EqualTo(TExpected expected) { - return Is.EqualTo(_assertConditions, NestedConditionsOperator.Or, expected); + return new AssertConditionOr(_otherAssertCondition, Is.EqualTo(expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index f5225bb11f..3d1fa415bb 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -3,17 +3,13 @@ public class StringEqualsAssertCondition : AssertCondition { private readonly StringComparison _stringComparison; - - public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : this([], null, expected, stringComparison) - { - } - public StringEqualsAssertCondition(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected, StringComparison stringComparison) : base(nestedConditions, @operator, expected) + public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : base(expected) { _stringComparison = stringComparison; } - protected override bool Passes(string actualValue) + protected internal override bool Passes(string actualValue) { return string.Equals(actualValue, ExpectedValue, _stringComparison); } diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs index e1cdec42f6..92ffe16020 100644 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs @@ -2,17 +2,10 @@ public abstract class DelegateAssertCondition { - internal readonly IReadOnlyCollection> NestedAssertConditions; - - internal DelegateAssertCondition(IReadOnlyCollection> nestedAssertConditions) - { - NestedAssertConditions = [..nestedAssertConditions, this]; - } - private Func? MessageFactory { get; set; } - protected Exception? Exception { get; private set; } - protected T? ActualValue { get; private set; } = default!; + protected Exception? Exception { get; set; } + protected T? ActualValue { get; set; } = default!; public bool Assert(DelegateInvocationResult delegateInvocationResult) { @@ -24,7 +17,7 @@ public bool Assert(DelegateInvocationResult delegateInvocationResult) public abstract string DefaultMessage { get; } - protected abstract bool Passes(T? actualValue, Exception? exception); + protected internal abstract bool Passes(T? actualValue, Exception? exception); public string Message => MessageFactory?.Invoke(ActualValue, Exception) ?? DefaultMessage; @@ -37,13 +30,6 @@ public DelegateAssertCondition WithMessage(Func messa public abstract class DelegateAssertCondition { - internal readonly IReadOnlyCollection NestedAssertConditions; - - internal DelegateAssertCondition(IReadOnlyCollection nestedAssertConditions) - { - NestedAssertConditions = [..nestedAssertConditions, this]; - } - private Func? MessageFactory { get; set; } protected Exception? Exception { get; private set; } @@ -57,7 +43,7 @@ public bool Assert(Exception? exception) public abstract string DefaultMessage { get; } - protected abstract bool Passes(Exception? exception); + protected internal abstract bool Passes(Exception? exception); public string Message => MessageFactory?.Invoke(Exception) ?? DefaultMessage; diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index ae6d666049..1631943dfc 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -2,15 +2,11 @@ public class ThrowsNothingAssertCondition : DelegateAssertCondition { - public ThrowsNothingAssertCondition(IReadOnlyCollection nestedAssertConditions) : base(nestedAssertConditions) - { - } - private Exception? _exception; public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; - protected override bool Passes(Exception? exception) + protected internal override bool Passes(Exception? exception) { _exception = exception; return exception == null; diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index 97b7c882fc..8a1e9ac720 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -7,4 +7,8 @@ latest + + + + diff --git a/TUnit/GlobalUsings.cs b/TUnit/GlobalUsings.cs new file mode 100644 index 0000000000..25326f38cc --- /dev/null +++ b/TUnit/GlobalUsings.cs @@ -0,0 +1,4 @@ +global using TUnit.Assertions; +global using TUnit.Assertions.AssertConditions.ConditionEntries.Static; +global using TUnit.Core.Attributes; +global using Assert = TUnit.Assertions.Assert; \ No newline at end of file From 3e9e465692b3ed0d6ca80361f415394ba37e1511 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:04:21 +0000 Subject: [PATCH 054/124] Rework --- TUnit.Assertions/Assert.cs | 19 ++++++++++++++++++- .../AssertConditions/AssertCondition.cs | 12 ++---------- .../AssertConditions/BaseAssertCondition.cs | 8 ++++++++ .../Combiners/AssertConditionAnd.cs | 5 +++-- .../Combiners/AssertConditionOr.cs | 4 ++-- .../Combiners/DelegateAssertConditionAnd.cs | 2 -- .../Combiners/DelegateAssertConditionOr.cs | 2 -- 7 files changed, 33 insertions(+), 19 deletions(-) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 450840ed73..1c8e0e77cc 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,4 +1,5 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Combiners; using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Exceptions; using TUnit.Assertions.Extensions; @@ -7,7 +8,7 @@ namespace TUnit.Assertions; public static class Assert { - public static void That(T value, AssertCondition assertCondition) + public static void That(TActual value, AssertCondition assertCondition) { if (!assertCondition.Assert(value)) { @@ -15,6 +16,22 @@ public static void That(T value, AssertCondition assertCondition) } } + public static void That(TActual value, AssertConditionOr orCondition) + { + if (!orCondition.Assert(value)) + { + throw new AssertionException(orCondition.Message); + } + } + + public static void That(TActual value, AssertConditionAnd andCondition) + { + if (!andCondition.Assert(value)) + { + throw new AssertionException(andCondition.Message); + } + } + public static Exception? That(Action value, DelegateAssertCondition assertCondition) { var exception = value.InvokeAndGetException(); diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index e50181ac79..ff66cec766 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,6 +1,4 @@ -using TUnit.Assertions.AssertConditions.Combiners; - -namespace TUnit.Assertions.AssertConditions; +namespace TUnit.Assertions.AssertConditions; public abstract class AssertCondition : BaseAssertCondition { @@ -11,15 +9,9 @@ internal AssertCondition(TExpected? expected) ExpectedValue = expected; } - public bool Assert(TActual actualValue) - { - ActualValue = actualValue; - return Passes(actualValue); - } - private Func? MessageFactory { get; set; } - public string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; + public override string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; public AssertCondition WithMessage(Func messageFactory) { diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index 487b87d8d3..4b1bce7c0d 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -10,7 +10,15 @@ internal BaseAssertCondition() protected TActual ActualValue { get; set; } = default!; + public abstract string Message { get; } + public abstract string DefaultMessage { get; } + + public bool Assert(TActual actualValue) + { + ActualValue = actualValue; + return Passes(actualValue); + } protected internal abstract bool Passes(TActual actualValue); diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs index 9c3965cbdb..70c048837a 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs @@ -11,13 +11,14 @@ public AssertConditionAnd(BaseAssertCondition condition1, Ba _condition2 = condition2; } - public override string DefaultMessage => + public override string Message => !_condition1.Passes(ActualValue) ? _condition1.DefaultMessage : !_condition2.Passes(ActualValue) ? _condition2.DefaultMessage : string.Empty; + public override string DefaultMessage => string.Empty; + protected internal override bool Passes(TActual actualValue) { - ActualValue = actualValue; return _condition1.Passes(actualValue) && _condition2.Passes(actualValue); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs index 76a4f5b89a..2f3064822d 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs @@ -11,11 +11,11 @@ public AssertConditionOr(BaseAssertCondition condition1, Bas _condition2 = condition2; } - public override string DefaultMessage => $"{_condition1.DefaultMessage} & {_condition2.DefaultMessage}"; + public override string Message => $"{_condition1.DefaultMessage} & {_condition2.DefaultMessage}"; + public override string DefaultMessage => string.Empty; protected internal override bool Passes(TActual actualValue) { - ActualValue = actualValue; return _condition1.Passes(actualValue) || _condition2.Passes(actualValue); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs index 3d0f232544..65d38d77d0 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs @@ -19,8 +19,6 @@ public DelegateAssertConditionAnd(DelegateAssertCondition condition1, D protected internal override bool Passes(TActual? actualValue, Exception? exception) { - ActualValue = actualValue; - Exception = exception; return _condition1.Passes(actualValue, exception) && _condition2.Passes(actualValue, exception); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs index 8d095a97ae..8beecaf7f2 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs @@ -17,8 +17,6 @@ public DelegateAssertConditionOr(DelegateAssertCondition condition1, De protected internal override bool Passes(TActual? actualValue, Exception? exception) { - ActualValue = actualValue; - Exception = exception; return _condition1.Passes(actualValue, exception) || _condition2.Passes(actualValue, exception); } } \ No newline at end of file From 9b1ac05d95f6e1c83a3ff722c33c6662fc316965 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:04:51 +0000 Subject: [PATCH 055/124] Analyzers project --- .../TUnit.Analyzers.Sample/Examples.cs | 24 ++++ .../TUnit.Analyzers.Sample/Spaceship.cs | 12 ++ .../TUnit.Analyzers.Sample.csproj | 14 ++ .../SampleCodeFixProviderTests.cs | 31 +++++ .../SampleSemanticAnalyzerTests.cs | 35 +++++ .../SampleSyntaxAnalyzerTests.cs | 25 ++++ .../TUnit.Analyzers.Tests.csproj | 25 ++++ .../AnalyzerReleases.Shipped.md | 8 ++ .../AnalyzerReleases.Unshipped.md | 4 + .../Properties/launchSettings.json | 9 ++ TUnit.Analyzers/TUnit.Analyzers/Readme.md | 29 ++++ .../TUnit.Analyzers/Resources.Designer.cs | 125 ++++++++++++++++++ .../TUnit.Analyzers/Resources.resx | 42 ++++++ .../TUnit.Analyzers/SampleCodeFixProvider.cs | 86 ++++++++++++ .../TUnit.Analyzers/SampleSemanticAnalyzer.cs | 107 +++++++++++++++ .../TUnit.Analyzers/SampleSyntaxAnalyzer.cs | 80 +++++++++++ .../TUnit.Analyzers/TUnit.Analyzers.csproj | 40 ++++++ 17 files changed, 696 insertions(+) create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Sample/Spaceship.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj create mode 100644 TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md create mode 100644 TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Unshipped.md create mode 100644 TUnit.Analyzers/TUnit.Analyzers/Properties/launchSettings.json create mode 100644 TUnit.Analyzers/TUnit.Analyzers/Readme.md create mode 100644 TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers/Resources.resx create mode 100644 TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers/TUnit.Analyzers.csproj diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs new file mode 100644 index 0000000000..0ba679908d --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs @@ -0,0 +1,24 @@ +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global + +using TUnit.Assertions; +using TUnit.Assertions.AssertConditions.ConditionEntries.Static; + +namespace TUnit.Analyzers.Sample; + +// If you don't see warnings, build the Analyzers Project. + +public class Examples +{ + public class CommonClass // Try to apply quick fix using the IDE. + { + } + + public void ToStars() + { + Assert.That("1", Is.EqualTo("1").And.EqualTo("1").Or.EqualTo("2")); + var spaceship = new Spaceship(); + spaceship.SetSpeed(300000000); // Invalid value, it should be highlighted. + spaceship.SetSpeed(42); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Spaceship.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Spaceship.cs new file mode 100644 index 0000000000..432f4431ba --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Spaceship.cs @@ -0,0 +1,12 @@ +using System; + +namespace TUnit.Analyzers.Sample; + +public class Spaceship +{ + public void SetSpeed(long speed) + { + if (speed > 299_792_458) + throw new ArgumentOutOfRangeException(nameof(speed)); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj b/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj new file mode 100644 index 0000000000..b360dc4908 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + + + + + + + + diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs new file mode 100644 index 0000000000..084e5a10ce --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Xunit; +using Verifier = + Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier; + +namespace TUnit.Analyzers.Tests; + +public class SampleCodeFixProviderTests +{ + [Fact] + public async Task ClassWithMyCompanyTitle_ReplaceWithCommonKeyword() + { + const string text = @" +public class MyCompanyClass +{ +} +"; + + const string newText = @" +public class CommonClass +{ +} +"; + + var expected = Verifier.Diagnostic() + .WithLocation(2, 14) + .WithArguments("MyCompanyClass"); + await Verifier.VerifyCodeFixAsync(text, expected, newText).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs new file mode 100644 index 0000000000..b573aa2dbf --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Xunit; +using Verifier = + Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< + TUnit.Analyzers.SampleSemanticAnalyzer>; + +namespace TUnit.Analyzers.Tests; + +public class SampleSemanticAnalyzerTests +{ + [Fact] + public async Task SetSpeedHugeSpeedSpecified_AlertDiagnostic() + { + const string text = @" +public class Program +{ + public void Main() + { + var spaceship = new Spaceship(); + spaceship.SetSpeed(300000000); + } +} + +public class Spaceship +{ + public void SetSpeed(long speed) {} +} +"; + + var expected = Verifier.Diagnostic() + .WithLocation(7, 28) + .WithArguments("300000000"); + await Verifier.VerifyAnalyzerAsync(text, expected); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs new file mode 100644 index 0000000000..c7e5689622 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Xunit; +using Verifier = + Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< + TUnit.Analyzers.SampleSyntaxAnalyzer>; + +namespace TUnit.Analyzers.Tests; + +public class SampleSyntaxAnalyzerTests +{ + [Fact] + public async Task ClassWithMyCompanyTitle_AlertDiagnostic() + { + const string text = @" +public class MyCompanyClass +{ +} +"; + + var expected = Verifier.Diagnostic() + .WithLocation(2, 14) + .WithArguments("MyCompanyClass"); + await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj b/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj new file mode 100644 index 0000000000..06e795c2e0 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000000..001bc4c7ab --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,8 @@ +## Release 1.0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------------------------------------------------ +TUnit0001 | Naming | Warning | Type names should not contain the company name. +TUnit0002 | Usage | Warning | The speed must be lower than the Speed of Light. \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Unshipped.md b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000000..062067f471 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Unshipped.md @@ -0,0 +1,4 @@ +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/Properties/launchSettings.json b/TUnit.Analyzers/TUnit.Analyzers/Properties/launchSettings.json new file mode 100644 index 0000000000..5ff5a737e1 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynAnalyzers": { + "commandName": "DebugRoslynComponent", + "targetProject": "../TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj" + } + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/Readme.md b/TUnit.Analyzers/TUnit.Analyzers/Readme.md new file mode 100644 index 0000000000..ea5de78756 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/Readme.md @@ -0,0 +1,29 @@ +# Roslyn Analyzers Sample + +A set of three sample projects that includes Roslyn analyzers with code fix providers. Enjoy this template to learn from and modify analyzers for your own needs. + +## Content +### TUnit.Analyzers +A .NET Standard project with implementations of sample analyzers and code fix providers. +**You must build this project to see the results (warnings) in the IDE.** + +- [SampleSemanticAnalyzer.cs](SampleSemanticAnalyzer.cs): An analyzer that reports invalid values used for the `speed` parameter of the `SetSpeed` function. +- [SampleSyntaxAnalyzer.cs](SampleSyntaxAnalyzer.cs): An analyzer that reports the company name used in class definitions. +- [SampleCodeFixProvider.cs](SampleCodeFixProvider.cs): A code fix that renames classes with company name in their definition. The fix is linked to [SampleSyntaxAnalyzer.cs](SampleSyntaxAnalyzer.cs). + +### TUnit.Analyzers.Sample +A project that references the sample analyzers. Note the parameters of `ProjectReference` in [TUnit.Analyzers.Sample.csproj](../TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj), they make sure that the project is referenced as a set of analyzers. + +### TUnit.Analyzers.Tests +Unit tests for the sample analyzers and code fix provider. The easiest way to develop language-related features is to start with unit tests. + +## How To? +### How to debug? +- Use the [launchSettings.json](Properties/launchSettings.json) profile. +- Debug tests. + +### How can I determine which syntax nodes I should expect? +Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/). + +### Learn more about wiring analyzers +The complete set of information is available at [roslyn github repo wiki](https://github.com/dotnet/roslyn/blob/main/docs/wiki/README.md). \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs new file mode 100644 index 0000000000..34da34bc84 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs @@ -0,0 +1,125 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TUnit.Analyzers { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TUnit.Analyzers.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Replace '{0}' with '{1}'. + /// + internal static string TUnit0001CodeFixTitle { + get { + return ResourceManager.GetString("TUnit0001CodeFixTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type names should not contain the company name.. + /// + internal static string TUnit0001Description { + get { + return ResourceManager.GetString("TUnit0001Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type name '{0}' contains the company name. + /// + internal static string TUnit0001MessageFormat { + get { + return ResourceManager.GetString("TUnit0001MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type name contains the company name. + /// + internal static string TUnit0001Title { + get { + return ResourceManager.GetString("TUnit0001Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The speed must be lower than the Speed of Light.. + /// + internal static string TUnit0002Description { + get { + return ResourceManager.GetString("TUnit0002Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified speed '{0}' must be lower than the Speed of Light. + /// + internal static string TUnit0002MessageFormat { + get { + return ResourceManager.GetString("TUnit0002MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The impossible speed. + /// + internal static string TUnit0002Title { + get { + return ResourceManager.GetString("TUnit0002Title", resourceCulture); + } + } + } +} diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx new file mode 100644 index 0000000000..1fccd8a88b --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx @@ -0,0 +1,42 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Type names should not contain the company name.An optional longer localizable description of the diagnostic. + + + Type name '{0}' contains the company nameThe format-able message the diagnostic displays. + + + Type name contains the company nameThe title of the diagnostic. + + + Replace '{0}' with '{1}'The title of the code fix. + + + The speed must be lower than the Speed of Light.An optional longer localizable description of the diagnostic. + + + The specified speed '{0}' must be lower than the Speed of LightThe format-able message the diagnostic displays. + + + The impossible speedThe title of the diagnostic. + + \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs new file mode 100644 index 0000000000..8055b1c022 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs @@ -0,0 +1,86 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Rename; + +namespace TUnit.Analyzers; + +/// +/// A sample code fix provider that renames classes with the company name in their definition. +/// All code fixes must be linked to specific analyzers. +/// +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SampleCodeFixProvider)), Shared] +public class SampleCodeFixProvider : CodeFixProvider +{ + private const string CommonName = "Common"; + + // Specify the diagnostic IDs of analyzers that are expected to be linked. + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(SampleSyntaxAnalyzer.DiagnosticId); + + // If you don't need the 'fix all' behaviour, return null. + public override FixAllProvider? GetFixAllProvider() => null; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + // We link only one diagnostic and assume there is only one diagnostic in the context. + var diagnostic = context.Diagnostics.Single(); + + // 'SourceSpan' of 'Location' is the highlighted area. We're going to use this area to find the 'SyntaxNode' to rename. + var diagnosticSpan = diagnostic.Location.SourceSpan; + + // Get the root of Syntax Tree that contains the highlighted diagnostic. + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // Find SyntaxNode corresponding to the diagnostic. + var diagnosticNode = root?.FindNode(diagnosticSpan); + + // To get the required metadata, we should match the Node to the specific type: 'ClassDeclarationSyntax'. + if (diagnosticNode is not ClassDeclarationSyntax declaration) + return; + + // Register a code action that will invoke the fix. + context.RegisterCodeFix( + CodeAction.Create( + title: string.Format(Resources.TUnit0001CodeFixTitle, SampleSyntaxAnalyzer.CompanyName, CommonName), + createChangedSolution: c => SanitizeCompanyNameAsync(context.Document, declaration, c), + equivalenceKey: nameof(Resources.TUnit0001CodeFixTitle)), + diagnostic); + } + + /// + /// Executed on the quick fix action raised by the user. + /// + /// Affected source file. + /// Highlighted class declaration Syntax Node. + /// Any fix is cancellable by the user, so we should support the cancellation token. + /// Clone of the solution with updates: renamed class. + private async Task SanitizeCompanyNameAsync(Document document, + ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken) + { + // 'Identifier' means the token of the node. Compute the new name based on the text of the token of the node. + var newName = classDeclarationSyntax.Identifier.Text.Replace(SampleSyntaxAnalyzer.CompanyName, CommonName); + + // To make a refactoring, we need to get compiled code metadata: the Semantic Model. + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // Attempt to find the 'TypeSymbol' (compile time metadata of the class) based on highlighted Class Declaration Syntax. + var typeSymbol = semanticModel?.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken); + if (typeSymbol == null) return document.Project.Solution; + + // Produce a new solution that has all references to the class being renamed, including the declaration. + var newSolution = await Renamer + .RenameSymbolAsync(document.Project.Solution, typeSymbol, new SymbolRenameOptions(), newName, + cancellationToken) + .ConfigureAwait(false); + + // Return the new solution with the updated type name. + return newSolution; + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs new file mode 100644 index 0000000000..076f695cb0 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs @@ -0,0 +1,107 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace TUnit.Analyzers; + +/// +/// A sample analyzer that reports invalid values being used for the 'speed' parameter of the 'SetSpeed' function. +/// To make sure that we analyze the method of the specific class, we use semantic analysis instead of the syntax tree, so this analyzer will not work if the project is not compilable. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class SampleSemanticAnalyzer : DiagnosticAnalyzer +{ + private const string CommonApiClassName = "Spaceship"; + private const string CommonApiMethodName = "SetSpeed"; + + // Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234. + private const string DiagnosticId = "TUnit0002"; + + // Feel free to use raw strings if you don't need localization. + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.TUnit0002Title), + Resources.ResourceManager, typeof(Resources)); + + // The message that will be displayed to the user. + private static readonly LocalizableString MessageFormat = + new LocalizableResourceString(nameof(Resources.TUnit0002MessageFormat), Resources.ResourceManager, + typeof(Resources)); + + private static readonly LocalizableString Description = + new LocalizableResourceString(nameof(Resources.TUnit0002Description), Resources.ResourceManager, + typeof(Resources)); + + // The category of the diagnostic (Design, Naming etc.). + private const string Category = "Usage"; + + private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, + DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + + // Keep in mind: you have to list your rules here. + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + // You must call this method to avoid analyzing generated code. + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + // You must call this method to enable the Concurrent Execution. + context.EnableConcurrentExecution(); + + // Subscribe to semantic (compile time) action invocation, e.g. method invocation. + context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation); + + // Check other 'context.Register...' methods that might be helpful for your purposes. + } + + /// + /// Executed on the completion of the semantic analysis associated with the Invocation operation. + /// + /// Operation context. + private void AnalyzeOperation(OperationAnalysisContext context) + { + // The Roslyn architecture is based on inheritance. + // To get the required metadata, we should match the 'Operation' and 'Syntax' objects to the particular types, + // which are based on the 'OperationKind' parameter specified in the 'Register...' method. + if (context.Operation is not IInvocationOperation invocationOperation || + context.Operation.Syntax is not InvocationExpressionSyntax invocationSyntax) + return; + + var methodSymbol = invocationOperation.TargetMethod; + + // Check whether the method name is 'SetSpeed' and it is a member of the 'Spaceship' class. + if (methodSymbol.MethodKind != MethodKind.Ordinary || + methodSymbol.ReceiverType?.Name != CommonApiClassName || + methodSymbol.Name != CommonApiMethodName + ) + return; + + // Count validation is enough in most cases. Keep analyzers as simple as possible. + if (invocationSyntax.ArgumentList.Arguments.Count != 1) + return; + + // Traverse through the syntax tree, starting with the particular 'InvocationSyntax' to the desired node. + var argumentSyntax = invocationSyntax.ArgumentList.Arguments.Single().Expression; + + // The 'ToString' method of 'Syntax' classes returns the corresponding part of the source code. + var argument = argumentSyntax.ToString(); + + if (!int.TryParse(argument, out var actualSpeed)) + return; + + if (actualSpeed <= 299_792_458) + return; + + var diagnostic = Diagnostic.Create(Rule, + // The highlighted area in the analyzed source code. Keep it as specific as possible. + argumentSyntax.GetLocation(), + // The value is passed to the 'MessageFormat' argument of your rule. + actualSpeed); + + // Reporting a diagnostic is the primary outcome of analyzers. + context.ReportDiagnostic(diagnostic); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs new file mode 100644 index 0000000000..cd77d0edba --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace TUnit.Analyzers; + +/// +/// A sample analyzer that reports the company name being used in class declarations. +/// Traverses through the Syntax Tree and checks the name (identifier) of each class node. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class SampleSyntaxAnalyzer : DiagnosticAnalyzer +{ + public const string CompanyName = "MyCompany"; + + // Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234. + public const string DiagnosticId = "TUnit0001"; + + // Feel free to use raw strings if you don't need localization. + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.TUnit0001Title), + Resources.ResourceManager, typeof(Resources)); + + // The message that will be displayed to the user. + private static readonly LocalizableString MessageFormat = + new LocalizableResourceString(nameof(Resources.TUnit0001MessageFormat), Resources.ResourceManager, + typeof(Resources)); + + private static readonly LocalizableString Description = + new LocalizableResourceString(nameof(Resources.TUnit0001Description), Resources.ResourceManager, + typeof(Resources)); + + // The category of the diagnostic (Design, Naming etc.). + private const string Category = "Usage"; + + private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, + DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + + // Keep in mind: you have to list your rules here. + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + // You must call this method to avoid analyzing generated code. + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + // You must call this method to enable the Concurrent Execution. + context.EnableConcurrentExecution(); + + // Subscribe to the Syntax Node with the appropriate 'SyntaxKind' (ClassDeclaration) action. + // To figure out which Syntax Nodes you should choose, consider installing the Roslyn syntax tree viewer plugin Rossynt: https://plugins.jetbrains.com/plugin/16902-rossynt/ + context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.SimpleMemberAccessExpression); + + // Check other 'context.Register...' methods that might be helpful for your purposes. + } + + /// + /// Executed for each Syntax Node with 'SyntaxKind' is 'ClassDeclaration'. + /// + /// Operation context. + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + // The Roslyn architecture is based on inheritance. + // To get the required metadata, we should match the 'Node' object to the particular type: 'ClassDeclarationSyntax'. + if (context.Node is not MemberAccessExpressionSyntax memberAccessExpressionSyntax) + return; + + if (memberAccessExpressionSyntax.Name.Identifier.Value is not "And" and not "Or") + { + return; + } + + var propertyName = memberAccessExpressionSyntax.Name.Identifier.Value; + + Console.WriteLine(propertyName); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/TUnit.Analyzers.csproj b/TUnit.Analyzers/TUnit.Analyzers/TUnit.Analyzers.csproj new file mode 100644 index 0000000000..7b71b51bbc --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/TUnit.Analyzers.csproj @@ -0,0 +1,40 @@ + + + + netstandard2.0 + false + enable + latest + + true + true + + TUnit.Analyzers + TUnit.Analyzers + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + True + True + Resources.resx + + + + From 0b5f50b254c6534fb4a3387741e5c36ca7c7bb05 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:07:52 +0000 Subject: [PATCH 056/124] Fix message generation --- .../AssertConditions/Combiners/AssertConditionAnd.cs | 2 +- .../AssertConditions/Combiners/AssertConditionOr.cs | 2 +- .../Combiners/DelegateAssertConditionAnd.cs | 2 +- .../Combiners/DelegateAssertConditionOr.cs | 2 +- TUnit.TestProject/Tests.cs | 7 ++----- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs index 70c048837a..3382bc9f86 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs @@ -19,6 +19,6 @@ public AssertConditionAnd(BaseAssertCondition condition1, Ba protected internal override bool Passes(TActual actualValue) { - return _condition1.Passes(actualValue) && _condition2.Passes(actualValue); + return _condition1.Assert(actualValue) && _condition2.Assert(actualValue); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs index 2f3064822d..7ea7ad81a4 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs @@ -16,6 +16,6 @@ public AssertConditionOr(BaseAssertCondition condition1, Bas protected internal override bool Passes(TActual actualValue) { - return _condition1.Passes(actualValue) || _condition2.Passes(actualValue); + return _condition1.Assert(actualValue) || _condition2.Assert(actualValue); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs index 65d38d77d0..af0f1e91df 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs @@ -19,6 +19,6 @@ public DelegateAssertConditionAnd(DelegateAssertCondition condition1, D protected internal override bool Passes(TActual? actualValue, Exception? exception) { - return _condition1.Passes(actualValue, exception) && _condition2.Passes(actualValue, exception); + return _condition1.Assert((actualValue, exception)) && _condition2.Assert((actualValue, exception)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs index 8beecaf7f2..f4e5c31e11 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs @@ -17,6 +17,6 @@ public DelegateAssertConditionOr(DelegateAssertCondition condition1, De protected internal override bool Passes(TActual? actualValue, Exception? exception) { - return _condition1.Passes(actualValue, exception) || _condition2.Passes(actualValue, exception); + return _condition1.Assert((actualValue, exception)) || _condition2.Assert((actualValue, exception)); } } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 0fc291fa4d..03c5e4f43c 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -177,17 +177,14 @@ public async Task Timeout1() [Test] public void String_And_Condition() { - var str = "My string"; + Assert.That("1", Is.EqualTo("1").And.EqualTo("1").Or.EqualTo("2")); - Assert.That(str, Is.EqualTo("My string").And.EqualTo("This")); } [Test] public void String_And_Condition2() { - var str = "My string"; - - Assert.That(str, Is.EqualTo("This").And.EqualTo("My string")); + Assert.That("1", Is.EqualTo("1").And.EqualTo("1").And.EqualTo("2")); } public static int One() => 1; From 3d250245076ba773470d758388b433bfa2401c75 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:42:46 +0000 Subject: [PATCH 057/124] Analyzer --- .../SampleSemanticAnalyzerTests.cs | 35 ------ .../TUnit.Analyzers/DisplayFormats.cs | 19 ++++ .../TUnit.Analyzers/Resources.Designer.cs | 35 +----- .../TUnit.Analyzers/Resources.resx | 15 +-- .../TUnit.Analyzers/SampleSemanticAnalyzer.cs | 107 ------------------ .../TUnit.Analyzers/SampleSyntaxAnalyzer.cs | 33 +++++- TUnit.TestProject/TUnit.TestProject.csproj | 1 + 7 files changed, 62 insertions(+), 183 deletions(-) delete mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers/DisplayFormats.cs delete mode 100644 TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs deleted file mode 100644 index b573aa2dbf..0000000000 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSemanticAnalyzerTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; -using Xunit; -using Verifier = - Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< - TUnit.Analyzers.SampleSemanticAnalyzer>; - -namespace TUnit.Analyzers.Tests; - -public class SampleSemanticAnalyzerTests -{ - [Fact] - public async Task SetSpeedHugeSpeedSpecified_AlertDiagnostic() - { - const string text = @" -public class Program -{ - public void Main() - { - var spaceship = new Spaceship(); - spaceship.SetSpeed(300000000); - } -} - -public class Spaceship -{ - public void SetSpeed(long speed) {} -} -"; - - var expected = Verifier.Diagnostic() - .WithLocation(7, 28) - .WithArguments("300000000"); - await Verifier.VerifyAnalyzerAsync(text, expected); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/DisplayFormats.cs b/TUnit.Analyzers/TUnit.Analyzers/DisplayFormats.cs new file mode 100644 index 0000000000..abab77fa08 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/DisplayFormats.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace TUnit.Analyzers; + +public class DisplayFormats +{ + public static SymbolDisplayFormat FullyQualifiedNonGeneric => new( + SymbolDisplayGlobalNamespaceStyle.Included, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + SymbolDisplayGenericsOptions.None, + SymbolDisplayMemberOptions.IncludeContainingType, + SymbolDisplayDelegateStyle.NameAndSignature, + SymbolDisplayExtensionMethodStyle.Default, + SymbolDisplayParameterOptions.IncludeType, + SymbolDisplayPropertyStyle.NameOnly, + SymbolDisplayLocalOptions.IncludeType, + SymbolDisplayKindOptions.None + ); +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs index 34da34bc84..11706d0f87 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs @@ -60,7 +60,7 @@ internal Resources() { } /// - /// Looks up a localized string similar to Replace '{0}' with '{1}'. + /// Looks up a localized string similar to Don't mix 'Or' & 'And' operators in assertions. /// internal static string TUnit0001CodeFixTitle { get { @@ -69,7 +69,7 @@ internal static string TUnit0001CodeFixTitle { } /// - /// Looks up a localized string similar to Type names should not contain the company name.. + /// Looks up a localized string similar to The speed must be lower than the Speed of Light.. /// internal static string TUnit0001Description { get { @@ -78,7 +78,7 @@ internal static string TUnit0001Description { } /// - /// Looks up a localized string similar to Type name '{0}' contains the company name. + /// Looks up a localized string similar to The specified speed '{0}' must be lower than the Speed of Light. /// internal static string TUnit0001MessageFormat { get { @@ -87,39 +87,12 @@ internal static string TUnit0001MessageFormat { } /// - /// Looks up a localized string similar to Type name contains the company name. + /// Looks up a localized string similar to Don't mix 'Or' & 'And' operators in assertions. /// internal static string TUnit0001Title { get { return ResourceManager.GetString("TUnit0001Title", resourceCulture); } } - - /// - /// Looks up a localized string similar to The speed must be lower than the Speed of Light.. - /// - internal static string TUnit0002Description { - get { - return ResourceManager.GetString("TUnit0002Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified speed '{0}' must be lower than the Speed of Light. - /// - internal static string TUnit0002MessageFormat { - get { - return ResourceManager.GetString("TUnit0002MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The impossible speed. - /// - internal static string TUnit0002Title { - get { - return ResourceManager.GetString("TUnit0002Title", resourceCulture); - } - } } } diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx index 1fccd8a88b..ac1232cd7d 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx @@ -19,24 +19,21 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Type names should not contain the company name.An optional longer localizable description of the diagnostic. + Don't mix 'Or' & 'And' operators in assertionsAn optional longer localizable description of the diagnostic. - Type name '{0}' contains the company nameThe format-able message the diagnostic displays. + Don't mix 'Or' & 'And' operators in assertionsThe format-able message the diagnostic displays. - Type name contains the company nameThe title of the diagnostic. + Don't mix 'Or' & 'And' operators in assertionsThe title of the diagnostic. - Replace '{0}' with '{1}'The title of the code fix. + Don't mix 'Or' & 'And' operators in assertionsThe title of the code fix. - + The speed must be lower than the Speed of Light.An optional longer localizable description of the diagnostic. - + The specified speed '{0}' must be lower than the Speed of LightThe format-able message the diagnostic displays. - - The impossible speedThe title of the diagnostic. - \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs deleted file mode 100644 index 076f695cb0..0000000000 --- a/TUnit.Analyzers/TUnit.Analyzers/SampleSemanticAnalyzer.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Operations; - -namespace TUnit.Analyzers; - -/// -/// A sample analyzer that reports invalid values being used for the 'speed' parameter of the 'SetSpeed' function. -/// To make sure that we analyze the method of the specific class, we use semantic analysis instead of the syntax tree, so this analyzer will not work if the project is not compilable. -/// -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class SampleSemanticAnalyzer : DiagnosticAnalyzer -{ - private const string CommonApiClassName = "Spaceship"; - private const string CommonApiMethodName = "SetSpeed"; - - // Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234. - private const string DiagnosticId = "TUnit0002"; - - // Feel free to use raw strings if you don't need localization. - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.TUnit0002Title), - Resources.ResourceManager, typeof(Resources)); - - // The message that will be displayed to the user. - private static readonly LocalizableString MessageFormat = - new LocalizableResourceString(nameof(Resources.TUnit0002MessageFormat), Resources.ResourceManager, - typeof(Resources)); - - private static readonly LocalizableString Description = - new LocalizableResourceString(nameof(Resources.TUnit0002Description), Resources.ResourceManager, - typeof(Resources)); - - // The category of the diagnostic (Design, Naming etc.). - private const string Category = "Usage"; - - private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, - DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - // Keep in mind: you have to list your rules here. - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) - { - // You must call this method to avoid analyzing generated code. - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - // You must call this method to enable the Concurrent Execution. - context.EnableConcurrentExecution(); - - // Subscribe to semantic (compile time) action invocation, e.g. method invocation. - context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation); - - // Check other 'context.Register...' methods that might be helpful for your purposes. - } - - /// - /// Executed on the completion of the semantic analysis associated with the Invocation operation. - /// - /// Operation context. - private void AnalyzeOperation(OperationAnalysisContext context) - { - // The Roslyn architecture is based on inheritance. - // To get the required metadata, we should match the 'Operation' and 'Syntax' objects to the particular types, - // which are based on the 'OperationKind' parameter specified in the 'Register...' method. - if (context.Operation is not IInvocationOperation invocationOperation || - context.Operation.Syntax is not InvocationExpressionSyntax invocationSyntax) - return; - - var methodSymbol = invocationOperation.TargetMethod; - - // Check whether the method name is 'SetSpeed' and it is a member of the 'Spaceship' class. - if (methodSymbol.MethodKind != MethodKind.Ordinary || - methodSymbol.ReceiverType?.Name != CommonApiClassName || - methodSymbol.Name != CommonApiMethodName - ) - return; - - // Count validation is enough in most cases. Keep analyzers as simple as possible. - if (invocationSyntax.ArgumentList.Arguments.Count != 1) - return; - - // Traverse through the syntax tree, starting with the particular 'InvocationSyntax' to the desired node. - var argumentSyntax = invocationSyntax.ArgumentList.Arguments.Single().Expression; - - // The 'ToString' method of 'Syntax' classes returns the corresponding part of the source code. - var argument = argumentSyntax.ToString(); - - if (!int.TryParse(argument, out var actualSpeed)) - return; - - if (actualSpeed <= 299_792_458) - return; - - var diagnostic = Diagnostic.Create(Rule, - // The highlighted area in the analyzed source code. Keep it as specific as possible. - argumentSyntax.GetLocation(), - // The value is passed to the 'MessageFormat' argument of your rule. - actualSpeed); - - // Reporting a diagnostic is the primary outcome of analyzers. - context.ReportDiagnostic(diagnostic); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs index cd77d0edba..2d1c5ff6a2 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -74,7 +76,36 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) } var propertyName = memberAccessExpressionSyntax.Name.Identifier.Value; + + if (!Debugger.IsAttached) + { + // Debugger.Launch(); + } + + if (memberAccessExpressionSyntax.Parent is not MemberAccessExpressionSyntax parentMemberAccessExpressionSyntax) + { + return; + } - Console.WriteLine(propertyName); + var symbolInfo = context.SemanticModel.GetSymbolInfo(parentMemberAccessExpressionSyntax); + + var fullyQualifiedSymbolInformation = symbolInfo.Symbol?.ToDisplayString(DisplayFormats.FullyQualifiedNonGeneric) + ?? string.Empty; + + if (!fullyQualifiedSymbolInformation.Contains("global::TUnit.Assertions")) + { + return; + } + + if(fullyQualifiedSymbolInformation.Contains(".And.") + && fullyQualifiedSymbolInformation.Contains(".Or.")) + { + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, + true, Description), + parentMemberAccessExpressionSyntax.GetLocation()) + ); + } } } \ No newline at end of file diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index ddd1ea0e4e..bfb59c414d 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -19,6 +19,7 @@ + From 8a3686da3af560319f172a0fb0a5f75aa02edd6c Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:13:30 +0000 Subject: [PATCH 058/124] Analyzer working --- ...s.cs => MixAndOrOperatorsAnalyzerTests.cs} | 4 +- .../SampleCodeFixProviderTests.cs | 62 +++---- .../AnalyzerReleases.Shipped.md | 3 +- ...alyzer.cs => MixAndOrOperatorsAnalyzer.cs} | 24 +-- .../TUnit.Analyzers/Resources.Designer.cs | 4 +- .../TUnit.Analyzers/Resources.resx | 8 +- .../TUnit.Analyzers/SampleCodeFixProvider.cs | 172 +++++++++--------- 7 files changed, 132 insertions(+), 145 deletions(-) rename TUnit.Analyzers/TUnit.Analyzers.Tests/{SampleSyntaxAnalyzerTests.cs => MixAndOrOperatorsAnalyzerTests.cs} (84%) rename TUnit.Analyzers/TUnit.Analyzers/{SampleSyntaxAnalyzer.cs => MixAndOrOperatorsAnalyzer.cs} (84%) diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs similarity index 84% rename from TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs rename to TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs index c7e5689622..12b0fe68e9 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleSyntaxAnalyzerTests.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs @@ -2,11 +2,11 @@ using Xunit; using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< - TUnit.Analyzers.SampleSyntaxAnalyzer>; + TUnit.Analyzers.MixAndOrOperatorsAnalyzer>; namespace TUnit.Analyzers.Tests; -public class SampleSyntaxAnalyzerTests +public class MixAndOrOperatorsAnalyzerTests { [Fact] public async Task ClassWithMyCompanyTitle_AlertDiagnostic() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs index 084e5a10ce..1eebd3b369 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/SampleCodeFixProviderTests.cs @@ -1,31 +1,31 @@ -using System.Threading.Tasks; -using Xunit; -using Verifier = - Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier; - -namespace TUnit.Analyzers.Tests; - -public class SampleCodeFixProviderTests -{ - [Fact] - public async Task ClassWithMyCompanyTitle_ReplaceWithCommonKeyword() - { - const string text = @" -public class MyCompanyClass -{ -} -"; - - const string newText = @" -public class CommonClass -{ -} -"; - - var expected = Verifier.Diagnostic() - .WithLocation(2, 14) - .WithArguments("MyCompanyClass"); - await Verifier.VerifyCodeFixAsync(text, expected, newText).ConfigureAwait(false); - } -} \ No newline at end of file +// using System.Threading.Tasks; +// using Xunit; +// using Verifier = +// Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier; +// +// namespace TUnit.Analyzers.Tests; +// +// public class SampleCodeFixProviderTests +// { +// [Fact] +// public async Task ClassWithMyCompanyTitle_ReplaceWithCommonKeyword() +// { +// const string text = @" +// public class MyCompanyClass +// { +// } +// "; +// +// const string newText = @" +// public class CommonClass +// { +// } +// "; +// +// var expected = Verifier.Diagnostic() +// .WithLocation(2, 14) +// .WithArguments("MyCompanyClass"); +// await Verifier.VerifyCodeFixAsync(text, expected, newText).ConfigureAwait(false); +// } +// } \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md index 001bc4c7ab..b554548217 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md +++ b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md @@ -4,5 +4,4 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------------------------------------------------ -TUnit0001 | Naming | Warning | Type names should not contain the company name. -TUnit0002 | Usage | Warning | The speed must be lower than the Speed of Light. \ No newline at end of file +TUnit0001 | Usage | Warning | Don't mix 'Or' & 'And' operators in assertions. diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs similarity index 84% rename from TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs rename to TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs index 2d1c5ff6a2..8bb73ed941 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/SampleSyntaxAnalyzer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs @@ -14,7 +14,7 @@ namespace TUnit.Analyzers; /// Traverses through the Syntax Tree and checks the name (identifier) of each class node. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] -public class SampleSyntaxAnalyzer : DiagnosticAnalyzer +public class MixAndOrOperatorsAnalyzer : DiagnosticAnalyzer { public const string CompanyName = "MyCompany"; @@ -75,36 +75,30 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) return; } - var propertyName = memberAccessExpressionSyntax.Name.Identifier.Value; - - if (!Debugger.IsAttached) - { - // Debugger.Launch(); - } - - if (memberAccessExpressionSyntax.Parent is not MemberAccessExpressionSyntax parentMemberAccessExpressionSyntax) + if (memberAccessExpressionSyntax.Parent?.Parent is not InvocationExpressionSyntax invocationExpressionSyntax) { return; } - var symbolInfo = context.SemanticModel.GetSymbolInfo(parentMemberAccessExpressionSyntax); + var symbolInfo = context.SemanticModel.GetSymbolInfo(invocationExpressionSyntax); var fullyQualifiedSymbolInformation = symbolInfo.Symbol?.ToDisplayString(DisplayFormats.FullyQualifiedNonGeneric) ?? string.Empty; - if (!fullyQualifiedSymbolInformation.Contains("global::TUnit.Assertions")) + if (!fullyQualifiedSymbolInformation.StartsWith("global::TUnit.Assertions")) { return; } - - if(fullyQualifiedSymbolInformation.Contains(".And.") - && fullyQualifiedSymbolInformation.Contains(".Or.")) + + var fullInvocationStatement = invocationExpressionSyntax.ToFullString(); + if(fullInvocationStatement.Contains(".And.") + && fullInvocationStatement.Contains(".Or.")) { context.ReportDiagnostic( Diagnostic.Create( new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true, Description), - parentMemberAccessExpressionSyntax.GetLocation()) + invocationExpressionSyntax.GetLocation()) ); } } diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs index 11706d0f87..788d5a7579 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs @@ -69,7 +69,7 @@ internal static string TUnit0001CodeFixTitle { } /// - /// Looks up a localized string similar to The speed must be lower than the Speed of Light.. + /// Looks up a localized string similar to Don't mix 'Or' & 'And' operators in assertions.. /// internal static string TUnit0001Description { get { @@ -78,7 +78,7 @@ internal static string TUnit0001Description { } /// - /// Looks up a localized string similar to The specified speed '{0}' must be lower than the Speed of Light. + /// Looks up a localized string similar to Don't mix 'Or' & 'And' operators in assertions. /// internal static string TUnit0001MessageFormat { get { diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx index ac1232cd7d..8405b41080 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx @@ -19,7 +19,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Don't mix 'Or' & 'And' operators in assertionsAn optional longer localizable description of the diagnostic. + Don't mix 'Or' & 'And' operators in assertions.An optional longer localizable description of the diagnostic. Don't mix 'Or' & 'And' operators in assertionsThe format-able message the diagnostic displays. @@ -30,10 +30,4 @@ Don't mix 'Or' & 'And' operators in assertionsThe title of the code fix. - - The speed must be lower than the Speed of Light.An optional longer localizable description of the diagnostic. - - - The specified speed '{0}' must be lower than the Speed of LightThe format-able message the diagnostic displays. - \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs index 8055b1c022..d42c450b24 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs @@ -1,86 +1,86 @@ -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; - -namespace TUnit.Analyzers; - -/// -/// A sample code fix provider that renames classes with the company name in their definition. -/// All code fixes must be linked to specific analyzers. -/// -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SampleCodeFixProvider)), Shared] -public class SampleCodeFixProvider : CodeFixProvider -{ - private const string CommonName = "Common"; - - // Specify the diagnostic IDs of analyzers that are expected to be linked. - public sealed override ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create(SampleSyntaxAnalyzer.DiagnosticId); - - // If you don't need the 'fix all' behaviour, return null. - public override FixAllProvider? GetFixAllProvider() => null; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - // We link only one diagnostic and assume there is only one diagnostic in the context. - var diagnostic = context.Diagnostics.Single(); - - // 'SourceSpan' of 'Location' is the highlighted area. We're going to use this area to find the 'SyntaxNode' to rename. - var diagnosticSpan = diagnostic.Location.SourceSpan; - - // Get the root of Syntax Tree that contains the highlighted diagnostic. - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - // Find SyntaxNode corresponding to the diagnostic. - var diagnosticNode = root?.FindNode(diagnosticSpan); - - // To get the required metadata, we should match the Node to the specific type: 'ClassDeclarationSyntax'. - if (diagnosticNode is not ClassDeclarationSyntax declaration) - return; - - // Register a code action that will invoke the fix. - context.RegisterCodeFix( - CodeAction.Create( - title: string.Format(Resources.TUnit0001CodeFixTitle, SampleSyntaxAnalyzer.CompanyName, CommonName), - createChangedSolution: c => SanitizeCompanyNameAsync(context.Document, declaration, c), - equivalenceKey: nameof(Resources.TUnit0001CodeFixTitle)), - diagnostic); - } - - /// - /// Executed on the quick fix action raised by the user. - /// - /// Affected source file. - /// Highlighted class declaration Syntax Node. - /// Any fix is cancellable by the user, so we should support the cancellation token. - /// Clone of the solution with updates: renamed class. - private async Task SanitizeCompanyNameAsync(Document document, - ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken) - { - // 'Identifier' means the token of the node. Compute the new name based on the text of the token of the node. - var newName = classDeclarationSyntax.Identifier.Text.Replace(SampleSyntaxAnalyzer.CompanyName, CommonName); - - // To make a refactoring, we need to get compiled code metadata: the Semantic Model. - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - - // Attempt to find the 'TypeSymbol' (compile time metadata of the class) based on highlighted Class Declaration Syntax. - var typeSymbol = semanticModel?.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken); - if (typeSymbol == null) return document.Project.Solution; - - // Produce a new solution that has all references to the class being renamed, including the declaration. - var newSolution = await Renamer - .RenameSymbolAsync(document.Project.Solution, typeSymbol, new SymbolRenameOptions(), newName, - cancellationToken) - .ConfigureAwait(false); - - // Return the new solution with the updated type name. - return newSolution; - } -} \ No newline at end of file +// using System.Collections.Immutable; +// using System.Composition; +// using System.Linq; +// using System.Threading; +// using System.Threading.Tasks; +// using Microsoft.CodeAnalysis; +// using Microsoft.CodeAnalysis.CodeActions; +// using Microsoft.CodeAnalysis.CodeFixes; +// using Microsoft.CodeAnalysis.CSharp.Syntax; +// using Microsoft.CodeAnalysis.Rename; +// +// namespace TUnit.Analyzers; +// +// /// +// /// A sample code fix provider that renames classes with the company name in their definition. +// /// All code fixes must be linked to specific analyzers. +// /// +// [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SampleCodeFixProvider)), Shared] +// public class SampleCodeFixProvider : CodeFixProvider +// { +// private const string CommonName = "Common"; +// +// // Specify the diagnostic IDs of analyzers that are expected to be linked. +// public sealed override ImmutableArray FixableDiagnosticIds { get; } = +// ImmutableArray.Create(SampleSyntaxAnalyzer.DiagnosticId); +// +// // If you don't need the 'fix all' behaviour, return null. +// public override FixAllProvider? GetFixAllProvider() => null; +// +// public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) +// { +// // We link only one diagnostic and assume there is only one diagnostic in the context. +// var diagnostic = context.Diagnostics.Single(); +// +// // 'SourceSpan' of 'Location' is the highlighted area. We're going to use this area to find the 'SyntaxNode' to rename. +// var diagnosticSpan = diagnostic.Location.SourceSpan; +// +// // Get the root of Syntax Tree that contains the highlighted diagnostic. +// var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); +// +// // Find SyntaxNode corresponding to the diagnostic. +// var diagnosticNode = root?.FindNode(diagnosticSpan); +// +// // To get the required metadata, we should match the Node to the specific type: 'ClassDeclarationSyntax'. +// if (diagnosticNode is not ClassDeclarationSyntax declaration) +// return; +// +// // Register a code action that will invoke the fix. +// context.RegisterCodeFix( +// CodeAction.Create( +// title: string.Format(Resources.TUnit0001CodeFixTitle, SampleSyntaxAnalyzer.CompanyName, CommonName), +// createChangedSolution: c => SanitizeCompanyNameAsync(context.Document, declaration, c), +// equivalenceKey: nameof(Resources.TUnit0001CodeFixTitle)), +// diagnostic); +// } +// +// /// +// /// Executed on the quick fix action raised by the user. +// /// +// /// Affected source file. +// /// Highlighted class declaration Syntax Node. +// /// Any fix is cancellable by the user, so we should support the cancellation token. +// /// Clone of the solution with updates: renamed class. +// private async Task SanitizeCompanyNameAsync(Document document, +// ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken) +// { +// // 'Identifier' means the token of the node. Compute the new name based on the text of the token of the node. +// var newName = classDeclarationSyntax.Identifier.Text.Replace(SampleSyntaxAnalyzer.CompanyName, CommonName); +// +// // To make a refactoring, we need to get compiled code metadata: the Semantic Model. +// var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); +// +// // Attempt to find the 'TypeSymbol' (compile time metadata of the class) based on highlighted Class Declaration Syntax. +// var typeSymbol = semanticModel?.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken); +// if (typeSymbol == null) return document.Project.Solution; +// +// // Produce a new solution that has all references to the class being renamed, including the declaration. +// var newSolution = await Renamer +// .RenameSymbolAsync(document.Project.Solution, typeSymbol, new SymbolRenameOptions(), newName, +// cancellationToken) +// .ConfigureAwait(false); +// +// // Return the new solution with the updated type name. +// return newSolution; +// } +// } \ No newline at end of file From a677a03c128b75fc8fc4592a0e09b6e7cd9103c7 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:41:27 +0000 Subject: [PATCH 059/124] Fix messages in and/or conditions --- TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs | 2 +- .../AssertConditions/Combiners/AssertConditionAnd.cs | 4 ++-- .../AssertConditions/Combiners/AssertConditionOr.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs index 0ba679908d..cfb92e0d10 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs @@ -16,7 +16,7 @@ public class Examples public void ToStars() { - Assert.That("1", Is.EqualTo("1").And.EqualTo("1").Or.EqualTo("2")); + Assert.That("1", Is.EqualTo("1").Or.EqualTo("2").And.EqualTo("1")); var spaceship = new Spaceship(); spaceship.SetSpeed(300000000); // Invalid value, it should be highlighted. spaceship.SetSpeed(42); diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs index 3382bc9f86..76564b6945 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs @@ -12,8 +12,8 @@ public AssertConditionAnd(BaseAssertCondition condition1, Ba } public override string Message => - !_condition1.Passes(ActualValue) ? _condition1.DefaultMessage : - !_condition2.Passes(ActualValue) ? _condition2.DefaultMessage : string.Empty; + !_condition1.Passes(ActualValue) ? _condition1.Message : + !_condition2.Passes(ActualValue) ? _condition2.Message : string.Empty; public override string DefaultMessage => string.Empty; diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs index 7ea7ad81a4..b4b355e361 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs @@ -11,7 +11,7 @@ public AssertConditionOr(BaseAssertCondition condition1, Bas _condition2 = condition2; } - public override string Message => $"{_condition1.DefaultMessage} & {_condition2.DefaultMessage}"; + public override string Message => $"{_condition1.Message} & {_condition2.Message}"; public override string DefaultMessage => string.Empty; protected internal override bool Passes(TActual actualValue) From 970bf6dad2b6f132e2fa305552912f23182cba07 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:42:03 +0000 Subject: [PATCH 060/124] Fix messages in and/or conditions --- .../AssertConditions/Combiners/DelegateAssertConditionAnd.cs | 4 ++-- .../AssertConditions/Combiners/DelegateAssertConditionOr.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs index af0f1e91df..c0c23180b8 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs @@ -14,8 +14,8 @@ public DelegateAssertConditionAnd(DelegateAssertCondition condition1, D } public override string DefaultMessage => - !_condition1.Passes(ActualValue, Exception) ? _condition1.DefaultMessage : - !_condition2.Passes(ActualValue, Exception) ? _condition2.DefaultMessage : string.Empty; + !_condition1.Passes(ActualValue, Exception) ? _condition1.Message : + !_condition2.Passes(ActualValue, Exception) ? _condition2.Message : string.Empty; protected internal override bool Passes(TActual? actualValue, Exception? exception) { diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs index f4e5c31e11..0c1161e020 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs @@ -13,7 +13,7 @@ public DelegateAssertConditionOr(DelegateAssertCondition condition1, De _condition2 = condition2; } - public override string DefaultMessage => $"{_condition1.DefaultMessage} & {_condition2.DefaultMessage}"; + public override string DefaultMessage => $"{_condition1.Message} & {_condition2.Message}"; protected internal override bool Passes(TActual? actualValue, Exception? exception) { From f44c28153804dda96d8fffd0b49b52186ba18909 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:06:08 +0000 Subject: [PATCH 061/124] And + Or Instance classes --- TUnit.Assertions/AssertConditions/And.cs | 6 ++---- .../AssertConditions/Collections/Count.cs | 6 +++++- .../Combiners/AssertConditionAnd.cs | 2 +- .../Combiners/AssertConditionOr.cs | 2 +- .../Combiners/DelegateAssertConditionAnd.cs | 2 +- .../Combiners/DelegateAssertConditionOr.cs | 2 +- .../Instance/AndIsInstance.cs | 19 +++++++++++++++++++ .../ConditionEntries/Instance/HasInstance.cs | 8 ++++---- .../ConditionEntries/Instance/OrIsInstance.cs | 19 +++++++++++++++++++ TUnit.Assertions/AssertConditions/Or.cs | 6 ++---- 10 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs create mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/And.cs index 91ecbe0c2a..43e911dcb0 100644 --- a/TUnit.Assertions/AssertConditions/And.cs +++ b/TUnit.Assertions/AssertConditions/And.cs @@ -1,4 +1,5 @@ using TUnit.Assertions.AssertConditions.Combiners; +using TUnit.Assertions.AssertConditions.ConditionEntries.Instance; using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Assertions.AssertConditions; @@ -12,8 +13,5 @@ public And(BaseAssertCondition otherAssertCondition) _otherAssertCondition = otherAssertCondition; } - public AssertConditionAnd EqualTo(TExpected expected) - { - return new AssertConditionAnd(_otherAssertCondition, Is.EqualTo(expected)); - } + public AndIsInstance Is => new(_otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Collections/Count.cs b/TUnit.Assertions/AssertConditions/Collections/Count.cs index d904ed6c2d..bb38ff5bea 100644 --- a/TUnit.Assertions/AssertConditions/Collections/Count.cs +++ b/TUnit.Assertions/AssertConditions/Collections/Count.cs @@ -1,6 +1,10 @@ namespace TUnit.Assertions.AssertConditions.Collections; -public class Count +public class PropertyOrMethodAccessor { + public PropertyOrMethodAccessor() + { + } + } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs index 76564b6945..75404c56bc 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions.AssertConditions.Combiners; -public class AssertConditionAnd : BaseAssertCondition +public sealed class AssertConditionAnd : BaseAssertCondition { private readonly BaseAssertCondition _condition1; private readonly BaseAssertCondition _condition2; diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs index b4b355e361..3e1ffc2f37 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions.AssertConditions.Combiners; -public class AssertConditionOr : BaseAssertCondition +public sealed class AssertConditionOr : BaseAssertCondition { private readonly BaseAssertCondition _condition1; private readonly BaseAssertCondition _condition2; diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs index c0c23180b8..f1bd6d71d9 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs @@ -2,7 +2,7 @@ namespace TUnit.Assertions.AssertConditions.Combiners; -public class DelegateAssertConditionAnd : DelegateAssertCondition +public sealed class DelegateAssertConditionAnd : DelegateAssertCondition { private readonly DelegateAssertCondition _condition1; private readonly DelegateAssertCondition _condition2; diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs index 0c1161e020..d56e80c7cf 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs @@ -2,7 +2,7 @@ namespace TUnit.Assertions.AssertConditions.Combiners; -public class DelegateAssertConditionOr : DelegateAssertCondition +public sealed class DelegateAssertConditionOr : DelegateAssertCondition { private readonly DelegateAssertCondition _condition1; private readonly DelegateAssertCondition _condition2; diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs new file mode 100644 index 0000000000..c6dfb980ed --- /dev/null +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs @@ -0,0 +1,19 @@ +using TUnit.Assertions.AssertConditions.Combiners; +using TUnit.Assertions.AssertConditions.ConditionEntries.Static; + +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Instance; + +public class AndIsInstance +{ + private readonly BaseAssertCondition _otherAssertCondition; + + public AndIsInstance(BaseAssertCondition otherAssertCondition) + { + _otherAssertCondition = otherAssertCondition; + } + + public AssertConditionAnd EqualTo(TExpected expected) + { + return new AssertConditionAnd(_otherAssertCondition, Is.EqualTo(expected)); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs index 7e81272eda..8a2d71a950 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs @@ -2,12 +2,12 @@ public class HasInstance { + private readonly AssertCondition _otherAssertConditions; + public HasInstance(AssertCondition otherAssertConditions) { - And = new And(otherAssertConditions); - Or = new Or(otherAssertConditions); + _otherAssertConditions = otherAssertConditions; } - public And And { get; } - public Or Or { get; } + } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs new file mode 100644 index 0000000000..c6d65a4263 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs @@ -0,0 +1,19 @@ +using TUnit.Assertions.AssertConditions.Combiners; +using TUnit.Assertions.AssertConditions.ConditionEntries.Static; + +namespace TUnit.Assertions.AssertConditions.ConditionEntries.Instance; + +public class OrIsInstance +{ + private readonly BaseAssertCondition _otherAssertCondition; + + public OrIsInstance(BaseAssertCondition otherAssertCondition) + { + _otherAssertCondition = otherAssertCondition; + } + + public AssertConditionOr EqualTo(TExpected expected) + { + return new AssertConditionOr(_otherAssertCondition, Is.EqualTo(expected)); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Or.cs index 639151fdbb..4377d8aa8b 100644 --- a/TUnit.Assertions/AssertConditions/Or.cs +++ b/TUnit.Assertions/AssertConditions/Or.cs @@ -1,4 +1,5 @@ using TUnit.Assertions.AssertConditions.Combiners; +using TUnit.Assertions.AssertConditions.ConditionEntries.Instance; using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Assertions.AssertConditions; @@ -12,8 +13,5 @@ public Or(BaseAssertCondition otherAssertCondition) _otherAssertCondition = otherAssertCondition; } - public AssertConditionOr EqualTo(TExpected expected) - { - return new AssertConditionOr(_otherAssertCondition, Is.EqualTo(expected)); - } + public OrIsInstance Is => new(_otherAssertCondition); } \ No newline at end of file From 6ec0848b54d030357ab21a9fc16cc894744aa4df Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:11:13 +0000 Subject: [PATCH 062/124] Remove NestedConditionsOperator --- TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs | 2 +- .../TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs | 5 +---- .../Collections/HasCountAssertCondition.cs | 2 +- .../AssertConditions/ConditionEntries/Static/Is.cs | 5 ----- .../ConditionEntries/Static/Is_Numbers.cs | 2 -- .../ConditionEntries/Static/Is_Strings.cs | 10 ---------- .../AssertConditions/{ => Operators}/And.cs | 2 -- .../AssertConditions/{ => Operators}/Or.cs | 2 -- TUnit.Assertions/NestedConditionsOperator.cs | 7 ------- TUnit.Assertions/TUnit.Assertions.csproj | 2 +- TUnit.Core/Exceptions/TimeoutException.cs | 4 +--- TUnit.Engine/MethodInvoker.cs | 1 - TUnit.Engine/TestClassCreator.cs | 3 +-- .../Extensions/ServiceCollectionExtensions.cs | 1 - TUnit.TestProject/Tests.cs | 4 ++-- 15 files changed, 8 insertions(+), 44 deletions(-) rename TUnit.Assertions/AssertConditions/{ => Operators}/And.cs (80%) rename TUnit.Assertions/AssertConditions/{ => Operators}/Or.cs (79%) delete mode 100644 TUnit.Assertions/NestedConditionsOperator.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs index cfb92e0d10..b3527904b6 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs @@ -16,7 +16,7 @@ public class Examples public void ToStars() { - Assert.That("1", Is.EqualTo("1").Or.EqualTo("2").And.EqualTo("1")); + Assert.That("1", Is.EqualTo("1").Or.Is.EqualTo("2").And.Is.EqualTo("1")); var spaceship = new Spaceship(); spaceship.SetSpeed(300000000); // Invalid value, it should be highlighted. spaceship.SetSpeed(42); diff --git a/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs index 8bb73ed941..ec86a30b61 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index fcd518ab44..c873ff944d 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions.AssertConditions.Collections; public class HasCountAssertCondition : AssertCondition, int> { - public HasCountAssertCondition(IReadOnlyCollection, int>> nestedConditions, NestedConditionsOperator? @operator, int expected) : base(expected) + public HasCountAssertCondition(int expected) : base(expected) { } diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs index 737da0b76b..89d7647d5c 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs @@ -18,9 +18,4 @@ public static AssertCondition SameReference(T expected) { return new SameReferenceAssertCondition(expected); } - - internal static AssertCondition SameReference(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, T expected) - { - return new SameReferenceAssertCondition(expected); - } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs index 90efa865d3..18513f0432 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs @@ -7,8 +7,6 @@ namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; public static partial class Is { public static AssertCondition Zero => new EqualsAssertCondition(0); - internal static AssertCondition ZeroInternal(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator) - => new EqualsAssertCondition(0); public static AssertCondition GreaterThan(T expected) where T : INumber { diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs index acc52ca20d..2034a86197 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs @@ -9,18 +9,8 @@ public static AssertCondition EqualTo(string expected) return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); } - internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected) - { - return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); - } - public static AssertCondition EqualTo(string expected, StringComparison stringComparison) { return new StringEqualsAssertCondition(expected, stringComparison); } - - internal static AssertCondition EqualTo(IReadOnlyCollection> nestedConditions, NestedConditionsOperator? @operator, string expected, StringComparison stringComparison) - { - return new StringEqualsAssertCondition(expected, stringComparison); - } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/And.cs b/TUnit.Assertions/AssertConditions/Operators/And.cs similarity index 80% rename from TUnit.Assertions/AssertConditions/And.cs rename to TUnit.Assertions/AssertConditions/Operators/And.cs index 43e911dcb0..4eeb23ceb1 100644 --- a/TUnit.Assertions/AssertConditions/And.cs +++ b/TUnit.Assertions/AssertConditions/Operators/And.cs @@ -1,6 +1,4 @@ -using TUnit.Assertions.AssertConditions.Combiners; using TUnit.Assertions.AssertConditions.ConditionEntries.Instance; -using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Assertions.AssertConditions; diff --git a/TUnit.Assertions/AssertConditions/Or.cs b/TUnit.Assertions/AssertConditions/Operators/Or.cs similarity index 79% rename from TUnit.Assertions/AssertConditions/Or.cs rename to TUnit.Assertions/AssertConditions/Operators/Or.cs index 4377d8aa8b..1d057cb5db 100644 --- a/TUnit.Assertions/AssertConditions/Or.cs +++ b/TUnit.Assertions/AssertConditions/Operators/Or.cs @@ -1,6 +1,4 @@ -using TUnit.Assertions.AssertConditions.Combiners; using TUnit.Assertions.AssertConditions.ConditionEntries.Instance; -using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Assertions.AssertConditions; diff --git a/TUnit.Assertions/NestedConditionsOperator.cs b/TUnit.Assertions/NestedConditionsOperator.cs deleted file mode 100644 index b01de33ccf..0000000000 --- a/TUnit.Assertions/NestedConditionsOperator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TUnit.Assertions; - -public enum NestedConditionsOperator -{ - And, - Or -} \ No newline at end of file diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index 8a1e9ac720..9b1b0f6186 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -8,7 +8,7 @@ - + diff --git a/TUnit.Core/Exceptions/TimeoutException.cs b/TUnit.Core/Exceptions/TimeoutException.cs index 4c52044206..b8fc0bc653 100644 --- a/TUnit.Core/Exceptions/TimeoutException.cs +++ b/TUnit.Core/Exceptions/TimeoutException.cs @@ -1,6 +1,4 @@ -using System.Runtime.Serialization; - -namespace TUnit.Core.Exceptions; +namespace TUnit.Core.Exceptions; public class TimeoutException : TUnitException { diff --git a/TUnit.Engine/MethodInvoker.cs b/TUnit.Engine/MethodInvoker.cs index c1ae76f7f1..2cc278ee49 100644 --- a/TUnit.Engine/MethodInvoker.cs +++ b/TUnit.Engine/MethodInvoker.cs @@ -1,7 +1,6 @@ using System.Globalization; using System.Reflection; using System.Runtime.ExceptionServices; -using TUnit.Core; namespace TUnit.Engine; diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs index 09710a8a58..1bb8bb453c 100644 --- a/TUnit.Engine/TestClassCreator.cs +++ b/TUnit.Engine/TestClassCreator.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using TUnit.Core; +using TUnit.Core; using TUnit.Core.Attributes; using TUnit.Engine.Extensions; diff --git a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs index ccd859098f..a4d6e241fc 100644 --- a/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.TestAdapter/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using TUnit.Engine; namespace TUnit.TestAdapter.Extensions; diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 03c5e4f43c..102e15aedb 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -177,14 +177,14 @@ public async Task Timeout1() [Test] public void String_And_Condition() { - Assert.That("1", Is.EqualTo("1").And.EqualTo("1").Or.EqualTo("2")); + Assert.That("1", Is.EqualTo("1").And.Is.EqualTo("1").Or.Is.EqualTo("2")); } [Test] public void String_And_Condition2() { - Assert.That("1", Is.EqualTo("1").And.EqualTo("1").And.EqualTo("2")); + Assert.That("1", Is.EqualTo("1").And.Is.EqualTo("1").And.Is.EqualTo("2")); } public static int One() => 1; From e318574b9108aad54c0c719cd57b19105bed6476 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:19:36 +0000 Subject: [PATCH 063/124] OrIs AndIs OrHas AndHas --- TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs | 1 - TUnit.Assertions.UnitTests/ZeroAssertionTests.cs | 2 +- TUnit.Assertions/Assert.cs | 2 +- .../AssertConditions/BaseAssertCondition.cs | 2 ++ .../ConditionEntries/Instance/HasInstance.cs | 13 ------------- .../AssertConditions/ConditionEntries/Static/Has.cs | 6 ------ .../AssertConditions/Connectors/AndHas.cs | 11 +++++++++++ .../AndIsInstance.cs => Connectors/AndIs.cs} | 9 +++------ .../{Combiners => Connectors}/AssertConditionAnd.cs | 2 +- .../{Combiners => Connectors}/AssertConditionOr.cs | 2 +- .../DelegateAssertConditionAnd.cs | 2 +- .../DelegateAssertConditionOr.cs | 2 +- .../AssertConditions/Connectors/OrHas.cs | 11 +++++++++++ .../Instance/OrIsInstance.cs => Connectors/OrIs.cs} | 9 +++------ TUnit.Assertions/AssertConditions/Operators/And.cs | 7 ++++--- TUnit.Assertions/AssertConditions/Operators/Or.cs | 7 ++++--- TUnit.Assertions/Has.cs | 6 ++++++ .../ConditionEntries/Static => }/Is.cs | 5 +++-- .../ConditionEntries/Static => }/Is_Numbers.cs | 3 ++- .../ConditionEntries/Static => }/Is_Strings.cs | 5 +++-- TUnit.Assertions/TUnit.Assertions.csproj | 1 + .../ConditionEntries/Static => }/Throws.cs | 2 +- TUnit.TestProject/Tests.cs | 1 - TUnit/GlobalUsings.cs | 1 - 24 files changed, 60 insertions(+), 52 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs delete mode 100644 TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs create mode 100644 TUnit.Assertions/AssertConditions/Connectors/AndHas.cs rename TUnit.Assertions/AssertConditions/{ConditionEntries/Instance/AndIsInstance.cs => Connectors/AndIs.cs} (53%) rename TUnit.Assertions/AssertConditions/{Combiners => Connectors}/AssertConditionAnd.cs (93%) rename TUnit.Assertions/AssertConditions/{Combiners => Connectors}/AssertConditionOr.cs (92%) rename TUnit.Assertions/AssertConditions/{Combiners => Connectors}/DelegateAssertConditionAnd.cs (94%) rename TUnit.Assertions/AssertConditions/{Combiners => Connectors}/DelegateAssertConditionOr.cs (93%) create mode 100644 TUnit.Assertions/AssertConditions/Connectors/OrHas.cs rename TUnit.Assertions/AssertConditions/{ConditionEntries/Instance/OrIsInstance.cs => Connectors/OrIs.cs} (53%) create mode 100644 TUnit.Assertions/Has.cs rename TUnit.Assertions/{AssertConditions/ConditionEntries/Static => }/Is.cs (81%) rename TUnit.Assertions/{AssertConditions/ConditionEntries/Static => }/Is_Numbers.cs (94%) rename TUnit.Assertions/{AssertConditions/ConditionEntries/Static => }/Is_Strings.cs (77%) rename TUnit.Assertions/{AssertConditions/ConditionEntries/Static => }/Throws.cs (68%) diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs index b3527904b6..7980f7530e 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs @@ -2,7 +2,6 @@ // ReSharper disable UnusedMember.Global using TUnit.Assertions; -using TUnit.Assertions.AssertConditions.ConditionEntries.Static; namespace TUnit.Analyzers.Sample; diff --git a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs index d44ea51915..66a1dbb658 100644 --- a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs @@ -1,4 +1,4 @@ -using Is = TUnit.Assertions.AssertConditions.ConditionEntries.Static.Is; +using Is = TUnit.Assertions.Is; namespace TUnit.Assertions.UnitTests; diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 1c8e0e77cc..232504af06 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,5 +1,5 @@ using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Combiners; +using TUnit.Assertions.AssertConditions.Connectors; using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Exceptions; using TUnit.Assertions.Extensions; diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index 4b1bce7c0d..315bb968c8 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -1,3 +1,5 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions; public abstract class BaseAssertCondition diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs deleted file mode 100644 index 8a2d71a950..0000000000 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/HasInstance.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Instance; - -public class HasInstance -{ - private readonly AssertCondition _otherAssertConditions; - - public HasInstance(AssertCondition otherAssertConditions) - { - _otherAssertConditions = otherAssertConditions; - } - - -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs b/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs deleted file mode 100644 index 208693025d..0000000000 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Has.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; - -public static class Has -{ - -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs new file mode 100644 index 0000000000..32b091104b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs @@ -0,0 +1,11 @@ +namespace TUnit.Assertions.AssertConditions.Connectors; + +public class AndHas +{ + private readonly BaseAssertCondition _otherAssertConditions; + + public AndHas(BaseAssertCondition otherAssertConditions) + { + _otherAssertConditions = otherAssertConditions; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs similarity index 53% rename from TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs rename to TUnit.Assertions/AssertConditions/Connectors/AndIs.cs index c6dfb980ed..2d74af735b 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/AndIsInstance.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs @@ -1,13 +1,10 @@ -using TUnit.Assertions.AssertConditions.Combiners; -using TUnit.Assertions.AssertConditions.ConditionEntries.Static; +namespace TUnit.Assertions.AssertConditions.Connectors; -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Instance; - -public class AndIsInstance +public class AndIs { private readonly BaseAssertCondition _otherAssertCondition; - public AndIsInstance(BaseAssertCondition otherAssertCondition) + public AndIs(BaseAssertCondition otherAssertCondition) { _otherAssertCondition = otherAssertCondition; } diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs similarity index 93% rename from TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs rename to TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index 75404c56bc..71203e4393 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.AssertConditions.Combiners; +namespace TUnit.Assertions.AssertConditions.Connectors; public sealed class AssertConditionAnd : BaseAssertCondition { diff --git a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs similarity index 92% rename from TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs rename to TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index 3e1ffc2f37..1bf29904c4 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.AssertConditions.Combiners; +namespace TUnit.Assertions.AssertConditions.Connectors; public sealed class AssertConditionOr : BaseAssertCondition { diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs similarity index 94% rename from TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs rename to TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs index f1bd6d71d9..a45542232b 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs @@ -1,6 +1,6 @@ using TUnit.Assertions.AssertConditions.Throws; -namespace TUnit.Assertions.AssertConditions.Combiners; +namespace TUnit.Assertions.AssertConditions.Connectors; public sealed class DelegateAssertConditionAnd : DelegateAssertCondition { diff --git a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs similarity index 93% rename from TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs rename to TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs index d56e80c7cf..43635b33b5 100644 --- a/TUnit.Assertions/AssertConditions/Combiners/DelegateAssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs @@ -1,6 +1,6 @@ using TUnit.Assertions.AssertConditions.Throws; -namespace TUnit.Assertions.AssertConditions.Combiners; +namespace TUnit.Assertions.AssertConditions.Connectors; public sealed class DelegateAssertConditionOr : DelegateAssertCondition { diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs new file mode 100644 index 0000000000..0214e4c597 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs @@ -0,0 +1,11 @@ +namespace TUnit.Assertions.AssertConditions.Connectors; + +public class OrHas +{ + private readonly BaseAssertCondition _otherAssertConditions; + + public OrHas(BaseAssertCondition otherAssertConditions) + { + _otherAssertConditions = otherAssertConditions; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs similarity index 53% rename from TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs rename to TUnit.Assertions/AssertConditions/Connectors/OrIs.cs index c6d65a4263..79b2215caf 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Instance/OrIsInstance.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs @@ -1,13 +1,10 @@ -using TUnit.Assertions.AssertConditions.Combiners; -using TUnit.Assertions.AssertConditions.ConditionEntries.Static; +namespace TUnit.Assertions.AssertConditions.Connectors; -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Instance; - -public class OrIsInstance +public class OrIs { private readonly BaseAssertCondition _otherAssertCondition; - public OrIsInstance(BaseAssertCondition otherAssertCondition) + public OrIs(BaseAssertCondition otherAssertCondition) { _otherAssertCondition = otherAssertCondition; } diff --git a/TUnit.Assertions/AssertConditions/Operators/And.cs b/TUnit.Assertions/AssertConditions/Operators/And.cs index 4eeb23ceb1..e6afc18746 100644 --- a/TUnit.Assertions/AssertConditions/Operators/And.cs +++ b/TUnit.Assertions/AssertConditions/Operators/And.cs @@ -1,6 +1,6 @@ -using TUnit.Assertions.AssertConditions.ConditionEntries.Instance; +using TUnit.Assertions.AssertConditions.Connectors; -namespace TUnit.Assertions.AssertConditions; +namespace TUnit.Assertions.AssertConditions.Operators; public class And { @@ -11,5 +11,6 @@ public And(BaseAssertCondition otherAssertCondition) _otherAssertCondition = otherAssertCondition; } - public AndIsInstance Is => new(_otherAssertCondition); + public AndIs Is => new(_otherAssertCondition); + public AndHas Has => new(_otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/Or.cs b/TUnit.Assertions/AssertConditions/Operators/Or.cs index 1d057cb5db..2246c3def5 100644 --- a/TUnit.Assertions/AssertConditions/Operators/Or.cs +++ b/TUnit.Assertions/AssertConditions/Operators/Or.cs @@ -1,6 +1,6 @@ -using TUnit.Assertions.AssertConditions.ConditionEntries.Instance; +using TUnit.Assertions.AssertConditions.Connectors; -namespace TUnit.Assertions.AssertConditions; +namespace TUnit.Assertions.AssertConditions.Operators; public class Or { @@ -11,5 +11,6 @@ public Or(BaseAssertCondition otherAssertCondition) _otherAssertCondition = otherAssertCondition; } - public OrIsInstance Is => new(_otherAssertCondition); + public OrIs Is => new(_otherAssertCondition); + public OrHas Has => new(_otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs new file mode 100644 index 0000000000..4f8b545e9f --- /dev/null +++ b/TUnit.Assertions/Has.cs @@ -0,0 +1,6 @@ +namespace TUnit.Assertions; + +public static class Has +{ + +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs b/TUnit.Assertions/Is.cs similarity index 81% rename from TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs rename to TUnit.Assertions/Is.cs index 89d7647d5c..52a706ca2e 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -1,6 +1,7 @@ -using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Generic; -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; +namespace TUnit.Assertions; public partial class Is { diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs b/TUnit.Assertions/Is_Numbers.cs similarity index 94% rename from TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs rename to TUnit.Assertions/Is_Numbers.cs index 18513f0432..aa1af3b799 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Numbers.cs +++ b/TUnit.Assertions/Is_Numbers.cs @@ -1,8 +1,9 @@ using System.Numerics; +using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; +namespace TUnit.Assertions; public static partial class Is { diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs b/TUnit.Assertions/Is_Strings.cs similarity index 77% rename from TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs rename to TUnit.Assertions/Is_Strings.cs index 2034a86197..79da228e33 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Is_Strings.cs +++ b/TUnit.Assertions/Is_Strings.cs @@ -1,6 +1,7 @@ -using TUnit.Assertions.AssertConditions.String; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.String; -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; +namespace TUnit.Assertions; public static partial class Is { diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index 9b1b0f6186..16bdc32442 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -8,6 +8,7 @@ + diff --git a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs b/TUnit.Assertions/Throws.cs similarity index 68% rename from TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs rename to TUnit.Assertions/Throws.cs index 2f1b7f1435..357505ebf4 100644 --- a/TUnit.Assertions/AssertConditions/ConditionEntries/Static/Throws.cs +++ b/TUnit.Assertions/Throws.cs @@ -1,6 +1,6 @@ using TUnit.Assertions.AssertConditions.Throws; -namespace TUnit.Assertions.AssertConditions.ConditionEntries.Static; +namespace TUnit.Assertions; public static class Throws { diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 102e15aedb..a07a403f9d 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,5 +1,4 @@ using TUnit.Assertions; -using TUnit.Assertions.AssertConditions.ConditionEntries.Static; using TUnit.Core; using TUnit.Core.Attributes; diff --git a/TUnit/GlobalUsings.cs b/TUnit/GlobalUsings.cs index 25326f38cc..c351b52512 100644 --- a/TUnit/GlobalUsings.cs +++ b/TUnit/GlobalUsings.cs @@ -1,4 +1,3 @@ global using TUnit.Assertions; -global using TUnit.Assertions.AssertConditions.ConditionEntries.Static; global using TUnit.Core.Attributes; global using Assert = TUnit.Assertions.Assert; \ No newline at end of file From 59667d8dd234c87fc42f3ee0c9673ac2a9765309 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:43:49 +0000 Subject: [PATCH 064/124] Length and Count assertions --- .../EnumerableEquivalentToAssertCondition.cs | 15 ++++++++++ .../AssertConditions/Generic/Property.cs | 16 ++++++++++ .../Generic/PropertyEqualsAssertCondition.cs | 28 ++++++++++++++++++ .../Generic/PropertyOrMethod.cs | 16 ++++++++++ .../PropertyOrMethodEqualsAssertCondition.cs | 29 +++++++++++++++++++ .../Extensions/ReflectionExtensions.cs | 14 +++++++++ TUnit.Assertions/Has.cs | 9 ++++-- TUnit.Assertions/Is_Enumerable.cs | 13 +++++++++ TUnit.TestProject/Tests.cs | 28 ++++++++++++++++++ 9 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/Property.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs create mode 100644 TUnit.Assertions/Extensions/ReflectionExtensions.cs create mode 100644 TUnit.Assertions/Is_Enumerable.cs diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs new file mode 100644 index 0000000000..59ee2838aa --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs @@ -0,0 +1,15 @@ +namespace TUnit.Assertions.AssertConditions.Collections; + +public class EnumerableEquivalentToAssertCondition : AssertCondition, IEnumerable> +{ + public EnumerableEquivalentToAssertCondition(IEnumerable expected) : base(expected) + { + } + + public override string DefaultMessage => "The two Enumerables were not equivalent"; + + protected internal override bool Passes(IEnumerable actualValue) + { + return actualValue.SequenceEqual(ExpectedValue!); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/Property.cs b/TUnit.Assertions/AssertConditions/Generic/Property.cs new file mode 100644 index 0000000000..e0b9316c52 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/Property.cs @@ -0,0 +1,16 @@ +namespace TUnit.Assertions.AssertConditions.Generic; + +public class Property +{ + private readonly string _name; + + public Property(string name) + { + _name = name; + } + + public AssertCondition EqualTo(TExpected expected) + { + return new PropertyEqualsAssertCondition(_name, expected); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs new file mode 100644 index 0000000000..70a571f549 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -0,0 +1,28 @@ +using TUnit.Assertions.Extensions; + +namespace TUnit.Assertions.AssertConditions.Generic; + +public class PropertyEqualsAssertCondition(string propertyName, TExpected expected) + : AssertCondition(expected) +{ + public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; + + protected internal override bool Passes(object actualValue) + { + var propertyValue = GetPropertyValue(actualValue); + + WithMessage(_ => $"Expected {ExpectedValue} but received {propertyValue}"); + + return Equals(propertyValue, ExpectedValue); + } + + private object? GetPropertyValue(object actualValue) + { + if (actualValue.GetType().GetProperty(propertyName) is null) + { + throw new ArgumentException($"No {propertyName} property or method was found on {actualValue.GetType().Name}"); + } + + return actualValue.GetPropertyValue(propertyName); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs new file mode 100644 index 0000000000..b30536d01b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs @@ -0,0 +1,16 @@ +namespace TUnit.Assertions.AssertConditions.Generic; + +public class PropertyOrMethod +{ + private readonly string _name; + + public PropertyOrMethod(string name) + { + _name = name; + } + + internal AssertCondition EqualTo(TExpected expected) + { + return new PropertyOrMethodEqualsAssertCondition(_name, expected); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs new file mode 100644 index 0000000000..be264af2aa --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -0,0 +1,29 @@ +using TUnit.Assertions.Extensions; + +namespace TUnit.Assertions.AssertConditions.Generic; + +public class PropertyOrMethodEqualsAssertCondition(string propertyName, TExpected expected) + : AssertCondition(expected) +{ + public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; + + protected internal override bool Passes(object actualValue) + { + var propertyValue = GetPropertyValue(actualValue); + + WithMessage(_ => $"Expected {ExpectedValue} but received {propertyValue}"); + + return Equals(propertyValue, ExpectedValue); + } + + private object? GetPropertyValue(object actualValue) + { + if (actualValue.GetType().GetProperty(propertyName) is null + && actualValue.GetType().GetProperty(propertyName) is null) + { + throw new ArgumentException($"No {propertyName} property or method was found on {actualValue.GetType().Name}"); + } + + return actualValue.GetPropertyValue(propertyName) ?? actualValue.GetMethodReturnValue(propertyName); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/ReflectionExtensions.cs b/TUnit.Assertions/Extensions/ReflectionExtensions.cs new file mode 100644 index 0000000000..f4dc858528 --- /dev/null +++ b/TUnit.Assertions/Extensions/ReflectionExtensions.cs @@ -0,0 +1,14 @@ +namespace TUnit.Assertions.Extensions; + +public static class ReflectionExtensions +{ + public static object? GetPropertyValue(this object obj, string propertyName) + { + return obj.GetType().GetProperty(propertyName)?.GetValue(obj); + } + + public static object? GetMethodReturnValue(this object obj, string methodName) + { + return obj.GetType().GetMethod(methodName)?.Invoke(obj, null); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 4f8b545e9f..8c3811dcbb 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -1,6 +1,11 @@ -namespace TUnit.Assertions; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Generic; + +namespace TUnit.Assertions; public static class Has { - + public static Property Count => new("Count"); + public static Property Length => new("Length"); + public static Property Value => new("Value"); } \ No newline at end of file diff --git a/TUnit.Assertions/Is_Enumerable.cs b/TUnit.Assertions/Is_Enumerable.cs new file mode 100644 index 0000000000..fdb7fa72f2 --- /dev/null +++ b/TUnit.Assertions/Is_Enumerable.cs @@ -0,0 +1,13 @@ +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Collections; +using TUnit.Assertions.AssertConditions.String; + +namespace TUnit.Assertions; + +public static partial class Is +{ + public static AssertCondition, IEnumerable> EquivalentTo(IEnumerable expected) + { + return new EnumerableEquivalentToAssertCondition(expected); + } +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index a07a403f9d..f1a0099bf0 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -186,6 +186,34 @@ public void String_And_Condition2() Assert.That("1", Is.EqualTo("1").And.Is.EqualTo("1").And.Is.EqualTo("2")); } + [Test] + public void Count1() + { + var list = new List { 1, 2, 3 }; + Assert.That(list, Has.Count.EqualTo(3)); + } + + [Test] + public void Count2() + { + var list = new List { 1, 2, 3 }; + Assert.That(list, Has.Count.EqualTo(1)); + } + + [Test] + public void Count3() + { + var list = new[] { 1, 2, 3 }; + Assert.That(list, Has.Length.EqualTo(3)); + } + + [Test] + public void Count4() + { + var list = new[] { 1, 2, 3 }; + Assert.That(list, Has.Length.EqualTo(1)); + } + public static int One() => 1; public static int Two() => 2; } \ No newline at end of file From 61e7537e73a5dea36b6c2c0a2e1bc18da29ae2d0 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:11:55 +0000 Subject: [PATCH 065/124] Generic Property --- TUnit.Assertions/AssertConditions/Generic/Property.cs | 11 +++++++++-- TUnit.Assertions/Has.cs | 7 +++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Generic/Property.cs b/TUnit.Assertions/AssertConditions/Generic/Property.cs index e0b9316c52..58b3283a49 100644 --- a/TUnit.Assertions/AssertConditions/Generic/Property.cs +++ b/TUnit.Assertions/AssertConditions/Generic/Property.cs @@ -1,6 +1,13 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class Property +public class Property : Property +{ + public Property(string name) : base(name) + { + } +} + +public class Property { private readonly string _name; @@ -9,7 +16,7 @@ public Property(string name) _name = name; } - public AssertCondition EqualTo(TExpected expected) + public AssertCondition EqualTo(TExpected expected) { return new PropertyEqualsAssertCondition(_name, expected); } diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 8c3811dcbb..bbce94f39f 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -5,7 +5,10 @@ namespace TUnit.Assertions; public static class Has { - public static Property Count => new("Count"); - public static Property Length => new("Length"); + public static Property Count => new("Count"); + public static Property Length => new("Length"); public static Property Value => new("Value"); + + public static Property Property(string name) => new(name); + public static Property Property(string name) => new(name); } \ No newline at end of file From 01e95e288189f634b22f212fc02f156e3225770b Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:28:21 +0000 Subject: [PATCH 066/124] Tweaks --- TUnit.Assertions/Assert.cs | 18 +-------- .../AssertConditions/ConnectorType.cs | 7 ++++ .../AssertConditions/Connectors/AndHas.cs | 8 ++++ .../AssertConditions/Generic/Property.cs | 40 ++++++++++++++----- .../Generic/PropertyEqualsAssertCondition.cs | 12 +++--- TUnit.Assertions/Has.cs | 10 ++--- 6 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/ConnectorType.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 232504af06..8726bb1cfc 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -8,7 +8,7 @@ namespace TUnit.Assertions; public static class Assert { - public static void That(TActual value, AssertCondition assertCondition) + public static void That(TActual value, BaseAssertCondition assertCondition) { if (!assertCondition.Assert(value)) { @@ -16,22 +16,6 @@ public static void That(TActual value, AssertCondition(TActual value, AssertConditionOr orCondition) - { - if (!orCondition.Assert(value)) - { - throw new AssertionException(orCondition.Message); - } - } - - public static void That(TActual value, AssertConditionAnd andCondition) - { - if (!andCondition.Assert(value)) - { - throw new AssertionException(andCondition.Message); - } - } - public static Exception? That(Action value, DelegateAssertCondition assertCondition) { var exception = value.InvokeAndGetException(); diff --git a/TUnit.Assertions/AssertConditions/ConnectorType.cs b/TUnit.Assertions/AssertConditions/ConnectorType.cs new file mode 100644 index 0000000000..623b575f17 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/ConnectorType.cs @@ -0,0 +1,7 @@ +namespace TUnit.Assertions.AssertConditions; + +public enum ConnectorType +{ + And, + Or +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs index 32b091104b..a52109df04 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs @@ -1,3 +1,5 @@ +using TUnit.Assertions.AssertConditions.Generic; + namespace TUnit.Assertions.AssertConditions.Connectors; public class AndHas @@ -8,4 +10,10 @@ public AndHas(BaseAssertCondition otherAssertConditions) { _otherAssertConditions = otherAssertConditions; } + + public Property Count => new("Count", ConnectorType.And, _otherAssertConditions); + public Property Length => new("Length", ConnectorType.And, _otherAssertConditions); + public Property Value => new("Value", ConnectorType.And, _otherAssertConditions); + + public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/Property.cs b/TUnit.Assertions/AssertConditions/Generic/Property.cs index 58b3283a49..5007cee255 100644 --- a/TUnit.Assertions/AssertConditions/Generic/Property.cs +++ b/TUnit.Assertions/AssertConditions/Generic/Property.cs @@ -1,23 +1,45 @@ -namespace TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Connectors; -public class Property : Property +namespace TUnit.Assertions.AssertConditions.Generic; + +public class Property : Property { + public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + : base(name, connectorType, otherAssertConditions) + { + } + public Property(string name) : base(name) { } } -public class Property +public class Property(string name) { - private readonly string _name; + private readonly ConnectorType? _connectorType; + private readonly BaseAssertCondition? _otherAssertConditions; - public Property(string name) + public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + : this(name) { - _name = name; + _connectorType = connectorType; + _otherAssertConditions = otherAssertConditions; } - - public AssertCondition EqualTo(TExpected expected) + + public BaseAssertCondition EqualTo(TExpected expected) { - return new PropertyEqualsAssertCondition(_name, expected); + var assertCondition = new PropertyEqualsAssertCondition(name, expected); + + if (_connectorType is ConnectorType.And) + { + return new AssertConditionAnd(_otherAssertConditions!, assertCondition); + } + + if (_connectorType is ConnectorType.Or) + { + return new AssertConditionOr(_otherAssertConditions!, assertCondition); + } + + return assertCondition; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index 70a571f549..1c64ad8e44 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -2,12 +2,12 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class PropertyEqualsAssertCondition(string propertyName, TExpected expected) - : AssertCondition(expected) +public class PropertyEqualsAssertCondition(string propertyName, TExpected expected) + : AssertCondition(expected) { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(object actualValue) + protected internal override bool Passes(TActual actualValue) { var propertyValue = GetPropertyValue(actualValue); @@ -16,8 +16,10 @@ protected internal override bool Passes(object actualValue) return Equals(propertyValue, ExpectedValue); } - private object? GetPropertyValue(object actualValue) - { + private object? GetPropertyValue(object? actualValue) + { + ArgumentNullException.ThrowIfNull(actualValue); + if (actualValue.GetType().GetProperty(propertyName) is null) { throw new ArgumentException($"No {propertyName} property or method was found on {actualValue.GetType().Name}"); diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index bbce94f39f..ca923e13c7 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -5,10 +5,10 @@ namespace TUnit.Assertions; public static class Has { - public static Property Count => new("Count"); - public static Property Length => new("Length"); - public static Property Value => new("Value"); + public static Property Count => new("Count"); + public static Property Length => new("Length"); + public static Property Value => new("Value"); - public static Property Property(string name) => new(name); - public static Property Property(string name) => new(name); + public static Property Property(string name) => new(name); + public static Property Property(string name) => new(name); } \ No newline at end of file From 6725ddc20208c816f6a9996278b1009b1ec89e23 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:20:18 +0000 Subject: [PATCH 067/124] Tweaks to generic assertion models --- TUnit.Assertions/Assert.cs | 3 +-- .../AssertConditions/AssertCondition.cs | 2 +- .../AssertConditions/BaseAssertCondition.cs | 10 +++++----- .../AssertConditions/Connectors/AndHas.cs | 14 +++++++------- .../AssertConditions/Connectors/AndIs.cs | 10 +++++----- .../Connectors/AssertConditionAnd.cs | 8 ++++---- .../Connectors/AssertConditionOr.cs | 8 ++++---- .../AssertConditions/Connectors/OrHas.cs | 6 +++--- .../AssertConditions/Connectors/OrIs.cs | 10 +++++----- .../AssertConditions/Generic/Property.cs | 12 ++++++------ TUnit.Assertions/AssertConditions/Operators/And.cs | 10 +++++----- TUnit.Assertions/AssertConditions/Operators/Or.cs | 10 +++++----- TUnit.TestProject/Tests.cs | 2 +- 13 files changed, 52 insertions(+), 53 deletions(-) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 8726bb1cfc..25fa958ab7 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,5 +1,4 @@ using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Connectors; using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Exceptions; using TUnit.Assertions.Extensions; @@ -8,7 +7,7 @@ namespace TUnit.Assertions; public static class Assert { - public static void That(TActual value, BaseAssertCondition assertCondition) + public static void That(TActual value, BaseAssertCondition assertCondition) { if (!assertCondition.Assert(value)) { diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index ff66cec766..f4635bc087 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions.AssertConditions; -public abstract class AssertCondition : BaseAssertCondition +public abstract class AssertCondition : BaseAssertCondition { internal TExpected? ExpectedValue { get; } diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index 315bb968c8..aa9f49387f 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -2,12 +2,12 @@ namespace TUnit.Assertions.AssertConditions; -public abstract class BaseAssertCondition +public abstract class BaseAssertCondition { internal BaseAssertCondition() { - And = new And(this); - Or = new Or(this); + And = new And(this); + Or = new Or(this); } protected TActual ActualValue { get; set; } = default!; @@ -24,6 +24,6 @@ public bool Assert(TActual actualValue) protected internal abstract bool Passes(TActual actualValue); - public And And { get; } - public Or Or { get; } + public And And { get; } + public Or Or { get; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs index a52109df04..4b4ef8a1e9 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs @@ -2,18 +2,18 @@ namespace TUnit.Assertions.AssertConditions.Connectors; -public class AndHas +public class AndHas { - private readonly BaseAssertCondition _otherAssertConditions; + private readonly BaseAssertCondition _otherAssertConditions; - public AndHas(BaseAssertCondition otherAssertConditions) + public AndHas(BaseAssertCondition otherAssertConditions) { _otherAssertConditions = otherAssertConditions; } - public Property Count => new("Count", ConnectorType.And, _otherAssertConditions); - public Property Length => new("Length", ConnectorType.And, _otherAssertConditions); - public Property Value => new("Value", ConnectorType.And, _otherAssertConditions); + public Property Count => new("Count", ConnectorType.And, _otherAssertConditions); + public Property Length => new("Length", ConnectorType.And, _otherAssertConditions); + public Property Value => new("Value", ConnectorType.And, _otherAssertConditions); - public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); + public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs index 2d74af735b..467672b77d 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs @@ -1,16 +1,16 @@ namespace TUnit.Assertions.AssertConditions.Connectors; -public class AndIs +public class AndIs { - private readonly BaseAssertCondition _otherAssertCondition; + private readonly BaseAssertCondition _otherAssertCondition; - public AndIs(BaseAssertCondition otherAssertCondition) + public AndIs(BaseAssertCondition otherAssertCondition) { _otherAssertCondition = otherAssertCondition; } - public AssertConditionAnd EqualTo(TExpected expected) + public AssertConditionAnd EqualTo(TExpected expected) { - return new AssertConditionAnd(_otherAssertCondition, Is.EqualTo(expected)); + return new AssertConditionAnd(_otherAssertCondition, Is.EqualTo(expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index 71203e4393..c616c6293e 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -1,11 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Connectors; -public sealed class AssertConditionAnd : BaseAssertCondition +public sealed class AssertConditionAnd : BaseAssertCondition { - private readonly BaseAssertCondition _condition1; - private readonly BaseAssertCondition _condition2; + private readonly BaseAssertCondition _condition1; + private readonly BaseAssertCondition _condition2; - public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) + public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) { _condition1 = condition1; _condition2 = condition2; diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index 1bf29904c4..e3c342f2fe 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -1,11 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Connectors; -public sealed class AssertConditionOr : BaseAssertCondition +public sealed class AssertConditionOr : BaseAssertCondition { - private readonly BaseAssertCondition _condition1; - private readonly BaseAssertCondition _condition2; + private readonly BaseAssertCondition _condition1; + private readonly BaseAssertCondition _condition2; - public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) + public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) { _condition1 = condition1; _condition2 = condition2; diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs index 0214e4c597..bd8bc8a0c9 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs @@ -1,10 +1,10 @@ namespace TUnit.Assertions.AssertConditions.Connectors; -public class OrHas +public class OrHas { - private readonly BaseAssertCondition _otherAssertConditions; + private readonly BaseAssertCondition _otherAssertConditions; - public OrHas(BaseAssertCondition otherAssertConditions) + public OrHas(BaseAssertCondition otherAssertConditions) { _otherAssertConditions = otherAssertConditions; } diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs index 79b2215caf..f3a9802dd8 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs @@ -1,16 +1,16 @@ namespace TUnit.Assertions.AssertConditions.Connectors; -public class OrIs +public class OrIs { - private readonly BaseAssertCondition _otherAssertCondition; + private readonly BaseAssertCondition _otherAssertCondition; - public OrIs(BaseAssertCondition otherAssertCondition) + public OrIs(BaseAssertCondition otherAssertCondition) { _otherAssertCondition = otherAssertCondition; } - public AssertConditionOr EqualTo(TExpected expected) + public AssertConditionOr EqualTo(TExpected expected) { - return new AssertConditionOr(_otherAssertCondition, Is.EqualTo(expected)); + return new AssertConditionOr(_otherAssertCondition, Is.EqualTo(expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/Property.cs b/TUnit.Assertions/AssertConditions/Generic/Property.cs index 5007cee255..fa1ae12804 100644 --- a/TUnit.Assertions/AssertConditions/Generic/Property.cs +++ b/TUnit.Assertions/AssertConditions/Generic/Property.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions.AssertConditions.Generic; public class Property : Property { - public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) : base(name, connectorType, otherAssertConditions) { } @@ -17,27 +17,27 @@ public Property(string name) : base(name) public class Property(string name) { private readonly ConnectorType? _connectorType; - private readonly BaseAssertCondition? _otherAssertConditions; + private readonly BaseAssertCondition? _otherAssertConditions; - public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) : this(name) { _connectorType = connectorType; _otherAssertConditions = otherAssertConditions; } - public BaseAssertCondition EqualTo(TExpected expected) + public BaseAssertCondition EqualTo(TExpected expected) { var assertCondition = new PropertyEqualsAssertCondition(name, expected); if (_connectorType is ConnectorType.And) { - return new AssertConditionAnd(_otherAssertConditions!, assertCondition); + return new AssertConditionAnd(_otherAssertConditions!, assertCondition); } if (_connectorType is ConnectorType.Or) { - return new AssertConditionOr(_otherAssertConditions!, assertCondition); + return new AssertConditionOr(_otherAssertConditions!, assertCondition); } return assertCondition; diff --git a/TUnit.Assertions/AssertConditions/Operators/And.cs b/TUnit.Assertions/AssertConditions/Operators/And.cs index e6afc18746..2e7bb92113 100644 --- a/TUnit.Assertions/AssertConditions/Operators/And.cs +++ b/TUnit.Assertions/AssertConditions/Operators/And.cs @@ -2,15 +2,15 @@ namespace TUnit.Assertions.AssertConditions.Operators; -public class And +public class And { - private readonly BaseAssertCondition _otherAssertCondition; + private readonly BaseAssertCondition _otherAssertCondition; - public And(BaseAssertCondition otherAssertCondition) + public And(BaseAssertCondition otherAssertCondition) { _otherAssertCondition = otherAssertCondition; } - public AndIs Is => new(_otherAssertCondition); - public AndHas Has => new(_otherAssertCondition); + public AndIs Is => new(_otherAssertCondition); + public AndHas Has => new(_otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/Or.cs b/TUnit.Assertions/AssertConditions/Operators/Or.cs index 2246c3def5..873437a0dc 100644 --- a/TUnit.Assertions/AssertConditions/Operators/Or.cs +++ b/TUnit.Assertions/AssertConditions/Operators/Or.cs @@ -2,15 +2,15 @@ namespace TUnit.Assertions.AssertConditions.Operators; -public class Or +public class Or { - private readonly BaseAssertCondition _otherAssertCondition; + private readonly BaseAssertCondition _otherAssertCondition; - public Or(BaseAssertCondition otherAssertCondition) + public Or(BaseAssertCondition otherAssertCondition) { _otherAssertCondition = otherAssertCondition; } - public OrIs Is => new(_otherAssertCondition); - public OrHas Has => new(_otherAssertCondition); + public OrIs Is => new(_otherAssertCondition); + public OrHas Has => new(_otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index f1a0099bf0..94fc9a0a78 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -190,7 +190,7 @@ public void String_And_Condition2() public void Count1() { var list = new List { 1, 2, 3 }; - Assert.That(list, Has.Count.EqualTo(3)); + Assert.That(list, Has.Count.EqualTo(3).And.Is.EqualTo("1")); } [Test] From ae792b6a3d3ad199b2d5eedee942a702e1b4998a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:20:07 +0000 Subject: [PATCH 068/124] Tweaks --- .../AssertConditions/AssertCondition.cs | 4 ++-- .../AssertConditions/BaseAssertCondition.cs | 6 +++--- .../EnumerableEquivalentToAssertCondition.cs | 9 +++++++-- .../Collections/HasCountAssertCondition.cs | 6 ++++-- .../Connectors/AssertConditionAnd.cs | 2 +- .../Connectors/AssertConditionOr.cs | 2 +- .../Generic/EqualsAssertCondition.cs | 2 +- .../Generic/PropertyEqualsAssertCondition.cs | 2 +- .../PropertyOrMethodEqualsAssertCondition.cs | 6 ++++-- .../Generic/SameReferenceAssertCondition.cs | 2 +- .../AssertConditions/NotNullAssertCondition.cs | 10 ++++++++++ .../AssertConditions/NullAssertCondition.cs | 16 ++++++++++++++++ .../Numbers/GreaterThanAssertCondition.cs | 4 +++- .../GreaterThanOrEqualToAssertCondition.cs | 4 +++- .../Numbers/IsEvenAssertCondition.cs | 4 +++- .../Numbers/IsOddAssertCondition.cs | 4 +++- .../Numbers/LessThanAssertCondition.cs | 4 +++- .../Numbers/LessThanOrEqualToAssertCondition.cs | 4 +++- .../Numbers/ZeroAssertCondition.cs | 2 +- .../String/StringEqualsAssertCondition.cs | 2 +- TUnit.Assertions/Is.cs | 4 ++++ TUnit.Assertions/Not.cs | 8 ++++++++ TUnit.TestProject/Tests.cs | 1 + 23 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/NullAssertCondition.cs create mode 100644 TUnit.Assertions/Not.cs diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index f4635bc087..84b6039e6a 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -9,11 +9,11 @@ internal AssertCondition(TExpected? expected) ExpectedValue = expected; } - private Func? MessageFactory { get; set; } + private Func? MessageFactory { get; set; } public override string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; - public AssertCondition WithMessage(Func messageFactory) + public AssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index aa9f49387f..c39db025d1 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -10,19 +10,19 @@ internal BaseAssertCondition() Or = new Or(this); } - protected TActual ActualValue { get; set; } = default!; + protected TActual? ActualValue { get; set; } = default!; public abstract string Message { get; } public abstract string DefaultMessage { get; } - public bool Assert(TActual actualValue) + public bool Assert(TActual? actualValue) { ActualValue = actualValue; return Passes(actualValue); } - protected internal abstract bool Passes(TActual actualValue); + protected internal abstract bool Passes(TActual? actualValue); public And And { get; } public Or Or { get; } diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs index 59ee2838aa..5a34a92ef9 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs @@ -8,8 +8,13 @@ public EnumerableEquivalentToAssertCondition(IEnumerable expected) : base(exp public override string DefaultMessage => "The two Enumerables were not equivalent"; - protected internal override bool Passes(IEnumerable actualValue) + protected internal override bool Passes(IEnumerable? actualValue) { - return actualValue.SequenceEqual(ExpectedValue!); + if (actualValue is null && ExpectedValue is null) + { + return true; + } + + return actualValue?.SequenceEqual(ExpectedValue!) ?? false; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index c873ff944d..22976d43bc 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -10,13 +10,15 @@ public HasCountAssertCondition(int expected) : base(expected) public override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; - protected internal override bool Passes(IEnumerable actualValue) + protected internal override bool Passes(IEnumerable? actualValue) { return GetCount(actualValue) == ExpectedValue; } - private int GetCount(IEnumerable actualValue) + private int GetCount(IEnumerable? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + if (actualValue is ICollection collection) { return collection.Count; diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index c616c6293e..1a4e3086e4 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -17,7 +17,7 @@ public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCon public override string DefaultMessage => string.Empty; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { return _condition1.Assert(actualValue) && _condition2.Assert(actualValue); } diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index e3c342f2fe..4c06fe6720 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -14,7 +14,7 @@ public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCond public override string Message => $"{_condition1.Message} & {_condition2.Message}"; public override string DefaultMessage => string.Empty; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { return _condition1.Assert(actualValue) || _condition2.Assert(actualValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index db2baf40e6..707d74e8a6 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -5,7 +5,7 @@ public class EqualsAssertCondition(TExpected expected) { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { return Equals(actualValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index 1c64ad8e44..5c79a32bac 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -7,7 +7,7 @@ public class PropertyEqualsAssertCondition(string propertyNa { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { var propertyValue = GetPropertyValue(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index be264af2aa..6d3be8acdf 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -7,7 +7,7 @@ public class PropertyOrMethodEqualsAssertCondition(string propertyNam { public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(object actualValue) + protected internal override bool Passes(object? actualValue) { var propertyValue = GetPropertyValue(actualValue); @@ -16,8 +16,10 @@ protected internal override bool Passes(object actualValue) return Equals(propertyValue, ExpectedValue); } - private object? GetPropertyValue(object actualValue) + private object? GetPropertyValue(object? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + if (actualValue.GetType().GetProperty(propertyName) is null && actualValue.GetType().GetProperty(propertyName) is null) { diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index f0406d6863..67d248a968 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -9,7 +9,7 @@ public SameReferenceAssertCondition(TExpected expected) : base(expected) public override string DefaultMessage => "The two objects are different references."; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { return ReferenceEquals(actualValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs new file mode 100644 index 0000000000..35bb77ec1c --- /dev/null +++ b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs @@ -0,0 +1,10 @@ +namespace TUnit.Assertions; + +public class NotNullAssertCondition : NullAssertCondition +{ + public override string DefaultMessage => "Value is null"; + protected internal override bool Passes(object? actualValue) + { + return !base.Passes(actualValue); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs new file mode 100644 index 0000000000..5d9c03d7ba --- /dev/null +++ b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs @@ -0,0 +1,16 @@ +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; + +public class NullAssertCondition : AssertCondition +{ + public NullAssertCondition() : base(null) + { + } + + public override string DefaultMessage => $"{ActualValue} is not null"; + protected internal override bool Passes(object? actualValue) + { + return actualValue is null; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index fab8d2220d..c9a3c5c8f8 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -12,8 +12,10 @@ public GreaterThanAssertCondition(TExpected? expected) : base(expected) public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + return actualValue > ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 4fde0c7a40..6ce562e38b 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -12,8 +12,10 @@ public GreaterThanOrEqualToAssertCondition(TExpected? expected) : base(expected) public override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + return actualValue >= ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index 9d6a87d2bd..5595ec3738 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -11,8 +11,10 @@ public IsEvenAssertCondition() : base(default) public override string DefaultMessage => $"{ActualValue} is not even"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + return actualValue % 2 == 0; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index 09651a967d..87d6d16227 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -11,8 +11,10 @@ public IsOddAssertCondition() : base(default) public override string DefaultMessage => $"{ActualValue} is not odd"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + return actualValue % 2 != 0; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index b714f4e746..91f6bf271f 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -12,8 +12,10 @@ public LessThanAssertCondition(TExpected? expected) : base(expected) public override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + return actualValue < ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index 3ef0358c46..7827d8389a 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -12,8 +12,10 @@ public LessThanOrEqualToAssertCondition(TExpected? expected) : base(expected) public override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { + ArgumentNullException.ThrowIfNull(actualValue); + return actualValue <= ExpectedValue!; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index 6d40e27fc0..aa80e9d0ba 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -11,7 +11,7 @@ public ZeroAssertCondition(TActual? expected) : base(expected) public override string DefaultMessage => $"{ActualValue} is not equal to 0"; - protected internal override bool Passes(TActual actualValue) + protected internal override bool Passes(TActual? actualValue) { return actualValue == TActual.Zero; } diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 3d1fa415bb..22fa5cad88 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -9,7 +9,7 @@ public StringEqualsAssertCondition(string expected, StringComparison stringCompa _stringComparison = stringComparison; } - protected internal override bool Passes(string actualValue) + protected internal override bool Passes(string? actualValue) { return string.Equals(actualValue, ExpectedValue, _stringComparison); } diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 52a706ca2e..248347cbdc 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -19,4 +19,8 @@ public static AssertCondition SameReference(T expected) { return new SameReferenceAssertCondition(expected); } + + public static AssertCondition Null => new NullAssertCondition(); + + public static Not Not => new(); } \ No newline at end of file diff --git a/TUnit.Assertions/Not.cs b/TUnit.Assertions/Not.cs new file mode 100644 index 0000000000..9a5227210e --- /dev/null +++ b/TUnit.Assertions/Not.cs @@ -0,0 +1,8 @@ +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; + +public class Not +{ + public AssertCondition Null => new NotNullAssertCondition(); +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 94fc9a0a78..12e2f5c344 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -190,6 +190,7 @@ public void String_And_Condition2() public void Count1() { var list = new List { 1, 2, 3 }; + Assert.That(list, Is.Null); Assert.That(list, Has.Count.EqualTo(3).And.Is.EqualTo("1")); } From c2411cdc7765e94e8e2f87dd1a8a13f027387c97 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 31 Jan 2024 19:40:47 +0000 Subject: [PATCH 069/124] Rework access --- TUnit.Assertions/AssertConditions/AssertCondition.cs | 2 +- .../AssertConditions/BaseAssertCondition.cs | 6 +++--- .../EnumerableEquivalentToAssertCondition.cs | 2 +- .../Collections/HasCountAssertCondition.cs | 2 +- .../Connectors/AssertConditionAnd.cs | 4 ++-- .../AssertConditions/Connectors/AssertConditionOr.cs | 4 ++-- .../Connectors/DelegateAssertConditionAnd.cs | 2 +- .../Connectors/DelegateAssertConditionOr.cs | 2 +- .../Generic/EqualsAssertCondition.cs | 2 +- .../Generic/NotTypeOfAssertCondition.cs | 11 +++++++++++ .../Generic/PropertyEqualsAssertCondition.cs | 2 +- .../Generic/PropertyOrMethodEqualsAssertCondition.cs | 2 +- .../Generic/SameReferenceAssertCondition.cs | 2 +- .../Generic/TypeOfAssertCondition.cs | 12 ++++++++++++ .../AssertConditions/NotNullAssertCondition.cs | 2 +- .../AssertConditions/NullAssertCondition.cs | 2 +- .../Numbers/GreaterThanAssertCondition.cs | 2 +- .../Numbers/GreaterThanOrEqualToAssertCondition.cs | 2 +- .../Numbers/IsEvenAssertCondition.cs | 2 +- .../AssertConditions/Numbers/IsOddAssertCondition.cs | 2 +- .../Numbers/LessThanAssertCondition.cs | 2 +- .../Numbers/LessThanOrEqualToAssertCondition.cs | 2 +- .../AssertConditions/Numbers/ZeroAssertCondition.cs | 2 +- .../String/StringEqualsAssertCondition.cs | 2 +- .../Throws/DelegateAssertCondition.cs | 4 ++-- .../Throws/ThrowsNothingAssertCondition.cs | 2 +- TUnit.Assertions/Is.cs | 1 + TUnit.Assertions/Not.cs | 2 ++ TUnit.TestProject/Tests.cs | 1 + 29 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 84b6039e6a..c08a68258a 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -11,7 +11,7 @@ internal AssertCondition(TExpected? expected) private Func? MessageFactory { get; set; } - public override string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; + protected internal override string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; public AssertCondition WithMessage(Func messageFactory) { diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index c39db025d1..dadf6d3dfd 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -12,11 +12,11 @@ internal BaseAssertCondition() protected TActual? ActualValue { get; set; } = default!; - public abstract string Message { get; } + protected internal abstract string Message { get; } - public abstract string DefaultMessage { get; } + protected abstract string DefaultMessage { get; } - public bool Assert(TActual? actualValue) + internal bool Assert(TActual? actualValue) { ActualValue = actualValue; return Passes(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs index 5a34a92ef9..8d097aee33 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs @@ -6,7 +6,7 @@ public EnumerableEquivalentToAssertCondition(IEnumerable expected) : base(exp { } - public override string DefaultMessage => "The two Enumerables were not equivalent"; + protected override string DefaultMessage => "The two Enumerables were not equivalent"; protected internal override bool Passes(IEnumerable? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index 22976d43bc..3050cff22a 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -8,7 +8,7 @@ public HasCountAssertCondition(int expected) : base(expected) { } - public override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; + protected override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; protected internal override bool Passes(IEnumerable? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index 1a4e3086e4..f3ea454ae5 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -11,11 +11,11 @@ public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCon _condition2 = condition2; } - public override string Message => + protected internal override string Message => !_condition1.Passes(ActualValue) ? _condition1.Message : !_condition2.Passes(ActualValue) ? _condition2.Message : string.Empty; - public override string DefaultMessage => string.Empty; + protected override string DefaultMessage => string.Empty; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index 4c06fe6720..0f82714104 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -11,8 +11,8 @@ public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCond _condition2 = condition2; } - public override string Message => $"{_condition1.Message} & {_condition2.Message}"; - public override string DefaultMessage => string.Empty; + protected internal override string Message => $"{_condition1.Message} & {_condition2.Message}"; + protected override string DefaultMessage => string.Empty; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs index a45542232b..0247152dcd 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs @@ -13,7 +13,7 @@ public DelegateAssertConditionAnd(DelegateAssertCondition condition1, D _condition2 = condition2; } - public override string DefaultMessage => + protected override string DefaultMessage => !_condition1.Passes(ActualValue, Exception) ? _condition1.Message : !_condition2.Passes(ActualValue, Exception) ? _condition2.Message : string.Empty; diff --git a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs index 43635b33b5..9a2f2117b0 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs @@ -13,7 +13,7 @@ public DelegateAssertConditionOr(DelegateAssertCondition condition1, De _condition2 = condition2; } - public override string DefaultMessage => $"{_condition1.Message} & {_condition2.Message}"; + protected override string DefaultMessage => $"{_condition1.Message} & {_condition2.Message}"; protected internal override bool Passes(TActual? actualValue, Exception? exception) { diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 707d74e8a6..02a5e4cd48 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -3,7 +3,7 @@ public class EqualsAssertCondition(TExpected expected) : AssertCondition(expected) { - public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; + protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs new file mode 100644 index 0000000000..8cd7044589 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs @@ -0,0 +1,11 @@ +namespace TUnit.Assertions.AssertConditions.Generic; + +public class NotTypeOfAssertCondition : TypeOfAssertCondition +{ + protected override string DefaultMessage => $"{ActualValue} is {typeof(TExpected).Name}"; + + protected internal override bool Passes(TActual? actualValue) + { + return !base.Passes(actualValue); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index 5c79a32bac..20b5cd8b0d 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Generic; public class PropertyEqualsAssertCondition(string propertyName, TExpected expected) : AssertCondition(expected) { - public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; + protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index 6d3be8acdf..77038d5630 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions.AssertConditions.Generic; public class PropertyOrMethodEqualsAssertCondition(string propertyName, TExpected expected) : AssertCondition(expected) { - public override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; + protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; protected internal override bool Passes(object? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index 67d248a968..8b7b65d851 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -7,7 +7,7 @@ public SameReferenceAssertCondition(TExpected expected) : base(expected) { } - public override string DefaultMessage => "The two objects are different references."; + protected override string DefaultMessage => "The two objects are different references."; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs new file mode 100644 index 0000000000..4857fdcd0e --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs @@ -0,0 +1,12 @@ +namespace TUnit.Assertions.AssertConditions.Generic; + +public class TypeOfAssertCondition() + : AssertCondition(default) +{ + protected override string DefaultMessage => $"{ActualValue} is {ActualValue?.GetType().Name ?? "null"} instead of {typeof(TExpected).Name}"; + + protected internal override bool Passes(TActual? actualValue) + { + return actualValue?.GetType() == typeof(TExpected); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs index 35bb77ec1c..c12c326846 100644 --- a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs @@ -2,7 +2,7 @@ namespace TUnit.Assertions; public class NotNullAssertCondition : NullAssertCondition { - public override string DefaultMessage => "Value is null"; + protected override string DefaultMessage => "Value is null"; protected internal override bool Passes(object? actualValue) { return !base.Passes(actualValue); diff --git a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs index 5d9c03d7ba..954c3958a9 100644 --- a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs @@ -8,7 +8,7 @@ public NullAssertCondition() : base(null) { } - public override string DefaultMessage => $"{ActualValue} is not null"; + protected override string DefaultMessage => $"{ActualValue} is not null"; protected internal override bool Passes(object? actualValue) { return actualValue is null; diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index c9a3c5c8f8..e0596a76c8 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -10,7 +10,7 @@ public GreaterThanAssertCondition(TExpected? expected) : base(expected) { } - public override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; + protected override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 6ce562e38b..944b8b7bd0 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -10,7 +10,7 @@ public GreaterThanOrEqualToAssertCondition(TExpected? expected) : base(expected) { } - public override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; + protected override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index 5595ec3738..20f4e7a6ae 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -9,7 +9,7 @@ public IsEvenAssertCondition() : base(default) { } - public override string DefaultMessage => $"{ActualValue} is not even"; + protected override string DefaultMessage => $"{ActualValue} is not even"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index 87d6d16227..bd332221e7 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -9,7 +9,7 @@ public IsOddAssertCondition() : base(default) { } - public override string DefaultMessage => $"{ActualValue} is not odd"; + protected override string DefaultMessage => $"{ActualValue} is not odd"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index 91f6bf271f..7b5ed393af 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -10,7 +10,7 @@ public LessThanAssertCondition(TExpected? expected) : base(expected) { } - public override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; + protected override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index 7827d8389a..f5dc3e5190 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -10,7 +10,7 @@ public LessThanOrEqualToAssertCondition(TExpected? expected) : base(expected) { } - public override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; + protected override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index aa80e9d0ba..67fec7ff4a 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -9,7 +9,7 @@ public ZeroAssertCondition(TActual? expected) : base(expected) { } - public override string DefaultMessage => $"{ActualValue} is not equal to 0"; + protected override string DefaultMessage => $"{ActualValue} is not equal to 0"; protected internal override bool Passes(TActual? actualValue) { diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 22fa5cad88..1a80a2ac5d 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -14,7 +14,7 @@ protected internal override bool Passes(string? actualValue) return string.Equals(actualValue, ExpectedValue, _stringComparison); } - public override string DefaultMessage => $""" + protected override string DefaultMessage => $""" Expected "{ExpectedValue}" but received "{ActualValue}" """; } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs index 92ffe16020..8fd0a233d0 100644 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs @@ -15,7 +15,7 @@ public bool Assert(DelegateInvocationResult delegateInvocationResult) return Passes(ActualValue, Exception); } - public abstract string DefaultMessage { get; } + protected abstract string DefaultMessage { get; } protected internal abstract bool Passes(T? actualValue, Exception? exception); @@ -41,7 +41,7 @@ public bool Assert(Exception? exception) return Passes(Exception); } - public abstract string DefaultMessage { get; } + protected abstract string DefaultMessage { get; } protected internal abstract bool Passes(Exception? exception); diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index 1631943dfc..494c813b35 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -4,7 +4,7 @@ public class ThrowsNothingAssertCondition : DelegateAssertCondition { private Exception? _exception; - public override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; + protected override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; protected internal override bool Passes(Exception? exception) { diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 248347cbdc..d2c4417859 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -21,6 +21,7 @@ public static AssertCondition SameReference(T expected) } public static AssertCondition Null => new NullAssertCondition(); + public static AssertCondition TypeOf() => new TypeOfAssertCondition(); public static Not Not => new(); } \ No newline at end of file diff --git a/TUnit.Assertions/Not.cs b/TUnit.Assertions/Not.cs index 9a5227210e..9d6f01df78 100644 --- a/TUnit.Assertions/Not.cs +++ b/TUnit.Assertions/Not.cs @@ -1,8 +1,10 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Generic; namespace TUnit.Assertions; public class Not { public AssertCondition Null => new NotNullAssertCondition(); + public AssertCondition TypeOf() => new NotTypeOfAssertCondition(); } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 12e2f5c344..014e120cc0 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -213,6 +213,7 @@ public void Count4() { var list = new[] { 1, 2, 3 }; Assert.That(list, Has.Length.EqualTo(1)); + Assert.That(list, Is.TypeOf().And.Has.Length.EqualTo(1)); } public static int One() => 1; From 4b677ac4518b561b0b8e936404af372f2b2095a4 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:03:16 +0000 Subject: [PATCH 070/124] Fix for length and count strong types --- TUnit.Assertions/AssertConditions/Connectors/AndHas.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs index 4b4ef8a1e9..84664e918a 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs @@ -11,9 +11,10 @@ public AndHas(BaseAssertCondition otherAssertConditions) _otherAssertConditions = otherAssertConditions; } - public Property Count => new("Count", ConnectorType.And, _otherAssertConditions); - public Property Length => new("Length", ConnectorType.And, _otherAssertConditions); + public Property Count => new("Count", ConnectorType.And, _otherAssertConditions); + public Property Length => new("Length", ConnectorType.And, _otherAssertConditions); public Property Value => new("Value", ConnectorType.And, _otherAssertConditions); public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); + public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); } \ No newline at end of file From 02c2fce1d26d9808f8ff2a37961126ecb46cc919 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:05:54 +0000 Subject: [PATCH 071/124] Tweaks --- TUnit.Assertions.UnitTests/ZeroAssertionTests.cs | 2 -- TUnit.Assertions/Has.cs | 7 +++---- TUnit.Assertions/Is_Enumerable.cs | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs index 66a1dbb658..61de8c1b42 100644 --- a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs @@ -1,5 +1,3 @@ -using Is = TUnit.Assertions.Is; - namespace TUnit.Assertions.UnitTests; diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index ca923e13c7..36e8420be4 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -1,5 +1,4 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Generic; namespace TUnit.Assertions; @@ -7,8 +6,8 @@ public static class Has { public static Property Count => new("Count"); public static Property Length => new("Length"); - public static Property Value => new("Value"); + public static Property Value => new("Value"); - public static Property Property(string name) => new(name); + public static Property Property(string name) => new(name); public static Property Property(string name) => new(name); } \ No newline at end of file diff --git a/TUnit.Assertions/Is_Enumerable.cs b/TUnit.Assertions/Is_Enumerable.cs index fdb7fa72f2..eca6328bf7 100644 --- a/TUnit.Assertions/Is_Enumerable.cs +++ b/TUnit.Assertions/Is_Enumerable.cs @@ -1,6 +1,5 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Collections; -using TUnit.Assertions.AssertConditions.String; namespace TUnit.Assertions; From 628e7b1c306f1ca93761b310ff0380b33093aa39 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:15:18 +0000 Subject: [PATCH 072/124] Fix analyzer --- .../Extensions/SyntaxExtensions.cs | 19 +++++++++++++++++++ .../MixAndOrOperatorsAnalyzer.cs | 16 ++++------------ 2 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 TUnit.Analyzers/TUnit.Analyzers/Extensions/SyntaxExtensions.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers/Extensions/SyntaxExtensions.cs b/TUnit.Analyzers/TUnit.Analyzers/Extensions/SyntaxExtensions.cs new file mode 100644 index 0000000000..e4caed1e33 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/Extensions/SyntaxExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace TUnit.Analyzers.Extensions; + +public static class SyntaxExtensions +{ + public static TOutput? GetAncestorSyntaxOfType(this SyntaxNode input) + where TOutput : SyntaxNode + { + var parent = input.Parent; + + while (parent != null && parent is not TOutput) + { + parent = parent.Parent; + } + + return parent as TOutput; + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs index ec86a30b61..0a75fb6475 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/MixAndOrOperatorsAnalyzer.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using TUnit.Analyzers.Extensions; namespace TUnit.Analyzers; @@ -13,8 +14,6 @@ namespace TUnit.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class MixAndOrOperatorsAnalyzer : DiagnosticAnalyzer { - public const string CompanyName = "MyCompany"; - // Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234. public const string DiagnosticId = "TUnit0001"; @@ -31,7 +30,6 @@ public class MixAndOrOperatorsAnalyzer : DiagnosticAnalyzer new LocalizableResourceString(nameof(Resources.TUnit0001Description), Resources.ResourceManager, typeof(Resources)); - // The category of the diagnostic (Design, Naming etc.). private const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, @@ -52,15 +50,9 @@ public override void Initialize(AnalysisContext context) // Subscribe to the Syntax Node with the appropriate 'SyntaxKind' (ClassDeclaration) action. // To figure out which Syntax Nodes you should choose, consider installing the Roslyn syntax tree viewer plugin Rossynt: https://plugins.jetbrains.com/plugin/16902-rossynt/ context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.SimpleMemberAccessExpression); - - // Check other 'context.Register...' methods that might be helpful for your purposes. } - - /// - /// Executed for each Syntax Node with 'SyntaxKind' is 'ClassDeclaration'. - /// - /// Operation context. - private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + + private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { // The Roslyn architecture is based on inheritance. // To get the required metadata, we should match the 'Node' object to the particular type: 'ClassDeclarationSyntax'. @@ -72,7 +64,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) return; } - if (memberAccessExpressionSyntax.Parent?.Parent is not InvocationExpressionSyntax invocationExpressionSyntax) + if (memberAccessExpressionSyntax.GetAncestorSyntaxOfType() is not { } invocationExpressionSyntax) { return; } From f114333485b242f1015f04ab0c7fb0bbd824f441 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 09:47:35 +0000 Subject: [PATCH 073/124] Rework --- .../AssertConditions/Connectors/AndHas.cs | 16 +++---- .../AssertConditions/Connectors/AndIs.cs | 6 +-- .../AssertConditions/Connectors/OrHas.cs | 6 +-- .../AssertConditions/Connectors/OrIs.cs | 6 +-- .../Extensions/AndIsExtensions.cs | 43 +++++++++++++++++++ TUnit.Assertions/Extensions/OrIsExtensions.cs | 43 +++++++++++++++++++ TUnit.TestProject/Tests.cs | 2 +- 7 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 TUnit.Assertions/Extensions/AndIsExtensions.cs create mode 100644 TUnit.Assertions/Extensions/OrIsExtensions.cs diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs index 84664e918a..ca7dd72614 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs @@ -4,17 +4,17 @@ namespace TUnit.Assertions.AssertConditions.Connectors; public class AndHas { - private readonly BaseAssertCondition _otherAssertConditions; + internal BaseAssertCondition OtherAssertCondition { get; } - public AndHas(BaseAssertCondition otherAssertConditions) + public AndHas(BaseAssertCondition otherAssertCondition) { - _otherAssertConditions = otherAssertConditions; + OtherAssertCondition = otherAssertCondition; } - public Property Count => new("Count", ConnectorType.And, _otherAssertConditions); - public Property Length => new("Length", ConnectorType.And, _otherAssertConditions); - public Property Value => new("Value", ConnectorType.And, _otherAssertConditions); + public Property Count => new("Count", ConnectorType.And, OtherAssertCondition); + public Property Length => new("Length", ConnectorType.And, OtherAssertCondition); + public Property Value => new("Value", ConnectorType.And, OtherAssertCondition); - public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); - public Property Property(string name) => new(name, ConnectorType.And, _otherAssertConditions); + public Property Property(string name) => new(name, ConnectorType.And, OtherAssertCondition); + public Property Property(string name) => new(name, ConnectorType.And, OtherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs index 467672b77d..2aca377a24 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs @@ -2,15 +2,15 @@ public class AndIs { - private readonly BaseAssertCondition _otherAssertCondition; + internal BaseAssertCondition OtherAssertCondition { get; } public AndIs(BaseAssertCondition otherAssertCondition) { - _otherAssertCondition = otherAssertCondition; + OtherAssertCondition = otherAssertCondition; } public AssertConditionAnd EqualTo(TExpected expected) { - return new AssertConditionAnd(_otherAssertCondition, Is.EqualTo(expected)); + return new AssertConditionAnd(OtherAssertCondition, Is.EqualTo(expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs index bd8bc8a0c9..e7185c8243 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs @@ -2,10 +2,10 @@ public class OrHas { - private readonly BaseAssertCondition _otherAssertConditions; + internal BaseAssertCondition OtherAssertCondition { get; } - public OrHas(BaseAssertCondition otherAssertConditions) + public OrHas(BaseAssertCondition otherAssertCondition) { - _otherAssertConditions = otherAssertConditions; + OtherAssertCondition = otherAssertCondition; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs index f3a9802dd8..e2477bf1d9 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs @@ -2,15 +2,15 @@ public class OrIs { - private readonly BaseAssertCondition _otherAssertCondition; + internal BaseAssertCondition OtherAssertCondition { get; } public OrIs(BaseAssertCondition otherAssertCondition) { - _otherAssertCondition = otherAssertCondition; + OtherAssertCondition = otherAssertCondition; } public AssertConditionOr EqualTo(TExpected expected) { - return new AssertConditionOr(_otherAssertCondition, Is.EqualTo(expected)); + return new AssertConditionOr(OtherAssertCondition, Is.EqualTo(expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/AndIsExtensions.cs b/TUnit.Assertions/Extensions/AndIsExtensions.cs new file mode 100644 index 0000000000..279258fd2b --- /dev/null +++ b/TUnit.Assertions/Extensions/AndIsExtensions.cs @@ -0,0 +1,43 @@ +using System.Numerics; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Numbers; + +namespace TUnit.Assertions.Extensions; + +public static class AndIsExtensions +{ + public static AssertConditionAnd Zero(this AndIs andIs) where T : INumber => + new(andIs.OtherAssertCondition, new EqualsAssertCondition(T.Zero)); + + public static AssertConditionAnd GreaterThan(this AndIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + } + + public static AssertConditionAnd GreaterThanOrEqualTo(this AndIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(expected)); + } + + public static AssertConditionAnd LessThan(this AndIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + } + + public static AssertConditionAnd LessThanOrEqualTo(this AndIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(expected)); + } + + public static AssertConditionAnd Even(this AndIs andIs) where T : INumber, IModulusOperators + { + return new(andIs.OtherAssertCondition, new IsEvenAssertCondition()); + } + + public static AssertConditionAnd Odd(this AndIs andIs) where T : INumber, IModulusOperators + { + return new(andIs.OtherAssertCondition, new IsOddAssertCondition()); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/OrIsExtensions.cs b/TUnit.Assertions/Extensions/OrIsExtensions.cs new file mode 100644 index 0000000000..40a742b343 --- /dev/null +++ b/TUnit.Assertions/Extensions/OrIsExtensions.cs @@ -0,0 +1,43 @@ +using System.Numerics; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Numbers; + +namespace TUnit.Assertions.Extensions; + +public static class OrIsExtensions +{ + public static AssertConditionOr Zero(this OrIs andIs) where T : INumber => + new(andIs.OtherAssertCondition, new EqualsAssertCondition(T.Zero)); + + public static AssertConditionOr GreaterThan(this OrIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + } + + public static AssertConditionOr GreaterThanOrEqualTo(this OrIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(expected)); + } + + public static AssertConditionOr LessThan(this OrIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + } + + public static AssertConditionOr LessThanOrEqualTo(this OrIs andIs, T expected) where T : INumber + { + return new(andIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(expected)); + } + + public static AssertConditionOr Even(this OrIs andIs) where T : INumber, IModulusOperators + { + return new(andIs.OtherAssertCondition, new IsEvenAssertCondition()); + } + + public static AssertConditionOr Odd(this OrIs andIs) where T : INumber, IModulusOperators + { + return new(andIs.OtherAssertCondition, new IsOddAssertCondition()); + } +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 014e120cc0..0a3127d06e 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -213,7 +213,7 @@ public void Count4() { var list = new[] { 1, 2, 3 }; Assert.That(list, Has.Length.EqualTo(1)); - Assert.That(list, Is.TypeOf().And.Has.Length.EqualTo(1)); + Assert.That(list).); } public static int One() => 1; From 257e22c1962ae20ee38b93d255929f73d1c02f37 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:43:34 +0000 Subject: [PATCH 074/124] Overhaul assertions with AssertionBuilder.cs --- .../TUnit.Analyzers.Sample/Examples.cs | 2 +- .../ZeroAssertionTests.cs | 12 +-- TUnit.Assertions/Assert.cs | 58 +++--------- .../AssertConditions/AssertCondition.cs | 2 +- .../AssertConditions/BaseAssertCondition.cs | 39 ++++++-- .../EnumerableEquivalentToAssertCondition.cs | 4 +- .../Collections/HasCountAssertCondition.cs | 4 +- .../AssertConditions/Connectors/AndIs.cs | 6 +- .../Connectors/AssertConditionAnd.cs | 10 +-- .../Connectors/AssertConditionOr.cs | 6 +- .../AssertConditions/Connectors/OrIs.cs | 6 +- .../Generic/EqualsAssertCondition.cs | 6 +- .../Generic/NotTypeOfAssertCondition.cs | 6 +- .../AssertConditions/Generic/Property.cs | 8 +- .../Generic/PropertyEqualsAssertCondition.cs | 6 +- .../Generic/PropertyOrMethod.cs | 45 ++++++++-- .../PropertyOrMethodEqualsAssertCondition.cs | 6 +- .../Generic/SameReferenceAssertCondition.cs | 4 +- .../Generic/TypeOfAssertCondition.cs | 6 +- .../NotNullAssertCondition.cs | 13 ++- .../AssertConditions/NullAssertCondition.cs | 10 +-- .../Numbers/GreaterThanAssertCondition.cs | 4 +- .../GreaterThanOrEqualToAssertCondition.cs | 4 +- .../Numbers/IsEvenAssertCondition.cs | 4 +- .../Numbers/IsOddAssertCondition.cs | 4 +- .../Numbers/LessThanAssertCondition.cs | 4 +- .../LessThanOrEqualToAssertCondition.cs | 4 +- .../Numbers/ZeroAssertCondition.cs | 4 +- .../String/StringEqualsAssertCondition.cs | 4 +- .../Throws/DelegateAssertCondition.cs | 6 +- TUnit.Assertions/AssertionBuilder.cs | 10 +++ TUnit.Assertions/AssertionData.cs | 7 ++ .../AsyncDelegateAssertionBuilder.cs | 37 ++++++++ TUnit.Assertions/DelegateAssertionBuilder.cs | 37 ++++++++ TUnit.Assertions/DelegateInvocationResult.cs | 7 -- .../Extensions/AndIsExtensions.cs | 14 +-- .../Extensions/DelegateExtensions.cs | 4 +- TUnit.Assertions/Extensions/IsExtensions.cs | 90 +++++++++++++++++++ TUnit.Assertions/Extensions/OrIsExtensions.cs | 28 +++--- TUnit.Assertions/Has.cs | 19 ++-- TUnit.Assertions/Is.cs | 27 +++--- TUnit.Assertions/Is_Enumerable.cs | 12 --- TUnit.Assertions/Is_Numbers.cs | 41 --------- TUnit.Assertions/Is_Strings.cs | 17 ---- TUnit.Assertions/Not.cs | 13 ++- TUnit.Assertions/ValueAssertionBuilder.cs | 17 ++++ TUnit.TestProject/Tests.cs | 62 ++++++------- 47 files changed, 455 insertions(+), 284 deletions(-) create mode 100644 TUnit.Assertions/AssertionBuilder.cs create mode 100644 TUnit.Assertions/AssertionData.cs create mode 100644 TUnit.Assertions/AsyncDelegateAssertionBuilder.cs create mode 100644 TUnit.Assertions/DelegateAssertionBuilder.cs delete mode 100644 TUnit.Assertions/DelegateInvocationResult.cs create mode 100644 TUnit.Assertions/Extensions/IsExtensions.cs delete mode 100644 TUnit.Assertions/Is_Enumerable.cs delete mode 100644 TUnit.Assertions/Is_Numbers.cs delete mode 100644 TUnit.Assertions/Is_Strings.cs create mode 100644 TUnit.Assertions/ValueAssertionBuilder.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs index 7980f7530e..6476d4655d 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs @@ -15,7 +15,7 @@ public class Examples public void ToStars() { - Assert.That("1", Is.EqualTo("1").Or.Is.EqualTo("2").And.Is.EqualTo("1")); + Assert.That("1"); var spaceship = new Spaceship(); spaceship.SetSpeed(300000000); // Invalid value, it should be highlighted. spaceship.SetSpeed(42); diff --git a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs index 61de8c1b42..8d16eb5c28 100644 --- a/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/ZeroAssertionTests.cs @@ -7,41 +7,41 @@ public class ZeroAssertionTests public void Int() { int zero = 0; - Assert.That(zero, Is.Zero); + Assert.That(zero); } [Test] public void Long() { long zero = 0; - Assert.That(zero, Is.Zero); + Assert.That(zero); } [Test] public void Short() { short zero = 0; - Assert.That(zero, Is.Zero); + Assert.That(zero); } [Test] public void Int_Bad() { int zero = 1; - Assert.That(zero, Is.Zero); + Assert.That(zero); } [Test] public void Long_Bad() { long zero = 1; - Assert.That(zero, Is.Zero); + Assert.That(zero); } [Test] public void Short_Bad() { short zero = 1; - Assert.That(zero, Is.Zero); + Assert.That(zero); } } \ No newline at end of file diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 25fa958ab7..6c47c47c9b 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -1,65 +1,29 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Throws; -using TUnit.Assertions.Exceptions; -using TUnit.Assertions.Extensions; - -namespace TUnit.Assertions; +namespace TUnit.Assertions; public static class Assert { - public static void That(TActual value, BaseAssertCondition assertCondition) + public static AssertionBuilder That(TActual value) { - if (!assertCondition.Assert(value)) - { - throw new AssertionException(assertCondition.Message); - } + return new ValueAssertionBuilder(value); } - public static Exception? That(Action value, DelegateAssertCondition assertCondition) + public static AssertionBuilder That(Action value) { - var exception = value.InvokeAndGetException(); - - if (!assertCondition.Assert(exception)) - { - throw new AssertionException(assertCondition.Message); - } - - return exception; + return new DelegateAssertionBuilder(value); } - public static DelegateInvocationResult That(Func value, DelegateAssertCondition assertCondition) + public static AssertionBuilder That(Func value) { - var delegateInvocationResult = value.InvokeAndGetException(); - - if (!assertCondition.Assert(delegateInvocationResult)) - { - throw new AssertionException(assertCondition.Message); - } - - return delegateInvocationResult; + return new DelegateAssertionBuilder(value); } - public static async Task That(Func value, DelegateAssertCondition assertCondition) + public static AssertionBuilder That(Func value) { - var exception = await value.InvokeAndGetExceptionAsync(); - - if (!assertCondition.Assert(exception)) - { - throw new AssertionException(assertCondition.Message); - } - - return exception; + return new AsyncDelegateAssertionBuilder(value); } - public static async Task> That(Func> value, DelegateAssertCondition assertCondition) + public static AssertionBuilder That(Func> value) { - var delegateInvocationResult = await value.InvokeAndGetExceptionAsync(); - - if (!assertCondition.Assert(delegateInvocationResult)) - { - throw new AssertionException(assertCondition.Message); - } - - return delegateInvocationResult; + return new AsyncDelegateAssertionBuilder(value!); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index c08a68258a..eab3f8a2ed 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -4,7 +4,7 @@ public abstract class AssertCondition : BaseAssertCondition< { internal TExpected? ExpectedValue { get; } - internal AssertCondition(TExpected? expected) + internal AssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder) { ExpectedValue = expected; } diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index dadf6d3dfd..ff597f93ae 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -1,28 +1,57 @@ +using System.Runtime.CompilerServices; using TUnit.Assertions.AssertConditions.Operators; +using TUnit.Assertions.Exceptions; namespace TUnit.Assertions.AssertConditions; public abstract class BaseAssertCondition { - internal BaseAssertCondition() + internal readonly AssertionBuilder AssertionBuilder; + + internal BaseAssertCondition(AssertionBuilder assertionBuilder) { + AssertionBuilder = assertionBuilder; + And = new And(this); Or = new Or(this); } - protected TActual? ActualValue { get; set; } = default!; + public TaskAwaiter GetAwaiter() + { + return AssertAsync().GetAwaiter(); + } + + private async Task AssertAsync() + { + var assertionData = await AssertionBuilder.GetAssertionData(); + + AssertAndThrow(assertionData.Result, assertionData.Exception); + } + + protected TActual? ActualValue { get; private set; } + protected Exception? Exception { get; private set; } + protected internal abstract string Message { get; } protected abstract string DefaultMessage { get; } + + private void AssertAndThrow(TActual? actual, Exception? exception) + { + if (!Assert(actual, exception)) + { + throw new AssertionException(Message); + } + } - internal bool Assert(TActual? actualValue) + internal bool Assert(TActual? actualValue, Exception? exception) { ActualValue = actualValue; - return Passes(actualValue); + Exception = exception; + return Passes(actualValue, exception); } - protected internal abstract bool Passes(TActual? actualValue); + protected internal abstract bool Passes(TActual? actualValue, Exception? exception); public And And { get; } public Or Or { get; } diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs index 8d097aee33..99f7848e7b 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs @@ -2,13 +2,13 @@ namespace TUnit.Assertions.AssertConditions.Collections; public class EnumerableEquivalentToAssertCondition : AssertCondition, IEnumerable> { - public EnumerableEquivalentToAssertCondition(IEnumerable expected) : base(expected) + public EnumerableEquivalentToAssertCondition(AssertionBuilder> assertionBuilder, IEnumerable expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => "The two Enumerables were not equivalent"; - protected internal override bool Passes(IEnumerable? actualValue) + protected internal override bool Passes(IEnumerable? actualValue, Exception? exception) { if (actualValue is null && ExpectedValue is null) { diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs index 3050cff22a..0168e05253 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs @@ -4,13 +4,13 @@ namespace TUnit.Assertions.AssertConditions.Collections; public class HasCountAssertCondition : AssertCondition, int> { - public HasCountAssertCondition(int expected) : base(expected) + public HasCountAssertCondition(AssertionBuilder> assertionBuilder, int expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; - protected internal override bool Passes(IEnumerable? actualValue) + protected internal override bool Passes(IEnumerable? actualValue, Exception? exception) { return GetCount(actualValue) == ExpectedValue; } diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs index 2aca377a24..f7e9f45d32 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs @@ -1,4 +1,6 @@ -namespace TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Generic; + +namespace TUnit.Assertions.AssertConditions.Connectors; public class AndIs { @@ -11,6 +13,6 @@ public AndIs(BaseAssertCondition otherAssertCondition) public AssertConditionAnd EqualTo(TExpected expected) { - return new AssertConditionAnd(OtherAssertCondition, Is.EqualTo(expected)); + return new AssertConditionAnd(OtherAssertCondition, new EqualsAssertCondition(OtherAssertCondition.AssertionBuilder, expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index f3ea454ae5..5e87362a42 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -5,20 +5,20 @@ public sealed class AssertConditionAnd : BaseAssertCondition private readonly BaseAssertCondition _condition1; private readonly BaseAssertCondition _condition2; - public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) + public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) { _condition1 = condition1; _condition2 = condition2; } protected internal override string Message => - !_condition1.Passes(ActualValue) ? _condition1.Message : - !_condition2.Passes(ActualValue) ? _condition2.Message : string.Empty; + !_condition1.Passes(ActualValue, Exception) ? _condition1.Message : + !_condition2.Passes(ActualValue, Exception) ? _condition2.Message : string.Empty; protected override string DefaultMessage => string.Empty; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { - return _condition1.Assert(actualValue) && _condition2.Assert(actualValue); + return _condition1.Assert(actualValue, exception) && _condition2.Assert(actualValue, exception); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index 0f82714104..6bbb6a85f7 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -5,7 +5,7 @@ public sealed class AssertConditionOr : BaseAssertCondition private readonly BaseAssertCondition _condition1; private readonly BaseAssertCondition _condition2; - public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) + public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) { _condition1 = condition1; _condition2 = condition2; @@ -14,8 +14,8 @@ public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCond protected internal override string Message => $"{_condition1.Message} & {_condition2.Message}"; protected override string DefaultMessage => string.Empty; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { - return _condition1.Assert(actualValue) || _condition2.Assert(actualValue); + return _condition1.Assert(actualValue, exception) || _condition2.Assert(actualValue, exception); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs index e2477bf1d9..138823e735 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs @@ -1,4 +1,6 @@ -namespace TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Generic; + +namespace TUnit.Assertions.AssertConditions.Connectors; public class OrIs { @@ -11,6 +13,6 @@ public OrIs(BaseAssertCondition otherAssertCondition) public AssertConditionOr EqualTo(TExpected expected) { - return new AssertConditionOr(OtherAssertCondition, Is.EqualTo(expected)); + return new AssertConditionOr(OtherAssertCondition, new EqualsAssertCondition(OtherAssertCondition.AssertionBuilder, expected)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 02a5e4cd48..95d964f3eb 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,11 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class EqualsAssertCondition(TExpected expected) - : AssertCondition(expected) +public class EqualsAssertCondition(AssertionBuilder assertionBuilder, TExpected expected) + : AssertCondition(assertionBuilder, expected) { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return Equals(actualValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs index 8cd7044589..6dca662942 100644 --- a/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs @@ -1,11 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class NotTypeOfAssertCondition : TypeOfAssertCondition +public class NotTypeOfAssertCondition(AssertionBuilder assertionBuilder) : TypeOfAssertCondition(assertionBuilder) { protected override string DefaultMessage => $"{ActualValue} is {typeof(TExpected).Name}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { - return !base.Passes(actualValue); + return !base.Passes(actualValue, exception); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/Property.cs b/TUnit.Assertions/AssertConditions/Generic/Property.cs index fa1ae12804..8cc786a0b6 100644 --- a/TUnit.Assertions/AssertConditions/Generic/Property.cs +++ b/TUnit.Assertions/AssertConditions/Generic/Property.cs @@ -9,18 +9,18 @@ public Property(string name, ConnectorType connectorType, BaseAssertCondition assertionBuilder, string name) : base(assertionBuilder, name) { } } -public class Property(string name) +public class Property(AssertionBuilder assertionBuilder, string name) { private readonly ConnectorType? _connectorType; private readonly BaseAssertCondition? _otherAssertConditions; public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) - : this(name) + : this(otherAssertConditions.AssertionBuilder, name) { _connectorType = connectorType; _otherAssertConditions = otherAssertConditions; @@ -28,7 +28,7 @@ public Property(string name, ConnectorType connectorType, BaseAssertCondition EqualTo(TExpected expected) { - var assertCondition = new PropertyEqualsAssertCondition(name, expected); + var assertCondition = new PropertyEqualsAssertCondition(assertionBuilder, name, expected); if (_connectorType is ConnectorType.And) { diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index 20b5cd8b0d..30f4bb3f26 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -2,12 +2,12 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class PropertyEqualsAssertCondition(string propertyName, TExpected expected) - : AssertCondition(expected) +public class PropertyEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) + : AssertCondition(assertionBuilder, expected) { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { var propertyValue = GetPropertyValue(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs index b30536d01b..f125ce59b4 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs @@ -1,16 +1,45 @@ -namespace TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Connectors; -public class PropertyOrMethod +namespace TUnit.Assertions.AssertConditions.Generic; + +public class PropertyOrMethod : Property { - private readonly string _name; + public PropertyOrMethod(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + : base(name, connectorType, otherAssertConditions) + { + } - public PropertyOrMethod(string name) + public PropertyOrMethod(AssertionBuilder assertionBuilder, string name) : base(assertionBuilder, name) { - _name = name; } - - internal AssertCondition EqualTo(TExpected expected) +} + +public class PropertyOrMethod(AssertionBuilder assertionBuilder, string name) +{ + private readonly ConnectorType? _connectorType; + private readonly BaseAssertCondition? _otherAssertConditions; + + public PropertyOrMethod(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + : this(otherAssertConditions.AssertionBuilder, name) { - return new PropertyOrMethodEqualsAssertCondition(_name, expected); + _connectorType = connectorType; + _otherAssertConditions = otherAssertConditions; + } + + public BaseAssertCondition EqualTo(TExpected expected) + { + var assertCondition = new PropertyEqualsAssertCondition(assertionBuilder, name, expected); + + if (_connectorType is ConnectorType.And) + { + return new AssertConditionAnd(_otherAssertConditions!, assertCondition); + } + + if (_connectorType is ConnectorType.Or) + { + return new AssertConditionOr(_otherAssertConditions!, assertCondition); + } + + return assertCondition; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index 77038d5630..aedb2b2503 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -2,12 +2,12 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class PropertyOrMethodEqualsAssertCondition(string propertyName, TExpected expected) - : AssertCondition(expected) +public class PropertyOrMethodEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) + : AssertCondition(assertionBuilder, expected) { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; - protected internal override bool Passes(object? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { var propertyValue = GetPropertyValue(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index 8b7b65d851..8d464617ed 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -3,13 +3,13 @@ public class SameReferenceAssertCondition : AssertCondition { - public SameReferenceAssertCondition(TExpected expected) : base(expected) + public SameReferenceAssertCondition(AssertionBuilder assertionBuilder, TExpected expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => "The two objects are different references."; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return ReferenceEquals(actualValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs index 4857fdcd0e..207e919e64 100644 --- a/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs @@ -1,11 +1,11 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class TypeOfAssertCondition() - : AssertCondition(default) +public class TypeOfAssertCondition(AssertionBuilder assertionBuilder) + : AssertCondition(assertionBuilder, default) { protected override string DefaultMessage => $"{ActualValue} is {ActualValue?.GetType().Name ?? "null"} instead of {typeof(TExpected).Name}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return actualValue?.GetType() == typeof(TExpected); } diff --git a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs index c12c326846..d066236717 100644 --- a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs @@ -1,10 +1,15 @@ -namespace TUnit.Assertions; +namespace TUnit.Assertions.AssertConditions; -public class NotNullAssertCondition : NullAssertCondition +public class NotNullAssertCondition : NullAssertCondition { + public NotNullAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder) + { + } + protected override string DefaultMessage => "Value is null"; - protected internal override bool Passes(object? actualValue) + + protected internal override bool Passes(TActual? actualValue, Exception? exception) { - return !base.Passes(actualValue); + return !base.Passes(actualValue, exception); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs index 954c3958a9..6492361e2e 100644 --- a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs @@ -1,15 +1,13 @@ -using TUnit.Assertions.AssertConditions; +namespace TUnit.Assertions.AssertConditions; -namespace TUnit.Assertions; - -public class NullAssertCondition : AssertCondition +public class NullAssertCondition : AssertCondition { - public NullAssertCondition() : base(null) + public NullAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { } protected override string DefaultMessage => $"{ActualValue} is not null"; - protected internal override bool Passes(object? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return actualValue is null; } diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs index e0596a76c8..f2a68e499f 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs @@ -6,13 +6,13 @@ public class GreaterThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public GreaterThanAssertCondition(TExpected? expected) : base(expected) + public GreaterThanAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs index 944b8b7bd0..b9cf2fbaa7 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs @@ -6,13 +6,13 @@ public class GreaterThanOrEqualToAssertCondition : AssertCon where TExpected : INumber where TActual : INumber, TExpected { - public GreaterThanOrEqualToAssertCondition(TExpected? expected) : base(expected) + public GreaterThanOrEqualToAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs index 20f4e7a6ae..7591d56ec2 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs @@ -5,13 +5,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsEvenAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsEvenAssertCondition() : base(default) + public IsEvenAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { } protected override string DefaultMessage => $"{ActualValue} is not even"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs index bd332221e7..a603a6d822 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs @@ -5,13 +5,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class IsOddAssertCondition : AssertCondition where TActual : INumber, IModulusOperators { - public IsOddAssertCondition() : base(default) + public IsOddAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { } protected override string DefaultMessage => $"{ActualValue} is not odd"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs index 7b5ed393af..6f19cef647 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs @@ -6,13 +6,13 @@ public class LessThanAssertCondition : AssertCondition where TActual : INumber, TExpected { - public LessThanAssertCondition(TExpected? expected) : base(expected) + public LessThanAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs index f5dc3e5190..a93c0222c3 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs @@ -6,13 +6,13 @@ public class LessThanOrEqualToAssertCondition : AssertCondit where TExpected : INumber where TActual : INumber, TExpected { - public LessThanOrEqualToAssertCondition(TExpected? expected) : base(expected) + public LessThanOrEqualToAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs index 67fec7ff4a..d6bcc6324e 100644 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs @@ -5,13 +5,13 @@ namespace TUnit.Assertions.AssertConditions.Numbers; public class ZeroAssertCondition : AssertCondition where TActual : INumber, IEqualityOperators { - public ZeroAssertCondition(TActual? expected) : base(expected) + public ZeroAssertCondition(AssertionBuilder assertionBuilder, TActual? expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"{ActualValue} is not equal to 0"; - protected internal override bool Passes(TActual? actualValue) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return actualValue == TActual.Zero; } diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index 1a80a2ac5d..b19729fef8 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -4,12 +4,12 @@ public class StringEqualsAssertCondition : AssertCondition { private readonly StringComparison _stringComparison; - public StringEqualsAssertCondition(string expected, StringComparison stringComparison) : base(expected) + public StringEqualsAssertCondition(AssertionBuilder assertionBuilder, string expected, StringComparison stringComparison) : base(assertionBuilder, expected) { _stringComparison = stringComparison; } - protected internal override bool Passes(string? actualValue) + protected internal override bool Passes(string? actualValue, Exception? exception) { return string.Equals(actualValue, ExpectedValue, _stringComparison); } diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs index 8fd0a233d0..ba765854ae 100644 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs @@ -7,10 +7,10 @@ public abstract class DelegateAssertCondition protected Exception? Exception { get; set; } protected T? ActualValue { get; set; } = default!; - public bool Assert(DelegateInvocationResult delegateInvocationResult) + public bool Assert(AssertionData assertionData) { - ActualValue = delegateInvocationResult.Result; - Exception = delegateInvocationResult.Exception; + ActualValue = assertionData.Result; + Exception = assertionData.Exception; return Passes(ActualValue, Exception); } diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs new file mode 100644 index 0000000000..017c102a31 --- /dev/null +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -0,0 +1,10 @@ +namespace TUnit.Assertions; + + +public abstract class AssertionBuilder +{ + protected internal abstract Task> GetAssertionData(); + + public Is Is => new(this); + public Has Has => new(this); +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertionData.cs b/TUnit.Assertions/AssertionData.cs new file mode 100644 index 0000000000..877c00b1ad --- /dev/null +++ b/TUnit.Assertions/AssertionData.cs @@ -0,0 +1,7 @@ +namespace TUnit.Assertions; + +public record AssertionData(T? Result, Exception? Exception) +{ + public static implicit operator AssertionData((T?, Exception?) tuple) => + new(tuple.Item1, tuple.Item2); +} \ No newline at end of file diff --git a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs new file mode 100644 index 0000000000..1c0a5b5b1c --- /dev/null +++ b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs @@ -0,0 +1,37 @@ +using TUnit.Assertions.Extensions; + +namespace TUnit.Assertions; + +public class AsyncDelegateAssertionBuilder : AssertionBuilder +{ + private readonly Func> _function; + + internal AsyncDelegateAssertionBuilder(Func> function) + { + _function = function; + } + + protected internal override async Task> GetAssertionData() + { + var assertionData = await _function.InvokeAndGetExceptionAsync(); + + return assertionData!; + } +} + +public class AsyncDelegateAssertionBuilder : AssertionBuilder +{ + private readonly Func _function; + + internal AsyncDelegateAssertionBuilder(Func function) + { + _function = function; + } + + protected internal override async Task> GetAssertionData() + { + var exception = await _function.InvokeAndGetExceptionAsync(); + + return new AssertionData(null, exception); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/DelegateAssertionBuilder.cs b/TUnit.Assertions/DelegateAssertionBuilder.cs new file mode 100644 index 0000000000..4137f87995 --- /dev/null +++ b/TUnit.Assertions/DelegateAssertionBuilder.cs @@ -0,0 +1,37 @@ +using TUnit.Assertions.Extensions; + +namespace TUnit.Assertions; + +public class DelegateAssertionBuilder : AssertionBuilder +{ + private readonly Func _function; + + internal DelegateAssertionBuilder(Func function) + { + _function = function; + } + + protected internal override Task> GetAssertionData() + { + var assertionData = _function.InvokeAndGetException(); + + return Task.FromResult(assertionData)!; + } +} + +public class DelegateAssertionBuilder : AssertionBuilder +{ + private readonly Action _action; + + internal DelegateAssertionBuilder(Action action) + { + _action = action; + } + + protected internal override Task> GetAssertionData() + { + var exception = _action.InvokeAndGetException(); + + return Task.FromResult(new AssertionData(null, exception)); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/DelegateInvocationResult.cs b/TUnit.Assertions/DelegateInvocationResult.cs deleted file mode 100644 index 059eb50ae4..0000000000 --- a/TUnit.Assertions/DelegateInvocationResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TUnit.Assertions; - -public record DelegateInvocationResult(T? Result, Exception? Exception) -{ - public static implicit operator DelegateInvocationResult((T?, Exception?) tuple) => - new(tuple.Item1, tuple.Item2); -} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/AndIsExtensions.cs b/TUnit.Assertions/Extensions/AndIsExtensions.cs index 279258fd2b..b99e488941 100644 --- a/TUnit.Assertions/Extensions/AndIsExtensions.cs +++ b/TUnit.Assertions/Extensions/AndIsExtensions.cs @@ -9,35 +9,35 @@ namespace TUnit.Assertions.Extensions; public static class AndIsExtensions { public static AssertConditionAnd Zero(this AndIs andIs) where T : INumber => - new(andIs.OtherAssertCondition, new EqualsAssertCondition(T.Zero)); + new(andIs.OtherAssertCondition, new EqualsAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, T.Zero)); public static AssertConditionAnd GreaterThan(this AndIs andIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); } public static AssertConditionAnd GreaterThanOrEqualTo(this AndIs andIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(expected)); + return new(andIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); } public static AssertConditionAnd LessThan(this AndIs andIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); } public static AssertConditionAnd LessThanOrEqualTo(this AndIs andIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(expected)); + return new(andIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); } public static AssertConditionAnd Even(this AndIs andIs) where T : INumber, IModulusOperators { - return new(andIs.OtherAssertCondition, new IsEvenAssertCondition()); + return new(andIs.OtherAssertCondition, new IsEvenAssertCondition(andIs.OtherAssertCondition.AssertionBuilder)); } public static AssertConditionAnd Odd(this AndIs andIs) where T : INumber, IModulusOperators { - return new(andIs.OtherAssertCondition, new IsOddAssertCondition()); + return new(andIs.OtherAssertCondition, new IsOddAssertCondition(andIs.OtherAssertCondition.AssertionBuilder)); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/DelegateExtensions.cs b/TUnit.Assertions/Extensions/DelegateExtensions.cs index bf4c3abe2e..42a6409283 100644 --- a/TUnit.Assertions/Extensions/DelegateExtensions.cs +++ b/TUnit.Assertions/Extensions/DelegateExtensions.cs @@ -28,7 +28,7 @@ internal static class DelegateExtensions } } - public static async Task> InvokeAndGetExceptionAsync(this Func> action) + public static async Task> InvokeAndGetExceptionAsync(this Func> action) { try { @@ -40,7 +40,7 @@ public static async Task> InvokeAndGetExceptionAsync } } - public static DelegateInvocationResult InvokeAndGetException(this Func action) + public static AssertionData InvokeAndGetException(this Func action) { try { diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs new file mode 100644 index 0000000000..68ad264e16 --- /dev/null +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Numerics; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Collections; +using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Numbers; +using TUnit.Assertions.AssertConditions.String; + +namespace TUnit.Assertions.Extensions; + +public static class IsExtensions +{ + #region Strings + + public static AssertCondition EqualTo(this Is @is, string expected) + { + return new StringEqualsAssertCondition(@is.AssertionBuilder, expected, StringComparison.Ordinal); + } + + public static AssertCondition EqualTo(this Is @is, string expected, StringComparison stringComparison) + { + return new StringEqualsAssertCondition(@is.AssertionBuilder, expected, stringComparison); + } + + #endregion + + #region Numbers + + public static AssertCondition Zero(this Is @is) + where TActual : INumber + { + return new EqualsAssertCondition(@is.AssertionBuilder, TActual.Zero); + } + + public static AssertCondition GreaterThan(this Is @is, T expected) where T : INumber + { + return new GreaterThanAssertCondition(@is.AssertionBuilder, expected); + } + + public static AssertCondition GreaterThanOrEqualTo(this Is @is, T expected) where T : INumber + { + return new GreaterThanOrEqualToAssertCondition(@is.AssertionBuilder, expected); + } + + public static AssertCondition LessThan(this Is @is, T expected) where T : INumber + { + return new GreaterThanAssertCondition(@is.AssertionBuilder, expected); + } + + public static AssertCondition LessThanOrEqualTo(this Is @is, T expected) where T : INumber + { + return new LessThanOrEqualToAssertCondition(@is.AssertionBuilder, expected); + } + + public static AssertCondition Even(this Is @is) where T : INumber, IModulusOperators + { + return new IsEvenAssertCondition(@is.AssertionBuilder); + } + + public static AssertCondition Odd(this Is @is) where T : INumber, IModulusOperators + { + return new IsOddAssertCondition(@is.AssertionBuilder); + } + + #endregion + + #region Enumerables + + public static EnumerableEquivalentToAssertCondition EquivalentTo(this Is> @is, T expected) + where T : IEnumerable + { + return new EnumerableEquivalentToAssertCondition(@is.AssertionBuilder, expected); + } + + #endregion + + #region Booleans + + public static AssertCondition True(this Is @is) + { + return new EqualsAssertCondition(@is.AssertionBuilder, true); + } + + public static AssertCondition False(this Is @is) + { + return new EqualsAssertCondition(@is.AssertionBuilder, false); + } + + #endregion +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/OrIsExtensions.cs b/TUnit.Assertions/Extensions/OrIsExtensions.cs index 40a742b343..66ad303ee8 100644 --- a/TUnit.Assertions/Extensions/OrIsExtensions.cs +++ b/TUnit.Assertions/Extensions/OrIsExtensions.cs @@ -8,36 +8,36 @@ namespace TUnit.Assertions.Extensions; public static class OrIsExtensions { - public static AssertConditionOr Zero(this OrIs andIs) where T : INumber => - new(andIs.OtherAssertCondition, new EqualsAssertCondition(T.Zero)); + public static AssertConditionOr Zero(this OrIs orIs) where T : INumber => + new(orIs.OtherAssertCondition, new EqualsAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, T.Zero)); - public static AssertConditionOr GreaterThan(this OrIs andIs, T expected) where T : INumber + public static AssertConditionOr GreaterThan(this OrIs orIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + return new(orIs.OtherAssertCondition, new GreaterThanAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); } - public static AssertConditionOr GreaterThanOrEqualTo(this OrIs andIs, T expected) where T : INumber + public static AssertConditionOr GreaterThanOrEqualTo(this OrIs orIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(expected)); + return new(orIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); } - public static AssertConditionOr LessThan(this OrIs andIs, T expected) where T : INumber + public static AssertConditionOr LessThan(this OrIs orIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(expected)); + return new(orIs.OtherAssertCondition, new GreaterThanAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); } - public static AssertConditionOr LessThanOrEqualTo(this OrIs andIs, T expected) where T : INumber + public static AssertConditionOr LessThanOrEqualTo(this OrIs orIs, T expected) where T : INumber { - return new(andIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(expected)); + return new(orIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); } - public static AssertConditionOr Even(this OrIs andIs) where T : INumber, IModulusOperators + public static AssertConditionOr Even(this OrIs orIs) where T : INumber, IModulusOperators { - return new(andIs.OtherAssertCondition, new IsEvenAssertCondition()); + return new(orIs.OtherAssertCondition, new IsEvenAssertCondition(orIs.OtherAssertCondition.AssertionBuilder)); } - public static AssertConditionOr Odd(this OrIs andIs) where T : INumber, IModulusOperators + public static AssertConditionOr Odd(this OrIs orIs) where T : INumber, IModulusOperators { - return new(andIs.OtherAssertCondition, new IsOddAssertCondition()); + return new(orIs.OtherAssertCondition, new IsOddAssertCondition(orIs.OtherAssertCondition.AssertionBuilder)); } } \ No newline at end of file diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 36e8420be4..48d6346d6a 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -2,12 +2,19 @@ namespace TUnit.Assertions; -public static class Has +public class Has { - public static Property Count => new("Count"); - public static Property Length => new("Length"); - public static Property Value => new("Value"); + internal AssertionBuilder AssertionBuilder { get; } + + public Has(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public Property Count => new(AssertionBuilder, "Count"); + public Property Length => new(AssertionBuilder, "Length"); + public Property Value => new(AssertionBuilder, "Value"); - public static Property Property(string name) => new(name); - public static Property Property(string name) => new(name); + public Property Property(string name) => new(AssertionBuilder, name); + public Property Property(string name) => new(AssertionBuilder, name); } \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index d2c4417859..5f6fe44e7a 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -3,25 +3,32 @@ namespace TUnit.Assertions; -public partial class Is +public partial class Is { - public static AssertCondition EqualTo(TExpected expected) + internal readonly AssertionBuilder AssertionBuilder; + + public Is(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition EqualTo(TActual expected) { - return new EqualsAssertCondition(expected); + return new EqualsAssertCondition(AssertionBuilder, expected); } - internal static AssertCondition EqualTo(TExpected expected) + internal AssertCondition EqualTo(TExpected expected) { - return new EqualsAssertCondition(expected); + return new EqualsAssertCondition(AssertionBuilder, expected); } - public static AssertCondition SameReference(T expected) + public AssertCondition SameReference(TActual expected) { - return new SameReferenceAssertCondition(expected); + return new SameReferenceAssertCondition(AssertionBuilder, expected); } - public static AssertCondition Null => new NullAssertCondition(); - public static AssertCondition TypeOf() => new TypeOfAssertCondition(); + public AssertCondition Null => new NullAssertCondition(AssertionBuilder); + public AssertCondition TypeOf() => new TypeOfAssertCondition(AssertionBuilder); - public static Not Not => new(); + public Not Not => new(AssertionBuilder); } \ No newline at end of file diff --git a/TUnit.Assertions/Is_Enumerable.cs b/TUnit.Assertions/Is_Enumerable.cs deleted file mode 100644 index eca6328bf7..0000000000 --- a/TUnit.Assertions/Is_Enumerable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Collections; - -namespace TUnit.Assertions; - -public static partial class Is -{ - public static AssertCondition, IEnumerable> EquivalentTo(IEnumerable expected) - { - return new EnumerableEquivalentToAssertCondition(expected); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/Is_Numbers.cs b/TUnit.Assertions/Is_Numbers.cs deleted file mode 100644 index aa1af3b799..0000000000 --- a/TUnit.Assertions/Is_Numbers.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Numerics; -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Generic; -using TUnit.Assertions.AssertConditions.Numbers; - -namespace TUnit.Assertions; - -public static partial class Is -{ - public static AssertCondition Zero => new EqualsAssertCondition(0); - - public static AssertCondition GreaterThan(T expected) where T : INumber - { - return new GreaterThanAssertCondition(expected); - } - - public static AssertCondition GreaterThanOrEqualTo(T expected) where T : INumber - { - return new GreaterThanOrEqualToAssertCondition(expected); - } - - public static AssertCondition LessThan(T expected) where T : INumber - { - return new GreaterThanAssertCondition(expected); - } - - public static AssertCondition LessThanOrEqualTo(T expected) where T : INumber - { - return new LessThanOrEqualToAssertCondition(expected); - } - - public static AssertCondition Even() where T : INumber, IModulusOperators - { - return new IsEvenAssertCondition(); - } - - public static AssertCondition Odd() where T : INumber, IModulusOperators - { - return new IsOddAssertCondition(); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/Is_Strings.cs b/TUnit.Assertions/Is_Strings.cs deleted file mode 100644 index 79da228e33..0000000000 --- a/TUnit.Assertions/Is_Strings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.String; - -namespace TUnit.Assertions; - -public static partial class Is -{ - public static AssertCondition EqualTo(string expected) - { - return new StringEqualsAssertCondition(expected, StringComparison.Ordinal); - } - - public static AssertCondition EqualTo(string expected, StringComparison stringComparison) - { - return new StringEqualsAssertCondition(expected, stringComparison); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/Not.cs b/TUnit.Assertions/Not.cs index 9d6f01df78..b50cbfa2e8 100644 --- a/TUnit.Assertions/Not.cs +++ b/TUnit.Assertions/Not.cs @@ -3,8 +3,15 @@ namespace TUnit.Assertions; -public class Not +public class Not { - public AssertCondition Null => new NotNullAssertCondition(); - public AssertCondition TypeOf() => new NotTypeOfAssertCondition(); + internal readonly AssertionBuilder AssertionBuilder; + + public Not(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition Null => new NotNullAssertCondition(AssertionBuilder); + public AssertCondition TypeOf() => new NotTypeOfAssertCondition(AssertionBuilder); } \ No newline at end of file diff --git a/TUnit.Assertions/ValueAssertionBuilder.cs b/TUnit.Assertions/ValueAssertionBuilder.cs new file mode 100644 index 0000000000..758ee74ab4 --- /dev/null +++ b/TUnit.Assertions/ValueAssertionBuilder.cs @@ -0,0 +1,17 @@ +namespace TUnit.Assertions; + + +public class ValueAssertionBuilder : AssertionBuilder +{ + private readonly T? _value; + + internal ValueAssertionBuilder(T? value) + { + _value = value; + } + + protected internal override Task> GetAssertionData() + { + return Task.FromResult(new AssertionData(_value, null)); + } +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 0a3127d06e..e92a28fc30 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,4 +1,5 @@ using TUnit.Assertions; +using TUnit.Assertions.Extensions; using TUnit.Core; using TUnit.Core.Attributes; @@ -15,14 +16,14 @@ public class Tests public void Test1() { var value = "1"; - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [Test] public void Test2() { var value = "2"; - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [Test] @@ -30,7 +31,7 @@ public async Task Test3() { await Task.Yield(); var value = "1"; - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [Test] @@ -38,14 +39,14 @@ public async Task Test4() { await Task.Yield(); var value = "2"; - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [TestWithData("1")] [TestWithData("2")] public void ParameterisedTests1(string value) { - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [TestWithData("1")] @@ -53,14 +54,14 @@ public void ParameterisedTests1(string value) public async Task ParameterisedTests2(string value) { await Task.Yield(); - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [Test, Skip("Reason1")] public void Skip1() { var value = "1"; - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [Test, Skip("Reason2")] @@ -68,77 +69,77 @@ public async Task Skip2() { await Task.Yield(); var value = "1"; - Assert.That(value, Is.EqualTo("1")); + Assert.That(value); } [TestDataSource(nameof(One))] public void TestDataSource1(int value) { - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(One))] public async Task TestDataSource2(int value) { await Task.Yield(); - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(Two))] public void TestDataSource3(int value) { - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(Two))] public async Task TestDataSource4(int value) { await Task.Yield(); - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(TestDataSources), nameof(One))] public void TestDataSource5(int value) { - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(TestDataSources), nameof(One))] public async Task TestDataSource6(int value) { await Task.Yield(); - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(TestDataSources), nameof(Two))] public void TestDataSource7(int value) { - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [TestDataSource(nameof(TestDataSources), nameof(Two))] public async Task TestDataSource8(int value) { await Task.Yield(); - Assert.That(value, Is.EqualTo(1)); + Assert.That(value); } [Test] public void TestContext1() { - Assert.That(TestContext.Current.TestName, Is.EqualTo(nameof(TestContext1))); + Assert.That(TestContext.Current.TestName); } [Test] public void TestContext2() { - Assert.That(TestContext.Current.TestName, Is.EqualTo(nameof(TestContext1))); + Assert.That(TestContext.Current.TestName); } [Test] public void Throws1() { - Assert.That(() => new string([]), Throws.Nothing); + Assert.That(() => new string([])); } [Test] @@ -148,13 +149,13 @@ await Assert.That(async () => { await Task.Yield(); new string([]); - }, Throws.Nothing); + }).Is.EqualTo(string.Empty); } [Test] public void Throws3() { - Assert.That(() => throw new ApplicationException(), Throws.Nothing); + Assert.That(() => throw new ApplicationException()); } [Test] @@ -163,8 +164,8 @@ public async Task Throws4() await Assert.That(async () => { await Task.Yield(); - throw new ApplicationException(); - }, Throws.Nothing); + return true; + }).Is.False(); } [Test, Timeout(500)] @@ -176,44 +177,43 @@ public async Task Timeout1() [Test] public void String_And_Condition() { - Assert.That("1", Is.EqualTo("1").And.Is.EqualTo("1").Or.Is.EqualTo("2")); + Assert.That("1"); } [Test] public void String_And_Condition2() { - Assert.That("1", Is.EqualTo("1").And.Is.EqualTo("1").And.Is.EqualTo("2")); + Assert.That("1"); } [Test] public void Count1() { var list = new List { 1, 2, 3 }; - Assert.That(list, Is.Null); - Assert.That(list, Has.Count.EqualTo(3).And.Is.EqualTo("1")); + Assert.That(list); + Assert.That(list); } [Test] public void Count2() { var list = new List { 1, 2, 3 }; - Assert.That(list, Has.Count.EqualTo(1)); + Assert.That(list); } [Test] public void Count3() { var list = new[] { 1, 2, 3 }; - Assert.That(list, Has.Length.EqualTo(3)); + Assert.That(list); } [Test] public void Count4() { var list = new[] { 1, 2, 3 }; - Assert.That(list, Has.Length.EqualTo(1)); - Assert.That(list).); + Assert.That(list).Is.EquivalentTo(new []{1}); } public static int One() => 1; From 9a050504eca14d0cf118db6254960d85fdb27d2a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:16:21 +0000 Subject: [PATCH 075/124] Tweak namespaces --- .../AwaitAssertionAnalyzerTests.cs | 31 ++++++++++++++++ .../MixAndOrOperatorsAnalyzerTests.cs | 25 ------------- .../AnalyzerReleases.Shipped.md | 1 + .../TUnit.Analyzers/Resources.Designer.cs | 36 +++++++++++++++++++ .../TUnit.Analyzers/Resources.resx | 12 +++++++ .../Generic/PropertyEqualsAssertCondition.cs | 2 -- .../PropertyOrMethodEqualsAssertCondition.cs | 2 -- .../AsyncDelegateAssertionBuilder.cs | 4 +-- TUnit.Assertions/DelegateAssertionBuilder.cs | 4 +-- .../Extensions/AndIsExtensions.cs | 3 +- .../Extensions/DelegateExtensions.cs | 2 +- TUnit.Assertions/Extensions/IsExtensions.cs | 5 ++- TUnit.Assertions/Extensions/OrIsExtensions.cs | 3 +- .../Extensions/ReflectionExtensions.cs | 2 +- TUnit.Core/Attributes/CleanUpAttribute.cs | 2 +- .../Attributes/OneTimeCleanUpAttribute.cs | 2 +- .../Attributes/OneTimeSetUpAttribute.cs | 2 +- TUnit.Core/Attributes/RepeatAttribute.cs | 2 +- TUnit.Core/Attributes/RetryAttribute.cs | 2 +- TUnit.Core/Attributes/SetUpAttribute.cs | 2 +- TUnit.Core/Attributes/SkipAttribute.cs | 2 +- TUnit.Core/Attributes/TUnitAttribute.cs | 2 +- TUnit.Core/Attributes/TestAttribute.cs | 2 +- .../Attributes/TestDataSourceAttribute.cs | 2 +- .../Attributes/TestWithDataAttribute.cs | 2 +- TUnit.Core/Attributes/TimeoutAttribute.cs | 2 +- TUnit.Core/TestDetails.cs | 1 - TUnit.Engine/SingleTestExecutor.cs | 1 - TUnit.Engine/TestClassCreator.cs | 1 - TUnit.TestAdapter/AsyncTestRunExecutor.cs | 1 - TUnit.TestAdapter/TestsLoader.cs | 1 - TUnit.TestProject/Tests.cs | 4 +-- 32 files changed, 101 insertions(+), 64 deletions(-) create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs delete mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs new file mode 100644 index 0000000000..fdcd6c5851 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Xunit; +using Verifier = + Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< + TUnit.Analyzers.AwaitAssertionAnalyzer>; + +namespace TUnit.Analyzers.Tests; + +public class AwaitAssertionAnalyzerTests +{ + [Fact] + public async Task ClassWithMyCompanyTitle_AlertDiagnostic() + { + const string text = """ + public class MyClass + { + + public async Task MyTest() + { + var one = 1; + {|#0:Assert.That(one).Is.EqualTo(1)|}; + } + + } + """; + + var expected = Verifier.Diagnostic().WithLocation(0); + + await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs deleted file mode 100644 index 12b0fe68e9..0000000000 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/MixAndOrOperatorsAnalyzerTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; -using Xunit; -using Verifier = - Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< - TUnit.Analyzers.MixAndOrOperatorsAnalyzer>; - -namespace TUnit.Analyzers.Tests; - -public class MixAndOrOperatorsAnalyzerTests -{ - [Fact] - public async Task ClassWithMyCompanyTitle_AlertDiagnostic() - { - const string text = @" -public class MyCompanyClass -{ -} -"; - - var expected = Verifier.Diagnostic() - .WithLocation(2, 14) - .WithArguments("MyCompanyClass"); - await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md index b554548217..b49126e4dc 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md +++ b/TUnit.Analyzers/TUnit.Analyzers/AnalyzerReleases.Shipped.md @@ -5,3 +5,4 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------------------------------------------------ TUnit0001 | Usage | Warning | Don't mix 'Or' & 'And' operators in assertions. +TUnit0002 | Usage | Error | Assert statements must be awaited. diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs index 788d5a7579..e760496b65 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.Designer.cs @@ -94,5 +94,41 @@ internal static string TUnit0001Title { return ResourceManager.GetString("TUnit0001Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to Assert statements must be awaited. + /// + internal static string TUnit0002CodeFixTitle { + get { + return ResourceManager.GetString("TUnit0002CodeFixTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assert statements must be awaited.. + /// + internal static string TUnit0002Description { + get { + return ResourceManager.GetString("TUnit0002Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assert statements must be awaited. + /// + internal static string TUnit0002MessageFormat { + get { + return ResourceManager.GetString("TUnit0002MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assert statements must be awaited. + /// + internal static string TUnit0002Title { + get { + return ResourceManager.GetString("TUnit0002Title", resourceCulture); + } + } } } diff --git a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx index 8405b41080..97b894095d 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/TUnit.Analyzers/Resources.resx @@ -30,4 +30,16 @@ Don't mix 'Or' & 'And' operators in assertionsThe title of the code fix. + + Assert statements must be awaited.An optional longer localizable description of the diagnostic. + + + Assert statements must be awaitedThe format-able message the diagnostic displays. + + + Assert statements must be awaitedThe title of the diagnostic. + + + Assert statements must be awaitedThe title of the code fix. + \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index 30f4bb3f26..cd46f8e2d2 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -1,5 +1,3 @@ -using TUnit.Assertions.Extensions; - namespace TUnit.Assertions.AssertConditions.Generic; public class PropertyEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index aedb2b2503..f37d7ece05 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -1,5 +1,3 @@ -using TUnit.Assertions.Extensions; - namespace TUnit.Assertions.AssertConditions.Generic; public class PropertyOrMethodEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) diff --git a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs index 1c0a5b5b1c..2dfee5eef3 100644 --- a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs @@ -1,6 +1,4 @@ -using TUnit.Assertions.Extensions; - -namespace TUnit.Assertions; +namespace TUnit.Assertions; public class AsyncDelegateAssertionBuilder : AssertionBuilder { diff --git a/TUnit.Assertions/DelegateAssertionBuilder.cs b/TUnit.Assertions/DelegateAssertionBuilder.cs index 4137f87995..fd829c29a2 100644 --- a/TUnit.Assertions/DelegateAssertionBuilder.cs +++ b/TUnit.Assertions/DelegateAssertionBuilder.cs @@ -1,6 +1,4 @@ -using TUnit.Assertions.Extensions; - -namespace TUnit.Assertions; +namespace TUnit.Assertions; public class DelegateAssertionBuilder : AssertionBuilder { diff --git a/TUnit.Assertions/Extensions/AndIsExtensions.cs b/TUnit.Assertions/Extensions/AndIsExtensions.cs index b99e488941..6fb15b66ba 100644 --- a/TUnit.Assertions/Extensions/AndIsExtensions.cs +++ b/TUnit.Assertions/Extensions/AndIsExtensions.cs @@ -1,10 +1,9 @@ using System.Numerics; -using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Connectors; using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; -namespace TUnit.Assertions.Extensions; +namespace TUnit.Assertions; public static class AndIsExtensions { diff --git a/TUnit.Assertions/Extensions/DelegateExtensions.cs b/TUnit.Assertions/Extensions/DelegateExtensions.cs index 42a6409283..c6feb458bb 100644 --- a/TUnit.Assertions/Extensions/DelegateExtensions.cs +++ b/TUnit.Assertions/Extensions/DelegateExtensions.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.Extensions; +namespace TUnit.Assertions; internal static class DelegateExtensions { diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs index 68ad264e16..fb1e8d22ff 100644 --- a/TUnit.Assertions/Extensions/IsExtensions.cs +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -1,12 +1,11 @@ -using System.Collections; -using System.Numerics; +using System.Numerics; using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Collections; using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; using TUnit.Assertions.AssertConditions.String; -namespace TUnit.Assertions.Extensions; +namespace TUnit.Assertions; public static class IsExtensions { diff --git a/TUnit.Assertions/Extensions/OrIsExtensions.cs b/TUnit.Assertions/Extensions/OrIsExtensions.cs index 66ad303ee8..df07861aae 100644 --- a/TUnit.Assertions/Extensions/OrIsExtensions.cs +++ b/TUnit.Assertions/Extensions/OrIsExtensions.cs @@ -1,10 +1,9 @@ using System.Numerics; -using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Connectors; using TUnit.Assertions.AssertConditions.Generic; using TUnit.Assertions.AssertConditions.Numbers; -namespace TUnit.Assertions.Extensions; +namespace TUnit.Assertions; public static class OrIsExtensions { diff --git a/TUnit.Assertions/Extensions/ReflectionExtensions.cs b/TUnit.Assertions/Extensions/ReflectionExtensions.cs index f4dc858528..20de79fe97 100644 --- a/TUnit.Assertions/Extensions/ReflectionExtensions.cs +++ b/TUnit.Assertions/Extensions/ReflectionExtensions.cs @@ -1,4 +1,4 @@ -namespace TUnit.Assertions.Extensions; +namespace TUnit.Assertions; public static class ReflectionExtensions { diff --git a/TUnit.Core/Attributes/CleanUpAttribute.cs b/TUnit.Core/Attributes/CleanUpAttribute.cs index 4db2f2456b..c64e7850ff 100644 --- a/TUnit.Core/Attributes/CleanUpAttribute.cs +++ b/TUnit.Core/Attributes/CleanUpAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class CleanUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs b/TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs index 155d570b55..59a72b50ba 100644 --- a/TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs +++ b/TUnit.Core/Attributes/OneTimeCleanUpAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class OneTimeCleanUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs b/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs index 40c13ba781..1d81e422e2 100644 --- a/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs +++ b/TUnit.Core/Attributes/OneTimeSetUpAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class OneTimeSetUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/RepeatAttribute.cs b/TUnit.Core/Attributes/RepeatAttribute.cs index c59dc96a83..49ca27ff08 100644 --- a/TUnit.Core/Attributes/RepeatAttribute.cs +++ b/TUnit.Core/Attributes/RepeatAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class RepeatAttribute : TUnitAttribute diff --git a/TUnit.Core/Attributes/RetryAttribute.cs b/TUnit.Core/Attributes/RetryAttribute.cs index d9d262512c..a7c4538ddc 100644 --- a/TUnit.Core/Attributes/RetryAttribute.cs +++ b/TUnit.Core/Attributes/RetryAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class RetryAttribute : TUnitAttribute diff --git a/TUnit.Core/Attributes/SetUpAttribute.cs b/TUnit.Core/Attributes/SetUpAttribute.cs index 96c1c4b5c9..16e11ba32c 100644 --- a/TUnit.Core/Attributes/SetUpAttribute.cs +++ b/TUnit.Core/Attributes/SetUpAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class SetUpAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/SkipAttribute.cs b/TUnit.Core/Attributes/SkipAttribute.cs index 311a6cbfd0..ff2ae4a9a5 100644 --- a/TUnit.Core/Attributes/SkipAttribute.cs +++ b/TUnit.Core/Attributes/SkipAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class SkipAttribute : TUnitAttribute diff --git a/TUnit.Core/Attributes/TUnitAttribute.cs b/TUnit.Core/Attributes/TUnitAttribute.cs index d8eb96163a..5b6efc3f3e 100644 --- a/TUnit.Core/Attributes/TUnitAttribute.cs +++ b/TUnit.Core/Attributes/TUnitAttribute.cs @@ -1,3 +1,3 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; public class TUnitAttribute : Attribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/TestAttribute.cs b/TUnit.Core/Attributes/TestAttribute.cs index 48c90ae33d..92c84c276c 100644 --- a/TUnit.Core/Attributes/TestAttribute.cs +++ b/TUnit.Core/Attributes/TestAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method)] public class TestAttribute : TUnitAttribute; \ No newline at end of file diff --git a/TUnit.Core/Attributes/TestDataSourceAttribute.cs b/TUnit.Core/Attributes/TestDataSourceAttribute.cs index e6421151bc..8966a678a8 100644 --- a/TUnit.Core/Attributes/TestDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestDataSourceAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public class TestDataSourceAttribute : TUnitAttribute diff --git a/TUnit.Core/Attributes/TestWithDataAttribute.cs b/TUnit.Core/Attributes/TestWithDataAttribute.cs index 64f8a17825..6a7209246e 100644 --- a/TUnit.Core/Attributes/TestWithDataAttribute.cs +++ b/TUnit.Core/Attributes/TestWithDataAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class TestWithDataAttribute : TUnitAttribute diff --git a/TUnit.Core/Attributes/TimeoutAttribute.cs b/TUnit.Core/Attributes/TimeoutAttribute.cs index ce77a920f5..23583a0277 100644 --- a/TUnit.Core/Attributes/TimeoutAttribute.cs +++ b/TUnit.Core/Attributes/TimeoutAttribute.cs @@ -1,4 +1,4 @@ -namespace TUnit.Core.Attributes; +namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method)] public class TimeoutAttribute : TUnitAttribute diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 7d717af00a..9a8edf84cf 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -1,7 +1,6 @@ using System.Reflection; using System.Security.Cryptography; using System.Text; -using TUnit.Core.Attributes; namespace TUnit.Core; diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 763ddb94e6..1017c068ca 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using System.Reflection; using TUnit.Core; -using TUnit.Core.Attributes; using TimeoutException = TUnit.Core.Exceptions.TimeoutException; namespace TUnit.Engine; diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs index 1bb8bb453c..d1760567ac 100644 --- a/TUnit.Engine/TestClassCreator.cs +++ b/TUnit.Engine/TestClassCreator.cs @@ -1,5 +1,4 @@ using TUnit.Core; -using TUnit.Core.Attributes; using TUnit.Engine.Extensions; namespace TUnit.Engine; diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index af35c3319e..e87e16f690 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using TUnit.Core; -using TUnit.Core.Attributes; using TUnit.Engine; namespace TUnit.TestAdapter; diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 371f34235f..4196f4efc3 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -1,6 +1,5 @@ using System.Reflection; using TUnit.Core; -using TUnit.Core.Attributes; using TUnit.Engine; namespace TUnit.TestAdapter; diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index e92a28fc30..bfe5a2af9e 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -1,7 +1,5 @@ using TUnit.Assertions; -using TUnit.Assertions.Extensions; using TUnit.Core; -using TUnit.Core.Attributes; namespace TUnit.TestProject; @@ -213,7 +211,7 @@ public void Count3() public void Count4() { var list = new[] { 1, 2, 3 }; - Assert.That(list).Is.EquivalentTo(new []{1}); + Assert.That(list).Has.Count.EqualTo(3); } public static int One() => 1; From 44fe05e7e8bd77bfab0e87843ac795eb50261518 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:16:53 +0000 Subject: [PATCH 076/124] Fix global usings --- TUnit/GlobalUsings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TUnit/GlobalUsings.cs b/TUnit/GlobalUsings.cs index c351b52512..b5b60e4215 100644 --- a/TUnit/GlobalUsings.cs +++ b/TUnit/GlobalUsings.cs @@ -1,3 +1,2 @@ global using TUnit.Assertions; -global using TUnit.Core.Attributes; global using Assert = TUnit.Assertions.Assert; \ No newline at end of file From 7410c4ffbc9a4e46447199d3b1ebe4a340af68de Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:17:13 +0000 Subject: [PATCH 077/124] Fix global usings --- TUnit/GlobalUsings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TUnit/GlobalUsings.cs b/TUnit/GlobalUsings.cs index b5b60e4215..83faddbd2e 100644 --- a/TUnit/GlobalUsings.cs +++ b/TUnit/GlobalUsings.cs @@ -1,2 +1,3 @@ global using TUnit.Assertions; +global using TUnit.Core; global using Assert = TUnit.Assertions.Assert; \ No newline at end of file From ad4f8175e5c23ce3d9043ba44da3de1609165e9d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:14:37 +0000 Subject: [PATCH 078/124] Analyzer for awaiting assertion calls --- .../TUnit.Analyzers.Sample/Examples.cs | 5 +- .../TUnit.Analyzers.Sample.csproj | 4 + .../AwaitAssertionAnalyzerTests.cs | 13 +-- TUnit.Analyzers/TUnit.Analyzers.Tests/Net.cs | 44 ++++++++++ .../TUnit.Analyzers.Tests.csproj | 16 ++-- .../Verifiers/CSharpAnalyzerVerifier.cs | 47 ++++++++++ .../Verifiers/CSharpAnalyzerVerifier`1.cs | 50 +++++++++++ .../Verifiers/CSharpCodeFixVerifier.cs | 49 +++++++++++ .../Verifiers/CSharpCodeFixVerifier`2.cs | 82 ++++++++++++++++++ .../CSharpCodeRefactoringVerifier.cs | 47 ++++++++++ .../CSharpCodeRefactoringVerifier`1.cs | 35 ++++++++ .../Verifiers/CSharpVerifierHelper.cs | 33 +++++++ .../AwaitAssertionCodeFixProvider.cs | 75 ++++++++++++++++ TUnit.Analyzers/TUnit.Analyzers/Readme.md | 4 +- .../TUnit.Analyzers/SampleCodeFixProvider.cs | 86 ------------------- .../TUnit.Assertions.UnitTests.csproj | 9 +- TUnit.Pipeline/TUnit.Pipeline.csproj | 4 +- 17 files changed, 497 insertions(+), 106 deletions(-) create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Net.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs create mode 100644 TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs delete mode 100644 TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs index 6476d4655d..5a79910110 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/Examples.cs @@ -1,6 +1,7 @@ // ReSharper disable UnusedType.Global // ReSharper disable UnusedMember.Global +using System.Threading.Tasks; using TUnit.Assertions; namespace TUnit.Analyzers.Sample; @@ -13,9 +14,9 @@ public class Examples { } - public void ToStars() + public async Task ToStars() { - Assert.That("1"); + await Assert.That("1").Is.EqualTo("2"); var spaceship = new Spaceship(); spaceship.SetSpeed(300000000); // Invalid value, it should be highlighted. spaceship.SetSpeed(42); diff --git a/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj b/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj index b360dc4908..0937190a22 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj +++ b/TUnit.Analyzers/TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj @@ -11,4 +11,8 @@ OutputItemType="Analyzer" ReferenceOutputAssembly="false"/> + + + + diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs index fdcd6c5851..13217d0ec6 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs @@ -1,24 +1,27 @@ using System.Threading.Tasks; -using Xunit; +using NUnit.Framework; using Verifier = - Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier< - TUnit.Analyzers.AwaitAssertionAnalyzer>; + ModularPipelines.Analyzers.Test.Verifiers.CSharpAnalyzerVerifier; namespace TUnit.Analyzers.Tests; public class AwaitAssertionAnalyzerTests { - [Fact] + [Test] public async Task ClassWithMyCompanyTitle_AlertDiagnostic() { const string text = """ + using System.Threading.Tasks; + using TUnit.Assertions; + using TUnit.Core; + public class MyClass { public async Task MyTest() { var one = 1; - {|#0:Assert.That(one).Is.EqualTo(1)|}; + {|#0:Assert.That(one).Is.EqualTo(1);|} } } diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Net.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Net.cs new file mode 100644 index 0000000000..fc95ec3cc2 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Net.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using Microsoft.CodeAnalysis.Testing; + +namespace TUnit.Analyzers.Tests; + +internal static class Net +{ + private static readonly Lazy LazyNet60 = new(() => + new ReferenceAssemblies( + "net6.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "6.0.21"), + Path.Combine("ref", "net6.0")) + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.Extensions.Logging", "6.0.0"))) + ); + + public static ReferenceAssemblies Net60 => LazyNet60.Value; + + private static readonly Lazy LazyNet70 = new(() => + new ReferenceAssemblies( + "net7.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "7.0.15"), + Path.Combine("ref", "net7.0")) + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.Extensions.Logging", "7.0.0"))) + ); + + private static readonly Lazy LazyNet80 = new(() => + new ReferenceAssemblies( + "net8.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "8.0.1"), + Path.Combine("ref", "net8.0")) + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.Extensions.Logging", "8.0.0"))) + ); + + public static ReferenceAssemblies Net70 => LazyNet70.Value; + public static ReferenceAssemblies Net80 => LazyNet80.Value; +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj b/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj index 06e795c2e0..87da5d872f 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/TUnit.Analyzers.Tests.csproj @@ -8,17 +8,21 @@ - - + + + - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs new file mode 100644 index 0000000000..08ccaa88ae --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs @@ -0,0 +1,47 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + public class Test : CSharpAnalyzerTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var project = solution.GetProject(projectId); + + if (project is null) + { + return solution; + } + + var compilationOptions = project.CompilationOptions; + + if (compilationOptions is null) + { + return solution; + } + + var parseOptions = project.ParseOptions as CSharpParseOptions; + + if (parseOptions is null) + { + return solution; + } + + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions) + .WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion.Preview)); + + return solution; + }); + } + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs new file mode 100644 index 0000000000..c2af9124cc --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using TUnit.Analyzers; +using TUnit.Analyzers.Tests; +using TUnit.Assertions; +using TUnit.Core; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + /// + public static DiagnosticResult Diagnostic() + => CSharpAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + ReferenceAssemblies = Net.Net80, + TestState = + { + AdditionalReferences = + { + typeof(TUnitAttribute).Assembly.Location, + typeof(AssertionBuilder<>).Assembly.Location, + }, + }, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs new file mode 100644 index 0000000000..cd02358caa --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs @@ -0,0 +1,49 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() +{ + public class Test : CSharpCodeFixTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var project = solution.GetProject(projectId); + + if (project is null) + { + return solution; + } + + var compilationOptions = project.CompilationOptions; + + if (compilationOptions is null) + { + return solution; + } + + var parseOptions = project.ParseOptions as CSharpParseOptions; + + if (parseOptions is null) + { + return solution; + } + + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions) + .WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion.Preview)); + + return solution; + }); + } + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs new file mode 100644 index 0000000000..36ff2a9818 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -0,0 +1,82 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using TUnit.Analyzers; +using TUnit.Analyzers.Tests; +using TUnit.Assertions; +using TUnit.Core; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() +{ + /// + public static DiagnosticResult Diagnostic() + => CSharpCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + CodeActionValidationMode = CodeActionValidationMode.SemanticStructure, + ReferenceAssemblies = Net.Net80, + TestState = + { + AdditionalReferences = + { + typeof(TUnitAttribute).Assembly.Location, + typeof(AssertionBuilder<>).Assembly.Location, + }, + }, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = Net.Net80, + TestState = + { + AdditionalReferences = + { + typeof(TUnitAttribute).Assembly.Location, + typeof(AssertionBuilder<>).Assembly.Location, + }, }, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs new file mode 100644 index 0000000000..6a1371da5d --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs @@ -0,0 +1,47 @@ +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() +{ + public class Test : CSharpCodeRefactoringTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var project = solution.GetProject(projectId); + + if (project is null) + { + return solution; + } + + var compilationOptions = project.CompilationOptions; + + if (compilationOptions is null) + { + return solution; + } + + var parseOptions = project.ParseOptions as CSharpParseOptions; + + if (parseOptions is null) + { + return solution; + } + + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions) + .WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion.Preview)); + + return solution; + }); + } + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs new file mode 100644 index 0000000000..898d930e25 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs @@ -0,0 +1,35 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() +{ + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs new file mode 100644 index 0000000000..8d967ee371 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace ModularPipelines.Analyzers.Test.Verifiers; + +internal static class CSharpVerifierHelper +{ + /// + /// By default, the compiler reports diagnostics for nullable reference types at + /// , and the analyzer test framework defaults to only validating + /// diagnostics at . This map contains all compiler diagnostic IDs + /// related to nullability mapped to , which is then used to enable all + /// of these warnings for default validation during analyzer and code fix tests. + /// + internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler(); + + private static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable", "-p:LangVersion=preview" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error) + .SetItem("CS8652", ReportDiagnostic.Suppress); + + return nullableWarnings; + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs new file mode 100644 index 0000000000..4d328dc91d --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs @@ -0,0 +1,75 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Rename; + +namespace TUnit.Analyzers; + +/// +/// A sample code fix provider that renames classes with the company name in their definition. +/// All code fixes must be linked to specific analyzers. +/// +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AwaitAssertionCodeFixProvider)), Shared] +public class AwaitAssertionCodeFixProvider : CodeFixProvider +{ + // Specify the diagnostic IDs of analyzers that are expected to be linked. + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(AwaitAssertionAnalyzer.DiagnosticId); + + // If you don't need the 'fix all' behaviour, return null. + public override FixAllProvider? GetFixAllProvider() => null; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + // We link only one diagnostic and assume there is only one diagnostic in the context. + var diagnostic = context.Diagnostics.Single(); + + // 'SourceSpan' of 'Location' is the highlighted area. We're going to use this area to find the 'SyntaxNode' to rename. + var diagnosticSpan = diagnostic.Location.SourceSpan; + + // Get the root of Syntax Tree that contains the highlighted diagnostic. + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // Find SyntaxNode corresponding to the diagnostic. + var diagnosticNode = root?.FindNode(diagnosticSpan); + + // To get the required metadata, we should match the Node to the specific type: 'ClassDeclarationSyntax'. + if (diagnosticNode is not ExpressionStatementSyntax expressionStatementSyntax) + return; + + // Register a code action that will invoke the fix. + context.RegisterCodeFix( + CodeAction.Create( + title: Resources.TUnit0002CodeFixTitle, + createChangedDocument: c => AwaitAssertionAsync(context.Document, expressionStatementSyntax, c), + equivalenceKey: nameof(Resources.TUnit0002CodeFixTitle)), + diagnostic); + } + + /// + /// Executed on the quick fix action raised by the user. + /// + /// Affected source file. + /// Highlighted class declaration Syntax Node. + /// Any fix is cancellable by the user, so we should support the cancellation token. + /// Clone of the solution with updates: renamed class. + private async Task AwaitAssertionAsync(Document document, + ExpressionStatementSyntax expressionStatementSyntax, CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken); + + var awaitExpressionSyntax = SyntaxFactory.AwaitExpression(expressionStatementSyntax.Expression); + + editor.ReplaceNode(expressionStatementSyntax.Expression, awaitExpressionSyntax); + + return editor.GetChangedDocument(); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TUnit.Analyzers/Readme.md b/TUnit.Analyzers/TUnit.Analyzers/Readme.md index ea5de78756..7053db9f47 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/Readme.md +++ b/TUnit.Analyzers/TUnit.Analyzers/Readme.md @@ -8,8 +8,8 @@ A .NET Standard project with implementations of sample analyzers and code fix pr **You must build this project to see the results (warnings) in the IDE.** - [SampleSemanticAnalyzer.cs](SampleSemanticAnalyzer.cs): An analyzer that reports invalid values used for the `speed` parameter of the `SetSpeed` function. -- [SampleSyntaxAnalyzer.cs](SampleSyntaxAnalyzer.cs): An analyzer that reports the company name used in class definitions. -- [SampleCodeFixProvider.cs](SampleCodeFixProvider.cs): A code fix that renames classes with company name in their definition. The fix is linked to [SampleSyntaxAnalyzer.cs](SampleSyntaxAnalyzer.cs). +- [AwaitAssertionAnalyzer.cs](AwaitAssertionAnalyzer.cs): An analyzer that reports the company name used in class definitions. +- [SampleCodeFixProvider.cs](SampleCodeFixProvider.cs): A code fix that renames classes with company name in their definition. The fix is linked to [AwaitAssertionAnalyzer.cs](AwaitAssertionAnalyzer.cs). ### TUnit.Analyzers.Sample A project that references the sample analyzers. Note the parameters of `ProjectReference` in [TUnit.Analyzers.Sample.csproj](../TUnit.Analyzers.Sample/TUnit.Analyzers.Sample.csproj), they make sure that the project is referenced as a set of analyzers. diff --git a/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs b/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs deleted file mode 100644 index d42c450b24..0000000000 --- a/TUnit.Analyzers/TUnit.Analyzers/SampleCodeFixProvider.cs +++ /dev/null @@ -1,86 +0,0 @@ -// using System.Collections.Immutable; -// using System.Composition; -// using System.Linq; -// using System.Threading; -// using System.Threading.Tasks; -// using Microsoft.CodeAnalysis; -// using Microsoft.CodeAnalysis.CodeActions; -// using Microsoft.CodeAnalysis.CodeFixes; -// using Microsoft.CodeAnalysis.CSharp.Syntax; -// using Microsoft.CodeAnalysis.Rename; -// -// namespace TUnit.Analyzers; -// -// /// -// /// A sample code fix provider that renames classes with the company name in their definition. -// /// All code fixes must be linked to specific analyzers. -// /// -// [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SampleCodeFixProvider)), Shared] -// public class SampleCodeFixProvider : CodeFixProvider -// { -// private const string CommonName = "Common"; -// -// // Specify the diagnostic IDs of analyzers that are expected to be linked. -// public sealed override ImmutableArray FixableDiagnosticIds { get; } = -// ImmutableArray.Create(SampleSyntaxAnalyzer.DiagnosticId); -// -// // If you don't need the 'fix all' behaviour, return null. -// public override FixAllProvider? GetFixAllProvider() => null; -// -// public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) -// { -// // We link only one diagnostic and assume there is only one diagnostic in the context. -// var diagnostic = context.Diagnostics.Single(); -// -// // 'SourceSpan' of 'Location' is the highlighted area. We're going to use this area to find the 'SyntaxNode' to rename. -// var diagnosticSpan = diagnostic.Location.SourceSpan; -// -// // Get the root of Syntax Tree that contains the highlighted diagnostic. -// var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); -// -// // Find SyntaxNode corresponding to the diagnostic. -// var diagnosticNode = root?.FindNode(diagnosticSpan); -// -// // To get the required metadata, we should match the Node to the specific type: 'ClassDeclarationSyntax'. -// if (diagnosticNode is not ClassDeclarationSyntax declaration) -// return; -// -// // Register a code action that will invoke the fix. -// context.RegisterCodeFix( -// CodeAction.Create( -// title: string.Format(Resources.TUnit0001CodeFixTitle, SampleSyntaxAnalyzer.CompanyName, CommonName), -// createChangedSolution: c => SanitizeCompanyNameAsync(context.Document, declaration, c), -// equivalenceKey: nameof(Resources.TUnit0001CodeFixTitle)), -// diagnostic); -// } -// -// /// -// /// Executed on the quick fix action raised by the user. -// /// -// /// Affected source file. -// /// Highlighted class declaration Syntax Node. -// /// Any fix is cancellable by the user, so we should support the cancellation token. -// /// Clone of the solution with updates: renamed class. -// private async Task SanitizeCompanyNameAsync(Document document, -// ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken) -// { -// // 'Identifier' means the token of the node. Compute the new name based on the text of the token of the node. -// var newName = classDeclarationSyntax.Identifier.Text.Replace(SampleSyntaxAnalyzer.CompanyName, CommonName); -// -// // To make a refactoring, we need to get compiled code metadata: the Semantic Model. -// var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); -// -// // Attempt to find the 'TypeSymbol' (compile time metadata of the class) based on highlighted Class Declaration Syntax. -// var typeSymbol = semanticModel?.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken); -// if (typeSymbol == null) return document.Project.Solution; -// -// // Produce a new solution that has all references to the class being renamed, including the declaration. -// var newSolution = await Renamer -// .RenameSymbolAsync(document.Project.Solution, typeSymbol, new SymbolRenameOptions(), newName, -// cancellationToken) -// .ConfigureAwait(false); -// -// // Return the new solution with the updated type name. -// return newSolution; -// } -// } \ No newline at end of file diff --git a/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj b/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj index 091cca0874..a4962c80df 100644 --- a/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj +++ b/TUnit.Assertions.UnitTests/TUnit.Assertions.UnitTests.csproj @@ -12,9 +12,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj index 39af739300..52bead6e0e 100644 --- a/TUnit.Pipeline/TUnit.Pipeline.csproj +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -9,8 +9,8 @@ - - + + From 1d344c6a10dd30d615554c2b1e5d0d66bf80268a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:15:22 +0000 Subject: [PATCH 079/124] AwaitAssertionAnalyzer --- .../TUnit.Analyzers/AwaitAssertionAnalyzer.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionAnalyzer.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionAnalyzer.cs b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionAnalyzer.cs new file mode 100644 index 0000000000..78cc8332c3 --- /dev/null +++ b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionAnalyzer.cs @@ -0,0 +1,109 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using TUnit.Analyzers.Extensions; + +namespace TUnit.Analyzers; + +/// +/// A sample analyzer that reports the company name being used in class declarations. +/// Traverses through the Syntax Tree and checks the name (identifier) of each class node. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class AwaitAssertionAnalyzer : DiagnosticAnalyzer +{ + // Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234. + public const string DiagnosticId = "TUnit0002"; + + // Feel free to use raw strings if you don't need localization. + private static readonly LocalizableString Title = new LocalizableResourceString(DiagnosticId + "Title", + Resources.ResourceManager, typeof(Resources)); + + // The message that will be displayed to the user. + private static readonly LocalizableString MessageFormat = + new LocalizableResourceString(DiagnosticId + "MessageFormat", Resources.ResourceManager, + typeof(Resources)); + + private static readonly LocalizableString Description = + new LocalizableResourceString(DiagnosticId + "Description", Resources.ResourceManager, + typeof(Resources)); + + // The category of the diagnostic (Design, Naming etc.). + private const string Category = "Usage"; + + private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, + DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); + + // Keep in mind: you have to list your rules here. + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + // You must call this method to avoid analyzing generated code. + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + // You must call this method to enable the Concurrent Execution. + context.EnableConcurrentExecution(); + + // Subscribe to the Syntax Node with the appropriate 'SyntaxKind' (ClassDeclaration) action. + // To figure out which Syntax Nodes you should choose, consider installing the Roslyn syntax tree viewer plugin Rossynt: https://plugins.jetbrains.com/plugin/16902-rossynt/ + context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.SimpleMemberAccessExpression); + + // Check other 'context.Register...' methods that might be helpful for your purposes. + } + + /// + /// Executed for each Syntax Node with 'SyntaxKind' is 'ClassDeclaration'. + /// + /// Operation context. + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + // The Roslyn architecture is based on inheritance. + // To get the required metadata, we should match the 'Node' object to the particular type: 'ClassDeclarationSyntax'. + if (context.Node is not MemberAccessExpressionSyntax memberAccessExpressionSyntax) + { + return; + } + + if (memberAccessExpressionSyntax.ToString() != "Assert.That") + { + return; + } + + var symbol = context.SemanticModel.GetSymbolInfo(memberAccessExpressionSyntax); + + if (symbol.Symbol is not IMethodSymbol methodSymbol) + { + return; + } + + if (methodSymbol.ToDisplayString(DisplayFormats.FullyQualifiedNonGeneric) != + "global::TUnit.Assertions.Assert.That") + { + return; + } + + var expressionStatementParent = memberAccessExpressionSyntax.GetAncestorSyntaxOfType(); + + if (expressionStatementParent is null) + { + return; + } + + if (expressionStatementParent.ChildNodes().Any(x => x is AwaitExpressionSyntax)) + { + return; + } + + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, + true, Description), + expressionStatementParent.GetLocation()) + ); + } +} \ No newline at end of file From 5aa04ba5199037aa4718024c44b39f76a577aa29 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:26:41 +0000 Subject: [PATCH 080/124] Enumerable EquivalentTo --- .../Collections/EnumerableEquivalentToAssertCondition.cs | 7 ++++--- TUnit.Assertions/Extensions/IsExtensions.cs | 4 ++-- TUnit.TestProject/Tests.cs | 5 ++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs index 99f7848e7b..ddb5ac231a 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs @@ -1,14 +1,15 @@ namespace TUnit.Assertions.AssertConditions.Collections; -public class EnumerableEquivalentToAssertCondition : AssertCondition, IEnumerable> +public class EnumerableEquivalentToAssertCondition : AssertCondition> + where T : IEnumerable { - public EnumerableEquivalentToAssertCondition(AssertionBuilder> assertionBuilder, IEnumerable expected) : base(assertionBuilder, expected) + public EnumerableEquivalentToAssertCondition(AssertionBuilder assertionBuilder, IEnumerable expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => "The two Enumerables were not equivalent"; - protected internal override bool Passes(IEnumerable? actualValue, Exception? exception) + protected internal override bool Passes(T? actualValue, Exception? exception) { if (actualValue is null && ExpectedValue is null) { diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs index fb1e8d22ff..5976fc5601 100644 --- a/TUnit.Assertions/Extensions/IsExtensions.cs +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -65,10 +65,10 @@ public static AssertCondition Odd(this Is @is) where T : INumber, #region Enumerables - public static EnumerableEquivalentToAssertCondition EquivalentTo(this Is> @is, T expected) + public static AssertCondition> EquivalentTo(this Is @is, IEnumerable expected) where T : IEnumerable { - return new EnumerableEquivalentToAssertCondition(@is.AssertionBuilder, expected); + return new EnumerableEquivalentToAssertCondition(@is.AssertionBuilder, expected); } #endregion diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index bfe5a2af9e..d6ccee4abc 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -203,7 +203,10 @@ public void Count2() [Test] public void Count3() { - var list = new[] { 1, 2, 3 }; + var list = new[] { 1, 2, 3 }.ToArray(); + + Assert.That(list).Is.EquivalentTo(new List { 1, 2, 3 }); + Assert.That(list); } From 6a07544e0aac8a00a4b1b48f3918f82e65d78b49 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:39:19 +0000 Subject: [PATCH 081/124] Does --- .../EnumerableContainsAssertCondition.cs | 21 ++++++++++++++++ .../String/StringContainsAssertCondition.cs | 23 ++++++++++++++++++ TUnit.Assertions/AssertionBuilder.cs | 1 + TUnit.Assertions/Does.cs | 20 ++++++++++++++++ TUnit.Assertions/Extensions/DoesExtensions.cs | 24 +++++++++++++++++++ TUnit.Assertions/TUnit.Assertions.csproj | 2 +- 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs create mode 100644 TUnit.Assertions/Does.cs create mode 100644 TUnit.Assertions/Extensions/DoesExtensions.cs diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs new file mode 100644 index 0000000000..c2fbb01a8e --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions.Collections; + +public class EnumerableContainsAssertCondition : AssertCondition + where T : IEnumerable +{ + public EnumerableContainsAssertCondition(AssertionBuilder assertionBuilder, TInner expected) : base(assertionBuilder, expected) + { + } + + protected override string DefaultMessage => $"{ExpectedValue} was not found in the collection"; + + protected internal override bool Passes(T? actualValue, Exception? exception) + { + if (actualValue is null) + { + return false; + } + + return actualValue.Contains(ExpectedValue); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs new file mode 100644 index 0000000000..192e7d37ef --- /dev/null +++ b/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs @@ -0,0 +1,23 @@ +namespace TUnit.Assertions.AssertConditions.String; + +public class StringContainsAssertCondition : AssertCondition +{ + private readonly StringComparison _stringComparison; + + public StringContainsAssertCondition(AssertionBuilder assertionBuilder, string expected, StringComparison stringComparison) : base(assertionBuilder, expected) + { + _stringComparison = stringComparison; + } + + protected internal override bool Passes(string? actualValue, Exception? exception) + { + ArgumentNullException.ThrowIfNull(actualValue); + ArgumentNullException.ThrowIfNull(ExpectedValue); + + return actualValue.Contains(ExpectedValue, _stringComparison); + } + + protected override string DefaultMessage => $""" + Expected "{ExpectedValue}" but received "{ActualValue}" + """; +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs index 017c102a31..5d8480466f 100644 --- a/TUnit.Assertions/AssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -5,6 +5,7 @@ public abstract class AssertionBuilder { protected internal abstract Task> GetAssertionData(); + public Does Does => new(this); public Is Is => new(this); public Has Has => new(this); } \ No newline at end of file diff --git a/TUnit.Assertions/Does.cs b/TUnit.Assertions/Does.cs new file mode 100644 index 0000000000..c05ba4850c --- /dev/null +++ b/TUnit.Assertions/Does.cs @@ -0,0 +1,20 @@ +using TUnit.Assertions.AssertConditions.Generic; + +namespace TUnit.Assertions; + +public class Does +{ + internal AssertionBuilder AssertionBuilder { get; } + + public Does(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public Property Count => new(AssertionBuilder, "Count"); + public Property Length => new(AssertionBuilder, "Length"); + public Property Value => new(AssertionBuilder, "Value"); + + public Property Property(string name) => new(AssertionBuilder, name); + public Property Property(string name) => new(AssertionBuilder, name); +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/DoesExtensions.cs b/TUnit.Assertions/Extensions/DoesExtensions.cs new file mode 100644 index 0000000000..cc28a476ff --- /dev/null +++ b/TUnit.Assertions/Extensions/DoesExtensions.cs @@ -0,0 +1,24 @@ +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Collections; +using TUnit.Assertions.AssertConditions.String; + +namespace TUnit.Assertions; + +public static class DoesExtensions +{ + public static AssertCondition Contain(this Is @is, TInner expected) + where T : IEnumerable + { + return new EnumerableContainsAssertCondition(@is.AssertionBuilder, expected); + } + + public static AssertCondition Contain(this Is @is, string expected) + { + return Contain(@is, expected, StringComparison.Ordinal); + } + + public static AssertCondition Contain(this Is @is, string expected, StringComparison stringComparison) + { + return new StringContainsAssertCondition(@is.AssertionBuilder, expected, stringComparison); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/TUnit.Assertions.csproj b/TUnit.Assertions/TUnit.Assertions.csproj index 16bdc32442..94c3e3d046 100644 --- a/TUnit.Assertions/TUnit.Assertions.csproj +++ b/TUnit.Assertions/TUnit.Assertions.csproj @@ -11,5 +11,5 @@ - + From bbd305d9196f61196d9228e7b66cbe61baebb6d3 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:05:45 +0000 Subject: [PATCH 082/124] Remove DelegateAssertCondition.cs --- .../Throws/DelegateAssertCondition.cs | 55 ------------------- .../Throws/ThrowsNothingAssertCondition.cs | 15 ++--- 2 files changed, 8 insertions(+), 62 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs deleted file mode 100644 index ba765854ae..0000000000 --- a/TUnit.Assertions/AssertConditions/Throws/DelegateAssertCondition.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.Throws; - -public abstract class DelegateAssertCondition -{ - private Func? MessageFactory { get; set; } - - protected Exception? Exception { get; set; } - protected T? ActualValue { get; set; } = default!; - - public bool Assert(AssertionData assertionData) - { - ActualValue = assertionData.Result; - Exception = assertionData.Exception; - - return Passes(ActualValue, Exception); - } - - protected abstract string DefaultMessage { get; } - - protected internal abstract bool Passes(T? actualValue, Exception? exception); - - public string Message => MessageFactory?.Invoke(ActualValue, Exception) ?? DefaultMessage; - - public DelegateAssertCondition WithMessage(Func messageFactory) - { - MessageFactory = messageFactory; - return this; - } -} - -public abstract class DelegateAssertCondition -{ - private Func? MessageFactory { get; set; } - - protected Exception? Exception { get; private set; } - - public bool Assert(Exception? exception) - { - Exception = exception; - - return Passes(Exception); - } - - protected abstract string DefaultMessage { get; } - - protected internal abstract bool Passes(Exception? exception); - - public string Message => MessageFactory?.Invoke(Exception) ?? DefaultMessage; - - public DelegateAssertCondition WithMessage(Func messageFactory) - { - MessageFactory = messageFactory; - return this; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index 494c813b35..cb8817b826 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -1,14 +1,15 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsNothingAssertCondition : DelegateAssertCondition +public class ThrowsNothingAssertCondition : AssertCondition { - private Exception? _exception; - - protected override string DefaultMessage => $"A {_exception?.GetType().Name} was thrown"; + public ThrowsNothingAssertCondition(AssertionBuilder assertionBuilder, object? expected) : base(assertionBuilder, expected) + { + } + + protected override string DefaultMessage => $"A {Exception?.GetType().Name} was thrown"; - protected internal override bool Passes(Exception? exception) + protected internal override bool Passes(object? actualValue, Exception? exception) { - _exception = exception; - return exception == null; + return exception is null; } } \ No newline at end of file From d5375a68368ec104b187422233ce6c6d8d972b0b Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:19:58 +0000 Subject: [PATCH 083/124] Remove redundant classes --- .../Connectors/DelegateAssertConditionAnd.cs | 24 ------------------- .../Connectors/DelegateAssertConditionOr.cs | 22 ----------------- .../Throws/ThrowsNothingAssertCondition.cs | 6 ++--- TUnit.Assertions/AssertionBuilder.cs | 1 + TUnit.Assertions/Throws.cs | 10 ++++++-- TUnit.TestProject/Tests.cs | 2 +- 6 files changed, 13 insertions(+), 52 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs delete mode 100644 TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs diff --git a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs deleted file mode 100644 index 0247152dcd..0000000000 --- a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionAnd.cs +++ /dev/null @@ -1,24 +0,0 @@ -using TUnit.Assertions.AssertConditions.Throws; - -namespace TUnit.Assertions.AssertConditions.Connectors; - -public sealed class DelegateAssertConditionAnd : DelegateAssertCondition -{ - private readonly DelegateAssertCondition _condition1; - private readonly DelegateAssertCondition _condition2; - - public DelegateAssertConditionAnd(DelegateAssertCondition condition1, DelegateAssertCondition condition2) - { - _condition1 = condition1; - _condition2 = condition2; - } - - protected override string DefaultMessage => - !_condition1.Passes(ActualValue, Exception) ? _condition1.Message : - !_condition2.Passes(ActualValue, Exception) ? _condition2.Message : string.Empty; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - return _condition1.Assert((actualValue, exception)) && _condition2.Assert((actualValue, exception)); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs deleted file mode 100644 index 9a2f2117b0..0000000000 --- a/TUnit.Assertions/AssertConditions/Connectors/DelegateAssertConditionOr.cs +++ /dev/null @@ -1,22 +0,0 @@ -using TUnit.Assertions.AssertConditions.Throws; - -namespace TUnit.Assertions.AssertConditions.Connectors; - -public sealed class DelegateAssertConditionOr : DelegateAssertCondition -{ - private readonly DelegateAssertCondition _condition1; - private readonly DelegateAssertCondition _condition2; - - public DelegateAssertConditionOr(DelegateAssertCondition condition1, DelegateAssertCondition condition2) - { - _condition1 = condition1; - _condition2 = condition2; - } - - protected override string DefaultMessage => $"{_condition1.Message} & {_condition2.Message}"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - return _condition1.Assert((actualValue, exception)) || _condition2.Assert((actualValue, exception)); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index cb8817b826..102df8bb07 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -1,14 +1,14 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsNothingAssertCondition : AssertCondition +public class ThrowsNothingAssertCondition : AssertCondition { - public ThrowsNothingAssertCondition(AssertionBuilder assertionBuilder, object? expected) : base(assertionBuilder, expected) + public ThrowsNothingAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { } protected override string DefaultMessage => $"A {Exception?.GetType().Name} was thrown"; - protected internal override bool Passes(object? actualValue, Exception? exception) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return exception is null; } diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs index 5d8480466f..a901c1a165 100644 --- a/TUnit.Assertions/AssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -6,6 +6,7 @@ public abstract class AssertionBuilder protected internal abstract Task> GetAssertionData(); public Does Does => new(this); + public Throws Throws => new(this); public Is Is => new(this); public Has Has => new(this); } \ No newline at end of file diff --git a/TUnit.Assertions/Throws.cs b/TUnit.Assertions/Throws.cs index 357505ebf4..e1005370dc 100644 --- a/TUnit.Assertions/Throws.cs +++ b/TUnit.Assertions/Throws.cs @@ -2,7 +2,13 @@ namespace TUnit.Assertions; -public static class Throws +public class Throws { - public static ThrowsNothingAssertCondition Nothing => new(); + internal AssertionBuilder AssertionBuilder { get; } + + public Throws(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + public ThrowsNothingAssertCondition Nothing => new(AssertionBuilder); } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index d6ccee4abc..fa6a57169d 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -163,7 +163,7 @@ await Assert.That(async () => { await Task.Yield(); return true; - }).Is.False(); + }).Throws.Nothing.And.Is.EqualTo(false).Or.Is.EqualTo(true); } [Test, Timeout(500)] From 9d5c661defdc42064ffbd806020f7ff1b642c90d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:01:27 +0000 Subject: [PATCH 084/124] DelegateAssertCondition --- .../AwaitAssertionAnalyzerTests.cs | 3 +- .../Verifiers/CSharpAnalyzerVerifier.cs | 2 +- .../Verifiers/CSharpAnalyzerVerifier`1.cs | 4 +- .../Verifiers/CSharpCodeFixVerifier.cs | 2 +- .../Verifiers/CSharpCodeFixVerifier`2.cs | 4 +- .../CSharpCodeRefactoringVerifier.cs | 2 +- .../CSharpCodeRefactoringVerifier`1.cs | 2 +- .../Verifiers/CSharpVerifierHelper.cs | 2 +- ... EnumerableCountEqualToAssertCondition.cs} | 11 +-- .../DelegateAssertCondition.cs | 17 ++++ .../StringLengthEqualToAssertCondition.cs | 22 +++++ .../Throws/ThrowsAnythingAssertCondition.cs | 15 ++++ .../ThrowsExactTypeOfAssertCondition.cs | 17 ++++ .../Throws/ThrowsSubClassOfAssertCondition.cs | 17 ++++ ...owsWithMessageContainingAssertCondition.cs | 21 +++++ ...ThrowsWithMessageEqualToAssertCondition.cs | 20 +++++ .../AssertConditions/Throws/WithMessage.cs | 31 +++++++ TUnit.Assertions/Does.cs | 2 +- TUnit.Assertions/Extensions/HasExtensions.cs | 88 +++++++++++++++++++ TUnit.Assertions/Extensions/IsExtensions.cs | 10 ++- TUnit.Assertions/Has.cs | 6 +- TUnit.Assertions/Is.cs | 5 -- TUnit.Assertions/Throws.cs | 8 +- 23 files changed, 279 insertions(+), 32 deletions(-) rename TUnit.Assertions/AssertConditions/Collections/{HasCountAssertCondition.cs => EnumerableCountEqualToAssertCondition.cs} (56%) create mode 100644 TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Throws/WithMessage.cs create mode 100644 TUnit.Assertions/Extensions/HasExtensions.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs index 13217d0ec6..4f5efe475f 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/AwaitAssertionAnalyzerTests.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using NUnit.Framework; -using Verifier = - ModularPipelines.Analyzers.Test.Verifiers.CSharpAnalyzerVerifier; +using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; namespace TUnit.Analyzers.Tests; diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs index 08ccaa88ae..8c52e6fc73 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing.Verifiers; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; public static partial class CSharpAnalyzerVerifier where TAnalyzer : DiagnosticAnalyzer, new() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs index c2af9124cc..51fa0c503b 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs @@ -5,12 +5,10 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; -using TUnit.Analyzers; -using TUnit.Analyzers.Tests; using TUnit.Assertions; using TUnit.Core; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; public static partial class CSharpAnalyzerVerifier where TAnalyzer : DiagnosticAnalyzer, new() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs index cd02358caa..4074d1ac9e 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing.Verifiers; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; public static partial class CSharpCodeFixVerifier where TAnalyzer : DiagnosticAnalyzer, new() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs index 36ff2a9818..2000ecec69 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -6,12 +6,10 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; -using TUnit.Analyzers; -using TUnit.Analyzers.Tests; using TUnit.Assertions; using TUnit.Core; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; public static partial class CSharpCodeFixVerifier where TAnalyzer : DiagnosticAnalyzer, new() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs index 6a1371da5d..7fa594b0af 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; public static partial class CSharpCodeRefactoringVerifier where TCodeRefactoring : CodeRefactoringProvider, new() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs index 898d930e25..1bbdb42268 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Testing; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; public static partial class CSharpCodeRefactoringVerifier where TCodeRefactoring : CodeRefactoringProvider, new() diff --git a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs index 8d967ee371..79517165e5 100644 --- a/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs +++ b/TUnit.Analyzers/TUnit.Analyzers.Tests/Verifiers/CSharpVerifierHelper.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -namespace ModularPipelines.Analyzers.Test.Verifiers; +namespace TUnit.Analyzers.Tests.Verifiers; internal static class CSharpVerifierHelper { diff --git a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs similarity index 56% rename from TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs index 0168e05253..4aeff6a991 100644 --- a/TUnit.Assertions/AssertConditions/Collections/HasCountAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs @@ -2,20 +2,21 @@ namespace TUnit.Assertions.AssertConditions.Collections; -public class HasCountAssertCondition : AssertCondition, int> +public class EnumerableCountEqualToAssertCondition : AssertCondition + where T : IEnumerable { - public HasCountAssertCondition(AssertionBuilder> assertionBuilder, int expected) : base(assertionBuilder, expected) + public EnumerableCountEqualToAssertCondition(AssertionBuilder assertionBuilder, int expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; - protected internal override bool Passes(IEnumerable? actualValue, Exception? exception) + protected internal override bool Passes(T? actualValue, Exception? exception) { return GetCount(actualValue) == ExpectedValue; } - private int GetCount(IEnumerable? actualValue) + private int GetCount(T? actualValue) { ArgumentNullException.ThrowIfNull(actualValue); @@ -24,7 +25,7 @@ private int GetCount(IEnumerable? actualValue) return collection.Count; } - if (actualValue is TActual[] array) + if (actualValue is TInner[] array) { return array.Length; } diff --git a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs new file mode 100644 index 0000000000..ae47afe147 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs @@ -0,0 +1,17 @@ +namespace TUnit.Assertions.AssertConditions; + +public class DelegateAssertCondition : AssertCondition +{ + private readonly Func _condition; + + public DelegateAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected, Func condition) : base(assertionBuilder, expected) + { + _condition = condition; + } + + protected override string DefaultMessage { get; } + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + return _condition(actualValue, ExpectedValue, exception); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs new file mode 100644 index 0000000000..bcc24eff7b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs @@ -0,0 +1,22 @@ +namespace TUnit.Assertions.AssertConditions.String; + +public class StringLengthEqualToAssertCondition : AssertCondition +{ + public StringLengthEqualToAssertCondition(AssertionBuilder assertionBuilder, int expected) : base(assertionBuilder, expected) + { + } + + protected override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; + + protected internal override bool Passes(string? actualValue, Exception? exception) + { + return GetCount(actualValue) == ExpectedValue; + } + + private int GetCount(string? actualValue) + { + ArgumentNullException.ThrowIfNull(actualValue); + + return actualValue.Length; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs new file mode 100644 index 0000000000..75801a4333 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs @@ -0,0 +1,15 @@ +namespace TUnit.Assertions.AssertConditions.Throws; + +public class ThrowsAnythingAssertCondition : AssertCondition +{ + public ThrowsAnythingAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) + { + } + + protected override string DefaultMessage => "Nothing was thrown"; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + return exception != null; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs new file mode 100644 index 0000000000..eba5478064 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs @@ -0,0 +1,17 @@ +namespace TUnit.Assertions.AssertConditions.Throws; + +public class ThrowsExactTypeOfAssertCondition : AssertCondition +{ + public ThrowsExactTypeOfAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) + { + } + + protected override string DefaultMessage => $"A {Exception?.GetType().Name} was thrown instead of {typeof(TExpected).Name}"; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + ArgumentNullException.ThrowIfNull(exception); + + return exception.GetType() == typeof(TExpected); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs new file mode 100644 index 0000000000..c5d9dc149b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs @@ -0,0 +1,17 @@ +namespace TUnit.Assertions.AssertConditions.Throws; + +public class ThrowsSubClassOfAssertCondition : AssertCondition +{ + public ThrowsSubClassOfAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) + { + } + + protected override string DefaultMessage => $"A {Exception?.GetType().Name} was thrown instead of subclass of {typeof(TExpected).Name}"; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + ArgumentNullException.ThrowIfNull(exception); + + return exception.GetType().IsSubclassOf(typeof(TExpected)); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs new file mode 100644 index 0000000000..ea8c7f818b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions.Throws; + +public class ThrowsWithMessageContainingAssertCondition : AssertCondition +{ + private readonly StringComparison _stringComparison; + + public ThrowsWithMessageContainingAssertCondition(AssertionBuilder assertionBuilder, string expected, StringComparison stringComparison) : base(assertionBuilder, expected) + { + _stringComparison = stringComparison; + } + + protected override string DefaultMessage => $"Message '{Exception?.Message}' did not contain '{ExpectedValue}'"; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + ArgumentNullException.ThrowIfNull(exception); + ArgumentNullException.ThrowIfNull(ExpectedValue); + + return exception.Message.Contains(ExpectedValue, _stringComparison); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs new file mode 100644 index 0000000000..ea3d588c9f --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs @@ -0,0 +1,20 @@ +namespace TUnit.Assertions.AssertConditions.Throws; + +public class ThrowsWithMessageEqualToAssertCondition : AssertCondition +{ + private readonly StringComparison _stringComparison; + + public ThrowsWithMessageEqualToAssertCondition(AssertionBuilder assertionBuilder, string expected, StringComparison stringComparison) : base(assertionBuilder, expected) + { + _stringComparison = stringComparison; + } + + protected override string DefaultMessage => $"Message was {Exception?.Message} instead of {ExpectedValue}"; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + ArgumentNullException.ThrowIfNull(exception); + + return string.Equals(exception.Message, ExpectedValue, _stringComparison); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs b/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs new file mode 100644 index 0000000000..1fff6a4681 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs @@ -0,0 +1,31 @@ +namespace TUnit.Assertions.AssertConditions.Throws; + +public class WithMessage +{ + protected AssertionBuilder AssertionBuilder { get; } + + public WithMessage(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition EqualTo(string expected) + { + return EqualTo(expected, StringComparison.Ordinal); + } + + public AssertCondition EqualTo(string expected, StringComparison stringComparison) + { + return new ThrowsWithMessageEqualToAssertCondition(AssertionBuilder, expected, stringComparison); + } + + public AssertCondition Containing(string expected) + { + return Containing(expected, StringComparison.Ordinal); + } + + public AssertCondition Containing(string expected, StringComparison stringComparison) + { + return new ThrowsWithMessageContainingAssertCondition(AssertionBuilder, expected, stringComparison); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Does.cs b/TUnit.Assertions/Does.cs index c05ba4850c..52d5e2dd18 100644 --- a/TUnit.Assertions/Does.cs +++ b/TUnit.Assertions/Does.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions; public class Does { - internal AssertionBuilder AssertionBuilder { get; } + protected AssertionBuilder AssertionBuilder { get; } public Does(AssertionBuilder assertionBuilder) { diff --git a/TUnit.Assertions/Extensions/HasExtensions.cs b/TUnit.Assertions/Extensions/HasExtensions.cs new file mode 100644 index 0000000000..9565525887 --- /dev/null +++ b/TUnit.Assertions/Extensions/HasExtensions.cs @@ -0,0 +1,88 @@ +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Collections; +using TUnit.Assertions.AssertConditions.String; + +namespace TUnit.Assertions; + +public static class HasExtensions +{ + public static EnumerableCount Count(this Is @is) where T : IEnumerable + { + return new EnumerableCount(@is.AssertionBuilder); + } + + public static StringLength Length(this Is @is) + { + return new StringLength(@is.AssertionBuilder); + } +} + +public class EnumerableCount where T : IEnumerable +{ + protected AssertionBuilder AssertionBuilder { get; } + + public EnumerableCount(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition EqualTo() + { + return new EnumerableCountEqualToAssertCondition(@AssertionBuilder, 0); + } +} + +public class StringLength +{ + protected AssertionBuilder AssertionBuilder { get; } + + public StringLength(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition EqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length == i; + }); + } + + public AssertCondition GreaterThan(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length > i; + }); + } + + public AssertCondition GreaterThanOrEqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length >= i; + }); + } + + public AssertCondition LessThan(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length < i; + }); + } + + public AssertCondition LessThanOrEqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length <= i; + }); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs index 5976fc5601..36c97aaadc 100644 --- a/TUnit.Assertions/Extensions/IsExtensions.cs +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -13,7 +13,7 @@ public static class IsExtensions public static AssertCondition EqualTo(this Is @is, string expected) { - return new StringEqualsAssertCondition(@is.AssertionBuilder, expected, StringComparison.Ordinal); + return EqualTo(@is, expected, StringComparison.Ordinal); } public static AssertCondition EqualTo(this Is @is, string expected, StringComparison stringComparison) @@ -66,10 +66,16 @@ public static AssertCondition Odd(this Is @is) where T : INumber, #region Enumerables public static AssertCondition> EquivalentTo(this Is @is, IEnumerable expected) - where T : IEnumerable + where T : IEnumerable { return new EnumerableEquivalentToAssertCondition(@is.AssertionBuilder, expected); } + + public static AssertCondition Empty(this Is @is) + where T : IEnumerable + { + return new EnumerableCountEqualToAssertCondition(@is.AssertionBuilder, 0); + } #endregion diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 48d6346d6a..9811e4059e 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -4,17 +4,13 @@ namespace TUnit.Assertions; public class Has { - internal AssertionBuilder AssertionBuilder { get; } + protected AssertionBuilder AssertionBuilder { get; } public Has(AssertionBuilder assertionBuilder) { AssertionBuilder = assertionBuilder; } - public Property Count => new(AssertionBuilder, "Count"); - public Property Length => new(AssertionBuilder, "Length"); - public Property Value => new(AssertionBuilder, "Value"); - public Property Property(string name) => new(AssertionBuilder, name); public Property Property(string name) => new(AssertionBuilder, name); } \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 5f6fe44e7a..fd6085b487 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -16,11 +16,6 @@ public AssertCondition EqualTo(TActual expected) { return new EqualsAssertCondition(AssertionBuilder, expected); } - - internal AssertCondition EqualTo(TExpected expected) - { - return new EqualsAssertCondition(AssertionBuilder, expected); - } public AssertCondition SameReference(TActual expected) { diff --git a/TUnit.Assertions/Throws.cs b/TUnit.Assertions/Throws.cs index e1005370dc..3222c10a62 100644 --- a/TUnit.Assertions/Throws.cs +++ b/TUnit.Assertions/Throws.cs @@ -4,11 +4,17 @@ namespace TUnit.Assertions; public class Throws { - internal AssertionBuilder AssertionBuilder { get; } + protected AssertionBuilder AssertionBuilder { get; } public Throws(AssertionBuilder assertionBuilder) { AssertionBuilder = assertionBuilder; } + + public WithMessage WithMessage => new(AssertionBuilder); + public ThrowsNothingAssertCondition Nothing => new(AssertionBuilder); + public ThrowsAnythingAssertCondition Exception => new(AssertionBuilder); + public ThrowsExactTypeOfAssertCondition TypeOf() => new(AssertionBuilder); + public ThrowsSubClassOfAssertCondition SubClassOf() => new(AssertionBuilder); } \ No newline at end of file From 8dd8dc112198bed52c473d6394dcb9f27100e118 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:06:51 +0000 Subject: [PATCH 085/124] StringLength.cs --- .../AssertConditions/AssertCondition.cs | 6 +- .../{Count.cs => PropertyOrMethodAccessor.cs} | 0 .../DelegateAssertCondition.cs | 6 +- .../Extensions/EnumerableCount.cs | 19 +++++ TUnit.Assertions/Extensions/HasExtensions.cs | 72 ------------------ TUnit.Assertions/Extensions/StringLength.cs | 74 +++++++++++++++++++ 6 files changed, 101 insertions(+), 76 deletions(-) rename TUnit.Assertions/AssertConditions/Collections/{Count.cs => PropertyOrMethodAccessor.cs} (100%) create mode 100644 TUnit.Assertions/Extensions/EnumerableCount.cs create mode 100644 TUnit.Assertions/Extensions/StringLength.cs diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index eab3f8a2ed..4aec3b5ecc 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -9,11 +9,11 @@ internal AssertCondition(AssertionBuilder assertionBuilder, TExpected? ExpectedValue = expected; } - private Func? MessageFactory { get; set; } + private Func? MessageFactory { get; set; } - protected internal override string Message => MessageFactory?.Invoke(ActualValue) ?? DefaultMessage; + protected internal override string Message => MessageFactory?.Invoke(ActualValue, ExpectedValue, Exception) ?? DefaultMessage; - public AssertCondition WithMessage(Func messageFactory) + public AssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; diff --git a/TUnit.Assertions/AssertConditions/Collections/Count.cs b/TUnit.Assertions/AssertConditions/Collections/PropertyOrMethodAccessor.cs similarity index 100% rename from TUnit.Assertions/AssertConditions/Collections/Count.cs rename to TUnit.Assertions/AssertConditions/Collections/PropertyOrMethodAccessor.cs diff --git a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs index ae47afe147..7e2bb4f986 100644 --- a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs @@ -4,9 +4,13 @@ public class DelegateAssertCondition : AssertCondition _condition; - public DelegateAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected, Func condition) : base(assertionBuilder, expected) + public DelegateAssertCondition(AssertionBuilder assertionBuilder, + TExpected? expected, + Func condition, + Func messageFactory) : base(assertionBuilder, expected) { _condition = condition; + WithMessage(messageFactory); } protected override string DefaultMessage { get; } diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs new file mode 100644 index 0000000000..dd744bc9ea --- /dev/null +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -0,0 +1,19 @@ +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Collections; + +namespace TUnit.Assertions; + +public class EnumerableCount where T : IEnumerable +{ + protected AssertionBuilder AssertionBuilder { get; } + + public EnumerableCount(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition EqualTo() + { + return new EnumerableCountEqualToAssertCondition(AssertionBuilder, 0); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/HasExtensions.cs b/TUnit.Assertions/Extensions/HasExtensions.cs index 9565525887..646dd62eca 100644 --- a/TUnit.Assertions/Extensions/HasExtensions.cs +++ b/TUnit.Assertions/Extensions/HasExtensions.cs @@ -1,5 +1,3 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Collections; using TUnit.Assertions.AssertConditions.String; namespace TUnit.Assertions; @@ -15,74 +13,4 @@ public static StringLength Length(this Is @is) { return new StringLength(@is.AssertionBuilder); } -} - -public class EnumerableCount where T : IEnumerable -{ - protected AssertionBuilder AssertionBuilder { get; } - - public EnumerableCount(AssertionBuilder assertionBuilder) - { - AssertionBuilder = assertionBuilder; - } - - public AssertCondition EqualTo() - { - return new EnumerableCountEqualToAssertCondition(@AssertionBuilder, 0); - } -} - -public class StringLength -{ - protected AssertionBuilder AssertionBuilder { get; } - - public StringLength(AssertionBuilder assertionBuilder) - { - AssertionBuilder = assertionBuilder; - } - - public AssertCondition EqualTo(int expected) - { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => - { - ArgumentNullException.ThrowIfNull(s); - return s.Length == i; - }); - } - - public AssertCondition GreaterThan(int expected) - { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => - { - ArgumentNullException.ThrowIfNull(s); - return s.Length > i; - }); - } - - public AssertCondition GreaterThanOrEqualTo(int expected) - { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => - { - ArgumentNullException.ThrowIfNull(s); - return s.Length >= i; - }); - } - - public AssertCondition LessThan(int expected) - { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => - { - ArgumentNullException.ThrowIfNull(s); - return s.Length < i; - }); - } - - public AssertCondition LessThanOrEqualTo(int expected) - { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => - { - ArgumentNullException.ThrowIfNull(s); - return s.Length <= i; - }); - } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs new file mode 100644 index 0000000000..dcf645657f --- /dev/null +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -0,0 +1,74 @@ +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; + +public class StringLength +{ + protected AssertionBuilder AssertionBuilder { get; } + + public StringLength(AssertionBuilder assertionBuilder) + { + AssertionBuilder = assertionBuilder; + } + + public AssertCondition EqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length == i; + }, + (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be equal to {i}"); + } + + public AssertCondition Empty => + new DelegateAssertCondition(AssertionBuilder, 0, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length == i; + }, + (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be equal to {i}"); + + public AssertCondition GreaterThan(int expected) + { + return new DelegateAssertCondition( + AssertionBuilder, + expected, + (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length > i; + }, + (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be greater than {i}"); + } + + public AssertCondition GreaterThanOrEqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length >= i; + }, + (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be greater than or equal to {i}"); + } + + public AssertCondition LessThan(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length < i; + }, + (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be less than {i}"); + } + + public AssertCondition LessThanOrEqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + { + ArgumentNullException.ThrowIfNull(s); + return s.Length <= i; + }, + (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be less than or equal to {i}"); + } +} \ No newline at end of file From 246150ddacd261ad5bd5debb11939d594fadd044 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:13:33 +0000 Subject: [PATCH 086/124] EnumerableCount --- .../AwaitAssertionCodeFixProvider.cs | 1 - .../DelegateAssertCondition.cs | 3 +- .../StringLengthEqualToAssertCondition.cs | 22 ----- .../Extensions/EnumerableCount.cs | 81 ++++++++++++++++++- TUnit.Assertions/Extensions/HasExtensions.cs | 2 - TUnit.Assertions/Extensions/StringLength.cs | 48 +++++------ 6 files changed, 103 insertions(+), 54 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs index 4d328dc91d..8cabe30789 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Rename; namespace TUnit.Analyzers; diff --git a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs index 7e2bb4f986..3dbe95b7e4 100644 --- a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs @@ -13,7 +13,8 @@ public DelegateAssertCondition(AssertionBuilder assertionBuilder, WithMessage(messageFactory); } - protected override string DefaultMessage { get; } + protected override string DefaultMessage => string.Empty; + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return _condition(actualValue, ExpectedValue, exception); diff --git a/TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs deleted file mode 100644 index bcc24eff7b..0000000000 --- a/TUnit.Assertions/AssertConditions/String/StringLengthEqualToAssertCondition.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.String; - -public class StringLengthEqualToAssertCondition : AssertCondition -{ - public StringLengthEqualToAssertCondition(AssertionBuilder assertionBuilder, int expected) : base(assertionBuilder, expected) - { - } - - protected override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; - - protected internal override bool Passes(string? actualValue, Exception? exception) - { - return GetCount(actualValue) == ExpectedValue; - } - - private int GetCount(string? actualValue) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue.Length; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs index dd744bc9ea..71f0309a22 100644 --- a/TUnit.Assertions/Extensions/EnumerableCount.cs +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -1,5 +1,5 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Collections; +using System.Collections; +using TUnit.Assertions.AssertConditions; namespace TUnit.Assertions; @@ -12,8 +12,81 @@ public EnumerableCount(AssertionBuilder assertionBuilder) AssertionBuilder = assertionBuilder; } - public AssertCondition EqualTo() + public AssertCondition EqualTo(int expected) { - return new EnumerableCountEqualToAssertCondition(AssertionBuilder, 0); + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + { + ArgumentNullException.ThrowIfNull(enumerable); + return GetCount(enumerable) == count; + }, + (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); + } + + public AssertCondition Empty => + new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, count, arg3) => + { + ArgumentNullException.ThrowIfNull(enumerable); + return GetCount(enumerable) == count; + }, + (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); + + public AssertCondition GreaterThan(int expected) + { + return new DelegateAssertCondition( + AssertionBuilder, + expected, + (enumerable, count, arg3) => + { + ArgumentNullException.ThrowIfNull(enumerable); + return GetCount(enumerable) > count; + }, + (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than {count}"); + } + + public AssertCondition GreaterThanOrEqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + { + ArgumentNullException.ThrowIfNull(enumerable); + return GetCount(enumerable) >= count; + }, + (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than or equal to {count}"); + } + + public AssertCondition LessThan(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + { + ArgumentNullException.ThrowIfNull(enumerable); + return GetCount(enumerable) < count; + }, + (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than {count}"); + } + + public AssertCondition LessThanOrEqualTo(int expected) + { + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + { + ArgumentNullException.ThrowIfNull(enumerable); + return GetCount(enumerable) <= count; + }, + (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than or equal to {count}"); + } + + private int GetCount(T? actualValue) + { + ArgumentNullException.ThrowIfNull(actualValue); + + if (actualValue is ICollection collection) + { + return collection.Count; + } + + if (actualValue is TInner[] array) + { + return array.Length; + } + + return actualValue.Count(); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/HasExtensions.cs b/TUnit.Assertions/Extensions/HasExtensions.cs index 646dd62eca..c056360549 100644 --- a/TUnit.Assertions/Extensions/HasExtensions.cs +++ b/TUnit.Assertions/Extensions/HasExtensions.cs @@ -1,5 +1,3 @@ -using TUnit.Assertions.AssertConditions.String; - namespace TUnit.Assertions; public static class HasExtensions diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index dcf645657f..ca92e38f57 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -13,62 +13,62 @@ public StringLength(AssertionBuilder assertionBuilder) public AssertCondition EqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => { - ArgumentNullException.ThrowIfNull(s); - return s.Length == i; + ArgumentNullException.ThrowIfNull(@string); + return @string.Length == length; }, - (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be equal to {i}"); + (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); } public AssertCondition Empty => - new DelegateAssertCondition(AssertionBuilder, 0, (s, i, arg3) => + new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, arg3) => { - ArgumentNullException.ThrowIfNull(s); - return s.Length == i; + ArgumentNullException.ThrowIfNull(@string); + return @string.Length == length; }, - (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be equal to {i}"); + (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); public AssertCondition GreaterThan(int expected) { return new DelegateAssertCondition( AssertionBuilder, expected, - (s, i, arg3) => + (@string, length, arg3) => { - ArgumentNullException.ThrowIfNull(s); - return s.Length > i; + ArgumentNullException.ThrowIfNull(@string); + return @string.Length > length; }, - (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be greater than {i}"); + (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be greater than {length}"); } public AssertCondition GreaterThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => { - ArgumentNullException.ThrowIfNull(s); - return s.Length >= i; + ArgumentNullException.ThrowIfNull(@string); + return @string.Length >= length; }, - (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be greater than or equal to {i}"); + (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}"); } public AssertCondition LessThan(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => { - ArgumentNullException.ThrowIfNull(s); - return s.Length < i; + ArgumentNullException.ThrowIfNull(@string); + return @string.Length < length; }, - (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be less than {i}"); + (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be less than {length}"); } public AssertCondition LessThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (s, i, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => { - ArgumentNullException.ThrowIfNull(s); - return s.Length <= i; + ArgumentNullException.ThrowIfNull(@string); + return @string.Length <= length; }, - (s, i, arg3) => $"{s} was {s?.Length} characters long but expected to be less than or equal to {i}"); + (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be less than or equal to {length}"); } } \ No newline at end of file From f9bb813d4d71b5b6eadb209c9cf52ad04b443890 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:45:06 +0000 Subject: [PATCH 087/124] InvertedAssertCondition.cs --- .../InvertedAssertCondition.cs | 20 ++++++++++ .../Extensions/EnumerableCount.cs | 24 ++++++------ TUnit.Assertions/Extensions/IsExtensions.cs | 38 ++++++++++++++++++- TUnit.Assertions/Extensions/StringLength.cs | 34 ++++++++++------- TUnit.Engine/SingleTestExecutor.cs | 2 +- TUnit.Pipeline/Program.cs | 2 +- 6 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs b/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs new file mode 100644 index 0000000000..3620bba6d8 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs @@ -0,0 +1,20 @@ +namespace TUnit.Assertions.AssertConditions; + +public class InvertedAssertCondition : AssertCondition +{ + private readonly AssertCondition _conditionToInvert; + + public InvertedAssertCondition(AssertCondition conditionToInvert, + Func messageFactory) : base(conditionToInvert.AssertionBuilder, conditionToInvert.ExpectedValue) + { + _conditionToInvert = conditionToInvert; + WithMessage(messageFactory); + } + + protected override string DefaultMessage => string.Empty; + + protected internal override bool Passes(TActual? actualValue, Exception? exception) + { + return !_conditionToInvert.Passes(actualValue, exception); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs index 71f0309a22..730c546d57 100644 --- a/TUnit.Assertions/Extensions/EnumerableCount.cs +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -14,63 +14,63 @@ public EnumerableCount(AssertionBuilder assertionBuilder) public AssertCondition EqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) == count; }, - (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); + (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); } public AssertCondition Empty => - new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, count, arg3) => + new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) == count; }, - (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); + (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); public AssertCondition GreaterThan(int expected) { return new DelegateAssertCondition( AssertionBuilder, expected, - (enumerable, count, arg3) => + (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) > count; }, - (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than {count}"); + (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than {count}"); } public AssertCondition GreaterThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) >= count; }, - (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than or equal to {count}"); + (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than or equal to {count}"); } public AssertCondition LessThan(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) < count; }, - (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than {count}"); + (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than {count}"); } public AssertCondition LessThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) <= count; }, - (enumerable, count, arg3) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than or equal to {count}"); + (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than or equal to {count}"); } private int GetCount(T? actualValue) diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs index 36c97aaadc..539a26457d 100644 --- a/TUnit.Assertions/Extensions/IsExtensions.cs +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -53,12 +53,46 @@ public static AssertCondition LessThanOrEqualTo(this Is @is, T expec public static AssertCondition Even(this Is @is) where T : INumber, IModulusOperators { - return new IsEvenAssertCondition(@is.AssertionBuilder); + return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value % 2 == 0; + }, + (value, _, _) => $"{value} was not even"); } public static AssertCondition Odd(this Is @is) where T : INumber, IModulusOperators { - return new IsOddAssertCondition(@is.AssertionBuilder); + return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value % 2 != 0; + }, + (value, _, _) => $"{value} was not odd"); + } + + public static AssertCondition Negative(this Is @is) where T : INumber + { + return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value < T.Zero; + }, + (value, _, _) => $"{value} was not negative"); + } + + public static AssertCondition Positive(this Is @is) where T : INumber + { + return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value > T.Zero; + }, + (value, _, _) => $"{value} was not positive"); } #endregion diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index ca92e38f57..22397a7106 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -13,62 +13,70 @@ public StringLength(AssertionBuilder assertionBuilder) public AssertCondition EqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length == length; }, - (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); + (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); } - public AssertCondition Empty => - new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, arg3) => + public AssertCondition IsEmpty => + new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length == length; }, - (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); + (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); + public AssertCondition IsNotEmpty => + new InvertedAssertCondition( + IsEmpty, + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to empty" + ); + + public AssertCondition GreaterThan(int expected) { return new DelegateAssertCondition( AssertionBuilder, expected, - (@string, length, arg3) => + (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length > length; }, - (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be greater than {length}"); + (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be greater than {length}"); } public AssertCondition GreaterThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length >= length; }, - (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}"); + (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}"); } public AssertCondition LessThan(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length < length; }, - (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be less than {length}"); + (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be less than {length}"); } public AssertCondition LessThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, arg3) => + return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length <= length; }, - (@string, length, arg3) => $"{@string} was {@string?.Length} characters long but expected to be less than or equal to {length}"); + (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be less than or equal to {length}"); } } \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 1017c068ca..c62ea37ba9 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -140,7 +140,7 @@ private async Task ExecuteTestMethodWithTimeout(TestDetails testDetails, object } var timeoutTask = Task.Delay(testDetails.Timeout, cancellationTokenSource.Token) - .ContinueWith(t => throw new TimeoutException(testDetails)); + .ContinueWith(_ => throw new TimeoutException(testDetails)); await await Task.WhenAny(timeoutTask, methodResult); } diff --git a/TUnit.Pipeline/Program.cs b/TUnit.Pipeline/Program.cs index 982b27198f..f50ca7fd20 100644 --- a/TUnit.Pipeline/Program.cs +++ b/TUnit.Pipeline/Program.cs @@ -2,7 +2,7 @@ using ModularPipelines.Host; await PipelineHostBuilder.Create() - .ConfigureServices((context, collection) => + .ConfigureServices((_, collection) => { collection.AddModulesFromAssembly(typeof(Program).Assembly); }) From 899ef8d731c37f0a9b3b945970c30ef9c2a1885a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:18:10 +0000 Subject: [PATCH 088/124] Fix --- .../AssertConditions/BaseAssertCondition.cs | 2 +- .../Generic/PropertyEqualsAssertCondition.cs | 2 +- .../Generic/PropertyOrMethodEqualsAssertCondition.cs | 2 +- TUnit.Assertions/Does.cs | 2 +- TUnit.Assertions/Extensions/EnumerableCount.cs | 12 ++++++------ TUnit.Assertions/Extensions/HasExtensions.cs | 10 ++++++---- TUnit.Assertions/Has.cs | 2 +- TUnit.Assertions/Is.cs | 2 +- TUnit.Assertions/Not.cs | 2 +- TUnit.TestProject/Tests.cs | 3 ++- TUnit/TUnit.csproj | 2 ++ 11 files changed, 23 insertions(+), 18 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index ff597f93ae..9e8bbf0aa9 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -6,7 +6,7 @@ namespace TUnit.Assertions.AssertConditions; public abstract class BaseAssertCondition { - internal readonly AssertionBuilder AssertionBuilder; + protected internal AssertionBuilder AssertionBuilder { get; } internal BaseAssertCondition(AssertionBuilder assertionBuilder) { diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index cd46f8e2d2..ebca797913 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -9,7 +9,7 @@ protected internal override bool Passes(TActual? actualValue, Exception? excepti { var propertyValue = GetPropertyValue(actualValue); - WithMessage(_ => $"Expected {ExpectedValue} but received {propertyValue}"); + WithMessage((_, _, _) => $"Expected {ExpectedValue} but received {propertyValue}"); return Equals(propertyValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index f37d7ece05..2b39257685 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -9,7 +9,7 @@ protected internal override bool Passes(TActual? actualValue, Exception? excepti { var propertyValue = GetPropertyValue(actualValue); - WithMessage(_ => $"Expected {ExpectedValue} but received {propertyValue}"); + WithMessage((_, _, _) => $"Expected {ExpectedValue} but received {propertyValue}"); return Equals(propertyValue, ExpectedValue); } diff --git a/TUnit.Assertions/Does.cs b/TUnit.Assertions/Does.cs index 52d5e2dd18..1047da039a 100644 --- a/TUnit.Assertions/Does.cs +++ b/TUnit.Assertions/Does.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions; public class Does { - protected AssertionBuilder AssertionBuilder { get; } + protected internal AssertionBuilder AssertionBuilder { get; } public Does(AssertionBuilder assertionBuilder) { diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs index 730c546d57..662553f05d 100644 --- a/TUnit.Assertions/Extensions/EnumerableCount.cs +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -3,9 +3,9 @@ namespace TUnit.Assertions; -public class EnumerableCount where T : IEnumerable +public class EnumerableCount where T : IEnumerable { - protected AssertionBuilder AssertionBuilder { get; } + protected internal AssertionBuilder AssertionBuilder { get; } public EnumerableCount(AssertionBuilder assertionBuilder) { @@ -81,12 +81,12 @@ private int GetCount(T? actualValue) { return collection.Count; } - - if (actualValue is TInner[] array) + + if (actualValue is IList list) { - return array.Length; + return list.Count; } - return actualValue.Count(); + return actualValue.Cast().Count(); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/HasExtensions.cs b/TUnit.Assertions/Extensions/HasExtensions.cs index c056360549..5a9dc3251e 100644 --- a/TUnit.Assertions/Extensions/HasExtensions.cs +++ b/TUnit.Assertions/Extensions/HasExtensions.cs @@ -1,14 +1,16 @@ +using System.Collections; + namespace TUnit.Assertions; public static class HasExtensions { - public static EnumerableCount Count(this Is @is) where T : IEnumerable + public static EnumerableCount Count(this Has has) where T : IEnumerable { - return new EnumerableCount(@is.AssertionBuilder); + return new EnumerableCount(has.AssertionBuilder); } - public static StringLength Length(this Is @is) + public static StringLength Length(this Has has) { - return new StringLength(@is.AssertionBuilder); + return new StringLength(has.AssertionBuilder); } } \ No newline at end of file diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 9811e4059e..80a371305d 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -4,7 +4,7 @@ namespace TUnit.Assertions; public class Has { - protected AssertionBuilder AssertionBuilder { get; } + protected internal AssertionBuilder AssertionBuilder { get; } public Has(AssertionBuilder assertionBuilder) { diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index fd6085b487..347ffc7759 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions; public partial class Is { - internal readonly AssertionBuilder AssertionBuilder; + protected internal AssertionBuilder AssertionBuilder { get; } public Is(AssertionBuilder assertionBuilder) { diff --git a/TUnit.Assertions/Not.cs b/TUnit.Assertions/Not.cs index b50cbfa2e8..42ad5533f8 100644 --- a/TUnit.Assertions/Not.cs +++ b/TUnit.Assertions/Not.cs @@ -5,7 +5,7 @@ namespace TUnit.Assertions; public class Not { - internal readonly AssertionBuilder AssertionBuilder; + protected internal AssertionBuilder AssertionBuilder { get; } public Not(AssertionBuilder assertionBuilder) { diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index fa6a57169d..915d483cde 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -214,7 +214,8 @@ public void Count3() public void Count4() { var list = new[] { 1, 2, 3 }; - Assert.That(list).Has.Count.EqualTo(3); + + Assert.That(list).Has.Count().EqualTo(3); } public static int One() => 1; diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index 9c1efe93f5..48f3c0b31d 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -8,8 +8,10 @@ + + From 33b5556191dd7f54ec9e6cb50f0b66c5591351c1 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:01:30 +0000 Subject: [PATCH 089/124] Working Filter.cs --- .../Attributes/TestCategoryAttribute.cs | 12 +++ TUnit.Core/TestDetails.cs | 19 ++++ .../Modules/PackTUnitFilesModule.cs | 2 +- .../Constants/TestAdapterConstants.cs | 7 ++ TUnit.TestAdapter/Filter.cs | 44 ++++++++ TUnit.TestAdapter/TUnitTestFilterProvider.cs | 100 ++++++++++++------ TUnit.TestProject/Tests.cs | 2 +- 7 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 TUnit.Core/Attributes/TestCategoryAttribute.cs create mode 100644 TUnit.TestAdapter/Filter.cs diff --git a/TUnit.Core/Attributes/TestCategoryAttribute.cs b/TUnit.Core/Attributes/TestCategoryAttribute.cs new file mode 100644 index 0000000000..796a932c99 --- /dev/null +++ b/TUnit.Core/Attributes/TestCategoryAttribute.cs @@ -0,0 +1,12 @@ +namespace TUnit.Core; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] +public class TestCategoryAttribute : TUnitAttribute +{ + public string Category { get; } + + public TestCategoryAttribute(string category) + { + Category = category; + } +} \ No newline at end of file diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 9a8edf84cf..c1e969808f 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -19,6 +19,7 @@ public TestDetails(MethodInfo MethodInfo, ArgumentValues = arguments?.Select(x => x.Value).ToArray(); TestName = MethodInfo.Name; + SimpleMethodName = MethodInfo.Name; DisplayName = MethodInfo.Name + GetArgumentValues(); ClassName = this.ClassType.Name; FullyQualifiedClassName = this.ClassType.FullName!; @@ -42,6 +43,8 @@ public TestDetails(MethodInfo MethodInfo, .FirstOrDefault(x => x.AttributeType == typeof(RepeatAttribute)) ?.ConstructorArguments.FirstOrDefault().Value as int? ?? 0; + AddCategories(methodAndClassAttributes); + Timeout = GetTimeout(methodAndClassAttributes); FileName = SourceLocation.FileName; @@ -51,6 +54,22 @@ public TestDetails(MethodInfo MethodInfo, Id = GenerateGuid(); } + private void AddCategories(CustomAttributeData[] methodAndClassAttributes) + { + var categoryAttributes = methodAndClassAttributes + .Where(x => x.AttributeType == typeof(TestCategoryAttribute)); + + var categories = categoryAttributes + .Select(x => x.ConstructorArguments.FirstOrDefault().Value) + .OfType(); + + Categories.AddRange(categories); + } + + public List Categories { get; } = new(); + + public string SimpleMethodName { get; set; } + private static TimeSpan GetTimeout(CustomAttributeData[] methodAndClassAttributes) { var timeoutMilliseconds = methodAndClassAttributes diff --git a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs index d7ecb1d172..9e023181b4 100644 --- a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs +++ b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs @@ -18,7 +18,7 @@ public class PackTUnitFilesModule : Module> var guid = Guid.NewGuid(); var version = $"0.0.1-alpha{guid}"; - var packedProjects = await projects.Value!.SelectAsync(async project => + await projects.Value!.SelectAsync(async project => { return await context.DotNet() .Pack( diff --git a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs index 16ae0eef6d..5ea3d7fd99 100644 --- a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs +++ b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs @@ -9,4 +9,11 @@ internal static class TestAdapterConstants public const string Name = "TUnit.Name"; public const string TestCategory = "TUnit.TestCategory"; + public static class Filters + { + public static readonly IReadOnlyList KnownFilters = [TestName, TestClasses, Categories]; + public const string TestName = "TestName"; + public const string TestClasses = "TestClass"; + public const string Categories = "Category"; + } } \ No newline at end of file diff --git a/TUnit.TestAdapter/Filter.cs b/TUnit.TestAdapter/Filter.cs new file mode 100644 index 0000000000..a0eac220f7 --- /dev/null +++ b/TUnit.TestAdapter/Filter.cs @@ -0,0 +1,44 @@ +using TUnit.TestAdapter.Constants; + +namespace TUnit.TestAdapter; + +public record Filter +{ + public List RunnableCategories { get; } = new(); + public List BannedCategories { get; } = new(); + public List RunnableTestNames { get; } = new(); + public List RunnableClasses { get; } = new(); + public List RunnableFullyQualifiedClasses { get; } = new(); + + public void AddFilter(string filterName, string? rawValue) + { + if (string.IsNullOrWhiteSpace(rawValue)) + { + return; + } + + foreach (var value in rawValue.Split(',')) + { + switch (filterName) + { + case TestAdapterConstants.Filters.TestName: + RunnableTestNames.Add(value); + return; + case TestAdapterConstants.Filters.TestClasses: + if (value.Contains('.')) + { + RunnableFullyQualifiedClasses.Add(value); + } + else + { + RunnableClasses.Add(value); + } + + return; + case TestAdapterConstants.Filters.Categories: + RunnableCategories.Add(value); + return; + } + } + } +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnitTestFilterProvider.cs b/TUnit.TestAdapter/TUnitTestFilterProvider.cs index 5f59a522e6..f865731bdb 100644 --- a/TUnit.TestAdapter/TUnitTestFilterProvider.cs +++ b/TUnit.TestAdapter/TUnitTestFilterProvider.cs @@ -1,40 +1,19 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using TUnit.Core; +using TUnit.TestAdapter.Constants; namespace TUnit.TestAdapter; -public class TUnitTestFilterProvider(IRunContext runContext) +public class TUnitTestFilterProvider(IRunContext runContext, IMessageLogger messageLogger) { - private static readonly Dictionary _supportedPropertiesCache; - private static readonly List SupportedProperties = []; - - static TUnitTestFilterProvider() - { - // Initialize the property cache - _supportedPropertiesCache = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["FullyQualifiedName"] = TestCaseProperties.FullyQualifiedName, - ["Name"] = TestCaseProperties.DisplayName, - ["TestCategory"] = TUnitProperties.TestCategory, - }; - - SupportedProperties.AddRange(_supportedPropertiesCache.Keys); - } - - public ITestCaseFilterExpression? GetFilter() - { - return runContext.GetTestCaseFilter(SupportedProperties, name => - _supportedPropertiesCache.TryGetValue(name, out var property) - ? property - : TestProperty.Find(name) - ); - } - public IEnumerable FilterTests(IEnumerable tests) { - var filter = GetFilter(); + var filterExpression = runContext.GetTestCaseFilter(null, _ => null); - if (filter is null) + messageLogger.SendMessage(TestMessageLevel.Informational, $"TestCaseFilterValue is: {filterExpression?.TestCaseFilterValue}"); + + if (filterExpression is null) { foreach (var testWithTestCase in tests) { @@ -46,12 +25,69 @@ public IEnumerable FilterTests(IEnumerable t foreach (var testWithTestCase in tests) { - var (testDetails, testCase) = testWithTestCase; + var (testDetails, _) = testWithTestCase; - if (filter.MatchTestCase(testCase, TestProperty.Find)) + if (string.IsNullOrWhiteSpace(filterExpression.TestCaseFilterValue)) { yield return testWithTestCase; + continue; + } + + var filter = ParseFilter(filterExpression.TestCaseFilterValue); + + if (TestMatchesFilter(testDetails, filter)) + { + yield return testWithTestCase; + } + } + } + + private Filter ParseFilter(string testCaseFilterValue) + { + var filter = new Filter(); + + foreach (var filterSegment in testCaseFilterValue.Split(';')) + { + var filterSplit = filterSegment.Split('='); + var filterName = filterSplit.FirstOrDefault(); + var filterValue = filterSplit.ElementAtOrDefault(1); + + if (string.IsNullOrWhiteSpace(filterName) || + !TestAdapterConstants.Filters.KnownFilters.Contains(filterName, StringComparer.InvariantCultureIgnoreCase)) + { + continue; } + + filter.AddFilter(filterName, filterValue); + } + + return filter; + } + + private bool TestMatchesFilter(TestDetails test, Filter filter) + { + messageLogger.SendMessage(TestMessageLevel.Informational, test.ToString()); + + if (filter.RunnableTestNames.Contains(test.SimpleMethodName)) + { + return true; } + + if (filter.RunnableCategories.Intersect(test.Categories).Any()) + { + return true; + } + + if (filter.RunnableClasses.Contains(test.ClassType.Name)) + { + return true; + } + + if (filter.RunnableFullyQualifiedClasses.Contains(test.ClassType.FullName!)) + { + return true; + } + + return false; } } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 915d483cde..88aaec1b16 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -215,7 +215,7 @@ public void Count4() { var list = new[] { 1, 2, 3 }; - Assert.That(list).Has.Count().EqualTo(3); + Assert.That(list).Has.Count().EqualTo(99); } public static int One() => 1; From fc64b4a2ae70194abae3e6ea21a69590f6eb6a5e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:08:59 +0000 Subject: [PATCH 090/124] Rework filters --- .../Constants/TestAdapterConstants.cs | 7 +-- TUnit.TestAdapter/Filter.cs | 13 ++++- TUnit.TestAdapter/TUnitTestFilterProvider.cs | 50 +++++++++++++------ 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs index 5ea3d7fd99..0572327567 100644 --- a/TUnit.TestAdapter/Constants/TestAdapterConstants.cs +++ b/TUnit.TestAdapter/Constants/TestAdapterConstants.cs @@ -11,9 +11,10 @@ internal static class TestAdapterConstants public static class Filters { - public static readonly IReadOnlyList KnownFilters = [TestName, TestClasses, Categories]; + public static readonly IReadOnlyList KnownFilters = [TestName, TestClass, Category, NotCategory]; public const string TestName = "TestName"; - public const string TestClasses = "TestClass"; - public const string Categories = "Category"; + public const string TestClass = "TestClass"; + public const string Category = "Category"; + public const string NotCategory = "NotCategory"; } } \ No newline at end of file diff --git a/TUnit.TestAdapter/Filter.cs b/TUnit.TestAdapter/Filter.cs index a0eac220f7..feacdfecf9 100644 --- a/TUnit.TestAdapter/Filter.cs +++ b/TUnit.TestAdapter/Filter.cs @@ -4,6 +4,8 @@ namespace TUnit.TestAdapter; public record Filter { + public bool IsEmpty { get; private set; } = true; + public List RunnableCategories { get; } = new(); public List BannedCategories { get; } = new(); public List RunnableTestNames { get; } = new(); @@ -19,12 +21,19 @@ public void AddFilter(string filterName, string? rawValue) foreach (var value in rawValue.Split(',')) { + if (string.IsNullOrWhiteSpace(value)) + { + continue; + } + + IsEmpty = false; + switch (filterName) { case TestAdapterConstants.Filters.TestName: RunnableTestNames.Add(value); return; - case TestAdapterConstants.Filters.TestClasses: + case TestAdapterConstants.Filters.TestClass: if (value.Contains('.')) { RunnableFullyQualifiedClasses.Add(value); @@ -35,7 +44,7 @@ public void AddFilter(string filterName, string? rawValue) } return; - case TestAdapterConstants.Filters.Categories: + case TestAdapterConstants.Filters.Category: RunnableCategories.Add(value); return; } diff --git a/TUnit.TestAdapter/TUnitTestFilterProvider.cs b/TUnit.TestAdapter/TUnitTestFilterProvider.cs index f865731bdb..422465842a 100644 --- a/TUnit.TestAdapter/TUnitTestFilterProvider.cs +++ b/TUnit.TestAdapter/TUnitTestFilterProvider.cs @@ -67,27 +67,49 @@ private Filter ParseFilter(string testCaseFilterValue) private bool TestMatchesFilter(TestDetails test, Filter filter) { messageLogger.SendMessage(TestMessageLevel.Informational, test.ToString()); - - if (filter.RunnableTestNames.Contains(test.SimpleMethodName)) - { - return true; - } - - if (filter.RunnableCategories.Intersect(test.Categories).Any()) - { - return true; - } - if (filter.RunnableClasses.Contains(test.ClassType.Name)) + if (filter.IsEmpty) { return true; } - if (filter.RunnableFullyQualifiedClasses.Contains(test.ClassType.FullName!)) + if (filter.BannedCategories.Intersect(test.Categories).Any()) { - return true; + return false; } - return false; + return AllowedTestName(test, filter) + && AllowedCategory(test, filter) + && AllowedClass(test, filter); + } + + private static bool AllowedTestName(TestDetails test, Filter filter) + { + return !filter.RunnableTestNames.Any() || + filter.RunnableTestNames.Contains(test.SimpleMethodName, StringComparer.InvariantCultureIgnoreCase); + } + + private static bool AllowedCategory(TestDetails test, Filter filter) + { + return !filter.RunnableCategories.Any() || + filter.RunnableCategories.Intersect(test.Categories, StringComparer.InvariantCultureIgnoreCase).Any(); + } + + private static bool AllowedClass(TestDetails test, Filter filter) + { + return AllowedSimpleClass(test, filter) + && AllowedFullyQualifiedClass(test, filter); + } + + private static bool AllowedSimpleClass(TestDetails test, Filter filter) + { + return !filter.RunnableClasses.Any() || + filter.RunnableClasses.Contains(test.ClassType.Name, StringComparer.InvariantCultureIgnoreCase); + } + + private static bool AllowedFullyQualifiedClass(TestDetails test, Filter filter) + { + return !filter.RunnableFullyQualifiedClasses.Any() || + filter.RunnableFullyQualifiedClasses.Contains(test.ClassType.FullName, StringComparer.InvariantCultureIgnoreCase); } } \ No newline at end of file From 3ada78a63b4e94766efc49abb0a9506ba7e26ea2 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:49:27 +0000 Subject: [PATCH 091/124] Rework connectors to reduce duplication for and/or cases --- .../AwaitAssertionCodeFixProvider.cs | 2 +- .../AssertConditions/ConnectorType.cs | 1 + .../AssertConditions/Connectors/AndHas.cs | 4 - .../AssertConditions/Connectors/AndIs.cs | 18 --- .../Connectors/AssertConditionAnd.cs | 3 + .../Connectors/AssertConditionOr.cs | 3 + .../AssertConditions/Connectors/OrIs.cs | 18 --- .../AssertConditions/Operators/And.cs | 4 +- .../AssertConditions/Operators/Or.cs | 6 +- .../AssertConditions/Throws/WithMessage.cs | 26 +-- TUnit.Assertions/AssertionBuilder.cs | 12 +- TUnit.Assertions/Does.cs | 15 +- .../Extensions/AndIsExtensions.cs | 42 ----- TUnit.Assertions/Extensions/Connector.cs | 27 ++++ .../Extensions/EnumerableCount.cs | 73 +++++---- TUnit.Assertions/Extensions/HasExtensions.cs | 4 +- TUnit.Assertions/Extensions/OrIsExtensions.cs | 42 ----- TUnit.Assertions/Extensions/StringLength.cs | 18 +-- TUnit.Assertions/Has.cs | 8 +- TUnit.Assertions/Is.cs | 22 +-- TUnit.Assertions/IsNot.cs | 18 +++ TUnit.Assertions/Not.cs | 17 -- TUnit.Assertions/Throws.cs | 26 +-- TUnit.TestProject/TUnit.TestProject.csproj | 3 +- TUnit.TestProject/Tests.cs | 149 ++++++++---------- TUnit/TUnit.csproj | 3 +- 26 files changed, 238 insertions(+), 326 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/Connectors/AndIs.cs delete mode 100644 TUnit.Assertions/AssertConditions/Connectors/OrIs.cs delete mode 100644 TUnit.Assertions/Extensions/AndIsExtensions.cs create mode 100644 TUnit.Assertions/Extensions/Connector.cs delete mode 100644 TUnit.Assertions/Extensions/OrIsExtensions.cs create mode 100644 TUnit.Assertions/IsNot.cs delete mode 100644 TUnit.Assertions/Not.cs diff --git a/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs index 8cabe30789..121e2d3abc 100644 --- a/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs +++ b/TUnit.Analyzers/TUnit.Analyzers/AwaitAssertionCodeFixProvider.cs @@ -24,7 +24,7 @@ public class AwaitAssertionCodeFixProvider : CodeFixProvider ImmutableArray.Create(AwaitAssertionAnalyzer.DiagnosticId); // If you don't need the 'fix all' behaviour, return null. - public override FixAllProvider? GetFixAllProvider() => null; + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { diff --git a/TUnit.Assertions/AssertConditions/ConnectorType.cs b/TUnit.Assertions/AssertConditions/ConnectorType.cs index 623b575f17..048182652a 100644 --- a/TUnit.Assertions/AssertConditions/ConnectorType.cs +++ b/TUnit.Assertions/AssertConditions/ConnectorType.cs @@ -2,6 +2,7 @@ public enum ConnectorType { + None, And, Or } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs index ca7dd72614..3f1ac75aba 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs @@ -11,10 +11,6 @@ public AndHas(BaseAssertCondition otherAssertCondition) OtherAssertCondition = otherAssertCondition; } - public Property Count => new("Count", ConnectorType.And, OtherAssertCondition); - public Property Length => new("Length", ConnectorType.And, OtherAssertCondition); - public Property Value => new("Value", ConnectorType.And, OtherAssertCondition); - public Property Property(string name) => new(name, ConnectorType.And, OtherAssertCondition); public Property Property(string name) => new(name, ConnectorType.And, OtherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs b/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs deleted file mode 100644 index f7e9f45d32..0000000000 --- a/TUnit.Assertions/AssertConditions/Connectors/AndIs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using TUnit.Assertions.AssertConditions.Generic; - -namespace TUnit.Assertions.AssertConditions.Connectors; - -public class AndIs -{ - internal BaseAssertCondition OtherAssertCondition { get; } - - public AndIs(BaseAssertCondition otherAssertCondition) - { - OtherAssertCondition = otherAssertCondition; - } - - public AssertConditionAnd EqualTo(TExpected expected) - { - return new AssertConditionAnd(OtherAssertCondition, new EqualsAssertCondition(OtherAssertCondition.AssertionBuilder, expected)); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index 5e87362a42..daeb5a331b 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -7,6 +7,9 @@ public sealed class AssertConditionAnd : BaseAssertCondition public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) { + ArgumentNullException.ThrowIfNull(condition1); + ArgumentNullException.ThrowIfNull(condition2); + _condition1 = condition1; _condition2 = condition2; } diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index 6bbb6a85f7..7524d64ee2 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -7,6 +7,9 @@ public sealed class AssertConditionOr : BaseAssertCondition public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) { + ArgumentNullException.ThrowIfNull(condition1); + ArgumentNullException.ThrowIfNull(condition2); + _condition1 = condition1; _condition2 = condition2; } diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs b/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs deleted file mode 100644 index 138823e735..0000000000 --- a/TUnit.Assertions/AssertConditions/Connectors/OrIs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using TUnit.Assertions.AssertConditions.Generic; - -namespace TUnit.Assertions.AssertConditions.Connectors; - -public class OrIs -{ - internal BaseAssertCondition OtherAssertCondition { get; } - - public OrIs(BaseAssertCondition otherAssertCondition) - { - OtherAssertCondition = otherAssertCondition; - } - - public AssertConditionOr EqualTo(TExpected expected) - { - return new AssertConditionOr(OtherAssertCondition, new EqualsAssertCondition(OtherAssertCondition.AssertionBuilder, expected)); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/And.cs b/TUnit.Assertions/AssertConditions/Operators/And.cs index 2e7bb92113..0c44898a3c 100644 --- a/TUnit.Assertions/AssertConditions/Operators/And.cs +++ b/TUnit.Assertions/AssertConditions/Operators/And.cs @@ -11,6 +11,6 @@ public And(BaseAssertCondition otherAssertCondition) _otherAssertCondition = otherAssertCondition; } - public AndIs Is => new(_otherAssertCondition); - public AndHas Has => new(_otherAssertCondition); + public Is Is => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); + public Has Has => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/Or.cs b/TUnit.Assertions/AssertConditions/Operators/Or.cs index 873437a0dc..7ac549210a 100644 --- a/TUnit.Assertions/AssertConditions/Operators/Or.cs +++ b/TUnit.Assertions/AssertConditions/Operators/Or.cs @@ -1,5 +1,3 @@ -using TUnit.Assertions.AssertConditions.Connectors; - namespace TUnit.Assertions.AssertConditions.Operators; public class Or @@ -11,6 +9,6 @@ public Or(BaseAssertCondition otherAssertCondition) _otherAssertCondition = otherAssertCondition; } - public OrIs Is => new(_otherAssertCondition); - public OrHas Has => new(_otherAssertCondition); + public Is Is => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); + public Has Has => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs b/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs index 1fff6a4681..6e6c482bbb 100644 --- a/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs +++ b/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs @@ -1,31 +1,31 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class WithMessage +public class WithMessage : Connector { protected AssertionBuilder AssertionBuilder { get; } - - public WithMessage(AssertionBuilder assertionBuilder) + + public WithMessage(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - - public AssertCondition EqualTo(string expected) + + public BaseAssertCondition EqualTo(string expected) { return EqualTo(expected, StringComparison.Ordinal); } - - public AssertCondition EqualTo(string expected, StringComparison stringComparison) + + public BaseAssertCondition EqualTo(string expected, StringComparison stringComparison) { - return new ThrowsWithMessageEqualToAssertCondition(AssertionBuilder, expected, stringComparison); + return Wrap(new ThrowsWithMessageEqualToAssertCondition(AssertionBuilder, expected, stringComparison)); } - - public AssertCondition Containing(string expected) + + public BaseAssertCondition Containing(string expected) { return Containing(expected, StringComparison.Ordinal); } - - public AssertCondition Containing(string expected, StringComparison stringComparison) + + public BaseAssertCondition Containing(string expected, StringComparison stringComparison) { - return new ThrowsWithMessageContainingAssertCondition(AssertionBuilder, expected, stringComparison); + return Wrap(new ThrowsWithMessageContainingAssertCondition(AssertionBuilder, expected, stringComparison)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs index a901c1a165..08a37985d3 100644 --- a/TUnit.Assertions/AssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -1,12 +1,14 @@ -namespace TUnit.Assertions; +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; public abstract class AssertionBuilder { protected internal abstract Task> GetAssertionData(); - public Does Does => new(this); - public Throws Throws => new(this); - public Is Is => new(this); - public Has Has => new(this); + public Does Does => new(this, ConnectorType.None, null); + public Throws Throws => new(this, ConnectorType.None, null); + public Is Is => new(this, ConnectorType.None, null); + public Has Has => new(this, ConnectorType.None, null); } \ No newline at end of file diff --git a/TUnit.Assertions/Does.cs b/TUnit.Assertions/Does.cs index 1047da039a..025836eefe 100644 --- a/TUnit.Assertions/Does.cs +++ b/TUnit.Assertions/Does.cs @@ -1,20 +1,15 @@ -using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions; namespace TUnit.Assertions; -public class Does +public class Does : Connector { protected internal AssertionBuilder AssertionBuilder { get; } - public Does(AssertionBuilder assertionBuilder) + public Does(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - - public Property Count => new(AssertionBuilder, "Count"); - public Property Length => new(AssertionBuilder, "Length"); - public Property Value => new(AssertionBuilder, "Value"); - - public Property Property(string name) => new(AssertionBuilder, name); - public Property Property(string name) => new(AssertionBuilder, name); + + } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/AndIsExtensions.cs b/TUnit.Assertions/Extensions/AndIsExtensions.cs deleted file mode 100644 index 6fb15b66ba..0000000000 --- a/TUnit.Assertions/Extensions/AndIsExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Numerics; -using TUnit.Assertions.AssertConditions.Connectors; -using TUnit.Assertions.AssertConditions.Generic; -using TUnit.Assertions.AssertConditions.Numbers; - -namespace TUnit.Assertions; - -public static class AndIsExtensions -{ - public static AssertConditionAnd Zero(this AndIs andIs) where T : INumber => - new(andIs.OtherAssertCondition, new EqualsAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, T.Zero)); - - public static AssertConditionAnd GreaterThan(this AndIs andIs, T expected) where T : INumber - { - return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionAnd GreaterThanOrEqualTo(this AndIs andIs, T expected) where T : INumber - { - return new(andIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionAnd LessThan(this AndIs andIs, T expected) where T : INumber - { - return new(andIs.OtherAssertCondition, new GreaterThanAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionAnd LessThanOrEqualTo(this AndIs andIs, T expected) where T : INumber - { - return new(andIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(andIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionAnd Even(this AndIs andIs) where T : INumber, IModulusOperators - { - return new(andIs.OtherAssertCondition, new IsEvenAssertCondition(andIs.OtherAssertCondition.AssertionBuilder)); - } - - public static AssertConditionAnd Odd(this AndIs andIs) where T : INumber, IModulusOperators - { - return new(andIs.OtherAssertCondition, new IsOddAssertCondition(andIs.OtherAssertCondition.AssertionBuilder)); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/Connector.cs b/TUnit.Assertions/Extensions/Connector.cs new file mode 100644 index 0000000000..b07f821127 --- /dev/null +++ b/TUnit.Assertions/Extensions/Connector.cs @@ -0,0 +1,27 @@ +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Connectors; + +namespace TUnit.Assertions; + +public abstract class Connector +{ + public ConnectorType ConnectorType { get; } + public BaseAssertCondition? OtherAssertCondition { get; } + + public Connector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) + { + ConnectorType = connectorType; + OtherAssertCondition = otherAssertCondition; + } + + public BaseAssertCondition Wrap(BaseAssertCondition assertCondition) + { + return ConnectorType switch + { + ConnectorType.None => assertCondition, + ConnectorType.And => new AssertConditionAnd(OtherAssertCondition!, assertCondition), + ConnectorType.Or => new AssertConditionAnd(OtherAssertCondition!, assertCondition), + _ => throw new ArgumentOutOfRangeException(nameof(ConnectorType), ConnectorType, "Unknown connector type") + }; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs index 662553f05d..fc609dc102 100644 --- a/TUnit.Assertions/Extensions/EnumerableCount.cs +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -3,76 +3,89 @@ namespace TUnit.Assertions; -public class EnumerableCount where T : IEnumerable +public class EnumerableCount : Connector where T : IEnumerable { protected internal AssertionBuilder AssertionBuilder { get; } - public EnumerableCount(AssertionBuilder assertionBuilder) + public EnumerableCount(AssertionBuilder assertionBuilder, ConnectorType connectorType, + BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - - public AssertCondition EqualTo(int expected) + + public BaseAssertCondition EqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) == count; }, - (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); + (enumerable, count, _) => + $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}") + ); } - - public AssertCondition Empty => - new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, count, _) => + + public BaseAssertCondition Empty => + Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) == count; }, - (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}"); + (enumerable, count, _) => + $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}") + ); - public AssertCondition GreaterThan(int expected) + public BaseAssertCondition GreaterThan(int expected) { - return new DelegateAssertCondition( - AssertionBuilder, - expected, + return Wrap(new DelegateAssertCondition( + AssertionBuilder, + expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) > count; }, - (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than {count}"); + (enumerable, count, _) => + $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than {count}") + ); } - - public AssertCondition GreaterThanOrEqualTo(int expected) + + public BaseAssertCondition GreaterThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) >= count; }, - (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than or equal to {count}"); + (enumerable, count, _) => + $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than or equal to {count}") + ); } - - public AssertCondition LessThan(int expected) + + public BaseAssertCondition LessThan(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) < count; }, - (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than {count}"); + (enumerable, count, _) => + $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than {count}") + ); } - - public AssertCondition LessThanOrEqualTo(int expected) + + public BaseAssertCondition LessThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) <= count; }, - (enumerable, count, _) => $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than or equal to {count}"); + (enumerable, count, _) => + $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than or equal to {count}") + ); } - + private int GetCount(T? actualValue) { ArgumentNullException.ThrowIfNull(actualValue); @@ -81,12 +94,12 @@ private int GetCount(T? actualValue) { return collection.Count; } - + if (actualValue is IList list) { return list.Count; } - + return actualValue.Cast().Count(); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/HasExtensions.cs b/TUnit.Assertions/Extensions/HasExtensions.cs index 5a9dc3251e..528bb2b848 100644 --- a/TUnit.Assertions/Extensions/HasExtensions.cs +++ b/TUnit.Assertions/Extensions/HasExtensions.cs @@ -6,11 +6,11 @@ public static class HasExtensions { public static EnumerableCount Count(this Has has) where T : IEnumerable { - return new EnumerableCount(has.AssertionBuilder); + return new EnumerableCount(has.AssertionBuilder, has.ConnectorType, has.OtherAssertCondition); } public static StringLength Length(this Has has) { - return new StringLength(has.AssertionBuilder); + return new StringLength(has.AssertionBuilder, has.ConnectorType, has.OtherAssertCondition); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/OrIsExtensions.cs b/TUnit.Assertions/Extensions/OrIsExtensions.cs deleted file mode 100644 index df07861aae..0000000000 --- a/TUnit.Assertions/Extensions/OrIsExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Numerics; -using TUnit.Assertions.AssertConditions.Connectors; -using TUnit.Assertions.AssertConditions.Generic; -using TUnit.Assertions.AssertConditions.Numbers; - -namespace TUnit.Assertions; - -public static class OrIsExtensions -{ - public static AssertConditionOr Zero(this OrIs orIs) where T : INumber => - new(orIs.OtherAssertCondition, new EqualsAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, T.Zero)); - - public static AssertConditionOr GreaterThan(this OrIs orIs, T expected) where T : INumber - { - return new(orIs.OtherAssertCondition, new GreaterThanAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionOr GreaterThanOrEqualTo(this OrIs orIs, T expected) where T : INumber - { - return new(orIs.OtherAssertCondition, new GreaterThanOrEqualToAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionOr LessThan(this OrIs orIs, T expected) where T : INumber - { - return new(orIs.OtherAssertCondition, new GreaterThanAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionOr LessThanOrEqualTo(this OrIs orIs, T expected) where T : INumber - { - return new(orIs.OtherAssertCondition, new LessThanOrEqualToAssertCondition(orIs.OtherAssertCondition.AssertionBuilder, expected)); - } - - public static AssertConditionOr Even(this OrIs orIs) where T : INumber, IModulusOperators - { - return new(orIs.OtherAssertCondition, new IsEvenAssertCondition(orIs.OtherAssertCondition.AssertionBuilder)); - } - - public static AssertConditionOr Odd(this OrIs orIs) where T : INumber, IModulusOperators - { - return new(orIs.OtherAssertCondition, new IsOddAssertCondition(orIs.OtherAssertCondition.AssertionBuilder)); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index 22397a7106..8d794e1b4c 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -2,15 +2,15 @@ namespace TUnit.Assertions; -public class StringLength +public class StringLength : Connector { protected AssertionBuilder AssertionBuilder { get; } - - public StringLength(AssertionBuilder assertionBuilder) + + public StringLength(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - + public AssertCondition EqualTo(int expected) { return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => @@ -20,7 +20,7 @@ public AssertCondition EqualTo(int expected) }, (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); } - + public AssertCondition IsEmpty => new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, _) => { @@ -36,7 +36,7 @@ public AssertCondition EqualTo(int expected) $"{@string} was {@string?.Length} characters long but expected to empty" ); - + public AssertCondition GreaterThan(int expected) { return new DelegateAssertCondition( @@ -49,7 +49,7 @@ public AssertCondition GreaterThan(int expected) }, (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be greater than {length}"); } - + public AssertCondition GreaterThanOrEqualTo(int expected) { return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => @@ -59,7 +59,7 @@ public AssertCondition GreaterThanOrEqualTo(int expected) }, (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}"); } - + public AssertCondition LessThan(int expected) { return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => @@ -69,7 +69,7 @@ public AssertCondition LessThan(int expected) }, (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be less than {length}"); } - + public AssertCondition LessThanOrEqualTo(int expected) { return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 80a371305d..3cd8240f93 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -1,16 +1,18 @@ -using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Generic; namespace TUnit.Assertions; -public class Has +public class Has : Connector { protected internal AssertionBuilder AssertionBuilder { get; } - public Has(AssertionBuilder assertionBuilder) + public Has(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } public Property Property(string name) => new(AssertionBuilder, name); + public Property Property(string name) => new(AssertionBuilder, name); } \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 347ffc7759..011570e9d3 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -3,27 +3,29 @@ namespace TUnit.Assertions; -public partial class Is +public class Is : Connector { protected internal AssertionBuilder AssertionBuilder { get; } - public Is(AssertionBuilder assertionBuilder) + public Is(AssertionBuilder assertionBuilder, ConnectorType connectorType, + BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - - public AssertCondition EqualTo(TActual expected) + + public BaseAssertCondition EqualTo(TActual expected) { - return new EqualsAssertCondition(AssertionBuilder, expected); + return Wrap(new EqualsAssertCondition(AssertionBuilder, expected)); } - public AssertCondition SameReference(TActual expected) + public BaseAssertCondition SameReference(TActual expected) { - return new SameReferenceAssertCondition(AssertionBuilder, expected); + return Wrap(new SameReferenceAssertCondition(AssertionBuilder, expected)); } - public AssertCondition Null => new NullAssertCondition(AssertionBuilder); - public AssertCondition TypeOf() => new TypeOfAssertCondition(AssertionBuilder); + public BaseAssertCondition Null => Wrap(new NullAssertCondition(AssertionBuilder)); + + public BaseAssertCondition TypeOf() => Wrap(new TypeOfAssertCondition(AssertionBuilder)); - public Not Not => new(AssertionBuilder); + public IsNot IsNot => new(AssertionBuilder, ConnectorType, OtherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/IsNot.cs b/TUnit.Assertions/IsNot.cs new file mode 100644 index 0000000000..e80e3061c7 --- /dev/null +++ b/TUnit.Assertions/IsNot.cs @@ -0,0 +1,18 @@ +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Generic; + +namespace TUnit.Assertions; + +public class IsNot : Connector +{ + protected internal AssertionBuilder AssertionBuilder { get; } + + public IsNot(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + { + AssertionBuilder = assertionBuilder; + } + + public BaseAssertCondition Null => Wrap(new NotNullAssertCondition(AssertionBuilder)); + + public BaseAssertCondition TypeOf() => Wrap(new NotTypeOfAssertCondition(AssertionBuilder)); +} \ No newline at end of file diff --git a/TUnit.Assertions/Not.cs b/TUnit.Assertions/Not.cs deleted file mode 100644 index 42ad5533f8..0000000000 --- a/TUnit.Assertions/Not.cs +++ /dev/null @@ -1,17 +0,0 @@ -using TUnit.Assertions.AssertConditions; -using TUnit.Assertions.AssertConditions.Generic; - -namespace TUnit.Assertions; - -public class Not -{ - protected internal AssertionBuilder AssertionBuilder { get; } - - public Not(AssertionBuilder assertionBuilder) - { - AssertionBuilder = assertionBuilder; - } - - public AssertCondition Null => new NotNullAssertCondition(AssertionBuilder); - public AssertCondition TypeOf() => new NotTypeOfAssertCondition(AssertionBuilder); -} \ No newline at end of file diff --git a/TUnit.Assertions/Throws.cs b/TUnit.Assertions/Throws.cs index 3222c10a62..aab13f13d8 100644 --- a/TUnit.Assertions/Throws.cs +++ b/TUnit.Assertions/Throws.cs @@ -1,20 +1,26 @@ -using TUnit.Assertions.AssertConditions.Throws; +using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Throws; namespace TUnit.Assertions; -public class Throws +public class Throws : Connector { protected AssertionBuilder AssertionBuilder { get; } - public Throws(AssertionBuilder assertionBuilder) + public Throws(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - - public WithMessage WithMessage => new(AssertionBuilder); - - public ThrowsNothingAssertCondition Nothing => new(AssertionBuilder); - public ThrowsAnythingAssertCondition Exception => new(AssertionBuilder); - public ThrowsExactTypeOfAssertCondition TypeOf() => new(AssertionBuilder); - public ThrowsSubClassOfAssertCondition SubClassOf() => new(AssertionBuilder); + + public WithMessage WithMessage => new(AssertionBuilder, ConnectorType, OtherAssertCondition); + + public BaseAssertCondition Nothing => Wrap(new ThrowsNothingAssertCondition(AssertionBuilder)); + + public BaseAssertCondition Exception => + Wrap(new ThrowsAnythingAssertCondition(AssertionBuilder)); + + public BaseAssertCondition TypeOf() => Wrap(new ThrowsExactTypeOfAssertCondition(AssertionBuilder)); + + public BaseAssertCondition SubClassOf() => + Wrap(new ThrowsSubClassOfAssertCondition(AssertionBuilder)); } \ No newline at end of file diff --git a/TUnit.TestProject/TUnit.TestProject.csproj b/TUnit.TestProject/TUnit.TestProject.csproj index bfb59c414d..866250782b 100644 --- a/TUnit.TestProject/TUnit.TestProject.csproj +++ b/TUnit.TestProject/TUnit.TestProject.csproj @@ -19,7 +19,8 @@ - + diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 88aaec1b16..93464bc86f 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -11,135 +11,135 @@ public class Tests // } [Test] - public void Test1() + public async Task Test1() { var value = "1"; - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [Test] - public void Test2() + public async Task Test2() { var value = "2"; - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [Test] public async Task Test3() { await Task.Yield(); var value = "1"; - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [Test] public async Task Test4() { await Task.Yield(); var value = "2"; - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [TestWithData("1")] [TestWithData("2")] - public void ParameterisedTests1(string value) + public async Task ParameterisedTests1(string value) { - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [TestWithData("1")] [TestWithData("2")] public async Task ParameterisedTests2(string value) { await Task.Yield(); - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } [Test, Skip("Reason1")] - public void Skip1() + public async Task Skip1() { var value = "1"; - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [Test, Skip("Reason2")] public async Task Skip2() { await Task.Yield(); var value = "1"; - Assert.That(value); + await Assert.That(value).Is.EqualTo("1"); } - + [TestDataSource(nameof(One))] - public void TestDataSource1(int value) + public async Task TestDataSource1(int value) { - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(One))] public async Task TestDataSource2(int value) { await Task.Yield(); - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(Two))] - public void TestDataSource3(int value) + public async Task TestDataSource3(int value) { - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(Two))] public async Task TestDataSource4(int value) { await Task.Yield(); - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(TestDataSources), nameof(One))] - public void TestDataSource5(int value) + public async Task TestDataSource5(int value) { - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(TestDataSources), nameof(One))] public async Task TestDataSource6(int value) { await Task.Yield(); - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(TestDataSources), nameof(Two))] - public void TestDataSource7(int value) + public async Task TestDataSource7(int value) { - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } - + [TestDataSource(nameof(TestDataSources), nameof(Two))] public async Task TestDataSource8(int value) { await Task.Yield(); - Assert.That(value); + await Assert.That(value).Is.EqualTo(1); } [Test] - public void TestContext1() + public async Task TestContext1() { - Assert.That(TestContext.Current.TestName); + await Assert.That(TestContext.Current.TestName).Is.EqualTo(nameof(TestContext1)); } - + [Test] - public void TestContext2() + public async Task TestContext2() { - Assert.That(TestContext.Current.TestName); + await Assert.That(TestContext.Current.TestName).Is.EqualTo(nameof(TestContext1)); } - + [Test] - public void Throws1() + public async Task Throws1() { - Assert.That(() => new string([])); + await Assert.That(() => new string([])).Is.EqualTo(string.Empty); } - + [Test] public async Task Throws2() { @@ -149,13 +149,13 @@ await Assert.That(async () => new string([]); }).Is.EqualTo(string.Empty); } - + [Test] - public void Throws3() + public async Task Throws3() { - Assert.That(() => throw new ApplicationException()); + await Assert.That(() => throw new ApplicationException()).Throws.Exception; } - + [Test] public async Task Throws4() { @@ -163,61 +163,42 @@ await Assert.That(async () => { await Task.Yield(); return true; - }).Throws.Nothing.And.Is.EqualTo(false).Or.Is.EqualTo(true); + }).Throws.Nothing; } - + [Test, Timeout(500)] + [TestCategory("Fail")] public async Task Timeout1() { await Task.Delay(TimeSpan.FromSeconds(5)); } [Test] - public void String_And_Condition() + public async Task String_And_Condition() { - Assert.That("1"); - + await Assert.That("1").Is.EqualTo("1").And.Has.Length().EqualTo(1); } - + [Test] - public void String_And_Condition2() + public async Task String_And_Condition2() { - Assert.That("1"); + await Assert.That("1").Is.EqualTo("2").And.Has.Length().EqualTo(1); } - + [Test] - public void Count1() + public async Task Count1() { var list = new List { 1, 2, 3 }; - Assert.That(list); - Assert.That(list); + await Assert.That(list).Is.EquivalentTo(new[] { 1, 2, 3 }).And.Has.Count().EqualTo(3); } - + [Test] - public void Count2() + public async Task Count2() { var list = new List { 1, 2, 3 }; - Assert.That(list); + await Assert.That(list).Is.EquivalentTo(new[] { 1, 2, 3 }).And.Has.Count().EqualTo(5); } - - [Test] - public void Count3() - { - var list = new[] { 1, 2, 3 }.ToArray(); - Assert.That(list).Is.EquivalentTo(new List { 1, 2, 3 }); - - Assert.That(list); - } - - [Test] - public void Count4() - { - var list = new[] { 1, 2, 3 }; - - Assert.That(list).Has.Count().EqualTo(99); - } - public static int One() => 1; public static int Two() => 2; } \ No newline at end of file diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index 48f3c0b31d..d9809494e8 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -8,7 +8,8 @@ - + From a9db7207648774ec89e88978bb6b81de33acb945 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:52:05 +0000 Subject: [PATCH 092/124] StringLength.cs fixes --- TUnit.Assertions/Extensions/StringLength.cs | 63 +++++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index 8d794e1b4c..484d6840a1 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -5,78 +5,91 @@ namespace TUnit.Assertions; public class StringLength : Connector { protected AssertionBuilder AssertionBuilder { get; } - - public StringLength(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + + public StringLength(AssertionBuilder assertionBuilder, ConnectorType connectorType, + BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public AssertCondition EqualTo(int expected) + public BaseAssertCondition EqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length == length; }, - (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to be equal to {length}") + ); } - public AssertCondition IsEmpty => - new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, _) => + public BaseAssertCondition IsEmpty => + Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length == length; }, - (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be equal to {length}"); + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to be equal to {length}") + ); - public AssertCondition IsNotEmpty => - new InvertedAssertCondition( + public BaseAssertCondition IsNotEmpty => + Wrap(new InvertedAssertCondition( IsEmpty, (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to empty" - ); + )); - public AssertCondition GreaterThan(int expected) + public BaseAssertCondition GreaterThan(int expected) { - return new DelegateAssertCondition( - AssertionBuilder, - expected, + return Wrap(new DelegateAssertCondition( + AssertionBuilder, + expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length > length; }, - (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be greater than {length}"); + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to be greater than {length}") + ); } - public AssertCondition GreaterThanOrEqualTo(int expected) + public BaseAssertCondition GreaterThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length >= length; }, - (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}"); + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}") + ); } - public AssertCondition LessThan(int expected) + public BaseAssertCondition LessThan(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length < length; }, - (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be less than {length}"); + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to be less than {length}") + ); } - public AssertCondition LessThanOrEqualTo(int expected) + public BaseAssertCondition LessThanOrEqualTo(int expected) { - return new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length <= length; }, - (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to be less than or equal to {length}"); + (@string, length, _) => + $"{@string} was {@string?.Length} characters long but expected to be less than or equal to {length}") + ); } } \ No newline at end of file From 84d8ad4bfeb34b360dcecc994ea6ca52cd9574a1 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:54:00 +0000 Subject: [PATCH 093/124] StringLength.cs fixes --- TUnit.Assertions/Extensions/StringLength.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index 484d6840a1..dfb11ba41f 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -35,8 +35,11 @@ public BaseAssertCondition EqualTo(int expected) ); public BaseAssertCondition IsNotEmpty => - Wrap(new InvertedAssertCondition( - IsEmpty, + Wrap(new DelegateAssertCondition(AssertionBuilder, default, (@string, length, _) => + { + ArgumentNullException.ThrowIfNull(@string); + return @string.Length > 0; + }, (@string, length, _) => $"{@string} was {@string?.Length} characters long but expected to empty" )); From e95b12183a168e954b4f3ecd04b3903f2cb5151a Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:02:31 +0000 Subject: [PATCH 094/124] Better message for and condition failures --- .../Connectors/AssertConditionAnd.cs | 22 ++++++++++++++++--- TUnit.Assertions/Extensions/StringLength.cs | 14 ++++++------ TUnit.TestProject/Tests.cs | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index daeb5a331b..ba69545a86 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -14,9 +14,25 @@ public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCon _condition2 = condition2; } - protected internal override string Message => - !_condition1.Passes(ActualValue, Exception) ? _condition1.Message : - !_condition2.Passes(ActualValue, Exception) ? _condition2.Message : string.Empty; + protected internal override string Message + { + get + { + var messages = new List(2); + + if (!_condition1.Assert(ActualValue, Exception)) + { + messages.Add(_condition1.Message); + } + + if (!_condition2.Assert(ActualValue, Exception)) + { + messages.Add(_condition2.Message); + } + + return string.Join($"{Environment.NewLine} ", messages); + } + } protected override string DefaultMessage => string.Empty; diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index dfb11ba41f..08abc6d9db 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -20,7 +20,7 @@ public BaseAssertCondition EqualTo(int expected) return @string.Length == length; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to be equal to {length}") + $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {length}") ); } @@ -31,7 +31,7 @@ public BaseAssertCondition EqualTo(int expected) return @string.Length == length; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to be equal to {length}") + $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {length}") ); public BaseAssertCondition IsNotEmpty => @@ -41,7 +41,7 @@ public BaseAssertCondition EqualTo(int expected) return @string.Length > 0; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to empty" + $"\"{@string}\" was {@string?.Length} characters long but expected to empty" )); @@ -56,7 +56,7 @@ public BaseAssertCondition GreaterThan(int expected) return @string.Length > length; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to be greater than {length}") + $"\"{@string}\" was {@string?.Length} characters long but expected to be greater than {length}") ); } @@ -68,7 +68,7 @@ public BaseAssertCondition GreaterThanOrEqualTo(int expected) return @string.Length >= length; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to be greater than or equal to {length}") + $"\"{@string}\" was {@string?.Length} characters long but expected to be greater than or equal to {length}") ); } @@ -80,7 +80,7 @@ public BaseAssertCondition LessThan(int expected) return @string.Length < length; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to be less than {length}") + $"\"{@string}\" was {@string?.Length} characters long but expected to be less than {length}") ); } @@ -92,7 +92,7 @@ public BaseAssertCondition LessThanOrEqualTo(int expected) return @string.Length <= length; }, (@string, length, _) => - $"{@string} was {@string?.Length} characters long but expected to be less than or equal to {length}") + $"\"{@string}\" was {@string?.Length} characters long but expected to be less than or equal to {length}") ); } } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 93464bc86f..de6510c5c7 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -182,7 +182,7 @@ public async Task String_And_Condition() [Test] public async Task String_And_Condition2() { - await Assert.That("1").Is.EqualTo("2").And.Has.Length().EqualTo(1); + await Assert.That("1").Is.EqualTo("2").And.Has.Length().EqualTo(2); } [Test] From 8b09d23c810ad7240758694325e3236e9643233c Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:08:57 +0000 Subject: [PATCH 095/124] Not rename --- TUnit.Assertions/Is.cs | 2 +- TUnit.TestProject/Tests.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 011570e9d3..1b0b52018f 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -27,5 +27,5 @@ public BaseAssertCondition SameReference(TActual expected) public BaseAssertCondition TypeOf() => Wrap(new TypeOfAssertCondition(AssertionBuilder)); - public IsNot IsNot => new(AssertionBuilder, ConnectorType, OtherAssertCondition); + public IsNot Not => new(AssertionBuilder, ConnectorType, OtherAssertCondition); } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index de6510c5c7..05e7a87911 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -147,7 +147,7 @@ await Assert.That(async () => { await Task.Yield(); new string([]); - }).Is.EqualTo(string.Empty); + }).Is.Not.EqualTo(string.Empty); } [Test] @@ -180,6 +180,7 @@ public async Task String_And_Condition() } [Test] + [TestCategory("Fail")] public async Task String_And_Condition2() { await Assert.That("1").Is.EqualTo("2").And.Has.Length().EqualTo(2); @@ -193,10 +194,11 @@ public async Task Count1() } [Test] + [TestCategory("Fail")] public async Task Count2() { var list = new List { 1, 2, 3 }; - await Assert.That(list).Is.EquivalentTo(new[] { 1, 2, 3 }).And.Has.Count().EqualTo(5); + await Assert.That(list).Is.EquivalentTo(new[] { 1, 2, 3, 4, 5 }).And.Has.Count().EqualTo(5); } public static int One() => 1; From 8d775da161fddc197267b6300df2ec45612086a0 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:33:44 +0000 Subject: [PATCH 096/124] Rework inverting logic --- .../AssertConditions/AssertCondition.cs | 10 ---- .../AssertConditions/BaseAssertCondition.cs | 23 ++++++-- .../DelegateAssertCondition.cs | 2 +- .../Generic/EqualsAssertCondition.cs | 4 +- .../Generic/PropertyEqualsAssertCondition.cs | 2 +- .../PropertyOrMethodEqualsAssertCondition.cs | 2 +- .../InvertedAssertCondition.cs | 2 +- TUnit.Assertions/Extensions/Connector.cs | 2 +- .../Extensions/EnumerableCount.cs | 48 ++++++++--------- TUnit.Assertions/Extensions/IsExtensions.cs | 14 ++--- TUnit.Assertions/Extensions/NotConnector.cs | 15 ++++++ TUnit.Assertions/Extensions/StringLength.cs | 52 +++++++++---------- TUnit.Assertions/Is.cs | 2 +- TUnit.Assertions/IsNot.cs | 11 ++-- TUnit.TestProject/Tests.cs | 20 ++++--- 15 files changed, 122 insertions(+), 87 deletions(-) create mode 100644 TUnit.Assertions/Extensions/NotConnector.cs diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 4aec3b5ecc..264787689b 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -8,14 +8,4 @@ internal AssertCondition(AssertionBuilder assertionBuilder, TExpected? { ExpectedValue = expected; } - - private Func? MessageFactory { get; set; } - - protected internal override string Message => MessageFactory?.Invoke(ActualValue, ExpectedValue, Exception) ?? DefaultMessage; - - public AssertCondition WithMessage(Func messageFactory) - { - MessageFactory = messageFactory; - return this; - } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index 9e8bbf0aa9..c70b37c146 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -30,9 +30,17 @@ private async Task AssertAsync() protected TActual? ActualValue { get; private set; } protected Exception? Exception { get; private set; } + + + protected internal virtual string Message => MessageFactory?.Invoke(ActualValue, Exception) ?? DefaultMessage; + + private Func? MessageFactory { get; set; } - - protected internal abstract string Message { get; } + public BaseAssertCondition WithMessage(Func messageFactory) + { + MessageFactory = messageFactory; + return this; + } protected abstract string DefaultMessage { get; } @@ -48,11 +56,20 @@ internal bool Assert(TActual? actualValue, Exception? exception) { ActualValue = actualValue; Exception = exception; - return Passes(actualValue, exception); + return IsInverted ? !Passes(actualValue, exception) : Passes(actualValue, exception); } protected internal abstract bool Passes(TActual? actualValue, Exception? exception); public And And { get; } public Or Or { get; } + + internal BaseAssertCondition Invert(Func messageFactory) + { + WithMessage(messageFactory); + IsInverted = true; + return this; + } + + protected bool IsInverted { get; set; } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs index 3dbe95b7e4..acd403ad37 100644 --- a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs @@ -7,7 +7,7 @@ public class DelegateAssertCondition : AssertCondition assertionBuilder, TExpected? expected, Func condition, - Func messageFactory) : base(assertionBuilder, expected) + Func messageFactory) : base(assertionBuilder, expected) { _condition = condition; WithMessage(messageFactory); diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index 95d964f3eb..eb9bfc1d00 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,7 +1,7 @@ namespace TUnit.Assertions.AssertConditions.Generic; -public class EqualsAssertCondition(AssertionBuilder assertionBuilder, TExpected expected) - : AssertCondition(assertionBuilder, expected) +public class EqualsAssertCondition(AssertionBuilder assertionBuilder, TActual expected) + : AssertCondition(assertionBuilder, expected) { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index ebca797913..2d169b02dd 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -9,7 +9,7 @@ protected internal override bool Passes(TActual? actualValue, Exception? excepti { var propertyValue = GetPropertyValue(actualValue); - WithMessage((_, _, _) => $"Expected {ExpectedValue} but received {propertyValue}"); + WithMessage((_, _) => $"Expected {ExpectedValue} but received {propertyValue}"); return Equals(propertyValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index 2b39257685..8b3c45613c 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -9,7 +9,7 @@ protected internal override bool Passes(TActual? actualValue, Exception? excepti { var propertyValue = GetPropertyValue(actualValue); - WithMessage((_, _, _) => $"Expected {ExpectedValue} but received {propertyValue}"); + WithMessage((_, _) => $"Expected {ExpectedValue} but received {propertyValue}"); return Equals(propertyValue, ExpectedValue); } diff --git a/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs b/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs index 3620bba6d8..36ca325da3 100644 --- a/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs @@ -5,7 +5,7 @@ public class InvertedAssertCondition : AssertCondition _conditionToInvert; public InvertedAssertCondition(AssertCondition conditionToInvert, - Func messageFactory) : base(conditionToInvert.AssertionBuilder, conditionToInvert.ExpectedValue) + Func messageFactory) : base(conditionToInvert.AssertionBuilder, conditionToInvert.ExpectedValue) { _conditionToInvert = conditionToInvert; WithMessage(messageFactory); diff --git a/TUnit.Assertions/Extensions/Connector.cs b/TUnit.Assertions/Extensions/Connector.cs index b07f821127..73ec3e117d 100644 --- a/TUnit.Assertions/Extensions/Connector.cs +++ b/TUnit.Assertions/Extensions/Connector.cs @@ -14,7 +14,7 @@ public Connector(ConnectorType connectorType, BaseAssertCondition? otherAsser OtherAssertCondition = otherAssertCondition; } - public BaseAssertCondition Wrap(BaseAssertCondition assertCondition) + protected BaseAssertCondition Wrap(BaseAssertCondition assertCondition) { return ConnectorType switch { diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs index fc609dc102..494e33ba6f 100644 --- a/TUnit.Assertions/Extensions/EnumerableCount.cs +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -15,24 +15,24 @@ public EnumerableCount(AssertionBuilder assertionBuilder, ConnectorType conne public BaseAssertCondition EqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); - return GetCount(enumerable) == count; + return GetCount(enumerable) == expected; }, - (enumerable, count, _) => - $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}") + (enumerable, _) => + $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be equal to {expected}") ); } public BaseAssertCondition Empty => - Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, count, _) => + Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); - return GetCount(enumerable) == count; + return GetCount(enumerable) == expected; }, - (enumerable, count, _) => - $"{enumerable} has a count of {GetCount(enumerable)} but expected to be equal to {count}") + (enumerable, _) => + $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be equal to {0}") ); public BaseAssertCondition GreaterThan(int expected) @@ -40,49 +40,49 @@ public BaseAssertCondition GreaterThan(int expected) return Wrap(new DelegateAssertCondition( AssertionBuilder, expected, - (enumerable, count, _) => + (enumerable, _, _) => { ArgumentNullException.ThrowIfNull(enumerable); - return GetCount(enumerable) > count; + return GetCount(enumerable) > expected; }, - (enumerable, count, _) => - $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than {count}") + (enumerable, _) => + $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be greater than {expected}") ); } public BaseAssertCondition GreaterThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); - return GetCount(enumerable) >= count; + return GetCount(enumerable) >= expected; }, - (enumerable, count, _) => - $"{enumerable} has a count of {GetCount(enumerable)} but expected to be greater than or equal to {count}") + (enumerable, _) => + $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be greater than or equal to {expected}") ); } public BaseAssertCondition LessThan(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); - return GetCount(enumerable) < count; + return GetCount(enumerable) < expected; }, - (enumerable, count, _) => - $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than {count}") + (enumerable, _) => + $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be less than {expected}") ); } public BaseAssertCondition LessThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, count, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); - return GetCount(enumerable) <= count; + return GetCount(enumerable) <= expected; }, - (enumerable, count, _) => - $"{enumerable} has a count of {GetCount(enumerable)} but expected to be less than or equal to {count}") + (enumerable, _) => + $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be less than or equal to {expected}") ); } diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs index 539a26457d..b4a7ffd15c 100644 --- a/TUnit.Assertions/Extensions/IsExtensions.cs +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -28,7 +28,7 @@ public static AssertCondition EqualTo(this Is @is, strin public static AssertCondition Zero(this Is @is) where TActual : INumber { - return new EqualsAssertCondition(@is.AssertionBuilder, TActual.Zero); + return new EqualsAssertCondition(@is.AssertionBuilder, TActual.Zero); } public static AssertCondition GreaterThan(this Is @is, T expected) where T : INumber @@ -59,7 +59,7 @@ public static AssertCondition Even(this Is @is) where T : INumber return value % 2 == 0; }, - (value, _, _) => $"{value} was not even"); + (value, _) => $"{value} was not even"); } public static AssertCondition Odd(this Is @is) where T : INumber, IModulusOperators @@ -70,7 +70,7 @@ public static AssertCondition Odd(this Is @is) where T : INumber, return value % 2 != 0; }, - (value, _, _) => $"{value} was not odd"); + (value, _) => $"{value} was not odd"); } public static AssertCondition Negative(this Is @is) where T : INumber @@ -81,7 +81,7 @@ public static AssertCondition Negative(this Is @is) where T : INumbe return value < T.Zero; }, - (value, _, _) => $"{value} was not negative"); + (value, _) => $"{value} was not negative"); } public static AssertCondition Positive(this Is @is) where T : INumber @@ -92,7 +92,7 @@ public static AssertCondition Positive(this Is @is) where T : INumbe return value > T.Zero; }, - (value, _, _) => $"{value} was not positive"); + (value, _) => $"{value} was not positive"); } #endregion @@ -117,12 +117,12 @@ public static AssertCondition Empty(this Is @is) public static AssertCondition True(this Is @is) { - return new EqualsAssertCondition(@is.AssertionBuilder, true); + return new EqualsAssertCondition(@is.AssertionBuilder, true); } public static AssertCondition False(this Is @is) { - return new EqualsAssertCondition(@is.AssertionBuilder, false); + return new EqualsAssertCondition(@is.AssertionBuilder, false); } #endregion diff --git a/TUnit.Assertions/Extensions/NotConnector.cs b/TUnit.Assertions/Extensions/NotConnector.cs new file mode 100644 index 0000000000..00e2a6639d --- /dev/null +++ b/TUnit.Assertions/Extensions/NotConnector.cs @@ -0,0 +1,15 @@ +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; + +public abstract class NotConnector : Connector +{ + protected NotConnector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + { + } + + protected BaseAssertCondition Invert(BaseAssertCondition assertCondition, Func messageFactory) + { + return Wrap(assertCondition.Invert(messageFactory)); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index 08abc6d9db..a9f641a1b8 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -14,33 +14,33 @@ public StringLength(AssertionBuilder assertionBuilder, ConnectorType con public BaseAssertCondition EqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); - return @string.Length == length; + return @string.Length == expected; }, - (@string, length, _) => - $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {length}") + (@string, _) => + $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {expected}") ); } public BaseAssertCondition IsEmpty => - Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (@string, length, _) => + Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); - return @string.Length == length; + return @string.Length == 0; }, - (@string, length, _) => - $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {length}") + (@string, _) => + $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {0}") ); public BaseAssertCondition IsNotEmpty => - Wrap(new DelegateAssertCondition(AssertionBuilder, default, (@string, length, _) => + Wrap(new DelegateAssertCondition(AssertionBuilder, default, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length > 0; }, - (@string, length, _) => + (@string, _) => $"\"{@string}\" was {@string?.Length} characters long but expected to empty" )); @@ -50,49 +50,49 @@ public BaseAssertCondition GreaterThan(int expected) return Wrap(new DelegateAssertCondition( AssertionBuilder, expected, - (@string, length, _) => + (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); - return @string.Length > length; + return @string.Length > expected; }, - (@string, length, _) => - $"\"{@string}\" was {@string?.Length} characters long but expected to be greater than {length}") + (@string, _) => + $"\"{@string}\" was {@string?.Length} characters long but expected to be greater than {expected}") ); } public BaseAssertCondition GreaterThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); - return @string.Length >= length; + return @string.Length >= expected; }, - (@string, length, _) => - $"\"{@string}\" was {@string?.Length} characters long but expected to be greater than or equal to {length}") + (@string, _) => + $"\"{@string}\" was {@string?.Length} characters long but expected to be greater than or equal to {expected}") ); } public BaseAssertCondition LessThan(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); - return @string.Length < length; + return @string.Length < expected; }, - (@string, length, _) => - $"\"{@string}\" was {@string?.Length} characters long but expected to be less than {length}") + (@string, _) => + $"\"{@string}\" was {@string?.Length} characters long but expected to be less than {expected}") ); } public BaseAssertCondition LessThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, length, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); - return @string.Length <= length; + return @string.Length <= expected; }, - (@string, length, _) => - $"\"{@string}\" was {@string?.Length} characters long but expected to be less than or equal to {length}") + (@string, _) => + $"\"{@string}\" was {@string?.Length} characters long but expected to be less than or equal to {expected}") ); } } \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 1b0b52018f..357e94c342 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -15,7 +15,7 @@ public Is(AssertionBuilder assertionBuilder, ConnectorType connectorTyp public BaseAssertCondition EqualTo(TActual expected) { - return Wrap(new EqualsAssertCondition(AssertionBuilder, expected)); + return Wrap(new EqualsAssertCondition(AssertionBuilder, expected)); } public BaseAssertCondition SameReference(TActual expected) diff --git a/TUnit.Assertions/IsNot.cs b/TUnit.Assertions/IsNot.cs index e80e3061c7..3a5e36df63 100644 --- a/TUnit.Assertions/IsNot.cs +++ b/TUnit.Assertions/IsNot.cs @@ -3,7 +3,7 @@ namespace TUnit.Assertions; -public class IsNot : Connector +public class IsNot : NotConnector { protected internal AssertionBuilder AssertionBuilder { get; } @@ -11,8 +11,13 @@ public IsNot(AssertionBuilder assertionBuilder, ConnectorType connector { AssertionBuilder = assertionBuilder; } + + public BaseAssertCondition EqualTo(TActual expected) => Invert(new EqualsAssertCondition(AssertionBuilder, expected), + (actual, exception) => $"Expected {actual} to equal {expected}"); - public BaseAssertCondition Null => Wrap(new NotNullAssertCondition(AssertionBuilder)); + public BaseAssertCondition Null => Invert(new NullAssertCondition(AssertionBuilder), + (actual, exception) => $"Expected {actual} to be null"); - public BaseAssertCondition TypeOf() => Wrap(new NotTypeOfAssertCondition(AssertionBuilder)); + public BaseAssertCondition TypeOf() => Invert(new TypeOfAssertCondition(AssertionBuilder), + (actual, exception) => $"Expected {actual} to not be of type {typeof(TExpected)}"); } \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 05e7a87911..2a07540c18 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -5,11 +5,6 @@ namespace TUnit.TestProject; public class Tests { - // [SetUp] - // public void Setup() - // { - // } - [Test] public async Task Test1() { @@ -18,6 +13,7 @@ public async Task Test1() } [Test] + [TestCategory("Fail")] public async Task Test2() { var value = "2"; @@ -33,6 +29,7 @@ public async Task Test3() } [Test] + [TestCategory("Fail")] public async Task Test4() { await Task.Yield(); @@ -42,6 +39,7 @@ public async Task Test4() [TestWithData("1")] [TestWithData("2")] + [TestCategory("Fail")] public async Task ParameterisedTests1(string value) { await Assert.That(value).Is.EqualTo("1"); @@ -49,6 +47,7 @@ public async Task ParameterisedTests1(string value) [TestWithData("1")] [TestWithData("2")] + [TestCategory("Fail")] public async Task ParameterisedTests2(string value) { await Task.Yield(); @@ -56,6 +55,7 @@ public async Task ParameterisedTests2(string value) } [Test, Skip("Reason1")] + [TestCategory("Skip")] public async Task Skip1() { var value = "1"; @@ -63,6 +63,7 @@ public async Task Skip1() } [Test, Skip("Reason2")] + [TestCategory("Skip")] public async Task Skip2() { await Task.Yield(); @@ -84,12 +85,14 @@ public async Task TestDataSource2(int value) } [TestDataSource(nameof(Two))] + [TestCategory("Fail")] public async Task TestDataSource3(int value) { await Assert.That(value).Is.EqualTo(1); } [TestDataSource(nameof(Two))] + [TestCategory("Fail")] public async Task TestDataSource4(int value) { await Task.Yield(); @@ -110,12 +113,14 @@ public async Task TestDataSource6(int value) } [TestDataSource(nameof(TestDataSources), nameof(Two))] + [TestCategory("Fail")] public async Task TestDataSource7(int value) { await Assert.That(value).Is.EqualTo(1); } [TestDataSource(nameof(TestDataSources), nameof(Two))] + [TestCategory("Fail")] public async Task TestDataSource8(int value) { await Task.Yield(); @@ -129,18 +134,21 @@ public async Task TestContext1() } [Test] + [TestCategory("Fail")] public async Task TestContext2() { await Assert.That(TestContext.Current.TestName).Is.EqualTo(nameof(TestContext1)); } [Test] + [TestCategory("Fail")] public async Task Throws1() { - await Assert.That(() => new string([])).Is.EqualTo(string.Empty); + await Assert.That(() => new string([])).Throws.Exception; } [Test] + [TestCategory("Fail")] public async Task Throws2() { await Assert.That(async () => From db84fbd537d3e5647f3b0c1ad779a61da0e82ef5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:38:21 +0000 Subject: [PATCH 097/124] Remove clutter --- .../AssertConditions/Connectors/AndHas.cs | 16 -------------- .../AssertConditions/Connectors/OrHas.cs | 11 ---------- .../Generic/NotTypeOfAssertCondition.cs | 11 ---------- .../InvertedAssertCondition.cs | 20 ------------------ .../NotNullAssertCondition.cs | 15 ------------- .../Numbers/IsEvenAssertCondition.cs | 20 ------------------ .../Numbers/IsOddAssertCondition.cs | 20 ------------------ .../Numbers/LessThanAssertCondition.cs | 21 ------------------- .../Numbers/ZeroAssertCondition.cs | 18 ---------------- .../String/StringCompareAssertionSettings.cs | 7 ------- 10 files changed, 159 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/Connectors/AndHas.cs delete mode 100644 TUnit.Assertions/AssertConditions/Connectors/OrHas.cs delete mode 100644 TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs diff --git a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs b/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs deleted file mode 100644 index 3f1ac75aba..0000000000 --- a/TUnit.Assertions/AssertConditions/Connectors/AndHas.cs +++ /dev/null @@ -1,16 +0,0 @@ -using TUnit.Assertions.AssertConditions.Generic; - -namespace TUnit.Assertions.AssertConditions.Connectors; - -public class AndHas -{ - internal BaseAssertCondition OtherAssertCondition { get; } - - public AndHas(BaseAssertCondition otherAssertCondition) - { - OtherAssertCondition = otherAssertCondition; - } - - public Property Property(string name) => new(name, ConnectorType.And, OtherAssertCondition); - public Property Property(string name) => new(name, ConnectorType.And, OtherAssertCondition); -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs b/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs deleted file mode 100644 index e7185c8243..0000000000 --- a/TUnit.Assertions/AssertConditions/Connectors/OrHas.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.Connectors; - -public class OrHas -{ - internal BaseAssertCondition OtherAssertCondition { get; } - - public OrHas(BaseAssertCondition otherAssertCondition) - { - OtherAssertCondition = otherAssertCondition; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs deleted file mode 100644 index 6dca662942..0000000000 --- a/TUnit.Assertions/AssertConditions/Generic/NotTypeOfAssertCondition.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.Generic; - -public class NotTypeOfAssertCondition(AssertionBuilder assertionBuilder) : TypeOfAssertCondition(assertionBuilder) -{ - protected override string DefaultMessage => $"{ActualValue} is {typeof(TExpected).Name}"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - return !base.Passes(actualValue, exception); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs b/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs deleted file mode 100644 index 36ca325da3..0000000000 --- a/TUnit.Assertions/AssertConditions/InvertedAssertCondition.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace TUnit.Assertions.AssertConditions; - -public class InvertedAssertCondition : AssertCondition -{ - private readonly AssertCondition _conditionToInvert; - - public InvertedAssertCondition(AssertCondition conditionToInvert, - Func messageFactory) : base(conditionToInvert.AssertionBuilder, conditionToInvert.ExpectedValue) - { - _conditionToInvert = conditionToInvert; - WithMessage(messageFactory); - } - - protected override string DefaultMessage => string.Empty; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - return !_conditionToInvert.Passes(actualValue, exception); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs deleted file mode 100644 index d066236717..0000000000 --- a/TUnit.Assertions/AssertConditions/NotNullAssertCondition.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace TUnit.Assertions.AssertConditions; - -public class NotNullAssertCondition : NullAssertCondition -{ - public NotNullAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder) - { - } - - protected override string DefaultMessage => "Value is null"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - return !base.Passes(actualValue, exception); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs deleted file mode 100644 index 7591d56ec2..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/IsEvenAssertCondition.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class IsEvenAssertCondition : AssertCondition - where TActual : INumber, IModulusOperators -{ - public IsEvenAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not even"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue % 2 == 0; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs deleted file mode 100644 index a603a6d822..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/IsOddAssertCondition.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class IsOddAssertCondition : AssertCondition - where TActual : INumber, IModulusOperators -{ - public IsOddAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not odd"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue % 2 != 0; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs deleted file mode 100644 index 6f19cef647..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanAssertCondition.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class LessThanAssertCondition : AssertCondition - where TExpected : INumber - where TActual : INumber, TExpected -{ - public LessThanAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not less than {ExpectedValue}"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue < ExpectedValue!; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs deleted file mode 100644 index d6bcc6324e..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/ZeroAssertCondition.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class ZeroAssertCondition : AssertCondition - where TActual : INumber, IEqualityOperators -{ - public ZeroAssertCondition(AssertionBuilder assertionBuilder, TActual? expected) : base(assertionBuilder, expected) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not equal to 0"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - return actualValue == TActual.Zero; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs b/TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs deleted file mode 100644 index 398c05b6f1..0000000000 --- a/TUnit.Assertions/AssertConditions/String/StringCompareAssertionSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TUnit.Assertions.AssertConditions.String; - -public record StringCompareAssertionSettings -{ - public StringComparison StringComparison { get; init; } = StringComparison.Ordinal; - public bool Trim { get; init; } = false; -}; \ No newline at end of file From ca8d9ee60024168b06c1668606d0b13a966c00a6 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:41:23 +0000 Subject: [PATCH 098/124] Expose Does and Throws on And & Or objects --- TUnit.Assertions/AssertConditions/Operators/And.cs | 4 ++-- TUnit.Assertions/AssertConditions/Operators/Or.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TUnit.Assertions/AssertConditions/Operators/And.cs b/TUnit.Assertions/AssertConditions/Operators/And.cs index 0c44898a3c..2c5f3239dd 100644 --- a/TUnit.Assertions/AssertConditions/Operators/And.cs +++ b/TUnit.Assertions/AssertConditions/Operators/And.cs @@ -1,5 +1,3 @@ -using TUnit.Assertions.AssertConditions.Connectors; - namespace TUnit.Assertions.AssertConditions.Operators; public class And @@ -13,4 +11,6 @@ public And(BaseAssertCondition otherAssertCondition) public Is Is => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); public Has Has => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); + public Does Does => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); + public Throws Throws => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/Or.cs b/TUnit.Assertions/AssertConditions/Operators/Or.cs index 7ac549210a..ec1bb0b69b 100644 --- a/TUnit.Assertions/AssertConditions/Operators/Or.cs +++ b/TUnit.Assertions/AssertConditions/Operators/Or.cs @@ -11,4 +11,6 @@ public Or(BaseAssertCondition otherAssertCondition) public Is Is => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); public Has Has => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); + public Does Does => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); + public Throws Throws => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); } \ No newline at end of file From 89472b08e6d2b53b1ffc26a858f0c59095463ba2 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 17:54:22 +0000 Subject: [PATCH 099/124] Move throws to DelegateAssertionBuilder --- TUnit.Assertions/Assert.cs | 8 ++++---- TUnit.Assertions/AssertionBuilder.cs | 2 -- TUnit.Assertions/AsyncDelegateAssertionBuilder.cs | 14 ++++++++++---- TUnit.Assertions/DelegateAssertionBuilder.cs | 14 ++++++++++---- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 6c47c47c9b..e86612d48a 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -7,22 +7,22 @@ public static AssertionBuilder That(TActual value) return new ValueAssertionBuilder(value); } - public static AssertionBuilder That(Action value) + public static DelegateAssertionBuilder That(Action value) { return new DelegateAssertionBuilder(value); } - public static AssertionBuilder That(Func value) + public static DelegateAssertionBuilder That(Func value) { return new DelegateAssertionBuilder(value); } - public static AssertionBuilder That(Func value) + public static AsyncDelegateAssertionBuilder That(Func value) { return new AsyncDelegateAssertionBuilder(value); } - public static AssertionBuilder That(Func> value) + public static AsyncDelegateAssertionBuilder That(Func> value) { return new AsyncDelegateAssertionBuilder(value!); } diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs index 08a37985d3..2377f3494c 100644 --- a/TUnit.Assertions/AssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -2,13 +2,11 @@ namespace TUnit.Assertions; - public abstract class AssertionBuilder { protected internal abstract Task> GetAssertionData(); public Does Does => new(this, ConnectorType.None, null); - public Throws Throws => new(this, ConnectorType.None, null); public Is Is => new(this, ConnectorType.None, null); public Has Has => new(this, ConnectorType.None, null); } \ No newline at end of file diff --git a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs index 2dfee5eef3..c68630b273 100644 --- a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs @@ -1,8 +1,12 @@ -namespace TUnit.Assertions; +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; public class AsyncDelegateAssertionBuilder : AssertionBuilder { private readonly Func> _function; + + public Throws Throws => new(this, ConnectorType.None, null); internal AsyncDelegateAssertionBuilder(Func> function) { @@ -17,19 +21,21 @@ protected internal override async Task> GetAssertionData() } } -public class AsyncDelegateAssertionBuilder : AssertionBuilder +public class AsyncDelegateAssertionBuilder : AssertionBuilder { private readonly Func _function; + + public Throws Throws => new(this, ConnectorType.None, null); internal AsyncDelegateAssertionBuilder(Func function) { _function = function; } - protected internal override async Task> GetAssertionData() + protected internal override async Task> GetAssertionData() { var exception = await _function.InvokeAndGetExceptionAsync(); - return new AssertionData(null, exception); + return new AssertionData(null, exception); } } \ No newline at end of file diff --git a/TUnit.Assertions/DelegateAssertionBuilder.cs b/TUnit.Assertions/DelegateAssertionBuilder.cs index fd829c29a2..60ebc32aec 100644 --- a/TUnit.Assertions/DelegateAssertionBuilder.cs +++ b/TUnit.Assertions/DelegateAssertionBuilder.cs @@ -1,8 +1,12 @@ -namespace TUnit.Assertions; +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; public class DelegateAssertionBuilder : AssertionBuilder { private readonly Func _function; + + public Throws Throws => new(this, ConnectorType.None, null); internal DelegateAssertionBuilder(Func function) { @@ -17,19 +21,21 @@ protected internal override Task> GetAssertionData() } } -public class DelegateAssertionBuilder : AssertionBuilder +public class DelegateAssertionBuilder : AssertionBuilder { private readonly Action _action; + + public Throws Throws => new(this, ConnectorType.None, null); internal DelegateAssertionBuilder(Action action) { _action = action; } - protected internal override Task> GetAssertionData() + protected internal override Task> GetAssertionData() { var exception = _action.InvokeAndGetException(); - return Task.FromResult(new AssertionData(null, exception)); + return Task.FromResult(new AssertionData(null, exception)); } } \ No newline at end of file From cb96538cbfe51e3a9c59f25a1f40894d5b8fa3e3 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sat, 3 Feb 2024 17:56:37 +0000 Subject: [PATCH 100/124] Refactor delegate builders --- TUnit.Assertions/Assert.cs | 2 +- TUnit.Assertions/AssertionBuilder.cs | 4 ---- TUnit.Assertions/AsyncDelegateAssertionBuilder.cs | 3 +++ TUnit.Assertions/DelegateAssertionBuilder.cs | 3 +++ TUnit.Assertions/ValueAssertionBuilder.cs | 8 +++++++- TUnit.TestProject/Tests.cs | 5 ++--- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index e86612d48a..1a0cddb760 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -2,7 +2,7 @@ public static class Assert { - public static AssertionBuilder That(TActual value) + public static ValueAssertionBuilder That(TActual value) { return new ValueAssertionBuilder(value); } diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs index 2377f3494c..d7cd791065 100644 --- a/TUnit.Assertions/AssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -5,8 +5,4 @@ namespace TUnit.Assertions; public abstract class AssertionBuilder { protected internal abstract Task> GetAssertionData(); - - public Does Does => new(this, ConnectorType.None, null); - public Is Is => new(this, ConnectorType.None, null); - public Has Has => new(this, ConnectorType.None, null); } \ No newline at end of file diff --git a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs index c68630b273..440512af96 100644 --- a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs @@ -6,6 +6,9 @@ public class AsyncDelegateAssertionBuilder : AssertionBuilder { private readonly Func> _function; + public Does Does => new(this, ConnectorType.None, null); + public Is Is => new(this, ConnectorType.None, null); + public Has Has => new(this, ConnectorType.None, null); public Throws Throws => new(this, ConnectorType.None, null); internal AsyncDelegateAssertionBuilder(Func> function) diff --git a/TUnit.Assertions/DelegateAssertionBuilder.cs b/TUnit.Assertions/DelegateAssertionBuilder.cs index 60ebc32aec..53d05f59d6 100644 --- a/TUnit.Assertions/DelegateAssertionBuilder.cs +++ b/TUnit.Assertions/DelegateAssertionBuilder.cs @@ -6,6 +6,9 @@ public class DelegateAssertionBuilder : AssertionBuilder { private readonly Func _function; + public Does Does => new(this, ConnectorType.None, null); + public Is Is => new(this, ConnectorType.None, null); + public Has Has => new(this, ConnectorType.None, null); public Throws Throws => new(this, ConnectorType.None, null); internal DelegateAssertionBuilder(Func function) diff --git a/TUnit.Assertions/ValueAssertionBuilder.cs b/TUnit.Assertions/ValueAssertionBuilder.cs index 758ee74ab4..a738672c6e 100644 --- a/TUnit.Assertions/ValueAssertionBuilder.cs +++ b/TUnit.Assertions/ValueAssertionBuilder.cs @@ -1,9 +1,15 @@ -namespace TUnit.Assertions; +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions; public class ValueAssertionBuilder : AssertionBuilder { private readonly T? _value; + + public Does Does => new(this, ConnectorType.None, null); + public Is Is => new(this, ConnectorType.None, null); + public Has Has => new(this, ConnectorType.None, null); internal ValueAssertionBuilder(T? value) { diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 2a07540c18..bad6fff679 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -153,9 +153,8 @@ public async Task Throws2() { await Assert.That(async () => { - await Task.Yield(); - new string([]); - }).Is.Not.EqualTo(string.Empty); + await Task.Yield();; + }).Throws.Exception; } [Test] From 70664ec980e983061492a9dacbf2ce913a016679 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 12:56:01 +0000 Subject: [PATCH 101/124] Generic And Or operators --- TUnit.Assertions/Assert.cs | 8 +- .../AssertConditions/AssertCondition.cs | 8 +- .../AssertConditions/BaseAssertCondition.cs | 16 +- .../EnumerableContainsAssertCondition.cs | 12 +- .../EnumerableCountEqualToAssertCondition.cs | 13 +- .../EnumerableEquivalentToAssertCondition.cs | 12 +- .../Collections/PropertyOrMethodAccessor.cs | 2 +- .../Connectors/AssertConditionAnd.cs | 14 +- .../Connectors/AssertConditionOr.cs | 14 +- .../DelegateAssertCondition.cs | 8 +- .../Generic/EqualsAssertCondition.cs | 10 +- .../AssertConditions/Generic/Property.cs | 23 +-- .../Generic/PropertyEqualsAssertCondition.cs | 8 +- .../Generic/PropertyOrMethod.cs | 23 +-- .../PropertyOrMethodEqualsAssertCondition.cs | 8 +- .../Generic/SameReferenceAssertCondition.cs | 8 +- .../Generic/TypeOfAssertCondition.cs | 8 +- .../AssertConditions/NullAssertCondition.cs | 6 +- .../Numbers/GreaterThanAssertCondition.cs | 21 --- .../GreaterThanOrEqualToAssertCondition.cs | 21 --- .../LessThanOrEqualToAssertCondition.cs | 21 --- .../AssertConditions/Operators/And.cs | 15 +- .../Operators/AssertionType.cs | 8 + .../AssertConditions/Operators/DelegateAnd.cs | 18 +++ .../AssertConditions/Operators/DelegateOr.cs | 18 +++ .../AssertConditions/Operators/IAnd.cs | 8 + .../Operators/IDelegateAssertions.cs | 8 + .../AssertConditions/Operators/IOr.cs | 8 + .../Operators/IValueAssertions.cs | 10 ++ .../AssertConditions/Operators/Or.cs | 15 +- .../AssertConditions/Operators/ValueAnd.cs | 19 +++ .../Operators/ValueDelegateAnd.cs | 22 +++ .../Operators/ValueDelegateOr.cs | 23 +++ .../AssertConditions/Operators/ValueOr.cs | 21 +++ .../String/StringContainsAssertCondition.cs | 8 +- .../String/StringEqualsAssertCondition.cs | 8 +- .../Throws/ThrowsAnythingAssertCondition.cs | 6 +- .../ThrowsExactTypeOfAssertCondition.cs | 6 +- .../Throws/ThrowsNothingAssertCondition.cs | 8 +- .../Throws/ThrowsSubClassOfAssertCondition.cs | 6 +- ...owsWithMessageContainingAssertCondition.cs | 6 +- ...ThrowsWithMessageEqualToAssertCondition.cs | 6 +- .../AssertConditions/Throws/WithMessage.cs | 22 +-- TUnit.Assertions/AssertionBuilder.cs | 8 +- .../AsyncDelegateAssertionBuilder.cs | 19 +-- TUnit.Assertions/DelegateAssertionBuilder.cs | 19 +-- TUnit.Assertions/Does.cs | 9 +- TUnit.Assertions/Extensions/Connector.cs | 15 +- TUnit.Assertions/Extensions/DoesExtensions.cs | 19 ++- .../Extensions/EnumerableCount.cs | 38 ++--- TUnit.Assertions/Extensions/HasExtensions.cs | 14 +- TUnit.Assertions/Extensions/IsExtensions.cs | 137 +++++++++++++----- TUnit.Assertions/Extensions/NotConnector.cs | 9 +- TUnit.Assertions/Extensions/StringLength.cs | 35 +++-- TUnit.Assertions/Has.cs | 13 +- TUnit.Assertions/Is.cs | 21 +-- TUnit.Assertions/IsNot.cs | 13 +- TUnit.Assertions/Throws.cs | 21 +-- TUnit.Assertions/ValueAssertionBuilder.cs | 17 ++- 59 files changed, 618 insertions(+), 322 deletions(-) delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs delete mode 100644 TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/AssertionType.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/IAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/IDelegateAssertions.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/IOr.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/IValueAssertions.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/ValueAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/ValueDelegateAnd.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/ValueDelegateOr.cs create mode 100644 TUnit.Assertions/AssertConditions/Operators/ValueOr.cs diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 1a0cddb760..5a30db7d41 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -12,9 +12,9 @@ public static DelegateAssertionBuilder That(Action value) return new DelegateAssertionBuilder(value); } - public static DelegateAssertionBuilder That(Func value) + public static DelegateAssertionBuilder That(Func value) { - return new DelegateAssertionBuilder(value); + return new DelegateAssertionBuilder(value); } public static AsyncDelegateAssertionBuilder That(Func value) @@ -22,8 +22,8 @@ public static AsyncDelegateAssertionBuilder That(Func value) return new AsyncDelegateAssertionBuilder(value); } - public static AsyncDelegateAssertionBuilder That(Func> value) + public static AsyncDelegateAssertionBuilder That(Func> value) { - return new AsyncDelegateAssertionBuilder(value!); + return new AsyncDelegateAssertionBuilder(value!); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/AssertCondition.cs b/TUnit.Assertions/AssertConditions/AssertCondition.cs index 264787689b..306918ce6b 100644 --- a/TUnit.Assertions/AssertConditions/AssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/AssertCondition.cs @@ -1,6 +1,10 @@ -namespace TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; -public abstract class AssertCondition : BaseAssertCondition +namespace TUnit.Assertions.AssertConditions; + +public abstract class AssertCondition : BaseAssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { internal TExpected? ExpectedValue { get; } diff --git a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs index c70b37c146..91db5f4569 100644 --- a/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/BaseAssertCondition.cs @@ -4,7 +4,9 @@ namespace TUnit.Assertions.AssertConditions; -public abstract class BaseAssertCondition +public abstract class BaseAssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { protected internal AssertionBuilder AssertionBuilder { get; } @@ -12,8 +14,8 @@ internal BaseAssertCondition(AssertionBuilder assertionBuilder) { AssertionBuilder = assertionBuilder; - And = new And(this); - Or = new Or(this); + And = TAnd.Create(this); + Or = TOr.Create(this); } public TaskAwaiter GetAwaiter() @@ -36,7 +38,7 @@ private async Task AssertAsync() private Func? MessageFactory { get; set; } - public BaseAssertCondition WithMessage(Func messageFactory) + public BaseAssertCondition WithMessage(Func messageFactory) { MessageFactory = messageFactory; return this; @@ -61,10 +63,10 @@ internal bool Assert(TActual? actualValue, Exception? exception) protected internal abstract bool Passes(TActual? actualValue, Exception? exception); - public And And { get; } - public Or Or { get; } + public TAnd And { get; } + public TOr Or { get; } - internal BaseAssertCondition Invert(Func messageFactory) + internal BaseAssertCondition Invert(Func messageFactory) { WithMessage(messageFactory); IsInverted = true; diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs index c2fbb01a8e..e49a6d28df 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableContainsAssertCondition.cs @@ -1,15 +1,19 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Collections; -public class EnumerableContainsAssertCondition : AssertCondition - where T : IEnumerable +public class EnumerableContainsAssertCondition : AssertCondition + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - public EnumerableContainsAssertCondition(AssertionBuilder assertionBuilder, TInner expected) : base(assertionBuilder, expected) + public EnumerableContainsAssertCondition(AssertionBuilder assertionBuilder, TInner expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"{ExpectedValue} was not found in the collection"; - protected internal override bool Passes(T? actualValue, Exception? exception) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { if (actualValue is null) { diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs index 4aeff6a991..834de5d168 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableCountEqualToAssertCondition.cs @@ -1,22 +1,25 @@ using System.Collections; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions.AssertConditions.Collections; -public class EnumerableCountEqualToAssertCondition : AssertCondition - where T : IEnumerable +public class EnumerableCountEqualToAssertCondition : AssertCondition + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - public EnumerableCountEqualToAssertCondition(AssertionBuilder assertionBuilder, int expected) : base(assertionBuilder, expected) + public EnumerableCountEqualToAssertCondition(AssertionBuilder assertionBuilder, int expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => $"Length is {GetCount(ActualValue)} instead of {ExpectedValue}"; - protected internal override bool Passes(T? actualValue, Exception? exception) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { return GetCount(actualValue) == ExpectedValue; } - private int GetCount(T? actualValue) + private int GetCount(TActual? actualValue) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs index ddb5ac231a..902d1ee30f 100644 --- a/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Collections/EnumerableEquivalentToAssertCondition.cs @@ -1,15 +1,19 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Collections; -public class EnumerableEquivalentToAssertCondition : AssertCondition> - where T : IEnumerable +public class EnumerableEquivalentToAssertCondition : AssertCondition, TAnd, TOr> + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - public EnumerableEquivalentToAssertCondition(AssertionBuilder assertionBuilder, IEnumerable expected) : base(assertionBuilder, expected) + public EnumerableEquivalentToAssertCondition(AssertionBuilder assertionBuilder, IEnumerable expected) : base(assertionBuilder, expected) { } protected override string DefaultMessage => "The two Enumerables were not equivalent"; - protected internal override bool Passes(T? actualValue, Exception? exception) + protected internal override bool Passes(TActual? actualValue, Exception? exception) { if (actualValue is null && ExpectedValue is null) { diff --git a/TUnit.Assertions/AssertConditions/Collections/PropertyOrMethodAccessor.cs b/TUnit.Assertions/AssertConditions/Collections/PropertyOrMethodAccessor.cs index bb38ff5bea..219cbc3afa 100644 --- a/TUnit.Assertions/AssertConditions/Collections/PropertyOrMethodAccessor.cs +++ b/TUnit.Assertions/AssertConditions/Collections/PropertyOrMethodAccessor.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions.AssertConditions.Collections; -public class PropertyOrMethodAccessor +public class PropertyOrMethodAccessor { public PropertyOrMethodAccessor() { diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs index ba69545a86..787e1a12f8 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionAnd.cs @@ -1,11 +1,15 @@ -namespace TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Operators; -public sealed class AssertConditionAnd : BaseAssertCondition +namespace TUnit.Assertions.AssertConditions.Connectors; + +public sealed class AssertConditionAnd : BaseAssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { - private readonly BaseAssertCondition _condition1; - private readonly BaseAssertCondition _condition2; + private readonly BaseAssertCondition _condition1; + private readonly BaseAssertCondition _condition2; - public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) + public AssertConditionAnd(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) { ArgumentNullException.ThrowIfNull(condition1); ArgumentNullException.ThrowIfNull(condition2); diff --git a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs index 7524d64ee2..ed39d5a1be 100644 --- a/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs +++ b/TUnit.Assertions/AssertConditions/Connectors/AssertConditionOr.cs @@ -1,11 +1,15 @@ -namespace TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Operators; -public sealed class AssertConditionOr : BaseAssertCondition +namespace TUnit.Assertions.AssertConditions.Connectors; + +public sealed class AssertConditionOr : BaseAssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { - private readonly BaseAssertCondition _condition1; - private readonly BaseAssertCondition _condition2; + private readonly BaseAssertCondition _condition1; + private readonly BaseAssertCondition _condition2; - public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) + public AssertConditionOr(BaseAssertCondition condition1, BaseAssertCondition condition2) : base(condition1.AssertionBuilder) { ArgumentNullException.ThrowIfNull(condition1); ArgumentNullException.ThrowIfNull(condition2); diff --git a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs index acd403ad37..f6367842f1 100644 --- a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs @@ -1,6 +1,10 @@ -namespace TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; -public class DelegateAssertCondition : AssertCondition +namespace TUnit.Assertions.AssertConditions; + +public class DelegateAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly Func _condition; diff --git a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs index eb9bfc1d00..b4417ad2c6 100644 --- a/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/EqualsAssertCondition.cs @@ -1,7 +1,11 @@ -namespace TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Operators; -public class EqualsAssertCondition(AssertionBuilder assertionBuilder, TActual expected) - : AssertCondition(assertionBuilder, expected) +namespace TUnit.Assertions.AssertConditions.Generic; + +public class EqualsAssertCondition(AssertionBuilder assertionBuilder, TActual expected) + : AssertCondition(assertionBuilder, expected) + where TAnd : And, IAnd + where TOr : Or, IOr { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; diff --git a/TUnit.Assertions/AssertConditions/Generic/Property.cs b/TUnit.Assertions/AssertConditions/Generic/Property.cs index 8cc786a0b6..26ede218e2 100644 --- a/TUnit.Assertions/AssertConditions/Generic/Property.cs +++ b/TUnit.Assertions/AssertConditions/Generic/Property.cs @@ -1,10 +1,13 @@ using TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions.AssertConditions.Generic; -public class Property : Property +public class Property : Property + where TAnd : And, IAnd + where TOr : Or, IOr { - public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) : base(name, connectorType, otherAssertConditions) { } @@ -14,30 +17,32 @@ public Property(AssertionBuilder assertionBuilder, string name) : base( } } -public class Property(AssertionBuilder assertionBuilder, string name) +public class Property(AssertionBuilder assertionBuilder, string name) + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly ConnectorType? _connectorType; - private readonly BaseAssertCondition? _otherAssertConditions; + private readonly BaseAssertCondition? _otherAssertConditions; - public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + public Property(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) : this(otherAssertConditions.AssertionBuilder, name) { _connectorType = connectorType; _otherAssertConditions = otherAssertConditions; } - public BaseAssertCondition EqualTo(TExpected expected) + public BaseAssertCondition EqualTo(TExpected expected) { - var assertCondition = new PropertyEqualsAssertCondition(assertionBuilder, name, expected); + var assertCondition = new PropertyEqualsAssertCondition(assertionBuilder, name, expected); if (_connectorType is ConnectorType.And) { - return new AssertConditionAnd(_otherAssertConditions!, assertCondition); + return new AssertConditionAnd(_otherAssertConditions!, assertCondition); } if (_connectorType is ConnectorType.Or) { - return new AssertConditionOr(_otherAssertConditions!, assertCondition); + return new AssertConditionOr(_otherAssertConditions!, assertCondition); } return assertCondition; diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs index 2d169b02dd..adfc149da4 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyEqualsAssertCondition.cs @@ -1,7 +1,11 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Generic; -public class PropertyEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) - : AssertCondition(assertionBuilder, expected) +public class PropertyEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) + : AssertCondition(assertionBuilder, expected) + where TAnd : And, IAnd + where TOr : Or, IOr { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs index f125ce59b4..ee29fb216e 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethod.cs @@ -1,10 +1,13 @@ using TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions.AssertConditions.Generic; -public class PropertyOrMethod : Property +public class PropertyOrMethod : Property + where TAnd : And, IAnd + where TOr : Or, IOr { - public PropertyOrMethod(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + public PropertyOrMethod(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) : base(name, connectorType, otherAssertConditions) { } @@ -14,30 +17,32 @@ public PropertyOrMethod(AssertionBuilder assertionBuilder, string name) } } -public class PropertyOrMethod(AssertionBuilder assertionBuilder, string name) +public class PropertyOrMethod(AssertionBuilder assertionBuilder, string name) + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly ConnectorType? _connectorType; - private readonly BaseAssertCondition? _otherAssertConditions; + private readonly BaseAssertCondition? _otherAssertConditions; - public PropertyOrMethod(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) + public PropertyOrMethod(string name, ConnectorType connectorType, BaseAssertCondition otherAssertConditions) : this(otherAssertConditions.AssertionBuilder, name) { _connectorType = connectorType; _otherAssertConditions = otherAssertConditions; } - public BaseAssertCondition EqualTo(TExpected expected) + public BaseAssertCondition EqualTo(TExpected expected) { - var assertCondition = new PropertyEqualsAssertCondition(assertionBuilder, name, expected); + var assertCondition = new PropertyEqualsAssertCondition(assertionBuilder, name, expected); if (_connectorType is ConnectorType.And) { - return new AssertConditionAnd(_otherAssertConditions!, assertCondition); + return new AssertConditionAnd(_otherAssertConditions!, assertCondition); } if (_connectorType is ConnectorType.Or) { - return new AssertConditionOr(_otherAssertConditions!, assertCondition); + return new AssertConditionOr(_otherAssertConditions!, assertCondition); } return assertCondition; diff --git a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs index 8b3c45613c..5432bd2f5d 100644 --- a/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/PropertyOrMethodEqualsAssertCondition.cs @@ -1,7 +1,11 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Generic; -public class PropertyOrMethodEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) - : AssertCondition(assertionBuilder, expected) +public class PropertyOrMethodEqualsAssertCondition(AssertionBuilder assertionBuilder, string propertyName, TExpected expected) + : AssertCondition(assertionBuilder, expected) + where TAnd : And, IAnd + where TOr : Or, IOr { protected override string DefaultMessage => $"Expected {ExpectedValue} but received {ActualValue}"; diff --git a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs index 8d464617ed..25f537ef8c 100644 --- a/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/SameReferenceAssertCondition.cs @@ -1,6 +1,10 @@ -namespace TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Operators; -public class SameReferenceAssertCondition : AssertCondition +namespace TUnit.Assertions.AssertConditions.Generic; + +public class SameReferenceAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { public SameReferenceAssertCondition(AssertionBuilder assertionBuilder, TExpected expected) : base(assertionBuilder, expected) diff --git a/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs index 207e919e64..9f6c668547 100644 --- a/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Generic/TypeOfAssertCondition.cs @@ -1,7 +1,11 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Generic; -public class TypeOfAssertCondition(AssertionBuilder assertionBuilder) - : AssertCondition(assertionBuilder, default) +public class TypeOfAssertCondition(AssertionBuilder assertionBuilder) + : AssertCondition(assertionBuilder, default) + where TAnd : And, IAnd + where TOr : Or, IOr { protected override string DefaultMessage => $"{ActualValue} is {ActualValue?.GetType().Name ?? "null"} instead of {typeof(TExpected).Name}"; diff --git a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs index 6492361e2e..2dbc472563 100644 --- a/TUnit.Assertions/AssertConditions/NullAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/NullAssertCondition.cs @@ -1,6 +1,10 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions; -public class NullAssertCondition : AssertCondition +public class NullAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { public NullAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs deleted file mode 100644 index f2a68e499f..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanAssertCondition.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class GreaterThanAssertCondition : AssertCondition - where TExpected : INumber - where TActual : INumber, TExpected -{ - public GreaterThanAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not greater than {ExpectedValue}"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue > ExpectedValue!; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs deleted file mode 100644 index b9cf2fbaa7..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/GreaterThanOrEqualToAssertCondition.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class GreaterThanOrEqualToAssertCondition : AssertCondition - where TExpected : INumber - where TActual : INumber, TExpected -{ - public GreaterThanOrEqualToAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not greater than or equal to {ExpectedValue}"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue >= ExpectedValue!; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs deleted file mode 100644 index a93c0222c3..0000000000 --- a/TUnit.Assertions/AssertConditions/Numbers/LessThanOrEqualToAssertCondition.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Numerics; - -namespace TUnit.Assertions.AssertConditions.Numbers; - -public class LessThanOrEqualToAssertCondition : AssertCondition - where TExpected : INumber - where TActual : INumber, TExpected -{ - public LessThanOrEqualToAssertCondition(AssertionBuilder assertionBuilder, TExpected? expected) : base(assertionBuilder, expected) - { - } - - protected override string DefaultMessage => $"{ActualValue} is not less than or equal to {ExpectedValue}"; - - protected internal override bool Passes(TActual? actualValue, Exception? exception) - { - ArgumentNullException.ThrowIfNull(actualValue); - - return actualValue <= ExpectedValue!; - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/And.cs b/TUnit.Assertions/AssertConditions/Operators/And.cs index 2c5f3239dd..b31fb3d0d7 100644 --- a/TUnit.Assertions/AssertConditions/Operators/And.cs +++ b/TUnit.Assertions/AssertConditions/Operators/And.cs @@ -1,16 +1,13 @@ namespace TUnit.Assertions.AssertConditions.Operators; -public class And +public abstract class And + where TAnd : And, IAnd + where TOr : Or, IOr { - private readonly BaseAssertCondition _otherAssertCondition; + protected readonly BaseAssertCondition OtherAssertCondition; - public And(BaseAssertCondition otherAssertCondition) + public And(BaseAssertCondition otherAssertCondition) { - _otherAssertCondition = otherAssertCondition; + OtherAssertCondition = otherAssertCondition; } - - public Is Is => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); - public Has Has => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); - public Does Does => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); - public Throws Throws => new(_otherAssertCondition.AssertionBuilder, ConnectorType.And, _otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/AssertionType.cs b/TUnit.Assertions/AssertConditions/Operators/AssertionType.cs new file mode 100644 index 0000000000..6aab074378 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/AssertionType.cs @@ -0,0 +1,8 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +[Flags] +public enum AssertionType +{ + Value = 1, + Delegate = 2 +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs b/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs new file mode 100644 index 0000000000..a029569ad3 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs @@ -0,0 +1,18 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public class DelegateAnd + : And, DelegateOr>, + IDelegateAssertions, DelegateOr>, + IAnd, TActual, DelegateAnd, DelegateOr> +{ + public DelegateAnd(BaseAssertCondition, DelegateOr> otherAssertCondition) : base(otherAssertCondition) + { + } + + public Throws, DelegateOr> Throws => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + + public static DelegateAnd Create(BaseAssertCondition, DelegateOr> otherAssertCondition) + { + return new DelegateAnd(otherAssertCondition)!; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs b/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs new file mode 100644 index 0000000000..6d602d240d --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs @@ -0,0 +1,18 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public class DelegateOr + : Or, DelegateOr>, + IDelegateAssertions, DelegateOr>, + IOr, TActual, DelegateAnd, DelegateOr> +{ + public DelegateOr(BaseAssertCondition, DelegateOr> otherAssertCondition) : base(otherAssertCondition) + { + } + + public Throws, DelegateOr> Throws => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + + public static DelegateOr Create(BaseAssertCondition, DelegateOr> otherAssertCondition) + { + return new DelegateOr(otherAssertCondition); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/IAnd.cs b/TUnit.Assertions/AssertConditions/Operators/IAnd.cs new file mode 100644 index 0000000000..e4b51262ea --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/IAnd.cs @@ -0,0 +1,8 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public interface IAnd + where TAnd : And, IAnd + where TOr : Or, IOr +{ + public static abstract TSelf Create(BaseAssertCondition otherAssertCondition); +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/IDelegateAssertions.cs b/TUnit.Assertions/AssertConditions/Operators/IDelegateAssertions.cs new file mode 100644 index 0000000000..1c911f2ca8 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/IDelegateAssertions.cs @@ -0,0 +1,8 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +internal interface IDelegateAssertions + where TAnd : And, IAnd + where TOr : Or, IOr +{ + public Throws Throws { get; } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/IOr.cs b/TUnit.Assertions/AssertConditions/Operators/IOr.cs new file mode 100644 index 0000000000..5147bbd9b7 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/IOr.cs @@ -0,0 +1,8 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public interface IOr + where TAnd : And, IAnd + where TOr : Or, IOr +{ + public static abstract TSelf Create(BaseAssertCondition otherAssertCondition); +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/IValueAssertions.cs b/TUnit.Assertions/AssertConditions/Operators/IValueAssertions.cs new file mode 100644 index 0000000000..8af1506c12 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/IValueAssertions.cs @@ -0,0 +1,10 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +internal interface IValueAssertions + where TAnd : And, IAnd + where TOr : Or, IOr +{ + public Is Is { get; } + public Does Does { get; } + public Has Has { get; } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/Or.cs b/TUnit.Assertions/AssertConditions/Operators/Or.cs index ec1bb0b69b..0b32e7d3d5 100644 --- a/TUnit.Assertions/AssertConditions/Operators/Or.cs +++ b/TUnit.Assertions/AssertConditions/Operators/Or.cs @@ -1,16 +1,13 @@ namespace TUnit.Assertions.AssertConditions.Operators; -public class Or +public class Or + where TAnd : And, IAnd + where TOr : Or, IOr { - private readonly BaseAssertCondition _otherAssertCondition; + protected readonly BaseAssertCondition OtherAssertCondition; - public Or(BaseAssertCondition otherAssertCondition) + public Or(BaseAssertCondition otherAssertCondition) { - _otherAssertCondition = otherAssertCondition; + OtherAssertCondition = otherAssertCondition; } - - public Is Is => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); - public Has Has => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); - public Does Does => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); - public Throws Throws => new(_otherAssertCondition.AssertionBuilder, ConnectorType.Or, _otherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/ValueAnd.cs b/TUnit.Assertions/AssertConditions/Operators/ValueAnd.cs new file mode 100644 index 0000000000..b4c421a368 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/ValueAnd.cs @@ -0,0 +1,19 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public class ValueAnd + : And, ValueOr>, IValueAssertions, ValueOr>, + IAnd, TActual, ValueAnd, ValueOr> +{ + public ValueAnd(BaseAssertCondition, ValueOr> otherAssertCondition) : base(otherAssertCondition) + { + } + + public Is, ValueOr> Is => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + public Has, ValueOr> Has => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + public Does, ValueOr> Does => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + + public static ValueAnd Create(BaseAssertCondition, ValueOr> otherAssertCondition) + { + return new ValueAnd(otherAssertCondition); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/ValueDelegateAnd.cs b/TUnit.Assertions/AssertConditions/Operators/ValueDelegateAnd.cs new file mode 100644 index 0000000000..db41191f7e --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/ValueDelegateAnd.cs @@ -0,0 +1,22 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public class ValueDelegateAnd + : And, ValueDelegateOr>, + IDelegateAssertions, ValueDelegateOr>, + IAnd, TActual, ValueDelegateAnd, ValueDelegateOr> +{ + public ValueDelegateAnd(BaseAssertCondition, ValueDelegateOr> otherAssertCondition) : base(otherAssertCondition) + { + } + + public Is, ValueDelegateOr> Is => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + public Has, ValueDelegateOr> Has => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + public Does, ValueDelegateOr> Does => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + + public Throws, ValueDelegateOr> Throws => new(OtherAssertCondition.AssertionBuilder, ConnectorType.And, OtherAssertCondition); + + public static ValueDelegateAnd Create(BaseAssertCondition, ValueDelegateOr> otherAssertCondition) + { + return new ValueDelegateAnd(otherAssertCondition); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/ValueDelegateOr.cs b/TUnit.Assertions/AssertConditions/Operators/ValueDelegateOr.cs new file mode 100644 index 0000000000..69b87ab75a --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/ValueDelegateOr.cs @@ -0,0 +1,23 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public class ValueDelegateOr + : Or, ValueDelegateOr>, + IValueAssertions, ValueDelegateOr>, + IDelegateAssertions, ValueDelegateOr>, + IOr, TActual, ValueDelegateAnd, ValueDelegateOr> +{ + public ValueDelegateOr(BaseAssertCondition, ValueDelegateOr> otherAssertCondition) : base(otherAssertCondition) + { + } + + public Is, ValueDelegateOr> Is => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + public Has, ValueDelegateOr> Has => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + public Does, ValueDelegateOr> Does => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + + public Throws, ValueDelegateOr> Throws => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + + public static ValueDelegateOr Create(BaseAssertCondition, ValueDelegateOr> otherAssertCondition) + { + return new ValueDelegateOr(otherAssertCondition); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/ValueOr.cs b/TUnit.Assertions/AssertConditions/Operators/ValueOr.cs new file mode 100644 index 0000000000..fc2156fdcf --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/ValueOr.cs @@ -0,0 +1,21 @@ +namespace TUnit.Assertions.AssertConditions.Operators; + +public class ValueOr + : Or, ValueOr>, + IValueAssertions, ValueOr>, + IOr, TActual, ValueAnd, ValueOr> + +{ + public ValueOr(BaseAssertCondition, ValueOr> otherAssertCondition) : base(otherAssertCondition) + { + } + + public Is, ValueOr> Is => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + public Has, ValueOr> Has => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + public Does, ValueOr> Does => new(OtherAssertCondition.AssertionBuilder, ConnectorType.Or, OtherAssertCondition); + + public static ValueOr Create(BaseAssertCondition, ValueOr> otherAssertCondition) + { + return new ValueOr(otherAssertCondition); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs index 192e7d37ef..edd5ed5a67 100644 --- a/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringContainsAssertCondition.cs @@ -1,6 +1,10 @@ -namespace TUnit.Assertions.AssertConditions.String; +using TUnit.Assertions.AssertConditions.Operators; -public class StringContainsAssertCondition : AssertCondition +namespace TUnit.Assertions.AssertConditions.String; + +public class StringContainsAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly StringComparison _stringComparison; diff --git a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs index b19729fef8..04303aec15 100644 --- a/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/String/StringEqualsAssertCondition.cs @@ -1,6 +1,10 @@ -namespace TUnit.Assertions.AssertConditions.String; +using TUnit.Assertions.AssertConditions.Operators; -public class StringEqualsAssertCondition : AssertCondition +namespace TUnit.Assertions.AssertConditions.String; + +public class StringEqualsAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly StringComparison _stringComparison; diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs index 75801a4333..23aeb0af9f 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingAssertCondition.cs @@ -1,6 +1,10 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsAnythingAssertCondition : AssertCondition +public class ThrowsAnythingAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { public ThrowsAnythingAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs index eba5478064..b9475b4b91 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfAssertCondition.cs @@ -1,6 +1,10 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsExactTypeOfAssertCondition : AssertCondition +public class ThrowsExactTypeOfAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { public ThrowsExactTypeOfAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index 102df8bb07..17426e6f8d 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -1,6 +1,10 @@ -namespace TUnit.Assertions.AssertConditions.Throws; +using TUnit.Assertions.AssertConditions.Operators; -public class ThrowsNothingAssertCondition : AssertCondition +namespace TUnit.Assertions.AssertConditions.Throws; + +public class ThrowsNothingAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { public ThrowsNothingAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs index c5d9dc149b..a81be7ac00 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfAssertCondition.cs @@ -1,6 +1,10 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsSubClassOfAssertCondition : AssertCondition +public class ThrowsSubClassOfAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { public ThrowsSubClassOfAssertCondition(AssertionBuilder assertionBuilder) : base(assertionBuilder, default) { diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs index ea8c7f818b..6277fda299 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingAssertCondition.cs @@ -1,6 +1,10 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsWithMessageContainingAssertCondition : AssertCondition +public class ThrowsWithMessageContainingAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly StringComparison _stringComparison; diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs index ea3d588c9f..fcece123c5 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToAssertCondition.cs @@ -1,6 +1,10 @@ +using TUnit.Assertions.AssertConditions.Operators; + namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsWithMessageEqualToAssertCondition : AssertCondition +public class ThrowsWithMessageEqualToAssertCondition : AssertCondition + where TAnd : And, IAnd + where TOr : Or, IOr { private readonly StringComparison _stringComparison; diff --git a/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs b/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs index 6e6c482bbb..e7aa987439 100644 --- a/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs +++ b/TUnit.Assertions/AssertConditions/Throws/WithMessage.cs @@ -1,31 +1,35 @@ -namespace TUnit.Assertions.AssertConditions.Throws; +using TUnit.Assertions.AssertConditions.Operators; -public class WithMessage : Connector +namespace TUnit.Assertions.AssertConditions.Throws; + +public class WithMessage : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { protected AssertionBuilder AssertionBuilder { get; } - public WithMessage(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + public WithMessage(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public BaseAssertCondition EqualTo(string expected) + public BaseAssertCondition EqualTo(string expected) { return EqualTo(expected, StringComparison.Ordinal); } - public BaseAssertCondition EqualTo(string expected, StringComparison stringComparison) + public BaseAssertCondition EqualTo(string expected, StringComparison stringComparison) { - return Wrap(new ThrowsWithMessageEqualToAssertCondition(AssertionBuilder, expected, stringComparison)); + return Wrap(new ThrowsWithMessageEqualToAssertCondition(AssertionBuilder, expected, stringComparison)); } - public BaseAssertCondition Containing(string expected) + public BaseAssertCondition Containing(string expected) { return Containing(expected, StringComparison.Ordinal); } - public BaseAssertCondition Containing(string expected, StringComparison stringComparison) + public BaseAssertCondition Containing(string expected, StringComparison stringComparison) { - return Wrap(new ThrowsWithMessageContainingAssertCondition(AssertionBuilder, expected, stringComparison)); + return Wrap(new ThrowsWithMessageContainingAssertCondition(AssertionBuilder, expected, stringComparison)); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilder.cs b/TUnit.Assertions/AssertionBuilder.cs index d7cd791065..e22001cb8f 100644 --- a/TUnit.Assertions/AssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilder.cs @@ -1,8 +1,6 @@ -using TUnit.Assertions.AssertConditions; +namespace TUnit.Assertions; -namespace TUnit.Assertions; - -public abstract class AssertionBuilder +public abstract class AssertionBuilder { - protected internal abstract Task> GetAssertionData(); + protected internal abstract Task> GetAssertionData(); } \ No newline at end of file diff --git a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs index 440512af96..06b9350e33 100644 --- a/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AsyncDelegateAssertionBuilder.cs @@ -1,22 +1,23 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class AsyncDelegateAssertionBuilder : AssertionBuilder +public class AsyncDelegateAssertionBuilder : AssertionBuilder { - private readonly Func> _function; + private readonly Func> _function; - public Does Does => new(this, ConnectorType.None, null); - public Is Is => new(this, ConnectorType.None, null); - public Has Has => new(this, ConnectorType.None, null); - public Throws Throws => new(this, ConnectorType.None, null); + public Does, DelegateOr> Does => new(this, ConnectorType.None, null); + public Is, DelegateOr> Is => new(this, ConnectorType.None, null); + public Has, DelegateOr> Has => new(this, ConnectorType.None, null); + public Throws, DelegateOr> Throws => new(this, ConnectorType.None, null); - internal AsyncDelegateAssertionBuilder(Func> function) + internal AsyncDelegateAssertionBuilder(Func> function) { _function = function; } - protected internal override async Task> GetAssertionData() + protected internal override async Task> GetAssertionData() { var assertionData = await _function.InvokeAndGetExceptionAsync(); @@ -28,7 +29,7 @@ public class AsyncDelegateAssertionBuilder : AssertionBuilder { private readonly Func _function; - public Throws Throws => new(this, ConnectorType.None, null); + public Throws, DelegateOr> Throws => new(this, ConnectorType.None, null); internal AsyncDelegateAssertionBuilder(Func function) { diff --git a/TUnit.Assertions/DelegateAssertionBuilder.cs b/TUnit.Assertions/DelegateAssertionBuilder.cs index 53d05f59d6..0b799fed90 100644 --- a/TUnit.Assertions/DelegateAssertionBuilder.cs +++ b/TUnit.Assertions/DelegateAssertionBuilder.cs @@ -1,22 +1,23 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class DelegateAssertionBuilder : AssertionBuilder +public class DelegateAssertionBuilder : AssertionBuilder { - private readonly Func _function; + private readonly Func _function; - public Does Does => new(this, ConnectorType.None, null); - public Is Is => new(this, ConnectorType.None, null); - public Has Has => new(this, ConnectorType.None, null); - public Throws Throws => new(this, ConnectorType.None, null); + public Does, DelegateOr> Does => new(this, ConnectorType.None, null); + public Is, DelegateOr> Is => new(this, ConnectorType.None, null); + public Has, DelegateOr> Has => new(this, ConnectorType.None, null); + public Throws, DelegateOr> Throws => new(this, ConnectorType.None, null); - internal DelegateAssertionBuilder(Func function) + internal DelegateAssertionBuilder(Func function) { _function = function; } - protected internal override Task> GetAssertionData() + protected internal override Task> GetAssertionData() { var assertionData = _function.InvokeAndGetException(); @@ -28,7 +29,7 @@ public class DelegateAssertionBuilder : AssertionBuilder { private readonly Action _action; - public Throws Throws => new(this, ConnectorType.None, null); + public Throws, DelegateOr> Throws => new(this, ConnectorType.None, null); internal DelegateAssertionBuilder(Action action) { diff --git a/TUnit.Assertions/Does.cs b/TUnit.Assertions/Does.cs index 025836eefe..0e5961273b 100644 --- a/TUnit.Assertions/Does.cs +++ b/TUnit.Assertions/Does.cs @@ -1,12 +1,15 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class Does : Connector +public class Does : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { - protected internal AssertionBuilder AssertionBuilder { get; } + protected internal AssertionBuilder AssertionBuilder { get; } - public Does(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + public Does(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } diff --git a/TUnit.Assertions/Extensions/Connector.cs b/TUnit.Assertions/Extensions/Connector.cs index 73ec3e117d..397a35dfc1 100644 --- a/TUnit.Assertions/Extensions/Connector.cs +++ b/TUnit.Assertions/Extensions/Connector.cs @@ -1,26 +1,29 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Connectors; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public abstract class Connector +public abstract class Connector + where TAnd : And, IAnd + where TOr : Or, IOr { public ConnectorType ConnectorType { get; } - public BaseAssertCondition? OtherAssertCondition { get; } + public BaseAssertCondition? OtherAssertCondition { get; } - public Connector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) + public Connector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) { ConnectorType = connectorType; OtherAssertCondition = otherAssertCondition; } - protected BaseAssertCondition Wrap(BaseAssertCondition assertCondition) + public BaseAssertCondition Wrap(BaseAssertCondition assertCondition) { return ConnectorType switch { ConnectorType.None => assertCondition, - ConnectorType.And => new AssertConditionAnd(OtherAssertCondition!, assertCondition), - ConnectorType.Or => new AssertConditionAnd(OtherAssertCondition!, assertCondition), + ConnectorType.And => new AssertConditionAnd(OtherAssertCondition!, assertCondition), + ConnectorType.Or => new AssertConditionAnd(OtherAssertCondition!, assertCondition), _ => throw new ArgumentOutOfRangeException(nameof(ConnectorType), ConnectorType, "Unknown connector type") }; } diff --git a/TUnit.Assertions/Extensions/DoesExtensions.cs b/TUnit.Assertions/Extensions/DoesExtensions.cs index cc28a476ff..75e1dd7496 100644 --- a/TUnit.Assertions/Extensions/DoesExtensions.cs +++ b/TUnit.Assertions/Extensions/DoesExtensions.cs @@ -1,24 +1,31 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Collections; +using TUnit.Assertions.AssertConditions.Operators; using TUnit.Assertions.AssertConditions.String; namespace TUnit.Assertions; public static class DoesExtensions { - public static AssertCondition Contain(this Is @is, TInner expected) - where T : IEnumerable + public static AssertCondition Contain(this Is @is, TInner expected) + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EnumerableContainsAssertCondition(@is.AssertionBuilder, expected); + return new EnumerableContainsAssertCondition(@is.AssertionBuilder, expected); } - public static AssertCondition Contain(this Is @is, string expected) + public static AssertCondition Contain(this Is @is, string expected) + where TAnd : And, IAnd + where TOr : Or, IOr { return Contain(@is, expected, StringComparison.Ordinal); } - public static AssertCondition Contain(this Is @is, string expected, StringComparison stringComparison) + public static AssertCondition Contain(this Is @is, string expected, StringComparison stringComparison) + where TAnd : And, IAnd + where TOr : Or, IOr { - return new StringContainsAssertCondition(@is.AssertionBuilder, expected, stringComparison); + return new StringContainsAssertCondition(@is.AssertionBuilder, expected, stringComparison); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/EnumerableCount.cs b/TUnit.Assertions/Extensions/EnumerableCount.cs index 494e33ba6f..748b604545 100644 --- a/TUnit.Assertions/Extensions/EnumerableCount.cs +++ b/TUnit.Assertions/Extensions/EnumerableCount.cs @@ -1,21 +1,25 @@ using System.Collections; using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class EnumerableCount : Connector where T : IEnumerable +public class EnumerableCount : Connector + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - protected internal AssertionBuilder AssertionBuilder { get; } + protected internal AssertionBuilder AssertionBuilder { get; } - public EnumerableCount(AssertionBuilder assertionBuilder, ConnectorType connectorType, - BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + public EnumerableCount(AssertionBuilder assertionBuilder, ConnectorType connectorType, + BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public BaseAssertCondition EqualTo(int expected) + public BaseAssertCondition EqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) == expected; @@ -25,8 +29,8 @@ public BaseAssertCondition EqualTo(int expected) ); } - public BaseAssertCondition Empty => - Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, expected, _) => + public BaseAssertCondition Empty => + Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) == expected; @@ -35,9 +39,9 @@ public BaseAssertCondition EqualTo(int expected) $"{enumerable} has a expected of {GetCount(enumerable)} but expected to be equal to {0}") ); - public BaseAssertCondition GreaterThan(int expected) + public BaseAssertCondition GreaterThan(int expected) { - return Wrap(new DelegateAssertCondition( + return Wrap(new DelegateAssertCondition( AssertionBuilder, expected, (enumerable, _, _) => @@ -50,9 +54,9 @@ public BaseAssertCondition GreaterThan(int expected) ); } - public BaseAssertCondition GreaterThanOrEqualTo(int expected) + public BaseAssertCondition GreaterThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) >= expected; @@ -62,9 +66,9 @@ public BaseAssertCondition GreaterThanOrEqualTo(int expected) ); } - public BaseAssertCondition LessThan(int expected) + public BaseAssertCondition LessThan(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) < expected; @@ -74,9 +78,9 @@ public BaseAssertCondition LessThan(int expected) ); } - public BaseAssertCondition LessThanOrEqualTo(int expected) + public BaseAssertCondition LessThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (enumerable, expected, _) => { ArgumentNullException.ThrowIfNull(enumerable); return GetCount(enumerable) <= expected; @@ -86,7 +90,7 @@ public BaseAssertCondition LessThanOrEqualTo(int expected) ); } - private int GetCount(T? actualValue) + private int GetCount(TActual? actualValue) { ArgumentNullException.ThrowIfNull(actualValue); diff --git a/TUnit.Assertions/Extensions/HasExtensions.cs b/TUnit.Assertions/Extensions/HasExtensions.cs index 528bb2b848..a0ed9e6db9 100644 --- a/TUnit.Assertions/Extensions/HasExtensions.cs +++ b/TUnit.Assertions/Extensions/HasExtensions.cs @@ -1,16 +1,22 @@ using System.Collections; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; public static class HasExtensions { - public static EnumerableCount Count(this Has has) where T : IEnumerable + public static EnumerableCount Count(this Has has) + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EnumerableCount(has.AssertionBuilder, has.ConnectorType, has.OtherAssertCondition); + return new EnumerableCount(has.AssertionBuilder, has.ConnectorType, has.OtherAssertCondition); } - public static StringLength Length(this Has has) + public static StringLength Length(this Has has) + where TAnd : And, IAnd + where TOr : Or, IOr { - return new StringLength(has.AssertionBuilder, has.ConnectorType, has.OtherAssertCondition); + return new StringLength(has.AssertionBuilder, has.ConnectorType, has.OtherAssertCondition); } } \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/IsExtensions.cs b/TUnit.Assertions/Extensions/IsExtensions.cs index b4a7ffd15c..2ded8f3abe 100644 --- a/TUnit.Assertions/Extensions/IsExtensions.cs +++ b/TUnit.Assertions/Extensions/IsExtensions.cs @@ -2,7 +2,7 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Collections; using TUnit.Assertions.AssertConditions.Generic; -using TUnit.Assertions.AssertConditions.Numbers; +using TUnit.Assertions.AssertConditions.Operators; using TUnit.Assertions.AssertConditions.String; namespace TUnit.Assertions; @@ -11,118 +11,179 @@ public static class IsExtensions { #region Strings - public static AssertCondition EqualTo(this Is @is, string expected) + public static BaseAssertCondition EqualTo(this Is @is, string expected) + where TAnd : And, IAnd + where TOr : Or, IOr { return EqualTo(@is, expected, StringComparison.Ordinal); } - public static AssertCondition EqualTo(this Is @is, string expected, StringComparison stringComparison) + public static BaseAssertCondition EqualTo(this Is @is, string expected, StringComparison stringComparison) + where TAnd : And, IAnd + where TOr : Or, IOr { - return new StringEqualsAssertCondition(@is.AssertionBuilder, expected, stringComparison); + return @is.Wrap(new StringEqualsAssertCondition(@is.AssertionBuilder, expected, stringComparison)); } #endregion #region Numbers - public static AssertCondition Zero(this Is @is) + public static BaseAssertCondition Zero(this Is @is) where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EqualsAssertCondition(@is.AssertionBuilder, TActual.Zero); + return @is.Wrap(new EqualsAssertCondition(@is.AssertionBuilder, TActual.Zero)); } - public static AssertCondition GreaterThan(this Is @is, T expected) where T : INumber + public static BaseAssertCondition GreaterThan(this Is @is, TActual expected) where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new GreaterThanAssertCondition(@is.AssertionBuilder, expected); + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value > expected; + }, + (value, _) => $"{value} was not greater than {expected}")); } - public static AssertCondition GreaterThanOrEqualTo(this Is @is, T expected) where T : INumber + public static BaseAssertCondition GreaterThanOrEqualTo(this Is @is, TActual expected) + where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new GreaterThanOrEqualToAssertCondition(@is.AssertionBuilder, expected); + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value >= expected; + }, + (value, _) => $"{value} was not greater than or equal to {expected}")); } - public static AssertCondition LessThan(this Is @is, T expected) where T : INumber + public static BaseAssertCondition LessThan(this Is @is, TActual expected) + where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new GreaterThanAssertCondition(@is.AssertionBuilder, expected); + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value < expected; + }, + (value, _) => $"{value} was not less than {expected}")); } - public static AssertCondition LessThanOrEqualTo(this Is @is, T expected) where T : INumber + public static BaseAssertCondition LessThanOrEqualTo(this Is @is, TActual expected) + where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new LessThanOrEqualToAssertCondition(@is.AssertionBuilder, expected); + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + { + ArgumentNullException.ThrowIfNull(value); + + return value <= expected; + }, + (value, _) => $"{value} was not less than or equal to {expected}")); } - public static AssertCondition Even(this Is @is) where T : INumber, IModulusOperators + public static BaseAssertCondition Even(this Is @is) + where TActual : INumber, IModulusOperators + where TAnd : And, IAnd + where TOr : Or, IOr { - return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => { ArgumentNullException.ThrowIfNull(value); return value % 2 == 0; }, - (value, _) => $"{value} was not even"); + (value, _) => $"{value} was not even")); } - public static AssertCondition Odd(this Is @is) where T : INumber, IModulusOperators + public static BaseAssertCondition Odd(this Is @is) + where TActual : INumber, IModulusOperators + where TAnd : And, IAnd + where TOr : Or, IOr { - return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => { ArgumentNullException.ThrowIfNull(value); return value % 2 != 0; }, - (value, _) => $"{value} was not odd"); + (value, _) => $"{value} was not odd")); } - public static AssertCondition Negative(this Is @is) where T : INumber + public static BaseAssertCondition Negative(this Is @is) + where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => { ArgumentNullException.ThrowIfNull(value); - return value < T.Zero; + return value < TActual.Zero; }, - (value, _) => $"{value} was not negative"); + (value, _) => $"{value} was not negative")); } - public static AssertCondition Positive(this Is @is) where T : INumber + public static BaseAssertCondition Positive(this Is @is) + where TActual : INumber + where TAnd : And, IAnd + where TOr : Or, IOr { - return new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => + return @is.Wrap(new DelegateAssertCondition(@is.AssertionBuilder, default, (value, _, _) => { ArgumentNullException.ThrowIfNull(value); - return value > T.Zero; + return value > TActual.Zero; }, - (value, _) => $"{value} was not positive"); + (value, _) => $"{value} was not positive")); } #endregion #region Enumerables - public static AssertCondition> EquivalentTo(this Is @is, IEnumerable expected) - where T : IEnumerable + public static BaseAssertCondition EquivalentTo(this Is @is, IEnumerable expected) + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EnumerableEquivalentToAssertCondition(@is.AssertionBuilder, expected); + return @is.Wrap(new EnumerableEquivalentToAssertCondition(@is.AssertionBuilder, expected)); } - public static AssertCondition Empty(this Is @is) - where T : IEnumerable + public static BaseAssertCondition Empty(this Is @is) + where TActual : IEnumerable + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EnumerableCountEqualToAssertCondition(@is.AssertionBuilder, 0); + return @is.Wrap(new EnumerableCountEqualToAssertCondition(@is.AssertionBuilder, 0)); } #endregion #region Booleans - public static AssertCondition True(this Is @is) + public static BaseAssertCondition True(this Is @is) + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EqualsAssertCondition(@is.AssertionBuilder, true); + return @is.Wrap(new EqualsAssertCondition(@is.AssertionBuilder, true)); } - public static AssertCondition False(this Is @is) + public static BaseAssertCondition False(this Is @is) + where TAnd : And, IAnd + where TOr : Or, IOr { - return new EqualsAssertCondition(@is.AssertionBuilder, false); + return @is.Wrap(new EqualsAssertCondition(@is.AssertionBuilder, false)); } #endregion diff --git a/TUnit.Assertions/Extensions/NotConnector.cs b/TUnit.Assertions/Extensions/NotConnector.cs index 00e2a6639d..8e2b235ca7 100644 --- a/TUnit.Assertions/Extensions/NotConnector.cs +++ b/TUnit.Assertions/Extensions/NotConnector.cs @@ -1,14 +1,17 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public abstract class NotConnector : Connector +public abstract class NotConnector : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { - protected NotConnector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + protected NotConnector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { } - protected BaseAssertCondition Invert(BaseAssertCondition assertCondition, Func messageFactory) + protected BaseAssertCondition Invert(BaseAssertCondition assertCondition, Func messageFactory) { return Wrap(assertCondition.Invert(messageFactory)); } diff --git a/TUnit.Assertions/Extensions/StringLength.cs b/TUnit.Assertions/Extensions/StringLength.cs index a9f641a1b8..026b8d6bdc 100644 --- a/TUnit.Assertions/Extensions/StringLength.cs +++ b/TUnit.Assertions/Extensions/StringLength.cs @@ -1,20 +1,23 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class StringLength : Connector +public class StringLength : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { protected AssertionBuilder AssertionBuilder { get; } public StringLength(AssertionBuilder assertionBuilder, ConnectorType connectorType, - BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public BaseAssertCondition EqualTo(int expected) + public BaseAssertCondition EqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length == expected; @@ -24,8 +27,8 @@ public BaseAssertCondition EqualTo(int expected) ); } - public BaseAssertCondition IsEmpty => - Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (@string, _, _) => + public BaseAssertCondition IsEmpty => + Wrap(new DelegateAssertCondition(AssertionBuilder, 0, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length == 0; @@ -34,8 +37,8 @@ public BaseAssertCondition EqualTo(int expected) $"\"{@string}\" was {@string?.Length} characters long but expected to be equal to {0}") ); - public BaseAssertCondition IsNotEmpty => - Wrap(new DelegateAssertCondition(AssertionBuilder, default, (@string, _, _) => + public BaseAssertCondition IsNotEmpty => + Wrap(new DelegateAssertCondition(AssertionBuilder, default, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length > 0; @@ -45,9 +48,9 @@ public BaseAssertCondition EqualTo(int expected) )); - public BaseAssertCondition GreaterThan(int expected) + public BaseAssertCondition GreaterThan(int expected) { - return Wrap(new DelegateAssertCondition( + return Wrap(new DelegateAssertCondition( AssertionBuilder, expected, (@string, _, _) => @@ -60,9 +63,9 @@ public BaseAssertCondition GreaterThan(int expected) ); } - public BaseAssertCondition GreaterThanOrEqualTo(int expected) + public BaseAssertCondition GreaterThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length >= expected; @@ -72,9 +75,9 @@ public BaseAssertCondition GreaterThanOrEqualTo(int expected) ); } - public BaseAssertCondition LessThan(int expected) + public BaseAssertCondition LessThan(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length < expected; @@ -84,9 +87,9 @@ public BaseAssertCondition LessThan(int expected) ); } - public BaseAssertCondition LessThanOrEqualTo(int expected) + public BaseAssertCondition LessThanOrEqualTo(int expected) { - return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => + return Wrap(new DelegateAssertCondition(AssertionBuilder, expected, (@string, _, _) => { ArgumentNullException.ThrowIfNull(@string); return @string.Length <= expected; diff --git a/TUnit.Assertions/Has.cs b/TUnit.Assertions/Has.cs index 3cd8240f93..4a81e9631e 100644 --- a/TUnit.Assertions/Has.cs +++ b/TUnit.Assertions/Has.cs @@ -1,18 +1,21 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class Has : Connector +public class Has : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { - protected internal AssertionBuilder AssertionBuilder { get; } + protected internal AssertionBuilder AssertionBuilder { get; } - public Has(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + public Has(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public Property Property(string name) => new(AssertionBuilder, name); + public Property Property(string name) => new(AssertionBuilder, name); - public Property Property(string name) => new(AssertionBuilder, name); + public Property Property(string name) => new(AssertionBuilder, name); } \ No newline at end of file diff --git a/TUnit.Assertions/Is.cs b/TUnit.Assertions/Is.cs index 357e94c342..ddf75f4c9c 100644 --- a/TUnit.Assertions/Is.cs +++ b/TUnit.Assertions/Is.cs @@ -1,31 +1,34 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class Is : Connector +public class Is : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { protected internal AssertionBuilder AssertionBuilder { get; } public Is(AssertionBuilder assertionBuilder, ConnectorType connectorType, - BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public BaseAssertCondition EqualTo(TActual expected) + public BaseAssertCondition EqualTo(TActual expected) { - return Wrap(new EqualsAssertCondition(AssertionBuilder, expected)); + return Wrap(new EqualsAssertCondition(AssertionBuilder, expected)); } - public BaseAssertCondition SameReference(TActual expected) + public BaseAssertCondition SameReference(TActual expected) { - return Wrap(new SameReferenceAssertCondition(AssertionBuilder, expected)); + return Wrap(new SameReferenceAssertCondition(AssertionBuilder, expected)); } - public BaseAssertCondition Null => Wrap(new NullAssertCondition(AssertionBuilder)); + public BaseAssertCondition Null => Wrap(new NullAssertCondition(AssertionBuilder)); - public BaseAssertCondition TypeOf() => Wrap(new TypeOfAssertCondition(AssertionBuilder)); + public BaseAssertCondition TypeOf() => Wrap(new TypeOfAssertCondition(AssertionBuilder)); - public IsNot Not => new(AssertionBuilder, ConnectorType, OtherAssertCondition); + public IsNot Not => new(AssertionBuilder, ConnectorType, OtherAssertCondition); } \ No newline at end of file diff --git a/TUnit.Assertions/IsNot.cs b/TUnit.Assertions/IsNot.cs index 3a5e36df63..d50e78b125 100644 --- a/TUnit.Assertions/IsNot.cs +++ b/TUnit.Assertions/IsNot.cs @@ -1,23 +1,26 @@ using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Generic; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class IsNot : NotConnector +public class IsNot : NotConnector + where TAnd : And, IAnd + where TOr : Or, IOr { protected internal AssertionBuilder AssertionBuilder { get; } - public IsNot(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + public IsNot(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public BaseAssertCondition EqualTo(TActual expected) => Invert(new EqualsAssertCondition(AssertionBuilder, expected), + public BaseAssertCondition EqualTo(TActual expected) => Invert(new EqualsAssertCondition(AssertionBuilder, expected), (actual, exception) => $"Expected {actual} to equal {expected}"); - public BaseAssertCondition Null => Invert(new NullAssertCondition(AssertionBuilder), + public BaseAssertCondition Null => Invert(new NullAssertCondition(AssertionBuilder), (actual, exception) => $"Expected {actual} to be null"); - public BaseAssertCondition TypeOf() => Invert(new TypeOfAssertCondition(AssertionBuilder), + public BaseAssertCondition TypeOf() => Invert(new TypeOfAssertCondition(AssertionBuilder), (actual, exception) => $"Expected {actual} to not be of type {typeof(TExpected)}"); } \ No newline at end of file diff --git a/TUnit.Assertions/Throws.cs b/TUnit.Assertions/Throws.cs index aab13f13d8..ed21e3901e 100644 --- a/TUnit.Assertions/Throws.cs +++ b/TUnit.Assertions/Throws.cs @@ -1,26 +1,29 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; using TUnit.Assertions.AssertConditions.Throws; namespace TUnit.Assertions; -public class Throws : Connector +public class Throws : Connector + where TAnd : And, IAnd + where TOr : Or, IOr { protected AssertionBuilder AssertionBuilder { get; } - public Throws(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) + public Throws(AssertionBuilder assertionBuilder, ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) : base(connectorType, otherAssertCondition) { AssertionBuilder = assertionBuilder; } - public WithMessage WithMessage => new(AssertionBuilder, ConnectorType, OtherAssertCondition); + public WithMessage WithMessage => new(AssertionBuilder, ConnectorType, OtherAssertCondition); - public BaseAssertCondition Nothing => Wrap(new ThrowsNothingAssertCondition(AssertionBuilder)); + public BaseAssertCondition Nothing => Wrap(new ThrowsNothingAssertCondition(AssertionBuilder)); - public BaseAssertCondition Exception => - Wrap(new ThrowsAnythingAssertCondition(AssertionBuilder)); + public BaseAssertCondition Exception => + Wrap(new ThrowsAnythingAssertCondition(AssertionBuilder)); - public BaseAssertCondition TypeOf() => Wrap(new ThrowsExactTypeOfAssertCondition(AssertionBuilder)); + public BaseAssertCondition TypeOf() => Wrap(new ThrowsExactTypeOfAssertCondition(AssertionBuilder)); - public BaseAssertCondition SubClassOf() => - Wrap(new ThrowsSubClassOfAssertCondition(AssertionBuilder)); + public BaseAssertCondition SubClassOf() => + Wrap(new ThrowsSubClassOfAssertCondition(AssertionBuilder)); } \ No newline at end of file diff --git a/TUnit.Assertions/ValueAssertionBuilder.cs b/TUnit.Assertions/ValueAssertionBuilder.cs index a738672c6e..cf7c3215cc 100644 --- a/TUnit.Assertions/ValueAssertionBuilder.cs +++ b/TUnit.Assertions/ValueAssertionBuilder.cs @@ -1,23 +1,24 @@ using TUnit.Assertions.AssertConditions; +using TUnit.Assertions.AssertConditions.Operators; namespace TUnit.Assertions; -public class ValueAssertionBuilder : AssertionBuilder +public class ValueAssertionBuilder : AssertionBuilder { - private readonly T? _value; + private readonly TActual? _value; - public Does Does => new(this, ConnectorType.None, null); - public Is Is => new(this, ConnectorType.None, null); - public Has Has => new(this, ConnectorType.None, null); + public Does, ValueOr> Does => new(this, ConnectorType.None, null); + public Is, ValueOr> Is => new(this, ConnectorType.None, null); + public Has, ValueOr> Has => new(this, ConnectorType.None, null); - internal ValueAssertionBuilder(T? value) + internal ValueAssertionBuilder(TActual? value) { _value = value; } - protected internal override Task> GetAssertionData() + protected internal override Task> GetAssertionData() { - return Task.FromResult(new AssertionData(_value, null)); + return Task.FromResult(new AssertionData(_value, null)); } } \ No newline at end of file From ec0da8b94ade22d9df1a5fae24eb4eecf73d92af Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 12:58:46 +0000 Subject: [PATCH 102/124] Pass category in tests --- TUnit.TestProject/Tests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index bad6fff679..df0e35667a 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -6,6 +6,7 @@ namespace TUnit.TestProject; public class Tests { [Test] + [TestCategory("Pass")] public async Task Test1() { var value = "1"; @@ -21,6 +22,7 @@ public async Task Test2() } [Test] + [TestCategory("Pass")] public async Task Test3() { await Task.Yield(); @@ -72,12 +74,14 @@ public async Task Skip2() } [TestDataSource(nameof(One))] + [TestCategory("Pass")] public async Task TestDataSource1(int value) { await Assert.That(value).Is.EqualTo(1); } [TestDataSource(nameof(One))] + [TestCategory("Pass")] public async Task TestDataSource2(int value) { await Task.Yield(); @@ -100,12 +104,14 @@ public async Task TestDataSource4(int value) } [TestDataSource(nameof(TestDataSources), nameof(One))] + [TestCategory("Pass")] public async Task TestDataSource5(int value) { await Assert.That(value).Is.EqualTo(1); } [TestDataSource(nameof(TestDataSources), nameof(One))] + [TestCategory("Pass")] public async Task TestDataSource6(int value) { await Task.Yield(); @@ -128,6 +134,7 @@ public async Task TestDataSource8(int value) } [Test] + [TestCategory("Pass")] public async Task TestContext1() { await Assert.That(TestContext.Current.TestName).Is.EqualTo(nameof(TestContext1)); @@ -158,12 +165,14 @@ await Assert.That(async () => } [Test] + [TestCategory("Pass")] public async Task Throws3() { await Assert.That(() => throw new ApplicationException()).Throws.Exception; } [Test] + [TestCategory("Pass")] public async Task Throws4() { await Assert.That(async () => @@ -181,6 +190,7 @@ public async Task Timeout1() } [Test] + [TestCategory("Pass")] public async Task String_And_Condition() { await Assert.That("1").Is.EqualTo("1").And.Has.Length().EqualTo(1); @@ -194,6 +204,7 @@ public async Task String_And_Condition2() } [Test] + [TestCategory("Pass")] public async Task Count1() { var list = new List { 1, 2, 3 }; From 74a0b53ee00cffd8a6cf87e4b9fd2ced402d57af Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:01:51 +0000 Subject: [PATCH 103/124] README.md --- README.md | 63 ++++++++++++++++++- TUnit.Assertions/Extensions/Connector.cs | 6 +- TUnit.Assertions/Extensions/DoesExtensions.cs | 57 +++++++++++++++-- TUnit.Assertions/IsNot.cs | 6 +- .../Modules/GetPackageProjectsModule.cs | 9 ++- 5 files changed, 129 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a32891afa2..4e6bda8c71 100644 --- a/README.md +++ b/README.md @@ -1 +1,62 @@ -# TUnit \ No newline at end of file +# TUnit +T(est)Unit! + +```csharp + [Test] + public async Task Test1() + { + var value = "Hello world!"; + + await Assert.That(value) + .Is.Not.Null + .And.Is.EqualTo("hello world!", StringComparison.InvariantCultureIgnoreCase) + .And.Has.Count().EqualTo(12) + .And.Does.StartWith("H"); + } +``` + +## Motivations +There are only three main testing frameworks in the .NET world - xUnit, NUnit and MSTest. +More frameworks means more options, and more options motivates more features or improvements. + +These testing frameworks are amazing, but I've had some issues with them. You might not have had any of these, but these are my experiences: + +### xUnit +There is no way to tap into information about a test in a generic way. +For example, I've had some Playwright tests run before, and I want them to save a screenshot or video ONLY when the test fails. +If the test passes, I don't have anything to investigate, and it'll use up unnecessary storage, and it'll probably slow my test suite down if I had hundreds or thousands of tests all trying to save screenshots. + +However, if I'm in a Dispose method which is called when the test ends, then there's no way for me to know if my test succeeded or failed. I'd have to do some really clunky workaround involving try catch and setting a boolean or exception to a class field and checking that. And to do that for every test was just not ideal. + +#### Assertions +I have stumbled across assertions so many times where the arguments are the wrong way round. +This can result in really confusing error messages. +```csharp +var one = 2; +Assert.Equal(1, one) +Assert.Equal(one, 1) +``` + +### NUnit + +#### Assertions +I absolutely love the newer assertion syntax in NUnit. The `Assert.That(something, Is.Something)`. I think it's really clear to read, it's clear what is being asserted, and it's clear what you're trying to achieve. + +However, there is a lack of type checking on assertions. (Yes, there are analyzer packages to help with this, but this still isn't strict type checking.) + +`Assert.That("1", Throws.Exception);` + +This assertion makes no sense, because we're passing in a string. This can never throw an exception because it isn't a delegate that can be executed. But it's still perfectly valid code that will compile. + +As does this: +`Assert.That(1, Does.Contain("Foo!"));` + +With TUnit assertions, I wanted to make these impossible to compile. So type constraints are built into the assertions themselves. There should be no way for a non-delegate to be able to do a `Throws` assertion, or for an `int` assertion to check for `string` conditions. + +So in TUnit, this will compile: + +`await Assert.That(() => 1).Throws.Nothing;` + +This won't: + +`await Assert.That(1).Throws.Nothing;` \ No newline at end of file diff --git a/TUnit.Assertions/Extensions/Connector.cs b/TUnit.Assertions/Extensions/Connector.cs index 397a35dfc1..672de441f1 100644 --- a/TUnit.Assertions/Extensions/Connector.cs +++ b/TUnit.Assertions/Extensions/Connector.cs @@ -1,4 +1,5 @@ -using TUnit.Assertions.AssertConditions; +using System.ComponentModel; +using TUnit.Assertions.AssertConditions; using TUnit.Assertions.AssertConditions.Connectors; using TUnit.Assertions.AssertConditions.Operators; @@ -8,7 +9,10 @@ public abstract class Connector where TAnd : And, IAnd where TOr : Or, IOr { + [EditorBrowsable(EditorBrowsableState.Advanced)] public ConnectorType ConnectorType { get; } + + [EditorBrowsable(EditorBrowsableState.Advanced)] public BaseAssertCondition? OtherAssertCondition { get; } public Connector(ConnectorType connectorType, BaseAssertCondition? otherAssertCondition) diff --git a/TUnit.Assertions/Extensions/DoesExtensions.cs b/TUnit.Assertions/Extensions/DoesExtensions.cs index 75e1dd7496..8b0e8cd520 100644 --- a/TUnit.Assertions/Extensions/DoesExtensions.cs +++ b/TUnit.Assertions/Extensions/DoesExtensions.cs @@ -7,25 +7,70 @@ namespace TUnit.Assertions; public static class DoesExtensions { - public static AssertCondition Contain(this Is @is, TInner expected) + public static BaseAssertCondition Contain(this Does does, TInner expected) where TActual : IEnumerable where TAnd : And, IAnd where TOr : Or, IOr { - return new EnumerableContainsAssertCondition(@is.AssertionBuilder, expected); + return does.Wrap(new EnumerableContainsAssertCondition(does.AssertionBuilder, expected)); } - public static AssertCondition Contain(this Is @is, string expected) + public static BaseAssertCondition Contain(this Does does, string expected) where TAnd : And, IAnd where TOr : Or, IOr { - return Contain(@is, expected, StringComparison.Ordinal); + return Contain(does, expected, StringComparison.Ordinal); } - public static AssertCondition Contain(this Is @is, string expected, StringComparison stringComparison) + public static BaseAssertCondition Contain(this Does does, string expected, StringComparison stringComparison) where TAnd : And, IAnd where TOr : Or, IOr { - return new StringContainsAssertCondition(@is.AssertionBuilder, expected, stringComparison); + return does.Wrap(new StringContainsAssertCondition(does.AssertionBuilder, expected, stringComparison)); + } + + public static BaseAssertCondition StartWith(this Does does, string expected) + where TAnd : And, IAnd + where TOr : Or, IOr + { + return StartWith(does, expected, StringComparison.Ordinal); + } + + public static BaseAssertCondition StartWith(this Does does, string expected, StringComparison stringComparison) + where TAnd : And, IAnd + where TOr : Or, IOr + { + return does.Wrap(new DelegateAssertCondition( + does.AssertionBuilder, + expected, + (actual, _, _) => + { + ArgumentNullException.ThrowIfNull(actual); + return actual.StartsWith(expected, stringComparison); + }, + (actual, _) => $"\"{actual}\" does not start with \"{expected}\"")); + } + + + public static BaseAssertCondition EndWith(this Does does, string expected) + where TAnd : And, IAnd + where TOr : Or, IOr + { + return EndWith(does, expected, StringComparison.Ordinal); + } + + public static BaseAssertCondition EndWith(this Does does, string expected, StringComparison stringComparison) + where TAnd : And, IAnd + where TOr : Or, IOr + { + return does.Wrap(new DelegateAssertCondition( + does.AssertionBuilder, + expected, + (actual, _, _) => + { + ArgumentNullException.ThrowIfNull(actual); + return actual.EndsWith(expected, stringComparison); + }, + (actual, _) => $"\"{actual}\" does not start with \"{expected}\"")); } } \ No newline at end of file diff --git a/TUnit.Assertions/IsNot.cs b/TUnit.Assertions/IsNot.cs index d50e78b125..b2483da088 100644 --- a/TUnit.Assertions/IsNot.cs +++ b/TUnit.Assertions/IsNot.cs @@ -16,11 +16,11 @@ public IsNot(AssertionBuilder assertionBuilder, ConnectorType connector } public BaseAssertCondition EqualTo(TActual expected) => Invert(new EqualsAssertCondition(AssertionBuilder, expected), - (actual, exception) => $"Expected {actual} to equal {expected}"); + (actual, _) => $"Expected {actual} to equal {expected}"); public BaseAssertCondition Null => Invert(new NullAssertCondition(AssertionBuilder), - (actual, exception) => $"Expected {actual} to be null"); + (actual, _) => $"Expected {actual} to be null"); public BaseAssertCondition TypeOf() => Invert(new TypeOfAssertCondition(AssertionBuilder), - (actual, exception) => $"Expected {actual} to not be of type {typeof(TExpected)}"); + (actual, _) => $"Expected {actual} to not be of type {typeof(TExpected)}"); } \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs b/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs index 7a78526f8f..68bef7b39c 100644 --- a/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs +++ b/TUnit.Pipeline/Modules/GetPackageProjectsModule.cs @@ -10,6 +10,13 @@ public class GetPackageProjectsModule : Module> { protected override Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) { - return context.Git().RootDirectory.GetFiles(x => x.Extension == ".csproj").Where(x => !x.Name.Contains("Pipeline")).Where(x => !x.Name.Contains("TestProject")).ToList().AsTask?>(); + return context.Git().RootDirectory + .GetFiles(x => x.Extension == ".csproj") + .Where(x => !x.Name.Contains("Pipeline")) + .Where(x => !x.Name.Contains("Sample")) + .Where(x => !x.Name.Contains("TestProject")) + .Where(x => !x.Name.Contains("Tests")) + .ToList() + .AsTask?>(); } } \ No newline at end of file From 3d410523069723143e2432320d403ab6d3440d59 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:22:55 +0000 Subject: [PATCH 104/124] Re-work ID matching --- TUnit.Core/TestDetails.cs | 26 +++++++------------ .../Extensions/TestExtensions.cs | 11 ++++---- TUnit.TestAdapter/TestCollector.cs | 22 +++++++++++++++- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index c1e969808f..df63e263d2 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -1,6 +1,4 @@ using System.Reflection; -using System.Security.Cryptography; -using System.Text; namespace TUnit.Core; @@ -30,6 +28,9 @@ public TestDetails(MethodInfo MethodInfo, var methodAndClassAttributes = MethodInfo.CustomAttributes .Concat(this.ClassType.CustomAttributes) .ToArray(); + + IsSingleTest = !methodAndClassAttributes.Any(x => x.AttributeType == typeof(TestWithDataAttribute) + || x.AttributeType == typeof(TestDataSourceAttribute)); SkipReason = methodAndClassAttributes .FirstOrDefault(x => x.AttributeType == typeof(SkipAttribute)) @@ -51,9 +52,11 @@ public TestDetails(MethodInfo MethodInfo, MinLineNumber = SourceLocation.MinLineNumber; MaxLineNumber = SourceLocation.MaxLineNumber; - Id = GenerateGuid(); + UniqueId = FullyQualifiedClassName + DisplayName + GetParameterTypes(ParameterTypes); } + public bool IsSingleTest { get; } + private void AddCategories(CustomAttributeData[] methodAndClassAttributes) { var categoryAttributes = methodAndClassAttributes @@ -84,17 +87,6 @@ private static TimeSpan GetTimeout(CustomAttributeData[] methodAndClassAttribute return TimeSpan.FromMilliseconds(timeoutMilliseconds.Value); } - private Guid GenerateGuid() - { - var bytes = Encoding.UTF8.GetBytes(DisplayName + MinLineNumber + new string(FullyQualifiedName.Reverse().ToArray())); - - var hashedBytes = SHA1.HashData(bytes); - - Array.Resize(ref hashedBytes, 16); - - return new Guid(hashedBytes); - } - public int RetryCount { get; } public int RepeatCount { get; } @@ -108,9 +100,9 @@ private string GetArgumentValues() return $"({string.Join(',', ArgumentValues.Select(StringifyArgument))})"; } - - public Guid Id { get; } - + + public string UniqueId { get; } + public string TestName { get; } public string ClassName { get; } diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index 643a59d1a5..13d100e9e8 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -11,21 +11,22 @@ public static TestCase ToTestCase(this TestDetails testDetails) var testCase = new TestCase(testDetails.FullyQualifiedName, TestAdapterConstants.ExecutorUri, testDetails.Source) { DisplayName = testDetails.DisplayName, - Id = testDetails.Id, CodeFilePath = testDetails.FileName, LineNumber = testDetails.MinLineNumber, LocalExtensionData = testDetails }; - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), testDetails.FullyQualifiedClassName); - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), testDetails.MethodInfo.Name + TestDetails.GetParameterTypes(testDetails.ParameterTypes)); + testCase.SetPropertyValue(GetOrRegisterTestProperty(nameof(TestDetails)), testDetails); + + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), testDetails.FullyQualifiedClassName); + testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), testDetails.MethodInfo.Name + TestDetails.GetParameterTypes(testDetails.ParameterTypes)); return testCase; } - private static TestProperty GetOrRegisterTestProperty(string name) + private static TestProperty GetOrRegisterTestProperty(string name) { return TestProperty.Find(name) - ?? TestProperty.Register(name, name, typeof(string), typeof(TestCase)); + ?? TestProperty.Register(name, name, typeof(T), typeof(TestCase)); } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index f4e89f0d5e..d21ea0804d 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -40,7 +40,7 @@ private IEnumerable TestWithTestCaseCore(TestCase[] testCasesA var tests = testsLoader.GetTests(new TypeInformation(assembly), allAssemblies); - var matchingTest = tests.FirstOrDefault(x => x.Id == testCase.Id); + var matchingTest = tests.FirstOrDefault(x => MatchTest(x, testCase)); if (matchingTest is null) { @@ -52,6 +52,26 @@ private IEnumerable TestWithTestCaseCore(TestCase[] testCasesA } } + private static bool MatchTest(TestDetails testDetails, TestCase testCase) + { + if (testDetails.IsSingleTest) + { + return testDetails.FullyQualifiedName == testCase.FullyQualifiedName + && testDetails.MinLineNumber == testCase.LineNumber; + } + + var testProperty = TestProperty.Find(nameof(TestDetails)); + + var embeddedTestDetails = testProperty is null ? null : testCase.GetPropertyValue(testProperty, null as TestDetails); + + if (embeddedTestDetails == null) + { + return false; + } + + return embeddedTestDetails.UniqueId == testDetails.UniqueId; + } + private void MarkNotFound(TestCase testCase) { var now = DateTimeOffset.Now; From 16ab883a41eaf841ccf3b52a7b4c742b88735023 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:32:45 +0000 Subject: [PATCH 105/124] Ability to run static tests --- TUnit.Engine/SingleTestExecutor.cs | 12 ++++++------ TUnit.Engine/TestClassCreator.cs | 7 ++++++- TUnit.TestProject/Tests.cs | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index c62ea37ba9..202cbb89d0 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -94,7 +94,7 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) try { - await ExecuteSetUps(@class); + await ExecuteSetUps(@class, testDetails.ClassType); var testLevelCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); @@ -145,11 +145,11 @@ private async Task ExecuteTestMethodWithTimeout(TestDetails testDetails, object await await Task.WhenAny(timeoutTask, methodResult); } - private async Task ExecuteSetUps(object @class) + private async Task ExecuteSetUps(object? @class, Type testDetailsClassType) { - await _oneTimeSetUpRegistry.GetOrAdd(@class.GetType().FullName!, _ => ExecuteOneTimeSetUps(@class)); + await _oneTimeSetUpRegistry.GetOrAdd(testDetailsClassType.FullName!, _ => ExecuteOneTimeSetUps(@class, testDetailsClassType)); - var setUpMethods = @class.GetType() + var setUpMethods = testDetailsClassType .GetMethods() .Where(x => !x.IsStatic) .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(SetUpAttribute))); @@ -187,9 +187,9 @@ private async Task ExecuteCleanUps(object @class) } } - private async Task ExecuteOneTimeSetUps(object @class) + private async Task ExecuteOneTimeSetUps(object? @class, Type testDetailsClassType) { - var oneTimeSetUpMethods = @class.GetType() + var oneTimeSetUpMethods = testDetailsClassType .GetMethods() .Where(x => x.IsStatic) .Where(x => x.CustomAttributes.Any(attributeData => attributeData.AttributeType == typeof(OneTimeSetUpAttribute))); diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs index d1760567ac..8b68f5253a 100644 --- a/TUnit.Engine/TestClassCreator.cs +++ b/TUnit.Engine/TestClassCreator.cs @@ -5,8 +5,13 @@ namespace TUnit.Engine; public class TestClassCreator(TestDataSourceRetriever testDataSourceRetriever) { - public object CreateTestClass(TestDetails testDetails, Type[] allClasses) + public object? CreateTestClass(TestDetails testDetails, Type[] allClasses) { + if (testDetails.MethodInfo.IsStatic) + { + return null; + } + if (testDetails.ClassType.HasAttribute(out var testDataSourceAttributes)) { return CreateWithTestDataSources(testDetails, testDataSourceAttributes, allClasses); diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index df0e35667a..615592444c 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -44,7 +44,7 @@ public async Task Test4() [TestCategory("Fail")] public async Task ParameterisedTests1(string value) { - await Assert.That(value).Is.EqualTo("1"); + await Assert.That(value).Is.EqualTo("1").And.Has.Length().EqualTo(1); } [TestWithData("1")] From ad3bebf73fbab12cde773995e3591a55f0fb9c8d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:35:18 +0000 Subject: [PATCH 106/124] Fix unique ID --- TUnit.TestAdapter/Extensions/TestExtensions.cs | 2 +- TUnit.TestAdapter/TestCollector.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index 13d100e9e8..a1dc066ef2 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -16,7 +16,7 @@ public static TestCase ToTestCase(this TestDetails testDetails) LocalExtensionData = testDetails }; - testCase.SetPropertyValue(GetOrRegisterTestProperty(nameof(TestDetails)), testDetails); + testCase.SetPropertyValue(GetOrRegisterTestProperty(nameof(TestDetails.UniqueId)), testDetails.UniqueId); testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), testDetails.FullyQualifiedClassName); testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), testDetails.MethodInfo.Name + TestDetails.GetParameterTypes(testDetails.ParameterTypes)); diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index d21ea0804d..b75a9f62e7 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -60,16 +60,16 @@ private static bool MatchTest(TestDetails testDetails, TestCase testCase) && testDetails.MinLineNumber == testCase.LineNumber; } - var testProperty = TestProperty.Find(nameof(TestDetails)); + var testProperty = TestProperty.Find(nameof(TestDetails.UniqueId)); - var embeddedTestDetails = testProperty is null ? null : testCase.GetPropertyValue(testProperty, null as TestDetails); + var uniqueId = testProperty is null ? null : testCase.GetPropertyValue(testProperty, null as string); - if (embeddedTestDetails == null) + if (uniqueId == null) { return false; } - return embeddedTestDetails.UniqueId == testDetails.UniqueId; + return uniqueId == testDetails.UniqueId; } private void MarkNotFound(TestCase testCase) From 28882a874edeef8bd1d86d4e43778a7f008c923e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:41:28 +0000 Subject: [PATCH 107/124] TUnitTestProperties --- TUnit.TestAdapter/Extensions/TestExtensions.cs | 12 +++--------- TUnit.TestAdapter/TUnitTestProperties.cs | 17 +++++++++++++++++ TUnit.TestAdapter/TestCollector.cs | 4 +--- 3 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 TUnit.TestAdapter/TUnitTestProperties.cs diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index a1dc066ef2..c3702f10e9 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -16,17 +16,11 @@ public static TestCase ToTestCase(this TestDetails testDetails) LocalExtensionData = testDetails }; - testCase.SetPropertyValue(GetOrRegisterTestProperty(nameof(TestDetails.UniqueId)), testDetails.UniqueId); + testCase.SetPropertyValue(TUnitTestProperties.UniqueId, testDetails.UniqueId); - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedType"), testDetails.FullyQualifiedClassName); - testCase.SetPropertyValue(GetOrRegisterTestProperty("ManagedMethod"), testDetails.MethodInfo.Name + TestDetails.GetParameterTypes(testDetails.ParameterTypes)); + testCase.SetPropertyValue(TUnitTestProperties.ManagedType, testDetails.FullyQualifiedClassName); + testCase.SetPropertyValue(TUnitTestProperties.ManagedMethod, testDetails.MethodInfo.Name + TestDetails.GetParameterTypes(testDetails.ParameterTypes)); return testCase; } - - private static TestProperty GetOrRegisterTestProperty(string name) - { - return TestProperty.Find(name) - ?? TestProperty.Register(name, name, typeof(T), typeof(TestCase)); - } } \ No newline at end of file diff --git a/TUnit.TestAdapter/TUnitTestProperties.cs b/TUnit.TestAdapter/TUnitTestProperties.cs new file mode 100644 index 0000000000..a86083ce77 --- /dev/null +++ b/TUnit.TestAdapter/TUnitTestProperties.cs @@ -0,0 +1,17 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TUnit.Core; + +namespace TUnit.TestAdapter; + +public static class TUnitTestProperties +{ + public static TestProperty GetOrRegisterTestProperty(string name) + { + return TestProperty.Find(name) + ?? TestProperty.Register(name, name, typeof(T), typeof(TestCase)); + } + + public static TestProperty UniqueId => GetOrRegisterTestProperty(nameof(TestDetails.UniqueId)); + public static TestProperty ManagedType => GetOrRegisterTestProperty("ManagedType"); + public static TestProperty ManagedMethod => GetOrRegisterTestProperty("ManagedMethod"); +} \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index b75a9f62e7..7296f659d2 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -60,9 +60,7 @@ private static bool MatchTest(TestDetails testDetails, TestCase testCase) && testDetails.MinLineNumber == testCase.LineNumber; } - var testProperty = TestProperty.Find(nameof(TestDetails.UniqueId)); - - var uniqueId = testProperty is null ? null : testCase.GetPropertyValue(testProperty, null as string); + var uniqueId = testCase.GetPropertyValue(TUnitTestProperties.UniqueId, null as string); if (uniqueId == null) { From 6886370958943a34f746bd2bcbd76a1327eaf6a5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:44:45 +0000 Subject: [PATCH 108/124] Tweak --- TUnit.Engine/Disposer.cs | 2 +- TUnit.Engine/SingleTestExecutor.cs | 9 +++++++-- TUnit.TestAdapter/TestCollector.cs | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/TUnit.Engine/Disposer.cs b/TUnit.Engine/Disposer.cs index 74c243b008..b1bb9060da 100644 --- a/TUnit.Engine/Disposer.cs +++ b/TUnit.Engine/Disposer.cs @@ -2,7 +2,7 @@ public class Disposer { - public ValueTask DisposeAsync(object obj) + public ValueTask DisposeAsync(object? obj) { if (obj is IAsyncDisposable asyncDisposable) { diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 202cbb89d0..698fcd72c3 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -127,7 +127,7 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) } } - private async Task ExecuteTestMethodWithTimeout(TestDetails testDetails, object @class, + private async Task ExecuteTestMethodWithTimeout(TestDetails testDetails, object? @class, CancellationTokenSource cancellationTokenSource) { var methodResult = _methodInvoker.InvokeMethod(@class, testDetails.MethodInfo, BindingFlags.Default, @@ -160,8 +160,13 @@ private async Task ExecuteSetUps(object? @class, Type testDetailsClassType) } } - private async Task ExecuteCleanUps(object @class) + private async Task ExecuteCleanUps(object? @class) { + if (@class is null) + { + return; + } + var cleanUpMethods = @class.GetType() .GetMethods() .Where(x => !x.IsStatic) diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 7296f659d2..73d886f2cc 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -64,7 +64,9 @@ private static bool MatchTest(TestDetails testDetails, TestCase testCase) if (uniqueId == null) { - return false; + return testDetails.FullyQualifiedName == testCase.FullyQualifiedName + && testDetails.MinLineNumber == testCase.LineNumber + && testDetails.DisplayName == testCase.DisplayName; } return uniqueId == testDetails.UniqueId; From bd90af5d100586de4eefc7424841676b7f498891 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:56:34 +0000 Subject: [PATCH 109/124] Tweak display name --- TUnit.Core/TestDetails.cs | 54 ++++++++++++++++++-------------- TUnit.TestAdapter/TestsLoader.cs | 31 +++++++++++------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index df63e263d2..060a809c6c 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -4,29 +4,31 @@ namespace TUnit.Core; public record TestDetails { - public TestDetails(MethodInfo MethodInfo, - Type ClassType, - SourceLocation SourceLocation, - ParameterArgument[]? arguments) + public TestDetails(MethodInfo methodInfo, + Type classType, + SourceLocation sourceLocation, + ParameterArgument[]? arguments, + int count) { - this.MethodInfo = MethodInfo; - this.ClassType = ClassType; - this.SourceLocation = SourceLocation; - + MethodInfo = methodInfo; + ClassType = classType; + SourceLocation = sourceLocation; + Count = count; + ParameterTypes = arguments?.Select(x => x.Type).ToArray(); ArgumentValues = arguments?.Select(x => x.Value).ToArray(); - TestName = MethodInfo.Name; - SimpleMethodName = MethodInfo.Name; - DisplayName = MethodInfo.Name + GetArgumentValues(); - ClassName = this.ClassType.Name; - FullyQualifiedClassName = this.ClassType.FullName!; - Assembly = this.ClassType.Assembly; - Source = this.ClassType.Assembly.Location; - FullyQualifiedName = $"{this.ClassType.FullName}.{MethodInfo.Name}{GetParameterTypes(ParameterTypes)}"; - - var methodAndClassAttributes = MethodInfo.CustomAttributes - .Concat(this.ClassType.CustomAttributes) + TestName = methodInfo.Name; + SimpleMethodName = methodInfo.Name; + DisplayName = methodInfo.Name + GetCount() + GetArgumentValues(); + ClassName = ClassType.Name; + FullyQualifiedClassName = ClassType.FullName!; + Assembly = ClassType.Assembly; + Source = ClassType.Assembly.Location; + FullyQualifiedName = $"{ClassType.FullName}.{methodInfo.Name}{GetParameterTypes(ParameterTypes)}"; + + var methodAndClassAttributes = methodInfo.CustomAttributes + .Concat(ClassType.CustomAttributes) .ToArray(); IsSingleTest = !methodAndClassAttributes.Any(x => x.AttributeType == typeof(TestWithDataAttribute) @@ -48,13 +50,18 @@ public TestDetails(MethodInfo MethodInfo, Timeout = GetTimeout(methodAndClassAttributes); - FileName = SourceLocation.FileName; - MinLineNumber = SourceLocation.MinLineNumber; - MaxLineNumber = SourceLocation.MaxLineNumber; + FileName = sourceLocation.FileName; + MinLineNumber = sourceLocation.MinLineNumber; + MaxLineNumber = sourceLocation.MaxLineNumber; UniqueId = FullyQualifiedClassName + DisplayName + GetParameterTypes(ParameterTypes); } + private string GetCount() + { + return Count == 1 ? string.Empty : Count.ToString(); + } + public bool IsSingleTest { get; } private void AddCategories(CustomAttributeData[] methodAndClassAttributes) @@ -126,7 +133,8 @@ private string GetArgumentValues() public Type[]? ParameterTypes { get; } public object?[]? ArgumentValues { get; } public SourceLocation SourceLocation { get; } - + public int Count { get; } + public string? SkipReason { get; } public bool IsSkipped => !string.IsNullOrEmpty(SkipReason); public string DisplayName { get; } diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 4196f4efc3..04aa16ba7a 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -27,8 +27,11 @@ public IEnumerable GetTests(TypeInformation typeInformation, Assemb .Where(t => t.IsAssignableTo(methodInfo.DeclaringType!) && !t.IsAbstract) .ToArray(); + var count = 0; + foreach (var testWithDataAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestWithDataAttribute))) { + count++; foreach (var customAttributeTypedArgument in testWithDataAttribute.ConstructorArguments) { var arguments = @@ -39,10 +42,11 @@ public IEnumerable GetTests(TypeInformation typeInformation, Assemb foreach (var classType in nonAbstractClassesContainingTest) { yield return new TestDetails( - MethodInfo: methodInfo, - ClassType: classType, - SourceLocation: sourceLocation, - arguments: arguments + methodInfo: methodInfo, + classType: classType, + sourceLocation: sourceLocation, + arguments: arguments, + count: count ); } } @@ -53,23 +57,26 @@ public IEnumerable GetTests(TypeInformation typeInformation, Assemb foreach (var classType in nonAbstractClassesContainingTest) { yield return new TestDetails( - MethodInfo: methodInfo, - ClassType: classType, - SourceLocation: sourceLocation, - arguments: null + methodInfo: methodInfo, + classType: classType, + sourceLocation: sourceLocation, + arguments: null, + count: 1 ); } } foreach (var testDataSourceAttribute in methodInfo.CustomAttributes.Where(x => x.AttributeType == typeof(TestDataSourceAttribute))) { + count++; foreach (var classType in nonAbstractClassesContainingTest) { yield return new TestDetails( - MethodInfo: methodInfo, - ClassType: classType, - SourceLocation: sourceLocation, - arguments: testDataSourceRetriever.GetTestDataSourceArguments(methodInfo, testDataSourceAttribute, allClasses) + methodInfo: methodInfo, + classType: classType, + sourceLocation: sourceLocation, + arguments: testDataSourceRetriever.GetTestDataSourceArguments(methodInfo, testDataSourceAttribute, allClasses), + count: count ); } } From 279dc33f085cd6753db2df700bc3a967e89b21e7 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:58:17 +0000 Subject: [PATCH 110/124] Unique ID --- TUnit.Core/TestDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 060a809c6c..6912e1189d 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -54,7 +54,7 @@ public TestDetails(MethodInfo methodInfo, MinLineNumber = sourceLocation.MinLineNumber; MaxLineNumber = sourceLocation.MaxLineNumber; - UniqueId = FullyQualifiedClassName + DisplayName + GetParameterTypes(ParameterTypes); + UniqueId = FullyQualifiedClassName + DisplayName + Count + GetParameterTypes(ParameterTypes); } private string GetCount() From a8b5654c56c813ac68293af5da7f6f91f621bbea Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 15:02:24 +0000 Subject: [PATCH 111/124] Tweak count display name --- TUnit.Core/TestDetails.cs | 6 +++--- TUnit.TestAdapter/Extensions/TestExtensions.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 6912e1189d..a1da8221c1 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -20,7 +20,7 @@ public TestDetails(MethodInfo methodInfo, TestName = methodInfo.Name; SimpleMethodName = methodInfo.Name; - DisplayName = methodInfo.Name + GetCount() + GetArgumentValues(); + DisplayName = methodInfo.Name + GetArgumentValues() + GetCountInBrackets(); ClassName = ClassType.Name; FullyQualifiedClassName = ClassType.FullName!; Assembly = ClassType.Assembly; @@ -57,9 +57,9 @@ public TestDetails(MethodInfo methodInfo, UniqueId = FullyQualifiedClassName + DisplayName + Count + GetParameterTypes(ParameterTypes); } - private string GetCount() + private string GetCountInBrackets() { - return Count == 1 ? string.Empty : Count.ToString(); + return Count == 1 ? string.Empty : $" [{Count}]"; } public bool IsSingleTest { get; } diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index c3702f10e9..7f7024c80f 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -8,7 +8,7 @@ public static class TestExtensions { public static TestCase ToTestCase(this TestDetails testDetails) { - var testCase = new TestCase(testDetails.FullyQualifiedName, TestAdapterConstants.ExecutorUri, testDetails.Source) + var testCase = new TestCase(testDetails.UniqueId, TestAdapterConstants.ExecutorUri, testDetails.Source) { DisplayName = testDetails.DisplayName, CodeFilePath = testDetails.FileName, From 9c951bf59ea3667ed5343d4e1960da7d72f21f65 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 15:20:35 +0000 Subject: [PATCH 112/124] Fix UniqueId usage --- TUnit.Core/TestDetails.cs | 2 -- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 2 +- TUnit.TestAdapter/TestCollector.cs | 4 ++-- TUnit.TestAdapter/TestDiscoverer.cs | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index a1da8221c1..148acbdd04 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -25,7 +25,6 @@ public TestDetails(MethodInfo methodInfo, FullyQualifiedClassName = ClassType.FullName!; Assembly = ClassType.Assembly; Source = ClassType.Assembly.Location; - FullyQualifiedName = $"{ClassType.FullName}.{methodInfo.Name}{GetParameterTypes(ParameterTypes)}"; var methodAndClassAttributes = methodInfo.CustomAttributes .Concat(ClassType.CustomAttributes) @@ -119,7 +118,6 @@ private string GetArgumentValues() public Assembly Assembly { get; } public string Source { get; } - public string FullyQualifiedName { get; } public MethodInfo MethodInfo { get; } public Type ClassType { get; } public string? FileName { get; } diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index e87e16f690..7e8be4006b 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -102,7 +102,7 @@ private void SetupRunOneTimeCleanUpForClass(TestDetails processingTestDetails, var lastTestForClass = allTestsOrderedByClass.Last(x => x.Details.FullyQualifiedClassName == processingTestDetails.FullyQualifiedClassName); - if (processingTestDetails.FullyQualifiedName != lastTestForClass.Details.FullyQualifiedName) + if (processingTestDetails.UniqueId != lastTestForClass.Details.UniqueId) { return; } diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index 73d886f2cc..f53933900e 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -56,7 +56,7 @@ private static bool MatchTest(TestDetails testDetails, TestCase testCase) { if (testDetails.IsSingleTest) { - return testDetails.FullyQualifiedName == testCase.FullyQualifiedName + return testDetails.UniqueId == testCase.FullyQualifiedName && testDetails.MinLineNumber == testCase.LineNumber; } @@ -64,7 +64,7 @@ private static bool MatchTest(TestDetails testDetails, TestCase testCase) if (uniqueId == null) { - return testDetails.FullyQualifiedName == testCase.FullyQualifiedName + return testDetails.UniqueId == testCase.FullyQualifiedName && testDetails.MinLineNumber == testCase.LineNumber && testDetails.DisplayName == testCase.DisplayName; } diff --git a/TUnit.TestAdapter/TestDiscoverer.cs b/TUnit.TestAdapter/TestDiscoverer.cs index 8a9a1bce6c..924a3f8532 100644 --- a/TUnit.TestAdapter/TestDiscoverer.cs +++ b/TUnit.TestAdapter/TestDiscoverer.cs @@ -27,7 +27,7 @@ public void DiscoverTests(IEnumerable sources, foreach (var test in assembliesAndTests.Values) { - logger.SendMessage(TestMessageLevel.Informational, "Test found: " + test.FullyQualifiedName); + logger.SendMessage(TestMessageLevel.Informational, "Test found: " + test.DisplayName); discoverySink.SendTestCase(test.ToTestCase()); } } From 49b47a15880f09bf443633a0d47cbf0fec0826a5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 15:23:42 +0000 Subject: [PATCH 113/124] Tweak MatchTest logic --- TUnit.TestAdapter/TestCollector.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index f53933900e..e83eaa89fa 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -54,22 +54,7 @@ private IEnumerable TestWithTestCaseCore(TestCase[] testCasesA private static bool MatchTest(TestDetails testDetails, TestCase testCase) { - if (testDetails.IsSingleTest) - { - return testDetails.UniqueId == testCase.FullyQualifiedName - && testDetails.MinLineNumber == testCase.LineNumber; - } - - var uniqueId = testCase.GetPropertyValue(TUnitTestProperties.UniqueId, null as string); - - if (uniqueId == null) - { - return testDetails.UniqueId == testCase.FullyQualifiedName - && testDetails.MinLineNumber == testCase.LineNumber - && testDetails.DisplayName == testCase.DisplayName; - } - - return uniqueId == testDetails.UniqueId; + return testDetails.UniqueId == testCase.FullyQualifiedName; } private void MarkNotFound(TestCase testCase) From 286cc7d510b581b7d152d3d101ff67b1b24a8ac9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 16:08:59 +0000 Subject: [PATCH 114/124] Internal classes --- TUnit.Core/Exceptions/TimeoutException.cs | 2 +- TUnit.Core/Properties/AssemblyInfo.cs | 5 -- TUnit.Core/TUnitTestResult.cs | 2 +- TUnit.Core/TestCollection.cs | 38 ------------ TUnit.Core/TestContext.cs | 28 ++++++--- TUnit.Core/TestDetails.cs | 7 +-- TUnit.Core/TestInformation.cs | 25 ++++++++ TUnit.Engine/Properties/AssemblyInfo.cs | 5 -- TUnit.Engine/SingleTestExecutor.cs | 59 ++++++++++--------- TUnit.Engine/TestClassCreator.cs | 13 ++-- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 2 +- .../Extensions/TestExtensions.cs | 2 +- TUnit.TestAdapter/Properties/AssemblyInfo.cs | 6 -- TUnit.TestAdapter/TUnitTestFilterProvider.cs | 4 +- TUnit.TestAdapter/TestAndClass.cs | 2 +- TUnit.TestAdapter/TestCollector.cs | 9 +-- TUnit.TestAdapter/TestWithResult.cs | 2 +- TUnit.TestAdapter/TestWithTestCase.cs | 2 +- TUnit.TestAdapter/TestsLoader.cs | 2 +- TUnit.TestProject/Tests.cs | 11 +++- 20 files changed, 107 insertions(+), 119 deletions(-) delete mode 100644 TUnit.Core/Properties/AssemblyInfo.cs delete mode 100644 TUnit.Core/TestCollection.cs create mode 100644 TUnit.Core/TestInformation.cs delete mode 100644 TUnit.Engine/Properties/AssemblyInfo.cs delete mode 100644 TUnit.TestAdapter/Properties/AssemblyInfo.cs diff --git a/TUnit.Core/Exceptions/TimeoutException.cs b/TUnit.Core/Exceptions/TimeoutException.cs index b8fc0bc653..4a054476d6 100644 --- a/TUnit.Core/Exceptions/TimeoutException.cs +++ b/TUnit.Core/Exceptions/TimeoutException.cs @@ -2,7 +2,7 @@ public class TimeoutException : TUnitException { - public TimeoutException(TestDetails testDetails) : base(GetMessage(testDetails)) + internal TimeoutException(TestDetails testDetails) : base(GetMessage(testDetails)) { } diff --git a/TUnit.Core/Properties/AssemblyInfo.cs b/TUnit.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index ebd2d1a856..0000000000 --- a/TUnit.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("TUnit.Core")] -[assembly: InternalsVisibleTo("TUnit.Engine")] -[assembly: InternalsVisibleTo("TUnit.TestAdapter")] \ No newline at end of file diff --git a/TUnit.Core/TUnitTestResult.cs b/TUnit.Core/TUnitTestResult.cs index 538b7c8b3c..2940c00453 100644 --- a/TUnit.Core/TUnitTestResult.cs +++ b/TUnit.Core/TUnitTestResult.cs @@ -1,6 +1,6 @@ namespace TUnit.Core; -public record TUnitTestResult +internal record TUnitTestResult { public required TestDetails TestDetails { get; init; } public required Status Status { get; init; } diff --git a/TUnit.Core/TestCollection.cs b/TUnit.Core/TestCollection.cs deleted file mode 100644 index b7a4acfa1a..0000000000 --- a/TUnit.Core/TestCollection.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Immutable; - -namespace TUnit.Core; - -/// -/// Contains a collection of tests that can be run. Should be disposed to ensure that the -/// temporary containing the test assemblies is unloaded. -/// -public sealed class TestCollection -{ - private readonly AssembliesAnd _assembliesAndTestDetails; - - /// - /// The test sources (assembly file names). - /// - public IReadOnlyList Sources { get; } - - /// - /// The tests that were discovered. - /// - public IReadOnlyList Tests { get; private set; } - - public TestCollection(IEnumerable sources, AssembliesAnd assembliesAndTestDetails) - { - _assembliesAndTestDetails = assembliesAndTestDetails; - Sources = ImmutableArray.CreateRange(sources); - Tests = ImmutableArray.CreateRange(assembliesAndTestDetails.Values); - } - - /// - /// Filters the tests in the test collection. This is used for partial test runs. - /// - /// The filter to apply. - public void Filter(Func filter) - { - Tests = ImmutableArray.CreateRange(Tests.Where(filter)); - } -} \ No newline at end of file diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs index ccdfcd222c..9b20406eec 100644 --- a/TUnit.Core/TestContext.cs +++ b/TUnit.Core/TestContext.cs @@ -1,14 +1,26 @@ -using System.Collections.Concurrent; - -namespace TUnit.Core; +namespace TUnit.Core; public class TestContext { - private static readonly AsyncLocal _asyncLocal = new(); - private static readonly ConcurrentDictionary _contexts = new(); - public static TestDetails Current + private readonly TestDetails _testDetails; + private readonly object? _classInstance; + + private static readonly AsyncLocal AsyncLocal = new(); + + public TestInformation TestInformation { get; } + + internal TestContext(TestDetails testDetails, object? classInstance) + { + _testDetails = testDetails; + _classInstance = classInstance; + TestInformation = new(_testDetails, _classInstance); + } + + public static TestContext Current { - get => _asyncLocal.Value!; - set => _asyncLocal.Value = value; + get => AsyncLocal.Value!; + set => AsyncLocal.Value = value; } + + public bool HasFailed { get; init; } } \ No newline at end of file diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 148acbdd04..309c3c1e60 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -2,7 +2,7 @@ namespace TUnit.Core; -public record TestDetails +internal record TestDetails { public TestDetails(MethodInfo methodInfo, Type classType, @@ -19,7 +19,6 @@ public TestDetails(MethodInfo methodInfo, ArgumentValues = arguments?.Select(x => x.Value).ToArray(); TestName = methodInfo.Name; - SimpleMethodName = methodInfo.Name; DisplayName = methodInfo.Name + GetArgumentValues() + GetCountInBrackets(); ClassName = ClassType.Name; FullyQualifiedClassName = ClassType.FullName!; @@ -76,9 +75,7 @@ private void AddCategories(CustomAttributeData[] methodAndClassAttributes) } public List Categories { get; } = new(); - - public string SimpleMethodName { get; set; } - + private static TimeSpan GetTimeout(CustomAttributeData[] methodAndClassAttributes) { var timeoutMilliseconds = methodAndClassAttributes diff --git a/TUnit.Core/TestInformation.cs b/TUnit.Core/TestInformation.cs new file mode 100644 index 0000000000..203376c08e --- /dev/null +++ b/TUnit.Core/TestInformation.cs @@ -0,0 +1,25 @@ +namespace TUnit.Core; + +public record TestInformation +{ + private readonly TestDetails _testDetails; + + internal TestInformation(TestDetails testDetails, object? classInstance) + { + _testDetails = testDetails; + ClassInstance = classInstance; + } + + public string TestName => _testDetails.TestName; + + public object?[]? TestArguments => _testDetails.ArgumentValues; + + public List Categories => _testDetails.Categories; + + public Type ClassType => _testDetails.ClassType; + public object? ClassInstance { get; } + + public int RepeatCount => _testDetails.RepeatCount; + public int RetryCount => _testDetails.RetryCount; + public int CurrentExecutionCount => _testDetails.CurrentExecutionCount; +} \ No newline at end of file diff --git a/TUnit.Engine/Properties/AssemblyInfo.cs b/TUnit.Engine/Properties/AssemblyInfo.cs deleted file mode 100644 index ebd2d1a856..0000000000 --- a/TUnit.Engine/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("TUnit.Core")] -[assembly: InternalsVisibleTo("TUnit.Engine")] -[assembly: InternalsVisibleTo("TUnit.TestAdapter")] \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 698fcd72c3..b01527cc76 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -5,7 +5,7 @@ namespace TUnit.Engine; -public class SingleTestExecutor +internal class SingleTestExecutor { private readonly MethodInvoker _methodInvoker; private readonly TestClassCreator _testClassCreator; @@ -47,7 +47,6 @@ public async Task ExecuteTest(TestDetails testDetails, Type[] a { await Task.Run(async () => { - TestContext.Current = testDetails; await ExecuteCore(testDetails, allClasses); }); @@ -88,42 +87,48 @@ private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) for (var i = 0; i < executionCount + 1; i++) { - TestContext.Current.CurrentExecutionCount++; - - var @class = _testClassCreator.CreateTestClass(testDetails, allClasses); - - try - { - await ExecuteSetUps(@class, testDetails.ClassType); + testDetails.CurrentExecutionCount++; - var testLevelCancellationTokenSource = - CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); + var createdClasses = _testClassCreator.CreateTestClass(testDetails, allClasses); - if (testDetails.Timeout != default) + foreach (var @class in createdClasses) + { + var context = new TestContext(testDetails, @class); + TestContext.Current = context; + + try { - testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); - } + await ExecuteSetUps(@class, testDetails.ClassType); - await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); + var testLevelCancellationTokenSource = + CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); - await ExecuteCleanUps(@class); + if (testDetails.Timeout != default) + { + testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); + } - if (isRetry) + await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); + + await ExecuteCleanUps(@class); + + if (isRetry) + { + break; + } + } + catch { - break; + if (!isRetry || i == executionCount) + { + throw; + } } - } - catch - { - if (!isRetry || i == executionCount) + finally { - throw; + await _disposer.DisposeAsync(@class); } } - finally - { - await _disposer.DisposeAsync(@class); - } } } diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs index 8b68f5253a..87a8c12a04 100644 --- a/TUnit.Engine/TestClassCreator.cs +++ b/TUnit.Engine/TestClassCreator.cs @@ -3,21 +3,24 @@ namespace TUnit.Engine; -public class TestClassCreator(TestDataSourceRetriever testDataSourceRetriever) +internal class TestClassCreator(TestDataSourceRetriever testDataSourceRetriever) { - public object? CreateTestClass(TestDetails testDetails, Type[] allClasses) + public IEnumerable CreateTestClass(TestDetails testDetails, Type[] allClasses) { if (testDetails.MethodInfo.IsStatic) { - return null; + yield break; } if (testDetails.ClassType.HasAttribute(out var testDataSourceAttributes)) { - return CreateWithTestDataSources(testDetails, testDataSourceAttributes, allClasses); + foreach (var withTestDataSource in CreateWithTestDataSources(testDetails, testDataSourceAttributes, allClasses)) + { + yield return withTestDataSource; + } } - return CreateBasicClass(testDetails); + yield return CreateBasicClass(testDetails); } private IEnumerable CreateWithTestDataSources(TestDetails testDetails, diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index 7e8be4006b..e8e1e4caf0 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -9,7 +9,7 @@ namespace TUnit.TestAdapter; -public class AsyncTestRunExecutor +internal class AsyncTestRunExecutor ( SingleTestExecutor singleTestExecutor, MethodInvoker methodInvoker, diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index 7f7024c80f..877fee67d2 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -4,7 +4,7 @@ namespace TUnit.TestAdapter.Extensions; -public static class TestExtensions +internal static class TestExtensions { public static TestCase ToTestCase(this TestDetails testDetails) { diff --git a/TUnit.TestAdapter/Properties/AssemblyInfo.cs b/TUnit.TestAdapter/Properties/AssemblyInfo.cs deleted file mode 100644 index 66b03104d8..0000000000 --- a/TUnit.TestAdapter/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("TUnit.Core")] -[assembly: InternalsVisibleTo("TUnit.Engine")] -[assembly: InternalsVisibleTo("TUnit.TestAdapter")] - diff --git a/TUnit.TestAdapter/TUnitTestFilterProvider.cs b/TUnit.TestAdapter/TUnitTestFilterProvider.cs index 422465842a..f37adfc26c 100644 --- a/TUnit.TestAdapter/TUnitTestFilterProvider.cs +++ b/TUnit.TestAdapter/TUnitTestFilterProvider.cs @@ -5,7 +5,7 @@ namespace TUnit.TestAdapter; -public class TUnitTestFilterProvider(IRunContext runContext, IMessageLogger messageLogger) +internal class TUnitTestFilterProvider(IRunContext runContext, IMessageLogger messageLogger) { public IEnumerable FilterTests(IEnumerable tests) { @@ -86,7 +86,7 @@ private bool TestMatchesFilter(TestDetails test, Filter filter) private static bool AllowedTestName(TestDetails test, Filter filter) { return !filter.RunnableTestNames.Any() || - filter.RunnableTestNames.Contains(test.SimpleMethodName, StringComparer.InvariantCultureIgnoreCase); + filter.RunnableTestNames.Contains(test.TestName, StringComparer.InvariantCultureIgnoreCase); } private static bool AllowedCategory(TestDetails test, Filter filter) diff --git a/TUnit.TestAdapter/TestAndClass.cs b/TUnit.TestAdapter/TestAndClass.cs index 15231f981f..5c4851fe19 100644 --- a/TUnit.TestAdapter/TestAndClass.cs +++ b/TUnit.TestAdapter/TestAndClass.cs @@ -2,4 +2,4 @@ namespace TUnit.TestAdapter; -public record TestAndClass(TestDetails TestDetails, object Class); \ No newline at end of file +internal record TestAndClass(TestDetails TestDetails, object Class); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestCollector.cs b/TUnit.TestAdapter/TestCollector.cs index e83eaa89fa..81eb283f0f 100644 --- a/TUnit.TestAdapter/TestCollector.cs +++ b/TUnit.TestAdapter/TestCollector.cs @@ -5,15 +5,8 @@ namespace TUnit.TestAdapter; -public class TestCollector(AssemblyLoader assemblyLoader, TestsLoader testsLoader, ITestExecutionRecorder testExecutionRecorder) +internal class TestCollector(AssemblyLoader assemblyLoader, TestsLoader testsLoader, ITestExecutionRecorder testExecutionRecorder) { - public TestCollection CollectionFromSources(IEnumerable sources) - { - var sourcesAsList = sources.ToList(); - - return new TestCollection(sourcesAsList, TestsFromSources(sourcesAsList)); - } - public AssembliesAnd TestsFromTestCases(IEnumerable testCases) { var testCasesArray = testCases.ToArray(); diff --git a/TUnit.TestAdapter/TestWithResult.cs b/TUnit.TestAdapter/TestWithResult.cs index 444091c88f..16b5a5b216 100644 --- a/TUnit.TestAdapter/TestWithResult.cs +++ b/TUnit.TestAdapter/TestWithResult.cs @@ -2,4 +2,4 @@ namespace TUnit.TestAdapter; -public record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file +internal record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestWithTestCase.cs b/TUnit.TestAdapter/TestWithTestCase.cs index 411781a35a..621205e1d9 100644 --- a/TUnit.TestAdapter/TestWithTestCase.cs +++ b/TUnit.TestAdapter/TestWithTestCase.cs @@ -3,4 +3,4 @@ namespace TUnit.TestAdapter; -public record TestWithTestCase(TestDetails Details, TestCase TestCase); \ No newline at end of file +internal record TestWithTestCase(TestDetails Details, TestCase TestCase); \ No newline at end of file diff --git a/TUnit.TestAdapter/TestsLoader.cs b/TUnit.TestAdapter/TestsLoader.cs index 04aa16ba7a..eac0130132 100644 --- a/TUnit.TestAdapter/TestsLoader.cs +++ b/TUnit.TestAdapter/TestsLoader.cs @@ -4,7 +4,7 @@ namespace TUnit.TestAdapter; -public class TestsLoader(SourceLocationHelper sourceLocationHelper, ClassLoader classLoader, TestDataSourceRetriever testDataSourceRetriever) +internal class TestsLoader(SourceLocationHelper sourceLocationHelper, ClassLoader classLoader, TestDataSourceRetriever testDataSourceRetriever) { private static readonly Type[] TestAttributes = [typeof(TestAttribute), typeof(TestWithDataAttribute), typeof(TestDataSourceAttribute)]; diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 615592444c..896fd20708 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -5,6 +5,13 @@ namespace TUnit.TestProject; public class Tests { + [Test] + [TestCategory("Pass")] + public void ConsoleOutput() + { + Console.WriteLine("Blah!"); + } + [Test] [TestCategory("Pass")] public async Task Test1() @@ -137,14 +144,14 @@ public async Task TestDataSource8(int value) [TestCategory("Pass")] public async Task TestContext1() { - await Assert.That(TestContext.Current.TestName).Is.EqualTo(nameof(TestContext1)); + await Assert.That(TestContext.Current.TestInformation.TestName).Is.EqualTo(nameof(TestContext1)); } [Test] [TestCategory("Fail")] public async Task TestContext2() { - await Assert.That(TestContext.Current.TestName).Is.EqualTo(nameof(TestContext1)); + await Assert.That(TestContext.Current.TestInformation.TestName).Is.EqualTo(nameof(TestContext1)); } [Test] From 2c2b8b79ece235e21199fbed9669319882d65ffc Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 16:09:23 +0000 Subject: [PATCH 115/124] Directory.Build.props --- Directory.Build.props | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000000..85f8db039f --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,16 @@ + + + + <_Parameter1>TUnit.Core + + + <_Parameter1>TUnit.Engine + + + <_Parameter1>TUnit + + + <_Parameter1>TUnit.TestAdapter + + + \ No newline at end of file From 2201afb933794b247e77707d4e7ff65df150e343 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 16:49:29 +0000 Subject: [PATCH 116/124] Output --- TUnit.Core/TUnitTestResult.cs | 4 +- TUnit.Core/TUnitTestResultWithDetails.cs | 6 ++ TUnit.Core/TestContext.cs | 16 +++- TUnit.Core/TestDetails.cs | 2 +- TUnit.Engine/ConsoleInterceptor.cs | 36 +++++++++ TUnit.Engine/SingleTestExecutor.cs | 98 ++++++++++++----------- TUnit.Engine/TestClassCreator.cs | 58 +++++++------- TUnit.TestAdapter/AsyncTestRunExecutor.cs | 1 + TUnit.TestAdapter/TestWithResult.cs | 2 +- 9 files changed, 142 insertions(+), 81 deletions(-) create mode 100644 TUnit.Core/TUnitTestResultWithDetails.cs create mode 100644 TUnit.Engine/ConsoleInterceptor.cs diff --git a/TUnit.Core/TUnitTestResult.cs b/TUnit.Core/TUnitTestResult.cs index 2940c00453..baf86e853e 100644 --- a/TUnit.Core/TUnitTestResult.cs +++ b/TUnit.Core/TUnitTestResult.cs @@ -1,12 +1,12 @@ namespace TUnit.Core; -internal record TUnitTestResult +public record TUnitTestResult { - public required TestDetails TestDetails { get; init; } public required Status Status { get; init; } public required DateTimeOffset Start { get; init; } public required DateTimeOffset End { get; init; } public required TimeSpan Duration { get; init; } public required Exception? Exception { get; init; } public required string ComputerName { get; init; } + public string? Output { get; internal set; } }; \ No newline at end of file diff --git a/TUnit.Core/TUnitTestResultWithDetails.cs b/TUnit.Core/TUnitTestResultWithDetails.cs new file mode 100644 index 0000000000..2849c2d6ab --- /dev/null +++ b/TUnit.Core/TUnitTestResultWithDetails.cs @@ -0,0 +1,6 @@ +namespace TUnit.Core; + +internal record TUnitTestResultWithDetails : TUnitTestResult +{ + public required TestDetails TestDetails { get; init; } +} \ No newline at end of file diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs index 9b20406eec..77a6a77984 100644 --- a/TUnit.Core/TestContext.cs +++ b/TUnit.Core/TestContext.cs @@ -1,9 +1,12 @@ -namespace TUnit.Core; +using System.Text; + +namespace TUnit.Core; public class TestContext { private readonly TestDetails _testDetails; private readonly object? _classInstance; + private readonly StringBuilder _outputBuilder = new(); private static readonly AsyncLocal AsyncLocal = new(); @@ -16,6 +19,11 @@ internal TestContext(TestDetails testDetails, object? classInstance) TestInformation = new(_testDetails, _classInstance); } + internal void Write(char c) + { + _outputBuilder.Append(c); + } + public static TestContext Current { get => AsyncLocal.Value!; @@ -23,4 +31,10 @@ public static TestContext Current } public bool HasFailed { get; init; } + public TUnitTestResult? Result { get; internal set; } + + public string GetOutput() + { + return _outputBuilder.ToString(); + } } \ No newline at end of file diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 309c3c1e60..e2e865daee 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -137,7 +137,7 @@ private string GetArgumentValues() private readonly TaskCompletionSource _completionSource = new(); public Task GetResultAsync() => _completionSource.Task; - public TUnitTestResult SetResult(TUnitTestResult unitTestResult) + public TUnitTestResultWithDetails SetResult(TUnitTestResultWithDetails unitTestResult) { _completionSource.SetResult(unitTestResult); return unitTestResult; diff --git a/TUnit.Engine/ConsoleInterceptor.cs b/TUnit.Engine/ConsoleInterceptor.cs new file mode 100644 index 0000000000..a9e4bb785f --- /dev/null +++ b/TUnit.Engine/ConsoleInterceptor.cs @@ -0,0 +1,36 @@ +using System.Text; +using TUnit.Core; + +namespace TUnit.Engine; + +public class ConsoleInterceptor : TextWriter +{ + public ConsoleInterceptor() + { + Encoding = Encoding.UTF8; + DefaultOut = Console.Out; + Console.SetOut(this); + } + + public override void Write(char value) + { + TestContext.Current.Write(value); + base.Write(value); + } + + public override Task WriteAsync(char value) + { + TestContext.Current.Write(value); + return base.WriteAsync(value); + } + + public override async ValueTask DisposeAsync() + { + Console.SetOut(DefaultOut); + await base.DisposeAsync(); + } + + public TextWriter DefaultOut { get; set; } + + public override Encoding Encoding { get; } +} \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index b01527cc76..0cb0cc013c 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -25,13 +25,13 @@ public SingleTestExecutor(MethodInvoker methodInvoker, private readonly ConcurrentDictionary _oneTimeSetUpRegistry = new(); - public async Task ExecuteTest(TestDetails testDetails, Type[] allClasses) + public async Task ExecuteTest(TestDetails testDetails, Type[] allClasses) { var start = DateTimeOffset.Now; if (testDetails.IsSkipped) { - return testDetails.SetResult(new TUnitTestResult + return testDetails.SetResult(new TUnitTestResultWithDetails { TestDetails = testDetails, Duration = TimeSpan.Zero, @@ -42,17 +42,24 @@ public async Task ExecuteTest(TestDetails testDetails, Type[] a Status = Status.Skipped }); } - + + object? @class = null; + TestContext? testContext = null; try { await Task.Run(async () => { - await ExecuteCore(testDetails, allClasses); + @class = _testClassCreator.CreateTestClass(testDetails, allClasses); + + testContext = new TestContext(testDetails, @class); + TestContext.Current = testContext; + + await ExecuteCore(testDetails, @class); }); - + var end = DateTimeOffset.Now; - - return testDetails.SetResult(new TUnitTestResult + + return testDetails.SetResult(new TUnitTestResultWithDetails { TestDetails = testDetails, Duration = end - start, @@ -60,14 +67,15 @@ await Task.Run(async () => End = end, ComputerName = Environment.MachineName, Exception = null, - Status = Status.Passed + Status = Status.Passed, + Output = testContext?.GetOutput() }); } catch (Exception e) { var end = DateTimeOffset.Now; - - return testDetails.SetResult(new TUnitTestResult + + var unitTestResult = new TUnitTestResultWithDetails { TestDetails = testDetails, Duration = end - start, @@ -75,60 +83,60 @@ await Task.Run(async () => End = end, ComputerName = Environment.MachineName, Exception = e, - Status = Status.Failed - }); + Status = Status.Failed, + Output = testContext?.GetOutput() + }; + + if (testContext != null) + { + testContext.Result = unitTestResult; + } + + await ExecuteCleanUps(@class); + + return testDetails.SetResult(unitTestResult); } } - private async Task ExecuteCore(TestDetails testDetails, Type[] allClasses) + private async Task ExecuteCore(TestDetails testDetails, object? @class) { var isRetry = testDetails.RetryCount > 0; var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - + for (var i = 0; i < executionCount + 1; i++) { testDetails.CurrentExecutionCount++; - - var createdClasses = _testClassCreator.CreateTestClass(testDetails, allClasses); - - foreach (var @class in createdClasses) + + try { - var context = new TestContext(testDetails, @class); - TestContext.Current = context; - - try - { - await ExecuteSetUps(@class, testDetails.ClassType); + await ExecuteSetUps(@class, testDetails.ClassType); - var testLevelCancellationTokenSource = - CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); + var testLevelCancellationTokenSource = + CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); - if (testDetails.Timeout != default) - { - testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); - } - - await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); + if (testDetails.Timeout != default) + { + testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); + } - await ExecuteCleanUps(@class); + await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); - if (isRetry) - { - break; - } - } - catch + if (isRetry) { - if (!isRetry || i == executionCount) - { - throw; - } + break; } - finally + } + catch + { + if (!isRetry || i == executionCount) { - await _disposer.DisposeAsync(@class); + throw; } } + finally + { + await _disposer.DisposeAsync(@class); + } } } diff --git a/TUnit.Engine/TestClassCreator.cs b/TUnit.Engine/TestClassCreator.cs index 87a8c12a04..a3b295fafc 100644 --- a/TUnit.Engine/TestClassCreator.cs +++ b/TUnit.Engine/TestClassCreator.cs @@ -5,58 +5,54 @@ namespace TUnit.Engine; internal class TestClassCreator(TestDataSourceRetriever testDataSourceRetriever) { - public IEnumerable CreateTestClass(TestDetails testDetails, Type[] allClasses) + public object? CreateTestClass(TestDetails testDetails, Type[] allClasses) { if (testDetails.MethodInfo.IsStatic) { - yield break; + return null; } if (testDetails.ClassType.HasAttribute(out var testDataSourceAttributes)) { - foreach (var withTestDataSource in CreateWithTestDataSources(testDetails, testDataSourceAttributes, allClasses)) - { - yield return withTestDataSource; - } + return CreateWithTestDataSources(testDetails, testDataSourceAttributes, allClasses); } - yield return CreateBasicClass(testDetails); + return CreateBasicClass(testDetails); } - private IEnumerable CreateWithTestDataSources(TestDetails testDetails, + private object CreateWithTestDataSources(TestDetails testDetails, IEnumerable testDataSourceAttributes, Type[] allClasses) { - foreach (var testDataSourceAttribute in testDataSourceAttributes) - { - var className = testDataSourceAttribute.ClassNameProvidingDataSource; + var testDataSourceAttribute = testDataSourceAttributes.First(); + + var className = testDataSourceAttribute.ClassNameProvidingDataSource; - ParameterArgument[]? testData; - if (string.IsNullOrEmpty(className)) - { - var @class = testDetails.MethodInfo.DeclaringType!; + ParameterArgument[]? testData; + if (string.IsNullOrEmpty(className)) + { + var @class = testDetails.MethodInfo.DeclaringType!; - testData = testDataSourceRetriever.GetTestDataSourceArguments( - @class, - testDataSourceAttribute.MethodNameProvidingDataSource - ); - } - else - { - var @class = allClasses.FirstOrDefault(x => x.FullName == className) - ?? allClasses.First(x => x.Name == className); + testData = testDataSourceRetriever.GetTestDataSourceArguments( + @class, + testDataSourceAttribute.MethodNameProvidingDataSource + ); + } + else + { + var @class = allClasses.FirstOrDefault(x => x.FullName == className) + ?? allClasses.First(x => x.Name == className); - testData = testDataSourceRetriever.GetTestDataSourceArguments( - @class, - testDataSourceAttribute.MethodNameProvidingDataSource - ); - } + testData = testDataSourceRetriever.GetTestDataSourceArguments( + @class, + testDataSourceAttribute.MethodNameProvidingDataSource + ); + } - yield return Activator.CreateInstance( + return Activator.CreateInstance( testDetails.ClassType, testData?.Select(x => x.Value).ToArray() )!; - } } private static object CreateBasicClass(TestDetails testDetails) diff --git a/TUnit.TestAdapter/AsyncTestRunExecutor.cs b/TUnit.TestAdapter/AsyncTestRunExecutor.cs index e8e1e4caf0..41ded4d416 100644 --- a/TUnit.TestAdapter/AsyncTestRunExecutor.cs +++ b/TUnit.TestAdapter/AsyncTestRunExecutor.cs @@ -70,6 +70,7 @@ public async Task RunInAsyncContext(AssembliesAnd assembliesAn Duration = result.Duration, StartTime = result.Start, EndTime = result.End, + Messages = { new TestResultMessage("Output", result.Output) }, ErrorMessage = result.Exception?.Message, ErrorStackTrace = result.Exception?.StackTrace, }); diff --git a/TUnit.TestAdapter/TestWithResult.cs b/TUnit.TestAdapter/TestWithResult.cs index 16b5a5b216..68852e4dc6 100644 --- a/TUnit.TestAdapter/TestWithResult.cs +++ b/TUnit.TestAdapter/TestWithResult.cs @@ -2,4 +2,4 @@ namespace TUnit.TestAdapter; -internal record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file +internal record TestWithResult(TestWithTestCase Test, Task Result); \ No newline at end of file From baf6de80b9ac58ec741dfe0a436ae2046026f076 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:11:10 +0000 Subject: [PATCH 117/124] Repeat tests run in parallel --- TUnit.Core/TestContext.cs | 12 +- TUnit.Engine/ConsoleInterceptor.cs | 306 +++++++++++++++++- .../Extensions/ServiceCollectionExtensions.cs | 3 +- TUnit.Engine/SingleTestExecutor.cs | 85 +++-- TUnit.Engine/TUnit.Engine.csproj | 3 +- .../Extensions/TestExtensions.cs | 1 - TUnit.TestProject/ConsoleConcurrentTests.cs | 67 ++++ TUnit.TestProject/Tests.cs | 4 +- 8 files changed, 427 insertions(+), 54 deletions(-) create mode 100644 TUnit.TestProject/ConsoleConcurrentTests.cs diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs index 77a6a77984..64a3c349c3 100644 --- a/TUnit.Core/TestContext.cs +++ b/TUnit.Core/TestContext.cs @@ -4,9 +4,11 @@ namespace TUnit.Core; public class TestContext { + public CancellationToken CancellationToken { get; internal set; } = CancellationToken.None; + internal readonly StringWriter OutputWriter = new(); + private readonly TestDetails _testDetails; private readonly object? _classInstance; - private readonly StringBuilder _outputBuilder = new(); private static readonly AsyncLocal AsyncLocal = new(); @@ -19,22 +21,16 @@ internal TestContext(TestDetails testDetails, object? classInstance) TestInformation = new(_testDetails, _classInstance); } - internal void Write(char c) - { - _outputBuilder.Append(c); - } - public static TestContext Current { get => AsyncLocal.Value!; set => AsyncLocal.Value = value; } - public bool HasFailed { get; init; } public TUnitTestResult? Result { get; internal set; } public string GetOutput() { - return _outputBuilder.ToString(); + return OutputWriter.ToString().Trim(); } } \ No newline at end of file diff --git a/TUnit.Engine/ConsoleInterceptor.cs b/TUnit.Engine/ConsoleInterceptor.cs index a9e4bb785f..1132a362fc 100644 --- a/TUnit.Engine/ConsoleInterceptor.cs +++ b/TUnit.Engine/ConsoleInterceptor.cs @@ -1,36 +1,320 @@ using System.Text; using TUnit.Core; +#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). namespace TUnit.Engine; public class ConsoleInterceptor : TextWriter { + public StringWriter InnerWriter => TestContext.Current.OutputWriter; + public ConsoleInterceptor() { - Encoding = Encoding.UTF8; DefaultOut = Console.Out; Console.SetOut(this); } + + public new void Dispose() + { + InnerWriter.Dispose(); + Console.SetOut(DefaultOut); + } + + public override async ValueTask DisposeAsync() + { + await InnerWriter.DisposeAsync(); + Console.SetOut(DefaultOut); + } + + public override void Flush() + { + InnerWriter.Flush(); + } + + public override void Write(bool value) + { + InnerWriter.Write(value); + } + + public override void Write(char[]? buffer) + { + InnerWriter.Write(buffer); + } + + public override void Write(decimal value) + { + InnerWriter.Write(value); + } + + public override void Write(double value) + { + InnerWriter.Write(value); + } + + public override void Write(int value) + { + InnerWriter.Write(value); + } + + public override void Write(long value) + { + InnerWriter.Write(value); + } + + public override void Write(object? value) + { + InnerWriter.Write(value); + } + + public override void Write(float value) + { + InnerWriter.Write(value); + } + + public override void Write(string format, object? arg0) + { + InnerWriter.Write(format, arg0); + } + + public override void Write(string format, object? arg0, object? arg1) + { + InnerWriter.Write(format, arg0, arg1); + } + + public override void Write(string format, object? arg0, object? arg1, object? arg2) + { + InnerWriter.Write(format, arg0, arg1, arg2); + } + + public override void Write(string format, params object?[] arg) + { + InnerWriter.Write(format, arg); + } + + public override void Write(uint value) + { + InnerWriter.Write(value); + } + + public override void Write(ulong value) + { + InnerWriter.Write(value); + } + + public new Task WriteAsync(char[]? buffer) + { + return InnerWriter.WriteAsync(buffer); + } + + public override void WriteLine() + { + InnerWriter.WriteLine(); + } + + public override void WriteLine(bool value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(char value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(char[]? buffer) + { + InnerWriter.WriteLine(buffer); + } + + public override void WriteLine(char[] buffer, int index, int count) + { + InnerWriter.WriteLine(buffer, index, count); + } + + public override void WriteLine(decimal value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(double value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(int value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(long value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(object? value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(float value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(string? value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(string format, object? arg0) + { + InnerWriter.WriteLine(format, arg0); + } + + public override void WriteLine(string format, object? arg0, object? arg1) + { + InnerWriter.WriteLine(format, arg0, arg1); + } + + public override void WriteLine(string format, object? arg0, object? arg1, object? arg2) + { + InnerWriter.WriteLine(format, arg0, arg1, arg2); + } + + public override void WriteLine(string format, params object?[] arg) + { + InnerWriter.WriteLine(format, arg); + } + + public override void WriteLine(uint value) + { + InnerWriter.WriteLine(value); + } + + public override void WriteLine(ulong value) + { + InnerWriter.WriteLine(value); + } + + public override Task WriteLineAsync() + { + return InnerWriter.WriteLineAsync(); + } + + public new Task WriteLineAsync(char[]? buffer) + { + return InnerWriter.WriteLineAsync(buffer); + } + + public override IFormatProvider FormatProvider => InnerWriter.FormatProvider; + + public override string NewLine + { + get => InnerWriter.NewLine; + set => InnerWriter.NewLine = value; + } + + public override void Close() + { + InnerWriter.Close(); + } + + public override Task FlushAsync() + { + return InnerWriter.FlushAsync(); + } + + public StringBuilder GetStringBuilder() + { + return InnerWriter.GetStringBuilder(); + } public override void Write(char value) { - TestContext.Current.Write(value); - base.Write(value); + InnerWriter.Write(value); + } + + public override void Write(char[] buffer, int index, int count) + { + InnerWriter.Write(buffer, index, count); + } + + public override void Write(ReadOnlySpan buffer) + { + InnerWriter.Write(buffer); + } + + public override void Write(string? value) + { + InnerWriter.Write(value); + } + + public override void Write(StringBuilder? value) + { + InnerWriter.Write(value); } public override Task WriteAsync(char value) { - TestContext.Current.Write(value); - return base.WriteAsync(value); + return InnerWriter.WriteAsync(value); } - public override async ValueTask DisposeAsync() + public override Task WriteAsync(char[] buffer, int index, int count) { - Console.SetOut(DefaultOut); - await base.DisposeAsync(); + return InnerWriter.WriteAsync(buffer, index, count); } - public TextWriter DefaultOut { get; set; } - - public override Encoding Encoding { get; } + public override Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = new CancellationToken()) + { + return InnerWriter.WriteAsync(buffer, cancellationToken); + } + + public override Task WriteAsync(string? value) + { + return InnerWriter.WriteAsync(value); + } + + public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = new CancellationToken()) + { + return InnerWriter.WriteAsync(value, cancellationToken); + } + + public override void WriteLine(ReadOnlySpan buffer) + { + InnerWriter.WriteLine(buffer); + } + + public override void WriteLine(StringBuilder? value) + { + InnerWriter.WriteLine(value); + } + + public override Task WriteLineAsync(char value) + { + return InnerWriter.WriteLineAsync(value); + } + + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + return InnerWriter.WriteLineAsync(buffer, index, count); + } + + public override Task WriteLineAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = new CancellationToken()) + { + return InnerWriter.WriteLineAsync(buffer, cancellationToken); + } + + public override Task WriteLineAsync(string? value) + { + return InnerWriter.WriteLineAsync(value); + } + + public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = new CancellationToken()) + { + return InnerWriter.WriteLineAsync(value, cancellationToken); + } + + public override Encoding Encoding => InnerWriter.Encoding; + + public TextWriter DefaultOut { get; } } \ No newline at end of file diff --git a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs index c77b09681b..89b4a0eb63 100644 --- a/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs +++ b/TUnit.Engine/Extensions/ServiceCollectionExtensions.cs @@ -11,6 +11,7 @@ public static IServiceCollection AddTestEngineServices(this IServiceCollection s .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(new ConsoleInterceptor()); } } \ No newline at end of file diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index 0cb0cc013c..aa1c9b3b99 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -54,7 +54,7 @@ await Task.Run(async () => testContext = new TestContext(testDetails, @class); TestContext.Current = testContext; - await ExecuteCore(testDetails, @class); + await ExecuteWithRepeatsOrRetries(testContext, testDetails, @class); }); var end = DateTimeOffset.Now; @@ -98,45 +98,68 @@ await Task.Run(async () => } } - private async Task ExecuteCore(TestDetails testDetails, object? @class) + private async Task ExecuteWithRepeatsOrRetries(TestContext testContext, TestDetails testDetails, object? @class) { var isRetry = testDetails.RetryCount > 0; var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - - for (var i = 0; i < executionCount + 1; i++) - { - testDetails.CurrentExecutionCount++; - - try - { - await ExecuteSetUps(@class, testDetails.ClassType); - - var testLevelCancellationTokenSource = - CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); - - if (testDetails.Timeout != default) - { - testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); - } - await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); + var tasks = new List(); - if (isRetry) - { - break; - } - } - catch + try + { + for (var i = 0; i < executionCount + 1; i++) { - if (!isRetry || i == executionCount) + var task = ExecuteCore(testContext, testDetails, @class); + tasks.Add(task); + + if (isRetry) { - throw; + try + { + await task; + break; + } + catch + { + if (i == executionCount) + { + throw; + } + } } } - finally - { - await _disposer.DisposeAsync(@class); - } + await Task.WhenAll(tasks); + } + finally + { + await _disposer.DisposeAsync(@class); + } + } + + private async Task ExecuteCore(TestContext testContext, TestDetails testDetails, object? @class) + { + testDetails.CurrentExecutionCount++; + + await ExecuteSetUps(@class, testDetails.ClassType); + + var testLevelCancellationTokenSource = + CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token); + + if (testDetails.Timeout != default) + { + testLevelCancellationTokenSource.CancelAfter(testDetails.Timeout); + } + + testContext.CancellationToken = testLevelCancellationTokenSource.Token; + + try + { + await ExecuteTestMethodWithTimeout(testDetails, @class, testLevelCancellationTokenSource); + } + catch + { + testLevelCancellationTokenSource.Cancel(); + throw; } } diff --git a/TUnit.Engine/TUnit.Engine.csproj b/TUnit.Engine/TUnit.Engine.csproj index 5d75f2b180..c863aa5807 100644 --- a/TUnit.Engine/TUnit.Engine.csproj +++ b/TUnit.Engine/TUnit.Engine.csproj @@ -8,7 +8,8 @@ - + + diff --git a/TUnit.TestAdapter/Extensions/TestExtensions.cs b/TUnit.TestAdapter/Extensions/TestExtensions.cs index 877fee67d2..a3facde71b 100644 --- a/TUnit.TestAdapter/Extensions/TestExtensions.cs +++ b/TUnit.TestAdapter/Extensions/TestExtensions.cs @@ -13,7 +13,6 @@ public static TestCase ToTestCase(this TestDetails testDetails) DisplayName = testDetails.DisplayName, CodeFilePath = testDetails.FileName, LineNumber = testDetails.MinLineNumber, - LocalExtensionData = testDetails }; testCase.SetPropertyValue(TUnitTestProperties.UniqueId, testDetails.UniqueId); diff --git a/TUnit.TestProject/ConsoleConcurrentTests.cs b/TUnit.TestProject/ConsoleConcurrentTests.cs new file mode 100644 index 0000000000..7ac9620789 --- /dev/null +++ b/TUnit.TestProject/ConsoleConcurrentTests.cs @@ -0,0 +1,67 @@ +using System.Reflection; +using TUnit.Core; + +namespace TUnit.TestProject; + +public class ConsoleConcurrentTests +{ + [Test, Repeat(1000)] + public void Test1() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test2() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test3() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test4() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test5() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test6() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test7() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test8() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test9() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } + + [Test, Repeat(1000)] + public void Test10() + { + Console.WriteLine(MethodBase.GetCurrentMethod()?.Name); + } +} \ No newline at end of file diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index 896fd20708..c79e1c3e0b 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -7,9 +7,11 @@ public class Tests { [Test] [TestCategory("Pass")] - public void ConsoleOutput() + public async Task ConsoleOutput() { Console.WriteLine("Blah!"); + + await Assert.That(TestContext.Current.GetOutput()).Is.EqualTo("Blah!"); } [Test] From 6516eb642555a11a71addc1fa7a4b4412e522aaa Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:16:58 +0000 Subject: [PATCH 118/124] ExecuteWithRetries or ExecuteWithRepeats --- TUnit.Engine/SingleTestExecutor.cs | 65 +++++++++++++++++------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/TUnit.Engine/SingleTestExecutor.cs b/TUnit.Engine/SingleTestExecutor.cs index aa1c9b3b99..e83e4dac84 100644 --- a/TUnit.Engine/SingleTestExecutor.cs +++ b/TUnit.Engine/SingleTestExecutor.cs @@ -42,6 +42,8 @@ public async Task ExecuteTest(TestDetails testDetail Status = Status.Skipped }); } + + var isRetry = testDetails.RetryCount > 0; object? @class = null; TestContext? testContext = null; @@ -54,7 +56,21 @@ await Task.Run(async () => testContext = new TestContext(testDetails, @class); TestContext.Current = testContext; - await ExecuteWithRepeatsOrRetries(testContext, testDetails, @class); + try + { + if (isRetry) + { + await ExecuteWithRetries(testContext, testDetails, @class); + } + else + { + await ExecuteWithRepeats(testContext, testDetails, @class); + } + } + finally + { + await _disposer.DisposeAsync(@class); + } }); var end = DateTimeOffset.Now; @@ -98,42 +114,35 @@ await Task.Run(async () => } } - private async Task ExecuteWithRepeatsOrRetries(TestContext testContext, TestDetails testDetails, object? @class) + private async Task ExecuteWithRetries(TestContext testContext, TestDetails testDetails, object? @class) { - var isRetry = testDetails.RetryCount > 0; - var executionCount = isRetry ? testDetails.RetryCount : testDetails.RepeatCount; - - var tasks = new List(); - - try + for (var i = 0; i < testDetails.RetryCount + 1; i++) { - for (var i = 0; i < executionCount + 1; i++) + try + { + await ExecuteCore(testContext, testDetails, @class); + break; + } + catch { - var task = ExecuteCore(testContext, testDetails, @class); - tasks.Add(task); - - if (isRetry) + if (i == testDetails.RetryCount) { - try - { - await task; - break; - } - catch - { - if (i == executionCount) - { - throw; - } - } + throw; } } - await Task.WhenAll(tasks); } - finally + } + + private async Task ExecuteWithRepeats(TestContext testContext, TestDetails testDetails, object? @class) + { + var tasks = new List(); + + for (var i = 0; i < testDetails.RepeatCount + 1; i++) { - await _disposer.DisposeAsync(@class); + tasks.Add(ExecuteCore(testContext, testDetails, @class)); } + + await Task.WhenAll(tasks); } private async Task ExecuteCore(TestContext testContext, TestDetails testDetails, object? @class) From d2acd1b094c65df3b33e4dd44da7bb5146d502b1 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:20:55 +0000 Subject: [PATCH 119/124] Workflows --- .github/dependabot.yml | 18 +++++++++ .github/workflows/dotnet.yml | 39 +++++++++++++++++++ .../TUnit.Assertions.csproj.DotSettings | 2 + TUnit.Core/TUnit.Core.csproj.DotSettings | 2 + 4 files changed, 61 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dotnet.yml create mode 100644 TUnit.Assertions/TUnit.Assertions.csproj.DotSettings create mode 100644 TUnit.Core/TUnit.Core.csproj.DotSettings diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..41fb5ab87b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/" + open-pull-requests-limit: 20 + schedule: + interval: "daily" + groups: + test-dependencies: + patterns: + - NUnit* + - "*Test*" + modularpipelines-dependencies: + patterns: + - "*ModularPipeline*" + ignore: + - dependency-name: "Microsoft.Extensions.*" + update-types: ["version-update:semver-major"] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000000..f2eecf3162 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,39 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + inputs: + publish-packages: + description: Publish packages? + type: boolean + required: true + +jobs: + modularpipeline: + environment: ${{ github.ref == 'refs/heads/main' && 'Production' || 'Pull Requests' }} + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + persist-credentials: false + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + - name: Run Pipeline + run: dotnet run -c Release + working-directory: "TUnit.Pipeline" + env: + DOTNET_ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'Production' || 'Development' }} + NuGet__ApiKey: ${{ secrets.NUGET__APIKEY }} + PULL_REQUEST_BRANCH: ${{ github.event.pull_request.head.ref }} + PUBLISH_PACKAGES: ${{ github.event.inputs.publish-packages }} diff --git a/TUnit.Assertions/TUnit.Assertions.csproj.DotSettings b/TUnit.Assertions/TUnit.Assertions.csproj.DotSettings new file mode 100644 index 0000000000..17962b139d --- /dev/null +++ b/TUnit.Assertions/TUnit.Assertions.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/TUnit.Core/TUnit.Core.csproj.DotSettings b/TUnit.Core/TUnit.Core.csproj.DotSettings new file mode 100644 index 0000000000..bd95c7d75d --- /dev/null +++ b/TUnit.Core/TUnit.Core.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file From 9505e2f206dc0cf0f1c94a3b2f3b813bb6e74386 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:33:05 +0000 Subject: [PATCH 120/124] Alpha UploadToNuGetModule.cs --- .../Modules/AddReferencesToTestProject.cs | 50 ------------------- TUnit.Pipeline/Modules/BuildProjectsModule.cs | 20 -------- .../Modules/PackTUnitFilesModule.cs | 18 +++++-- TUnit.Pipeline/Modules/UploadToNuGetModule.cs | 29 +++++++++++ 4 files changed, 43 insertions(+), 74 deletions(-) delete mode 100644 TUnit.Pipeline/Modules/AddReferencesToTestProject.cs delete mode 100644 TUnit.Pipeline/Modules/BuildProjectsModule.cs create mode 100644 TUnit.Pipeline/Modules/UploadToNuGetModule.cs diff --git a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs b/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs deleted file mode 100644 index da7f0da703..0000000000 --- a/TUnit.Pipeline/Modules/AddReferencesToTestProject.cs +++ /dev/null @@ -1,50 +0,0 @@ -using EnumerableAsyncProcessor.Extensions; -using ModularPipelines.Attributes; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Extensions; -using ModularPipelines.Git.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using File = ModularPipelines.FileSystem.File; - -namespace TUnit.Pipeline.Modules; -[DependsOn] -[DependsOn] -[DependsOn] -public class AddReferencesToTestProject : Module -{ - protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var testProject = context.Git().RootDirectory.FindFile(x => x.Name == "TUnit.TestProject.csproj").AssertExists(); - var projects = await GetModule(); - var localNugetDirectory = await GetModule(); - - await projects.Value! - .ForEachAsync( - x => RemovePackage(context, cancellationToken, testProject, x), - cancellationToken: cancellationToken) - .ProcessOneAtATime(); - - return await projects.Value! - .Where(x => x.Name == "TUnit.TestAdapter") - .SelectAsync(async x => await context.DotNet().Add.Package(new DotNetAddPackageOptions(testProject, x.Name) - { - Version = x.Version, - Source = localNugetDirectory.Value!, - }, cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); - } - - private static async Task RemovePackage(IPipelineContext context, CancellationToken cancellationToken, File testProject, PackedProject x) - { - var existingPackages = await context.DotNet().List.Package(new DotNetListPackageOptions(testProject), cancellationToken); - - if (!existingPackages.StandardOutput.Contains($" > {x.Name} ")) - { - return; - } - - await context.DotNet().Remove.Package(new DotNetRemovePackageOptions(testProject, x.Name), cancellationToken); - } -} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/BuildProjectsModule.cs b/TUnit.Pipeline/Modules/BuildProjectsModule.cs deleted file mode 100644 index 55c6108314..0000000000 --- a/TUnit.Pipeline/Modules/BuildProjectsModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -using EnumerableAsyncProcessor.Extensions; -using ModularPipelines.Attributes; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace TUnit.Pipeline.Modules; - -[DependsOn] -[DependsOn] -public class BuildProjectsModule : Module -{ - protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var projects = await GetModule(); - return await projects.Value!.SelectAsync(x => context.DotNet().Build(new DotNetBuildOptions(x), cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime(); - } -} \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs index 9e023181b4..ff27e5448d 100644 --- a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs +++ b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs @@ -5,6 +5,7 @@ using ModularPipelines.Models; using ModularPipelines.Modules; using ModularPipelines.Attributes; +using ModularPipelines.Git.Extensions; namespace TUnit.Pipeline.Modules; [DependsOn] @@ -14,9 +15,15 @@ public class PackTUnitFilesModule : Module> protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) { var projects = await GetModule(); - - var guid = Guid.NewGuid(); - var version = $"0.0.1-alpha{guid}"; + + var git = await context.Git().Versioning.GetGitVersioningInformation(); + + var version = git.SemVer; + + if (git.BranchName == "main") + { + version += "alpha01"; + } await projects.Value!.SelectAsync(async project => { @@ -25,7 +32,10 @@ public class PackTUnitFilesModule : Module> new DotNetPackOptions(project) { Properties = new[] - { new KeyValue("Version", version), new KeyValue("PackageVersion", version) } + { + new KeyValue("Version", version), + new KeyValue("PackageVersion", version) + } }, cancellationToken); }, cancellationToken: cancellationToken) .ProcessOneAtATime(); diff --git a/TUnit.Pipeline/Modules/UploadToNuGetModule.cs b/TUnit.Pipeline/Modules/UploadToNuGetModule.cs new file mode 100644 index 0000000000..af25890ce4 --- /dev/null +++ b/TUnit.Pipeline/Modules/UploadToNuGetModule.cs @@ -0,0 +1,29 @@ +using EnumerableAsyncProcessor.Extensions; +using ModularPipelines.Attributes; +using ModularPipelines.Context; +using ModularPipelines.DotNet.Extensions; +using ModularPipelines.DotNet.Options; +using ModularPipelines.Git.Attributes; +using ModularPipelines.Git.Extensions; +using ModularPipelines.Models; +using ModularPipelines.Modules; + +namespace TUnit.Pipeline.Modules; + +[SkipIfBranch("main")] +[DependsOn] +public class UploadToNuGetModule : Module +{ + protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + var nupkgs = context.Git().RootDirectory + .GetFiles(x => x.Extension is ".nupkg" or ".snupkg"); + + return await nupkgs.SelectAsync(file => + context.DotNet().Nuget.Push(new DotNetNugetPushOptions(file) + { + Source = "https://api.nuget.org/v3/index.json" + }, cancellationToken), cancellationToken: cancellationToken) + .ProcessOneAtATime(); + } +} \ No newline at end of file From 962e86905b73312b8e7feff83ca03772db0854ce Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:37:40 +0000 Subject: [PATCH 121/124] RunOnlyOnBranch --- TUnit.Pipeline/Modules/UploadToNuGetModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TUnit.Pipeline/Modules/UploadToNuGetModule.cs b/TUnit.Pipeline/Modules/UploadToNuGetModule.cs index af25890ce4..3d5b391855 100644 --- a/TUnit.Pipeline/Modules/UploadToNuGetModule.cs +++ b/TUnit.Pipeline/Modules/UploadToNuGetModule.cs @@ -10,7 +10,7 @@ namespace TUnit.Pipeline.Modules; -[SkipIfBranch("main")] +[RunOnlyOnBranch("main")] [DependsOn] public class UploadToNuGetModule : Module { From 23eb50d28a3b6063c82542eb1a736bcba240aed7 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:39:11 +0000 Subject: [PATCH 122/124] Debug --- TUnit.Pipeline/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TUnit.Pipeline/Program.cs b/TUnit.Pipeline/Program.cs index f50ca7fd20..dc6b8c36ed 100644 --- a/TUnit.Pipeline/Program.cs +++ b/TUnit.Pipeline/Program.cs @@ -1,4 +1,5 @@ -using ModularPipelines.Extensions; +using Microsoft.Extensions.Logging; +using ModularPipelines.Extensions; using ModularPipelines.Host; await PipelineHostBuilder.Create() @@ -6,4 +7,5 @@ await PipelineHostBuilder.Create() { collection.AddModulesFromAssembly(typeof(Program).Assembly); }) + .SetLogLevel(LogLevel.Debug) .ExecutePipelineAsync(); \ No newline at end of file From a3f4309407192fd650064ea820aab237e9876412 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:08:01 +0000 Subject: [PATCH 123/124] null warning --- TUnit.Pipeline/Modules/PackTUnitFilesModule.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs index ff27e5448d..a619ad56f9 100644 --- a/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs +++ b/TUnit.Pipeline/Modules/PackTUnitFilesModule.cs @@ -33,13 +33,13 @@ public class PackTUnitFilesModule : Module> { Properties = new[] { - new KeyValue("Version", version), - new KeyValue("PackageVersion", version) + new KeyValue("Version", version!), + new KeyValue("PackageVersion", version!) } }, cancellationToken); }, cancellationToken: cancellationToken) .ProcessOneAtATime(); - return projects.Value!.Select(x => new PackedProject(x.NameWithoutExtension, version)).ToList(); + return projects.Value!.Select(x => new PackedProject(x.NameWithoutExtension, version!)).ToList(); } } \ No newline at end of file From 0b2cc9b101abd203cfdeaee1d7f66b1850141c1d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:23:12 +0000 Subject: [PATCH 124/124] Update modular pipelines --- TUnit.Pipeline/TUnit.Pipeline.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TUnit.Pipeline/TUnit.Pipeline.csproj b/TUnit.Pipeline/TUnit.Pipeline.csproj index 52bead6e0e..f651059b7b 100644 --- a/TUnit.Pipeline/TUnit.Pipeline.csproj +++ b/TUnit.Pipeline/TUnit.Pipeline.csproj @@ -9,8 +9,8 @@ - - + +