Move source generator validation to AnalyzerTest
This commit is contained in:
Родитель
8d32189fb0
Коммит
d80065912c
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче