Example #1
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());
            }
        }
Example #2
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);
            }
        }
Example #4
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];
        }
Example #5
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));
        }
Example #7
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);
            }
        }