DiagnosticsSquiggleTaggerProviderTests.cs 19.1 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;
C
Cyrus Najmabadi 已提交
13
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
H
heejaechang 已提交
14 15 16 17 18
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Text;
C
Cyrus Najmabadi 已提交
19
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
H
heejaechang 已提交
20
using Microsoft.VisualStudio.Text;
C
Cyrus Najmabadi 已提交
21
using Microsoft.VisualStudio.Text.Tagging;
H
heejaechang 已提交
22 23 24 25 26 27
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
C
Cyrus Najmabadi 已提交
28
    internal class DiagnosticTaggerWrapper : IDisposable
H
heejaechang 已提交
29
    {
30
        private readonly TestWorkspace _workspace;
31
        public readonly DiagnosticAnalyzerService AnalyzerService;
32 33 34 35
        private readonly ISolutionCrawlerRegistrationService _registrationService;
        private readonly ImmutableArray<IIncrementalAnalyzer> _incrementalAnalyzers;
        private readonly SolutionCrawlerRegistrationService _solutionCrawlerService;
        private readonly AsynchronousOperationListener _asyncListener;
36
        public readonly DiagnosticService DiagnosticService;
37
        private readonly IEnumerable<Lazy<IAsynchronousOperationListener, FeatureMetadata>> _listeners;
38 39

        private DiagnosticsSquiggleTaggerProvider _taggerProvider;
40

41 42 43
        public DiagnosticTaggerWrapper(
            TestWorkspace workspace,
            Dictionary<string, DiagnosticAnalyzer[]> analyzerMap = null,
44
            bool createTaggerProvider = true)
H
Heejae Chang 已提交
45
            : this(workspace, analyzerMap, updateSource: null, createTaggerProvider: createTaggerProvider)
46
        {
C
Cyrus Najmabadi 已提交
47
        }
C
Cyrus Najmabadi 已提交
48

49 50 51 52 53
        public DiagnosticTaggerWrapper(
            TestWorkspace workspace,
            IDiagnosticUpdateSource updateSource,
            bool createTaggerProvider = true)
            : this(workspace, null, updateSource, createTaggerProvider)
C
Cyrus Najmabadi 已提交
54 55
        {
        }
C
Cyrus Najmabadi 已提交
56

H
Heejae Chang 已提交
57 58
        private static DiagnosticAnalyzerService CreateDiagnosticAnalyzerService(
            Dictionary<string, DiagnosticAnalyzer[]> analyzerMap, IAsynchronousOperationListener listener)
C
Cyrus Najmabadi 已提交
59 60
        {
            return analyzerMap == null || analyzerMap.Count == 0
61 62
                ? new MyDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap(), listener: listener)
                : new MyDiagnosticAnalyzerService(analyzerMap.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.ToImmutableArray()), listener: listener);
C
Cyrus Najmabadi 已提交
63 64
        }

65 66
        private DiagnosticTaggerWrapper(
            TestWorkspace workspace,
H
Heejae Chang 已提交
67
            Dictionary<string, DiagnosticAnalyzer[]> analyzerMap,
68 69
            IDiagnosticUpdateSource updateSource,
            bool createTaggerProvider)
C
Cyrus Najmabadi 已提交
70
        {
H
Heejae Chang 已提交
71 72 73 74 75
            _asyncListener = new AsynchronousOperationListener();
            _listeners = AsynchronousOperationListener.CreateListeners(
                ValueTuple.Create(FeatureAttribute.DiagnosticService, _asyncListener),
                ValueTuple.Create(FeatureAttribute.ErrorSquiggles, _asyncListener));

H
Heejae Chang 已提交
76 77
            if (analyzerMap != null || updateSource == null)
            {
78
                AnalyzerService = CreateDiagnosticAnalyzerService(analyzerMap, _asyncListener);
H
Heejae Chang 已提交
79
            }
H
Heejae Chang 已提交
80

C
Cyrus Najmabadi 已提交
81 82
            if (updateSource == null)
            {
H
Heejae Chang 已提交
83
                updateSource = AnalyzerService;
C
Cyrus Najmabadi 已提交
84 85
            }

86
            _workspace = workspace;
C
Cyrus Najmabadi 已提交
87

88 89
            _registrationService = workspace.Services.GetService<ISolutionCrawlerRegistrationService>();
            _registrationService.Register(workspace);
C
Cyrus Najmabadi 已提交
90

91 92
            DiagnosticService = new DiagnosticService(_listeners);
            DiagnosticService.Register(updateSource);
93

94 95 96 97
            if (createTaggerProvider)
            {
                var taggerProvider = this.TaggerProvider;
            }
98

H
Heejae Chang 已提交
99
            if (AnalyzerService != null)
C
Cyrus Najmabadi 已提交
100
            {
H
Heejae Chang 已提交
101
                _incrementalAnalyzers = ImmutableArray.Create(AnalyzerService.CreateIncrementalAnalyzer(workspace));
102
                _solutionCrawlerService = workspace.Services.GetService<ISolutionCrawlerRegistrationService>() as SolutionCrawlerRegistrationService;
C
Cyrus Najmabadi 已提交
103
            }
104
        }
105

106 107 108 109 110 111
        public DiagnosticsSquiggleTaggerProvider TaggerProvider
        {
            get
            {
                if (_taggerProvider == null)
                {
112 113
                    WpfTestCase.RequireWpfFact($"{nameof(DiagnosticTaggerWrapper)}.{nameof(TaggerProvider)} creates asynchronous taggers");

114
                    _taggerProvider = new DiagnosticsSquiggleTaggerProvider(
115
                        _workspace.Services.GetService<IOptionService>(), DiagnosticService,
116
                        _workspace.GetService<IForegroundNotificationService>(), _listeners);
117 118 119 120 121 122 123 124
                }

                return _taggerProvider;
            }
        }



125 126
        public void Dispose()
        {
127
            _registrationService.Unregister(_workspace);
128 129
        }

J
Jared Parsons 已提交
130
        public async Task WaitForTags()
131
        {
132
            if (_solutionCrawlerService != null)
C
Cyrus Najmabadi 已提交
133
            {
134
                _solutionCrawlerService.WaitUntilCompletion_ForTestingPurposesOnly(_workspace, _incrementalAnalyzers);
C
Cyrus Najmabadi 已提交
135 136
            }

137
            await _asyncListener.CreateWaitTask();
138
        }
139 140 141 142 143 144 145 146 147 148 149 150 151

        private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService
        {
            internal MyDiagnosticAnalyzerService(
                ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> analyzersMap,
                IAsynchronousOperationListener listener)
                : base(new HostAnalyzerManager(ImmutableArray.Create<AnalyzerReference>(new TestAnalyzerReferenceByLanguage(analyzersMap)), hostDiagnosticUpdateSource: null),
                      hostDiagnosticUpdateSource: null,
                      registrationService: new MockDiagnosticUpdateSourceRegistrationService(),
                      listener: listener)
            {
            }
        }
152
    }
153

154 155
    public class DiagnosticsSquiggleTaggerProviderTests
    {
156
        [WpfFact(Skip = "xunit"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
J
Jared Parsons 已提交
157
        public async Task Test_TagSourceDiffer()
158
        {
159 160 161 162 163 164
            var analyzer = new Analyzer();
            var analyzerMap = new Dictionary<string, DiagnosticAnalyzer[]>
            {
                { LanguageNames.CSharp, new DiagnosticAnalyzer[] { analyzer } }
            };

C
Cyrus Najmabadi 已提交
165
            using (var workspace = await TestWorkspace.CreateCSharpAsync(new string[] { "class A { }", "class E { }" }, CSharpParseOptions.Default))
166
            using (var wrapper = new DiagnosticTaggerWrapper(workspace, analyzerMap))
167 168
            {
                var tagger = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
169 170 171
                using (var disposable = tagger as IDisposable)
                {
                    // test first update
172
                    await wrapper.WaitForTags();
H
heejaechang 已提交
173

174
                    var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
C
Cyrus Najmabadi 已提交
175
                    var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
176
                    Assert.True(spans.First().Span.Contains(new Span(0, 1)));
C
Cyrus Najmabadi 已提交
177

178
                    // test second update
179
                    analyzer.ChangeSeverity();
H
heejaechang 已提交
180

181
                    var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
182
                    var text = await document.GetTextAsync();
183
                    workspace.TryApplyChanges(document.WithText(text.WithChanges(new TextChange(new TextSpan(text.Length - 1, 1), string.Empty))).Project.Solution);
H
heejaechang 已提交
184

185
                    await wrapper.WaitForTags();
H
heejaechang 已提交
186

187
                    snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
C
Cyrus Najmabadi 已提交
188
                    spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
189
                    Assert.True(spans.First().Span.Contains(new Span(0, 1)));
190 191 192 193
                }
            }
        }

194
        [WpfFact(Skip = "xunit"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
J
Jared Parsons 已提交
195
        public async Task MultipleTaggersAndDispose()
196
        {
C
Cyrus Najmabadi 已提交
197
            using (var workspace = await TestWorkspace.CreateCSharpAsync(new string[] { "class A {" }, CSharpParseOptions.Default))
198
            using (var wrapper = new DiagnosticTaggerWrapper(workspace))
199 200
            {
                // Make two taggers.
201 202
                var tagger1 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
                var tagger2 = wrapper.TaggerProvider.CreateTagger<IErrorTag>(workspace.Documents.First().GetTextBuffer());
203 204 205 206 207 208

                // But dispose the first one. We still want the second one to work.
                ((IDisposable)tagger1).Dispose();

                using (var disposable = tagger2 as IDisposable)
                {
209
                    await wrapper.WaitForTags();
210 211

                    var snapshot = workspace.Documents.First().GetTextBuffer().CurrentSnapshot;
C
Cyrus Najmabadi 已提交
212
                    var spans = tagger2.GetTags(snapshot.GetSnapshotSpanCollection()).ToList();
213
                    Assert.False(spans.IsEmpty());
214 215 216 217
                }
            }
        }

J
Jared Parsons 已提交
218
        [WpfFact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
J
Jared Parsons 已提交
219
        public async Task TaggerProviderCreatedAfterInitialDiagnosticsReported()
220
        {
C
Cyrus Najmabadi 已提交
221
            using (var workspace = await TestWorkspace.CreateCSharpAsync(new string[] { "class C {" }, CSharpParseOptions.Default))
222 223 224
            using (var wrapper = new DiagnosticTaggerWrapper(workspace, analyzerMap: null, createTaggerProvider: false))
            {
                // First, make sure all diagnostics have been reported.
225
                await wrapper.WaitForTags();
226 227 228 229 230 231 232 233

                // 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)
                {
234
                    await wrapper.WaitForTags();
235 236 237 238 239

                    // 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());
240
                }
H
heejaechang 已提交
241 242 243
            }
        }

244 245 246 247 248 249 250 251 252
        [WpfFact]
        public async Task TestWithMockDiagnosticService_TaggerProviderCreatedBeforeInitialDiagnosticsReported()
        {
            // This test produces diagnostics from a mock service so that we are disconnected from
            // all teh asynchrony of hte 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.

C
Cyrus Najmabadi 已提交
253
            using (var workspace = await TestWorkspace.CreateCSharpAsync(new string[] { "class A { }" }, CSharpParseOptions.Default))
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
            using (var wrapper = new DiagnosticTaggerWrapper(workspace))
            {
                var asyncListener = new AsynchronousOperationListener();
                var listeners = AsynchronousOperationListener.CreateListeners(
                    ValueTuple.Create(FeatureAttribute.DiagnosticService, asyncListener),
                    ValueTuple.Create(FeatureAttribute.ErrorSquiggles, asyncListener));

                var diagnosticService = new MockDiagnosticService(workspace);
                var provider = new DiagnosticsSquiggleTaggerProvider(
                        workspace.Services.GetService<IOptionService>(), diagnosticService,
                        workspace.GetService<IForegroundNotificationService>(), listeners);

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

                // Now product hte first diagnostic and fire the events.
                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 asyncListener.CreateWaitTask();

                    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);
                }
            }
        }

        [WpfFact]
        public async Task TestWithMockDiagnosticService_TaggerProviderCreatedAfterInitialDiagnosticsReported()
        {
            // This test produces diagnostics from a mock service so that we are disconnected from
            // all teh asynchrony of hte 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.

C
Cyrus Najmabadi 已提交
295
            using (var workspace = await TestWorkspace.CreateCSharpAsync(new string[] { "class A { }" }, CSharpParseOptions.Default))
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
            using (var wrapper = new DiagnosticTaggerWrapper(workspace))
            {
                var asyncListener = new AsynchronousOperationListener();
                var listeners = AsynchronousOperationListener.CreateListeners(
                    ValueTuple.Create(FeatureAttribute.DiagnosticService, asyncListener),
                    ValueTuple.Create(FeatureAttribute.ErrorSquiggles, asyncListener));

                var diagnosticService = new MockDiagnosticService(workspace);
                var provider = new DiagnosticsSquiggleTaggerProvider(
                        workspace.Services.GetService<IOptionService>(), diagnosticService,
                        workspace.GetService<IForegroundNotificationService>(), listeners);

                // Create and fire the diagnostic events before hte tagger is even made.
                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 asyncListener.CreateWaitTask();

                    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);
                }
            }
        }

        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 已提交
396 397
        private class Analyzer : DiagnosticAnalyzer
        {
B
beep boop 已提交
398
            private DiagnosticDescriptor _rule = new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Error, true);
H
heejaechang 已提交
399 400 401 402 403

            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            {
                get
                {
B
beep boop 已提交
404
                    return ImmutableArray.Create(_rule);
H
heejaechang 已提交
405 406 407 408 409 410 411
                }
            }

            public override void Initialize(AnalysisContext context)
            {
                context.RegisterSyntaxTreeAction(c =>
                {
B
beep boop 已提交
412
                    c.ReportDiagnostic(Diagnostic.Create(_rule, Location.Create(c.Tree, new Text.TextSpan(0, 1))));
H
heejaechang 已提交
413 414 415 416 417
                });
            }

            public void ChangeSeverity()
            {
B
beep boop 已提交
418
                _rule = new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Warning, true);
H
heejaechang 已提交
419 420 421
            }
        }
    }
J
Jared Parsons 已提交
422
}