From 8491f47cbf80cbec6588e547377d945c2445d9a5 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 11 Mar 2021 08:15:57 -0800 Subject: [PATCH 1/5] Add ProjectState.GeneratedSources API --- .../CodeActionTest`1.cs | 2 ++ .../Model/EvaluatedProjectState.cs | 7 +++++ .../ProjectState.cs | 3 ++ .../PublicAPI.Unshipped.txt | 2 ++ .../SolutionState.cs | 25 ++++++++++++--- .../SourceGeneratorValidationTests.cs | 31 ++++--------------- 6 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs index 58489fb18f..4593954579 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs @@ -64,6 +64,7 @@ protected static bool CodeActionExpected(SolutionState state) return state.InheritanceMode != null || state.MarkupHandling != null || state.Sources.Any() + || state.GeneratedSources.Any() || state.AdditionalFiles.Any() || state.AnalyzerConfigFiles.Any() || state.AdditionalFilesFactories.Any(); @@ -72,6 +73,7 @@ protected static bool CodeActionExpected(SolutionState state) protected static bool HasAnyChange(SolutionState oldState, SolutionState newState) { return !oldState.Sources.SequenceEqual(newState.Sources, SourceFileEqualityComparer.Instance) + || !oldState.GeneratedSources.SequenceEqual(newState.GeneratedSources, SourceFileEqualityComparer.Instance) || !oldState.AdditionalFiles.SequenceEqual(newState.AdditionalFiles, SourceFileEqualityComparer.Instance) || !oldState.AnalyzerConfigFiles.SequenceEqual(newState.AnalyzerConfigFiles, SourceFileEqualityComparer.Instance); } diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs index 1e512e23bc..421c091064 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs @@ -21,6 +21,7 @@ public EvaluatedProjectState(ProjectState state, ReferenceAssemblies defaultRefe state.OutputKind ?? OutputKind.DynamicallyLinkedLibrary, state.DocumentationMode ?? DocumentationMode.Diagnose, state.Sources.ToImmutableArray(), + state.GeneratedSources.ToImmutableArray(), state.AdditionalFiles.ToImmutableArray(), state.AnalyzerConfigFiles.ToImmutableArray(), state.AdditionalProjectReferences.ToImmutableArray(), @@ -37,6 +38,7 @@ private EvaluatedProjectState( OutputKind outputKind, DocumentationMode documentationMode, ImmutableArray<(string filename, SourceText content)> sources, + ImmutableArray<(string filename, SourceText content)> generatedSources, ImmutableArray<(string filename, SourceText content)> additionalFiles, ImmutableArray<(string filename, SourceText content)> analyzerConfigFiles, ImmutableArray additionalProjectReferences, @@ -50,6 +52,7 @@ private EvaluatedProjectState( OutputKind = outputKind; DocumentationMode = documentationMode; Sources = sources; + GeneratedSources = generatedSources; AdditionalFiles = additionalFiles; AnalyzerConfigFiles = analyzerConfigFiles; AdditionalProjectReferences = additionalProjectReferences; @@ -71,6 +74,8 @@ private EvaluatedProjectState( public ImmutableArray<(string filename, SourceText content)> Sources { get; } + public ImmutableArray<(string filename, SourceText content)> GeneratedSources { get; } + public ImmutableArray<(string filename, SourceText content)> AdditionalFiles { get; } public ImmutableArray<(string filename, SourceText content)> AnalyzerConfigFiles { get; } @@ -109,6 +114,7 @@ private EvaluatedProjectState With( Optional outputKind = default, Optional documentationMode = default, Optional> sources = default, + Optional> generatedSources = default, Optional> additionalFiles = default, Optional> analyzerConfigFiles = default, Optional> additionalProjectReferences = default, @@ -123,6 +129,7 @@ private EvaluatedProjectState With( GetValueOrDefault(outputKind, OutputKind), GetValueOrDefault(documentationMode, DocumentationMode), GetValueOrDefault(sources, Sources), + GetValueOrDefault(generatedSources, GeneratedSources), GetValueOrDefault(additionalFiles, AdditionalFiles), GetValueOrDefault(analyzerConfigFiles, AnalyzerConfigFiles), GetValueOrDefault(additionalProjectReferences, AdditionalProjectReferences), diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs index 4dcbd966e1..8bde053df3 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs @@ -32,6 +32,7 @@ internal ProjectState(ProjectState sourceState) Sources = new SourceFileList(DefaultPrefix, DefaultExtension); Sources.AddRange(sourceState.Sources); + GeneratedSources.AddRange(sourceState.GeneratedSources); AdditionalFiles.AddRange(sourceState.AdditionalFiles); AnalyzerConfigFiles.AddRange(sourceState.AnalyzerConfigFiles); AdditionalFilesFactories.AddRange(sourceState.AdditionalFilesFactories); @@ -64,6 +65,8 @@ internal ProjectState(ProjectState sourceState) /// public SourceFileList Sources { get; } + public SourceFileCollection GeneratedSources { get; } = new SourceFileCollection(); + public SourceFileCollection AdditionalFiles { get; } = new SourceFileCollection(); public SourceFileCollection AnalyzerConfigFiles { get; } = new SourceFileCollection(); diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt index f488f5620e..ed076974b9 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt @@ -127,6 +127,7 @@ Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AnalyzerConfigFiles.g Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AssemblyName.get -> string Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.DocumentationMode.get -> Microsoft.CodeAnalysis.DocumentationMode Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.EvaluatedProjectState(Microsoft.CodeAnalysis.Testing.ProjectState state, Microsoft.CodeAnalysis.Testing.ReferenceAssemblies defaultReferenceAssemblies) -> void +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.GeneratedSources.get -> System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.Language.get -> string Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.Name.get -> string Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.OutputKind.get -> Microsoft.CodeAnalysis.OutputKind @@ -151,6 +152,7 @@ Microsoft.CodeAnalysis.Testing.ProjectState.AnalyzerConfigFiles.get -> Microsoft Microsoft.CodeAnalysis.Testing.ProjectState.AssemblyName.get -> string Microsoft.CodeAnalysis.Testing.ProjectState.DocumentationMode.get -> Microsoft.CodeAnalysis.DocumentationMode? Microsoft.CodeAnalysis.Testing.ProjectState.DocumentationMode.set -> void +Microsoft.CodeAnalysis.Testing.ProjectState.GeneratedSources.get -> Microsoft.CodeAnalysis.Testing.SourceFileCollection Microsoft.CodeAnalysis.Testing.ProjectState.Language.get -> string Microsoft.CodeAnalysis.Testing.ProjectState.Name.get -> string Microsoft.CodeAnalysis.Testing.ProjectState.OutputKind.get -> Microsoft.CodeAnalysis.OutputKind? diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs index ac58cd419b..0e248099a1 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs @@ -135,6 +135,11 @@ public SolutionState WithInheritedValuesApplied(SolutionState? baseState, Immuta result.Sources.AddRange(baseState.Sources); } + if (GeneratedSources.Count == 0) + { + result.GeneratedSources.AddRange(baseState.GeneratedSources); + } + if (AdditionalFiles.Count == 0) { result.AdditionalFiles.AddRange(baseState.AdditionalFiles); @@ -176,6 +181,7 @@ public SolutionState WithInheritedValuesApplied(SolutionState? baseState, Immuta result.MarkupHandling = markupHandling; result.InheritanceMode = StateInheritanceMode.Explicit; result.Sources.AddRange(Sources); + result.GeneratedSources.AddRange(GeneratedSources); result.AdditionalFiles.AddRange(AdditionalFiles); result.AnalyzerConfigFiles.AddRange(AnalyzerConfigFiles); result.AdditionalProjects.AddRange(AdditionalProjects); @@ -203,6 +209,11 @@ private static bool HasAnyContentChanges(bool willInherit, SolutionState state, return true; } + if ((!willInherit || state.GeneratedSources.Any()) && !ContentEqual(state.GeneratedSources, baseState.GeneratedSources)) + { + return true; + } + if ((!willInherit || state.AdditionalFiles.Any()) && !ContentEqual(state.AdditionalFiles, baseState.AdditionalFiles)) { return true; @@ -252,8 +263,9 @@ private static bool ContentEqual(SourceFileCollection x, SourceFileCollection y) /// /// Processes the markup syntax for this according to the current /// , and returns a new with the - /// , , - /// , and updated accordingly. + /// , , + /// , , and + /// updated accordingly. /// /// Additional options to apply during markup processing. /// The diagnostic descriptor to use for markup spans without an explicit name, @@ -276,7 +288,8 @@ public SolutionState WithProcessedMarkup(MarkupOptions markupOptions, Diagnostic var markupLocations = ImmutableDictionary.Empty; (var expected, var testSources) = ProcessMarkupSources(Sources, ExpectedDiagnostics, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); - var (additionalExpected1, additionalFiles) = ProcessMarkupSources(AdditionalFiles.Concat(AdditionalFilesFactories.SelectMany(factory => factory())), expected, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (additionalExpected2, testGeneratedSources) = ProcessMarkupSources(GeneratedSources, expected, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (additionalExpected1, additionalFiles) = ProcessMarkupSources(AdditionalFiles.Concat(AdditionalFilesFactories.SelectMany(factory => factory())), additionalExpected2, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); var (additionalExpected, analyzerConfigFiles) = ProcessMarkupSources(AnalyzerConfigFiles, additionalExpected1, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); var result = new SolutionState(Name, Language, DefaultPrefix, DefaultExtension); @@ -286,18 +299,22 @@ public SolutionState WithProcessedMarkup(MarkupOptions markupOptions, Diagnostic result.OutputKind = OutputKind; result.DocumentationMode = DocumentationMode; result.Sources.AddRange(testSources); + result.GeneratedSources.AddRange(testGeneratedSources); result.AdditionalFiles.AddRange(additionalFiles); result.AnalyzerConfigFiles.AddRange(analyzerConfigFiles); foreach (var (projectName, projectState) in AdditionalProjects) { var (correctedIntermediateDiagnostics, additionalProjectSources) = ProcessMarkupSources(projectState.Sources, additionalExpected, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); - var (correctedDiagnostics1, additionalProjectAdditionalFiles) = ProcessMarkupSources(projectState.AdditionalFiles.Concat(projectState.AdditionalFilesFactories.SelectMany(factory => factory())), correctedIntermediateDiagnostics, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (correctedDiagnostics2, additionalProjectGeneratedSources) = ProcessMarkupSources(projectState.GeneratedSources, correctedIntermediateDiagnostics, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (correctedDiagnostics1, additionalProjectAdditionalFiles) = ProcessMarkupSources(projectState.AdditionalFiles.Concat(projectState.AdditionalFilesFactories.SelectMany(factory => factory())), correctedDiagnostics2, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); var (correctedDiagnostics, additionalProjectAnalyzerConfigFiles) = ProcessMarkupSources(projectState.AnalyzerConfigFiles, correctedDiagnostics1, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); var processedProjectState = new ProjectState(projectState); processedProjectState.Sources.Clear(); processedProjectState.Sources.AddRange(additionalProjectSources); + processedProjectState.GeneratedSources.Clear(); + processedProjectState.GeneratedSources.AddRange(additionalProjectGeneratedSources); processedProjectState.AdditionalFiles.Clear(); processedProjectState.AdditionalFilesFactories.Clear(); processedProjectState.AdditionalFiles.AddRange(additionalProjectAdditionalFiles); diff --git a/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs b/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs index b226e905f8..312d6f543e 100644 --- a/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs +++ b/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs @@ -26,12 +26,8 @@ public async Task AddSimpleFile() { @"// Comment", }, - }, - FixedState = - { - Sources = + GeneratedSources = { - @"// Comment", ("Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)), }, }, @@ -49,12 +45,8 @@ public async Task AddSimpleFileByGeneratorType() { @"// Comment", }, - }, - FixedState = - { - Sources = + GeneratedSources = { - @"// Comment", (typeof(AddEmptyFile), "EmptyGeneratedFile.cs", string.Empty), }, }, @@ -72,12 +64,8 @@ public async Task AddSimpleFileByGeneratorTypeWithEncoding() { @"// Comment", }, - }, - FixedState = - { - Sources = + GeneratedSources = { - @"// Comment", (typeof(AddEmptyFile), "EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)), }, }, @@ -94,10 +82,7 @@ public async Task AddSimpleFileToEmptyProject() Sources = { }, - }, - FixedState = - { - Sources = + GeneratedSources = { (typeof(AddEmptyFile), "EmptyGeneratedFile.cs", string.Empty), }, @@ -114,14 +99,10 @@ public async Task AddSimpleFileWithDiagnostic() { Sources = { - @"// Comment", + @"{|#0:|}// Comment", }, - }, - FixedState = - { - Sources = + GeneratedSources = { - @"{|#0:|}// Comment", ("Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFileWithDiagnostic\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)), }, ExpectedDiagnostics = From 63af4f90a3191c5feace6d43bfb8d58989bc715e Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 11 Mar 2021 08:46:06 -0800 Subject: [PATCH 2/5] Allow running tests with no enabled analyzers --- .../AnalyzerTest`1.cs | 5 +++++ .../SourceGeneratorTest`1.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs index b94f740812..0849056d38 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs @@ -988,6 +988,11 @@ private async Task> GetSortedDiagnosticsAsync(Evaluat /// . protected async Task> GetSortedDiagnosticsAsync(Solution solution, ImmutableArray analyzers, ImmutableArray additionalDiagnostics, CompilerDiagnostics compilerDiagnostics, CancellationToken cancellationToken) { + if (analyzers.IsEmpty) + { + analyzers = ImmutableArray.Create(new EmptyDiagnosticAnalyzer()); + } + var diagnostics = ImmutableArray.CreateBuilder(); foreach (var project in solution.Projects) { diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs index 7120b52565..9e761f116c 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs @@ -48,7 +48,7 @@ protected SourceGeneratorTest() } protected override IEnumerable GetDiagnosticAnalyzers() - => new DiagnosticAnalyzer[] { new EmptyDiagnosticAnalyzer() }; + => Enumerable.Empty(); /// /// Returns the source generators being tested - to be implemented in non-abstract class. From 8b63f3d79dacc09b11bb3af9f58e83d39e099247 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 11 Mar 2021 08:46:46 -0800 Subject: [PATCH 3/5] Avoid requiring custom TestBehaviors for source generator tests --- .../AnalyzerTest`1.cs | 18 ++++++++++++------ .../SourceGeneratorTest`1.cs | 3 --- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs index 0849056d38..35e74fade3 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs @@ -259,7 +259,7 @@ private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray IsSubjectToExclusion(x, sources))) + if (!expected.Any(x => IsSubjectToExclusion(x, analyzers, sources))) { return; } @@ -268,7 +268,7 @@ private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray !IsSubjectToExclusion(x, sources)) + .Where(x => !IsSubjectToExclusion(x, analyzers, sources)) .Select(x => IsInSourceFile(x, sources) ? x.WithLineOffset(1) : x) .ToArray(); @@ -285,7 +285,7 @@ private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray IsSubjectToExclusion(x, sources))) + if (!expected.Any(x => IsSubjectToExclusion(x, analyzers, sources))) { return; } @@ -300,7 +300,7 @@ private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray IsSubjectToExclusion(x, sources)).Select(x => x.Id).Distinct(); + var suppressedDiagnostics = expected.Where(x => IsSubjectToExclusion(x, analyzers, sources)).Select(x => x.Id).Distinct(); var suppression = prefix + " " + string.Join(", ", suppressedDiagnostics); var transformedProject = primaryProject.WithSources(primaryProject.Sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $"{suppression}\r\n"))).ToImmutableArray()); VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(transformedProject, additionalProjects, analyzers, suppressionVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, suppressionVerifier); @@ -886,7 +886,7 @@ void AppendLocation(DiagnosticLocation location) } } - private static bool IsSubjectToExclusion(DiagnosticResult result, (string filename, SourceText content)[] sources) + private static bool IsSubjectToExclusion(DiagnosticResult result, ImmutableArray analyzers, (string filename, SourceText content)[] sources) { if (result.Id.StartsWith("CS", StringComparison.Ordinal) || result.Id.StartsWith("BC", StringComparison.Ordinal)) @@ -912,12 +912,18 @@ private static bool IsSubjectToExclusion(DiagnosticResult result, (string filena return false; } + if (!analyzers.Any(analyzer => analyzer.SupportedDiagnostics.Any(supported => supported.Id == result.Id))) + { + // This diagnostic is not reported by an active analyzer + return false; + } + return true; } private static bool IsSuppressible(ImmutableArray analyzers, DiagnosticResult result, (string filename, SourceText content)[] sources) { - if (!IsSubjectToExclusion(result, sources)) + if (!IsSubjectToExclusion(result, analyzers, sources)) { return false; } diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs index 9e761f116c..4a87523491 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs @@ -42,9 +42,6 @@ public string FixedCode protected SourceGeneratorTest() { FixedState = new SolutionState(DefaultTestProjectName, Language, DefaultFilePathPrefix, DefaultFileExt); - - // Disable test behaviors that don't make sense for source generator scenarios - TestBehaviors |= TestBehaviors.SkipSuppressionCheck | TestBehaviors.SkipGeneratedCodeCheck; } protected override IEnumerable GetDiagnosticAnalyzers() From 1e28e66d211c031a46040ba73acfc23f0ec28866 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 11 Mar 2021 08:48:02 -0800 Subject: [PATCH 4/5] Simplify source generator testing --- .../PublicAPI.Unshipped.txt | 4 +- .../SourceGeneratorTest`1.cs | 68 ++++--------------- 2 files changed, 14 insertions(+), 58 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt index acdf9847b6..89d2b2dec6 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt @@ -3,10 +3,8 @@ Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.EmptySourceGenerator Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.Execute(Microsoft.CodeAnalysis.GeneratorExecutionContext context) -> void Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.Initialize(Microsoft.CodeAnalysis.GeneratorInitializationContext context) -> void Microsoft.CodeAnalysis.Testing.SourceGeneratorTest -Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.FixedCode.set -> void -Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.FixedState.get -> Microsoft.CodeAnalysis.Testing.SolutionState Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.SourceGeneratorTest() -> void -Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.VerifySourceGeneratorAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.SolutionState fixedState, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.VerifySourceGeneratorAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.CodeAnalysis.Testing.SourceGeneratorVerifier Microsoft.CodeAnalysis.Testing.SourceGeneratorVerifier.SourceGeneratorVerifier() -> void abstract Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.CreateGeneratorDriver(Microsoft.CodeAnalysis.Project project, System.Collections.Immutable.ImmutableArray sourceGenerators) -> Microsoft.CodeAnalysis.GeneratorDriver diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs index 4a87523491..875c335af0 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs @@ -22,28 +22,6 @@ namespace Microsoft.CodeAnalysis.Testing public abstract class SourceGeneratorTest : AnalyzerTest where TVerifier : IVerifier, new() { - /// - /// Sets the expected output source file for source generator testing. - /// - /// - public string FixedCode - { - set - { - if (value != null) - { - FixedState.Sources.Add(value); - } - } - } - - public SolutionState FixedState { get; } - - protected SourceGeneratorTest() - { - FixedState = new SolutionState(DefaultTestProjectName, Language, DefaultFilePathPrefix, DefaultFileExt); - } - protected override IEnumerable GetDiagnosticAnalyzers() => Enumerable.Empty(); @@ -61,41 +39,33 @@ public override async Task RunAsync(CancellationToken cancellationToken = defaul var defaultDiagnostic = GetDefaultDiagnostic(analyzers); var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); var fixableDiagnostics = ImmutableArray.Empty; + var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); - var rawTestState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics); - var rawFixedState = FixedState.WithInheritedValuesApplied(rawTestState, fixableDiagnostics); - - var testState = rawTestState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); - var fixedState = rawFixedState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); - - await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); - var diagnostics = await VerifySourceGeneratorAsync(testState, fixedState, Verify, cancellationToken).ConfigureAwait(false); - await VerifyDiagnosticsAsync(new EvaluatedProjectState(fixedState, ReferenceAssemblies).WithAdditionalDiagnostics(diagnostics), fixedState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), fixedState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state with generated sources"), cancellationToken).ConfigureAwait(false); + var diagnostics = await VerifySourceGeneratorAsync(testState, Verify, cancellationToken).ConfigureAwait(false); + await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies).WithAdditionalDiagnostics(diagnostics), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); } /// /// Called to test a C# source generator when applied on the input source as a string. /// /// The effective input test state. - /// The effective test state after the source generators are applied. /// The verifier to use for test assertions. /// The that the task will observe. /// A representing the asynchronous operation. - protected async Task> VerifySourceGeneratorAsync(SolutionState testState, SolutionState fixedState, IVerifier verifier, CancellationToken cancellationToken) + protected async Task> VerifySourceGeneratorAsync(SolutionState testState, IVerifier verifier, CancellationToken cancellationToken) { - return await VerifySourceGeneratorAsync(Language, GetSourceGenerators().ToImmutableArray(), testState, fixedState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken); + return await VerifySourceGeneratorAsync(Language, GetSourceGenerators().ToImmutableArray(), testState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken); } private async Task> VerifySourceGeneratorAsync( string language, ImmutableArray sourceGenerators, - SolutionState oldState, - SolutionState newState, + SolutionState testState, Func, Project, IVerifier, CancellationToken, Task<(Project project, ImmutableArray diagnostics)>> getFixedProject, IVerifier verifier, CancellationToken cancellationToken) { - var project = await CreateProjectAsync(new EvaluatedProjectState(oldState, ReferenceAssemblies), oldState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); + var project = await CreateProjectAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); _ = await GetCompilerDiagnosticsAsync(project, verifier, cancellationToken).ConfigureAwait(false); ImmutableArray diagnostics; @@ -103,29 +73,17 @@ private async Task> VerifySourceGeneratorAsync( // After applying the source generator, compare the resulting string to the inputted one var updatedDocuments = project.Documents.ToArray(); + var expectedSources = testState.Sources.Concat(testState.GeneratedSources).ToArray(); - verifier.Equal(newState.Sources.Count, updatedDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.Sources)}' and '{nameof(updatedDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.Sources)}' contains '{newState.Sources.Count}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); + verifier.Equal(expectedSources.Length, updatedDocuments.Length, $"expected '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' to match '{nameof(updatedDocuments)}', but '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' contains '{expectedSources.Length}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); for (var i = 0; i < updatedDocuments.Length; i++) { var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); - verifier.EqualOrDiff(newState.Sources[i].content.ToString(), actual.ToString(), $"content of '{newState.Sources[i].filename}' did not match. Diff shown with expected as baseline:"); - verifier.Equal(newState.Sources[i].content.Encoding, actual.Encoding, $"encoding of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.Encoding}' but was '{actual.Encoding}'"); - verifier.Equal(newState.Sources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); - verifier.Equal(newState.Sources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{newState.Sources[i].filename}' but was '{updatedDocuments[i].Name}'"); - } - - var updatedAdditionalDocuments = project.AdditionalDocuments.ToArray(); - - verifier.Equal(newState.AdditionalFiles.Count, updatedAdditionalDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' and '{nameof(updatedAdditionalDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' contains '{newState.AdditionalFiles.Count}' documents and '{nameof(updatedAdditionalDocuments)}' contains '{updatedAdditionalDocuments.Length}' documents"); - - for (var i = 0; i < updatedAdditionalDocuments.Length; i++) - { - var actual = await updatedAdditionalDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); - verifier.EqualOrDiff(newState.AdditionalFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AdditionalFiles[i].filename}' did not match. Diff shown with expected as baseline:"); - verifier.Equal(newState.AdditionalFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.Encoding}' but was '{actual.Encoding}'"); - verifier.Equal(newState.AdditionalFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); - verifier.Equal(newState.AdditionalFiles[i].filename, updatedAdditionalDocuments[i].Name, $"file name was expected to be '{newState.AdditionalFiles[i].filename}' but was '{updatedAdditionalDocuments[i].Name}'"); + verifier.EqualOrDiff(expectedSources[i].content.ToString(), actual.ToString(), $"content of '{expectedSources[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(expectedSources[i].content.Encoding, actual.Encoding, $"encoding of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.Encoding}' but was '{actual.Encoding}'"); + verifier.Equal(expectedSources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(expectedSources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{expectedSources[i].filename}' but was '{updatedDocuments[i].Name}'"); } return diagnostics; From e0c5578f5a05e7cb8fe1701ae56b59da171e5bbb Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 11 Mar 2021 08:52:52 -0800 Subject: [PATCH 5/5] Add TestBehaviors.SkipGeneratedSourcesCheck --- .../PublicAPI.Unshipped.txt | 1 + .../TestBehaviors.cs | 10 ++++++++ .../SourceGeneratorTest`1.cs | 25 +++++++++++-------- .../SourceGeneratorValidationTests.cs | 21 ++++++++++++++++ 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt index ed076974b9..0609c72fdc 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt @@ -225,6 +225,7 @@ Microsoft.CodeAnalysis.Testing.StateInheritanceMode.Explicit = 1 -> Microsoft.Co Microsoft.CodeAnalysis.Testing.TestBehaviors Microsoft.CodeAnalysis.Testing.TestBehaviors.None = 0 -> Microsoft.CodeAnalysis.Testing.TestBehaviors Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipGeneratedCodeCheck = 1 -> Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipGeneratedSourcesCheck = 4 -> Microsoft.CodeAnalysis.Testing.TestBehaviors Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipSuppressionCheck = 2 -> Microsoft.CodeAnalysis.Testing.TestBehaviors Microsoft.CodeAnalysis.Testing.TestFileMarkupParser abstract Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs index e63ca069f6..9fad0cfd12 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs @@ -36,5 +36,15 @@ public enum TestBehaviors /// at the beginning of the file. /// SkipSuppressionCheck = 0x02, + + /// + /// Skip a verification check that the contents of match the sources + /// produced by the active source generators (if any). + /// + /// + /// When this flag is set, the property is completely ignored; tests + /// are encouraged to leave it empty for optimal readability. + /// + SkipGeneratedSourcesCheck = 0x04, } } diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs index 875c335af0..74df7f76d8 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs @@ -72,18 +72,21 @@ private async Task> VerifySourceGeneratorAsync( (project, diagnostics) = await getFixedProject(sourceGenerators, project, verifier, cancellationToken).ConfigureAwait(false); // After applying the source generator, compare the resulting string to the inputted one - var updatedDocuments = project.Documents.ToArray(); - var expectedSources = testState.Sources.Concat(testState.GeneratedSources).ToArray(); - - verifier.Equal(expectedSources.Length, updatedDocuments.Length, $"expected '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' to match '{nameof(updatedDocuments)}', but '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' contains '{expectedSources.Length}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); - - for (var i = 0; i < updatedDocuments.Length; i++) + if (!TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck)) { - var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); - verifier.EqualOrDiff(expectedSources[i].content.ToString(), actual.ToString(), $"content of '{expectedSources[i].filename}' did not match. Diff shown with expected as baseline:"); - verifier.Equal(expectedSources[i].content.Encoding, actual.Encoding, $"encoding of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.Encoding}' but was '{actual.Encoding}'"); - verifier.Equal(expectedSources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); - verifier.Equal(expectedSources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{expectedSources[i].filename}' but was '{updatedDocuments[i].Name}'"); + var updatedDocuments = project.Documents.ToArray(); + var expectedSources = testState.Sources.Concat(testState.GeneratedSources).ToArray(); + + verifier.Equal(expectedSources.Length, updatedDocuments.Length, $"expected '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' to match '{nameof(updatedDocuments)}', but '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' contains '{expectedSources.Length}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); + + for (var i = 0; i < updatedDocuments.Length; i++) + { + var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(expectedSources[i].content.ToString(), actual.ToString(), $"content of '{expectedSources[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(expectedSources[i].content.Encoding, actual.Encoding, $"encoding of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.Encoding}' but was '{actual.Encoding}'"); + verifier.Equal(expectedSources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(expectedSources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{expectedSources[i].filename}' but was '{updatedDocuments[i].Name}'"); + } } return diagnostics; diff --git a/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs b/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs index 312d6f543e..06de2efdc9 100644 --- a/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs +++ b/tests/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests/SourceGeneratorValidationTests.cs @@ -114,6 +114,27 @@ public async Task AddSimpleFileWithDiagnostic() }.RunAsync(); } + [Fact] + public async Task AddImplicitSimpleFileWithDiagnostic() + { + await new CSharpSourceGeneratorTest + { + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck, + TestState = + { + Sources = + { + @"{|#0:|}// Comment", + }, + ExpectedDiagnostics = + { + // /0/Test0.cs(1,1): warning SG0001: Message + new DiagnosticResult(AddEmptyFileWithDiagnostic.Descriptor).WithLocation(0), + }, + }, + }.RunAsync(); + } + private class CSharpSourceGeneratorTest : SourceGeneratorTest where TSourceGenerator : ISourceGenerator, new() {