Skip to content

Commit

Permalink
Squashed 'src/Microsoft.DotNet.XUnitAssert/src/' changes from cb1e33b…
Browse files Browse the repository at this point in the history
…a6..574aebac4

574aebac4 xunit/xunit#2872: Expand special handling for sets in Assert.Contains/DoesNotContain
3b8edcbf1 xunit/xunit#2880: Update XML documentation for string-based Assert.Equal
d9f8361d2 Consolidate string and span-based Assert.Equal primary implementation
9ad71163e Move span-of-char assertions to StringAsserts and update docs/param names to indicate they're treated like strings
d70b34621 xunit/xunit#2871: Inner exception stack trace is missing from Assert.Collection failure
141681779 Missed #nullable enable in AsyncCollectionAsserts
22c89b0ea xunit/xunit#2367: Add IAsyncEnumerable<> overloads (.NET Core 3.0+)
d5c32630a While formatting type names in Assert.Equal/NotEqual, convert generated type names to '<generated>'
d7b807179 Add platform conditionals to support .NET 6 Roslyn Analyzers
6d9024665 xunit/xunit#2850: Assert.Equal failing value-slot collections of different concrete types (also fixed for KeyValuePair keys and values)
1f66b837a xunit/xunit#2811: Add SortedSet and ImmutableSortedSet overloads for Assert.Contains/DoesNotContain
1dab747d3 Update FuncEqualityComparer to throw if GetHashCode is called, and update EqualException/NotEqualException to process it
6e0a7cd70 xunit/xunit#2828: Prefer IEquatable<> over custom collection equality
c35ef46d5 xunit/xunit#2824: Assert.Equal fails with null values in dictionary
455865ac8 xunit/xunit#2821: Assert.Equal for collections of IEquatable objects don't call custom Equals
9af2c9c12 Clarify names for range comparer
2e6d9b267 Updates for .NET 8 SDK

git-subtree-dir: src/Microsoft.DotNet.XUnitAssert/src
git-subtree-split: 574aebac41dbbbf9e5e98bb9c65c6c5fab9b47f5
  • Loading branch information
ViktorHofer committed Mar 28, 2024
1 parent d809d22 commit a757c66
Show file tree
Hide file tree
Showing 21 changed files with 1,761 additions and 813 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Expand Up @@ -227,4 +227,5 @@ dotnet_diagnostic.CA1200.severity = none # Avoid using cref tags with a prefix
dotnet_diagnostic.CA1707.severity = none # Remove the underscores from type name
dotnet_diagnostic.CA1720.severity = none # Identifier contains type name
dotnet_diagnostic.CA1810.severity = none # Do not use static constructors
dotnet_diagnostic.CA1859.severity = none # Use concrete types when possible for improved performance
dotnet_diagnostic.CA2007.severity = none # Consider calling ConfigureAwait on the awaited task
466 changes: 466 additions & 0 deletions AsyncCollectionAsserts.cs

Large diffs are not rendered by default.

52 changes: 38 additions & 14 deletions CollectionAsserts.cs
Expand Up @@ -226,13 +226,25 @@ partial class Assert
{
GuardArgumentNotNull(nameof(collection), collection);

// We special case HashSet<T> because it has a custom Contains implementation that is based on the comparer
// passed into their constructors, which we don't have access to.
var hashSet = collection as HashSet<T>;
if (hashSet != null)
Contains(expected, hashSet);
else
Contains(expected, collection, GetEqualityComparer<T>());
// We special case sets because they are constructed with their comparers, which we don't have access to.
// We want to let them do their normal logic when appropriate, and not try to use our default comparer.
var set = collection as ISet<T>;
if (set != null)
{
Contains(expected, set);
return;
}
#if NET5_0_OR_GREATER
var readOnlySet = collection as IReadOnlySet<T>;
if (readOnlySet != null)
{
Contains(expected, readOnlySet);
return;
}
#endif

// Fall back to the assumption that this is a linear container and use our default comparer
Contains(expected, collection, GetEqualityComparer<T>());
}

/// <summary>
Expand Down Expand Up @@ -326,13 +338,25 @@ partial class Assert
{
GuardArgumentNotNull(nameof(collection), collection);

// We special case HashSet<T> because it has a custom Contains implementation that is based on the comparer
// passed into their constructors, which we don't have access to.
var hashSet = collection as HashSet<T>;
if (hashSet != null)
DoesNotContain(expected, hashSet);
else
DoesNotContain(expected, collection, GetEqualityComparer<T>());
// We special case sets because they are constructed with their comparers, which we don't have access to.
// We want to let them do their normal logic when appropriate, and not try to use our default comparer.
var set = collection as ISet<T>;
if (set != null)
{
DoesNotContain(expected, set);
return;
}
#if NET5_0_OR_GREATER
var readOnlySet = collection as IReadOnlySet<T>;
if (readOnlySet != null)
{
DoesNotContain(expected, readOnlySet);
return;
}
#endif

// Fall back to the assumption that this is a linear container and use our default comparer
DoesNotContain(expected, collection, GetEqualityComparer<T>());
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions Comparers.cs
Expand Up @@ -19,16 +19,16 @@ namespace Xunit
#endif
partial class Assert
{
static IComparer<T> GetComparer<T>()
where T : IComparable =>
new AssertComparer<T>();

#if XUNIT_NULLABLE
static IEqualityComparer<T?> GetEqualityComparer<T>(IEqualityComparer? innerComparer = null) =>
new AssertEqualityComparer<T?>(innerComparer);
#else
static IEqualityComparer<T> GetEqualityComparer<T>(IEqualityComparer innerComparer = null) =>
new AssertEqualityComparer<T>(innerComparer);
#endif

static IComparer<T> GetRangeComparer<T>()
where T : IComparable =>
new AssertRangeComparer<T>();
}
}
29 changes: 23 additions & 6 deletions EqualityAsserts.cs
Expand Up @@ -12,6 +12,7 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
using Xunit.Internal;
using Xunit.Sdk;

#if XUNIT_NULLABLE
Expand Down Expand Up @@ -176,7 +177,15 @@ partial class Assert
{
try
{
if (CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer<T>.DefaultInnerComparer, out mismatchedIndex))
bool result;

// Call AssertEqualityComparer.Equals because it checks for IEquatable<> before using CollectionTracker
if (aec != null)
result = aec.Equals(expected, expectedTracker, actual, actualTracker, out mismatchedIndex);
else
result = CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer<T>.DefaultInnerComparer, out mismatchedIndex);

if (result)
return;
}
catch (Exception ex)
Expand Down Expand Up @@ -251,8 +260,8 @@ partial class Assert

if (expectedType != actualType)
{
var expectedTypeName = expectedType == null ? "" : ArgumentFormatter.FormatTypeName(expectedType) + " ";
var actualTypeName = actualType == null ? "" : ArgumentFormatter.FormatTypeName(actualType) + " ";
var expectedTypeName = expectedType == null ? "" : (AssertHelper.IsCompilerGenerated(expectedType) ? "<generated> " : ArgumentFormatter.FormatTypeName(expectedType) + " ");
var actualTypeName = actualType == null ? "" : (AssertHelper.IsCompilerGenerated(actualType) ? "<generated> " : ArgumentFormatter.FormatTypeName(actualType) + " ");

var typeNameIndent = Math.Max(expectedTypeName.Length, actualTypeName.Length);

Expand Down Expand Up @@ -651,7 +660,15 @@ partial class Assert
{
try
{
if (!CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer<T>.DefaultInnerComparer, out mismatchedIndex))
bool result;

// Call AssertEqualityComparer.Equals because it checks for IEquatable<> before using CollectionTracker
if (aec != null)
result = aec.Equals(expected, expectedTracker, actual, actualTracker, out mismatchedIndex);
else
result = CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer<T>.DefaultInnerComparer, out mismatchedIndex);

if (!result)
return;

// For NotEqual that doesn't throw, pointers are irrelevant, because
Expand Down Expand Up @@ -719,8 +736,8 @@ partial class Assert

if (expectedType != actualType)
{
var expectedTypeName = expectedType == null ? "" : ArgumentFormatter.FormatTypeName(expectedType) + " ";
var actualTypeName = actualType == null ? "" : ArgumentFormatter.FormatTypeName(actualType) + " ";
var expectedTypeName = expectedType == null ? "" : (AssertHelper.IsCompilerGenerated(expectedType) ? "<generated> " : ArgumentFormatter.FormatTypeName(expectedType) + " ");
var actualTypeName = actualType == null ? "" : (AssertHelper.IsCompilerGenerated(actualType) ? "<generated> " : ArgumentFormatter.FormatTypeName(actualType) + " ");

var typeNameIndent = Math.Max(expectedTypeName.Length, actualTypeName.Length);

Expand Down
4 changes: 3 additions & 1 deletion README.md
@@ -1,6 +1,8 @@
# About This Project

This project contains the xUnit.net assertion library source code, intended to be used as a Git submodule. Code here is built with a target-framework of `netstandard1.1`, and must support both `net452` and `netcoreapp1.0`. The code must be buildable by a minimum of C# 6.0. These constraints are supported by the [suggested contribution workflow](#suggested-contribution-workflow), which makes it trivial to know when you've used unavailable features.
This project contains the xUnit.net assertion library source code, intended to be used as a Git submodule.

Code here is built with several target frameworks: `netstandard1.1` and `net6.0` for xUnit.net v2; and `netstandard2.0` and `net6.0` for xUnit.net v3. At a minimum the code needs to be able to support `net452` and later for .NET Framework, `netcoreapp1.0` and later for .NET Core, and `net5.0` and later for .NET. The minimum (and default) C# version is 6.0, unless specific features require targeting later compilers. Additionally, we compile with the full Roslyn analyzer set enabled when building for v3, so you will frequently see conditional code and/or rules being disabled as appropriate. These constraints are supported by the [suggested contribution workflow](#suggested-contribution-workflow), which aims to make it easy to know when you've used unavailable features.

> _**Note:** If your PR requires a newer target framework or a newer C# language to build, please start a discussion in the related issue(s) before starting any work. PRs that arbitrarily use newer target frameworks and/or newer C# language features will need to be fixed; you may be asked to fix them, or we may fix them for you, or we may decline the PR (at our discretion)._
Expand Down
4 changes: 2 additions & 2 deletions RangeAsserts.cs
Expand Up @@ -31,7 +31,7 @@ partial class Assert
T low,
T high)
where T : IComparable =>
InRange(actual, low, high, GetComparer<T>());
InRange(actual, low, high, GetRangeComparer<T>());

/// <summary>
/// Verifies that a value is within a given range, using a comparer.
Expand Down Expand Up @@ -70,7 +70,7 @@ partial class Assert
T low,
T high)
where T : IComparable =>
NotInRange(actual, low, high, GetComparer<T>());
NotInRange(actual, low, high, GetRangeComparer<T>());

/// <summary>
/// Verifies that a value is not within a given range, using a comparer.
Expand Down
29 changes: 28 additions & 1 deletion Sdk/ArgumentFormatter.cs
Expand Up @@ -123,8 +123,12 @@ static ArgumentFormatter()
/// <param name="s">The string value to be escaped</param>
public static string EscapeString(string s)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(s);
#else
if (s == null)
throw new ArgumentNullException(nameof(s));
#endif

var builder = new StringBuilder(s.Length);
for (var i = 0; i < s.Length; i++)
Expand Down Expand Up @@ -322,7 +326,11 @@ static string FormatCharValue(char value)
string.Format(CultureInfo.CurrentCulture, "{0:G17}", value);

static string FormatEnumValue(object value) =>
#if NETCOREAPP2_0_OR_GREATER
value.ToString()?.Replace(", ", " | ", StringComparison.Ordinal) ?? "null";
#else
value.ToString()?.Replace(", ", " | ") ?? "null";
#endif

static string FormatEnumerableValue(
IEnumerable enumerable,
Expand Down Expand Up @@ -365,7 +373,11 @@ static string FormatCharValue(char value)

static string FormatStringValue(string value)
{
#if NETCOREAPP2_0_OR_GREATER
value = EscapeString(value).Replace(@"""", @"\""", StringComparison.Ordinal); // escape double quotes
#else
value = EscapeString(value).Replace(@"""", @"\"""); // escape double quotes
#endif

if (value.Length > MAX_STRING_LENGTH)
{
Expand Down Expand Up @@ -452,7 +464,11 @@ static string FormatStringValue(string value)
if (result == null)
return typeInfo.Name;

#if NETCOREAPP2_1_OR_GREATER
var tickIdx = result.IndexOf('`', StringComparison.Ordinal);
#else
var tickIdx = result.IndexOf('`');
#endif
if (tickIdx > 0)
result = result.Substring(0, tickIdx);

Expand Down Expand Up @@ -508,7 +524,11 @@ static bool IsAnonymousType(this TypeInfo typeInfo)
if (typeInfo.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) == null)
return false;

#if NETCOREAPP2_1_OR_GREATER
return typeInfo.Name.Contains("AnonymousType", StringComparison.Ordinal);
#else
return typeInfo.Name.Contains("AnonymousType");
#endif
}

static bool IsSZArrayType(this TypeInfo typeInfo)
Expand Down Expand Up @@ -565,8 +585,15 @@ static bool IsSZArrayType(this TypeInfo typeInfo)
return value != null;
}

static Exception UnwrapException(Exception ex)
#if XUNIT_NULLABLE
internal static Exception? UnwrapException(Exception? ex)
#else
internal static Exception UnwrapException(Exception ex)
#endif
{
if (ex == null)
return null;

while (true)
{
var tiex = ex as TargetInvocationException;
Expand Down

0 comments on commit a757c66

Please sign in to comment.