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

Add support for KeyValuePair<,> in AssertEqualityComparer to enable collections in values #2803

Closed
nachtjasmin opened this issue Oct 25, 2023 · 4 comments

Comments

@nachtjasmin
Copy link

While working on a .NET 6 library, I've stumbled across the following bug:

When asserting a class that's deriving from System.Net.Http.Headers.HttpHeaders, the equality assertion does not work as intended.

Test case

using Xunit;
using System.Net.Http.Headers;

namespace xunit_http_headers_collection_repro;

public class MyHttpHeaders : HttpHeaders {}

public class UnitTest1
{
    [Fact]
    public void Test()
    {
        var a = new MyHttpHeaders();
        var b = new MyHttpHeaders();

        a.Add("test", "test");
        b.Add("test", "test");

        Assert.Equal(a, b);
    }
}

The output message

  Failed xunit_http_headers_collection_repro.UnitTest1.Test [18 ms]
  Error Message:
   Assert.Equal() Failure: Collections differ
           ↓ (pos 0)
Expected: [["test"] = ["test"]]
Actual:   [["test"] = ["test"]]
           ↑ (pos 0)

Clearly the collections are identical, so I assume a bug in here. The xunit version is 2.5.3 and there's a full repro repository here.

@bradwilson
Copy link
Member

HttpHeaders does not implement any of the equatable interfaces that we look for (IComparable, IEquatable, IStructuralEquatable, etc.), so we fall back to default logic.

HttpHeaders does implement IEnumerable<> so our default logic treats it like a collection. The values is enumerates are of type KeyValuePair<string, IEnumerable<string>>, which also does not implement any of the equatable interfaces. So as we iterate through each item, we end up using default Equals logic to determine if the values are equals.

Since KeyValuePair is a struct, that means the default behavior is to compare all of its members using Equals (which are Key and Value). The key type is string so comparisons are fine, but the value type is IEnumerable<string>, which will end up comparing via reference. Since the two collections stored internally are different instances (regardless of whether those instances contain identical values), that will fail.

We have not created any special logic for HttpHeaders, nor do I think we should. In general we expect the things that people want to compare for equality either fall into the bucket of things we have special treatment for, or they are responsible for their own definition of equality. HttpHeaders definitely falls into the latter category.

We also have not created any special logic for handling KeyValuePair<K,V>. This one, I'm not 100% sure about whether we should. We obviously already have special logic for dictionaries (and if HttpHeaders had presented itself as a dictionary, rather than a simple collection, we would have chosen that path).

Let me think on whether we want to add special case code for KeyValuePair.

@bradwilson
Copy link
Member

Yeah, I think this is a good idea, and it's pretty simple to add.

@bradwilson bradwilson changed the title v2: CollectionAssertions do not work with HttpHeaders Add support for KeyValuePair<,> in AssertEqualityComparer to enable collections in values Oct 25, 2023
bradwilson added a commit to xunit/assert.xunit that referenced this issue Oct 25, 2023
bradwilson added a commit that referenced this issue Oct 25, 2023
bradwilson added a commit that referenced this issue Oct 25, 2023
@bradwilson
Copy link
Member

Available in v2 2.5.4-pre.6
Available in v3 0.1.1-pre.300

@nachtjasmin
Copy link
Author

Damn, thanks for the very detailed explanation and the quick fix! I appreciate it a lot! 😮

ViktorHofer added a commit to v-wuzhai/arcade that referenced this issue Nov 14, 2023
…b0..cb1e33ba6

cb1e33ba6 Updated doc comment for AllException.ForFailures
5986aa7bf Remove all ValueTask support
e48a30666 Performance optimization: unused code in comparison hot path
094b4d4cf Formatting
b3e3c931a Performance improvements in CollectionTrackerExtensions.AsTracker
6a94a86a0 Performance improvements in AssertEqualityComparer (conditional IEquatable<Y> and IComparable<Y> with type caching)
f1843557b Replace dictionary with ring buffer in CollectionTracker.Enumerator
3bb330972 Don't depend on the Assert class inside AssertEqualityComparer
10f8fe362 Ensure all throws of NotImplementedException contain messages
37e83a858 Ensure all GetHashCode functions are implemented (xunit/xunit#2804)
4828bf193 xunit/xunit#2803: Add support for KeyValuePair<,> in AssertEqualityComparer to enable collections in values
a92673b02 xunit/xunit#2800: Record exceptions from Assert.(Not)Equal comparer
ba2525400 Replace concatenation and interpolation with String.Format

git-subtree-dir: src/Microsoft.DotNet.XUnitAssert/src
git-subtree-split: cb1e33ba612d4829e0848a44bde1026f5f9e3576
ViktorHofer added a commit to v-wuzhai/arcade that referenced this issue Nov 14, 2023
…b0..cb1e33ba6

cb1e33ba6 Updated doc comment for AllException.ForFailures
5986aa7bf Remove all ValueTask support
e48a30666 Performance optimization: unused code in comparison hot path
094b4d4cf Formatting
b3e3c931a Performance improvements in CollectionTrackerExtensions.AsTracker
6a94a86a0 Performance improvements in AssertEqualityComparer (conditional IEquatable<Y> and IComparable<Y> with type caching)
f1843557b Replace dictionary with ring buffer in CollectionTracker.Enumerator
3bb330972 Don't depend on the Assert class inside AssertEqualityComparer
10f8fe362 Ensure all throws of NotImplementedException contain messages
37e83a858 Ensure all GetHashCode functions are implemented (xunit/xunit#2804)
4828bf193 xunit/xunit#2803: Add support for KeyValuePair<,> in AssertEqualityComparer to enable collections in values
a92673b02 xunit/xunit#2800: Record exceptions from Assert.(Not)Equal comparer
ba2525400 Replace concatenation and interpolation with String.Format

git-subtree-dir: src/Microsoft.DotNet.XUnitAssert/src
git-subtree-split: cb1e33ba612d4829e0848a44bde1026f5f9e3576
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants