Verify output text before verifying tree shape

Fixes #924
This commit is contained in:
Sam Harwell 2022-02-10 16:16:15 -08:00
Родитель d1f6cbb7d0
Коммит 71e83fbfdd
4 изменённых файлов: 43 добавлений и 25 удалений

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

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -195,17 +196,18 @@ namespace Microsoft.CodeAnalysis.Testing
/// <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="Project"/> with the changes from the <see cref="CodeAction"/>.</returns>
protected async Task<Project> ApplyCodeActionAsync(Project project, CodeAction codeAction, IVerifier verifier, CancellationToken cancellationToken)
protected async Task<(Project updatedProject, ExceptionDispatchInfo? validationError)> ApplyCodeActionAsync(Project project, CodeAction codeAction, IVerifier verifier, CancellationToken cancellationToken)
{
var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
var solution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
var changedProject = solution.GetProject(project.Id);
ExceptionDispatchInfo? validationError = null;
if (changedProject != project)
{
project = await RecreateProjectDocumentsAsync(changedProject, verifier, cancellationToken).ConfigureAwait(false);
(project, validationError) = await RecreateProjectDocumentsAsync(changedProject, verifier, cancellationToken).ConfigureAwait(false);
}
return project;
return (project, validationError);
}
/// <summary>
@ -215,15 +217,16 @@ namespace Microsoft.CodeAnalysis.Testing
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>The updated <see cref="Project"/>.</returns>
private async Task<Project> RecreateProjectDocumentsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
private async Task<(Project updatedProject, ExceptionDispatchInfo? validationError)> RecreateProjectDocumentsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
{
ExceptionDispatchInfo? validationError = null;
foreach (var documentId in project.DocumentIds)
{
var document = project.GetDocument(documentId);
var initialTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false);
var recreatedTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
if (CodeActionValidationMode != CodeActionValidationMode.None)
if (CodeActionValidationMode != CodeActionValidationMode.None && validationError is null)
{
try
{
@ -236,23 +239,32 @@ namespace Microsoft.CodeAnalysis.Testing
await initialTree.GetRootAsync(cancellationToken).ConfigureAwait(false),
checkTrivia: CodeActionValidationMode == CodeActionValidationMode.Full);
}
catch
catch (Exception genericError)
{
// Try to revalidate the tree with a better message
var renderedInitialTree = TreeToString(await initialTree.GetRootAsync(cancellationToken).ConfigureAwait(false), CodeActionValidationMode);
var renderedRecreatedTree = TreeToString(await recreatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false), CodeActionValidationMode);
verifier.EqualOrDiff(renderedRecreatedTree, renderedInitialTree);
// This is not expected to be hit, but it will be hit if the validation failure occurred in a
// portion of the tree not captured by the rendered form from TreeToString.
throw;
try
{
verifier.EqualOrDiff(renderedRecreatedTree, renderedInitialTree);
}
catch (Exception specificError)
{
validationError = ExceptionDispatchInfo.Capture(specificError);
}
finally
{
// This is not expected to be hit, but it will be hit if the validation failure occurred in
// a portion of the tree not captured by the rendered form from TreeToString.
validationError ??= ExceptionDispatchInfo.Capture(genericError);
}
}
}
project = document.Project;
}
return project;
return (project, validationError);
}
private static async Task<Document> RecreateDocumentAsync(Document document, CancellationToken cancellationToken)

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

@ -28,7 +28,7 @@ Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.XmlReferences.get -> Syst
Microsoft.CodeAnalysis.Testing.AnalyzerVerifier<TAnalyzer, TTest, TVerifier>
Microsoft.CodeAnalysis.Testing.AnalyzerVerifier<TAnalyzer, TTest, TVerifier>.AnalyzerVerifier() -> void
Microsoft.CodeAnalysis.Testing.CodeActionTest<TVerifier>
Microsoft.CodeAnalysis.Testing.CodeActionTest<TVerifier>.ApplyCodeActionAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.CodeActions.CodeAction codeAction, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Project>
Microsoft.CodeAnalysis.Testing.CodeActionTest<TVerifier>.ApplyCodeActionAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.CodeActions.CodeAction codeAction, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<(Microsoft.CodeAnalysis.Project updatedProject, System.Runtime.ExceptionServices.ExceptionDispatchInfo validationError)>
Microsoft.CodeAnalysis.Testing.CodeActionTest<TVerifier>.CodeActionEquivalenceKey.get -> string
Microsoft.CodeAnalysis.Testing.CodeActionTest<TVerifier>.CodeActionEquivalenceKey.set -> void
Microsoft.CodeAnalysis.Testing.CodeActionTest<TVerifier>.CodeActionIndex.get -> int?

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

@ -535,6 +535,7 @@ namespace Microsoft.CodeAnalysis.Testing
var previousDiagnostics = ImmutableArray.Create<(Project project, Diagnostic diagnostic)>();
ExceptionDispatchInfo? firstValidationError = null;
var currentIteration = -1;
bool done;
do
@ -558,7 +559,7 @@ namespace Microsoft.CodeAnalysis.Testing
}
catch (Exception ex)
{
return (project, ExceptionDispatchInfo.Capture(ex));
return (project, firstValidationError ?? ExceptionDispatchInfo.Capture(ex));
}
previousDiagnostics = analyzerDiagnostics;
@ -600,7 +601,8 @@ namespace Microsoft.CodeAnalysis.Testing
anyActions = true;
var originalProjectId = project.Id;
var fixedProject = await ApplyCodeActionAsync(fixableDocument.Project, actionToApply, verifier, cancellationToken).ConfigureAwait(false);
var (fixedProject, currentError) = await ApplyCodeActionAsync(fixableDocument.Project, actionToApply, verifier, cancellationToken).ConfigureAwait(false);
firstValidationError ??= currentError;
if (fixedProject != fixableDocument.Project)
{
done = false;
@ -638,10 +640,10 @@ namespace Microsoft.CodeAnalysis.Testing
}
catch (Exception ex)
{
return (project, ExceptionDispatchInfo.Capture(ex));
return (project, firstValidationError ?? ExceptionDispatchInfo.Capture(ex));
}
return (project, null);
return (project, firstValidationError ?? null);
}
private Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> FixAllAnalyzerDiagnosticsInDocumentAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, ImmutableArray<CodeFixProvider> codeFixProviders, int? codeFixIndex, string? codeFixEquivalenceKey, Action<CodeAction, IVerifier>? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken)
@ -676,6 +678,7 @@ namespace Microsoft.CodeAnalysis.Testing
var previousDiagnostics = ImmutableArray.Create<(Project project, Diagnostic diagnostic)>();
ExceptionDispatchInfo? firstValidationError = null;
var currentIteration = -1;
bool done;
do
@ -699,7 +702,7 @@ namespace Microsoft.CodeAnalysis.Testing
}
catch (Exception ex)
{
return (project, ExceptionDispatchInfo.Capture(ex));
return (project, firstValidationError ?? ExceptionDispatchInfo.Capture(ex));
}
var fixableDiagnostics = analyzerDiagnostics
@ -772,7 +775,8 @@ namespace Microsoft.CodeAnalysis.Testing
}
var originalProjectId = project.Id;
var fixedProject = await ApplyCodeActionAsync(fixableDocument.Project, action, verifier, cancellationToken).ConfigureAwait(false);
var (fixedProject, currentError) = await ApplyCodeActionAsync(fixableDocument.Project, action, verifier, cancellationToken).ConfigureAwait(false);
firstValidationError ??= currentError;
if (fixedProject != fixableDocument.Project)
{
done = false;
@ -799,10 +803,10 @@ namespace Microsoft.CodeAnalysis.Testing
}
catch (Exception ex)
{
return (project, ExceptionDispatchInfo.Capture(ex));
return (project, firstValidationError ?? ExceptionDispatchInfo.Capture(ex));
}
return (project, null);
return (project, firstValidationError);
}
/// <summary>

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

@ -227,6 +227,7 @@ namespace Microsoft.CodeAnalysis.Testing
numberOfIterations = -numberOfIterations;
}
ExceptionDispatchInfo? firstValidationError = null;
var currentIteration = -1;
bool done;
do
@ -239,7 +240,7 @@ namespace Microsoft.CodeAnalysis.Testing
}
catch (Exception ex)
{
return (project, ExceptionDispatchInfo.Capture(ex));
return (project, firstValidationError ?? ExceptionDispatchInfo.Capture(ex));
}
done = true;
@ -262,7 +263,8 @@ namespace Microsoft.CodeAnalysis.Testing
anyActions = true;
var originalProjectId = project.Id;
var fixedProject = await ApplyCodeActionAsync(triggerDocument.Project, actionToApply, verifier, cancellationToken).ConfigureAwait(false);
var (fixedProject, currentError) = await ApplyCodeActionAsync(triggerDocument.Project, actionToApply, verifier, cancellationToken).ConfigureAwait(false);
firstValidationError ??= currentError;
if (fixedProject != triggerDocument.Project)
{
done = false;
@ -294,10 +296,10 @@ namespace Microsoft.CodeAnalysis.Testing
}
catch (Exception ex)
{
return (project, ExceptionDispatchInfo.Capture(ex));
return (project, firstValidationError ?? ExceptionDispatchInfo.Capture(ex));
}
return (project, null);
return (project, firstValidationError);
async Task<Location> GetTriggerLocationAsync()
{