diff --git a/src/TestFramework/TestFramework/Assertions/Assert.ContainsAll.cs b/src/TestFramework/TestFramework/Assertions/Assert.ContainsAll.cs
new file mode 100644
index 0000000000..106568b46f
--- /dev/null
+++ b/src/TestFramework/TestFramework/Assertions/Assert.ContainsAll.cs
@@ -0,0 +1,542 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// A collection of helper classes to test various conditions within
+/// unit tests. If the condition being tested is not met, an exception
+/// is thrown.
+///
+#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
+#pragma warning disable RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
+public sealed partial class Assert
+{
+ #region ContainsAll
+
+ ///
+ /// Tests whether contains every element of
+ /// (with multiplicity) and throws an exception if any element in occurs
+ /// more times than in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] does not contain all of [1, 1] .
+ ///
+ /// The type of the collection items.
+ ///
+ /// The collection of items expected to all be present in .
+ ///
+ ///
+ /// The collection expected to contain every item of .
+ ///
+ ///
+ /// The message to include in the exception when an element in
+ /// is not found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if contains at least one element that occurs more times
+ /// than in .
+ ///
+ public static void ContainsAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.ContainsAll", "expected");
+ CheckParameterNotNull(collection, "Assert.ContainsAll", "collection");
+ ContainsAllImpl(expected, collection, EqualityComparer.Default, comparerName: null, message, expectedExpression, collectionExpression);
+ }
+
+ ///
+ /// Tests whether contains every element of
+ /// (with multiplicity) and throws an exception if any element in occurs
+ /// more times than in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] does not contain all of [1, 1] .
+ ///
+ /// The type of the collection items.
+ ///
+ /// The collection of items expected to all be present in .
+ ///
+ ///
+ /// The collection expected to contain every item of .
+ ///
+ ///
+ /// The equality comparer to use when comparing elements.
+ ///
+ ///
+ /// The message to include in the exception when an element in
+ /// is not found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if contains at least one element that occurs more times
+ /// than in .
+ ///
+ public static void ContainsAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, [NotNull] IEqualityComparer? comparer, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.ContainsAll", "expected");
+ CheckParameterNotNull(collection, "Assert.ContainsAll", "collection");
+ CheckParameterNotNull(comparer, "Assert.ContainsAll", "comparer");
+ ContainsAllImpl(expected, collection, comparer, comparer.GetType().Name, message, expectedExpression, collectionExpression);
+ }
+
+ ///
+ /// Tests whether contains every element of
+ /// (with multiplicity) and throws an exception if any element in occurs
+ /// more times than in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] does not contain all of [1, 1] .
+ ///
+ ///
+ /// The collection of items expected to all be present in .
+ ///
+ ///
+ /// The collection expected to contain every item of .
+ ///
+ ///
+ /// The message to include in the exception when an element in
+ /// is not found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if contains at least one element that occurs more times
+ /// than in .
+ ///
+ public static void ContainsAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.ContainsAll", "expected");
+ CheckParameterNotNull(collection, "Assert.ContainsAll", "collection");
+
+ ContainsAllImpl(expected.Cast(), collection.Cast(), EqualityComparer.Default, comparerName: null, message, expectedExpression, collectionExpression);
+ }
+
+ ///
+ /// Tests whether contains every element of
+ /// (with multiplicity) and throws an exception if any element in occurs
+ /// more times than in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] does not contain all of [1, 1] .
+ ///
+ ///
+ /// The collection of items expected to all be present in .
+ ///
+ ///
+ /// The collection expected to contain every item of .
+ ///
+ ///
+ /// The equality comparer to use when comparing elements.
+ ///
+ ///
+ /// The message to include in the exception when an element in
+ /// is not found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if contains at least one element that occurs more times
+ /// than in .
+ ///
+ public static void ContainsAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, [NotNull] IEqualityComparer? comparer, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.ContainsAll", "expected");
+ CheckParameterNotNull(collection, "Assert.ContainsAll", "collection");
+ CheckParameterNotNull(comparer, "Assert.ContainsAll", "comparer");
+
+ ContainsAllImpl(expected.Cast(), collection.Cast(), new NonGenericEqualityComparerAdapter(comparer), comparer.GetType().Name, message, expectedExpression, collectionExpression);
+ }
+
+ #endregion // ContainsAll
+
+ #region DoesNotContainAll
+
+ ///
+ /// Tests whether does not contain every element of
+ /// (with multiplicity) and throws an exception if every element in occurs at
+ /// least as many times in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] is considered not to contain all of [1, 1]
+ /// (so this assertion would pass for those inputs).
+ ///
+ /// The type of the collection items.
+ ///
+ /// The collection of items expected not to all be present in .
+ ///
+ ///
+ /// The collection expected not to contain every item of .
+ ///
+ ///
+ /// The message to include in the exception when every element in
+ /// is also found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if every element of occurs at least as many times in .
+ ///
+ public static void DoesNotContainAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.DoesNotContainAll", "expected");
+ CheckParameterNotNull(collection, "Assert.DoesNotContainAll", "collection");
+ DoesNotContainAllImpl(expected, collection, EqualityComparer.Default, comparerName: null, message, expectedExpression, collectionExpression);
+ }
+
+ ///
+ /// Tests whether does not contain every element of
+ /// (with multiplicity) and throws an exception if every element in occurs at
+ /// least as many times in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] is considered not to contain all of [1, 1]
+ /// (so this assertion would pass for those inputs).
+ ///
+ /// The type of the collection items.
+ ///
+ /// The collection of items expected not to all be present in .
+ ///
+ ///
+ /// The collection expected not to contain every item of .
+ ///
+ ///
+ /// The equality comparer to use when comparing elements.
+ ///
+ ///
+ /// The message to include in the exception when every element in
+ /// is also found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if every element of occurs at least as many times in .
+ ///
+ public static void DoesNotContainAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, [NotNull] IEqualityComparer? comparer, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.DoesNotContainAll", "expected");
+ CheckParameterNotNull(collection, "Assert.DoesNotContainAll", "collection");
+ CheckParameterNotNull(comparer, "Assert.DoesNotContainAll", "comparer");
+ DoesNotContainAllImpl(expected, collection, comparer, comparer.GetType().Name, message, expectedExpression, collectionExpression);
+ }
+
+ ///
+ /// Tests whether does not contain every element of
+ /// (with multiplicity) and throws an exception if every element in occurs at
+ /// least as many times in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] is considered not to contain all of [1, 1]
+ /// (so this assertion would pass for those inputs).
+ ///
+ ///
+ /// The collection of items expected not to all be present in .
+ ///
+ ///
+ /// The collection expected not to contain every item of .
+ ///
+ ///
+ /// The message to include in the exception when every element in
+ /// is also found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if every element of occurs at least as many times in .
+ ///
+ public static void DoesNotContainAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.DoesNotContainAll", "expected");
+ CheckParameterNotNull(collection, "Assert.DoesNotContainAll", "collection");
+
+ DoesNotContainAllImpl(expected.Cast(), collection.Cast(), EqualityComparer.Default, comparerName: null, message, expectedExpression, collectionExpression);
+ }
+
+ ///
+ /// Tests whether does not contain every element of
+ /// (with multiplicity) and throws an exception if every element in occurs at
+ /// least as many times in .
+ ///
+ ///
+ /// Element multiplicity is significant: [1] is considered not to contain all of [1, 1]
+ /// (so this assertion would pass for those inputs).
+ ///
+ ///
+ /// The collection of items expected not to all be present in .
+ ///
+ ///
+ /// The collection expected not to contain every item of .
+ ///
+ ///
+ /// The equality comparer to use when comparing elements.
+ ///
+ ///
+ /// The message to include in the exception when every element in
+ /// is also found (with sufficient multiplicity) in . The message is shown in test results.
+ ///
+ ///
+ /// The syntactic expression of expected as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// The syntactic expression of collection as given by the compiler via caller argument expression.
+ /// Users shouldn't pass a value for this parameter.
+ ///
+ ///
+ /// Thrown if every element of occurs at least as many times in .
+ ///
+ public static void DoesNotContainAll([NotNull] IEnumerable? expected, [NotNull] IEnumerable? collection, [NotNull] IEqualityComparer? comparer, string? message = "", [CallerArgumentExpression(nameof(expected))] string expectedExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "")
+ {
+ CheckParameterNotNull(expected, "Assert.DoesNotContainAll", "expected");
+ CheckParameterNotNull(collection, "Assert.DoesNotContainAll", "collection");
+ CheckParameterNotNull(comparer, "Assert.DoesNotContainAll", "comparer");
+
+ DoesNotContainAllImpl(expected.Cast(), collection.Cast(), new NonGenericEqualityComparerAdapter(comparer), comparer.GetType().Name, message, expectedExpression, collectionExpression);
+ }
+
+ #endregion // DoesNotContainAll
+
+ private static void ContainsAllImpl(IEnumerable expected, IEnumerable collection, IEqualityComparer comparer, string? comparerName, string? message, string expectedExpression, string collectionExpression)
+ {
+ // Snapshot once so we don't enumerate twice (counting + rendering on failure)
+ // and so lazy/single-pass enumerables behave deterministically.
+ List expectedList = expected is List el ? el : [.. expected];
+ List collectionList = collection is List cl ? cl : [.. collection];
+
+ if (TryFindMissingElements(expectedList, collectionList, comparer, out List? missing))
+ {
+ ReportAssertContainsAllFailed(expectedList, collectionList, missing, comparerName, message, expectedExpression, collectionExpression);
+ }
+ }
+
+ private static void DoesNotContainAllImpl(IEnumerable expected, IEnumerable collection, IEqualityComparer comparer, string? comparerName, string? message, string expectedExpression, string collectionExpression)
+ {
+ List expectedList = expected is List el ? el : [.. expected];
+ List collectionList = collection is List cl ? cl : [.. collection];
+
+ if (!HasAnyMissingElement(expectedList, collectionList, comparer))
+ {
+ ReportAssertDoesNotContainAllFailed(expectedList, collectionList, comparerName, message, expectedExpression, collectionExpression);
+ }
+ }
+
+ ///
+ /// Determines whether contains any element not present
+ /// (with sufficient multiplicity) in .
+ ///
+ ///
+ /// if at least one element is missing — in which case
+ /// holds the excess elements (in their first-seen order in ) — and
+ /// when every element of is matched in
+ /// .
+ ///
+ private static bool TryFindMissingElements(IEnumerable expected, IEnumerable collection, IEqualityComparer comparer, [NotNullWhen(true)] out List? missing)
+ {
+#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
+ Dictionary collectionCounts = CountElements(collection, comparer, out int collectionNulls);
+#pragma warning restore CS8714
+
+ missing = null;
+
+ // Walk the expected items in source order so excess elements appear in first-seen positional order
+ // (with multiplicity preserved). For each element, decrement its remaining quota in the collection;
+ // when the quota reaches zero, additional occurrences are reported as missing.
+ foreach (T? element in expected)
+ {
+ if (element is null)
+ {
+ if (collectionNulls > 0)
+ {
+ collectionNulls--;
+ }
+ else
+ {
+ missing ??= [];
+ missing.Add(default);
+ }
+
+ continue;
+ }
+
+ if (collectionCounts.TryGetValue(element, out int remaining) && remaining > 0)
+ {
+ collectionCounts[element] = remaining - 1;
+ }
+ else
+ {
+ missing ??= [];
+ missing.Add(element);
+ }
+ }
+
+ return missing is not null;
+ }
+
+ ///
+ /// Equivalent to
+ /// for the DoesNotContainAll path, but skips the of excess elements
+ /// and exits the walk on the first uncovered element. The
+ /// O(|collection|) count-dictionary build is still performed up-front.
+ ///
+ private static bool HasAnyMissingElement(IEnumerable expected, IEnumerable collection, IEqualityComparer comparer)
+ {
+#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
+ Dictionary collectionCounts = CountElements(collection, comparer, out int collectionNulls);
+#pragma warning restore CS8714
+
+ foreach (T? element in expected)
+ {
+ if (element is null)
+ {
+ if (collectionNulls > 0)
+ {
+ collectionNulls--;
+ }
+ else
+ {
+ return true;
+ }
+
+ continue;
+ }
+
+ if (collectionCounts.TryGetValue(element, out int remaining) && remaining > 0)
+ {
+ collectionCounts[element] = remaining - 1;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
+ private static Dictionary CountElements(IEnumerable collection, IEqualityComparer comparer, out int nullCount)
+ {
+#pragma warning disable IDE0028 // Collection initialization can be simplified - target-typed new with constructor argument is preferred over collection expression here
+ Dictionary counts = new(comparer);
+#pragma warning restore IDE0028
+ nullCount = 0;
+ foreach (T? element in collection)
+ {
+ if (element is null)
+ {
+ nullCount++;
+ continue;
+ }
+
+ counts.TryGetValue(element, out int count);
+ counts[element] = count + 1;
+ }
+
+ return counts;
+ }
+#pragma warning restore CS8714
+
+ [DoesNotReturn]
+ private static void ReportAssertContainsAllFailed(IEnumerable expected, IEnumerable collection, List missing, string? comparerName, string? message, string expectedExpression, string collectionExpression)
+ {
+ string expectedText = AssertionValueRenderer.RenderValue(expected);
+ string collectionText = AssertionValueRenderer.RenderValue(collection);
+ string missingText = AssertionValueRenderer.RenderValue(missing);
+
+ EvidenceBlock evidence = EvidenceBlock.Create()
+ .AddLine("missing:", missingText)
+ .AddLine("expected:", expectedText)
+ .AddLine("collection:", collectionText);
+
+ if (comparerName is not null)
+ {
+ evidence.AddLine("comparer:", comparerName);
+ }
+
+ StructuredAssertionMessage structured = new(FrameworkMessages.ContainsAllFailedSummary);
+ structured.WithUserMessage(message);
+ structured.WithEvidence(evidence);
+ structured.WithExpectedAndActual(expectedText, collectionText);
+ structured.WithCallSiteExpression(BuildCallSiteWithComparer("Assert.ContainsAll", expectedExpression, collectionExpression, comparerName is not null));
+
+ ReportAssertFailed(structured);
+ }
+
+ [DoesNotReturn]
+ private static void ReportAssertDoesNotContainAllFailed(IEnumerable expected, IEnumerable collection, string? comparerName, string? message, string expectedExpression, string collectionExpression)
+ {
+ string expectedText = AssertionValueRenderer.RenderValue(expected);
+ string collectionText = AssertionValueRenderer.RenderValue(collection);
+
+ EvidenceBlock evidence = EvidenceBlock.Create()
+ .AddLine("expected:", expectedText)
+ .AddLine("collection:", collectionText);
+
+ if (comparerName is not null)
+ {
+ evidence.AddLine("comparer:", comparerName);
+ }
+
+ StructuredAssertionMessage structured = new(FrameworkMessages.DoesNotContainAllFailedSummary);
+ structured.WithUserMessage(message);
+ structured.WithEvidence(evidence);
+ structured.WithExpectedAndActual(expectedText: null, actualText: collectionText);
+ structured.WithCallSiteExpression(BuildCallSiteWithComparer("Assert.DoesNotContainAll", expectedExpression, collectionExpression, comparerName is not null));
+
+ ReportAssertFailed(structured);
+ }
+
+ private static string? BuildCallSiteWithComparer(string assertionMethodName, string expectedExpression, string collectionExpression, bool hasComparer)
+ => hasComparer
+ // No [CallerArgumentExpression] is captured for the comparer parameter, so the third
+ // expression slot is always unavailable. Pass the "" placeholder directly as
+ // the third expression to ensure the call site is rendered (e.g. as
+ // "Assert.ContainsAll(, , )") even when callers do not
+ // support [CallerArgumentExpression] and the other expressions are also empty.
+ ? FormatCallSiteExpression(assertionMethodName, expectedExpression, collectionExpression, expression3: "", "", "", "")
+ : FormatCallSiteExpression(assertionMethodName, expectedExpression, collectionExpression, "", "");
+}
diff --git a/src/TestFramework/TestFramework/Assertions/Assert.cs b/src/TestFramework/TestFramework/Assertions/Assert.cs
index b3c20de7f0..ed1550c0bd 100644
--- a/src/TestFramework/TestFramework/Assertions/Assert.cs
+++ b/src/TestFramework/TestFramework/Assertions/Assert.cs
@@ -213,6 +213,29 @@ internal static void ThrowAssertFailed(StructuredAssertionMessage structuredMess
return $"{assertionMethodName}({arg1}, {arg2})";
}
+ ///
+ /// Formats a call-site expression for display at the bottom of a structured assertion message,
+ /// using three captured expressions. Multiline (or empty/whitespace) expressions are replaced with the
+ /// supplied placeholders. Only when all three expressions are empty/whitespace is the entire call-site
+ /// line suppressed.
+ ///
+ internal static string? FormatCallSiteExpression(string assertionMethodName, string expression1, string expression2, string expression3, string placeholder1 = "", string placeholder2 = "", string placeholder3 = "")
+ {
+ bool empty1 = string.IsNullOrWhiteSpace(expression1);
+ bool empty2 = string.IsNullOrWhiteSpace(expression2);
+ bool empty3 = string.IsNullOrWhiteSpace(expression3);
+ if (empty1 && empty2 && empty3)
+ {
+ return null;
+ }
+
+ string arg1 = empty1 || IsMultiline(expression1) ? NormalizeCallSitePlaceholder(placeholder1) : expression1;
+ string arg2 = empty2 || IsMultiline(expression2) ? NormalizeCallSitePlaceholder(placeholder2) : expression2;
+ string arg3 = empty3 || IsMultiline(expression3) ? NormalizeCallSitePlaceholder(placeholder3) : expression3;
+
+ return $"{assertionMethodName}({arg1}, {arg2}, {arg3})";
+ }
+
// string.Contains(char) is not available on netstandard2.0 / net462, so use IndexOf to check for newline characters.
private static bool IsMultiline(string expression)
=> expression.IndexOf('\n') >= 0 || expression.IndexOf('\r') >= 0;
diff --git a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
index b33be34749..a10010875c 100644
--- a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
@@ -14,3 +14,11 @@ static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEquivalent(T? e
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEquivalent(T? expected, T? actual, string? message = "", string! expectedExpression = "", string! actualExpression = "") -> void
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreNotEquivalent(T? notExpected, T? actual, bool strict, string? message = "", string! notExpectedExpression = "", string! actualExpression = "") -> void
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreNotEquivalent(T? notExpected, T? actual, string? message = "", string! notExpectedExpression = "", string! actualExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ContainsAll(System.Collections.IEnumerable? expected, System.Collections.IEnumerable? collection, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ContainsAll(System.Collections.IEnumerable? expected, System.Collections.IEnumerable? collection, System.Collections.IEqualityComparer? comparer, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ContainsAll(System.Collections.Generic.IEnumerable? expected, System.Collections.Generic.IEnumerable? collection, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ContainsAll(System.Collections.Generic.IEnumerable? expected, System.Collections.Generic.IEnumerable? collection, System.Collections.Generic.IEqualityComparer? comparer, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.DoesNotContainAll(System.Collections.IEnumerable? expected, System.Collections.IEnumerable? collection, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.DoesNotContainAll(System.Collections.IEnumerable? expected, System.Collections.IEnumerable? collection, System.Collections.IEqualityComparer? comparer, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.DoesNotContainAll(System.Collections.Generic.IEnumerable? expected, System.Collections.Generic.IEnumerable? collection, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
+static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.DoesNotContainAll(System.Collections.Generic.IEnumerable? expected, System.Collections.Generic.IEnumerable? collection, System.Collections.Generic.IEqualityComparer? comparer, string? message = "", string! expectedExpression = "", string! collectionExpression = "") -> void
\ No newline at end of file
diff --git a/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx b/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx
index 30692162b0..f83e8f7320 100644
--- a/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx
+++ b/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx
@@ -494,6 +494,12 @@ Actual: {2}
Expected string to not match the specified regular expression.
+
+ Expected collection to contain all specified items.
+
+
+ Expected collection to not contain all specified items.
+
Expected collection to contain exactly one element.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf
index f8916f09cf..d54e5c0328 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
Řetězec '{0}' neobsahuje řetězec '{1}'. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
Řetězec {0} obsahuje řetězec {1}. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf
index 97b028d506..ca7993ca71 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
Die Zeichenfolge "{0}" enthält nicht die Zeichenfolge "{1}". {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
Die Zeichenfolge „{0}“ enthält die Zeichenfolge „{1}“. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf
index 9098b0fd10..cb24bcb3b1 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
La cadena '{0}' no contiene la cadena '{1}'. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
La cadena '{0}' contiene la cadena '{1}'. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf
index 825e132acb..0173e96d42 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
La chaîne '{0}' ne contient pas la chaîne '{1}'. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
La chaîne « {0} » contient la chaîne « {1} ». {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf
index 93dfbf878b..d302a8c4fb 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
La stringa '{0}' non contiene la stringa '{1}'. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
La stringa '{0}' contiene la stringa '{1}'. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf
index adcc0d0fa6..d4105787cf 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf
@@ -298,6 +298,11 @@
{0}。{1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
文字列 '{0}' は文字列 '{1}' を含んでいません。{2}。
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
文字列 '{0}' は文字列 '{1}' を含んでいます。{2}。
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf
index 726e6be85b..25220dd512 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
'{0}' 문자열이 '{1}' 문자열을 포함하지 않습니다. {2}
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
'{0}' 문자열에 '{1}' 문자열이 포함되어 있습니다. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf
index 35f61e31ed..d84a4c64b4 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
Ciąg „{0}” nie zawiera ciągu „{1}”. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
Ciąg „{0}” zawiera ciąg „{1}”. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf
index ca7b4e1d27..9416b439f6 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
A cadeia de caracteres '{0}' não contém a cadeia de caracteres '{1}'. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
A cadeia de caracteres "{0}" contém a cadeia de caracteres "{1}". {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf
index a971123ced..ebe12cbd84 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
Строка "{0}" не содержит строку "{1}". {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
Строка "{0}" не содержит строку "{1}". {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf
index bc731fe9a4..c97e68be85 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf
@@ -298,6 +298,11 @@
{0}. {1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
'{0}' dizesi, '{1}' dizesini içermiyor. {2}.
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
'{0}' dizesi, '{1}' dizesini içermiyor. {2}.
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf
index 0c8f340763..43c378a0c1 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf
@@ -298,6 +298,11 @@
{0}。{1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
字符串“{0}”不包含字符串“{1}”。{2}。
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
字符串“{0}”确实包含字符串“{1}”。{2}
diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf
index d8d8fcf1e0..245072218b 100644
--- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf
+++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf
@@ -298,6 +298,11 @@
{0}。{1}
+
+ Expected collection to contain all specified items.
+ Expected collection to contain all specified items.
+
+
String '{0}' does not contain string '{1}'. {2}.
字串 '{0}' 未包含字串 '{1}'。{2}。
@@ -348,6 +353,11 @@
Expected string to contain the specified substring.
+
+ Expected collection to not contain all specified items.
+ Expected collection to not contain all specified items.
+
+
String '{0}' does contain string '{1}'. {2}.
字串 '{0}' 有包含字串 '{1}'。{2}。
diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs
index 6ae04d2944..33373d1f65 100644
--- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs
+++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections;
+using System.Runtime.CompilerServices;
using AwesomeAssertions;
@@ -381,9 +382,21 @@ private sealed class CaseInsensitiveStringComparer : IEqualityComparer,
public int GetHashCode(string? obj) => obj is null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(obj);
- bool IEqualityComparer.Equals(object? x, object? y) => Equals(x as string, y as string);
-
- int IEqualityComparer.GetHashCode(object obj) => obj is string value ? GetHashCode(value) : 0;
+ bool IEqualityComparer.Equals(object? x, object? y)
+ => (x, y) switch
+ {
+ (null, null) => true,
+ (string left, string right) => Equals(left, right),
+ (null, _) or (_, null) => false,
+ _ => false,
+ };
+
+ int IEqualityComparer.GetHashCode(object obj)
+ => obj is string value
+ ? GetHashCode(value)
+ : obj is null
+ ? 0
+ : RuntimeHelpers.GetHashCode(obj);
}
private sealed class NullEqualsEmptyStringComparer : IEqualityComparer
diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ContainsAll.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ContainsAll.cs
new file mode 100644
index 0000000000..568ea0b7ef
--- /dev/null
+++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ContainsAll.cs
@@ -0,0 +1,477 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections;
+using System.Runtime.CompilerServices;
+
+using AwesomeAssertions;
+
+using TestFramework.ForTestingMSTest;
+
+namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests;
+
+public partial class AssertTests : TestContainer
+{
+ public void ContainsAll_Generic_AllPresent_ShouldPass()
+ => Assert.ContainsAll(new[] { 1, 2 }, new[] { 1, 2, 3 });
+
+ public void ContainsAll_Generic_EmptyExpected_ShouldPass()
+ => Assert.ContainsAll(Array.Empty(), new[] { 1, 2, 3 });
+
+ public void ContainsAll_Generic_DuplicatesWithinCollectionMultiplicity_ShouldPass()
+ => Assert.ContainsAll(new[] { 1, 1, 2 }, new[] { 1, 1, 2, 3 });
+
+ public void ContainsAll_Generic_DuplicatesExceedCollectionMultiplicity_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1, 1, 1 }, new[] { 1, 1 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: [1]
+ expected: [1, 1, 1]
+ collection: [1, 1]
+
+ Assert.ContainsAll(new[] { 1, 1, 1 }, new[] { 1, 1 })
+ """);
+ }
+
+ public void ContainsAll_Generic_MultipleMissing_PreservesFirstSeenOrderAndMultiplicity()
+ {
+ // Walk: 3 ✓, 1 ✓, 1 → missing(1), 4 → missing(4), 1 → missing(1) => [1, 4, 1]
+ Action action = () => Assert.ContainsAll(new[] { 3, 1, 1, 4, 1 }, new[] { 1, 2, 3 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: [1, 4, 1]
+ expected: [3, 1, 1, 4, 1]
+ collection: [1, 2, 3]
+
+ Assert.ContainsAll(new[] { 3, 1, 1, 4, 1 }, new[] { 1, 2, 3 })
+ """);
+ }
+
+ public void ContainsAll_Generic_ExcessInMiddleOfMatchingRun_ReportsOnlyExcess()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1, 2, 1, 3 }, new[] { 1, 2, 3 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: [1]
+ expected: [1, 2, 1, 3]
+ collection: [1, 2, 3]
+
+ Assert.ContainsAll(new[] { 1, 2, 1, 3 }, new[] { 1, 2, 3 })
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_DuplicatesExceedCollectionMultiplicity_ShouldPass()
+ => Assert.DoesNotContainAll(new[] { 1, 1 }, new[] { 1 });
+
+ public void DoesNotContainAll_Generic_DuplicatesWithinCollectionMultiplicity_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { 1, 1 }, new[] { 1, 1, 2 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: [1, 1]
+ collection: [1, 1, 2]
+
+ Assert.DoesNotContainAll(new[] { 1, 1 }, new[] { 1, 1, 2 })
+ """);
+ }
+
+ public void ContainsAll_Generic_MissingElement_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1, 2, 3 }, new[] { 1, 2 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: [3]
+ expected: [1, 2, 3]
+ collection: [1, 2]
+
+ Assert.ContainsAll(new[] { 1, 2, 3 }, new[] { 1, 2 })
+ """);
+ }
+
+ public void ContainsAll_Generic_NullInExpectedButNotInCollection_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new string?[] { "a", null }, new string?[] { "a" });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: [null]
+ expected: ["a", null]
+ collection: ["a"]
+
+ Assert.ContainsAll(new string?[] { "a", null }, new string?[] { "a" })
+ """);
+ }
+
+ public void ContainsAll_Generic_StringMessage_MissingElement_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1, 2, 3 }, new[] { 1, 2 }, "User-provided message");
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+ User-provided message
+
+ missing: [3]
+ expected: [1, 2, 3]
+ collection: [1, 2]
+
+ Assert.ContainsAll(new[] { 1, 2, 3 }, new[] { 1, 2 })
+ """);
+ }
+
+ public void ContainsAll_Generic_WithComparer_AllPresent_ShouldPass()
+ => Assert.ContainsAll(new[] { "A" }, new[] { "a", "b" }, new CaseInsensitiveStringComparer());
+
+ public void ContainsAll_Generic_WithComparer_MissingElement_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new[] { "A", "C" }, new[] { "a", "b" }, new CaseInsensitiveStringComparer());
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: ["C"]
+ expected: ["A", "C"]
+ collection: ["a", "b"]
+ comparer: CaseInsensitiveStringComparer
+
+ Assert.ContainsAll(new[] { "A", "C" }, new[] { "a", "b" }, )
+ """);
+ }
+
+ public void ContainsAll_Generic_NullExpected_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(null, new[] { 1, 2 });
+ action.Should().Throw()
+ .WithMessage("Assert.ContainsAll failed. The parameter 'expected' is invalid. The value cannot be null.");
+ }
+
+ public void ContainsAll_Generic_NullCollection_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1, 2 }, null);
+ action.Should().Throw()
+ .WithMessage("Assert.ContainsAll failed. The parameter 'collection' is invalid. The value cannot be null.");
+ }
+
+ public void ContainsAll_Generic_NullComparer_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1 }, new[] { 1 }, (IEqualityComparer?)null);
+ action.Should().Throw()
+ .WithMessage("Assert.ContainsAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
+ }
+
+ public void ContainsAll_NonGeneric_AllPresent_ShouldPass()
+ => Assert.ContainsAll(new ArrayList { 1, 2 }, new ArrayList { 1, 2, 3 });
+
+ public void ContainsAll_NonGeneric_WithComparer_AllPresent_ShouldPass()
+ => Assert.ContainsAll(new ArrayList { "A", "b" }, new ArrayList { "a", "B", "c" }, new CaseInsensitiveStringComparer());
+
+ public void DoesNotContainAll_NonGeneric_WithComparer_MissingElement_ShouldPass()
+ => Assert.DoesNotContainAll(new ArrayList { "A", "C" }, new ArrayList { "a", "b" }, new CaseInsensitiveStringComparer());
+
+ public void ContainsAll_NonGeneric_MissingElement_ShouldFail()
+ {
+ IEnumerable expected = new ArrayList { 1, 2, 3 };
+ IEnumerable collection = new ArrayList { 1, 2 };
+ Action action = () => Assert.ContainsAll(expected, collection);
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: [3]
+ expected: [1, 2, 3]
+ collection: [1, 2]
+
+ Assert.ContainsAll(expected, collection)
+ """);
+ }
+
+ public void ContainsAll_NonGeneric_WithComparer_MissingElement_ShouldFail()
+ {
+ IEnumerable expected = new ArrayList { "A", "C" };
+ IEnumerable collection = new ArrayList { "a", "b" };
+ Action action = () => Assert.ContainsAll(expected, collection, new CaseInsensitiveStringComparer());
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: ["C"]
+ expected: ["A", "C"]
+ collection: ["a", "b"]
+ comparer: CaseInsensitiveStringComparer
+
+ Assert.ContainsAll(expected, collection, )
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_MissingElement_ShouldPass()
+ => Assert.DoesNotContainAll(new[] { 1, 2, 3 }, new[] { 1, 2 });
+
+ public void DoesNotContainAll_Generic_AllPresent_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { 1, 2 }, new[] { 1, 2, 3 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: [1, 2]
+ collection: [1, 2, 3]
+
+ Assert.DoesNotContainAll(new[] { 1, 2 }, new[] { 1, 2, 3 })
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_StringMessage_AllPresent_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { 1 }, new[] { 1, 2 }, "User-provided message");
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+ User-provided message
+
+ expected: [1]
+ collection: [1, 2]
+
+ Assert.DoesNotContainAll(new[] { 1 }, new[] { 1, 2 })
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_WithComparer_MissingElement_ShouldPass()
+ => Assert.DoesNotContainAll(new[] { "A", "C" }, new[] { "a", "b" }, new CaseInsensitiveStringComparer());
+
+ public void DoesNotContainAll_Generic_WithComparer_AllPresent_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { "A" }, new[] { "a", "b" }, new CaseInsensitiveStringComparer());
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: ["A"]
+ collection: ["a", "b"]
+ comparer: CaseInsensitiveStringComparer
+
+ Assert.DoesNotContainAll(new[] { "A" }, new[] { "a", "b" }, )
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_NullExpected_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(null, new[] { 1, 2 });
+ action.Should().Throw()
+ .WithMessage("Assert.DoesNotContainAll failed. The parameter 'expected' is invalid. The value cannot be null.");
+ }
+
+ public void DoesNotContainAll_Generic_NullCollection_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { 1, 2 }, null);
+ action.Should().Throw()
+ .WithMessage("Assert.DoesNotContainAll failed. The parameter 'collection' is invalid. The value cannot be null.");
+ }
+
+ public void DoesNotContainAll_Generic_NullComparer_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { 1 }, new[] { 1 }, (IEqualityComparer?)null);
+ action.Should().Throw()
+ .WithMessage("Assert.DoesNotContainAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
+ }
+
+ public void ContainsAll_NonGeneric_NullExpected_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(null, new ArrayList { 1, 2 });
+ action.Should().Throw()
+ .WithMessage("Assert.ContainsAll failed. The parameter 'expected' is invalid. The value cannot be null.");
+ }
+
+ public void ContainsAll_NonGeneric_NullCollection_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new ArrayList { 1 }, null);
+ action.Should().Throw()
+ .WithMessage("Assert.ContainsAll failed. The parameter 'collection' is invalid. The value cannot be null.");
+ }
+
+ public void ContainsAll_NonGeneric_NullComparer_ShouldFail()
+ {
+ Action action = () => Assert.ContainsAll(new ArrayList { 1 }, new ArrayList { 1 }, (IEqualityComparer?)null);
+ action.Should().Throw()
+ .WithMessage("Assert.ContainsAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
+ }
+
+ public void DoesNotContainAll_NonGeneric_NullExpected_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(null, new ArrayList { 1, 2 });
+ action.Should().Throw()
+ .WithMessage("Assert.DoesNotContainAll failed. The parameter 'expected' is invalid. The value cannot be null.");
+ }
+
+ public void DoesNotContainAll_NonGeneric_NullCollection_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new ArrayList { 1 }, null);
+ action.Should().Throw()
+ .WithMessage("Assert.DoesNotContainAll failed. The parameter 'collection' is invalid. The value cannot be null.");
+ }
+
+ public void DoesNotContainAll_NonGeneric_NullComparer_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(new ArrayList { 1 }, new ArrayList { 1 }, (IEqualityComparer?)null);
+ action.Should().Throw()
+ .WithMessage("Assert.DoesNotContainAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
+ }
+
+ public void DoesNotContainAll_Generic_BothEmpty_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(Array.Empty(), Array.Empty());
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: []
+ collection: []
+
+ Assert.DoesNotContainAll(Array.Empty(), Array.Empty())
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_EmptyExpected_ShouldFail()
+ {
+ Action action = () => Assert.DoesNotContainAll(Array.Empty(), new[] { 1 });
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: []
+ collection: [1]
+
+ Assert.DoesNotContainAll(Array.Empty(), new[] { 1 })
+ """);
+ }
+
+ public void DoesNotContainAll_NonGeneric_WithComparer_AllPresent_ShouldFail()
+ {
+ IEnumerable expected = new ArrayList { "A" };
+ IEnumerable collection = new ArrayList { "a", "b" };
+ Action action = () => Assert.DoesNotContainAll(expected, collection, new CaseInsensitiveStringComparer());
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: ["A"]
+ collection: ["a", "b"]
+ comparer: CaseInsensitiveStringComparer
+
+ Assert.DoesNotContainAll(expected, collection, )
+ """);
+ }
+
+ public void ContainsAll_Generic_NullInCollectionButNotInExpected_ShouldPass()
+ => Assert.ContainsAll(new string?[] { "a" }, new string?[] { "a", null });
+
+ public void ContainsAll_Generic_WithComparer_NoCallerArgumentExpression_ShouldRenderPlaceholderCallSite()
+ {
+ // Simulate a caller (e.g. another language or a wrapper) that does not propagate
+ // [CallerArgumentExpression] values. The call site must still render with placeholders,
+ // including a "" placeholder, so the failure shows which overload was invoked.
+ Action action = () => Assert.ContainsAll(
+ new[] { "A", "C" },
+ new[] { "a", "b" },
+ new CaseInsensitiveStringComparer(),
+ message: null,
+ expectedExpression: string.Empty,
+ collectionExpression: string.Empty);
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to contain all specified items.
+
+ missing: ["C"]
+ expected: ["A", "C"]
+ collection: ["a", "b"]
+ comparer: CaseInsensitiveStringComparer
+
+ Assert.ContainsAll(, , )
+ """);
+ }
+
+ public void DoesNotContainAll_Generic_WithComparer_NoCallerArgumentExpression_ShouldRenderPlaceholderCallSite()
+ {
+ Action action = () => Assert.DoesNotContainAll(
+ new[] { "A" },
+ new[] { "a", "b" },
+ new CaseInsensitiveStringComparer(),
+ message: null,
+ expectedExpression: string.Empty,
+ collectionExpression: string.Empty);
+ action.Should().Throw()
+ .WithMessage(
+ """
+ Assertion failed. Expected collection to not contain all specified items.
+
+ expected: ["A"]
+ collection: ["a", "b"]
+ comparer: CaseInsensitiveStringComparer
+
+ Assert.DoesNotContainAll(, , )
+ """);
+ }
+
+ public void ContainsAll_AssertFailedException_PopulatesExpectedAndActual()
+ {
+ Action action = () => Assert.ContainsAll(new[] { 1, 2, 3 }, new[] { 1, 2 });
+ AssertFailedException ex = action.Should().Throw().Which;
+ ex.ExpectedText.Should().Be("[1, 2, 3]");
+ ex.ActualText.Should().Be("[1, 2]");
+ }
+
+ public void DoesNotContainAll_AssertFailedException_PopulatesActualOnly()
+ {
+ Action action = () => Assert.DoesNotContainAll(new[] { 1 }, new[] { 1, 2 });
+ AssertFailedException ex = action.Should().Throw().Which;
+ ex.ExpectedText.Should().BeNull();
+ ex.ActualText.Should().Be("[1, 2]");
+ }
+
+ public void ContainsAll_CaseInsensitiveStringComparer_NonGenericEquals_WithNonStringValues_ShouldReturnFalse()
+ {
+ IEqualityComparer comparer = new CaseInsensitiveStringComparer();
+ object left = new();
+ object right = new();
+
+ comparer.Equals(null, null).Should().BeTrue();
+ comparer.Equals(null, "a").Should().BeFalse();
+ comparer.Equals(left, right).Should().BeFalse();
+ comparer.Equals("A", "a").Should().BeTrue();
+ }
+
+ public void ContainsAll_CaseInsensitiveStringComparer_NonGenericGetHashCode_WithNonStringValue_ShouldUseRuntimeHashCode()
+ {
+ IEqualityComparer comparer = new CaseInsensitiveStringComparer();
+ object value = new();
+
+ comparer.GetHashCode(value).Should().Be(RuntimeHelpers.GetHashCode(value));
+ }
+}