Move source generator validation to AnalyzerTest

This commit is contained in:
Sam Harwell 2023-02-10 15:47:31 -06:00
Родитель 8d32189fb0
Коммит d80065912c
14 изменённых файлов: 521 добавлений и 350 удалений

Просмотреть файл

@ -12,6 +12,10 @@ using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DiffPlex;
using DiffPlex.Chunkers;
using DiffPlex.DiffBuilder;
using DiffPlex.DiffBuilder.Model;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
@ -196,7 +200,11 @@ namespace Microsoft.CodeAnalysis.Testing
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual async Task RunImplAsync(CancellationToken cancellationToken)
{
Verify.NotEmpty($"{nameof(TestState)}.{nameof(SolutionState.Sources)}", TestState.Sources);
if (!TestState.GeneratedSources.Any())
{
// Verify the test state has at least one source, which may or may not be generated
Verify.NotEmpty($"{nameof(TestState)}.{nameof(SolutionState.Sources)}", TestState.Sources);
}
var analyzers = GetDiagnosticAnalyzers().ToArray();
var defaultDiagnostic = GetDefaultDiagnostic(analyzers);
@ -204,6 +212,7 @@ namespace Microsoft.CodeAnalysis.Testing
var fixableDiagnostics = ImmutableArray<string>.Empty;
var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath);
var diagnostics = await VerifySourceGeneratorAsync(testState, Verify, cancellationToken).ConfigureAwait(false);
await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify, cancellationToken).ConfigureAwait(false);
}
@ -254,6 +263,187 @@ namespace Microsoft.CodeAnalysis.Testing
$" {FormatDiagnostics(analyzers, DefaultFilePath, actual)}{Environment.NewLine}";
}
/// <summary>
/// Called to test a C# source generator when applied on the input source as a string.
/// </summary>
/// <param name="testState">The effective input test state.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected async Task<ImmutableArray<Diagnostic>> VerifySourceGeneratorAsync(SolutionState testState, IVerifier verifier, CancellationToken cancellationToken)
{
var sourceGenerators = GetSourceGenerators().ToImmutableArray();
if (sourceGenerators.IsEmpty)
{
return ImmutableArray<Diagnostic>.Empty;
}
return await VerifySourceGeneratorAsync(Language, GetSourceGenerators().ToImmutableArray(), testState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken);
}
private protected async Task<ImmutableArray<Diagnostic>> VerifySourceGeneratorAsync(
string language,
ImmutableArray<Type> sourceGenerators,
SolutionState testState,
Func<ImmutableArray<Type>, Project, IVerifier, CancellationToken, Task<(Project project, ImmutableArray<Diagnostic> diagnostics)>> getFixedProject,
IVerifier verifier,
CancellationToken cancellationToken)
{
var project = await CreateProjectAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken);
ImmutableArray<Diagnostic> diagnostics;
(project, diagnostics) = await getFixedProject(sourceGenerators, project, verifier, cancellationToken).ConfigureAwait(false);
// After applying the source generator, compare the resulting string to the inputted one
if (!TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck))
{
var numOriginalSources = testState.Sources.Count;
var updatedOriginalDocuments = project.Documents.Take(numOriginalSources).ToArray();
var generatedDocuments = project.Documents.Skip(numOriginalSources).ToArray();
// Verify no changes occurred to the original documents
var updatedOriginalDocumentsWithTextBuilder = ImmutableArray.CreateBuilder<(Document document, SourceText content)>();
foreach (var updatedOriginalDocument in updatedOriginalDocuments)
{
updatedOriginalDocumentsWithTextBuilder.Add((updatedOriginalDocument, await updatedOriginalDocument.GetTextAsync(CancellationToken.None).ConfigureAwait(false)));
}
VerifyDocuments(
verifier.PushContext("Original files after running source generators"),
updatedOriginalDocumentsWithTextBuilder.ToImmutable(),
testState.Sources.ToImmutableArray(),
allowReordering: false,
DefaultFilePathPrefix,
GetNameAndFoldersFromPath,
MatchDiagnosticsTimeout);
// Verify the source generated documents match expectations
var generatedDocumentsWithTextBuilder = ImmutableArray.CreateBuilder<(Document document, SourceText content)>();
foreach (var generatedDocument in generatedDocuments)
{
generatedDocumentsWithTextBuilder.Add((generatedDocument, await generatedDocument.GetTextAsync(CancellationToken.None).ConfigureAwait(false)));
}
VerifyDocuments(
verifier.PushContext("Verifying source generated files"),
generatedDocumentsWithTextBuilder.ToImmutable(),
testState.GeneratedSources.ToImmutableArray(),
allowReordering: true,
DefaultFilePathPrefix,
static (_, path) => GetNameAndFoldersFromSourceGeneratedFilePath(path),
MatchDiagnosticsTimeout);
}
return diagnostics;
static void VerifyDocuments(
IVerifier verifier,
ImmutableArray<(Document document, SourceText content)> actualDocuments,
ImmutableArray<(string filename, SourceText content)> expectedDocuments,
bool allowReordering,
string defaultFilePathPrefix,
Func<string, string, (string fileName, IEnumerable<string> folders)> getNameAndFolders,
TimeSpan matchTimeout)
{
ImmutableArray<WeightedMatch.Result<(string filename, SourceText content), (Document document, SourceText content)>> matches;
if (allowReordering)
{
matches = WeightedMatch.Match(
expectedDocuments,
actualDocuments,
ImmutableArray.Create<Func<(string filename, SourceText content), (Document document, SourceText content), bool, double>>(
static (expected, actual, exactOnly) =>
{
if (actual.content.ToString() == expected.content.ToString())
{
return 0.0;
}
if (exactOnly)
{
// Avoid expensive diff calculation when exact match was requested.
return 1.0;
}
var diffBuilder = new InlineDiffBuilder(new Differ());
var diff = diffBuilder.BuildDiffModel(expected.content.ToString(), actual.content.ToString(), ignoreWhitespace: true, ignoreCase: false, new LineChunker());
var changeCount = diff.Lines.Count(static line => line.Type is ChangeType.Inserted or ChangeType.Deleted);
if (changeCount == 0)
{
// We have a failure caused only by line ending or whitespace differences. Make sure
// to use a non-zero value so it can be distinguished from exact matches.
changeCount = 1;
}
// Apply a multiplier to the content distance to account for its increased importance
// over encoding and checksum algorithm changes.
var priority = 3;
return priority * changeCount / (double)diff.Lines.Count;
},
static (expected, actual, exactOnly) =>
{
return actual.content.Encoding == expected.content.Encoding ? 0.0 : 1.0;
},
static (expected, actual, exactOnly) =>
{
return actual.content.ChecksumAlgorithm == expected.content.ChecksumAlgorithm ? 0.0 : 1.0;
},
(expected, actual, exactOnly) =>
{
var distance = 0.0;
var (fileName, folders) = getNameAndFolders(defaultFilePathPrefix, expected.filename);
if (fileName != actual.document.Name)
{
distance += 1.0;
}
if (!folders.SequenceEqual(actual.document.Folders))
{
distance += 1.0;
}
return distance;
}),
matchTimeout);
}
else
{
// Matching with an empty set of matching functions always takes the 1:1 alignment without reordering
matches = WeightedMatch.Match(
expectedDocuments,
actualDocuments,
ImmutableArray<Func<(string filename, SourceText content), (Document document, SourceText content), bool, double>>.Empty,
matchTimeout);
}
// Use EqualOrDiff to verify the actual and expected filenames (and total collection length) in a convenient manner
verifier.EqualOrDiff(
string.Join(Environment.NewLine, matches.Select(match => match.TryGetExpected(out var expected) ? expected.filename : string.Empty)),
string.Join(Environment.NewLine, matches.Select(match => match.TryGetActual(out var actual) ? actual.document.FilePath : string.Empty)),
$"Expected source file list to match");
// Follow by verifying each property of interest
foreach (var result in matches)
{
if (!result.TryGetExpected(out var expected)
|| !result.TryGetActual(out var actual))
{
throw new InvalidOperationException("Unexpected state: should have failed during the previous assertion.");
}
verifier.EqualOrDiff(expected.content.ToString(), actual.content.ToString(), $"content of '{expected.filename}' did not match. Diff shown with expected as baseline:");
verifier.Equal(expected.content.Encoding, actual.content.Encoding, $"encoding of '{expected.filename}' was expected to be '{expected.content.Encoding?.WebName}' but was '{actual.content.Encoding?.WebName}'");
verifier.Equal(expected.content.ChecksumAlgorithm, actual.content.ChecksumAlgorithm, $"checksum algorithm of '{expected.filename}' was expected to be '{expected.content.ChecksumAlgorithm}' but was '{actual.content.ChecksumAlgorithm}'");
// Source-generated sources are implicitly in a subtree, so they have a different folders calculation.
var (fileName, folders) = getNameAndFolders(defaultFilePathPrefix, expected.filename);
verifier.Equal(fileName, actual.document.Name, $"file name was expected to be '{fileName}' but was '{actual.document.Name}'");
verifier.SequenceEqual(folders, actual.document.Folders, message: $"folders was expected to be '{string.Join("/", folders)}' but was '{string.Join("/", actual.document.Folders)}'");
}
}
}
/// <summary>
/// General method that gets a collection of actual <see cref="Diagnostic"/>s found in the source after the
/// analyzer is run, then verifies each of them.
@ -921,10 +1111,11 @@ namespace Microsoft.CodeAnalysis.Testing
var diagnostics = ImmutableArray.CreateBuilder<(Project project, Diagnostic diagnostic)>();
foreach (var project in solution.Projects)
{
var compilation = await GetProjectCompilationAsync(project, verifier, cancellationToken).ConfigureAwait(false);
var (compilation, generatorDiagnostics) = await GetProjectCompilationAsync(project, verifier, cancellationToken).ConfigureAwait(false);
var compilationWithAnalyzers = CreateCompilationWithAnalyzers(compilation, analyzers, GetAnalyzerOptions(project), cancellationToken);
var allDiagnostics = await compilationWithAnalyzers.GetAllDiagnosticsAsync().ConfigureAwait(false);
diagnostics.AddRange(generatorDiagnostics.Select(diagnostic => (project, diagnostic)));
diagnostics.AddRange(allDiagnostics.Where(diagnostic => !IsCompilerDiagnostic(diagnostic) || IsCompilerDiagnosticIncluded(diagnostic, compilerDiagnostics)).Select(diagnostic => (project, diagnostic)));
}
@ -933,13 +1124,13 @@ namespace Microsoft.CodeAnalysis.Testing
return results;
}
protected virtual async Task<Compilation> GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
protected virtual async Task<(Compilation compilation, ImmutableArray<Diagnostic> generatorDiagnostics)> GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
{
var (finalProject, _) = await ApplySourceGeneratorAsync(GetSourceGenerators().ToImmutableArray(), project, verifier, cancellationToken).ConfigureAwait(false);
return (await finalProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!;
var (finalProject, generatorDiagnostics) = await ApplySourceGeneratorAsync(GetSourceGenerators().ToImmutableArray(), project, verifier, cancellationToken).ConfigureAwait(false);
return ((await finalProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!, generatorDiagnostics);
}
private async Task<(Project project, ImmutableArray<Diagnostic> diagnostics)> ApplySourceGeneratorAsync(ImmutableArray<Type> sourceGeneratorTypes, Project project, IVerifier verifier, CancellationToken cancellationToken)
private protected async Task<(Project project, ImmutableArray<Diagnostic> diagnostics)> ApplySourceGeneratorAsync(ImmutableArray<Type> sourceGeneratorTypes, Project project, IVerifier verifier, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
verifier.True(compilation is { });
@ -963,7 +1154,7 @@ namespace Microsoft.CodeAnalysis.Testing
return (updatedProject, result.Diagnostics);
}
private static (string fileName, IEnumerable<string> folders) GetNameAndFoldersFromSourceGeneratedFilePath(string filePath)
private protected static (string fileName, IEnumerable<string> folders) GetNameAndFoldersFromSourceGeneratedFilePath(string filePath)
{
// Source-generated files are always implicitly subpaths under the project root path.
var folders = Path.GetDirectoryName(filePath)!.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

Просмотреть файл

@ -25,6 +25,7 @@ Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.TestBehaviors.set -> void
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.TestCode.set -> void
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.TestState.get -> Microsoft.CodeAnalysis.Testing.SolutionState
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.VerifyDiagnosticsAsync(Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState primaryProject, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState> additionalProjects, Microsoft.CodeAnalysis.Testing.DiagnosticResult[] expected, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.VerifySourceGeneratorAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic>>
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.XmlReferences.get -> System.Collections.Generic.Dictionary<string, string>
Microsoft.CodeAnalysis.Testing.AnalyzerVerifier<TAnalyzer, TTest, TVerifier>
Microsoft.CodeAnalysis.Testing.AnalyzerVerifier<TAnalyzer, TTest, TVerifier>.AnalyzerVerifier() -> void
@ -349,7 +350,7 @@ virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.DefaultFilePathPr
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.DefaultTestProjectName.get -> string
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetAnalyzerOptions(Microsoft.CodeAnalysis.Project project) -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetDefaultDiagnostic(Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer[] analyzers) -> Microsoft.CodeAnalysis.DiagnosticDescriptor
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetProjectCompilationAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Compilation>
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetProjectCompilationAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<(Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic> generatorDiagnostics)>
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetSourceGenerators() -> System.Collections.Generic.IEnumerable<System.Type>
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.IsCompilerDiagnosticIncluded(Microsoft.CodeAnalysis.Diagnostic diagnostic, Microsoft.CodeAnalysis.Testing.CompilerDiagnostics compilerDiagnostics) -> bool
virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.RunImplAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task

Просмотреть файл

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Testing;
namespace Microsoft.CodeAnalysis.CSharp.Testing
@ -23,15 +22,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Testing
public override string Language => LanguageNames.CSharp;
protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray<ISourceGenerator> sourceGenerators)
{
return CSharpGeneratorDriver.Create(
sourceGenerators,
project.AnalyzerOptions.AdditionalFiles,
(CSharpParseOptions)project.ParseOptions!,
project.AnalyzerOptions.AnalyzerConfigOptionsProvider);
}
protected override CompilationOptions CreateCompilationOptions()
=> new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);

Просмотреть файл

@ -3,7 +3,6 @@ Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorVerifier<TSourceGenerator, TVerifier>
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorVerifier<TSourceGenerator, TVerifier>.CSharpSourceGeneratorVerifier() -> void
override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions
override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.CreateGeneratorDriver(Microsoft.CodeAnalysis.Project project, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator> sourceGenerators) -> Microsoft.CodeAnalysis.GeneratorDriver
override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions
override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.DefaultFileExt.get -> string
override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.GetSourceGenerators() -> System.Collections.Generic.IEnumerable<System.Type>

Просмотреть файл

@ -4,10 +4,7 @@ Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.Execute(Microsoft.Co
Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.Initialize(Microsoft.CodeAnalysis.GeneratorInitializationContext context) -> void
Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>
Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>.SourceGeneratorTest() -> void
Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>.VerifySourceGeneratorAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic>>
Microsoft.CodeAnalysis.Testing.SourceGeneratorVerifier<TSourceGenerator, TTest, TVerifier>
Microsoft.CodeAnalysis.Testing.SourceGeneratorVerifier<TSourceGenerator, TTest, TVerifier>.SourceGeneratorVerifier() -> void
abstract Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>.CreateGeneratorDriver(Microsoft.CodeAnalysis.Project project, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator> sourceGenerators) -> Microsoft.CodeAnalysis.GeneratorDriver
override Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>.GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer>
override Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>.RunImplAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
override abstract Microsoft.CodeAnalysis.Testing.SourceGeneratorTest<TVerifier>.GetSourceGenerators() -> System.Collections.Generic.IEnumerable<System.Type>

Просмотреть файл

@ -4,20 +4,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DiffPlex;
using DiffPlex.Chunkers;
using DiffPlex.DiffBuilder;
using DiffPlex.DiffBuilder.Model;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Testing.Model;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Testing
{
@ -32,279 +20,5 @@ namespace Microsoft.CodeAnalysis.Testing
/// </summary>
/// <returns>The <see cref="ISourceGenerator"/> to be used.</returns>
protected override abstract IEnumerable<Type> GetSourceGenerators();
protected abstract GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray<ISourceGenerator> sourceGenerators);
protected override async Task RunImplAsync(CancellationToken cancellationToken)
{
var analyzers = GetDiagnosticAnalyzers().ToArray();
var defaultDiagnostic = GetDefaultDiagnostic(analyzers);
var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray();
var fixableDiagnostics = ImmutableArray<string>.Empty;
var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath);
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);
}
/// <summary>
/// Called to test a C# source generator when applied on the input source as a string.
/// </summary>
/// <param name="testState">The effective input test state.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected async Task<ImmutableArray<Diagnostic>> VerifySourceGeneratorAsync(SolutionState testState, IVerifier verifier, CancellationToken cancellationToken)
{
return await VerifySourceGeneratorAsync(Language, InstantiateSourceGenerators(GetSourceGenerators().ToImmutableArray()), testState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken);
}
private ImmutableArray<ISourceGenerator> InstantiateSourceGenerators(ImmutableArray<Type> sourceGenerators)
{
return ImmutableArray.CreateRange(
sourceGenerators,
sourceGeneratorType =>
{
var instance = Activator.CreateInstance(sourceGeneratorType);
if (instance is ISourceGenerator generator)
{
return generator;
}
var iincrementalGeneratorType = typeof(ISourceGenerator).Assembly.GetType("Microsoft.CodeAnalysis.IIncrementalGenerator");
var asGeneratorMethod = (from method in typeof(ISourceGenerator).Assembly.GetType("Microsoft.CodeAnalysis.GeneratorExtensions")!.GetMethods()
where method is { Name: "AsSourceGenerator", IsStatic: true, IsPublic: true }
let parameterTypes = method.GetParameters().Select(parameter => parameter.ParameterType).ToArray()
where parameterTypes.SequenceEqual(new[] { iincrementalGeneratorType })
select method).SingleOrDefault();
return (ISourceGenerator)asGeneratorMethod.Invoke(null, new[] { instance })!;
});
}
private async Task<ImmutableArray<Diagnostic>> VerifySourceGeneratorAsync(
string language,
ImmutableArray<ISourceGenerator> sourceGenerators,
SolutionState testState,
Func<ImmutableArray<ISourceGenerator>, Project, IVerifier, CancellationToken, Task<(Project project, ImmutableArray<Diagnostic> diagnostics)>> getFixedProject,
IVerifier verifier,
CancellationToken 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<Diagnostic> diagnostics;
(project, diagnostics) = await getFixedProject(sourceGenerators, project, verifier, cancellationToken).ConfigureAwait(false);
// After applying the source generator, compare the resulting string to the inputted one
if (!TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck))
{
var numOriginalSources = testState.Sources.Count;
var updatedOriginalDocuments = project.Documents.Take(numOriginalSources).ToArray();
var generatedDocuments = project.Documents.Skip(numOriginalSources).ToArray();
// Verify no changes occurred to the original documents
var updatedOriginalDocumentsWithTextBuilder = ImmutableArray.CreateBuilder<(Document document, SourceText content)>();
foreach (var updatedOriginalDocument in updatedOriginalDocuments)
{
updatedOriginalDocumentsWithTextBuilder.Add((updatedOriginalDocument, await GetSourceTextFromDocumentAsync(updatedOriginalDocument, CancellationToken.None).ConfigureAwait(false)));
}
VerifyDocuments(
verifier.PushContext("Original files after running source generators"),
updatedOriginalDocumentsWithTextBuilder.ToImmutable(),
testState.Sources.ToImmutableArray(),
allowReordering: false,
DefaultFilePathPrefix,
GetNameAndFoldersFromPath,
MatchDiagnosticsTimeout);
// Verify the source generated documents match expectations
var generatedDocumentsWithTextBuilder = ImmutableArray.CreateBuilder<(Document document, SourceText content)>();
foreach (var generatedDocument in generatedDocuments)
{
generatedDocumentsWithTextBuilder.Add((generatedDocument, await GetSourceTextFromDocumentAsync(generatedDocument, CancellationToken.None).ConfigureAwait(false)));
}
VerifyDocuments(
verifier.PushContext("Verifying source generated files"),
generatedDocumentsWithTextBuilder.ToImmutable(),
testState.GeneratedSources.ToImmutableArray(),
allowReordering: true,
DefaultFilePathPrefix,
static (_, path) => GetNameAndFoldersFromSourceGeneratedFilePath(path),
MatchDiagnosticsTimeout);
}
return diagnostics;
static void VerifyDocuments(
IVerifier verifier,
ImmutableArray<(Document document, SourceText content)> actualDocuments,
ImmutableArray<(string filename, SourceText content)> expectedDocuments,
bool allowReordering,
string defaultFilePathPrefix,
Func<string, string, (string fileName, IEnumerable<string> folders)> getNameAndFolders,
TimeSpan matchTimeout)
{
ImmutableArray<WeightedMatch.Result<(string filename, SourceText content), (Document document, SourceText content)>> matches;
if (allowReordering)
{
matches = WeightedMatch.Match(
expectedDocuments,
actualDocuments,
ImmutableArray.Create<Func<(string filename, SourceText content), (Document document, SourceText content), bool, double>>(
static (expected, actual, exactOnly) =>
{
if (actual.content.ToString() == expected.content.ToString())
{
return 0.0;
}
if (exactOnly)
{
// Avoid expensive diff calculation when exact match was requested.
return 1.0;
}
var diffBuilder = new InlineDiffBuilder(new Differ());
var diff = diffBuilder.BuildDiffModel(expected.content.ToString(), actual.content.ToString(), ignoreWhitespace: true, ignoreCase: false, new LineChunker());
var changeCount = diff.Lines.Count(static line => line.Type is ChangeType.Inserted or ChangeType.Deleted);
if (changeCount == 0)
{
// We have a failure caused only by line ending or whitespace differences. Make sure
// to use a non-zero value so it can be distinguished from exact matches.
changeCount = 1;
}
// Apply a multiplier to the content distance to account for its increased importance
// over encoding and checksum algorithm changes.
var priority = 3;
return priority * changeCount / (double)diff.Lines.Count;
},
static (expected, actual, exactOnly) =>
{
return actual.content.Encoding == expected.content.Encoding ? 0.0 : 1.0;
},
static (expected, actual, exactOnly) =>
{
return actual.content.ChecksumAlgorithm == expected.content.ChecksumAlgorithm ? 0.0 : 1.0;
},
(expected, actual, exactOnly) =>
{
var distance = 0.0;
var (fileName, folders) = getNameAndFolders(defaultFilePathPrefix, expected.filename);
if (fileName != actual.document.Name)
{
distance += 1.0;
}
if (!folders.SequenceEqual(actual.document.Folders))
{
distance += 1.0;
}
return distance;
}),
matchTimeout);
}
else
{
// Matching with an empty set of matching functions always takes the 1:1 alignment without reordering
matches = WeightedMatch.Match(
expectedDocuments,
actualDocuments,
ImmutableArray<Func<(string filename, SourceText content), (Document document, SourceText content), bool, double>>.Empty,
matchTimeout);
}
// Use EqualOrDiff to verify the actual and expected filenames (and total collection length) in a convenient manner
verifier.EqualOrDiff(
string.Join(Environment.NewLine, matches.Select(match => match.TryGetExpected(out var expected) ? expected.filename : string.Empty)),
string.Join(Environment.NewLine, matches.Select(match => match.TryGetActual(out var actual) ? actual.document.FilePath : string.Empty)),
$"Expected source file list to match");
// Follow by verifying each property of interest
foreach (var result in matches)
{
if (!result.TryGetExpected(out var expected)
|| !result.TryGetActual(out var actual))
{
throw new InvalidOperationException("Unexpected state: should have failed during the previous assertion.");
}
verifier.EqualOrDiff(expected.content.ToString(), actual.content.ToString(), $"content of '{expected.filename}' did not match. Diff shown with expected as baseline:");
verifier.Equal(expected.content.Encoding, actual.content.Encoding, $"encoding of '{expected.filename}' was expected to be '{expected.content.Encoding?.WebName}' but was '{actual.content.Encoding?.WebName}'");
verifier.Equal(expected.content.ChecksumAlgorithm, actual.content.ChecksumAlgorithm, $"checksum algorithm of '{expected.filename}' was expected to be '{expected.content.ChecksumAlgorithm}' but was '{actual.content.ChecksumAlgorithm}'");
// Source-generated sources are implicitly in a subtree, so they have a different folders calculation.
var (fileName, folders) = getNameAndFolders(defaultFilePathPrefix, expected.filename);
verifier.Equal(fileName, actual.document.Name, $"file name was expected to be '{fileName}' but was '{actual.document.Name}'");
verifier.SequenceEqual(folders, actual.document.Folders, message: $"folders was expected to be '{string.Join("/", folders)}' but was '{string.Join("/", actual.document.Folders)}'");
}
}
}
private static (string fileName, IEnumerable<string> folders) GetNameAndFoldersFromSourceGeneratedFilePath(string filePath)
{
// Source-generated files are always implicitly subpaths under the project root path.
var folders = Path.GetDirectoryName(filePath)!.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var fileName = Path.GetFileName(filePath);
return (fileName, folders);
}
private async Task<(Project project, ImmutableArray<Diagnostic> diagnostics)> ApplySourceGeneratorAsync(ImmutableArray<ISourceGenerator> sourceGenerators, Project project, IVerifier verifier, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
verifier.True(compilation is { });
var driver = CreateGeneratorDriver(project, sourceGenerators).RunGenerators(compilation, cancellationToken);
var result = driver.GetRunResult();
var updatedProject = project;
foreach (var tree in result.GeneratedTrees)
{
var (fileName, folders) = GetNameAndFoldersFromSourceGeneratedFilePath(tree.FilePath);
updatedProject = updatedProject.AddDocument(fileName, await tree.GetTextAsync(cancellationToken).ConfigureAwait(false), folders: folders, filePath: tree.FilePath).Project;
}
return (updatedProject, result.Diagnostics);
}
/// <summary>
/// Get the existing compiler diagnostics on the input document.
/// </summary>
/// <param name="project">The <see cref="Project"/> to run the compiler diagnostic analyzers on.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>The compiler diagnostics that were found in the code.</returns>
private static async Task<ImmutableArray<Diagnostic>> GetCompilerDiagnosticsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
{
var allDiagnostics = ImmutableArray.Create<Diagnostic>();
foreach (var document in project.Documents)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
verifier.True(semanticModel is { });
allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken));
}
return allDiagnostics;
}
/// <summary>
/// Given a document, turn it into a string based on the syntax root.
/// </summary>
/// <param name="document">The <see cref="Document"/> to be converted to a string.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="SourceText"/> containing the syntax of the <see cref="Document"/> after formatting.</returns>
private static async Task<SourceText> GetSourceTextFromDocumentAsync(Document document, CancellationToken cancellationToken)
{
var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
return await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -3,7 +3,6 @@ Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSo
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, TVerifier)
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, TVerifier).New() -> Void
Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions
Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).CreateGeneratorDriver(project As Microsoft.CodeAnalysis.Project, sourceGenerators As System.Collections.Immutable.ImmutableArray(Of Microsoft.CodeAnalysis.ISourceGenerator)) -> Microsoft.CodeAnalysis.GeneratorDriver
Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions
Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).DefaultFileExt() -> String
Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).GetSourceGenerators() -> System.Collections.Generic.IEnumerable(Of System.Type)

Просмотреть файл

@ -1,6 +1,5 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.Testing
Public Class VisualBasicSourceGeneratorTest(Of TSourceGenerator As {ISourceGenerator, New}, TVerifier As {IVerifier, New})
@ -21,14 +20,6 @@ Public Class VisualBasicSourceGeneratorTest(Of TSourceGenerator As {ISourceGener
End Get
End Property
Protected Overrides Function CreateGeneratorDriver(project As Project, sourceGenerators As ImmutableArray(Of ISourceGenerator)) As GeneratorDriver
Return VisualBasicGeneratorDriver.Create(
sourceGenerators,
project.AnalyzerOptions.AdditionalFiles,
CType(project.ParseOptions, VisualBasicParseOptions),
project.AnalyzerOptions.AnalyzerConfigOptionsProvider)
End Function
Protected Overrides Function CreateCompilationOptions() As CompilationOptions
Return New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
End Function

Просмотреть файл

@ -2,7 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing.TestAnalyzers;
using Microsoft.CodeAnalysis.Testing.TestGenerators;
using Microsoft.CodeAnalysis.Text;
using Xunit;
using CSharpTest = Microsoft.CodeAnalysis.Testing.TestAnalyzers.CSharpAnalyzerWithSourceGeneratorTest<
Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer,
@ -23,7 +28,7 @@ namespace Microsoft.CodeAnalysis.Testing
TestState =
{
Sources = { "class MainClass : TestClass { }" },
GeneratedSources = { (typeof(GenerateSourceFile), "Generated.g.cs", "content not yet validated") },
GeneratedSources = { (typeof(GenerateSourceFile), "Generated.g.cs", "class TestClass { }") },
},
}.RunAsync();
}
@ -36,7 +41,312 @@ namespace Microsoft.CodeAnalysis.Testing
TestState =
{
Sources = { "Class MainClass : Inherits TestClass : End Class" },
GeneratedSources = { (typeof(GenerateSourceFile), "Generated.g.vb", "content not yet validated") },
GeneratedSources = { (typeof(GenerateSourceFile), "Generated.g.vb", "Class TestClass : End Class") },
},
}.RunAsync();
}
[Fact]
public async Task AddSimpleFile()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFile>
{
TestState =
{
Sources =
{
@"// Comment",
},
GeneratedSources =
{
("Microsoft.CodeAnalysis.Testing.Utilities\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
},
}.RunAsync();
}
[Fact]
public async Task MultipleFilesAllowEitherOrder()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddTwoEmptyFiles>
{
TestState =
{
Sources =
{
@"// Comment",
},
GeneratedSources =
{
(typeof(AddTwoEmptyFiles), "EmptyGeneratedFile1.cs", SourceText.From(string.Empty, Encoding.UTF8)),
(typeof(AddTwoEmptyFiles), "EmptyGeneratedFile2.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
},
}.RunAsync();
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddTwoEmptyFiles>
{
TestState =
{
Sources =
{
@"// Comment",
},
GeneratedSources =
{
(typeof(AddTwoEmptyFiles), "EmptyGeneratedFile2.cs", SourceText.From(string.Empty, Encoding.UTF8)),
(typeof(AddTwoEmptyFiles), "EmptyGeneratedFile1.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
},
}.RunAsync();
}
[Fact]
public async Task AddSimpleFileByGeneratorType()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFile>
{
TestState =
{
Sources =
{
@"// Comment",
},
GeneratedSources =
{
(typeof(AddEmptyFile), "EmptyGeneratedFile.cs", string.Empty),
},
},
}.RunAsync();
}
[Fact]
public async Task AddSimpleFileByGeneratorTypeWithEncoding()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFile>
{
TestState =
{
Sources =
{
@"// Comment",
},
GeneratedSources =
{
(typeof(AddEmptyFile), "EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
},
}.RunAsync();
}
[Fact]
public async Task AddSimpleFileToEmptyProject()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFile>
{
TestState =
{
Sources =
{
},
GeneratedSources =
{
(typeof(AddEmptyFile), "EmptyGeneratedFile.cs", string.Empty),
},
},
}.RunAsync();
}
[Fact]
public async Task AddSimpleFileWithWrongExpectedEncoding()
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFile>
{
TestState =
{
GeneratedSources =
{
(typeof(AddEmptyFile), "EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.Unicode)),
},
},
}.RunAsync();
});
var expectedMessage = @"Context: Source generator application
Context: Verifying source generated files
encoding of 'Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\EmptyGeneratedFile.cs' was expected to be 'utf-16' but was 'utf-8'";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
}
[Fact]
public async Task AddSimpleFileVerifiesCompilerDiagnostics_CSharp()
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddFileWithCompileError>
{
TestState =
{
Sources =
{
@"class A {",
},
GeneratedSources =
{
(typeof(AddFileWithCompileError), "ErrorGeneratedFile.cs", @"class C {"),
},
},
}.RunAsync();
});
var expectedMessage = @"Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
Diagnostics:
// /0/Test0.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(1, 10, 1, 10),
// Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(""Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs"", 1, 10, 1, 10),
";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
}
[Fact]
public async Task AddSimpleFileVerifiesCompilerDiagnosticsEvenWhenSourceGeneratorOutputsSkipped_CSharp()
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddFileWithCompileError>
{
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
TestState =
{
Sources =
{
@"class A {",
},
},
}.RunAsync();
});
var expectedMessage = @"Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
Diagnostics:
// /0/Test0.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(1, 10, 1, 10),
// Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(""Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs"", 1, 10, 1, 10),
";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
}
[Fact]
public async Task AddSimpleFileVerifiesCompilerDiagnostics_VisualBasic()
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await new VisualBasicAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddFileWithCompileError>
{
TestState =
{
Sources =
{
"Class A",
},
GeneratedSources =
{
(typeof(AddFileWithCompileError), "ErrorGeneratedFile.vb", "Class C"),
},
},
}.RunAsync();
});
var expectedMessage = @"Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
Diagnostics:
// /0/Test0.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(1, 1, 1, 8),
// Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb"", 1, 1, 1, 8),
";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
}
[Fact]
public async Task AddSimpleFileVerifiesCompilerDiagnosticsEvenWhenSourceGeneratorOutputsSkipped_VisualBasic()
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await new VisualBasicAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddFileWithCompileError>
{
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
TestState =
{
Sources =
{
"Class A",
},
},
}.RunAsync();
});
var expectedMessage = @"Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
Diagnostics:
// /0/Test0.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(1, 1, 1, 8),
// Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb"", 1, 1, 1, 8),
";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
}
[Fact]
public async Task AddSimpleFileWithDiagnostic()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFileWithDiagnostic>
{
TestState =
{
Sources =
{
@"{|#0:|}// Comment",
},
GeneratedSources =
{
("Microsoft.CodeAnalysis.Testing.Utilities\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFileWithDiagnostic\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
ExpectedDiagnostics =
{
// /0/Test0.cs(1,1): warning SG0001: Message
new DiagnosticResult(AddEmptyFileWithDiagnostic.Descriptor).WithLocation(0),
},
},
}.RunAsync();
}
[Fact]
public async Task AddImplicitSimpleFileWithDiagnostic()
{
await new CSharpAnalyzerWithSourceGeneratorTest<EmptyDiagnosticAnalyzer, AddEmptyFileWithDiagnostic>
{
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
TestState =
{
Sources =
{
@"{|#0:|}// Comment",
},
ExpectedDiagnostics =
{
// /0/Test0.cs(1,1): warning SG0001: Message
new DiagnosticResult(AddEmptyFileWithDiagnostic.Descriptor).WithLocation(0),
},
},
}.RunAsync();
}

Просмотреть файл

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
@ -30,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Testing
},
GeneratedSources =
{
("Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
("Microsoft.CodeAnalysis.Testing.Utilities\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
},
}.RunAsync();
@ -147,7 +146,7 @@ namespace Microsoft.CodeAnalysis.Testing
var expectedMessage = @"Context: Source generator application
Context: Verifying source generated files
encoding of 'Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\EmptyGeneratedFile.cs' was expected to be 'utf-16' but was 'utf-8'";
encoding of 'Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFile\EmptyGeneratedFile.cs' was expected to be 'utf-16' but was 'utf-8'";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
}
@ -172,14 +171,13 @@ encoding of 'Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft
}.RunAsync();
});
var expectedMessage = @"Context: Diagnostics of test state
Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
var expectedMessage = @"Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
Diagnostics:
// /0/Test0.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(1, 10, 1, 10),
// Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(""Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs"", 1, 10, 1, 10),
// Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs(1,10): error CS1513: } expected
DiagnosticResult.CompilerError(""CS1513"").WithSpan(""Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.cs"", 1, 10, 1, 10),
";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
@ -206,14 +204,13 @@ DiagnosticResult.CompilerError(""CS1513"").WithSpan(""Microsoft.CodeAnalysis.Sou
}.RunAsync();
});
var expectedMessage = @"Context: Diagnostics of test state
Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
var expectedMessage = @"Mismatch between number of diagnostics returned, expected ""0"" actual ""2""
Diagnostics:
// /0/Test0.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(1, 1, 1, 8),
// Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb"", 1, 1, 1, 8),
// Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb(1) : error BC30481: 'Class' statement must end with a matching 'End Class'.
DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.TestGenerators.AddFileWithCompileError\ErrorGeneratedFile.vb"", 1, 1, 1, 8),
";
new DefaultVerifier().EqualOrDiff(expectedMessage, exception.Message);
@ -232,7 +229,7 @@ DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.So
},
GeneratedSources =
{
("Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFileWithDiagnostic\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
("Microsoft.CodeAnalysis.Testing.Utilities\\Microsoft.CodeAnalysis.Testing.TestGenerators.AddEmptyFileWithDiagnostic\\EmptyGeneratedFile.cs", SourceText.From(string.Empty, Encoding.UTF8)),
},
ExpectedDiagnostics =
{
@ -271,15 +268,6 @@ DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.So
protected override string DefaultFileExt => "cs";
protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray<ISourceGenerator> sourceGenerators)
{
return CSharpGeneratorDriver.Create(
sourceGenerators,
project.AnalyzerOptions.AdditionalFiles,
(CSharpParseOptions)project.ParseOptions!,
project.AnalyzerOptions.AnalyzerConfigOptionsProvider);
}
protected override CompilationOptions CreateCompilationOptions()
{
return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
@ -303,15 +291,6 @@ DiagnosticResult.CompilerError(""BC30481"").WithSpan(""Microsoft.CodeAnalysis.So
protected override string DefaultFileExt => "vb";
protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray<ISourceGenerator> sourceGenerators)
{
return VisualBasicGeneratorDriver.Create(
sourceGenerators,
project.AnalyzerOptions.AdditionalFiles,
(VisualBasicParseOptions)project.ParseOptions!,
project.AnalyzerOptions.AnalyzerConfigOptionsProvider);
}
protected override CompilationOptions CreateCompilationOptions()
{
return new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary);