|
// 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.
#nullable disable
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.Diagnostics)]
public class DiagnosticServiceTests
{
private static DiagnosticService GetDiagnosticService(TestWorkspace workspace)
{
var diagnosticService = Assert.IsType<DiagnosticService>(workspace.ExportProvider.GetExportedValue<IDiagnosticService>());
// These tests were originally written under the assumption that the diagnostic service will not be
// initialized with listeners. If this check ever fails, the tests that use this method should be reviewed
// for impact.
Assert.Empty(diagnosticService.GetTestAccessor().EventListenerTracker.GetTestAccessor().EventListeners);
return diagnosticService;
}
[Fact]
public async Task TestGetDiagnostics1()
{
using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var source = new TestDiagnosticUpdateSource(false, null);
var diagnosticService = GetDiagnosticService(workspace);
diagnosticService.Register(source);
diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); };
var id = Tuple.Create(workspace, document);
var diagnostic = RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id);
var data1 = await diagnosticService.GetDiagnosticsAsync(workspace, null, null, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(diagnostic, data1.Single());
var data2 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, null, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(diagnostic, data2.Single());
var data3 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, document.Id, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(diagnostic, data3.Single());
var data4 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, document.Id, id, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(diagnostic, data4.Single());
}
[Fact]
public async Task TestGetDiagnostics2()
{
using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var document2 = document.Project.AddDocument("TestDocument2", string.Empty);
var source = new TestDiagnosticUpdateSource(false, null);
var diagnosticService = GetDiagnosticService(workspace);
diagnosticService.Register(source);
diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); };
var id = Tuple.Create(workspace, document);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id);
var id2 = Tuple.Create(workspace, document.Project, document);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id2);
RaiseDiagnosticEvent(mutex, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));
var id3 = Tuple.Create(workspace, document.Project);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, null, id3);
RaiseDiagnosticEvent(mutex, source, workspace, null, null, Tuple.Create(workspace));
var data1 = await diagnosticService.GetDiagnosticsAsync(workspace, null, null, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(5, data1.Count());
var data2 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, null, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(4, data2.Count());
var data3 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, null, id3, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(1, data3.Count());
var data4 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, document.Id, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(2, data4.Count());
var data5 = await diagnosticService.GetDiagnosticsAsync(workspace, document.Project.Id, document.Id, id, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(1, data5.Count());
}
[Fact]
public async Task TestCleared()
{
using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var document2 = document.Project.AddDocument("TestDocument2", string.Empty);
var diagnosticService = GetDiagnosticService(workspace);
var source1 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null);
diagnosticService.Register(source1);
var source2 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null);
diagnosticService.Register(source2);
diagnosticService.DiagnosticsUpdated += MarkSet;
// add bunch of data to the service for both sources
RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document));
RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document.Project, document));
RaiseDiagnosticEvent(mutex, source1, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));
RaiseDiagnosticEvent(mutex, source2, workspace, document.Project.Id, null, Tuple.Create(workspace, document.Project));
RaiseDiagnosticEvent(mutex, source2, workspace, null, null, Tuple.Create(workspace));
// confirm data is there.
var data1 = await diagnosticService.GetDiagnosticsAsync(workspace, null, null, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(5, data1.Count());
diagnosticService.DiagnosticsUpdated -= MarkSet;
// confirm clear for a source
mutex.Reset();
var count = 0;
diagnosticService.DiagnosticsUpdated += MarkCalled;
source1.RaiseDiagnosticsClearedEvent();
mutex.WaitOne();
// confirm there are 2 data left
var data2 = await diagnosticService.GetDiagnosticsAsync(workspace, null, null, null, includeSuppressedDiagnostics: false, CancellationToken.None);
Assert.Equal(2, data2.Count());
void MarkCalled(object sender, DiagnosticsUpdatedArgs args)
{
// event is serialized. no concurrent call
if (++count == 3)
{
mutex.Set();
}
}
void MarkSet(object sender, DiagnosticsUpdatedArgs args)
{
mutex.Set();
}
}
private static DiagnosticData RaiseDiagnosticEvent(ManualResetEvent set, TestDiagnosticUpdateSource source, TestWorkspace workspace, ProjectId projectId, DocumentId documentId, object id)
{
set.Reset();
var diagnostic = CreateDiagnosticData(projectId, documentId);
source.RaiseDiagnosticsUpdatedEvent(
DiagnosticsUpdatedArgs.DiagnosticsCreated(id, workspace, workspace.CurrentSolution, projectId, documentId, ImmutableArray.Create(diagnostic)));
set.WaitOne();
return diagnostic;
}
private static DiagnosticData CreateDiagnosticData(ProjectId projectId, DocumentId documentId)
{
return new DiagnosticData(
id: "test1",
category: "Test",
message: "test1 message",
severity: DiagnosticSeverity.Info,
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: false,
warningLevel: 1,
customTags: ImmutableArray<string>.Empty,
properties: ImmutableDictionary<string, string>.Empty,
projectId,
location: new DiagnosticDataLocation(new("originalFile1", new(10, 10), new(20, 20)), documentId));
}
private class TestDiagnosticUpdateSource : IDiagnosticUpdateSource
{
private readonly bool _support;
private readonly ImmutableArray<DiagnosticData> _diagnosticData;
public TestDiagnosticUpdateSource(bool support, DiagnosticData[] diagnosticData)
{
_support = support;
_diagnosticData = (diagnosticData ?? Array.Empty<DiagnosticData>()).ToImmutableArray();
}
public bool SupportGetDiagnostics { get { return _support; } }
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared;
public ValueTask<ImmutableArray<DiagnosticData>> GetDiagnosticsAsync(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default)
=> new(_support ? _diagnosticData : ImmutableArray<DiagnosticData>.Empty);
public void RaiseDiagnosticsUpdatedEvent(DiagnosticsUpdatedArgs args)
=> DiagnosticsUpdated?.Invoke(this, args);
public void RaiseDiagnosticsClearedEvent()
=> DiagnosticsCleared?.Invoke(this, EventArgs.Empty);
}
}
}
|