using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;
using Xunit.Sdk;

#if XUNIT_IMMUTABLE_COLLECTIONS
using System.Collections.Immutable;
#endif

public class EquivalenceAssertsTests
{
	public class NullValues
	{
		[Fact]
		public void TwoNullsAreEquivalent()
		{
			Assert.Equivalent(null, null);
		}

		[Theory]
		[InlineData(null, 42)]
		[InlineData(42, null)]
		public void NullIsNotEquivalentToNonNull(
			object? expected,
			object? actual)
		{
			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				$"Expected: {expected ?? "null"}" + Environment.NewLine +
				$"Actual:   {actual ?? "null"}",
				ex.Message
			);
		}
	}

	public class ValueTypes
	{
		[Theory]
		[InlineData(42, 42)]
		[InlineData(2112L, 2112L)]
		[InlineData('a', 'a')]
		[InlineData(1.1f, 1.1f)]
		[InlineData(1.1, 1.1)]
		[InlineData(true, true)]
		[InlineData(ConsoleKey.A, ConsoleKey.A)]
		public void SameType_Success(
			object? expected,
			object? actual)
		{
			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void SameType_Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(12, 13));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 12" + Environment.NewLine +
				"Actual:   13",
				ex.Message
			);
		}

		[Fact]
		public void SameValueFromDifferentIntrinsicTypes_Success()
		{
			Assert.Equivalent(12, 12L);
		}

				// https://github.com/xunit/xunit/issues/2913
		[Fact]
		public void Decimals_Success()
		{
			Assert.Equivalent(1m, 1m);
		}

		// https://github.com/xunit/xunit/issues/2913
		[Fact]
		public void Decimals_Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(1m, 2m));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 1" + Environment.NewLine +
				"Actual:   2",
				ex.Message
			);
		}

		// https://github.com/xunit/xunit/issues/2913
		[Fact]
		public void IntrinsicPlusNonIntrinsic_Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(1m, new object()));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 1" + Environment.NewLine +
				"Actual:   Object { }",
				ex.Message
			);
		}
	}

	public class NullableValueTypes
	{
		[Fact]
		public void Success()
		{
			int? expected = 42;
			int? actual = 42;

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			int? expected = 42;
			int? actual = 2112;

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class ValueTypes_Identical_Deep
	{
		[Fact]
		public void Success()
		{
			var expected = new DeepStruct(new ShallowClass { Value1 = 42, Value2 = "Hello, world!" });
			var actual = new DeepStruct(new ShallowClass { Value1 = 42, Value2 = "Hello, world!" });

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			var expected = new DeepStruct(new ShallowClass { Value1 = 42, Value2 = "Hello, world!" });
			var actual = new DeepStruct(new ShallowClass { Value1 = 13, Value2 = "Hello, world!" });

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Shallow.Value1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   13",
				ex.Message
			);
		}
	}

	public class Strings
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent("Hello", "Hello");
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent("Hello, world", "Hello, world!"));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: \"Hello, world\"" + Environment.NewLine +
				"Actual:   \"Hello, world!\"",
				ex.Message
			);
		}

		[Fact]
		public void NullIsNotEquivalentToEmptyString()
		{
			var ex = Record.Exception(() => Assert.Equivalent(null, string.Empty));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: null" + Environment.NewLine +
				"Actual:   \"\"",
				ex.Message
			);
		}
	}

	public class AnonymousTypes_Identical_Shallow
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new { x = 42 }, new { x = 42 });
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new { x = 42 }, new { x = 2112 }));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'x'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class AnonymousTypes_Identical_Deep
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new { x = new { y = 42 } }, new { x = new { y = 42 } });
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new { x = new { y = 42 } }, new { x = new { y = 2112 } }));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'x.y'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class AnonymousTypes_Compatible_Shallow
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new { x = 42, y = 2112 }, new { y = 2112, x = 42 });
		}

		[Fact]
		public void Success_IgnorePrivateValue()
		{
			var expected = new PrivateMembersClass(1, "help");
			var actual = new PrivateMembersClass(2, "me");

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new { x = 42, y = 2600 }, new { y = 2600, x = 2112 }));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'x'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class AnonymousTypes_Compatible_Deep
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new { x = new { y = 2112 }, z = 42 }, new { z = 42, x = new { y = 2112 } });
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new { x = new { y = 2600 }, z = 42 }, new { z = 42, x = new { y = 2112 } }));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'x.y'" + Environment.NewLine +
				"Expected: 2600" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class ComplexTypes_Identical_Shallow_NotStructuralEquatable
	{
		[Fact]
		public void Success()
		{
			var expected = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" };
			var actual = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" };

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Success_IgnoreStaticValue()
		{
			try
			{
				ShallowClass.StaticValue = 1;
				ShallowClass2.StaticValue = 2;

				var expected = new ShallowClass();
				var actual = new ShallowClass2();

				Assert.Equivalent(expected, actual);
			}
			finally
			{
				ShallowClass.StaticValue = default;
				ShallowClass2.StaticValue = default;
			}
		}

		[Fact]
		public void Failure()
		{
			var expected = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" };
			var actual = new ShallowClass { Value1 = 2112, Value2 = "Hello, world!" };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Value1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class ComplexTypes_Identical_Deep_NotStructuralEquatable
	{
		[Fact]
		public void Success()
		{
			var expected = new DeepClass { Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };
			var actual = new DeepClass { Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			var expected = new DeepClass { Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };
			var actual = new DeepClass { Shallow = new ShallowClass { Value1 = 2600, Value2 = "Hello, world!" }, Value3 = 21.12m };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Shallow.Value1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2600",
				ex.Message
			);
		}
	}

	public class ComplexTypes_Compatible_Shallow_NotStructuralEquatable
	{
		[Fact]
		public void Success()
		{
			var expected = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" };
			var actual = new ShallowClass2 { Value1 = 42, Value2 = "Hello, world!" };

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			var expected = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" };
			var actual = new ShallowClass2 { Value1 = 2112, Value2 = "Hello, world!" };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Value1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class ComplexTypes_Compatible_Deep_NotStructuralEquatable
	{
		[Fact]
		public void Success()
		{
			var expected = new DeepClass { Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };
			var actual = new DeepClass2 { Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			var expected = new DeepClass { Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };
			var actual = new DeepClass2 { Shallow = new ShallowClass { Value1 = 2600, Value2 = "Hello, world!" }, Value3 = 21.12m };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Shallow.Value1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2600",
				ex.Message
			);
		}
	}

	public class MixedComplexAndAnonymousTypes
	{
		[Fact]
		public void Success()
		{
			var expected = new { Shallow = new { Value1 = 42, Value2 = "Hello, world!" }, Value3 = 21.12m };
			var actual = new DeepClass { Value3 = 21.12m, Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" } };

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void Failure()
		{
			var expected = new { Shallow = new { Value1 = 42, Value2 = "Hello, world" }, Value3 = 21.12m };
			var actual = new DeepClass { Value3 = 21.12m, Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello, world!" } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Shallow.Value2'" + Environment.NewLine +
				"Expected: \"Hello, world\"" + Environment.NewLine +
				"Actual:   \"Hello, world!\"",
				ex.Message
			);
		}
	}

	public class MismatchedMembers_NotStrict
	{
		[Fact]
		public void Shallow_Success()
		{
			// Expected can be subset of Actual when strict is false
			Assert.Equivalent(
				new { x = 42 },
				new { x = 42, y = 2112 },
				strict: false
			);
		}

		[Fact]
		public void Deep_Success()
		{
			// Expected can be subset of Actual when strict is false
			Assert.Equivalent(
				new { w = 42, x = new { y = 2112 } },
				new { w = 42, x = new { y = 2112, z = 2600 } },
				strict: false
			);
		}

		[Fact]
		public void Shallow_Failure()
		{
			// Expected can never be superset of Actual
			var ex = Record.Exception(
				() => Assert.Equivalent(
					new { x = 42, y = 2112 },
					new { x = 42, z = 2112 },
					strict: false
				)
			);

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched member list" + Environment.NewLine +
				"Expected: [\"x\", \"y\"]" + Environment.NewLine +
				"Actual:   [\"x\", \"z\"]",
				ex.Message
			);
		}

		[Fact]
		public void Deep_Failure()
		{
			// Expected can never be superset of Actual
			var ex = Record.Exception(
				() => Assert.Equivalent(
					new { w = 42, x = new { y = 2112 } },
					new { w = 42, x = new { z = 2112 } },
					strict: false
				)
			);

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched member list" + Environment.NewLine +
				"Expected: [\"x.y\"]" + Environment.NewLine +
				"Actual:   [\"x.z\"]",
				ex.Message
			);
		}
	}

	public class MismatchedMembers_Strict
	{
		[Fact]
		public void Failure()
		{
			// Expected cannot be subset of Actual when strict is true
			var ex = Record.Exception(
				() => Assert.Equivalent(
					new { x = 42 },
					new { x = 42, y = 2112 },
					strict: true
				)
			);

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched member list" + Environment.NewLine +
				"Expected: [\"x\"]" + Environment.NewLine +
				"Actual:   [\"x\", \"y\"]",
				ex.Message
			);
		}
	}

	public class ArrayOfValueTypes_NotStrict
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new[] { 1, 4 }, new[] { 9, 4, 1 }, strict: false);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { 1, 4 } };
			var actual = new { x = new[] { 9, 4, 1 } };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new[] { 1, 6 }, new[] { 9, 4, 1 }, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray()
		{
			var expected = new { x = new[] { 1, 6 } };
			var actual = new { x = new[] { 9, 4, 1 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}
	}

	public class ArrayOfValueTypes_Strict
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new[] { 1, 9, 4 }, new[] { 9, 4, 1 }, strict: true);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { 1, 9, 4 } };
			var actual = new { x = new[] { 9, 4, 1 } };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Failure_ValueNotFoundInActual()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new[] { 1, 6 }, new[] { 9, 4, 1 }, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_ExtraValueInActual()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new[] { 1, 9, 4 }, new[] { 6, 9, 4, 1 }, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found" + Environment.NewLine +
				"Expected: [1, 9, 4]" + Environment.NewLine +
				"Actual:   [6] left over from [6, 9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ValueNotFoundInActual()
		{
			var expected = new { x = new[] { 1, 6 } };
			var actual = new { x = new[] { 9, 4, 1 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ExtraValueInActual()
		{
			var expected = new { x = new[] { 1, 9, 4 } };
			var actual = new { x = new[] { 6, 9, 4, 1, 12 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine +
				"Expected: [1, 9, 4]" + Environment.NewLine +
				"Actual:   [6, 12] left over from [6, 9, 4, 1, 12]",
				ex.Message
			);
		}
	}

#if XUNIT_IMMUTABLE_COLLECTIONS

	public class ImmutableArrayOfValueTypes_NotStrict
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new[] { 1, 4 }.ToImmutableArray(), new[] { 9, 4, 1 }.ToImmutableArray(), strict: false);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { 1, 4 }.ToImmutableArray() };
			var actual = new { x = new[] { 9, 4, 1 }.ToImmutableArray() };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Failure()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new[] { 1, 6 }.ToImmutableArray(), new[] { 9, 4, 1 }.ToImmutableArray(), strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray()
		{
			var expected = new { x = new[] { 1, 6 }.ToImmutableArray() };
			var actual = new { x = new[] { 9, 4, 1 }.ToImmutableArray() };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}
	}

	public class ImmutableArrayOfValueTypes_Strict
	{
		[Fact]
		public void Success()
		{
			Assert.Equivalent(new[] { 1, 9, 4 }.ToImmutableArray(), new[] { 9, 4, 1 }.ToImmutableArray(), strict: true);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { 1, 9, 4 }.ToImmutableArray() };
			var actual = new { x = new[] { 9, 4, 1 }.ToImmutableArray() };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Failure_ValueNotFoundInActual()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new[] { 1, 6 }.ToImmutableArray(), new[] { 9, 4, 1 }.ToImmutableArray(), strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_ExtraValueInActual()
		{
			var ex = Record.Exception(() => Assert.Equivalent(new[] { 1, 9, 4 }.ToImmutableArray(), new[] { 6, 9, 4, 1 }.ToImmutableArray(), strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found" + Environment.NewLine +
				"Expected: [1, 9, 4]" + Environment.NewLine +
				"Actual:   [6] left over from [6, 9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ValueNotFoundInActual()
		{
			var expected = new { x = new[] { 1, 6 }.ToImmutableArray() };
			var actual = new { x = new[] { 9, 4, 1 }.ToImmutableArray() };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ExtraValueInActual()
		{
			var expected = new { x = new[] { 1, 9, 4 }.ToImmutableArray() };
			var actual = new { x = new[] { 6, 9, 4, 1, 12 }.ToImmutableArray() };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine +
				"Expected: [1, 9, 4]" + Environment.NewLine +
				"Actual:   [6, 12] left over from [6, 9, 4, 1, 12]",
				ex.Message
			);
		}
	}

#endif

	public class ArrayOfObjects_NotStrict
	{
		[Fact]
		public void Success()
		{
			var expected = new[] { new { Foo = "Bar" } };
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { new { Foo = "Bar" } } };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } } };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Failure()
		{
			var expected = new[] { new { Foo = "Biff" } };
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray()
		{
			var expected = new { x = new[] { new { Foo = "Biff" } } };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}
	}

	public class ArrayOfObjects_Strict
	{
		[Fact]
		public void Success()
		{
			var expected = new[] { new { Foo = "Bar" }, new { Foo = "Baz" } };
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { new { Foo = "Bar" }, new { Foo = "Baz" } } };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } } };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Failure_ValueNotFoundInActual()
		{
			var expected = new[] { new { Foo = "Biff" } };
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_ExtraValueInActual()
		{
			var expected = new[] { new { Foo = "Bar" } };
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found" + Environment.NewLine +
				"Expected: [{ Foo = \"Bar\" }]" + Environment.NewLine +
				"Actual:   [{ Foo = \"Baz\" }] left over from [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ValueNotFoundInActual()
		{
			var expected = new { x = new[] { new { Foo = "Biff" } } };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ExtraValueInActual()
		{
			var expected = new { x = new[] { new { Foo = "Bar" } } };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine +
				"Expected: [{ Foo = \"Bar\" }]" + Environment.NewLine +
				"Actual:   [{ Foo = \"Baz\" }] left over from [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}
	}

	public class ListOfObjects_NotStrict
	{
		[Fact]
		public void Success()
		{
			var expected = new[] { new { Foo = "Bar" } }.ToList();
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList();

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Success_EmbeddedArray()
		{
			var expected = new { x = new[] { new { Foo = "Bar" } }.ToList() };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList() };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Failure()
		{
			var expected = new[] { new { Foo = "Biff" } }.ToList();
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList();

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray()
		{
			var expected = new { x = new[] { new { Foo = "Biff" } }.ToList() };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList() };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}
	}

	public class ListOfObjects_Strict
	{
		[Fact]
		public void Success()
		{
			var expected = new[] { new { Foo = "Bar" }, new { Foo = "Baz" } }.ToList();
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList();

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Success_EmbeddedList()
		{
			var expected = new { x = new[] { new { Foo = "Bar" }, new { Foo = "Baz" } }.ToList() };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList() };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Failure_ValueNotFoundInActual()
		{
			var expected = new[] { new { Foo = "Biff" } }.ToList();
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList();

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_ExtraValueInActual()
		{
			var expected = new[] { new { Foo = "Bar" } }.ToList();
			var actual = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList();

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found" + Environment.NewLine +
				"Expected: [{ Foo = \"Bar\" }]" + Environment.NewLine +
				"Actual:   [{ Foo = \"Baz\" }] left over from [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ValueNotFoundInActual()
		{
			var expected = new { x = new[] { new { Foo = "Biff" } }.ToList() };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList() };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: { Foo = \"Biff\" }" + Environment.NewLine +
				"In:       [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedArray_ExtraValueInActual()
		{
			var expected = new { x = new[] { new { Foo = "Bar" } }.ToList() };
			var actual = new { x = new[] { new { Foo = "Baz" }, new { Foo = "Bar" } }.ToList() };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine +
				"Expected: [{ Foo = \"Bar\" }]" + Environment.NewLine +
				"Actual:   [{ Foo = \"Baz\" }] left over from [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]",
				ex.Message
			);
		}
	}

	public class EquivalentCollectionsInDifferentTypes
	{
		[Fact]
		public void ArrayIsEquivalentToList()
		{
			Assert.Equivalent(new[] { 1, 2, 3 }, new List<int> { 1, 2, 3 });
		}

		[Fact]
		public void ListIsEquivalentToArray()
		{
			Assert.Equivalent(new List<int> { 1, 2, 3 }, new[] { 1, 2, 3 });
		}

#if XUNIT_IMMUTABLE_COLLECTIONS
		[Fact]
		public void ArrayIsEquivalentToImmutableArray()
		{
			Assert.Equivalent(new[] { 1, 2, 3 }, new[] { 1, 2, 3 }.ToImmutableArray());
		}

		[Fact]
		public void ImmutableArrayIsEquivalentToArray()
		{
			Assert.Equivalent(new[] { 1, 2, 3 }.ToImmutableArray(), new[] { 1, 2, 3 });
		}

		[Fact]
		public void ImmutableListIsEquivalentToImmutableSortedSet()
		{
			Assert.Equivalent(new[] { 1, 2, 3 }.ToImmutableList(), new[] { 1, 2, 3 }.ToImmutableSortedSet());
		}
#endif
	}

	public class Dictionaries_NotStrict
	{
		[Fact]
		public void Success()
		{
			var expected = new Dictionary<string, int> { ["Foo"] = 42 };
			var actual = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void SuccessWithArrayValues()
		{
			var expected = new Dictionary<string, int[]> { ["Foo"] = new[] { 42 } };
			var actual = new Dictionary<string, int[]> { ["Foo"] = new[] { 42 }, ["Bar"] = new[] { 2112 } };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void SuccessWithListValues()
		{
			var expected = new Dictionary<string, List<int>> { ["Foo"] = new List<int> { 42 } };
			var actual = new Dictionary<string, List<int>> { ["Foo"] = new List<int> { 42 }, ["Bar"] = new List<int> { 2112 } };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Success_EmbeddedDictionary()
		{
			var expected = new { x = new Dictionary<string, int> { ["Foo"] = 42 } };
			var actual = new { x = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 } };

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Failure()
		{
			var expected = new Dictionary<string, int> { ["Foo"] = 16 };
			var actual = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: [\"Foo\"] = 16" + Environment.NewLine +
				"In:       [[\"Foo\"] = 42, [\"Bar\"] = 2112]",
				ex.Message
			);
		}

		[Fact]
		public void FailureWithArrayValues()
		{
			var expected = new Dictionary<string, int[]> { ["Foo"] = new[] { 16 } };
			var actual = new Dictionary<string, int[]> { ["Foo"] = new[] { 42 }, ["Bar"] = new[] { 2112 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: [\"Foo\"] = [16]" + Environment.NewLine +
				"In:       [[\"Foo\"] = [42], [\"Bar\"] = [2112]]",
				ex.Message
			);
		}

		[Fact]
		public void FailureWithListValues()
		{
			var expected = new Dictionary<string, List<int>> { ["Foo"] = new List<int> { 16 } };
			var actual = new Dictionary<string, List<int>> { ["Foo"] = new List<int> { 42 }, ["Bar"] = new List<int> { 2112 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: [\"Foo\"] = [16]" + Environment.NewLine +
				"In:       [[\"Foo\"] = [42], [\"Bar\"] = [2112]]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedDictionary()
		{
			var expected = new { x = new Dictionary<string, int> { ["Foo"] = 16 } };
			var actual = new { x = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: [\"Foo\"] = 16" + Environment.NewLine +
				"In:       [[\"Foo\"] = 42, [\"Bar\"] = 2112]",
				ex.Message
			);
		}
	}

	public class Dictionaries_Strict
	{
		[Fact]
		public void Success()
		{
			var expected = new Dictionary<string, int> { ["Bar"] = 2112, ["Foo"] = 42 };
			var actual = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Success_EmbeddedDictionary()
		{
			var expected = new { x = new Dictionary<string, int> { ["Bar"] = 2112, ["Foo"] = 42 } };
			var actual = new { x = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 } };

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Failure_ValueNotFoundInActual()
		{
			var expected = new Dictionary<string, int> { ["Foo"] = 16 };
			var actual = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine +
				"Expected: [\"Foo\"] = 16" + Environment.NewLine +
				"In:       [[\"Foo\"] = 42, [\"Bar\"] = 2112]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_ExtraValueInActual()
		{
			var expected = new Dictionary<string, int> { ["Bar"] = 2112, ["Foo"] = 42 };
			var actual = new Dictionary<string, int> { ["Foo"] = 42, ["Biff"] = 2600, ["Bar"] = 2112 };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found" + Environment.NewLine +
				"Expected: [[\"Bar\"] = 2112, [\"Foo\"] = 42]" + Environment.NewLine +
				"Actual:   [[\"Biff\"] = 2600] left over from [[\"Foo\"] = 42, [\"Biff\"] = 2600, [\"Bar\"] = 2112]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedDictionary_ValueNotFoundInActual()
		{
			var expected = new { x = new Dictionary<string, int> { ["Foo"] = 16 } };
			var actual = new { x = new Dictionary<string, int> { ["Foo"] = 42, ["Bar"] = 2112 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine +
				"Expected: [\"Foo\"] = 16" + Environment.NewLine +
				"In:       [[\"Foo\"] = 42, [\"Bar\"] = 2112]",
				ex.Message
			);
		}

		[Fact]
		public void Failure_EmbeddedDictionary_ExtraValueInActual()
		{
			var expected = new { x = new Dictionary<string, int> { ["Bar"] = 2112, ["Foo"] = 42 } };
			var actual = new { x = new Dictionary<string, int> { ["Foo"] = 42, ["Biff"] = 2600, ["Bar"] = 2112 } };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine +
				"Expected: [[\"Bar\"] = 2112, [\"Foo\"] = 42]" + Environment.NewLine +
				"Actual:   [[\"Biff\"] = 2600] left over from [[\"Foo\"] = 42, [\"Biff\"] = 2600, [\"Bar\"] = 2112]",
				ex.Message
			);
		}
	}

	public class KeyValuePairs_NotStrict
	{
		[Fact]
		public void Success()
		{
			var expected = new KeyValuePair<int, int[]>(42, new[] { 1, 4 });
			var actual = new KeyValuePair<int, int[]>(42, new[] { 9, 4, 1 });

			Assert.Equivalent(expected, actual, strict: false);
		}

		[Fact]
		public void Failure_Key()
		{
			var expected = new KeyValuePair<int, int[]>(42, new[] { 1, 4 });
			var actual = new KeyValuePair<int, int[]>(41, new[] { 9, 4, 1 });

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Key'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   41",
				ex.Message
			);
		}

		[Fact]
		public void Failure_Value()
		{
			var expected = new KeyValuePair<int, int[]>(42, new[] { 1, 6 });
			var actual = new KeyValuePair<int, int[]>(42, new[] { 9, 4, 1 });

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'Value'" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [9, 4, 1]",
				ex.Message
			);
		}
	}

	public class KeyValuePairs_Strict
	{
		[Fact]
		public void Success()
		{
			var expected = new KeyValuePair<int, int[]>(42, new[] { 1, 4 });
			var actual = new KeyValuePair<int, int[]>(42, new[] { 4, 1 });

			Assert.Equivalent(expected, actual, strict: true);
		}

		[Fact]
		public void Failure_Key()
		{
			var expected = new KeyValuePair<int, int[]>(42, new[] { 1, 4 });
			var actual = new KeyValuePair<int, int[]>(41, new[] { 4, 1 });

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Key'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   41",
				ex.Message
			);
		}

		[Fact]
		public void Failure_Value()
		{
			var expected = new KeyValuePair<int, int[]>(42, new[] { 1, 6 });
			var actual = new KeyValuePair<int, int[]>(42, new[] { 4, 1 });

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Collection value not found in member 'Value'" + Environment.NewLine +
				"Expected: 6" + Environment.NewLine +
				"In:       [4, 1]",
				ex.Message
			);
		}
	}

	public class SpecialCases
	{
		// DateTime

		[Fact]
		public void DateTime_Success()
		{
			var expected = new DateTime(2022, 12, 1, 1, 3, 1);
			var actual = new DateTime(2022, 12, 1, 1, 3, 1);

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void DateTime_Failure()
		{
			var expected = new DateTime(2022, 12, 1, 1, 3, 1);
			var actual = new DateTime(2011, 9, 13, 18, 22, 0);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 2022-12-01T01:03:01.0000000" + Environment.NewLine +
				"Actual:   2011-09-13T18:22:00.0000000",
				ex.Message
			);
		}

		[Fact]
		public void DateTimeToString_Failure()
		{
			var expected = new DateTime(2022, 12, 1, 1, 3, 1);
			var actual = "2022-12-01T01:03:01.0000000";

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 2022-12-01T01:03:01.0000000" + Environment.NewLine +
				"Actual:   \"2022-12-01T01:03:01.0000000\"",
				ex.Message
			);
			Assert.IsType<ArgumentException>(ex.InnerException);  // Thrown by DateTime.CompareTo
		}

		[Fact]
		public void StringToDateTime_Success()
		{
			var expected = "2022-12-01T01:03:01.0000000";
			var actual = new DateTime(2022, 12, 1, 1, 3, 1);

			Assert.Equivalent(expected, actual);
		}

		// DateTimeOffset

		[Fact]
		public void DateTimeOffset_Success()
		{
			var expected = new DateTimeOffset(2022, 12, 1, 1, 3, 1, TimeSpan.Zero);
			var actual = new DateTimeOffset(2022, 12, 1, 1, 3, 1, TimeSpan.Zero);

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void DateTimeOffset_Failure()
		{
			var expected = new DateTimeOffset(2022, 12, 1, 1, 3, 1, TimeSpan.Zero);
			var actual = new DateTimeOffset(2011, 9, 13, 18, 22, 0, TimeSpan.Zero);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure" + Environment.NewLine +
				"Expected: 2022-12-01T01:03:01.0000000+00:00" + Environment.NewLine +
				"Actual:   2011-09-13T18:22:00.0000000+00:00",
				ex.Message
			);
		}

		// FileSystemInfo-derived types

		[Fact]
		public void DirectoryInfo_Success()
		{
			var assemblyPath = Path.GetDirectoryName(typeof(SpecialCases).Assembly.Location);
			Assert.NotNull(assemblyPath);

			var expected = new DirectoryInfo(assemblyPath);
			var actual = new DirectoryInfo(assemblyPath);

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void DirectoryInfo_Failure()
		{
			var assemblyPath = Path.GetDirectoryName(typeof(SpecialCases).Assembly.Location);
			Assert.NotNull(assemblyPath);
			var assemblyParentPath = Path.GetDirectoryName(assemblyPath);
			Assert.NotNull(assemblyParentPath);
			Assert.NotEqual(assemblyPath, assemblyParentPath);

			var expected = new FileInfo(assemblyPath);
			var actual = new FileInfo(assemblyParentPath);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.StartsWith("Assert.Equivalent() Failure: Mismatched value on member 'FullName'" + Environment.NewLine, ex.Message);
		}

		[Fact]
		public void FileInfo_Success()
		{
			var assembly = typeof(SpecialCases).Assembly.Location;
			var expected = new FileInfo(assembly);
			var actual = new FileInfo(assembly);

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void FileInfo_Failure()
		{
			var expected = new FileInfo(typeof(SpecialCases).Assembly.Location);
			var actual = new FileInfo(typeof(Assert).Assembly.Location);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.StartsWith("Assert.Equivalent() Failure: Mismatched value on member 'FullName'" + Environment.NewLine, ex.Message);
		}

		[Fact]
		public void FileInfoToDirectoryInfo_Failure_TopLevel()
		{
			var location = typeof(SpecialCases).Assembly.Location;
			var expected = new FileInfo(location);
			var actual = new DirectoryInfo(location);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Types did not match" + Environment.NewLine +
				"Expected type: System.IO.FileInfo" + Environment.NewLine +
				"Actual type:   System.IO.DirectoryInfo",
				ex.Message
			);
		}

		[Fact]
		public void FileInfoToDirectoryInfo_Failure_Embedded()
		{
			var location = typeof(SpecialCases).Assembly.Location;
			var expected = new { Info = new FileInfo(location) };
			var actual = new { Info = new DirectoryInfo(location) };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Types did not match in member 'Info'" + Environment.NewLine +
				"Expected type: System.IO.FileInfo" + Environment.NewLine +
				"Actual type:   System.IO.DirectoryInfo",
				ex.Message
			);
		}

		// Ensuring we use reference equality for the circular reference hash sets

		// [Theory]
		// [InlineData(true)]
		// [InlineData(false)]
		// public void Issue2939(bool strict)
		// {
		// 	var expected = new Uri("http://example.com");
		// 	var actual = new Uri("http://example.com");

		// 	Assert.Equivalent(expected, actual, strict);
		// }
	}

	public class CircularReferences
	{
		[Fact]
		public void Expected_Shallow()
		{
			var expected = new SelfReferential(circularReference: true);
			var actual = new SelfReferential(circularReference: false);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal("Assert.Equivalent() Failure: Circular reference found in 'expected.Other'", ex.Message);
		}

		[Fact]
		public void Actual_Shallow()
		{
			var expected = new SelfReferential(circularReference: false);
			var actual = new SelfReferential(circularReference: true);

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal("Assert.Equivalent() Failure: Circular reference found in 'actual.Other'", ex.Message);
		}
	}

	public class DepthLimit
	{
		[Fact]
		public void PreventArbitrarilyLargeDepthObjectTree()
		{
			var expected = new InfiniteRecursionClass();
			var actual = new InfiniteRecursionClass();

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Exceeded the maximum depth 50 with 'Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent'; check for infinite recursion or circular references",
				ex.Message
			);
		}

		class InfiniteRecursionClass
		{
			public InfiniteRecursionClass Parent => new();
		}
	}

	public class Indexers
	{
		[Fact]
		public void Equivalent()
		{
			var expected = new ClassWithIndexer { Value = "Hello" };
			var actual = new ClassWithIndexer { Value = "Hello" };

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void NotEquivalent()
		{
			var expected = new ClassWithIndexer { Value = "Hello" };
			var actual = new ClassWithIndexer { Value = "There" };

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Value'" + Environment.NewLine +
				"Expected: \"Hello\"" + Environment.NewLine +
				"Actual:   \"There\"",
				ex.Message
			);
		}
	}

	public class Tuples
	{
		[Fact]
		public void Equivalent()
		{
			var expected = Tuple.Create(42, "Hello world");
			var actual = Tuple.Create(42, "Hello world");

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void NotEquivalent()
		{
			var expected = Tuple.Create(42, "Hello world");
			var actual = Tuple.Create(2112, "Hello world");

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Item1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}
	}

	public class ValueTuples
	{
		[Fact]
		public void Equivalent()
		{
			var expected = (answer: 42, greeting: "Hello world");
			var actual = (answer: 42, greeting: "Hello world");

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void NotEquivalent()
		{
			var expected = (answer: 42, greeting: "Hello world");
			var actual = (answer: 2112, greeting: "Hello world");

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Item1'" + Environment.NewLine +
				"Expected: 42" + Environment.NewLine +
				"Actual:   2112",
				ex.Message
			);
		}

		[Fact]
		public void ValueTupleInsideClass_Equivalent()
		{
			var expected = new Person { ID = 42, Relationships = (parent: new Person { ID = 2112 }, child: null) };
			var actual = new Person { ID = 42, Relationships = (parent: new Person { ID = 2112 }, child: null) };

			Assert.Equivalent(expected, actual);
		}

		class Person
		{
			public int ID { get; set; }

			public (Person? parent, Person? child) Relationships;
		}
	}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER

	// https://github.com/xunit/xunit/issues/3088
	public sealed class ByRefLikeParameters
	{
		[Fact]
		public void Equivalent()
		{
			var expected = new RefParameterClass(42.ToString());
			var actual = new RefParameterClass(42.ToString());

			Assert.Equivalent(expected, actual);
		}

		[Fact]
		public void NotEquivalent()
		{
			var expected = new RefParameterClass2(42.ToString());
			var actual = new RefParameterClass2(2112.ToString());

			var ex = Record.Exception(() => Assert.Equivalent(expected, actual));

			Assert.IsType<EquivalentException>(ex);
			Assert.Equal(
				"Assert.Equivalent() Failure: Mismatched value on member 'Value'" + Environment.NewLine +
				"Expected: \"42\"" + Environment.NewLine +
				"Actual:   \"2112\"",
				ex.Message
			);
		}

		public sealed class RefParameterClass(string value)
		{
			public ReadOnlySpan<char> Value => value.AsSpan();
		}

		public sealed class RefParameterClass2(string value)
		{
			public ReadOnlySpan<char> ValueSpan => value.AsSpan();

			// Added so `NotEquivalent` has a getter to check.
			public string Value => value;
		}
	}

#endif

	class ShallowClass
	{
		public static int StaticValue { get; set; }
		public int Value1;
		public string? Value2 { get; set; }
	}

	class ShallowClass2
	{
		public static int StaticValue { get; set; }
		public int Value1 { get; set; }
		public string? Value2;
	}

	class PrivateMembersClass
	{
		public PrivateMembersClass(int value1, string value2)
		{
			Value1 = value1;
			Value2 = value2;
		}

		private readonly int Value1;
		private string Value2 { get; }
	}

	class DeepClass
	{
		public decimal Value3;

		public ShallowClass? Shallow { get; set; }
	}

	class DeepClass2
	{
		public decimal Value3 { get; set; }
		public ShallowClass? Shallow;
	}

	struct DeepStruct
	{
		public DeepStruct(ShallowClass shallow)
		{
			Shallow = shallow;
		}

		public ShallowClass Shallow { get; }
	}

	class SelfReferential
	{
		public SelfReferential(bool circularReference)
		{
			// When we don't want this object to be self-referential, we need to make *another*
			// object here instead, since a null value would end up short circuiting the
			// circular reference check before we saw it. We don't anticipate people having
			// to do strange things like this; it's just for testing. :)
			Other = circularReference ? this : new SelfReferential(true);
		}

		public SelfReferential Other { get; }
	}

	class ClassWithIndexer
	{
		public string? Value;

		public string this[int idx] => idx.ToString();
	}
}
