Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve code coverage of fake should overloads #1847

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 17 additions & 7 deletions Build/Build.cs
Expand Up @@ -50,12 +50,15 @@ class Build : NukeBuild

AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts";

AbsolutePath TestResultsDirectory => RootDirectory / "TestResults";

string SemVer;

Target Clean => _ => _
.Executes(() =>
{
EnsureCleanDirectory(ArtifactsDirectory);
EnsureCleanDirectory(TestResultsDirectory);
});

Target CalculateNugetVersion => _ => _
Expand Down Expand Up @@ -140,7 +143,10 @@ class Build : NukeBuild
.SetConfiguration("Debug")
.EnableNoBuild()
.SetDataCollector("XPlat Code Coverage")
.SetResultsDirectory(RootDirectory / "TestResults")
.SetResultsDirectory(TestResultsDirectory)
.AddRunSetting(
"DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DoesNotReturnAttribute",
"DoesNotReturnAttribute")
.CombineWith(
projects,
(_, project) => _
Expand All @@ -160,14 +166,15 @@ class Build : NukeBuild
{
ReportGenerator(s => s
.SetProcessToolPath(ToolPathResolver.GetPackageExecutable("ReportGenerator", "ReportGenerator.dll", framework: "net6.0"))
.SetTargetDirectory(RootDirectory / "TestResults" / "reports")
.AddReports(RootDirectory / "TestResults/**/coverage.cobertura.xml")
.SetTargetDirectory(TestResultsDirectory / "reports")
.AddReports(TestResultsDirectory / "**/coverage.cobertura.xml")
.AddReportTypes("HtmlInline_AzurePipelines_Dark", "lcov")
.SetClassFilters("-System.Diagnostics.CodeAnalysis.StringSyntaxAttribute")
.SetClassFilters(
"-System.Diagnostics.CodeAnalysis.StringSyntaxAttribute",
"-System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute")
.SetAssemblyFilters("+FluentAssertions"));

string link = RootDirectory / "TestResults" / "reports" / "index.html";

string link = TestResultsDirectory / "reports" / "index.html";
Serilog.Log.Information($"Code coverage report: \x1b]8;;file://{link.Replace('\\', '/')}\x1b\\{link}\x1b]8;;\x1b\\");
});

Expand All @@ -192,7 +199,10 @@ class Build : NukeBuild
.SetConfiguration("Debug")
.EnableNoBuild()
.SetDataCollector("XPlat Code Coverage")
.SetResultsDirectory(RootDirectory / "TestResults")
.SetResultsDirectory(TestResultsDirectory)
.AddRunSetting(
"DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DoesNotReturnAttribute",
"DoesNotReturnAttribute")
.CombineWith(
testCombinations,
(_, v) => _.SetProjectFile(v.project).SetFramework(v.framework)));
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq.Expressions;
using System.Net.Http;
Expand Down Expand Up @@ -999,6 +1000,7 @@ public static void Should(this TypeSelectorAssertions _)
InvalidShouldCall();
}

[DoesNotReturn]
private static void InvalidShouldCall()
{
throw new InvalidOperationException("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'.");
Expand Down
18 changes: 18 additions & 0 deletions Src/FluentAssertions/DoesNotReturnAttribute.cs
@@ -0,0 +1,18 @@
// copied from https://source.dot.net/#System.Private.CoreLib/NullableAttributes.cs
#if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
sealed class DoesNotReturnAttribute : Attribute
{
}
}
#endif
3 changes: 3 additions & 0 deletions Src/FluentAssertions/Execution/FallbackTestFramework.cs
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace FluentAssertions.Execution
{
/// <summary>
Expand All @@ -13,6 +15,7 @@ internal class FallbackTestFramework : ITestFramework
/// <summary>
/// Throws a framework-specific exception to indicate a failing unit test.
/// </summary>
[DoesNotReturn]
public void Throw(string message)
{
throw new AssertionFailedException(message);
Expand Down
5 changes: 4 additions & 1 deletion Src/FluentAssertions/Execution/ITestFramework.cs
@@ -1,4 +1,6 @@
namespace FluentAssertions.Execution
using System.Diagnostics.CodeAnalysis;

namespace FluentAssertions.Execution
{
/// <summary>
/// Represents an abstraction of a particular test framework such as MSTest, nUnit, etc.
Expand All @@ -13,6 +15,7 @@ internal interface ITestFramework
/// <summary>
/// Throws a framework-specific exception to indicate a failing unit test.
/// </summary>
[DoesNotReturn]
void Throw(string message);
}
}
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/LateBoundTestFramework.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

Expand All @@ -8,6 +9,7 @@ internal abstract class LateBoundTestFramework : ITestFramework
{
private Assembly assembly;

[DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType(ExceptionFullName);
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/NSpecFramework.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

Expand Down Expand Up @@ -27,6 +28,7 @@ public bool IsAvailable
}
}

[DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType("NSpec.Domain.AssertionException");
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/TestFrameworkProvider.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using FluentAssertions.Common;

Expand All @@ -22,6 +23,7 @@ internal static class TestFrameworkProvider

#endregion

[DoesNotReturn]
public static void Throw(string message)
{
if (testFramework is null)
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/XUnit2TestFramework.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace FluentAssertions.Execution
Expand All @@ -25,6 +26,7 @@ public bool IsAvailable
}
}

[DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType("Xunit.Sdk.XunitException");
Expand Down
49 changes: 48 additions & 1 deletion Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs
Expand Up @@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions.Numeric;
using FluentAssertions.Primitives;
using FluentAssertions.Specialized;
using FluentAssertions.Types;
using Xunit;

Expand Down Expand Up @@ -32,6 +35,49 @@ private static bool OverridesEquals(Type t)
return equals is not null;
}

[Theory]
[InlineData(typeof(ReferenceTypeAssertions<object, ObjectAssertions>))]
[InlineData(typeof(BooleanAssertions<BooleanAssertions>))]
[InlineData(typeof(DateTimeAssertions<DateTimeAssertions>))]
[InlineData(typeof(DateTimeOffsetAssertions<DateTimeOffsetAssertions>))]
#if NET6_0_OR_GREATER
[InlineData(typeof(DateOnlyAssertions<DateOnlyAssertions>))]
#endif
[InlineData(typeof(ExecutionTimeAssertions))]
[InlineData(typeof(GuidAssertions<GuidAssertions>))]
[InlineData(typeof(MethodInfoSelectorAssertions))]
[InlineData(typeof(NumericAssertions<int, NumericAssertions<int>>))]
[InlineData(typeof(PropertyInfoSelectorAssertions))]
[InlineData(typeof(SimpleTimeSpanAssertions<SimpleTimeSpanAssertions>))]
[InlineData(typeof(TaskCompletionSourceAssertions<int>))]
[InlineData(typeof(TypeSelectorAssertions))]
[InlineData(typeof(EnumAssertions<StringComparison, EnumAssertions<StringComparison>>))]
public void Fake_should_method_throws(Type type)
{
// Arrange
MethodInfo fakeOverload = AllTypes.From(typeof(FluentAssertions.AssertionExtensions).Assembly)
.ThatAreClasses()
.ThatAreStatic()
.Where(t => t.IsPublic)
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public))
.Single(m => m.Name == "Should" && IsGuardOverload(m)
&& m.GetParameters().Single().ParameterType.Name == type.Name);

if (type.IsConstructedGenericType)
{
fakeOverload = fakeOverload.MakeGenericMethod(type.GenericTypeArguments);
}

// Act
Action act = () => fakeOverload.Invoke(null, new object[] { null });

// Assert
act.Should()
.ThrowExactly<TargetInvocationException>()
.WithInnerExceptionExactly<InvalidOperationException>()
.WithMessage("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'.");
}

[Fact]
public void Should_methods_have_a_matching_overload_to_guard_against_chaining_and_constraints()
{
Expand All @@ -58,7 +104,8 @@ public void Should_methods_have_a_matching_overload_to_guard_against_chaining_an
// Assert
fakeOverloads.Should().BeEquivalentTo(realOverloads, opt => opt
.Using<Type>(ctx => ctx.Subject.Name.Should().Be(ctx.Expectation.Name))
.WhenTypeIs<Type>());
.WhenTypeIs<Type>(),
"AssertionExtensions.cs should have a guard overload of Should calling InvalidShouldCall()");
}

private static bool IsGuardOverload(MethodInfo m) =>
Expand Down