Пример #1
0
        private static async Task AssertNoCompilerErrorsAsync(CodeFixProvider codeFix, Solution fixedSolution)
        {
            var diagnostics = await Analyze.GetDiagnosticsAsync(fixedSolution).ConfigureAwait(false);

            var introducedDiagnostics = diagnostics
                                        .SelectMany(x => x)
                                        .Where(IsIncluded)
                                        .ToArray();

            if (introducedDiagnostics.Select(x => x.Id)
                .Except(DiagnosticSettings.AllowedErrorIds())
                .Any())
            {
                var errorBuilder = StringBuilderPool.Borrow();
                errorBuilder.AppendLine($"{codeFix} introduced syntax error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}.");
                foreach (var introducedDiagnostic in introducedDiagnostics)
                {
                    var errorInfo = await introducedDiagnostic.ToStringAsync(fixedSolution).ConfigureAwait(false);

                    errorBuilder.AppendLine($"{errorInfo}");
                }

                errorBuilder.AppendLine("First source file with error is:");
                var sources = await Task.WhenAll(fixedSolution.Projects.SelectMany(p => p.Documents).Select(d => CodeReader.GetStringFromDocumentAsync(d, Formatter.Annotation, CancellationToken.None)));

                var lineSpan = introducedDiagnostics.First().Location.GetMappedLineSpan();
                var match    = sources.SingleOrDefault(x => CodeReader.FileName(x) == lineSpan.Path);
                errorBuilder.Append(match);
                errorBuilder.AppendLine();
                throw AssertException.Create(errorBuilder.Return());
            }
        }
Пример #2
0
        private static List <string> MergeFixedCodeWithErrorsIndicated(IReadOnlyList <string> codeWithErrorsIndicated, string fixedCode)
        {
            var merged = new List <string>(codeWithErrorsIndicated.Count);
            var found  = false;

            foreach (var code in codeWithErrorsIndicated)
            {
                if (code.IndexOf('↓') >= 0)
                {
                    if (found)
                    {
                        throw AssertException.Create("Expected only one with errors indicated.");
                    }

                    merged.Add(fixedCode);
                    found = true;
                }
                else
                {
                    merged.Add(code);
                }
            }

            if (!found)
            {
                throw AssertException.Create("Expected one with errors indicated.");
            }

            return(merged);
        }
Пример #3
0
        private static List <string> MergeFixedCode(IReadOnlyList <string> codes, string fixedCode)
        {
            var merged   = new List <string>(codes.Count);
            var found    = false;
            var fileName = CodeReader.FileName(fixedCode);

            foreach (var code in codes)
            {
                if (CodeReader.FileName(code) == fileName)
                {
                    if (found)
                    {
                        throw AssertException.Create("Expected only one with errors indicated.");
                    }

                    merged.Add(fixedCode);
                    found = true;
                }
                else
                {
                    merged.Add(code);
                }
            }

            if (!found)
            {
                throw AssertException.Create("Expected one with errors indicated.");
            }

            return(merged);
        }
Пример #4
0
        /// <summary>
        /// Check the solution for compiler errors and warnings, uses:
        /// </summary>
        public static async Task NoCompilerErrorsAsync(Solution solution, IReadOnlyList <string> allowedIds, AllowedDiagnostics allowedDiagnostics)
        {
            var diagnostics = await Analyze.GetDiagnosticsAsync(solution).ConfigureAwait(false);

            var introducedDiagnostics = diagnostics
                                        .SelectMany(x => x)
                                        .Where(x => IsIncluded(x, allowedDiagnostics))
                                        .ToArray();

            if (introducedDiagnostics.Select(x => x.Id)
                .Except(allowedIds ?? Enumerable.Empty <string>())
                .Any())
            {
                var error = StringBuilderPool.Borrow();
                error.AppendLine($"Found error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}.");
                foreach (var introducedDiagnostic in introducedDiagnostics)
                {
                    var errorInfo = await introducedDiagnostic.ToStringAsync(solution).ConfigureAwait(false);

                    error.AppendLine($"{errorInfo}");
                }

                throw AssertException.Create(StringBuilderPool.Return(error));
            }
        }
Пример #5
0
 private static void AssertCodeFixCanFixDiagnosticsFromAnalyzer(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix)
 {
     if (!analyzer.SupportedDiagnostics.Select(d => d.Id).Intersect(codeFix.FixableDiagnosticIds).Any())
     {
         var message = $"Analyzer {analyzer} does not produce diagnostics fixable by {codeFix}.{Environment.NewLine}" +
                       $"The analyzer produces the following diagnostics: {{{string.Join(", ", analyzer.SupportedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" +
                       $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}";
         throw AssertException.Create(message);
     }
 }
Пример #6
0
        /// <summary>
        /// Verifies that <paramref name="code"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>.
        /// </summary>
        /// <param name="analyzer">The analyzer.</param>
        /// <param name="code">
        /// The code to create the solution from.
        /// Can be a .cs, .csproj or .sln file
        /// </param>
        /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param>
        /// <param name="metadataReferences">The metadata references to use when compiling.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task ValidAsync(DiagnosticAnalyzer analyzer, FileInfo code, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences)
        {
            var diagnostics = await Analyze.GetDiagnosticsAsync(analyzer, code, compilationOptions, metadataReferences)
                              .ConfigureAwait(false);

            if (diagnostics.SelectMany(x => x).Any())
            {
                throw AssertException.Create(string.Join(Environment.NewLine, diagnostics.SelectMany(x => x)));
            }
        }
Пример #7
0
        /// <summary>
        /// Verifies that <paramref name="solution"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>.
        /// </summary>
        /// <param name="analyzer">The <see cref="DiagnosticAnalyzer"/>.</param>
        /// <param name="solution">The <see cref="Solution"/> for which no errors or warnings are expected.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task ValidAsync(DiagnosticAnalyzer analyzer, Solution solution)
        {
            var diagnostics = await Analyze.GetDiagnosticsAsync(solution, analyzer)
                              .ConfigureAwait(false);

            if (diagnostics.SelectMany(x => x).Any())
            {
                throw AssertException.Create(string.Join(Environment.NewLine, diagnostics.SelectMany(x => x)));
            }
        }
Пример #8
0
        /// <summary>
        /// Verifies that
        /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics when analyzed.
        /// 2. The code fix fixes the code.
        /// </summary>
        /// <param name="analyzer">The analyzer to run on the code..</param>
        /// <param name="codeFix">The code fix to apply.</param>
        /// <param name="diagnosticsAndSources">The code and expected diagnostics.</param>
        /// <param name="fixedCode">The expected code produced by the code fix.</param>
        /// <param name="fixTitle">The title of the fix to apply if more than one.</param>
        /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param>
        /// <param name="metadataReferences">The meta data metadataReferences to add to the compilation.</param>
        /// <param name="allowCompilationErrors">If compilation errors are accepted in the fixed code.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task CodeFixAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, string fixedCode, string fixTitle, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences, AllowCompilationErrors allowCompilationErrors)
        {
            AssertCodeFixCanFixDiagnosticsFromAnalyzer(analyzer, codeFix);
            var data = await DiagnosticsWithMetadataAsync(analyzer, diagnosticsAndSources, compilationOptions, metadataReferences).ConfigureAwait(false);

            var fixableDiagnostics = data.ActualDiagnostics.SelectMany(x => x)
                                     .Where(x => codeFix.FixableDiagnosticIds.Contains(x.Id))
                                     .ToArray();

            if (fixableDiagnostics.Length == 0)
            {
                var message = $"Code analyzed with {analyzer} did not generate any diagnostics fixable by {codeFix}.{Environment.NewLine}" +
                              $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" +
                              $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}";
                throw AssertException.Create(message);
            }

            if (fixableDiagnostics.Length > 1)
            {
                var message = $"Code analyzed with {analyzer} generated more than one diagnostic fixable by {codeFix}.{Environment.NewLine}" +
                              $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" +
                              $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}{Environment.NewLine}" +
                              $"Maybe you meant to call AnalyzerAssert.FixAll?";
                throw AssertException.Create(message);
            }

            var diagnostic    = fixableDiagnostics.Single();
            var fixedSolution = await Fix.ApplyAsync(data.Solution, codeFix, diagnostic, fixTitle, CancellationToken.None).ConfigureAwait(false);

            if (ReferenceEquals(data.Solution, fixedSolution))
            {
                throw AssertException.Create($"{codeFix} did not change any document.");
            }

            var fixedSource = await CodeReader.GetStringFromDocumentAsync(
                fixedSolution.GetDocument(data.Solution.GetDocument(diagnostic.Location.SourceTree).Id),
                Formatter.Annotation,
                CancellationToken.None)
                              .ConfigureAwait(false);

            CodeAssert.AreEqual(fixedCode, fixedSource);

            if (allowCompilationErrors == AllowCompilationErrors.No)
            {
                await AssertNoCompilerErrorsAsync(codeFix, fixedSolution).ConfigureAwait(false);
            }
        }
Пример #9
0
        /// <summary>
        /// Verifies that <paramref name="code"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>.
        /// </summary>
        /// <param name="analyzer">The analyzer.</param>
        /// <param name="code">The code to analyze.</param>
        /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param>
        /// <param name="metadataReferences">The metadata references to use when compiling.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task ValidAsync(DiagnosticAnalyzer analyzer, IReadOnlyList <string> code, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences)
        {
            var diagnostics = await Analyze.GetDiagnosticsAsync(
                analyzer,
                code,
                compilationOptions,
                metadataReferences)
                              .ConfigureAwait(false);

            if (diagnostics.SelectMany(x => x).Any())
            {
                var builder = StringBuilderPool.Borrow().AppendLine("Expected no diagnostics, found:");
                foreach (var diagnostic in diagnostics.SelectMany(x => x))
                {
                    builder.AppendLine(diagnostic.ToString(code));
                }

                throw AssertException.Create(builder.Return());
            }
        }
Пример #10
0
        private static async Task <DiagnosticsMetadata> CreateDiagnosticsMetadataAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReference)
        {
            AssertCodeFixCanFixDiagnosticsFromAnalyzer(analyzer, codeFix);
            var data = await DiagnosticsWithMetadataAsync(analyzer, diagnosticsAndSources, compilationOptions, metadataReference)
                       .ConfigureAwait(false);

            var fixableDiagnostics = data.ActualDiagnostics.SelectMany(x => x)
                                     .Where(x => codeFix.FixableDiagnosticIds.Contains(x.Id))
                                     .ToArray();

            if (fixableDiagnostics.Length == 0)
            {
                var message =
                    $"Code analyzed with {analyzer} did not generate any diagnostics fixable by {codeFix}.{Environment.NewLine}" +
                    $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" +
                    $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}";
                throw AssertException.Create(message);
            }

            return(data);
        }
        /// <summary>
        /// Verifies that
        /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics
        /// 2. The code fix does not change the code.
        /// </summary>
        /// <param name="analyzer">The type of the analyzer.</param>
        /// <param name="codeFix">The type of the code fix.</param>
        /// <param name="diagnosticsAndSources">The code with error positions indicated.</param>
        /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param>
        /// <param name="metadataReferences">The meta data references to use when compiling.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task NoFixAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences)
        {
            AssertCodeFixCanFixDiagnosticsFromAnalyzer(analyzer, codeFix);
            var data = await DiagnosticsWithMetadataAsync(analyzer, diagnosticsAndSources, compilationOptions, metadataReferences).ConfigureAwait(false);

            var fixableDiagnostics = data.ActualDiagnostics.SelectMany(x => x)
                                     .Where(x => codeFix.FixableDiagnosticIds.Contains(x.Id))
                                     .ToArray();

            if (fixableDiagnostics.Length != 1)
            {
                throw AssertException.Create("Expected code to have exactly one fixable diagnostic.");
            }

            if (await Fix.IsRegisteringFixAsync(data.Solution, codeFix, fixableDiagnostics.Single()))
            {
                var fixedSolution = await Fix.ApplyAsync(data.Solution, codeFix, fixableDiagnostics.Single(), null, CancellationToken.None)
                                    .ConfigureAwait(false);
                await AreEqualAsync(data.Sources, fixedSolution, "Expected the code fix to not change any document.").ConfigureAwait(false);
            }
        }
Пример #12
0
        private static void AssertAnalyzerSupportsExpectedDiagnostic(DiagnosticAnalyzer analyzer, ExpectedDiagnostic expectedDiagnostic, out DiagnosticDescriptor descriptor, out IReadOnlyList <string> suppressedDiagnostics)
        {
            var descriptors = analyzer.SupportedDiagnostics.Where(x => x.Id == expectedDiagnostic.Id).ToArray();

            if (descriptors.Length == 0)
            {
                var message = $"Analyzer {analyzer} does not produce a diagnostic with ID {expectedDiagnostic.Id}.{Environment.NewLine}" +
                              $"The analyzer produces the following diagnostics: {{{string.Join(", ", analyzer.SupportedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" +
                              $"The expected diagnostic is: {expectedDiagnostic.Id}";
                throw AssertException.Create(message);
            }

            if (descriptors.Length > 1)
            {
                var message = $"Analyzer {analyzer} supports multiple diagnostics with ID {expectedDiagnostic.Id}.{Environment.NewLine}" +
                              $"The analyzer produces the following diagnostics: {{{string.Join(", ", analyzer.SupportedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" +
                              $"The expected diagnostic is: {expectedDiagnostic.Id}";
                throw AssertException.Create(message);
            }

            suppressedDiagnostics = analyzer.SupportedDiagnostics.Select(x => x.Id).Where(x => x != expectedDiagnostic.Id).ToArray();
            descriptor            = descriptors[0];
        }
Пример #13
0
        /// <summary>
        /// Verify that two strings of code are equal. Agnostic to end of line characters.
        /// </summary>
        /// <param name="expected">The expected code.</param>
        /// <param name="actual">The actual code.</param>
        /// <param name="messageHeader">The first line to add to the exception message on error.</param>
        internal static void AreEqual(string expected, string actual, string messageHeader)
        {
            var pos      = 0;
            var otherPos = 0;
            var line     = 1;

            while (pos < expected.Length && otherPos < actual.Length)
            {
                if (expected[pos] == '\r')
                {
                    pos++;
                    continue;
                }

                if (actual[otherPos] == '\r')
                {
                    otherPos++;
                    continue;
                }

                if (expected[pos] != actual[otherPos])
                {
                    var errorBuilder = StringBuilderPool.Borrow();
                    if (messageHeader != null)
                    {
                        errorBuilder.AppendLine(messageHeader);
                    }

                    errorBuilder.AppendLine($"Mismatch on line {line}");
                    var expectedLine = expected.Split('\n')[line - 1].Trim('\r');
                    var actualLine   = actual.Split('\n')[line - 1].Trim('\r');
                    var diffPos      = Math.Min(expectedLine.Length, actualLine.Length);
                    for (var i = 0; i < Math.Min(expectedLine.Length, actualLine.Length); i++)
                    {
                        if (expectedLine[i] != actualLine[i])
                        {
                            diffPos = i;
                            break;
                        }
                    }

                    errorBuilder.AppendLine($"Expected: {expectedLine}");
                    errorBuilder.AppendLine($"Actual:   {actualLine}");
                    errorBuilder.AppendLine($"          {new string(' ', diffPos)}^");
                    throw AssertException.Create(errorBuilder.Return());
                }

                if (expected[pos] == '\n')
                {
                    line++;
                }

                pos++;
                otherPos++;
            }

            while (pos < expected.Length && expected[pos] == '\r')
            {
                pos++;
            }

            while (otherPos < actual.Length && actual[otherPos] == '\r')
            {
                otherPos++;
            }

            if (pos == expected.Length && otherPos == actual.Length)
            {
                return;
            }

            if (messageHeader != null)
            {
                throw AssertException.Create($"{messageHeader}{Environment.NewLine}" +
                                             $"Mismatch at end of text.");
            }

            throw AssertException.Create($"Mismatch at end of text.");
        }
        /// <summary>
        /// Verifies that <paramref name="sources"/> produces the expected diagnostics.
        /// </summary>
        /// <param name="analyzer">The analyzer to apply.</param>
        /// <param name="sources">The code with error positions indicated.</param>
        /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param>
        /// <param name="metadataReferences">The meta data metadataReferences to use when compiling.</param>
        /// <param name="expectedMessage">The expected message in the diagnostic produced by the analyzer.</param>
        /// <returns>The meta data from the run..</returns>
        public static async Task <DiagnosticsMetadata> DiagnosticsWithMetadataAsync(
            DiagnosticAnalyzer analyzer,
            DiagnosticsAndSources sources,
            CSharpCompilationOptions compilationOptions,
            IReadOnlyList <MetadataReference> metadataReferences,
            string expectedMessage = null)
        {
            if (sources.ExpectedDiagnostics.Count == 0)
            {
                throw AssertException.Create("Expected code to have at least one error position indicated with '↓'");
            }

            var data = await Analyze.GetDiagnosticsWithMetadataAsync(
                analyzer,
                sources.Code,
                compilationOptions,
                metadataReferences)
                       .ConfigureAwait(false);

            var expecteds = sources.ExpectedDiagnostics;
            var actuals   = data.Diagnostics
                            .SelectMany(x => x)
                            .ToArray();

            if (expecteds.SetEquals(actuals))
            {
                if (expectedMessage != null)
                {
                    foreach (var actual in data.Diagnostics.SelectMany(x => x))
                    {
                        var actualMessage = actual.GetMessage(CultureInfo.InvariantCulture);
                        TextAssert.AreEqual(expectedMessage, actualMessage, $"Expected and actual diagnostic message for the diagnostic {actual} does not match");
                    }
                }

                return(new DiagnosticsMetadata(
                           sources.Code,
                           sources.ExpectedDiagnostics,
                           data.Diagnostics,
                           data.Solution));
            }

            var error = StringBuilderPool.Borrow();

            error.AppendLine("Expected and actual diagnostics do not match.");
            var missingExpected = expecteds.Except(actuals);

            for (var i = 0; i < missingExpected.Count; i++)
            {
                if (i == 0)
                {
                    error.Append("Expected:\r\n");
                }

                var expected = missingExpected[i];
                error.AppendLine(expected.ToString(sources.Code));
            }

            if (actuals.Length == 0)
            {
                error.AppendLine("Actual: <no errors>");
            }

            var missingActual = actuals.Except(expecteds);

            if (actuals.Length > 0 && missingActual.Count == 0)
            {
                error.AppendLine("Actual: <missing>");
            }

            for (var i = 0; i < missingActual.Count; i++)
            {
                if (i == 0)
                {
                    error.Append("Actual:\r\n");
                }

                var actual = missingActual[i];
                error.AppendLine(actual.ToString(sources.Code));
            }

            throw AssertException.Create(StringBuilderPool.Return(error));
        }
Пример #15
0
        /// <summary>
        /// Verify that two strings of code are equal. Agnostic to end of line characters.
        /// </summary>
        /// <param name="expected">The expected code.</param>
        /// <param name="actual">The actual code.</param>
        /// <param name="messageHeader">The first line to add to the exception message on error.</param>
        internal static void AreEqual(string expected, string actual, string messageHeader)
        {
            var expectedPos = 0;
            var actualPos   = 0;
            var line        = 1;

            while (expectedPos < expected.Length && actualPos < actual.Length)
            {
                var ec = expected[expectedPos];
                var ac = actual[actualPos];
                if (ec == '\r' || ac == '\r')
                {
                    if (ec == '\r')
                    {
                        expectedPos++;
                    }

                    if (ac == '\r')
                    {
                        actualPos++;
                    }

                    continue;
                }

                if (ec != ac)
                {
                    var errorBuilder = StringBuilderPool.Borrow();
                    if (messageHeader != null)
                    {
                        errorBuilder.AppendLine(messageHeader);
                    }

                    errorBuilder.AppendLine($"Mismatch on line {line} of file {CodeReader.FileName(expected)}");
                    var expectedLine = expected.Split('\n')[line - 1].Trim('\r');
                    var actualLine   = actual.Split('\n')[line - 1].Trim('\r');
                    var diffPos      = Math.Min(expectedLine.Length, actualLine.Length);
                    for (var i = 0; i < Math.Min(expectedLine.Length, actualLine.Length); i++)
                    {
                        if (expectedLine[i] != actualLine[i])
                        {
                            diffPos = i;
                            break;
                        }
                    }

                    errorBuilder.AppendLine($"Expected: {expectedLine}")
                    .AppendLine($"Actual:   {actualLine}")
                    .AppendLine($"          {new string(' ', diffPos)}^")
                    .AppendLine("Expected:")
                    .Append(expected)
                    .AppendLine()
                    .AppendLine("Actual:")
                    .Append(actual)
                    .AppendLine();

                    throw AssertException.Create(errorBuilder.Return());
                }

                if (ec == '\n')
                {
                    line++;
                }

                expectedPos++;
                actualPos++;
            }

            while (expectedPos < expected.Length && expected[expectedPos] == '\r')
            {
                expectedPos++;
            }

            while (actualPos < actual.Length && actual[actualPos] == '\r')
            {
                actualPos++;
            }

            if (expectedPos == expected.Length && actualPos == actual.Length)
            {
                return;
            }

            if (messageHeader != null)
            {
                var message = StringBuilderPool.Borrow()
                              .AppendLine(messageHeader)
                              .AppendLine($"Mismatch at end of file {CodeReader.FileName(expected)}")
                              .AppendLine("Expected:")
                              .Append(expected)
                              .AppendLine()
                              .AppendLine("Actual:")
                              .Append(actual)
                              .AppendLine()
                              .Return();
                throw AssertException.Create(message);
            }
            else
            {
                var message = StringBuilderPool.Borrow()
                              .AppendLine($"Mismatch at end of file {CodeReader.FileName(expected)}")
                              .AppendLine("Expected:")
                              .Append(expected)
                              .AppendLine()
                              .AppendLine("Actual:")
                              .Append(actual)
                              .AppendLine()
                              .Return();
                throw AssertException.Create(message);
            }
        }