diff --git a/Arcade.slnx b/Arcade.slnx
index ad6355b7062..1e9aa9aae67 100644
--- a/Arcade.slnx
+++ b/Arcade.slnx
@@ -33,6 +33,7 @@
+
@@ -72,6 +73,7 @@
+
diff --git a/Documentation/update-xunit.md b/Documentation/update-xunit.md
index 3aab0389da9..270eaa1f6ed 100644
--- a/Documentation/update-xunit.md
+++ b/Documentation/update-xunit.md
@@ -3,4 +3,6 @@ This document aims to establish the necessary actions to update the xunit versio
1. For security reasons, nuget packages need to be manually mirrored from nuget.org to the dotnet-public AzDO feed. [See the instructions](/Documentation/MirroringPackages.md). Mirror the following xunit packages: `xunit,xunit.console,xunit.runner.reporters,xunit.runner.utility,xunit.runner.console,xunit.runner.visualstudio` with version `latest`.
2. Update `XUnitVersion`, `XUnitAnalyzersVersion`, `XUnitRunnerVisualStudioVersion`, `XUnitV3Version` and `MicrosoftTestingPlatformVersion` properties in [Arcade SDK's DefaultVersions.props](/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props) to the desired values. Make sure to use a coherent version of `xunit.analyzers`. Note that `MicrosoftTestingPlatformVersion` must be compatible with the `XUnitV3Version` (xunit.v3.mtp-v1 depends on a specific minimum version of Microsoft.Testing.Platform).
3. Update other hardcoded values of `XUnitVersion` inside the Arcade repository (i.e. in [SendingJobsToHelix.md](/Documentation/AzureDevOps/SendingJobsToHelix.md), [Directory.Packages.props](/Directory.Packages.props) and others).
-4. Submit a Pull request with these changes to [dotnet/arcade](https://github.com/dotnet/arcade).
+4. Update Microsoft.DotNet.XUnitAssert which is an AOT compatible fork of the xunit.assert library by following [the instructions](/src/Microsoft.DotNet.XUnitAssert/README.md). It's likely that new XUnit versions introduce AOT incompatibilities which will cause the compiler (AOT analyzer) to fail. If you aren't sure how to resolve the errors, consult with @agocke's team who owns this library.
+5. Submit a Pull request with these changes to [dotnet/arcade](https://github.com/dotnet/arcade) and tag @ViktorHofer as a reviewer.
+6. Update the build from source compatible xunit/xunit fork in the [dotnet/source-build-reference-packages](https://github.com/dotnet/source-build-reference-packages/tree/main/src/externalPackages) repository by following [the instructions](https://github.com/dotnet/source-build-reference-packages) and submit a Pull Request.
diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT
index 3dbc4c4b7c2..ec48c2cc1a7 100644
--- a/THIRD-PARTY-NOTICES.TXT
+++ b/THIRD-PARTY-NOTICES.TXT
@@ -10,6 +10,26 @@ The attached notices are provided for information only.
---------------------------------------
+The code in src/Microsoft.DotNet.XUnitAssert/src/* was imported from:
+ https://github.com/xunit/assert.xunit
+
+This set of code is covered by the following license:
+
+ Copyright (c) .NET Foundation and Contributors
+ All Rights Reserved
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
The code in src/Microsoft.DotNet.XUnitConsoleRunner/src/* was imported from:
https://github.com/xunit/xunit/tree/v2/src/xunit.console
diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnit/XUnit.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnit/XUnit.targets
index 74d8b5189b8..f3bee3148e5 100644
--- a/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnit/XUnit.targets
+++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnit/XUnit.targets
@@ -8,8 +8,8 @@
-
-
+
+
diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets
index 578d760106d..f39adb984b1 100644
--- a/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets
+++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets
@@ -11,8 +11,8 @@
-
-
+
+
diff --git a/src/Microsoft.DotNet.XUnitAssert/README.md b/src/Microsoft.DotNet.XUnitAssert/README.md
new file mode 100644
index 00000000000..e1c15c635c1
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/README.md
@@ -0,0 +1,22 @@
+# Custom Version of Xunit Assert
+
+## Origin/Attribution
+
+This is a fork of the code in https://github.com/xunit/assert.xunit for building the
+`Microsoft.DotNet.XUnitAssert` NuGet package. See `../../THIRD-PARTY-NOTICES.TXT` for the license for this code.
+
+## Updating
+
+This repository is a "github subtree" of the assert.xunit repo. Follow these steps to update the code:
+
+1. Find what version you want to update to. This can be a tag or a commit on the assert.xunit repo.
+2. Run the pull command. From the root of the repo run: `git subtree pull --squash --prefix src/Microsoft.DotNet.XUnitAssert/src https://github.com/xunit/assert.xunit `
+3. Resolve merge commits.
+4. Commit the result.
+5. Get someone with admin permissions to **Merge** (not squash or rebase) the results. Git subtree does not like squash.
+
+## Purpose
+
+This copy of assert.xunit is intended to be AOT-compatible and contains breaking changes from the
+original code. In general, code which relied on reflection or dynamic code generation has been
+removed in this fork.
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig b/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig
new file mode 100644
index 00000000000..c6af9b3bdb5
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig
@@ -0,0 +1,228 @@
+# top-most EditorConfig file
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = tab
+
+[*.sln]
+end_of_line = crlf
+
+# Visual Studio demands 2-spaced project files
+# Tabs are not legal whitespace for YAML files
+[*.{csproj,json,props,targets,xslt,yaml,yml}]
+indent_style = space
+indent_size = 2
+
+[*.cs]
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = false
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = true
+csharp_style_var_for_built_in_types = true
+csharp_style_var_when_type_is_apparent = true
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true
+csharp_style_expression_bodied_constructors = false
+csharp_style_expression_bodied_indexers = true
+csharp_style_expression_bodied_lambdas = true
+csharp_style_expression_bodied_local_functions = true
+csharp_style_expression_bodied_methods = true
+csharp_style_expression_bodied_operators = true
+csharp_style_expression_bodied_properties = true
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true
+
+# Modifier preferences
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+
+# Code-block preferences
+csharp_prefer_braces = false
+csharp_prefer_simple_using_statement = true
+csharp_style_namespace_declarations = file_scoped
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_pattern_local_over_anonymous_function = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:warning
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+#### Code quality rules ####
+
+# Analyzer severity
+
+dotnet_diagnostic.IDE0040.severity = none # Add accessibility modifiers
+dotnet_diagnostic.IDE0079.severity = none # Remove unnecessary suppression
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/.gitattributes b/src/Microsoft.DotNet.XUnitAssert/src/.gitattributes
new file mode 100644
index 00000000000..f062fc07828
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/.gitattributes
@@ -0,0 +1,6 @@
+* text=auto eol=lf
+*.cs text diff=csharp
+*.csproj text merge=union
+*.resx text merge=union
+*.sln text eol=crlf merge=union
+*.vbproj text merge=union
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/.github/FUNDING.yml b/src/Microsoft.DotNet.XUnitAssert/src/.github/FUNDING.yml
new file mode 100644
index 00000000000..0a99d30a482
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: xunit
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs b/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs
new file mode 100644
index 00000000000..3e9d4a26ff8
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs
@@ -0,0 +1,76 @@
+#pragma warning disable CA1052 // Static holder types should be static
+
+#if XUNIT_NULLABLE
+#nullable enable
+#else
+// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE
+#pragma warning disable CS8603
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Xunit
+{
+ ///
+ /// Contains various static methods that are used to verify that conditions are met during the
+ /// process of running tests.
+ ///
+#if XUNIT_VISIBILITY_INTERNAL
+ internal
+#else
+ public
+#endif
+ partial class Assert
+ {
+ static readonly Type typeofDictionary = typeof(Dictionary<,>);
+ static readonly Type typeofHashSet = typeof(HashSet<>);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected Assert() { }
+
+ /// Do not call this method.
+ [Obsolete("This is an override of Object.Equals(). Call Assert.Equal() instead.", true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static new bool Equals(
+ object a,
+ object b)
+ {
+ throw new InvalidOperationException("Assert.Equals should not be used");
+ }
+
+ /// Do not call this method.
+ [Obsolete("This is an override of Object.ReferenceEquals(). Call Assert.Same() instead.", true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static new bool ReferenceEquals(
+ object a,
+ object b)
+ {
+ throw new InvalidOperationException("Assert.ReferenceEquals should not be used");
+ }
+
+ ///
+ /// Safely perform , returning when the
+ /// type is not generic.
+ ///
+ /// The potentially generic type
+ /// The generic type definition, when is generic; , otherwise.
+#if XUNIT_NULLABLE
+ static Type? SafeGetGenericTypeDefinition(Type? type)
+#else
+ static Type SafeGetGenericTypeDefinition(Type type)
+#endif
+ {
+ if (type == null)
+ return null;
+
+ if (!type.IsGenericType)
+ return null;
+
+ return type.GetGenericTypeDefinition();
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs
new file mode 100644
index 00000000000..1e3b915b66e
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs
@@ -0,0 +1,464 @@
+#pragma warning disable CA1052 // Static holder types should be static
+#pragma warning disable CA1720 // Identifier contains type name
+
+#if NET8_0_OR_GREATER
+
+#if XUNIT_NULLABLE
+#nullable enable
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xunit.Internal;
+using Xunit.Sdk;
+
+namespace Xunit
+{
+ partial class Assert
+ {
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static void All(
+ IAsyncEnumerable collection,
+ Action action) =>
+ All(AssertHelper.ToEnumerable(collection), action);
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action. The item index is provided to the action, in addition to the item.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static void All(
+ IAsyncEnumerable collection,
+ Action action) =>
+ All(AssertHelper.ToEnumerable(collection), action);
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static Task AllAsync(
+ IAsyncEnumerable collection,
+ Func action) =>
+ AllAsync(AssertHelper.ToEnumerable(collection), action);
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action. The item index is provided to the action, in addition to the item.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static Task AllAsync(
+ IAsyncEnumerable collection,
+ Func action) =>
+ AllAsync(AssertHelper.ToEnumerable(collection), action);
+
+ ///
+ /// Verifies that a collection contains exactly a given number of elements, which meet
+ /// the criteria provided by the element inspectors.
+ ///
+ /// The type of the object to be verified
+ /// The collection to be inspected
+ /// The element inspectors, which inspect each element in turn. The
+ /// total number of element inspectors must exactly match the number of elements in the collection.
+ public static void Collection(
+ IAsyncEnumerable collection,
+ params Action[] elementInspectors) =>
+ Collection(AssertHelper.ToEnumerable(collection), elementInspectors);
+
+ ///
+ /// Verifies that a collection contains exactly a given number of elements, which meet
+ /// the criteria provided by the element inspectors.
+ ///
+ /// The type of the object to be verified
+ /// The collection to be inspected
+ /// The element inspectors, which inspect each element in turn. The
+ /// total number of element inspectors must exactly match the number of elements in the collection.
+ public static Task CollectionAsync(
+ IAsyncEnumerable collection,
+ params Func[] elementInspectors) =>
+ CollectionAsync(AssertHelper.ToEnumerable(collection), elementInspectors);
+
+ ///
+ /// Verifies that a collection contains a given object.
+ ///
+ /// The type of the object to be verified
+ /// The object expected to be in the collection
+ /// The collection to be inspected
+ /// Thrown when the object is not present in the collection
+ public static void Contains(
+ T expected,
+ IAsyncEnumerable collection) =>
+ Contains(expected, AssertHelper.ToEnumerable(collection));
+
+ ///
+ /// Verifies that a collection contains a given object, using an equality comparer.
+ ///
+ /// The type of the object to be verified
+ /// The object expected to be in the collection
+ /// The collection to be inspected
+ /// The comparer used to equate objects in the collection with the expected object
+ /// Thrown when the object is not present in the collection
+ public static void Contains(
+ T expected,
+ IAsyncEnumerable collection,
+ IEqualityComparer comparer) =>
+ Contains(expected, AssertHelper.ToEnumerable(collection), comparer);
+
+ ///
+ /// Verifies that a collection contains a given object.
+ ///
+ /// The type of the object to be verified
+ /// The collection to be inspected
+ /// The filter used to find the item you're ensuring the collection contains
+ /// Thrown when the object is not present in the collection
+ public static void Contains(
+ IAsyncEnumerable collection,
+ Predicate filter) =>
+ Contains(AssertHelper.ToEnumerable(collection), filter);
+
+ ///
+ /// Verifies that a collection contains each object only once.
+ ///
+ /// The type of the object to be compared
+ /// The collection to be inspected
+ /// Thrown when an object is present inside the collection more than once
+ public static void Distinct(IAsyncEnumerable collection) =>
+ Distinct(AssertHelper.ToEnumerable(collection), EqualityComparer.Default);
+
+ ///
+ /// Verifies that a collection contains each object only once.
+ ///
+ /// The type of the object to be compared
+ /// The collection to be inspected
+ /// The comparer used to equate objects in the collection with the expected object
+ /// Thrown when an object is present inside the collection more than once
+ public static void Distinct(
+ IAsyncEnumerable collection,
+ IEqualityComparer comparer) =>
+ Distinct(AssertHelper.ToEnumerable(collection), comparer);
+
+ ///
+ /// Verifies that a collection does not contain a given object.
+ ///
+ /// The type of the object to be compared
+ /// The object that is expected not to be in the collection
+ /// The collection to be inspected
+ /// Thrown when the object is present inside the collection
+ public static void DoesNotContain(
+ T expected,
+ IAsyncEnumerable collection) =>
+ DoesNotContain(expected, AssertHelper.ToEnumerable(collection));
+
+ ///
+ /// Verifies that a collection does not contain a given object, using an equality comparer.
+ ///
+ /// The type of the object to be compared
+ /// The object that is expected not to be in the collection
+ /// The collection to be inspected
+ /// The comparer used to equate objects in the collection with the expected object
+ /// Thrown when the object is present inside the collection
+ public static void DoesNotContain(
+ T expected,
+ IAsyncEnumerable collection,
+ IEqualityComparer comparer) =>
+ DoesNotContain(expected, AssertHelper.ToEnumerable(collection), comparer);
+
+ ///
+ /// Verifies that a collection does not contain a given object.
+ ///
+ /// The type of the object to be compared
+ /// The collection to be inspected
+ /// The filter used to find the item you're ensuring the collection does not contain
+ /// Thrown when the object is present inside the collection
+ public static void DoesNotContain(
+ IAsyncEnumerable collection,
+ Predicate filter) =>
+ DoesNotContain(AssertHelper.ToEnumerable(collection), filter);
+
+ ///
+ /// Verifies that a collection is empty.
+ ///
+ /// The collection to be inspected
+ /// Thrown when the collection is null
+ /// Thrown when the collection is not empty
+ public static void Empty(IAsyncEnumerable collection) =>
+ Empty(AssertHelper.ToEnumerable(collection));
+
+ ///
+ /// Verifies that two sequences are equivalent, using a default comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// Thrown when the objects are not equal
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IAsyncEnumerable? actual) =>
+#else
+ IEnumerable expected,
+ IAsyncEnumerable actual) =>
+#endif
+ Equal(expected, AssertHelper.ToEnumerable(actual), GetEqualityComparer());
+
+ ///
+ /// Verifies that two sequences are equivalent, using a default comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// Thrown when the objects are not equal
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IAsyncEnumerable? expected,
+ IAsyncEnumerable? actual) =>
+#else
+ IAsyncEnumerable expected,
+ IAsyncEnumerable actual) =>
+#endif
+ Equal(AssertHelper.ToEnumerable(expected), AssertHelper.ToEnumerable(actual), GetEqualityComparer());
+
+ ///
+ /// Verifies that two sequences are equivalent, using a custom equatable comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The comparer used to compare the two objects
+ /// Thrown when the objects are not equal
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ IEqualityComparer comparer) =>
+ Equal(expected, AssertHelper.ToEnumerable(actual), GetEqualityComparer>(new AssertEqualityComparerAdapter(comparer)));
+
+ ///
+ /// Verifies that two sequences are equivalent, using a custom equatable comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The comparer used to compare the two objects
+ /// Thrown when the objects are not equal
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IAsyncEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IAsyncEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ IEqualityComparer comparer) =>
+ Equal(AssertHelper.ToEnumerable(expected), AssertHelper.ToEnumerable(actual), GetEqualityComparer>(new AssertEqualityComparerAdapter(comparer)));
+
+ ///
+ /// Verifies that two collections are equal, using a comparer function against
+ /// items in the two collections.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The function to compare two items for equality
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ Func comparer) =>
+ Equal(expected, AssertHelper.ToEnumerable(actual), AssertEqualityComparer.FromComparer(comparer));
+
+ ///
+ /// Verifies that two collections are equal, using a comparer function against
+ /// items in the two collections.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The function to compare two items for equality
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IAsyncEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IAsyncEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ Func comparer) =>
+ Equal(AssertHelper.ToEnumerable(expected), AssertHelper.ToEnumerable(actual), AssertEqualityComparer.FromComparer(comparer));
+
+ ///
+ /// Verifies that a collection is not empty.
+ ///
+ /// The collection to be inspected
+ /// Thrown when a null collection is passed
+ /// Thrown when the collection is empty
+ public static void NotEmpty(IAsyncEnumerable collection) =>
+ NotEmpty(AssertHelper.ToEnumerable(collection));
+
+ ///
+ /// Verifies that two sequences are not equivalent, using a default comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected object
+ /// The actual object
+ /// Thrown when the objects are equal
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IAsyncEnumerable? actual) =>
+#else
+ IEnumerable expected,
+ IAsyncEnumerable actual) =>
+#endif
+ NotEqual(expected, AssertHelper.ToEnumerable(actual), GetEqualityComparer());
+
+ ///
+ /// Verifies that two sequences are not equivalent, using a default comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected object
+ /// The actual object
+ /// Thrown when the objects are equal
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IAsyncEnumerable? expected,
+ IAsyncEnumerable? actual) =>
+#else
+ IAsyncEnumerable expected,
+ IAsyncEnumerable actual) =>
+#endif
+ NotEqual(AssertHelper.ToEnumerable(expected), AssertHelper.ToEnumerable(actual), GetEqualityComparer());
+
+ ///
+ /// Verifies that two sequences are not equivalent, using a custom equality comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected object
+ /// The actual object
+ /// The comparer used to compare the two objects
+ /// Thrown when the objects are equal
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ IEqualityComparer comparer) =>
+ NotEqual(expected, AssertHelper.ToEnumerable(actual), GetEqualityComparer>(new AssertEqualityComparerAdapter(comparer)));
+
+ ///
+ /// Verifies that two sequences are not equivalent, using a custom equality comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected object
+ /// The actual object
+ /// The comparer used to compare the two objects
+ /// Thrown when the objects are equal
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IAsyncEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IAsyncEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ IEqualityComparer comparer) =>
+ NotEqual(AssertHelper.ToEnumerable(expected), AssertHelper.ToEnumerable(actual), GetEqualityComparer>(new AssertEqualityComparerAdapter(comparer)));
+
+ ///
+ /// Verifies that two collections are not equal, using a comparer function against
+ /// items in the two collections.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The function to compare two items for equality
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ Func comparer) =>
+ NotEqual(expected, AssertHelper.ToEnumerable(actual), AssertEqualityComparer.FromComparer(comparer));
+
+ ///
+ /// Verifies that two collections are not equal, using a comparer function against
+ /// items in the two collections.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The function to compare two items for equality
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IAsyncEnumerable? expected,
+ IAsyncEnumerable? actual,
+#else
+ IAsyncEnumerable expected,
+ IAsyncEnumerable actual,
+#endif
+ Func comparer) =>
+ NotEqual(AssertHelper.ToEnumerable(expected), AssertHelper.ToEnumerable(actual), AssertEqualityComparer.FromComparer(comparer));
+
+ ///
+ /// Verifies that the given collection contains only a single
+ /// element of the given type.
+ ///
+ /// The collection type.
+ /// The collection.
+ /// The single item in the collection.
+ /// Thrown when the collection does not contain
+ /// exactly one element.
+ public static T Single(IAsyncEnumerable collection) =>
+ Single(AssertHelper.ToEnumerable(collection));
+
+ ///
+ /// Verifies that the given collection contains only a single
+ /// element of the given type which matches the given predicate. The
+ /// collection may or may not contain other values which do not
+ /// match the given predicate.
+ ///
+ /// The collection type.
+ /// The collection.
+ /// The item matching predicate.
+ /// The single item in the filtered collection.
+ /// Thrown when the filtered collection does
+ /// not contain exactly one element.
+ public static T Single(
+ IAsyncEnumerable collection,
+ Predicate predicate) =>
+ Single(AssertHelper.ToEnumerable(collection), predicate);
+ }
+}
+
+#endif
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs
new file mode 100644
index 00000000000..4c2a4c02308
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs
@@ -0,0 +1,138 @@
+#pragma warning disable CA1052 // Static holder types should be static
+
+#if XUNIT_NULLABLE
+#nullable enable
+#else
+// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE
+#pragma warning disable CS8625
+#endif
+
+using Xunit.Sdk;
+
+#if XUNIT_NULLABLE
+using System.Diagnostics.CodeAnalysis;
+#endif
+
+namespace Xunit
+{
+ partial class Assert
+ {
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// Thrown if the condition is not false
+#if XUNIT_NULLABLE
+ public static void False([DoesNotReturnIf(parameterValue: true)] bool condition) =>
+#else
+ public static void False(bool condition) =>
+#endif
+ False((bool?)condition, null);
+
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// Thrown if the condition is not false
+#if XUNIT_NULLABLE
+ public static void False([DoesNotReturnIf(parameterValue: true)] bool? condition) =>
+#else
+ public static void False(bool? condition) =>
+#endif
+ False(condition, null);
+
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// The message to show when the condition is not false
+ /// Thrown if the condition is not false
+ public static void False(
+#if XUNIT_NULLABLE
+ [DoesNotReturnIf(parameterValue: true)] bool condition,
+ string? userMessage) =>
+#else
+ bool condition,
+ string userMessage) =>
+#endif
+ False((bool?)condition, userMessage);
+
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// The message to show when the condition is not false
+ /// Thrown if the condition is not false
+ public static void False(
+#if XUNIT_NULLABLE
+ [DoesNotReturnIf(parameterValue: true)] bool? condition,
+ string? userMessage)
+#else
+ bool? condition,
+ string userMessage)
+#endif
+ {
+ if (!condition.HasValue || condition.GetValueOrDefault())
+ throw FalseException.ForNonFalseValue(userMessage, condition);
+ }
+
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// Thrown when the condition is false
+#if XUNIT_NULLABLE
+ public static void True([DoesNotReturnIf(parameterValue: false)] bool condition) =>
+#else
+ public static void True(bool condition) =>
+#endif
+ True((bool?)condition, null);
+
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// Thrown when the condition is false
+#if XUNIT_NULLABLE
+ public static void True([DoesNotReturnIf(parameterValue: false)] bool? condition) =>
+#else
+ public static void True(bool? condition) =>
+#endif
+ True(condition, null);
+
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// The message to be shown when the condition is false
+ /// Thrown when the condition is false
+ public static void True(
+#if XUNIT_NULLABLE
+ [DoesNotReturnIf(parameterValue: false)] bool condition,
+ string? userMessage) =>
+#else
+ bool condition,
+ string userMessage) =>
+#endif
+ True((bool?)condition, userMessage);
+
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// The message to be shown when the condition is false
+ /// Thrown when the condition is false
+ public static void True(
+#if XUNIT_NULLABLE
+ [DoesNotReturnIf(parameterValue: false)] bool? condition,
+ string? userMessage)
+#else
+ bool? condition,
+ string userMessage)
+#endif
+ {
+ if (!condition.HasValue || !condition.GetValueOrDefault())
+ throw TrueException.ForNonTrueValue(userMessage, condition);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs
new file mode 100644
index 00000000000..331a9a5ea01
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs
@@ -0,0 +1,800 @@
+#pragma warning disable CA1031 // Do not catch general exception types
+#pragma warning disable CA1052 // Static holder types should be static
+#pragma warning disable CA1720 // Identifier contains type name
+#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
+#pragma warning disable IDE0063 // Use simple 'using' statement
+#pragma warning disable IDE0066 // Convert switch statement to expression
+#pragma warning disable IDE0305 // Simplify collection initialization
+
+#if XUNIT_NULLABLE
+#nullable enable
+#else
+// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE
+#pragma warning disable CS8603
+#pragma warning disable CS8604
+#pragma warning disable CS8625
+#endif
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit.Sdk;
+
+namespace Xunit
+{
+ partial class Assert
+ {
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static void All(
+ IEnumerable collection,
+ Action action)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ All(collection, (item, index) => action(item), throwIfEmpty: false);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Indicates whether to throw an exception if the collection is empty
+ /// Thrown when the collection contains at least one non-matching element
+ /// Also thrown when collection is empty and is set to true
+ public static void All(
+ IEnumerable collection,
+ Action action,
+ bool throwIfEmpty)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ All(collection, (item, index) => action(item), throwIfEmpty);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action. The item index is provided to the action, in addition to the item.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static void All(
+ IEnumerable collection,
+ Action action)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ All(collection, action, throwIfEmpty: false);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action. The item index is provided to the action, in addition to the item.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Indicates whether to throw an exception if the collection is empty
+ /// Thrown when the collection contains at least one non-matching element
+ /// Also thrown when collection is empty and is set to true
+ public static void All(
+ IEnumerable collection,
+ Action action,
+ bool throwIfEmpty)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ var errors = new List>();
+ var idx = 0;
+
+ foreach (var item in collection)
+ {
+ try
+ {
+ action(item, idx);
+ }
+ catch (Exception ex)
+ {
+ errors.Add(new Tuple(idx, ArgumentFormatter.Format(item), ex));
+ }
+
+ ++idx;
+ }
+
+ if (throwIfEmpty && idx == 0)
+ throw AllException.ForEmptyCollection();
+
+ if (errors.Count > 0)
+ throw AllException.ForFailures(idx, errors);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static async Task AllAsync(
+ IEnumerable collection,
+ Func action)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ await AllAsync(collection, async (item, index) => await action(item), throwIfEmpty: false);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Indicates whether to throw an exception if the collection is empty
+ /// Thrown when the collection contains at least one non-matching element
+ /// Also thrown when collection is empty and is set to true
+ public static async Task AllAsync(
+ IEnumerable collection,
+ Func action,
+ bool throwIfEmpty)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ await AllAsync(collection, (item, index) => action(item), throwIfEmpty);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action. The item index is provided to the action, in addition to the item.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Thrown when the collection contains at least one non-matching element
+ public static async Task AllAsync(
+ IEnumerable collection,
+ Func action)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ await AllAsync(collection, action, throwIfEmpty: false);
+ }
+
+ ///
+ /// Verifies that all items in the collection pass when executed against
+ /// action. The item index is provided to the action, in addition to the item.
+ ///
+ /// The type of the object to be verified
+ /// The collection
+ /// The action to test each item against
+ /// Indicates whether to throw an exception if the collection is empty
+ /// Thrown when the collection contains at least one non-matching element
+ /// Also thrown when collection is empty and is set to true
+ public static async Task AllAsync(
+ IEnumerable collection,
+ Func action,
+ bool throwIfEmpty)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(action), action);
+
+ var errors = new List>();
+ var idx = 0;
+
+ foreach (var item in collection)
+ {
+ try
+ {
+ await action(item, idx);
+ }
+ catch (Exception ex)
+ {
+ errors.Add(new Tuple(idx, ArgumentFormatter.Format(item), ex));
+ }
+
+ ++idx;
+ }
+
+ if (throwIfEmpty && idx == 0)
+ throw AllException.ForEmptyCollection();
+
+ if (errors.Count > 0)
+ throw AllException.ForFailures(idx, errors.ToArray());
+ }
+
+ ///
+ /// Verifies that a collection contains exactly a given number of elements, which meet
+ /// the criteria provided by the element inspectors.
+ ///
+ /// The type of the object to be verified
+ /// The collection to be inspected
+ /// The element inspectors, which inspect each element in turn. The
+ /// total number of element inspectors must exactly match the number of elements in the collection.
+ public static void Collection(
+ IEnumerable collection,
+ params Action[] elementInspectors)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(elementInspectors), elementInspectors);
+
+ using (var tracker = collection.AsTracker())
+ {
+ var index = 0;
+
+ foreach (var item in tracker)
+ {
+ try
+ {
+ if (index < elementInspectors.Length)
+ elementInspectors[index](item);
+ }
+ catch (Exception ex)
+ {
+ var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent);
+ throw CollectionException.ForMismatchedItem(ex, index, pointerIndent, formattedCollection);
+ }
+
+ index++;
+ }
+
+ if (tracker.IterationCount != elementInspectors.Length)
+ throw CollectionException.ForMismatchedItemCount(elementInspectors.Length, tracker.IterationCount, tracker.FormatStart());
+ }
+ }
+
+ ///
+ /// Verifies that a collection contains exactly a given number of elements, which meet
+ /// the criteria provided by the element inspectors.
+ ///
+ /// The type of the object to be verified
+ /// The collection to be inspected
+ /// The element inspectors, which inspect each element in turn. The
+ /// total number of element inspectors must exactly match the number of elements in the collection.
+ public static async Task CollectionAsync(
+ IEnumerable collection,
+ params Func[] elementInspectors)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(elementInspectors), elementInspectors);
+
+ using (var tracker = collection.AsTracker())
+ {
+ var index = 0;
+
+ foreach (var item in tracker)
+ {
+ try
+ {
+ if (index < elementInspectors.Length)
+ await elementInspectors[index](item);
+ }
+ catch (Exception ex)
+ {
+ var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent);
+ throw CollectionException.ForMismatchedItem(ex, index, pointerIndent, formattedCollection);
+ }
+
+ index++;
+ }
+
+ if (tracker.IterationCount != elementInspectors.Length)
+ throw CollectionException.ForMismatchedItemCount(elementInspectors.Length, tracker.IterationCount, tracker.FormatStart());
+ }
+ }
+
+ ///
+ /// Verifies that a collection contains a given object.
+ ///
+ /// The type of the object to be verified
+ /// The object expected to be in the collection
+ /// The collection to be inspected
+ /// Thrown when the object is not present in the collection
+ public static void Contains(
+ T expected,
+ IEnumerable collection)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+
+ // We special case sets because they are constructed with their comparers, which we don't have access to.
+ // We want to let them do their normal logic when appropriate, and not try to use our default comparer.
+ if (collection is ISet set)
+ {
+ Contains(expected, set);
+ return;
+ }
+#if NET8_0_OR_GREATER
+ if (collection is IReadOnlySet readOnlySet)
+ {
+ Contains(expected, readOnlySet);
+ return;
+ }
+#endif
+
+ // Fall back to the assumption that this is a linear container and use our default comparer
+ Contains(expected, collection, GetEqualityComparer());
+ }
+
+ ///
+ /// Verifies that a collection contains a given object, using an equality comparer.
+ ///
+ /// The type of the object to be verified
+ /// The object expected to be in the collection
+ /// The collection to be inspected
+ /// The comparer used to equate objects in the collection with the expected object
+ /// Thrown when the object is not present in the collection
+ public static void Contains(
+ T expected,
+ IEnumerable collection,
+ IEqualityComparer comparer)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(comparer), comparer);
+
+ using (var tracker = collection.AsTracker())
+ if (!tracker.Contains(expected, comparer))
+ throw ContainsException.ForCollectionItemNotFound(ArgumentFormatter.Format(expected), tracker.FormatStart());
+ }
+
+ ///
+ /// Verifies that a collection contains a given object.
+ ///
+ /// The type of the object to be verified
+ /// The collection to be inspected
+ /// The filter used to find the item you're ensuring the collection contains
+ /// Thrown when the object is not present in the collection
+ public static void Contains(
+ IEnumerable collection,
+ Predicate filter)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(filter), filter);
+
+ using (var tracker = collection.AsTracker())
+ {
+ foreach (var item in tracker)
+ if (filter(item))
+ return;
+
+ throw ContainsException.ForCollectionFilterNotMatched(tracker.FormatStart());
+ }
+ }
+
+ ///
+ /// Verifies that a collection contains each object only once.
+ ///
+ /// The type of the object to be compared
+ /// The collection to be inspected
+ /// Thrown when an object is present inside the collection more than once
+ public static void Distinct(IEnumerable collection) =>
+ Distinct(collection, EqualityComparer.Default);
+
+ ///
+ /// Verifies that a collection contains each object only once.
+ ///
+ /// The type of the object to be compared
+ /// The collection to be inspected
+ /// The comparer used to equate objects in the collection with the expected object
+ /// Thrown when an object is present inside the collection more than once
+ public static void Distinct(
+ IEnumerable collection,
+ IEqualityComparer comparer)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(comparer), comparer);
+
+ using (var tracker = collection.AsTracker())
+ {
+ var set = new HashSet(comparer);
+
+ foreach (var item in tracker)
+ if (!set.Add(item))
+ throw DistinctException.ForDuplicateItem(ArgumentFormatter.Format(item), tracker.FormatStart());
+ }
+ }
+
+ ///
+ /// Verifies that a collection does not contain a given object.
+ ///
+ /// The type of the object to be compared
+ /// The object that is expected not to be in the collection
+ /// The collection to be inspected
+ /// Thrown when the object is present inside the collection
+ public static void DoesNotContain(
+ T expected,
+ IEnumerable collection)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+
+ // We special case sets because they are constructed with their comparers, which we don't have access to.
+ // We want to let them do their normal logic when appropriate, and not try to use our default comparer.
+ if (collection is ISet set)
+ {
+ DoesNotContain(expected, set);
+ return;
+ }
+#if NET8_0_OR_GREATER
+ if (collection is IReadOnlySet readOnlySet)
+ {
+ DoesNotContain(expected, readOnlySet);
+ return;
+ }
+#endif
+
+ // Fall back to the assumption that this is a linear container and use our default comparer
+ DoesNotContain(expected, collection, GetEqualityComparer());
+ }
+
+ ///
+ /// Verifies that a collection does not contain a given object, using an equality comparer.
+ ///
+ /// The type of the object to be compared
+ /// The object that is expected not to be in the collection
+ /// The collection to be inspected
+ /// The comparer used to equate objects in the collection with the expected object
+ /// Thrown when the object is present inside the collection
+ public static void DoesNotContain(
+ T expected,
+ IEnumerable collection,
+ IEqualityComparer comparer)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(comparer), comparer);
+
+ using (var tracker = collection.AsTracker())
+ {
+ var index = 0;
+
+ foreach (var item in tracker)
+ {
+ if (comparer.Equals(item, expected))
+ {
+ var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent);
+
+ throw DoesNotContainException.ForCollectionItemFound(
+ ArgumentFormatter.Format(expected),
+ index,
+ pointerIndent,
+ formattedCollection
+ );
+ }
+
+ ++index;
+ }
+ }
+ }
+
+ ///
+ /// Verifies that a collection does not contain a given object.
+ ///
+ /// The type of the object to be compared
+ /// The collection to be inspected
+ /// The filter used to find the item you're ensuring the collection does not contain
+ /// Thrown when the object is present inside the collection
+ public static void DoesNotContain(
+ IEnumerable collection,
+ Predicate filter)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+ GuardArgumentNotNull(nameof(filter), filter);
+
+ using (var tracker = collection.AsTracker())
+ {
+ var index = 0;
+
+ foreach (var item in tracker)
+ {
+ if (filter(item))
+ {
+ var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent);
+
+ throw DoesNotContainException.ForCollectionFilterMatched(
+ index,
+ pointerIndent,
+ formattedCollection
+ );
+ }
+
+ ++index;
+ }
+ }
+ }
+
+ ///
+ /// Verifies that a collection is empty.
+ ///
+ /// The collection to be inspected
+ /// Thrown when the collection is null
+ /// Thrown when the collection is not empty
+ public static void Empty(IEnumerable collection)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+
+ using (var tracker = collection.AsTracker())
+ {
+ var enumerator = tracker.GetEnumerator();
+ if (enumerator.MoveNext())
+ throw EmptyException.ForNonEmptyCollection(tracker.FormatStart());
+ }
+ }
+
+ ///
+ /// Verifies that two sequences are equivalent, using a default comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// Thrown when the objects are not equal
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IEnumerable? actual) =>
+#else
+ IEnumerable expected,
+ IEnumerable actual) =>
+#endif
+ Equal(expected, actual, GetEqualityComparer>());
+
+ ///
+ /// Verifies that two sequences are equivalent, using a custom equatable comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The comparer used to compare the two objects
+ /// Thrown when the objects are not equal
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IEnumerable? actual,
+#else
+ IEnumerable expected,
+ IEnumerable actual,
+#endif
+ IEqualityComparer comparer) =>
+ Equal(expected, actual, GetEqualityComparer>(new AssertEqualityComparerAdapter(comparer)));
+
+ ///
+ /// Verifies that two collections are equal, using a comparer function against
+ /// items in the two collections.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The function to compare two items for equality
+ public static void Equal(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IEnumerable? actual,
+#else
+ IEnumerable expected,
+ IEnumerable actual,
+#endif
+ Func comparer) =>
+ Equal(expected, actual, AssertEqualityComparer.FromComparer(comparer));
+
+ ///
+ /// Verifies that a collection is not empty.
+ ///
+ /// The collection to be inspected
+ /// Thrown when a null collection is passed
+ /// Thrown when the collection is empty
+ public static void NotEmpty(IEnumerable collection)
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+
+ var enumerator = collection.GetEnumerator();
+ try
+ {
+ if (!enumerator.MoveNext())
+ throw NotEmptyException.ForNonEmptyCollection();
+ }
+ finally
+ {
+ (enumerator as IDisposable)?.Dispose();
+ }
+ }
+
+ ///
+ /// Verifies that two sequences are not equivalent, using a default comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected object
+ /// The actual object
+ /// Thrown when the objects are equal
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IEnumerable? actual) =>
+#else
+ IEnumerable expected,
+ IEnumerable actual) =>
+#endif
+ NotEqual(expected, actual, GetEqualityComparer>());
+
+ ///
+ /// Verifies that two sequences are not equivalent, using a custom equality comparer.
+ ///
+ /// The type of the objects to be compared
+ /// The expected object
+ /// The actual object
+ /// The comparer used to compare the two objects
+ /// Thrown when the objects are equal
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IEnumerable? actual,
+#else
+ IEnumerable expected,
+ IEnumerable actual,
+#endif
+ IEqualityComparer comparer) =>
+ NotEqual(expected, actual, GetEqualityComparer>(new AssertEqualityComparerAdapter(comparer)));
+
+ ///
+ /// Verifies that two collections are not equal, using a comparer function against
+ /// items in the two collections.
+ ///
+ /// The type of the objects to be compared
+ /// The expected value
+ /// The value to be compared against
+ /// The function to compare two items for equality
+ public static void NotEqual(
+#if XUNIT_NULLABLE
+ IEnumerable? expected,
+ IEnumerable? actual,
+#else
+ IEnumerable expected,
+ IEnumerable actual,
+#endif
+ Func comparer) =>
+ NotEqual(expected, actual, AssertEqualityComparer.FromComparer(comparer));
+
+ ///
+ /// Verifies that the given collection contains only a single
+ /// element of the given type.
+ ///
+ /// The collection.
+ /// The single item in the collection.
+ /// Thrown when the collection does not contain
+ /// exactly one element.
+#if XUNIT_NULLABLE
+ public static object? Single(IEnumerable collection)
+#else
+ public static object Single(IEnumerable collection)
+#endif
+ {
+ GuardArgumentNotNull(nameof(collection), collection);
+
+ return Single(collection.Cast