diff --git a/Build/Build.cs b/Build/Build.cs
index 756dcc2051..dd66fc1a1f 100644
--- a/Build/Build.cs
+++ b/Build/Build.cs
@@ -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 => _ => _
@@ -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) => _
@@ -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\\");
});
@@ -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)));
diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs
index b3c0bf3ef5..4375b958c3 100644
--- a/Src/FluentAssertions/AssertionExtensions.cs
+++ b/Src/FluentAssertions/AssertionExtensions.cs
@@ -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;
@@ -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'.");
diff --git a/Src/FluentAssertions/DoesNotReturnAttribute.cs b/Src/FluentAssertions/DoesNotReturnAttribute.cs
new file mode 100644
index 0000000000..1a0daac542
--- /dev/null
+++ b/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
+{
+ /// Applied to a method that will never return under any circumstance.
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+#if SYSTEM_PRIVATE_CORELIB
+ public
+#else
+ internal
+#endif
+ sealed class DoesNotReturnAttribute : Attribute
+ {
+ }
+}
+#endif
diff --git a/Src/FluentAssertions/Execution/FallbackTestFramework.cs b/Src/FluentAssertions/Execution/FallbackTestFramework.cs
index 1abd625713..34232ad62d 100644
--- a/Src/FluentAssertions/Execution/FallbackTestFramework.cs
+++ b/Src/FluentAssertions/Execution/FallbackTestFramework.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace FluentAssertions.Execution
{
///
@@ -13,6 +15,7 @@ internal class FallbackTestFramework : ITestFramework
///
/// Throws a framework-specific exception to indicate a failing unit test.
///
+ [DoesNotReturn]
public void Throw(string message)
{
throw new AssertionFailedException(message);
diff --git a/Src/FluentAssertions/Execution/ITestFramework.cs b/Src/FluentAssertions/Execution/ITestFramework.cs
index 694dba015a..321df438ca 100644
--- a/Src/FluentAssertions/Execution/ITestFramework.cs
+++ b/Src/FluentAssertions/Execution/ITestFramework.cs
@@ -1,4 +1,6 @@
-namespace FluentAssertions.Execution
+using System.Diagnostics.CodeAnalysis;
+
+namespace FluentAssertions.Execution
{
///
/// Represents an abstraction of a particular test framework such as MSTest, nUnit, etc.
@@ -13,6 +15,7 @@ internal interface ITestFramework
///
/// Throws a framework-specific exception to indicate a failing unit test.
///
+ [DoesNotReturn]
void Throw(string message);
}
}
diff --git a/Src/FluentAssertions/Execution/LateBoundTestFramework.cs b/Src/FluentAssertions/Execution/LateBoundTestFramework.cs
index 6106e59269..7c5037ef65 100644
--- a/Src/FluentAssertions/Execution/LateBoundTestFramework.cs
+++ b/Src/FluentAssertions/Execution/LateBoundTestFramework.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
@@ -8,6 +9,7 @@ internal abstract class LateBoundTestFramework : ITestFramework
{
private Assembly assembly;
+ [DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType(ExceptionFullName);
diff --git a/Src/FluentAssertions/Execution/NSpecFramework.cs b/Src/FluentAssertions/Execution/NSpecFramework.cs
index dd1ec4a4d3..84d69e2d95 100644
--- a/Src/FluentAssertions/Execution/NSpecFramework.cs
+++ b/Src/FluentAssertions/Execution/NSpecFramework.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
@@ -27,6 +28,7 @@ public bool IsAvailable
}
}
+ [DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType("NSpec.Domain.AssertionException");
diff --git a/Src/FluentAssertions/Execution/TestFrameworkProvider.cs b/Src/FluentAssertions/Execution/TestFrameworkProvider.cs
index 621f63e05a..730b4accb6 100644
--- a/Src/FluentAssertions/Execution/TestFrameworkProvider.cs
+++ b/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;
@@ -22,6 +23,7 @@ internal static class TestFrameworkProvider
#endregion
+ [DoesNotReturn]
public static void Throw(string message)
{
if (testFramework is null)
diff --git a/Src/FluentAssertions/Execution/XUnit2TestFramework.cs b/Src/FluentAssertions/Execution/XUnit2TestFramework.cs
index eaa3ad4670..df18b93e88 100644
--- a/Src/FluentAssertions/Execution/XUnit2TestFramework.cs
+++ b/Src/FluentAssertions/Execution/XUnit2TestFramework.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace FluentAssertions.Execution
@@ -25,6 +26,7 @@ public bool IsAvailable
}
}
+ [DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType("Xunit.Sdk.XunitException");
diff --git a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs
index e4d55a2028..1116280c94 100644
--- a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs
+++ b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs
@@ -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;
@@ -32,6 +35,49 @@ private static bool OverridesEquals(Type t)
return equals is not null;
}
+ [Theory]
+ [InlineData(typeof(ReferenceTypeAssertions