diff --git a/src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs b/src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs new file mode 100644 index 0000000000..0929bfe3dc --- /dev/null +++ b/src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs @@ -0,0 +1,234 @@ +// 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 AreAllDistinct + + /// + /// Tests whether all items in the specified collection are distinct (no two + /// elements are equal) and throws an exception if any two elements in the + /// collection are equal. + /// + /// The type of the collection items. + /// + /// The collection in which to search for duplicate elements. + /// + /// + /// The message to include in the exception when contains + /// at least one duplicate element. The message is shown in test results. + /// + /// + /// 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 is null or contains at least one duplicate element. + /// + public static void AreAllDistinct([NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllDistinct", "collection"); + AreAllDistinctImpl(collection, EqualityComparer.Default, comparerTypeName: null, message, collectionExpression); + } + + /// + /// Tests whether all items in the specified collection are distinct (no two + /// elements are equal) using the supplied and throws + /// an exception if any two elements in the collection are equal. + /// + /// The type of the collection items. + /// + /// The collection in which to search for duplicate elements. + /// + /// + /// The equality comparer to use when comparing elements. + /// + /// + /// The message to include in the exception when contains + /// at least one duplicate element. The message is shown in test results. + /// + /// + /// 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 is null or contains at least one duplicate element. + /// + public static void AreAllDistinct([NotNull] IEnumerable? collection, [NotNull] IEqualityComparer? comparer, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllDistinct", "collection"); + CheckParameterNotNull(comparer, "Assert.AreAllDistinct", "comparer"); + AreAllDistinctImpl(collection, comparer, comparerTypeName: comparer.GetType().ToString(), message, collectionExpression); + } + + /// + /// Tests whether all items in the specified collection are distinct (no two + /// elements are equal) and throws an exception if any two elements in the + /// collection are equal. + /// + /// + /// The collection in which to search for duplicate elements. + /// + /// + /// The message to include in the exception when contains + /// at least one duplicate element. The message is shown in test results. + /// + /// + /// 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 is null or contains at least one duplicate element. + /// + public static void AreAllDistinct([NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllDistinct", "collection"); + AreAllDistinctImpl(collection.Cast(), EqualityComparer.Default, comparerTypeName: null, message, collectionExpression); + } + + /// + /// Tests whether all items in the specified collection are distinct (no two + /// elements are equal) using the supplied and throws + /// an exception if any two elements in the collection are equal. + /// + /// + /// The collection in which to search for duplicate elements. + /// + /// + /// The equality comparer to use when comparing elements. + /// + /// + /// The message to include in the exception when contains + /// at least one duplicate element. The message is shown in test results. + /// + /// + /// 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 is null or contains at least one duplicate element. + /// + public static void AreAllDistinct([NotNull] IEnumerable? collection, [NotNull] IEqualityComparer? comparer, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllDistinct", "collection"); + CheckParameterNotNull(comparer, "Assert.AreAllDistinct", "comparer"); + AreAllDistinctImpl(collection.Cast(), new NonGenericEqualityComparerAdapter(comparer), comparerTypeName: comparer.GetType().ToString(), message, collectionExpression); + } + +#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 void AreAllDistinctImpl(IEnumerable collection, IEqualityComparer comparer, string? comparerTypeName, string? message, string collectionExpression) + { + List snapshot = collection is List list ? list : [.. collection]; + +#pragma warning disable IDE0028 // Collection initialization can be simplified - target-typed `new` cannot pass the comparer in the same syntactic form expected. + var seen = new HashSet(comparer); +#pragma warning restore IDE0028 + + bool seenNull = false; + List? duplicates = null; + HashSet? duplicatesSeen = null; + bool nullDuplicateRecorded = false; + + foreach (T item in snapshot) + { + if (item is null) + { + if (!seenNull) + { + seenNull = true; + continue; + } + + if (!nullDuplicateRecorded) + { + duplicates ??= []; + duplicates.Add(default!); + nullDuplicateRecorded = true; + } + + continue; + } + + if (!seen.Add(item)) + { +#pragma warning disable IDE0028 // Collection initialization can be simplified - target-typed `new` cannot pass the comparer in the same syntactic form expected. + duplicatesSeen ??= new(comparer); +#pragma warning restore IDE0028 + if (duplicatesSeen.Add(item)) + { + duplicates ??= []; + duplicates.Add(item); + } + } + } + + if (duplicates is not null) + { + ReportAssertAreAllDistinctFailed(snapshot, duplicates, comparerTypeName, message, collectionExpression); + } + } +#pragma warning restore CS8714 + + [DoesNotReturn] + private static void ReportAssertAreAllDistinctFailed(IEnumerable collection, List duplicates, string? comparerTypeName, string? message, string collectionExpression) + { + string collectionText = AssertionValueRenderer.RenderValue(collection); + string duplicatesText = AssertionValueRenderer.RenderValue(duplicates); + + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("duplicates:", duplicatesText) + .AddLine("collection:", collectionText); + + if (comparerTypeName is not null) + { + evidence.AddLine("comparer:", comparerTypeName); + } + + StructuredAssertionMessage structured = new(FrameworkMessages.AreAllDistinctFailedSummary); + structured.WithUserMessage(message); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText: null, actualText: collectionText); + structured.WithCallSiteExpression(BuildCallSiteWithComparerForCollection("Assert.AreAllDistinct", collectionExpression, comparerTypeName is not null)); + + ReportAssertFailed(structured); + } + + private static string? BuildCallSiteWithComparerForCollection(string assertionMethodName, string collectionExpression, bool hasComparer) + { + string? callSite = FormatCallSiteExpression(assertionMethodName, collectionExpression, ""); + if (callSite is null || !hasComparer) + { + return callSite; + } + + // FormatCallSiteExpression has no overload accepting a third argument expression; insert + // the placeholder so the rendered call-site reflects the overload that was actually invoked. + Debug.Assert(callSite.Length > 0 && callSite[callSite.Length - 1] == ')', "FormatCallSiteExpression contract: rendered call-site must end with ')'."); + return string.Concat(callSite.Remove(callSite.Length - 1), ", )"); + } + + #endregion // AreAllDistinct + + // TODO: Deduplicate with the same adapter in Assert.CollectionEquivalence.cs (introduced by PR #8234) + // once both PRs have landed. + private sealed class NonGenericEqualityComparerAdapter : IEqualityComparer + { + private readonly IEqualityComparer _comparer; + + public NonGenericEqualityComparerAdapter(IEqualityComparer comparer) + => _comparer = comparer; + + bool IEqualityComparer.Equals(object? x, object? y) => _comparer.Equals(x, y); + + int IEqualityComparer.GetHashCode(object? obj) => obj is null ? 0 : _comparer.GetHashCode(obj); + } +} diff --git a/src/TestFramework/TestFramework/Assertions/Assert.AreAllNotNull.cs b/src/TestFramework/TestFramework/Assertions/Assert.AreAllNotNull.cs new file mode 100644 index 0000000000..88b0f15899 --- /dev/null +++ b/src/TestFramework/TestFramework/Assertions/Assert.AreAllNotNull.cs @@ -0,0 +1,105 @@ +// 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 AreAllNotNull + + /// + /// Tests whether all items in the specified collection are non-null and throws + /// an exception if any element is null. + /// + /// + /// The collection in which to search for null elements. + /// + /// + /// The message to include in the exception when contains + /// a null element. The message is shown in test results. + /// + /// + /// 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 is null or contains at least one null element. + /// + public static void AreAllNotNull([NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllNotNull", "collection"); + AreAllNotNullImpl(collection.Cast(), message, collectionExpression); + } + + /// + /// Tests whether all items in the specified collection are non-null and throws + /// an exception if any element is null. + /// + /// The type of the collection items. + /// + /// The collection in which to search for null elements. + /// + /// + /// The message to include in the exception when contains + /// a null element. The message is shown in test results. + /// + /// + /// 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 is null or contains at least one null element. + /// + public static void AreAllNotNull([NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllNotNull", "collection"); + AreAllNotNullImpl(collection, message, collectionExpression); + } + + private static void AreAllNotNullImpl(IEnumerable collection, string? message, string collectionExpression) + { + List snapshot = collection is List list ? list : [.. collection]; + List? nullIndices = null; + for (int i = 0; i < snapshot.Count; i++) + { + if (snapshot[i] is null) + { + nullIndices ??= []; + nullIndices.Add(i); + } + } + + if (nullIndices is not null) + { + ReportAssertAreAllNotNullFailed(snapshot, nullIndices, message, collectionExpression); + } + } + + [DoesNotReturn] + private static void ReportAssertAreAllNotNullFailed(IEnumerable collection, List nullIndices, string? message, string collectionExpression) + { + string collectionText = AssertionValueRenderer.RenderValue(collection); + string nullIndicesText = AssertionValueRenderer.RenderValue(nullIndices); + + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("null indices:", nullIndicesText) + .AddLine("collection:", collectionText); + + StructuredAssertionMessage structured = new(FrameworkMessages.AreAllNotNullFailedSummary); + structured.WithUserMessage(message); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText: null, actualText: collectionText); + structured.WithCallSiteExpression(FormatCallSiteExpression("Assert.AreAllNotNull", collectionExpression, "")); + + ReportAssertFailed(structured); + } + + #endregion // AreAllNotNull +} diff --git a/src/TestFramework/TestFramework/Assertions/Assert.AreAllOfType.cs b/src/TestFramework/TestFramework/Assertions/Assert.AreAllOfType.cs new file mode 100644 index 0000000000..5ae37365b4 --- /dev/null +++ b/src/TestFramework/TestFramework/Assertions/Assert.AreAllOfType.cs @@ -0,0 +1,174 @@ +// 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 AreAllOfType + + /// + /// Tests whether all elements in the specified collection are instances of (or + /// derived from) the expected type and throws an exception if the expected type is + /// not in the inheritance hierarchy of one or more of the elements, or if any + /// element is null. + /// + /// + /// The expected type of each element of . + /// + /// + /// The collection containing elements the test expects to be of the specified type. + /// + /// + /// The message to include in the exception when an element in + /// is null or not an instance of . The message is shown in + /// test results. + /// + /// + /// The syntactic expression of expectedType 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 or is null, + /// or some elements of are null or do not inherit from / + /// implement . + /// + public static void AreAllOfType([NotNull] Type? expectedType, [NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(expectedType))] string expectedTypeExpression = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(expectedType, "Assert.AreAllOfType", "expectedType"); + CheckParameterNotNull(collection, "Assert.AreAllOfType", "collection"); + AreAllOfTypeImpl(collection, expectedType, genericTypeArgumentName: null, message, collectionExpression, expectedTypeExpression); + } + + /// + /// Tests whether all elements in the specified collection are instances of (or + /// derived from) the expected type and throws an exception if the expected type is + /// not in the inheritance hierarchy of one or more of the elements, or if any + /// element is null. + /// + /// The type each element of is expected to be. + /// + /// The collection containing elements the test expects to be of the specified type. + /// + /// + /// The message to include in the exception when an element in + /// is null or not an instance of . The message is shown in + /// test results. + /// + /// + /// 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 is null or some elements of + /// are null or do not inherit from / implement + /// . + /// + public static void AreAllOfType([NotNull] IEnumerable? collection, string? message = "", [CallerArgumentExpression(nameof(collection))] string collectionExpression = "") + { + CheckParameterNotNull(collection, "Assert.AreAllOfType", "collection"); + AreAllOfTypeImpl(collection, typeof(TExpected), genericTypeArgumentName: "TExpected", message, collectionExpression, expectedTypeExpression: null); + } + + private static void AreAllOfTypeImpl(IEnumerable collection, Type expectedType, string? genericTypeArgumentName, string? message, string collectionExpression, string? expectedTypeExpression) + { + List snapshot = [.. collection.Cast()]; + List? mismatches = null; + for (int i = 0; i < snapshot.Count; i++) + { + object? element = snapshot[i]; + if (element is null) + { + mismatches ??= []; + mismatches.Add(new TypeMismatch(i, actualType: null)); + } + else if (!expectedType.IsInstanceOfType(element)) + { + mismatches ??= []; + mismatches.Add(new TypeMismatch(i, element.GetType())); + } + } + + if (mismatches is not null) + { + ReportAssertAreAllOfTypeFailed(snapshot, expectedType, mismatches, genericTypeArgumentName, message, collectionExpression, expectedTypeExpression); + } + } + + [DoesNotReturn] + private static void ReportAssertAreAllOfTypeFailed(IEnumerable collection, Type expectedType, List mismatches, string? genericTypeArgumentName, string? message, string collectionExpression, string? expectedTypeExpression) + { + string collectionText = AssertionValueRenderer.RenderValue(collection); + string expectedTypeText = $"{expectedType} (or derived)"; + + StringBuilder mismatchesBuilder = new(); + mismatchesBuilder.Append('['); + for (int i = 0; i < mismatches.Count; i++) + { + if (i > 0) + { + mismatchesBuilder.Append(", "); + } + + TypeMismatch mismatch = mismatches[i]; + mismatchesBuilder.Append("index ").Append(mismatch.Index).Append(": "); + if (mismatch.ActualType is null) + { + mismatchesBuilder.Append(""); + } + else + { + mismatchesBuilder.Append(mismatch.ActualType); + } + } + + mismatchesBuilder.Append(']'); + + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected type:", expectedTypeText) + .AddLine("mismatches:", mismatchesBuilder.ToString()) + .AddLine("collection:", collectionText); + + string assertionMethodName = genericTypeArgumentName is null + ? "Assert.AreAllOfType" + : $"Assert.AreAllOfType<{genericTypeArgumentName}>"; + + string? callSite = genericTypeArgumentName is null + ? FormatCallSiteExpression(assertionMethodName, expectedTypeExpression!, collectionExpression, "", "") + : FormatCallSiteExpression(assertionMethodName, collectionExpression, ""); + + StructuredAssertionMessage structured = new(FrameworkMessages.AreAllOfTypeFailedSummary); + structured.WithUserMessage(message); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedTypeText, collectionText); + structured.WithCallSiteExpression(callSite); + + ReportAssertFailed(structured); + } + + private readonly struct TypeMismatch + { + public TypeMismatch(int index, Type? actualType) + { + Index = index; + ActualType = actualType; + } + + public int Index { get; } + + public Type? ActualType { get; } + } + + #endregion // AreAllOfType +} diff --git a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt index b6e5dde3df..b33be34749 100644 --- a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt @@ -2,6 +2,14 @@ [MSTESTEXP]static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Scope() -> System.IDisposable! Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException.ActualText.get -> string? Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException.ExpectedText.get -> string? +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.IEnumerable? collection, System.Collections.IEqualityComparer? comparer, string? message = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.IEnumerable? collection, string? message = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.Generic.IEnumerable? collection, System.Collections.Generic.IEqualityComparer? comparer, string? message = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllDistinct(System.Collections.Generic.IEnumerable? collection, string? message = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllNotNull(System.Collections.IEnumerable? collection, string? message = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllNotNull(System.Collections.Generic.IEnumerable? collection, string? message = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllOfType(System.Type? expectedType, System.Collections.IEnumerable? collection, string? message = "", string! expectedTypeExpression = "", string! collectionExpression = "") -> void +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreAllOfType(System.Collections.IEnumerable? collection, string? message = "", string! collectionExpression = "") -> void static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEquivalent(T? expected, T? actual, bool strict, string? message = "", string! expectedExpression = "", string! actualExpression = "") -> void 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 diff --git a/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx b/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx index dd9fc1b561..30692162b0 100644 --- a/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx +++ b/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx @@ -479,6 +479,15 @@ Actual: {2} Expected value to not be null. + + Expected all items in collection to be non-null. + + + Expected all items in collection to be distinct. + + + Expected all items in collection to be of the specified type. + Expected string to match the specified regular expression. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf index 59ccc4447e..fcdb0c36a4 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf @@ -17,6 +17,21 @@ Byla nalezena duplicitní položka:<{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Očekáváno:<{1}>. Aktuálně:<{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf index 512defa5c8..5e7f1c48d2 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf @@ -17,6 +17,21 @@ Doppeltes Element gefunden: <{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Erwartet:<{1}>. Tatsächlich:<{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf index 0e8a5863fd..e82d1f0364 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf @@ -17,6 +17,21 @@ Se encontró un elemento duplicado:<{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Se esperaba <{1}>, pero es <{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf index f2393eb9c6..1338340da3 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf @@ -17,6 +17,21 @@ Un élément dupliqué a été trouvé : <{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Attendu : <{1}>, Réel : <{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf index 561a73665e..9f26017606 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf @@ -17,6 +17,21 @@ Rilevato elemento duplicato:<{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Previsto:<{1}>. Effettivo:<{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf index 16e474fec9..430fa3d6fb 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf @@ -17,6 +17,21 @@ 重複する項目が見つかりました:<{1}>。{0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} <{1}> が必要ですが、<{2}> が指定されました。{0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf index af329e6b4f..c9f2dd212a 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf @@ -17,6 +17,21 @@ 중복된 항목이 있습니다. <{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} 예상 값: <{1}>. 실제 값: <{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf index 2400f343a5..c9b1425b60 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf @@ -17,6 +17,21 @@ Znaleziono duplikat:<{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Oczekiwana:<{1}>. Rzeczywista:<{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf index f154240002..dd34fd89e8 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf @@ -17,6 +17,21 @@ Item duplicado encontrado:<{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Esperado:<{1}>. Real:<{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf index 723aab1646..4fa3d74ff0 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf @@ -17,6 +17,21 @@ Обнаружен совпадающий элемент: <{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Ожидается: <{1}>. Фактически: <{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf index 207520dbec..17a10138eb 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf @@ -17,6 +17,21 @@ Yinelenen öğe bulundu:<{1}>. {0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} Beklenen:<{1}>. Gerçek:<{2}>. {0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf index 89894d5145..667f1f5ba8 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf @@ -17,6 +17,21 @@ 找到了重复项: <{1}>。{0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} 应为: <{1}>,实际为: <{2}>。{0} diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf index ae7770aaeb..3aeee9b380 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf @@ -17,6 +17,21 @@ 找到重複的項目: <{1}>。{0} + + Expected all items in collection to be distinct. + Expected all items in collection to be distinct. + + + + Expected all items in collection to be non-null. + Expected all items in collection to be non-null. + + + + Expected all items in collection to be of the specified type. + Expected all items in collection to be of the specified type. + + Expected:<{1}>. Actual:<{2}>. {0} 預期: <{1}>。實際: <{2}>。{0} diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs new file mode 100644 index 0000000000..6ae04d2944 --- /dev/null +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreAll.cs @@ -0,0 +1,665 @@ +// 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 AwesomeAssertions; + +using TestFramework.ForTestingMSTest; + +namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests; + +public partial class AssertTests : TestContainer +{ + #region AreAllNotNull + + public void AreAllNotNull_Generic_NoNulls_ShouldPass() + => Assert.AreAllNotNull(new[] { "a", "b", "c" }); + + public void AreAllNotNull_Generic_Empty_ShouldPass() + => Assert.AreAllNotNull(Array.Empty()); + + public void AreAllNotNull_Generic_ValueTypes_ShouldPass() + => Assert.AreAllNotNull(new[] { 1, 2, 3 }); + + public void AreAllNotNull_Generic_HasNull_ShouldFail() + { + Action action = () => Assert.AreAllNotNull(new[] { "a", null, "b", null }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be non-null. + + null indices: [1, 3] + collection: ["a", null, "b", null] + + Assert.AreAllNotNull(new[] { "a", null, "b", null }) + """); + } + + public void AreAllNotNull_Generic_WithUserMessage_ShouldFail() + { + Action action = () => Assert.AreAllNotNull(new[] { "a", null }, "User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be non-null. + User-provided message + + null indices: [1] + collection: ["a", null] + + Assert.AreAllNotNull(new[] { "a", null }) + """); + } + + public void AreAllNotNull_Generic_NullCollection_ShouldFail() + { + Action action = () => Assert.AreAllNotNull((IEnumerable?)null); + action.Should().Throw() + .WithMessage("Assert.AreAllNotNull failed. The parameter 'collection' is invalid. The value cannot be null."); + } + + public void AreAllNotNull_NonGeneric_NoNulls_ShouldPass() + { + ArrayList list = ["a", "b", "c"]; + Assert.AreAllNotNull(list); + } + + public void AreAllNotNull_NonGeneric_HasNull_ShouldFail() + { + ArrayList list = ["a", null, "b"]; + Action action = () => Assert.AreAllNotNull(list); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be non-null. + + null indices: [1] + collection: ["a", null, "b"] + + Assert.AreAllNotNull(list) + """); + } + + public void AreAllNotNull_NonGeneric_NullCollection_ShouldFail() + { + Action action = () => Assert.AreAllNotNull(null); + action.Should().Throw() + .WithMessage("Assert.AreAllNotNull failed. The parameter 'collection' is invalid. The value cannot be null."); + } + + public void AreAllNotNull_NonGeneric_WithUserMessage_ShouldFail() + { + ArrayList list = ["a", null]; + Action action = () => Assert.AreAllNotNull(list, "User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be non-null. + User-provided message + + null indices: [1] + collection: ["a", null] + + Assert.AreAllNotNull(list) + """); + } + + public void AreAllNotNull_Generic_NullableValueType_HasNull_ShouldFail() + { + Action action = () => Assert.AreAllNotNull(new int?[] { 1, null, 3 }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be non-null. + + null indices: [1] + collection: [1, null, 3] + + Assert.AreAllNotNull(new int?[] { 1, null, 3 }) + """); + } + + public void AreAllNotNull_Generic_LazyEnumerable_HasNull_ShouldFail() + { + static IEnumerable Lazy() + { + yield return "a"; + yield return null; + yield return "b"; + } + + Action action = () => Assert.AreAllNotNull(Lazy()); + action.Should().Throw() + .WithMessage("*null indices: [1]*collection:*"); + } + + #endregion // AreAllNotNull + + #region AreAllDistinct + + public void AreAllDistinct_Generic_AllDistinct_ShouldPass() + => Assert.AreAllDistinct(new[] { 1, 2, 3 }); + + public void AreAllDistinct_Generic_Empty_ShouldPass() + => Assert.AreAllDistinct(Array.Empty()); + + public void AreAllDistinct_Generic_SingleNull_ShouldPass() + => Assert.AreAllDistinct(new string?[] { "a", null, "b" }); + + public void AreAllDistinct_Generic_HasDuplicate_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new[] { 1, 2, 3, 4, 3 }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [3] + collection: [1, 2, 3, 4, 3] + + Assert.AreAllDistinct(new[] { 1, 2, 3, 4, 3 }) + """); + } + + public void AreAllDistinct_Generic_HasMultipleDuplicates_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new[] { 1, 2, 3, 2, 1, 1 }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [2, 1] + collection: [1, 2, 3, 2, 1, 1] + + Assert.AreAllDistinct(new[] { 1, 2, 3, 2, 1, 1 }) + """); + } + + public void AreAllDistinct_Generic_NullDuplicate_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new string?[] { "a", null, "b", null }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [null] + collection: ["a", null, "b", null] + + Assert.AreAllDistinct(new string?[] { "a", null, "b", null }) + """); + } + + public void AreAllDistinct_Generic_WithUserMessage_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new[] { 1, 1 }, "User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + User-provided message + + duplicates: [1] + collection: [1, 1] + + Assert.AreAllDistinct(new[] { 1, 1 }) + """); + } + + public void AreAllDistinct_Generic_WithComparer_AllDistinct_ShouldPass() + => Assert.AreAllDistinct(new[] { "a", "B", "c" }, new CaseInsensitiveStringComparer()); + + public void AreAllDistinct_Generic_WithComparer_HasDuplicate_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new[] { "A", "B", "a" }, new CaseInsensitiveStringComparer()); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: ["a"] + collection: ["A", "B", "a"] + comparer: CaseInsensitiveStringComparer + + Assert.AreAllDistinct(new[] { "A", "B", "a" }, ) + """); + } + + public void AreAllDistinct_Generic_NullCollection_ShouldFail() + { + Action action = () => Assert.AreAllDistinct((IEnumerable?)null); + action.Should().Throw() + .WithMessage("Assert.AreAllDistinct failed. The parameter 'collection' is invalid. The value cannot be null."); + } + + public void AreAllDistinct_Generic_NullComparer_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new[] { 1, 2 }, (IEqualityComparer?)null); + action.Should().Throw() + .WithMessage("Assert.AreAllDistinct failed. The parameter 'comparer' is invalid. The value cannot be null."); + } + + public void AreAllDistinct_NonGeneric_AllDistinct_ShouldPass() + { + ArrayList list = [1, "a", 3.5]; + Assert.AreAllDistinct(list); + } + + public void AreAllDistinct_NonGeneric_HasDuplicate_ShouldFail() + { + ArrayList list = [1, 2, 3, 2]; + Action action = () => Assert.AreAllDistinct(list); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [2] + collection: [1, 2, 3, 2] + + Assert.AreAllDistinct(list) + """); + } + + public void AreAllDistinct_NonGeneric_WithComparer_HasDuplicate_ShouldFail() + { + ArrayList list = ["A", "B", "a"]; + Action action = () => Assert.AreAllDistinct(list, new CaseInsensitiveStringComparer()); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: ["a"] + collection: ["A", "B", "a"] + comparer: CaseInsensitiveStringComparer + + Assert.AreAllDistinct(list, ) + """); + } + + public void AreAllDistinct_NonGeneric_NullCollection_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(null); + action.Should().Throw() + .WithMessage("Assert.AreAllDistinct failed. The parameter 'collection' is invalid. The value cannot be null."); + } + + public void AreAllDistinct_NonGeneric_NullComparer_ShouldFail() + { + ArrayList list = [1, 2]; + Action action = () => Assert.AreAllDistinct(list, (IEqualityComparer?)null); + action.Should().Throw() + .WithMessage("Assert.AreAllDistinct failed. The parameter 'comparer' is invalid. The value cannot be null."); + } + + public void AreAllDistinct_NonGeneric_WithUserMessage_ShouldFail() + { + ArrayList list = [1, 1]; + Action action = () => Assert.AreAllDistinct(list, "User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + User-provided message + + duplicates: [1] + collection: [1, 1] + + Assert.AreAllDistinct(list) + """); + } + + public void AreAllDistinct_NonGeneric_SingleNull_ShouldPass() + { + ArrayList list = ["a", null, "b"]; + Assert.AreAllDistinct(list); + } + + public void AreAllDistinct_NonGeneric_NullDuplicate_ShouldFail() + { + ArrayList list = ["a", null, null]; + Action action = () => Assert.AreAllDistinct(list); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [null] + collection: ["a", null, null] + + Assert.AreAllDistinct(list) + """); + } + + public void AreAllDistinct_Generic_ManyNulls_ReportsNullOnce_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new string?[] { null, "a", null, "b", null }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [null] + collection: [null, "a", null, "b", null] + + Assert.AreAllDistinct(new string?[] { null, "a", null, "b", null }) + """); + } + + public void AreAllDistinct_Generic_ManyDuplicatesOfSameValue_ReportsValueOnce_ShouldFail() + { + Action action = () => Assert.AreAllDistinct(new[] { 5, 5, 5, 5 }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be distinct. + + duplicates: [5] + collection: [5, 5, 5, 5] + + Assert.AreAllDistinct(new[] { 5, 5, 5, 5 }) + """); + } + + // Pins the current behavior: null elements are short-circuited and never passed to the user-provided comparer. + // A comparer that treats null as equal to "" is therefore not consulted, and [null, ""] is considered distinct. + public void AreAllDistinct_Generic_ComparerTreatsNullAsEmpty_NullAndEmpty_ShouldPass() + => Assert.AreAllDistinct(new string?[] { null, string.Empty }, new NullEqualsEmptyStringComparer()); + + // Pins the current behavior: a comparer that throws on null is never invoked with a null argument because + // null elements are short-circuited before the comparer is consulted. + public void AreAllDistinct_Generic_ComparerThrowsOnNull_WithNulls_ShouldNotInvokeComparerForNull() + => Assert.AreAllDistinct(new string?[] { null, "a", "b" }, new ThrowOnNullStringComparer()); + + private sealed class CaseInsensitiveStringComparer : IEqualityComparer, IEqualityComparer + { + public bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + + 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; + } + + private sealed class NullEqualsEmptyStringComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) => (x ?? string.Empty) == (y ?? string.Empty); + + public int GetHashCode(string? obj) => (obj ?? string.Empty).GetHashCode(); + } + + private sealed class ThrowOnNullStringComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) + => x is null || y is null + ? throw new InvalidOperationException("Comparer should not be invoked with null.") + : x == y; + + public int GetHashCode(string? obj) + => obj is null + ? throw new InvalidOperationException("Comparer should not be invoked with null.") + : obj.GetHashCode(); + } + + #endregion // AreAllDistinct + + #region AreAllOfType + + public void AreAllOfType_Generic_AllMatch_ShouldPass() + => Assert.AreAllOfType(new object[] { "a", "b", "c" }); + + public void AreAllOfType_Generic_DerivedTypes_ShouldPass() + => Assert.AreAllOfType(new object[] { new DerivedAllItemsA(), new DerivedAllItemsB() }); + + public void AreAllOfType_Generic_BaseTypeAcceptsDerived_ShouldPass() + => Assert.AreAllOfType(new object[] { 1, "two", 3.0 }); + + public void AreAllOfType_Generic_Empty_ShouldPass() + => Assert.AreAllOfType(Array.Empty()); + + public void AreAllOfType_Generic_NullElement_ShouldFail() + { + Action action = () => Assert.AreAllOfType(new object?[] { "a", null, "b" }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + + expected type: System.String (or derived) + mismatches: [index 1: ] + collection: ["a", null, "b"] + + Assert.AreAllOfType(new object?[] { "a", null, "b" }) + """); + } + + public void AreAllOfType_Generic_HasMismatch_ShouldFail() + { + Action action = () => Assert.AreAllOfType(new object[] { "a", 42, "b", true }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + + expected type: System.String (or derived) + mismatches: [index 1: System.Int32, index 3: System.Boolean] + collection: ["a", 42, "b", true] + + Assert.AreAllOfType(new object[] { "a", 42, "b", true }) + """); + } + + public void AreAllOfType_Generic_WithUserMessage_ShouldFail() + { + Action action = () => Assert.AreAllOfType(new object[] { 1 }, "User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + User-provided message + + expected type: System.String (or derived) + mismatches: [index 0: System.Int32] + collection: [1] + + Assert.AreAllOfType(new object[] { 1 }) + """); + } + + public void AreAllOfType_Generic_HasMismatch_ShouldPopulateExpectedAndActualPayload() + { + try + { + Assert.AreAllOfType(new object[] { 1 }); + } + catch (AssertFailedException ex) + { + ex.ExpectedText.Should().Be("System.String (or derived)"); + ex.ActualText.Should().Be("[1]"); + ex.Data["assert.expected"].Should().Be("System.String (or derived)"); + ex.Data["assert.actual"].Should().Be("[1]"); + return; + } + + throw new AssertFailedException("Expected AssertFailedException was not thrown."); + } + + public void AreAllOfType_Generic_NullCollection_ShouldFail() + { + Action action = () => Assert.AreAllOfType(null); + action.Should().Throw() + .WithMessage("Assert.AreAllOfType failed. The parameter 'collection' is invalid. The value cannot be null."); + } + + public void AreAllOfType_NonGeneric_AllMatch_ShouldPass() + { + ArrayList list = ["a", "b"]; + Assert.AreAllOfType(typeof(string), list); + } + + public void AreAllOfType_NonGeneric_HasMismatch_ShouldFail() + { + ArrayList list = ["a", 42]; + Action action = () => Assert.AreAllOfType(typeof(string), list); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + + expected type: System.String (or derived) + mismatches: [index 1: System.Int32] + collection: ["a", 42] + + Assert.AreAllOfType(typeof(string), list) + """); + } + + public void AreAllOfType_NonGeneric_NullCollection_ShouldFail() + { + Action action = () => Assert.AreAllOfType(typeof(string), null); + action.Should().Throw() + .WithMessage("Assert.AreAllOfType failed. The parameter 'collection' is invalid. The value cannot be null."); + } + + public void AreAllOfType_NonGeneric_NullExpectedType_ShouldFail() + { + ArrayList list = ["a"]; + Action action = () => Assert.AreAllOfType(null, list); + action.Should().Throw() + .WithMessage("Assert.AreAllOfType failed. The parameter 'expectedType' is invalid. The value cannot be null."); + } + + public void AreAllOfType_NonGeneric_WithUserMessage_ShouldFail() + { + ArrayList list = [1]; + Action action = () => Assert.AreAllOfType(typeof(string), list, "User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + User-provided message + + expected type: System.String (or derived) + mismatches: [index 0: System.Int32] + collection: [1] + + Assert.AreAllOfType(typeof(string), list) + """); + } + + public void AreAllOfType_NonGeneric_DerivedTypes_ShouldPass() + { + ArrayList list = [new DerivedAllItemsA(), new DerivedAllItemsB()]; + Assert.AreAllOfType(typeof(DerivedAllItemsBase), list); + } + + public void AreAllOfType_NonGeneric_NullElement_ShouldFail() + { + ArrayList list = ["a", null, "b"]; + Action action = () => Assert.AreAllOfType(typeof(string), list); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + + expected type: System.String (or derived) + mismatches: [index 1: ] + collection: ["a", null, "b"] + + Assert.AreAllOfType(typeof(string), list) + """); + } + + public void AreAllOfType_NonGeneric_Empty_ShouldPass() + { + ArrayList list = []; + Assert.AreAllOfType(typeof(string), list); + } + + public void AreAllOfType_Generic_BoxedValueType_AllMatch_ShouldPass() + => Assert.AreAllOfType(new object[] { 1, 2, 3 }); + + public void AreAllOfType_Generic_BoxedValueType_HasMismatch_ShouldFail() + { + Action action = () => Assert.AreAllOfType(new object[] { 1, "x" }); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + + expected type: System.Int32 (or derived) + mismatches: [index 1: System.String] + collection: [1, "x"] + + Assert.AreAllOfType(new object[] { 1, "x" }) + """); + } + + // Pins the current behavior of `AreAllOfType` for nullable value types: a `null` element fails because + // `typeof(int?).IsInstanceOfType(null)` returns false. Boxed non-null nullable values are accepted because + // boxing erases the `Nullable` wrapper. + public void AreAllOfType_Generic_NullableValueType_NullElement_ShouldFail() + { + Action action = () => Assert.AreAllOfType(new object?[] { 1, null, 3 }); + action.Should().Throw() + .WithMessage("*mismatches: [index 1: ]*"); + } + + public void AreAllOfType_Generic_StructMismatch_ShouldFail() + { + Action action = () => Assert.AreAllOfType(new object[] { DateTime.UtcNow, "x" }); + action.Should().Throw() + .WithMessage( + """ + *expected type: System.DateTime (or derived) + mismatches: [index 1: System.String]* + """); + } + + public void AreAllOfType_NonGeneric_BoxedValueType_AllMatch_ShouldPass() + { + ArrayList list = [1, 2, 3]; + Assert.AreAllOfType(typeof(int), list); + } + + public void AreAllOfType_NonGeneric_BoxedValueType_HasMismatch_ShouldFail() + { + ArrayList list = [1, "x"]; + Action action = () => Assert.AreAllOfType(typeof(int), list); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected all items in collection to be of the specified type. + + expected type: System.Int32 (or derived) + mismatches: [index 1: System.String] + collection: [1, "x"] + + Assert.AreAllOfType(typeof(int), list) + """); + } + + public void AreAllOfType_Generic_Interface_AllImplement_ShouldPass() + => Assert.AreAllOfType(new object[] { new System.IO.MemoryStream(), new System.IO.MemoryStream() }); + + public void AreAllOfType_NonGeneric_Interface_HasMismatch_ShouldFail() + { + ArrayList list = [new System.IO.MemoryStream(), "not-disposable-string"]; + Action action = () => Assert.AreAllOfType(typeof(IDisposable), list); + action.Should().Throw() + .WithMessage("*expected type: System.IDisposable (or derived)*mismatches: [index 1: System.String]*"); + } + + private class DerivedAllItemsBase; + + private sealed class DerivedAllItemsA : DerivedAllItemsBase; + + private sealed class DerivedAllItemsB : DerivedAllItemsBase; + + #endregion // AreAllOfType +}