/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Test/Core/Assert/AssertEx.cs
1 084 строки
40 KB
Jason Malinowski
Switch code that was using Assert.True/False as a replacement for .Fail
21 апр 2026, 23:28
21 апр 2026, 23:28
3bf59ba
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using DiffPlex; using DiffPlex.Chunkers; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; namespace Roslyn.Test.Utilities { /// <summary> /// Assert style type to deal with the lack of features in xUnit's Assert type /// </summary> public static class AssertEx { private static readonly IChunker s_lineChunker = new LineChunker(); private static readonly IChunker s_lineEndingsPreservingChunker = new LineEndingsPreservingChunker(); private static readonly InlineDiffBuilder s_diffBuilder = new InlineDiffBuilder(new Differ()); #region AssertEqualityComparer<T> private class AssertEqualityComparer<T> : IEqualityComparer<T> { public static readonly IEqualityComparer<T> Instance = new AssertEqualityComparer<T>(); private static bool CanBeNull() { var type = typeof(T); return !type.GetTypeInfo().IsValueType || (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)); } public static bool IsNull(T @object) { if (!CanBeNull()) { return false; } return object.Equals(@object, default(T)); } public static bool Equals(T left, T right) { return Instance.Equals(left, right); } bool IEqualityComparer<T>.Equals(T x, T y) { if (CanBeNull()) { if (object.Equals(x, default(T))) { return object.Equals(y, default(T)); } if (object.Equals(y, default(T))) { return false; } } if (x is IEquatable<T> equatable) { return equatable.Equals(y); } if (x is IComparable<T> comparableT) { return comparableT.CompareTo(y) == 0; } if (x is IComparable comparable) { return comparable.CompareTo(y) == 0; } if (x is IEnumerable enumerableX && y is IEnumerable enumerableY) { var enumeratorX = enumerableX.GetEnumerator(); var enumeratorY = enumerableY.GetEnumerator(); while (true) { bool hasNextX = enumeratorX.MoveNext(); bool hasNextY = enumeratorY.MoveNext(); if (!hasNextX || !hasNextY) { return hasNextX == hasNextY; } if (!Equals(enumeratorX.Current, enumeratorY.Current)) { return false; } } } return object.Equals(x, y); } int IEqualityComparer<T>.GetHashCode(T obj) { throw new NotImplementedException(); } } #endregion public static void AreEqual<T>(T expected, T actual, string message = null, IEqualityComparer<T> comparer = null) { if (ReferenceEquals(expected, actual)) { return; } if (expected == null) { Assert.Fail("expected was null, but actual wasn't" + Environment.NewLine + message); } else if (actual == null) { Assert.Fail("actual was null, but expected wasn't" + Environment.NewLine + message); } else if (!(comparer ?? AssertEqualityComparer<T>.Instance).Equals(expected, actual)) { string expectedAndActual; if (expected is IEnumerable expectedEnumerable && actual is IEnumerable actualEnumerable) { expectedAndActual = GetAssertMessage(expectedEnumerable.OfType<object>(), actualEnumerable.OfType<object>(), comparer: null); } else { expectedAndActual = $""" Expected: {expected} Actual: {actual} """; } Assert.Fail(message + Environment.NewLine + expectedAndActual); } } public static void Equal<T>(ReadOnlySpan<T> expected, T[] actual) => Equal<T>(expected.ToArray(), actual); public static void Equal<T>(ImmutableArray<T> expected, IEnumerable<T> actual) => Equal(expected, actual, comparer: null, message: null); public static void Equal<T>(ImmutableArray<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string message = null) { if (actual == null || expected.IsDefault) { Assert.True((actual == null) == expected.IsDefault, message); } else { Equal((IEnumerable<T>)expected, actual, comparer, message); } } public static void Equal<T>(IEnumerable<T> expected, ImmutableArray<T> actual) => Equal(expected, actual, comparer: null, message: null, itemInspector: null); public static void SequenceEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string message = null, string itemSeparator = null) { if (expected == null || actual == null) { Assert.True(expected is null == actual is null, message); } else { Equal(expected, actual, comparer, message, itemSeparator); } } public static void Equal<T>(ImmutableArray<T> expected, ImmutableArray<T> actual) => Equal(expected, actual, comparer: null, message: null, itemInspector: null); public static void Equal<T>(ImmutableArray<T> expected, ImmutableArray<T> actual, IEqualityComparer<T> comparer = null, string message = null, string itemSeparator = null) { Equal(expected, (IEnumerable<T>)actual, comparer, message, itemSeparator); } public static void Equal(string expected, string actual) { if (string.Equals(expected, actual, StringComparison.Ordinal)) { return; } var message = new StringBuilder(); message.AppendLine(); message.AppendLine("Expected:"); message.AppendLine(expected); message.AppendLine("Actual:"); message.AppendLine(actual); Assert.Fail(message.ToString()); } public static void Equal<T>( IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string message = null, string itemSeparator = null, Func<T, string> itemInspector = null, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { if (expected == null) { Assert.Null(actual); } else { Assert.NotNull(actual); } if (SequenceEqual(expected, actual, comparer)) { return; } Assert.Fail(GetAssertMessage(expected, actual, comparer, message, itemInspector, itemSeparator, expectedValueSourcePath, expectedValueSourceLine)); } public static void Equal<T>( ReadOnlySpan<T> expected, ReadOnlySpan<T> actual, IEqualityComparer<T> comparer = null, string message = null, string itemSeparator = null, Func<T, string> itemInspector = null, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { if (SequenceEqual(expected, actual, comparer)) return; Assert.Fail(GetAssertMessage(expected, actual, comparer, message, itemInspector, itemSeparator, expectedValueSourcePath, expectedValueSourceLine)); } /// <summary> /// Asserts that two strings are equal, and prints a diff between the two if they are not. /// </summary> /// <param name="expected">The expected string. This is presented as the "baseline/before" side in the diff.</param> /// <param name="actual">The actual string. This is presented as the changed or "after" side in the diff.</param> /// <param name="message">The message to precede the diff, if the values are not equal.</param> public static void EqualOrDiff(string expected, string actual, string message = null) { if (expected == actual) { return; } var diff = s_diffBuilder.BuildDiffModel(expected, actual, ignoreWhitespace: false, ignoreCase: false, s_lineChunker); var messageBuilder = new StringBuilder(); messageBuilder.AppendLine( string.IsNullOrEmpty(message) ? "Actual and expected values differ. Expected shown in baseline of diff:" : message); if (!diff.Lines.Any(line => line.Type == ChangeType.Inserted || line.Type == ChangeType.Deleted)) { // We have a failure only caused by line ending differences; recalculate with line endings visible diff = s_diffBuilder.BuildDiffModel(expected, actual, ignoreWhitespace: false, ignoreCase: false, s_lineEndingsPreservingChunker); } foreach (var line in diff.Lines) { switch (line.Type) { case ChangeType.Inserted: messageBuilder.Append('+'); break; case ChangeType.Deleted: messageBuilder.Append('-'); break; default: messageBuilder.Append(' '); break; } messageBuilder.AppendLine(line.Text.Replace("\r", "<CR>").Replace("\n", "<LF>")); } Assert.Fail(messageBuilder.ToString()); } public static void NotEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string message = null) { if (ReferenceEquals(expected, actual)) { Assert.Fail("expected and actual references are identical\r\n" + message); } if (expected == null || actual == null) { return; } else if (SequenceEqual(expected, actual, comparer)) { Assert.Fail("expected and actual sequences match\r\n" + message); } } private static bool SequenceEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null) { if (ReferenceEquals(expected, actual)) { return true; } var enumerator1 = expected.GetEnumerator(); var enumerator2 = actual.GetEnumerator(); while (true) { var hasNext1 = enumerator1.MoveNext(); var hasNext2 = enumerator2.MoveNext(); if (hasNext1 != hasNext2) { return false; } if (!hasNext1) { break; } var value1 = enumerator1.Current; var value2 = enumerator2.Current; if (!(comparer != null ? comparer.Equals(value1, value2) : AssertEqualityComparer<T>.Equals(value1, value2))) { return false; } } return true; } private static bool SequenceEqual<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual, IEqualityComparer<T> comparer = null) { if (expected.Length != actual.Length) return false; for (int i = 0; i < expected.Length; i++) { if (!(comparer is not null ? comparer.Equals(expected[i], actual[i]) : AssertEqualityComparer<T>.Equals(expected[i], actual[i]))) { return false; } } return true; } public static void SetEqual(IEnumerable<string> expected, IEnumerable<string> actual, IEqualityComparer<string> comparer = null, string message = null, string itemSeparator = "\r\n", Func<string, string> itemInspector = null) { var indexes = new Dictionary<string, int>(comparer); int counter = 0; foreach (var expectedItem in expected) { if (!indexes.ContainsKey(expectedItem)) { indexes.Add(expectedItem, counter++); } } SetEqual<string>(expected, actual.OrderBy(e => getIndex(e)), comparer, message, itemSeparator, itemInspector); int getIndex(string item) { // exact match to expected items if (indexes.TryGetValue(item, out var index)) { return index; } // closest match to expected items int closestDistance = int.MaxValue; string closestItem = null; foreach (var expectedItem in indexes.Keys) { var distance = levenshtein(item, expectedItem); if (distance < closestDistance) { closestDistance = distance; closestItem = expectedItem; } } if (closestItem != null) { _ = indexes.TryGetValue(closestItem, out index); return index; } return -1; } // Adapted from Toub's https://blogs.msdn.microsoft.com/toub/2006/05/05/generic-levenshtein-edit-distance-with-c/ int levenshtein(string first, string second) { // Get the length of both. If either is 0, return // the length of the other, since that number of insertions // would be required. int n = first.Length, m = second.Length; if (n == 0) return m; if (m == 0) return n; // Rather than maintain an entire matrix (which would require O(n*m) space), // just store the current row and the next row, each of which has a length m+1, // so just O(m) space. Initialize the current row. int curRow = 0, nextRow = 1; int[][] rows = new int[][] { new int[m + 1], new int[m + 1] }; for (int j = 0; j <= m; ++j) rows[curRow][j] = j; // For each virtual row (since we only have physical storage for two) for (int i = 1; i <= n; ++i) { // Fill in the values in the row rows[nextRow][0] = i; for (int j = 1; j <= m; ++j) { int dist1 = rows[curRow][j] + 1; int dist2 = rows[nextRow][j - 1] + 1; int dist3 = rows[curRow][j - 1] + (first[i - 1].Equals(second[j - 1]) ? 0 : 1); rows[nextRow][j] = Math.Min(dist1, Math.Min(dist2, dist3)); } // Swap the current and next rows if (curRow == 0) { curRow = 1; nextRow = 0; } else { curRow = 0; nextRow = 1; } } // Return the computed edit distance return rows[curRow][m]; } } public static void SetEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string message = null, string itemSeparator = "\r\n", Func<T, string> itemInspector = null) { var expectedSet = new HashSet<T>(expected, comparer); var result = expected.Count() == actual.Count() && expectedSet.SetEquals(actual); if (!result) { Assert.True(result, GetAssertMessage( ToString(expected, itemSeparator, itemInspector), ToString(actual, itemSeparator, itemInspector), prefix: message)); } } public static void SetEqual<T>(T[] expected, T[] actual) => SetEqual((IEnumerable<T>)actual, expected); public static void SetEqual<T>(IEnumerable<T> actual, params T[] expected) { var expectedSet = new HashSet<T>(expected); if (!expectedSet.SetEquals(actual)) { var message = GetAssertMessage(ToString(expected, ",\r\n", itemInspector: withQuotes), ToString(actual, ",\r\n", itemInspector: withQuotes)); Assert.Fail(message); } string withQuotes(T t) => $"\"{Convert.ToString(t)}\""; } public static void None<T>(IEnumerable<T> actual, Func<T, bool> predicate) { var none = !actual.Any(predicate); if (!none) { Assert.True(none, string.Format( "Unexpected item found among existing items: {0}\nExisting items: {1}", ToString(actual.First(predicate)), ToString(actual))); } } public static void Any<T>(IEnumerable<T> actual, Func<T, bool> predicate) { var any = actual.Any(predicate); Assert.True(any, string.Format("No expected item was found.\nExisting items: {0}", ToString(actual))); } public static void All<T>(IEnumerable<T> actual, Func<T, bool> predicate) { var all = actual.All(predicate); if (!all) { Assert.True(all, string.Format( "Not all items satisfy condition:\n{0}", ToString(actual.Where(i => !predicate(i))))); } } public static string ToString(object o) { return Convert.ToString(o); } public static string ToString<T>(IEnumerable<T> list, string separator = ", ", Func<T, string> itemInspector = null) { if (itemInspector == null) { itemInspector = i => Convert.ToString(i); } return string.Join(separator, list.Select(itemInspector)); } public static void NotNull<T>([NotNull] T @object, string message) { Assert.False(AssertEqualityComparer<T>.IsNull(@object), message); } // compares against a baseline public static void AssertEqualToleratingWhitespaceDifferences( string expected, string actual, string message = null, bool escapeQuotes = false, [CallerFilePath] string expectedValueSourcePath = null, [CallerLineNumber] int expectedValueSourceLine = 0) { var normalizedExpected = NormalizeWhitespace(expected); var normalizedActual = NormalizeWhitespace(actual); if (normalizedExpected != normalizedActual) { Assert.Fail(GetAssertMessage(expected, actual, message, escapeQuotes, expectedValueSourcePath, expectedValueSourceLine)); } } // compares two results (no baseline) public static void AssertResultsEqual(string result1, string result2) { if (result1 != result2) { string message; if (DiffToolAvailable) { string file1 = Path.GetTempFileName(); File.WriteAllText(file1, result1); string file2 = Path.GetTempFileName(); File.WriteAllText(file2, result2); message = MakeDiffToolLink(file1, file2); } else { message = GetAssertMessage(result1, result2); } Assert.Fail(message); } } public static void AssertContainsToleratingWhitespaceDifferences(string expectedSubString, string actualString) { expectedSubString = NormalizeWhitespace(expectedSubString); actualString = NormalizeWhitespace(actualString); Assert.Contains(expectedSubString, actualString, StringComparison.Ordinal); } public static void AssertStartsWithToleratingWhitespaceDifferences(string expectedSubString, string actualString) { expectedSubString = NormalizeWhitespace(expectedSubString); actualString = NormalizeWhitespace(actualString); Assert.StartsWith(expectedSubString, actualString, StringComparison.Ordinal); } internal static string NormalizeWhitespace(string input) { var output = new StringBuilder(); var inputLines = input.Split('\n', '\r'); foreach (var line in inputLines) { var trimmedLine = line.Trim(); if (trimmedLine.Length > 0) { if (!(trimmedLine[0] == '{' || trimmedLine[0] == '}')) { output.Append(" "); } output.AppendLine(trimmedLine); } } return output.ToString(); } public static string GetAssertMessage(string expected, string actual, string prefix = null, bool escapeQuotes = false, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) => GetAssertMessage(DiffUtil.Lines(expected), DiffUtil.Lines(actual), prefix, escapeQuotes, expectedValueSourcePath, expectedValueSourceLine); public static string GetAssertMessage<T>(IEnumerable<T> expected, IEnumerable<T> actual, string prefix = null, bool escapeQuotes = false, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { Func<T, string> itemInspector = escapeQuotes ? new Func<T, string>(t => t.ToString().Replace("\"", "\"\"")) : null; return GetAssertMessage(expected, actual, prefix: prefix, itemInspector: itemInspector, itemSeparator: "\r\n", expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine); } private static readonly string s_diffToolPath = Environment.GetEnvironmentVariable("ROSLYN_DIFFTOOL"); public static string GetAssertMessage<T>( IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer = null, string prefix = null, Func<T, string> itemInspector = null, string itemSeparator = null, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { if (itemInspector == null) { if (typeof(T) == typeof(byte)) { itemInspector = b => $"0x{b:X2}"; } else { itemInspector = new Func<T, string>(obj => (obj != null) ? obj.ToString() : "<null>"); } } if (itemSeparator == null) { if (typeof(T) == typeof(byte)) { itemSeparator = ", "; } else { itemSeparator = "," + Environment.NewLine; } } var expectedString = string.Join(itemSeparator, expected.Take(10).Select(itemInspector)); var actualString = string.Join(itemSeparator, actual.Select(itemInspector)); var diffString = DiffUtil.DiffReport(expected, actual, itemSeparator, comparer, itemInspector); if (DifferOnlyInWhitespace(expectedString, actualString)) { expectedString = VisualizeWhitespace(expectedString); actualString = VisualizeWhitespace(actualString); diffString = VisualizeWhitespace(diffString); } var message = new StringBuilder(); if (!string.IsNullOrEmpty(prefix)) { message.AppendLine(prefix); message.AppendLine(); } message.AppendLine("Expected:"); message.AppendLine(expectedString); if (expected.Count() > 10) { message.AppendLine("... truncated ..."); } message.AppendLine("Actual:"); message.AppendLine(actualString); message.AppendLine("Differences:"); message.AppendLine(diffString); if (TryGenerateExpectedSourceFileAndGetDiffLink(actualString, expected.Count(), expectedValueSourcePath, expectedValueSourceLine, out var link)) { message.AppendLine(link); } return message.ToString(); } private static bool DifferOnlyInWhitespace(IEnumerable<char> expected, IEnumerable<char> actual) => expected.Where(c => !char.IsWhiteSpace(c)).SequenceEqual(actual.Where(c => !char.IsWhiteSpace(c))); private static string VisualizeWhitespace(string str) { var result = new StringBuilder(str.Length); var i = 0; while (i < str.Length) { var c = str[i++]; if (c == '\r' && i < str.Length && str[i] == '\n') { result.Append("␍␊\r\n"); i++; } else { result.Append(c switch { ' ' => "·", '\t' => "→", '\r' => "␍\r", '\n' => "␊\n", _ => c, }); } } return result.ToString(); } public static string GetAssertMessage<T>( ReadOnlySpan<T> expected, ReadOnlySpan<T> actual, IEqualityComparer<T> comparer = null, string prefix = null, Func<T, string> itemInspector = null, string itemSeparator = null, string expectedValueSourcePath = null, int expectedValueSourceLine = 0) { if (itemInspector == null) { if (typeof(T) == typeof(byte)) { itemInspector = b => $"0x{b:X2}"; } else { itemInspector = new Func<T, string>(obj => (obj != null) ? obj.ToString() : "<null>"); } } if (itemSeparator == null) { if (typeof(T) == typeof(byte)) { itemSeparator = ", "; } else { itemSeparator = "," + Environment.NewLine; } } const int maxDisplayedExpectedEntries = 10; var expectedString = join(itemSeparator, expected[..Math.Min(expected.Length, maxDisplayedExpectedEntries)], itemInspector); var actualString = join(itemSeparator, actual, itemInspector); var message = new StringBuilder(); if (!string.IsNullOrEmpty(prefix)) { message.AppendLine(prefix); message.AppendLine(); } message.AppendLine("Expected:"); message.AppendLine(expectedString); if (expected.Length > maxDisplayedExpectedEntries) { message.AppendLine("... truncated ..."); } message.AppendLine("Actual:"); message.AppendLine(actualString); message.AppendLine("Differences:"); message.AppendLine(DiffUtil.DiffReport(expected.ToArray(), actual.ToArray(), itemSeparator, comparer, itemInspector)); if (TryGenerateExpectedSourceFileAndGetDiffLink(actualString, expected.Length, expectedValueSourcePath, expectedValueSourceLine, out var link)) { message.AppendLine(link); } return message.ToString(); static string join(string itemSeparator, ReadOnlySpan<T> items, Func<T, string> itemInspector) { var result = new StringBuilder(); var iter = items.GetEnumerator(); if (iter.MoveNext()) result.Append(itemInspector(iter.Current)); while (iter.MoveNext()) result.Append($"{itemSeparator}{itemInspector(iter.Current)}"); return result.ToString(); } } internal static bool TryGenerateExpectedSourceFileAndGetDiffLink(string actualString, int expectedLineCount, string expectedValueSourcePath, int expectedValueSourceLine, out string link) { // add a link to a .cmd file that opens a diff tool: if (DiffToolAvailable && expectedValueSourcePath != null && expectedValueSourceLine != 0) { var actualFile = Path.GetTempFileName(); var testFileLines = File.ReadAllLines(expectedValueSourcePath); File.WriteAllLines(actualFile, testFileLines.Take(expectedValueSourceLine)); File.AppendAllText(actualFile, actualString); File.AppendAllLines(actualFile, testFileLines.Skip(expectedValueSourceLine + expectedLineCount)); link = MakeDiffToolLink(actualFile, expectedValueSourcePath); return true; } link = null; return false; } internal static bool DiffToolAvailable => !string.IsNullOrEmpty(s_diffToolPath); internal static string MakeDiffToolLink(string actualFilePath, string expectedFilePath) { var compareCmd = Path.GetTempFileName() + ".cmd"; File.WriteAllText(compareCmd, string.Format("\"{0}\" \"{1}\" \"{2}\"", s_diffToolPath, actualFilePath, expectedFilePath)); return "file://" + compareCmd; } public static void Empty<T>(IEnumerable<T> items, string message = "") { // realize the list in case it can't be traversed twice via .Count()/.Any() and .Select() var list = items.ToList(); if (list.Count != 0) { Assert.Fail($"Expected 0 items but found {list.Count}: {message}\r\nItems:\r\n {string.Join("\r\n ", list)}"); } } private sealed class LineComparer : IEqualityComparer<string> { public static readonly LineComparer Instance = new LineComparer(); public bool Equals(string left, string right) => left.Trim() == right.Trim(); public int GetHashCode(string str) => str.Trim().GetHashCode(); } private static IEnumerable<string> GetLines(string str) => str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); public static void AssertLinesEqual(string expected, string actual) { AssertEx.Equal( GetLines(expected), GetLines(actual), comparer: LineComparer.Instance); } public static void AssertLinesEqual(string expected, string actual, string message, string expectedValueSourcePath, int expectedValueSourceLine, bool escapeQuotes) { AssertEx.Equal( GetLines(expected), GetLines(actual), comparer: LineComparer.Instance, message: message, itemInspector: escapeQuotes ? new Func<string, string>(line => line.Replace("\"", "\"\"")) : null, itemSeparator: Environment.NewLine, expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine); } public static void Equal(bool[,] expected, Func<int, int, bool> getResult, int size) { Equal<bool>(expected, getResult, (b1, b2) => b1 == b2, b => b ? "true" : "false", "{0,-6:G}", size); } public static void Equal<T>(T[,] expected, Func<int, int, T> getResult, Func<T, T, bool> valuesEqual, Func<T, string> printValue, string format, int size) { bool mismatch = false; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (!valuesEqual(expected[i, j], getResult(i, j))) { mismatch = true; } } } if (mismatch) { var builder = new StringBuilder(); builder.AppendLine("Actual result: "); for (int i = 0; i < size; i++) { builder.Append("{ "); for (int j = 0; j < size; j++) { string resultWithComma = printValue(getResult(i, j)); if (j < size - 1) { resultWithComma += ","; } builder.Append(string.Format(format, resultWithComma)); if (j < size - 1) { builder.Append(' '); } } builder.AppendLine("},"); } Assert.Fail(builder.ToString()); } } /// <summary> /// Run multiple assertions at once and collect the result. /// This is useful when you want to verify multiple assertions but don't want to re-run to adjust every other case. /// </summary> public static void Multiple(params Action[] assertions) { Multiple(includeStackTrace: false, assertions); } /// <inheritdoc cref="Multiple(System.Action[])"/> public static void Multiple(bool includeStackTrace, params Action[] assertions) { List<(int, Exception)> exceptions = null; // Run assertions in reverse order so that line numbers don't change as we adjust the baseline. for (int index = assertions.Length - 1; index >= 0; --index) { try { assertions[index](); } catch (Exception ex) { (exceptions ??= new()).Add((index, ex)); } } if (exceptions is null) return; var stringBuilder = new StringBuilder() .AppendLine($"{exceptions.Count} out of {assertions.Length} assertions failed.") .AppendLine(); foreach (var (index, ex) in exceptions) { var stack = ex.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None); stringBuilder .AppendLine($"Assertion failed at index {index}:") .AppendLine(stack[^2]) // Prints the failing line in the original test case. .AppendLine(ex.Message); if (includeStackTrace) stringBuilder.AppendLine(ex.StackTrace); stringBuilder .AppendLine() .AppendLine(); } Assert.Fail(stringBuilder.ToString()); } #nullable enable public static void Contains<T>(IEnumerable<T> collection, Predicate<T> filter, Func<T, string>? itemInspector = null, string? itemSeparator = null) { foreach (var item in collection) { if (filter(item)) { return; } } Assert.Fail("Filter does not match any item in the collection: " + Environment.NewLine + ToString(collection, itemSeparator ?? Environment.NewLine, itemInspector)); } #nullable enable /// <summary> /// The xunit Assert.Equal method is not callable in Visual Basic due to the presence of /// the unmanaged constraint. Need to indirect through C# here until we resolve this. /// /// https://github.com/dotnet/roslyn/issues/75063 /// </summary> public static void Equal<T>(T[] expected, T[] actual) => Assert.Equal<T>(expected, actual); /// <summary> /// The xunit Assert.Equal method is not callable in Visual Basic due to the presence of /// the unmanaged constraint. Need to indirect through C# here until we resolve this. /// /// https://github.com/dotnet/roslyn/issues/75063 /// </summary> public static void Equal<T>(T expected, T actual) => Assert.Equal<T>(expected, actual); /// <summary> /// The xunit Assert.NotEqual method is not callable in Visual Basic due to the presence of /// the unmanaged constraint. Need to indirect through C# here until we resolve this. /// /// https://github.com/dotnet/roslyn/issues/75063 /// </summary> public static void NotEqual<T>(T expected, T actual) => Assert.NotEqual<T>(expected, actual); /// <summary> /// This assert passes if the collection is not null and empty /// </summary> /// <remarks> /// The core <see cref="Xunit.Assert.Empty(IEnumerable)"/> is annotated to not accept null but many /// of our call sites pass a potentially nullable value. /// </remarks> public static void AssertEmpty(IEnumerable? collection) { Assert.NotNull(collection); Assert.Empty(collection); } /// <summary> /// This assert passes if the collection is not null and has a single item. /// </summary> /// <remarks> /// The core <see cref="Xunit.Assert.Single{T}(IEnumerable{T})"/> is annotated to not accept null but many /// of our call sites pass a potentially nullable value. /// </remarks> public static T Single<T>(IEnumerable<T>? collection) { Assert.NotNull(collection); return Assert.Single(collection); } /// <summary> /// Verify the collection is not null and all the items pass the action. /// </summary> /// <remarks> /// The core <see cref="Xunit.Assert.All{T}(IEnumerable{T}, Action{T})"/> is annotated to not accept null but many /// of our call sites pass a potentially nullable value. /// </remarks> public static void All<T>(IEnumerable<T>? collection, Action<T> action) { Assert.NotNull(collection); Assert.All(collection, action); } #nullable disable } }