Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions TUnit.Assertions.Tests/MemoryAssertionTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#if NET5_0_OR_GREATER
using TUnit.Assertions.Enums;

namespace TUnit.Assertions.Tests;

public class MemoryAssertionTests
Expand Down Expand Up @@ -131,6 +133,68 @@ public async Task Test_ReadOnlyMemory_Chaining_With_And()
await Assert.That(memory).IsNotEmpty().And.Contains(2);
}

// IsEquivalentTo tests
[Test]
public async Task Test_Memory_IsEquivalentTo()
{
Memory<int> memory = new[] { 3, 1, 2 };
await Assert.That(memory).IsEquivalentTo(new[] { 1, 2, 3 });
}

[Test]
public async Task Test_Memory_IsEquivalentTo_Ordered()
{
Memory<int> memory = new[] { 1, 2, 3 };
await Assert.That(memory).IsEquivalentTo(new[] { 1, 2, 3 }, CollectionOrdering.Matching);
}

[Test]
public async Task Test_Memory_IsEquivalentTo_Fails()
{
Memory<int> memory = new[] { 1, 2, 3 };
var action = async () => await Assert.That(memory).IsEquivalentTo(new[] { 1, 2, 4 });

var exception = await Assert.That(action).Throws<AssertionException>();

await Assert.That(exception.Message).Contains("does not contain expected item: 4");
}

[Test]
public async Task Test_Memory_IsEquivalentTo_DifferentCount_Fails()
{
Memory<int> memory = new[] { 1, 2, 3 };
var action = async () => await Assert.That(memory).IsEquivalentTo(new[] { 1, 2 });

var exception = await Assert.That(action).Throws<AssertionException>();

await Assert.That(exception.Message).Contains("3 items but expected 2");
}

[Test]
public async Task Test_ReadOnlyMemory_IsEquivalentTo()
{
ReadOnlyMemory<byte> memory = new byte[] { 0x01, 0x02, 0x03 };
await Assert.That(memory).IsEquivalentTo(new byte[] { 0x01, 0x02, 0x03 });
}

[Test]
public async Task Test_ReadOnlyMemory_IsEquivalentTo_Unordered()
{
ReadOnlyMemory<byte> memory = new byte[] { 0x03, 0x01, 0x02 };
await Assert.That(memory).IsEquivalentTo(new byte[] { 0x01, 0x02, 0x03 });
}

[Test]
public async Task Test_ReadOnlyMemory_IsEquivalentTo_Fails()
{
ReadOnlyMemory<byte> memory = new byte[] { 0x01, 0x02, 0x03 };
var action = async () => await Assert.That(memory).IsEquivalentTo(new byte[] { 0x01, 0x02, 0x04 });

var exception = await Assert.That(action).Throws<AssertionException>();

await Assert.That(exception.Message).Contains("does not contain expected item");
}

// Failure tests
[Test]
public async Task Test_Memory_IsEmpty_Fails()
Expand Down
68 changes: 68 additions & 0 deletions TUnit.Assertions/Collections/MemoryAssertions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
using TUnit.Assertions.Abstractions;
using TUnit.Assertions.Conditions.Helpers;
using TUnit.Assertions.Core;
using TUnit.Assertions.Enums;
using TUnit.Assertions.Sources;

namespace TUnit.Assertions.Collections;
Expand Down Expand Up @@ -161,6 +164,71 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TMemory>
protected override string GetExpectation() => $"to not contain {_expected}";
}

/// <summary>
/// Asserts that a memory is equivalent to an expected collection.
/// </summary>
public class MemoryIsEquivalentToAssertion<TMemory, TItem> : MemoryAssertionBase<TMemory, TItem>
{
private readonly Func<TMemory, ICollectionAdapter<TItem>> _adapterFactory;
private readonly IEnumerable<TItem> _expected;
private readonly IEqualityComparer<TItem> _comparer;
private readonly CollectionOrdering _ordering;

[RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")]
public MemoryIsEquivalentToAssertion(
AssertionContext<TMemory> context,
Func<TMemory, ICollectionAdapter<TItem>> adapterFactory,
IEnumerable<TItem> expected,
CollectionOrdering ordering = CollectionOrdering.Any)
: this(context, adapterFactory, expected, StructuralEqualityComparer<TItem>.Instance, ordering)
{
}

public MemoryIsEquivalentToAssertion(
AssertionContext<TMemory> context,
Func<TMemory, ICollectionAdapter<TItem>> adapterFactory,
IEnumerable<TItem> expected,
IEqualityComparer<TItem> comparer,
CollectionOrdering ordering = CollectionOrdering.Any)
: base(context)
{
_adapterFactory = adapterFactory;
_expected = expected ?? throw new ArgumentNullException(nameof(expected));
_comparer = comparer;
_ordering = ordering;
}

protected override ICollectionAdapter<TItem> CreateAdapter(TMemory value) => _adapterFactory(value);

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TMemory> metadata)
{
if (metadata.Exception != null)
{
return Task.FromResult(AssertionResult.Failed($"threw {metadata.Exception.GetType().Name}"));
}

if (metadata.Value == null)
{
return Task.FromResult(AssertionResult.Failed("value was null"));
}

var adapter = _adapterFactory(metadata.Value);

var result = CollectionEquivalencyChecker.AreEquivalent(
adapter.AsEnumerable(),
_expected,
_ordering,
_comparer);

return Task.FromResult(result.AreEquivalent
? AssertionResult.Passed
: AssertionResult.Failed(result.ErrorMessage!));
}

protected override string GetExpectation() =>
$"to be equivalent to [{string.Join(", ", _expected)}]";
}

/// <summary>
/// Asserts that a memory has exactly one item.
/// </summary>
Expand Down
62 changes: 62 additions & 0 deletions TUnit.Assertions/Sources/MemoryAssertionBase.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using TUnit.Assertions.Abstractions;
using TUnit.Assertions.Adapters;
using TUnit.Assertions.Collections;
using TUnit.Assertions.Conditions;
using TUnit.Assertions.Core;
using TUnit.Assertions.Enums;

namespace TUnit.Assertions.Sources;

Expand Down Expand Up @@ -211,6 +213,66 @@ public MemoryIsInDescendingOrderAssertion<TMemory, TItem> IsInDescendingOrder()
return new MemoryIsInDescendingOrderAssertion<TMemory, TItem>(Context, CreateAdapter);
}

/// <summary>
/// Asserts that the memory is equivalent to the expected collection.
/// Two collections are equivalent if they contain the same elements, regardless of order (default).
/// </summary>
[RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")]
public MemoryIsEquivalentToAssertion<TMemory, TItem> IsEquivalentTo(
IEnumerable<TItem> expected,
CollectionOrdering ordering = CollectionOrdering.Any,
[CallerArgumentExpression(nameof(expected))] string? expectedExpression = null,
[CallerArgumentExpression(nameof(ordering))] string? orderingExpression = null)
{
Context.ExpressionBuilder.Append(".IsEquivalentTo(");
var added = false;
if (expectedExpression != null)
{
Context.ExpressionBuilder.Append(expectedExpression);
added = true;
}
if (orderingExpression != null)
{
Context.ExpressionBuilder.Append(added ? ", " : "");
Context.ExpressionBuilder.Append(orderingExpression);
}
Context.ExpressionBuilder.Append(')');
return new MemoryIsEquivalentToAssertion<TMemory, TItem>(Context, CreateAdapter, expected, ordering);
}

/// <summary>
/// Asserts that the memory is equivalent to the expected collection using a custom equality comparer.
/// </summary>
public MemoryIsEquivalentToAssertion<TMemory, TItem> IsEquivalentTo(
IEnumerable<TItem> expected,
IEqualityComparer<TItem> comparer,
CollectionOrdering ordering = CollectionOrdering.Any,
[CallerArgumentExpression(nameof(expected))] string? expectedExpression = null,
[CallerArgumentExpression(nameof(comparer))] string? comparerExpression = null,
[CallerArgumentExpression(nameof(ordering))] string? orderingExpression = null)
{
Context.ExpressionBuilder.Append(".IsEquivalentTo(");
var added = false;
if (expectedExpression != null)
{
Context.ExpressionBuilder.Append(expectedExpression);
added = true;
}
if (comparerExpression != null)
{
Context.ExpressionBuilder.Append(added ? ", " : "");
Context.ExpressionBuilder.Append(comparerExpression);
added = true;
}
if (orderingExpression != null)
{
Context.ExpressionBuilder.Append(added ? ", " : "");
Context.ExpressionBuilder.Append(orderingExpression);
}
Context.ExpressionBuilder.Append(')');
return new MemoryIsEquivalentToAssertion<TMemory, TItem>(Context, CreateAdapter, expected, comparer, ordering);
}

/// <summary>
/// Asserts that all items in the memory are distinct.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,16 @@ namespace .Collections
protected override .<TItem> CreateAdapter(TMemory value) { }
protected override string GetExpectation() { }
}
public class MemoryIsEquivalentToAssertion<TMemory, TItem> : .<TMemory, TItem>
{
[.("Collection equivalency uses structural comparison for complex objects, which requ" +
"ires reflection and is not compatible with AOT")]
public MemoryIsEquivalentToAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem> expected, . ordering = 0) { }
public MemoryIsEquivalentToAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem> expected, .<TItem> comparer, . ordering = 0) { }
protected override .<.> CheckAsync(.<TMemory> metadata) { }
protected override .<TItem> CreateAdapter(TMemory value) { }
protected override string GetExpectation() { }
}
public class MemoryIsInDescendingOrderAssertion<TMemory, TItem> : .<TMemory, TItem>
{
public MemoryIsInDescendingOrderAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem>? comparer = null) { }
Expand Down Expand Up @@ -5973,6 +5983,10 @@ namespace .Sources
public .<TSource, TMemory> IsAssignableFrom<TSource>() { }
public .<TTarget, TMemory> IsAssignableTo<TTarget>() { }
public .<TMemory, TItem> IsEmpty() { }
[.("Collection equivalency uses structural comparison for complex objects, which requ" +
"ires reflection and is not compatible with AOT")]
public .<TMemory, TItem> IsEquivalentTo(.<TItem> expected, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("ordering")] string? orderingExpression = null) { }
public .<TMemory, TItem> IsEquivalentTo(.<TItem> expected, .<TItem> comparer, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) { }
public .<TMemory, TItem> IsInDescendingOrder() { }
public .<TMemory, TItem> IsInOrder() { }
public .<TSource, TMemory> IsNotAssignableFrom<TSource>() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,16 @@ namespace .Collections
protected override .<TItem> CreateAdapter(TMemory value) { }
protected override string GetExpectation() { }
}
public class MemoryIsEquivalentToAssertion<TMemory, TItem> : .<TMemory, TItem>
{
[.("Collection equivalency uses structural comparison for complex objects, which requ" +
"ires reflection and is not compatible with AOT")]
public MemoryIsEquivalentToAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem> expected, . ordering = 0) { }
public MemoryIsEquivalentToAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem> expected, .<TItem> comparer, . ordering = 0) { }
protected override .<.> CheckAsync(.<TMemory> metadata) { }
protected override .<TItem> CreateAdapter(TMemory value) { }
protected override string GetExpectation() { }
}
public class MemoryIsInDescendingOrderAssertion<TMemory, TItem> : .<TMemory, TItem>
{
public MemoryIsInDescendingOrderAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem>? comparer = null) { }
Expand Down Expand Up @@ -5920,6 +5930,10 @@ namespace .Sources
public .<TSource, TMemory> IsAssignableFrom<TSource>() { }
public .<TTarget, TMemory> IsAssignableTo<TTarget>() { }
public .<TMemory, TItem> IsEmpty() { }
[.("Collection equivalency uses structural comparison for complex objects, which requ" +
"ires reflection and is not compatible with AOT")]
public .<TMemory, TItem> IsEquivalentTo(.<TItem> expected, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("ordering")] string? orderingExpression = null) { }
public .<TMemory, TItem> IsEquivalentTo(.<TItem> expected, .<TItem> comparer, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) { }
public .<TMemory, TItem> IsInDescendingOrder() { }
public .<TMemory, TItem> IsInOrder() { }
public .<TSource, TMemory> IsNotAssignableFrom<TSource>() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,16 @@ namespace .Collections
protected override .<TItem> CreateAdapter(TMemory value) { }
protected override string GetExpectation() { }
}
public class MemoryIsEquivalentToAssertion<TMemory, TItem> : .<TMemory, TItem>
{
[.("Collection equivalency uses structural comparison for complex objects, which requ" +
"ires reflection and is not compatible with AOT")]
public MemoryIsEquivalentToAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem> expected, . ordering = 0) { }
public MemoryIsEquivalentToAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem> expected, .<TItem> comparer, . ordering = 0) { }
protected override .<.> CheckAsync(.<TMemory> metadata) { }
protected override .<TItem> CreateAdapter(TMemory value) { }
protected override string GetExpectation() { }
}
public class MemoryIsInDescendingOrderAssertion<TMemory, TItem> : .<TMemory, TItem>
{
public MemoryIsInDescendingOrderAssertion(.<TMemory> context, <TMemory, .<TItem>> adapterFactory, .<TItem>? comparer = null) { }
Expand Down Expand Up @@ -5973,6 +5983,10 @@ namespace .Sources
public .<TSource, TMemory> IsAssignableFrom<TSource>() { }
public .<TTarget, TMemory> IsAssignableTo<TTarget>() { }
public .<TMemory, TItem> IsEmpty() { }
[.("Collection equivalency uses structural comparison for complex objects, which requ" +
"ires reflection and is not compatible with AOT")]
public .<TMemory, TItem> IsEquivalentTo(.<TItem> expected, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("ordering")] string? orderingExpression = null) { }
public .<TMemory, TItem> IsEquivalentTo(.<TItem> expected, .<TItem> comparer, . ordering = 0, [.("expected")] string? expectedExpression = null, [.("comparer")] string? comparerExpression = null, [.("ordering")] string? orderingExpression = null) { }
public .<TMemory, TItem> IsInDescendingOrder() { }
public .<TMemory, TItem> IsInOrder() { }
public .<TSource, TMemory> IsNotAssignableFrom<TSource>() { }
Expand Down
Loading