Skip to content

Commit

Permalink
Squashed 'src/Microsoft.DotNet.XUnitAssert/src/' changes from c02d676…
Browse files Browse the repository at this point in the history
…a..46dfaa92

46dfaa92 xunit/xunit#2773: Add Assert.RaisesAny and Assert.RaisesAnyAsync non-generic for EventArgs
c0485bdd Add additional NETSTANDARD excludeions for System.AppDomain usage
b6cb339c xunit/xunit#2767: Verify types match when comparing FileSystemInfo values
03b07513 Use AppDomain.CurrentDomain.GetAssemblies() to find System.IO.FileSystemInfo type
482be8e0 xunit/xunit#2767: Special case FileSystemInfo objects by just comparing the FullName in Assert.Equivalent
78d70dec xunit/xunit#2767: Prevent stack overflow in Assert.Equivalent with a max traversal depth of 50
d0a234a5 Delay calling Dispose() on CollectionTracker's inner enumerator, for xunit/xunit#2762
96236a4c Add disable for CS8607 in AssertEqualityComparerAdapter because of nullability inconsistency
6193f9ee Move to explicit DateTime(Offset) handling in Assert.Equivalent (related: xunit/xunit#2758)
20e76223 xunit/xunit#2743: Assert.Equal with HashSet and custom comparer ignores comparer
247c1016 Mark BitArray as safe to multi-enumerate
8926c0fc Wrap AssertEqualityComparer collection logic in !XUNIT_FRAMEWORK for v2 xunit.execution.*
90d59772 xunit/xunit#2755: Assert.Equal regression for dictionaries with collection values
17c7b611 Add nullable annotation to Assert.NotNull<T>(T?) (dotnet#64)
1886f126 xunit/xunit#2741: Ensure all exception factory methods are public

git-subtree-dir: src/Microsoft.DotNet.XUnitAssert/src
git-subtree-split: 46dfaa9248b7aa4c8c88e5cf6d4a6c84671a93f5
  • Loading branch information
agocke committed Oct 11, 2023
1 parent 499f4c1 commit 9856e56
Show file tree
Hide file tree
Showing 13 changed files with 408 additions and 68 deletions.
4 changes: 2 additions & 2 deletions EqualityAsserts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public static void Equal<T>(

if (itemComparer != null)
{
if (CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, out mismatchedIndex))
if (CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer<T>.DefaultInnerComparer, out mismatchedIndex))
return;

var expectedStartIdx = -1;
Expand Down Expand Up @@ -610,7 +610,7 @@ public static void NotEqual<T>(
if (itemComparer != null)
{
int? mismatchedIndex;
if (!CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, out mismatchedIndex))
if (!CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer<T>.DefaultInnerComparer, out mismatchedIndex))
return;

formattedExpected = expectedTracker?.FormatStart() ?? "null";
Expand Down
146 changes: 143 additions & 3 deletions EventAsserts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Xunit
partial class Assert
{
/// <summary>
/// Verifies that a event with the exact event args is raised.
/// Verifies that an event with the exact event args is raised.
/// </summary>
/// <typeparam name="T">The type of the event arguments to expect</typeparam>
/// <param name="attach">Code to attach the event handler</param>
Expand All @@ -45,6 +45,27 @@ public static RaisedEvent<T> Raises<T>(
return raisedEvent;
}

/// <summary>
/// Verifies that an event is raised.
/// </summary>
/// <param name="attach">Code to attach the event handler</param>
/// <param name="detach">Code to detach the event handler</param>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <returns>The event sender and arguments wrapped in an object</returns>
/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
public static RaisedEvent<EventArgs> RaisesAny(
Action<EventHandler> attach,
Action<EventHandler> detach,
Action testCode)
{
var raisedEvent = RaisesInternal(attach, detach, testCode);

if (raisedEvent == null)
throw RaisesAnyException.ForNoEvent(typeof(EventArgs));

return raisedEvent;
}

/// <summary>
/// Verifies that an event with the exact or a derived event args is raised.
/// </summary>
Expand All @@ -67,6 +88,27 @@ public static RaisedEvent<T> RaisesAny<T>(
return raisedEvent;
}

/// <summary>
/// Verifies that an event is raised.
/// </summary>
/// <param name="attach">Code to attach the event handler</param>
/// <param name="detach">Code to detach the event handler</param>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <returns>The event sender and arguments wrapped in an object</returns>
/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
public static async Task<RaisedEvent<EventArgs>> RaisesAnyAsync(
Action<EventHandler> attach,
Action<EventHandler> detach,
Func<Task> testCode)
{
var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

if (raisedEvent == null)
throw RaisesAnyException.ForNoEvent(typeof(EventArgs));

return raisedEvent;
}

/// <summary>
/// Verifies that an event with the exact or a derived event args is raised.
/// </summary>
Expand All @@ -90,6 +132,27 @@ public static async Task<RaisedEvent<T>> RaisesAnyAsync<T>(
}

#if XUNIT_VALUETASK
/// <summary>
/// Verifies that an event is raised.
/// </summary>
/// <param name="attach">Code to attach the event handler</param>
/// <param name="detach">Code to detach the event handler</param>
/// <param name="testCode">A delegate to the code to be tested</param>
/// <returns>The event sender and arguments wrapped in an object</returns>
/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
public static async ValueTask<RaisedEvent<EventArgs>> RaisesAnyAsync(
Action<EventHandler> attach,
Action<EventHandler> detach,
Func<ValueTask> testCode)
{
var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

if (raisedEvent == null)
throw RaisesAnyException.ForNoEvent(typeof(EventArgs));

return raisedEvent;
}

/// <summary>
/// Verifies that an event with the exact or a derived event args is raised.
/// </summary>
Expand All @@ -114,7 +177,7 @@ public static async ValueTask<RaisedEvent<T>> RaisesAnyAsync<T>(
#endif

/// <summary>
/// Verifies that a event with the exact event args (and not a derived type) is raised.
/// Verifies that an event with the exact event args (and not a derived type) is raised.
/// </summary>
/// <typeparam name="T">The type of the event arguments to expect</typeparam>
/// <param name="attach">Code to attach the event handler</param>
Expand All @@ -140,7 +203,7 @@ public static async Task<RaisedEvent<T>> RaisesAsync<T>(

#if XUNIT_VALUETASK
/// <summary>
/// Verifies that a event with the exact event args (and not a derived type) is raised.
/// Verifies that an event with the exact event args (and not a derived type) is raised.
/// </summary>
/// <typeparam name="T">The type of the event arguments to expect</typeparam>
/// <param name="attach">Code to attach the event handler</param>
Expand All @@ -163,8 +226,33 @@ public static async ValueTask<RaisedEvent<T>> RaisesAsync<T>(

return raisedEvent;
}
#endif

#if XUNIT_NULLABLE
static RaisedEvent<EventArgs>? RaisesInternal(
#else
static RaisedEvent<EventArgs> RaisesInternal(
#endif
Action<EventHandler> attach,
Action<EventHandler> detach,
Action testCode)
{
GuardArgumentNotNull(nameof(attach), attach);
GuardArgumentNotNull(nameof(detach), detach);
GuardArgumentNotNull(nameof(testCode), testCode);

#if XUNIT_NULLABLE
RaisedEvent<EventArgs>? raisedEvent = null;
void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#else
RaisedEvent<EventArgs> raisedEvent = null;
EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#endif
attach(handler);
testCode();
detach(handler);
return raisedEvent;
}

#if XUNIT_NULLABLE
static RaisedEvent<T>? RaisesInternal<T>(
Expand Down Expand Up @@ -192,6 +280,32 @@ static RaisedEvent<T> RaisesInternal<T>(
return raisedEvent;
}

#if XUNIT_NULLABLE
static async Task<RaisedEvent<EventArgs>?> RaisesAsyncInternal(
#else
static async Task<RaisedEvent<EventArgs>> RaisesAsyncInternal(
#endif
Action<EventHandler> attach,
Action<EventHandler> detach,
Func<Task> testCode)
{
GuardArgumentNotNull(nameof(attach), attach);
GuardArgumentNotNull(nameof(detach), detach);
GuardArgumentNotNull(nameof(testCode), testCode);

#if XUNIT_NULLABLE
RaisedEvent<EventArgs>? raisedEvent = null;
void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#else
RaisedEvent<EventArgs> raisedEvent = null;
EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#endif
attach(handler);
await testCode();
detach(handler);
return raisedEvent;
}

#if XUNIT_NULLABLE
static async Task<RaisedEvent<T>?> RaisesAsyncInternal<T>(
#else
Expand Down Expand Up @@ -219,6 +333,32 @@ static async Task<RaisedEvent<T>> RaisesAsyncInternal<T>(
}

#if XUNIT_VALUETASK
#if XUNIT_NULLABLE
static async ValueTask<RaisedEvent<EventArgs>?> RaisesAsyncInternal(
#else
static async Task<RaisedEvent<EventArgs>> RaisesAsyncInternal(
#endif
Action<EventHandler> attach,
Action<EventHandler> detach,
Func<ValueTask> testCode)
{
GuardArgumentNotNull(nameof(attach), attach);
GuardArgumentNotNull(nameof(detach), detach);
GuardArgumentNotNull(nameof(testCode), testCode);

#if XUNIT_NULLABLE
RaisedEvent<EventArgs>? raisedEvent = null;
void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#else
RaisedEvent<EventArgs> raisedEvent = null;
EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#endif
attach(handler);
await testCode();
detach(handler);
return raisedEvent;
}

#if XUNIT_NULLABLE
static async ValueTask<RaisedEvent<T>?> RaisesAsyncInternal<T>(
#else
Expand Down
4 changes: 4 additions & 0 deletions NullAsserts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ public static void NotNull(object @object)
/// <param name="value">The value to e validated</param>
/// <returns>The non-<c>null</c> value</returns>
/// <exception cref="NotNullException">Thrown when the value is null</exception>
#if XUNIT_NULLABLE
public static T NotNull<T>([NotNull] T? value)
#else
public static T NotNull<T>(T? value)
#endif
where T : struct
{
if (!value.HasValue)
Expand Down
1 change: 1 addition & 0 deletions Sdk/ArgumentFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ static bool SafeToMultiEnumerate(IEnumerable? collection) =>
static bool SafeToMultiEnumerate(IEnumerable collection) =>
#endif
collection is Array ||
collection is BitArray ||
collection is IList ||
collection is IDictionary ||
GetSetElementType(collection) != null;
Expand Down
15 changes: 13 additions & 2 deletions Sdk/AssertEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ namespace Xunit.Sdk
/// <typeparam name="T">The type that is being compared.</typeparam>
class AssertEqualityComparer<T> : IEqualityComparer<T>
{
static readonly IEqualityComparer DefaultInnerComparer = new AssertEqualityComparerAdapter<object>(new AssertEqualityComparer<object>());
static readonly TypeInfo NullableTypeInfo = typeof(Nullable<>).GetTypeInfo();
internal static readonly IEqualityComparer DefaultInnerComparer = new AssertEqualityComparerAdapter<object>(new AssertEqualityComparer<object>());

readonly Lazy<IEqualityComparer> innerComparer;

Expand Down Expand Up @@ -67,6 +66,18 @@ public bool Equals(
if (x == null || y == null)
return false;

#if !XUNIT_FRAMEWORK
// Collections?
using (var xTracker = x.AsNonStringTracker())
using (var yTracker = y.AsNonStringTracker())
{
int? _;

if (xTracker != null && yTracker != null)
return CollectionTracker.AreCollectionsEqual(xTracker, yTracker, InnerComparer, InnerComparer == DefaultInnerComparer, out _);
}
#endif

// Implements IEquatable<T>?
var equatable = x as IEquatable<T>;
if (equatable != null)
Expand Down
31 changes: 25 additions & 6 deletions Sdk/AssertEqualityComparerAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
namespace Xunit.Sdk
{
/// <summary>
/// A class that wraps <see cref="IEqualityComparer{T}"/> to create <see cref="IEqualityComparer"/>.
/// A class that wraps <see cref="IEqualityComparer{T}"/> to add <see cref="IEqualityComparer"/>.
/// </summary>
/// <typeparam name="T">The type that is being compared.</typeparam>
class AssertEqualityComparerAdapter<T> : IEqualityComparer
class AssertEqualityComparerAdapter<T> : IEqualityComparer, IEqualityComparer<T>
{
readonly IEqualityComparer<T> innerComparer;

Expand Down Expand Up @@ -44,9 +44,28 @@ public AssertEqualityComparerAdapter(IEqualityComparer<T> innerComparer)
#endif

/// <inheritdoc/>
public int GetHashCode(object obj)
{
throw new NotImplementedException();
}
public bool Equals(
#if XUNIT_NULLABLE
T? x,
T? y) =>
#else
T x,
T y) =>
#endif
innerComparer.Equals(x, y);


/// <inheritdoc/>
public int GetHashCode(object obj) =>
innerComparer.GetHashCode((T)obj);

// This warning disable is here because sometimes IEqualityComparer<T>.GetHashCode marks the obj parameter
// with [DisallowNull] and sometimes it doesn't, and we need to be able to support both scenarios when
// someone brings in the assertion library via source.
#pragma warning disable CS8607
/// <inheritdoc/>
public int GetHashCode(T obj) =>
innerComparer.GetHashCode(obj);
#pragma warning restore CS8607
}
}
Loading

0 comments on commit 9856e56

Please sign in to comment.