|
// Licensed to the .NET Foundation under one or more agreements.
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.EditAndContinue.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Diagnostics), Trait(Traits.Feature, Traits.Features.Tagging)]
public class DiagnosticsSquiggleTaggerProviderTests
{
private static readonly TestComposition s_compositionWithMockDiagnosticService =
EditorTestCompositions.EditorFeatures
.AddExcludedPartTypes(typeof(IDiagnosticService), typeof(IDiagnosticAnalyzerService))
.AddParts(typeof(MockDiagnosticService), typeof(MockDiagnosticAnalyzerService));
[WpfTheory, CombinatorialData]
public async Task Test_TagSourceDiffer(bool pull)
{
var analyzer = new Analyzer();
var analyzerMap = new Dictionary<string, ImmutableArray<DiagnosticAnalyzer>>
{
{ LanguageNames.CSharp, ImmutableArray.Create<DiagnosticAnalyzer>(analyzer) }
};
using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A { }", "class E { }" }, parseOptions: CSharpParseOptions.Default);
workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptionsStorage.PullDiagnosticTagging, pull);
using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider, IErrorTag>(workspace, analyzerMap);
var firstDocument = workspace.Documents.First();
var tagger = wrapper.TaggerProvider.CreateTagger<IErrorTag>(firstDocument.GetTextBuffer());
using var disposable = tagger as IDisposable;
// test first update
await wrapper.WaitForTags();
var snapshot = firstDocument.GetTextBuffer().CurrentSnapshot;
var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
Assert.True(spans.First().Span.Contains(new Span(0, 1)));
// test second update
analyzer.ChangeSeverity();
var document = workspace.CurrentSolution.GetRequiredDocument(firstDocument.Id);
var text = await document.GetTextAsync();
workspace.TryApplyChanges(document.WithText(text.WithChanges(new TextChange(new TextSpan(text.Length - 1, 1), string.Empty))).Project.Solution);
await wrapper.WaitForTags();
snapshot = firstDocument.GetTextBuffer().CurrentSnapshot;
spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
Assert.True(spans.First().Span.Contains(new Span(0, 1)));
}
[WpfTheory, CombinatorialData]
public async Task MultipleTaggersAndDispose(bool pull)
{
using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A {" }, parseOptions: CSharpParseOptions.Default);
workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptionsStorage.PullDiagnosticTagging, pull);
using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider, IErrorTag>(workspace);
// Make two taggers.
var firstDocument = workspace.Documents.First();
var tagger1 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(firstDocument.GetTextBuffer());
var tagger2 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(firstDocument.GetTextBuffer());
// But dispose the first one. We still want the second one to work.
((IDisposable)tagger1).Dispose();
using var disposable = tagger2 as IDisposable;
await wrapper.WaitForTags();
var snapshot = firstDocument.GetTextBuffer().CurrentSnapshot;
var spans = tagger2.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
Assert.False(spans.IsEmpty());
}
[WpfTheory, CombinatorialData]
public async Task TaggerProviderCreatedAfterInitialDiagnosticsReported(bool pull)
{
using var workspace = TestWorkspace.CreateCSharp(new string[] { "class C {" }, parseOptions: CSharpParseOptions.Default);
workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptionsStorage.PullDiagnosticTagging, pull);
using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider, IErrorTag>(workspace, analyzerMap: null, createTaggerProvider: false);
// First, make sure all diagnostics have been reported.
await wrapper.WaitForTags();
// Now make the tagger.
var taggerProvider = wrapper.TaggerProvider;
// Make a taggers.
var firstDocument = workspace.Documents.First();
var tagger1 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(firstDocument.GetTextBuffer());
using var disposable = tagger1 as IDisposable;
await wrapper.WaitForTags();
// We should have tags at this point.
var snapshot = firstDocument.GetTextBuffer().CurrentSnapshot;
var spans = tagger1.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
Assert.False(spans.IsEmpty());
}
[WpfTheory]
[InlineData(DiagnosticKind.CompilerSyntax)]
[InlineData(DiagnosticKind.CompilerSemantic)]
[InlineData(DiagnosticKind.AnalyzerSyntax)]
[InlineData(DiagnosticKind.AnalyzerSemantic)]
internal async Task TestWithMockDiagnosticService_TaggerProviderCreatedBeforeInitialDiagnosticsReported(DiagnosticKind diagnosticKind)
{
// This test produces diagnostics from a mock service so that we are disconnected from
// all the asynchrony of the actual async analyzer engine. If this fails, then the
// issue is almost certainly in the DiagnosticsSquiggleTaggerProvider code. If this
// succeed, but other squiggle tests fail, then it is likely an issue with the
// diagnostics engine not actually reporting all diagnostics properly.
using var workspace = TestWorkspace.CreateCSharp(
new string[] { "class A { }" },
parseOptions: CSharpParseOptions.Default,
composition: s_compositionWithMockDiagnosticService);
var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();
var diagnosticService = Assert.IsType<MockDiagnosticService>(workspace.ExportProvider.GetExportedValue<IDiagnosticService>());
var analyzerService = Assert.IsType<MockDiagnosticAnalyzerService>(workspace.ExportProvider.GetExportedValue<IDiagnosticAnalyzerService>());
var provider = workspace.ExportProvider.GetExportedValues<ITaggerProvider>().OfType<DiagnosticsSquiggleTaggerProvider>().Single();
// Create the tagger before the first diagnostic event has been fired.
var firstDocument = workspace.Documents.First();
var tagger = provider.CreateTagger<IErrorTag>(firstDocument.GetTextBuffer());
Contract.ThrowIfNull(tagger);
// Now product the first diagnostic and fire the events.
var tree = await workspace.CurrentSolution.Projects.Single().Documents.Single().GetRequiredSyntaxTreeAsync(CancellationToken.None);
var span = TextSpan.FromBounds(0, 5);
diagnosticService.CreateDiagnosticAndFireEvents(workspace, analyzerService, Location.Create(tree, span), diagnosticKind);
using var disposable = tagger as IDisposable;
await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).ExpeditedWaitAsync();
await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).ExpeditedWaitAsync();
var snapshot = firstDocument.GetTextBuffer().CurrentSnapshot;
var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(1, spans.Count);
Assert.Equal(span.ToSpan(), spans[0].Span.Span);
}
[WpfTheory]
[InlineData(DiagnosticKind.CompilerSyntax)]
[InlineData(DiagnosticKind.CompilerSemantic)]
[InlineData(DiagnosticKind.AnalyzerSyntax)]
[InlineData(DiagnosticKind.AnalyzerSemantic)]
internal async Task TestWithMockDiagnosticService_TaggerProviderCreatedAfterInitialDiagnosticsReported(DiagnosticKind diagnosticKind)
{
// This test produces diagnostics from a mock service so that we are disconnected from
// all the asynchrony of the actual async analyzer engine. If this fails, then the
// issue is almost certainly in the DiagnosticsSquiggleTaggerProvider code. If this
// succeed, but other squiggle tests fail, then it is likely an issue with the
// diagnostics engine not actually reporting all diagnostics properly.
using var workspace = TestWorkspace.CreateCSharp(
new string[] { "class A { }" },
parseOptions: CSharpParseOptions.Default,
composition: s_compositionWithMockDiagnosticService);
var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();
var diagnosticService = Assert.IsType<MockDiagnosticService>(workspace.ExportProvider.GetExportedValue<IDiagnosticService>());
var analyzerService = Assert.IsType<MockDiagnosticAnalyzerService>(workspace.ExportProvider.GetExportedValue<IDiagnosticAnalyzerService>());
var provider = workspace.ExportProvider.GetExportedValues<ITaggerProvider>().OfType<DiagnosticsSquiggleTaggerProvider>().Single();
// Create and fire the diagnostic events before the tagger is even made.
var tree = await workspace.CurrentSolution.Projects.Single().Documents.Single().GetRequiredSyntaxTreeAsync(CancellationToken.None);
var span = TextSpan.FromBounds(0, 5);
diagnosticService.CreateDiagnosticAndFireEvents(workspace, analyzerService, Location.Create(tree, span), diagnosticKind);
var firstDocument = workspace.Documents.First();
var tagger = provider.CreateTagger<IErrorTag>(firstDocument.GetTextBuffer());
Contract.ThrowIfNull(tagger);
using var disposable = tagger as IDisposable;
await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).ExpeditedWaitAsync();
await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).ExpeditedWaitAsync();
var snapshot = firstDocument.GetTextBuffer().CurrentSnapshot;
var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(1, spans.Count);
Assert.Equal(span.ToSpan(), spans[0].Span.Span);
}
private class Analyzer : DiagnosticAnalyzer
{
private DiagnosticDescriptor _rule = new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Error, true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return ImmutableArray.Create(_rule);
}
}
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(c =>
{
c.ReportDiagnostic(Diagnostic.Create(_rule, Location.Create(c.Tree, new TextSpan(0, 1))));
});
}
public void ChangeSeverity()
=> _rule = new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Warning, true);
}
}
}
|