DiagnosticsSquiggleTaggerProviderTests.cs 13.2 KB
Newer Older
H
heejaechang 已提交
1 2 3 4 5 6
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
7
using System.Threading;
J
Jared Parsons 已提交
8
using System.Threading.Tasks;
9
using Microsoft.CodeAnalysis.Common;
H
heejaechang 已提交
10 11 12
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics;
13
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
H
heejaechang 已提交
14 15
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Shared.TestHooks;
16
using Microsoft.CodeAnalysis.Test.Utilities;
H
heejaechang 已提交
17
using Microsoft.CodeAnalysis.Text;
C
Cyrus Najmabadi 已提交
18
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
H
heejaechang 已提交
19
using Microsoft.VisualStudio.Text;
C
Cyrus Najmabadi 已提交
20
using Microsoft.VisualStudio.Text.Tagging;
H
heejaechang 已提交
21 22 23 24 25 26
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
27
    [UseExportProvider]
28 29
    public class DiagnosticsSquiggleTaggerProviderTests
    {
J
Jinu Joseph 已提交
30
        [WpfFact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
J
Jared Parsons 已提交
31
        public async Task Test_TagSourceDiffer()
32
        {
33 34 35 36 37 38
            var analyzer = new Analyzer();
            var analyzerMap = new Dictionary<string, DiagnosticAnalyzer[]>
            {
                { LanguageNames.CSharp, new DiagnosticAnalyzer[] { analyzer } }
            };

C
Cyrus Najmabadi 已提交
39 40 41 42 43 44
            using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A { }", "class E { }" }, CSharpParseOptions.Default);
            using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider>(workspace, analyzerMap);
            var tagger = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
            using var disposable = tagger as IDisposable;
            // test first update
            await wrapper.WaitForTags();
H
heejaechang 已提交
45

C
Cyrus Najmabadi 已提交
46 47 48
            var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
            var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
            Assert.True(spans.First().Span.Contains(new Span(0, 1)));
C
Cyrus Najmabadi 已提交
49

C
Cyrus Najmabadi 已提交
50 51
            // test second update
            analyzer.ChangeSeverity();
H
heejaechang 已提交
52

C
Cyrus Najmabadi 已提交
53 54 55
            var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
            var text = await document.GetTextAsync();
            workspace.TryApplyChanges(document.WithText(text.WithChanges(new TextChange(new TextSpan(text.Length - 1, 1), string.Empty))).Project.Solution);
H
heejaechang 已提交
56

C
Cyrus Najmabadi 已提交
57
            await wrapper.WaitForTags();
H
heejaechang 已提交
58

C
Cyrus Najmabadi 已提交
59 60 61
            snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
            spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
            Assert.True(spans.First().Span.Contains(new Span(0, 1)));
62 63
        }

J
Jinu Joseph 已提交
64
        [WpfFact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
J
Jared Parsons 已提交
65
        public async Task MultipleTaggersAndDispose()
66
        {
C
Cyrus Najmabadi 已提交
67 68 69 70 71
            using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A {" }, CSharpParseOptions.Default);
            using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider>(workspace);
            // Make two taggers.
            var tagger1 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
            var tagger2 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
72

C
Cyrus Najmabadi 已提交
73 74
            // But dispose the first one. We still want the second one to work.
            ((IDisposable)tagger1).Dispose();
75

C
Cyrus Najmabadi 已提交
76 77
            using var disposable = tagger2 as IDisposable;
            await wrapper.WaitForTags();
78

C
Cyrus Najmabadi 已提交
79 80 81
            var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
            var spans = tagger2.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
            Assert.False(spans.IsEmpty());
82 83
        }

J
Jared Parsons 已提交
84
        [WpfFact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
J
Jared Parsons 已提交
85
        public async Task TaggerProviderCreatedAfterInitialDiagnosticsReported()
86
        {
C
Cyrus Najmabadi 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
            using var workspace = TestWorkspace.CreateCSharp(new string[] { "class C {" }, CSharpParseOptions.Default);
            using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider>(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 tagger1 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
            using var disposable = tagger1 as IDisposable;
            await wrapper.WaitForTags();

            // We should have tags at this point.
            var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
            var spans = tagger1.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
            Assert.False(spans.IsEmpty());
H
heejaechang 已提交
104 105
        }

106 107 108 109
        [WpfFact]
        public async Task TestWithMockDiagnosticService_TaggerProviderCreatedBeforeInitialDiagnosticsReported()
        {
            // This test produces diagnostics from a mock service so that we are disconnected from
N
nnpcYvIVl 已提交
110
            // all the asynchrony of the actual async analyzer engine.  If this fails, then the 
111 112 113 114
            // 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.

C
Cyrus Najmabadi 已提交
115 116 117 118 119 120 121 122 123 124 125 126
            using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A { }" }, CSharpParseOptions.Default);
            using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider>(workspace);
            var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();

            var diagnosticService = new MockDiagnosticService(workspace);
            var provider = new DiagnosticsSquiggleTaggerProvider(
                workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
                diagnosticService, workspace.GetService<IForegroundNotificationService>(), listenerProvider);

            // Create the tagger before the first diagnostic event has been fired.
            var tagger = provider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());

N
nnpcYvIVl 已提交
127
            // Now product the first diagnostic and fire the events.
C
Cyrus Najmabadi 已提交
128 129 130 131 132 133 134 135 136 137 138 139
            var tree = await workspace.CurrentSolution.Projects.Single().Documents.Single().GetSyntaxTreeAsync();
            var span = TextSpan.FromBounds(0, 5);
            diagnosticService.CreateDiagnosticAndFireEvents(Location.Create(tree, span));

            using var disposable = tagger as IDisposable;
            await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateExpeditedWaitTask();
            await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).CreateExpeditedWaitTask();

            var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
            var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
            Assert.Equal(1, spans.Count);
            Assert.Equal(span.ToSpan(), spans[0].Span.Span);
140 141 142 143 144 145
        }

        [WpfFact]
        public async Task TestWithMockDiagnosticService_TaggerProviderCreatedAfterInitialDiagnosticsReported()
        {
            // This test produces diagnostics from a mock service so that we are disconnected from
N
nnpcYvIVl 已提交
146
            // all the asynchrony of the actual async analyzer engine.  If this fails, then the 
147 148 149 150
            // 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.

C
Cyrus Najmabadi 已提交
151 152 153 154 155 156 157 158 159
            using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A { }" }, CSharpParseOptions.Default);
            using var wrapper = new DiagnosticTaggerWrapper<DiagnosticsSquiggleTaggerProvider>(workspace);
            var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();

            var diagnosticService = new MockDiagnosticService(workspace);
            var provider = new DiagnosticsSquiggleTaggerProvider(
                workspace.ExportProvider.GetExportedValue<IThreadingContext>(),
                diagnosticService, workspace.GetService<IForegroundNotificationService>(), listenerProvider);

N
nnpcYvIVl 已提交
160
            // Create and fire the diagnostic events before the tagger is even made.
C
Cyrus Najmabadi 已提交
161 162 163 164 165 166 167 168 169 170 171 172 173
            var tree = await workspace.CurrentSolution.Projects.Single().Documents.Single().GetSyntaxTreeAsync();
            var span = TextSpan.FromBounds(0, 5);
            diagnosticService.CreateDiagnosticAndFireEvents(Location.Create(tree, span));

            var tagger = provider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
            using var disposable = tagger as IDisposable;
            await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateExpeditedWaitTask();
            await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).CreateExpeditedWaitTask();

            var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
            var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
            Assert.Equal(1, spans.Count);
            Assert.Equal(span.ToSpan(), spans[0].Span.Span);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        }

        private class MockDiagnosticService : IDiagnosticService
        {
            public const string DiagnosticId = "MockId";

            private readonly Workspace _workspace;
            private DiagnosticData _diagnostic;

            public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;

            public MockDiagnosticService(Workspace workspace)
            {
                _workspace = workspace;
            }

            public IEnumerable<DiagnosticData> GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
            {
                Assert.Equal(workspace, _workspace);
                Assert.Equal(projectId, GetProjectId());
                Assert.Equal(documentId, GetDocumentId());

                if (_diagnostic == null)
                {
                    yield break;
                }
                else
                {
                    yield return _diagnostic;
                }
            }

            public IEnumerable<UpdatedEventArgs> GetDiagnosticsUpdatedEventArgs(Workspace workspace, ProjectId projectId, DocumentId documentId, CancellationToken cancellationToken)
            {
                Assert.Equal(workspace, _workspace);
                Assert.Equal(projectId, GetProjectId());
                Assert.Equal(documentId, GetDocumentId());

                if (_diagnostic == null)
                {
                    yield break;
                }
                else
                {
                    yield return new UpdatedEventArgs(this, workspace, GetProjectId(), GetDocumentId());
                }
            }

            internal void CreateDiagnosticAndFireEvents(Location location)
            {
                var document = _workspace.CurrentSolution.Projects.Single().Documents.Single();
                _diagnostic = DiagnosticData.Create(document,
                    Diagnostic.Create(DiagnosticId, "MockCategory", "MockMessage", DiagnosticSeverity.Error, DiagnosticSeverity.Error, isEnabledByDefault: true, warningLevel: 0,
                    location: location));

                DiagnosticsUpdated?.Invoke(this, DiagnosticsUpdatedArgs.DiagnosticsCreated(
                    this, _workspace, _workspace.CurrentSolution,
                    GetProjectId(), GetDocumentId(),
                    ImmutableArray.Create(_diagnostic)));
            }

            private DocumentId GetDocumentId()
            {
                return _workspace.CurrentSolution.Projects.Single().Documents.Single().Id;
            }

            private ProjectId GetProjectId()
            {
                return _workspace.CurrentSolution.Projects.Single().Id;
            }
        }

H
heejaechang 已提交
246 247
        private class Analyzer : DiagnosticAnalyzer
        {
B
beep boop 已提交
248
            private DiagnosticDescriptor _rule = new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Error, true);
H
heejaechang 已提交
249 250 251 252 253

            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            {
                get
                {
B
beep boop 已提交
254
                    return ImmutableArray.Create(_rule);
H
heejaechang 已提交
255 256 257 258 259 260 261
                }
            }

            public override void Initialize(AnalysisContext context)
            {
                context.RegisterSyntaxTreeAction(c =>
                {
H
HeeJae Chang 已提交
262
                    c.ReportDiagnostic(Diagnostic.Create(_rule, Location.Create(c.Tree, new TextSpan(0, 1))));
H
heejaechang 已提交
263 264 265 266 267
                });
            }

            public void ChangeSeverity()
            {
B
beep boop 已提交
268
                _rule = new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Warning, true);
H
heejaechang 已提交
269 270 271
            }
        }
    }
J
Jared Parsons 已提交
272
}