Add Assert.AreAllNotNull / AreAllDistinct / AreAllOfType#8237
Add Assert.AreAllNotNull / AreAllDistinct / AreAllOfType#8237Evangelink wants to merge 8 commits into
Conversation
Mirror the CollectionAssert.AllItems* APIs onto Assert per RFC 012. Adds: - Assert.AllItemsAreNotNull (generic + non-generic IEnumerable) - Assert.AllItemsAreUnique (generic + non-generic, both with optional comparer) - Assert.AllItemsAreInstancesOfType (generic <TExpected> + non-generic Type) All produce structured assertion messages including evidence (null indices / duplicates / mismatches), source collection, optional comparer, and expression-based call site. Notes: - NonGenericEqualityComparerAdapter is duplicated with PR #8234 (Assert.IsSubsetOf); will be deduped post-merge.
Review — Dimensions 16–21Dimension 16 · Naming & Conventions — NIT
// current
return string.Concat(callSite.Substring(0, callSite.Length - 1), ", <comparer>)");
// preferred
return string.Concat(callSite[..^1], ", <comparer>)");
In Everything else — method names ( Dimension 17 · Documentation Accuracy — MODERATENull elements are silently skipped in The implementation: if (element is not null && !expectedType.IsInstanceOfType(element))So a
A null element does not inherit from Suggested addition to both the non-generic and generic overloads' /// Null elements are skipped and do not cause the assertion to fail.(or a dedicated Dimension 18 · Analyzer & Code Fix QualityN/A — no changes under Dimension 19 · IPC Wire CompatibilityN/A — no changes to serialization/IPC types in Dimension 20 · Build Infrastructure & Dependencies — LGTM
Dimension 21 · Scope & PR Discipline — NITIntentional The PR description says:
However there is no // TODO: deduplicate with the same adapter in Assert.CollectionEquivalence.cs once PR #8234 is merged.
private sealed class NonGenericEqualityComparerAdapter : IEqualityComparer<object?>Scope — the PR is tightly focused on the three Summary
|
Evangelink
left a comment
There was a problem hiding this comment.
Review: Dimensions 6–10
Dim 6 — Cross-TFM Compatibility ✅ LGTM
All APIs used ([CallerArgumentExpression], Cast<>(), HashSet<T>(IEqualityComparer<T>), collection expressions [.. collection], StringBuilder) are already used elsewhere in the codebase across net462/netstandard2.0/net8.0/net9.0. No new TFM gaps introduced.
#pragma warning disable CS8714 is correct: HashSet<T> requires notnull, but T here can be nullable (e.g. string?). This suppression is the right approach rather than constraining the overloads.
Dim 7 — Resource & IDisposable Management ✅ LGTM
List<T>, HashSet<T>, and StringBuilder are not IDisposable. No file handles, streams, or other disposable resources created.
Dim 8 — Defensive Coding at Boundaries ✅ LGTM
- Custom comparer throws: exceptions from user-supplied
IEqualityComparer/IEqualityComparer<T>propagating out of the assertion is consistent with howHashSet<T>behaves everywhere in the framework. Acceptable. collection is List<T> list ? list : [.. collection]: Aliases the original list rather than copying — indices remain stable within the single-threaded execution of the assertion. Non-thread-safe behavior is expected and documented by convention.collection.Cast<object?>(): safe —CheckParameterNotNullalready guards against null.
Dim 9 — Localization & Resources ✅ LGTM
New .resx summary entries (AllItemsAreNotNullFailedSummary, AllItemsAreUniqueFailedSummary, AllItemsAreInstancesOfTypeFailedSummary) are correctly added and used via FrameworkMessages.*. .xlf files are left for the build to regenerate.
Evidence labels ("null indices:", "expected type:", "mismatches:", "collection:", "duplicates:", "comparer:") and the "(or derived)" fragment are hardcoded string literals — but this is consistent with the established pattern: Assert.IsInstanceOfType.cs, Assert.IsNull.cs, Assert.ThrowsException.cs, etc. all hardcode the same class of label strings. The .resx boundary (summary/user-facing message vs. structural label) is applied uniformly.
The "index " prefix inside the mismatch StringBuilder is similarly a structural formatting string, consistent with the pattern throughout.
Dim 10 — Test Isolation ✅ LGTM
No static mutable state. Each test creates its input data and assertion action locally. No shared setup that could leak between test cases.
Generated by Expert Code Review (on open) for issue #8237 · ● 24.2M
- Drop comparer.GetType().Name from failure evidence (Dim 16): the CLR type name is fragile across TFMs (e.g. StringComparer.OrdinalIgnoreCase reports OrdinalComparer on .NET Framework, OrdinalIgnoreCaseComparer on .NET 5+). The <comparer> placeholder in the call-site already conveys that a custom comparer was used. Fixes net48 test failures. - Document that null elements are skipped in AllItemsAreInstancesOfType (Dim 17). - Add TODO for NonGenericEqualityComparerAdapter dedup with PR #8234 (Dim 21). - Note why callSite[..^1] cannot be used (Dim 16 NIT was invalid: System.Range/System.Index not available on net462/netstandard2.0). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Assert.AllItemsAreNotNull -> Assert.AreAllNotNull
- Assert.AllItemsAreUnique -> Assert.AreAllDistinct (LINQ-aligned vocabulary)
- Assert.AllItemsAreInstancesOfType -> Assert.AreAllOfType
- Param order flipped to (expectedType, collection) to match
the modern (expected, actual, options) Assert convention.
- Null elements now FAIL (previously skipped) to match the
semantics of the singular Assert.IsInstanceOfType ([NotNull])
and the LINQ Enumerable.OfType<T> contract that null is T == false.
- Mismatch evidence renders null elements as 'index N: <null>'.
Also:
- Renamed resx entries (AllItems* -> AreAll*) and regenerated xlf.
- Updated PublicAPI.Unshipped.txt (8 entries, alphabetical).
- Updated unit tests (renamed file + methods, adjusted expected
failure messages, added _NullElement_ShouldFail tests for
AreAllOfType).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs:165
duplicatesSeen ??= new HashSet<T>(comparer);can be simplified to target-typednew(comparer)(matchingseen) so theIDE0028suppression isn’t needed here either.
#pragma warning disable IDE0028
duplicatesSeen ??= new HashSet<T>(comparer);
#pragma warning restore IDE0028
- Files reviewed: 20/20 changed files
- Comments generated: 3
3d5f95d to
53d0687
Compare
…ng and expand test coverage - Replace parallel List<int>/List<Type?> with a single List<TypeMismatch> using a private readonly struct (avoids ValueTuple which is unavailable on net462). - Add Debug.Assert pinning the FormatCallSiteExpression contract that the rendered call-site ends with ')'. - Expand AreAllOfType tests to cover boxed value types, Nullable<T> null elements, struct mismatches, non-generic value-type elements, and interface expected types. - Add AreAllDistinct tests pinning the null short-circuit semantics for user-provided comparers (null elements are not passed to the comparer). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Resolved in
Addressed in
|
|
@copilot fix these build issues @azure-pipelines src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs(132,9): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007) @azure-pipelines src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs(132,27): error IDE0028: (NETCORE_ENGINEERING_TELEMETRY=Build) Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) @azure-pipelines src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs(161,36): error IDE0028: (NETCORE_ENGINEERING_TELEMETRY=Build) Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) @azure-pipelines src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs(132,9): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007) @azure-pipelines src/TestFramework/TestFramework/Assertions/Assert.AreAllDistinct.cs(132,27): error IDE0028: (NETCORE_ENGINEERING_TELEMETRY=Build) Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) |
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Fixed in 297979e: |
|
@copilot resolve the merge conflicts in this pull request |
…-allitems # Conflicts: # src/TestFramework/TestFramework/Resources/FrameworkMessages.resx Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Resolved in 8ff7b77: merged current |
Summary
Mirror the existing
CollectionAssert.AllItems*APIs ontoAssert, producing structured assertion messages per RFC 012.This is the second PR in a series adding collection-style assertions to
Assert. PR1 (#8234) introducedAssert.IsSubsetOf/IsNotSubsetOf.APIs added
Assert.AreAllNotNull(IEnumerable, ...)andAssert.AreAllNotNull<T>(IEnumerable<T>, ...)Assert.AreAllDistinct(IEnumerable, [IEqualityComparer], ...)andAssert.AreAllDistinct<T>(IEnumerable<T>, [IEqualityComparer<T>], ...)Assert.AreAllOfType(Type, IEnumerable, ...)andAssert.AreAllOfType<TExpected>(IEnumerable, ...)All overloads accept an optional
string? messageand use[CallerArgumentExpression]to surface the original source-text in the rendered call site.Output format
nullis reported a single time even when multiple null entries are duplicated.AreAllOfType, the generic call site renders asAssert.AreAllOfType<TExpected>(...)(we cannot know the user-typedTfromtypeof(T).Name, so we use a stable literal placeholder).Tests
Unit tests covering: pass paths, empty collections, null collection (parameter-validation), null comparer, user messages, lazy
IEnumerable<T>,T = int?, derived types, comparer (custom + null-handling), 3+ nulls reported once, repeated-duplicates reported once, both generic and non-generic dispatch.Notes for reviewer
NonGenericEqualityComparerAdapteris intentionally duplicated with PR Add Assert.ContainsAll and Assert.DoesNotContainAll with structured assertion messages #8234. It will be deduped to a single shared file once both PRs land.PublicAPI.Unshipped.txt.Build.cmdis clean (0 warnings, 0 errors) across all TFMs.Relates to #7956