File: Diagnostics\ExternalDiagnosticUpdateSourceTests.vb
Web Access
Project: src\src\VisualStudio\Core\Test\Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj (Microsoft.VisualStudio.LanguageServices.UnitTests)
' 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.
 
Imports System.Collections.Immutable
Imports System.ComponentModel.Composition
Imports System.IO.Pipelines
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.[Shared].Utilities
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Shared.TestHooks
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.ServiceHub.Framework
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel
Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks
Imports Microsoft.VisualStudio.RpcContracts.DiagnosticManagement
Imports Microsoft.VisualStudio.Shell.ServiceBroker
Imports Roslyn.Test.Utilities
Imports Roslyn.Utilities
 
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
    <[UseExportProvider]>
    Public Class ExternalDiagnosticUpdateSourceTests
        Private Shared ReadOnly s_composition As TestComposition = VisualStudioTestCompositions.LanguageServices.AddParts(
            GetType(TestServiceBroker),
            GetType(MockServiceProvider),
            GetType(StubVsServiceExporter(Of )),
            GetType(StubVsServiceExporter(Of ,)),
            GetType(MockVisualStudioWorkspace),
            GetType(ProjectCodeModelFactory))
 
        Private Shared ReadOnly s_projectGuid As Guid = Guid.NewGuid()
 
        <WpfFact>
        Public Async Function TestExternalDiagnostics_SupportedId() As Task
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList)
                Dim analyzer = New AnalyzerForErrorLogTest()
 
                Dim analyzerReference = New TestAnalyzerReferenceByLanguage(
                    ImmutableDictionary(Of String, ImmutableArray(Of DiagnosticAnalyzer)).Empty.Add(LanguageNames.CSharp, ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)))
 
                workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}))
 
                Dim threadingContext = workspace.ExportProvider.GetExport(Of IThreadingContext).Value
 
                Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    Dim project = workspace.CurrentSolution.Projects.First()
                    source.OnSolutionBuildStarted()
                    Await waiter.ExpeditedWaitAsync()
 
                    Assert.True(source.IsSupportedDiagnosticId(project.Id, "ID1"))
                    Assert.False(source.IsSupportedDiagnosticId(project.Id, "CA1002"))
                End Using
            End Using
        End Function
 
        <WpfFact>
        Public Async Function TestExternalDiagnostics_SupportedDiagnosticId_Concurrent() As Task
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList)
                Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    Dim project = workspace.CurrentSolution.Projects.First()
                    source.OnSolutionBuildStarted()
                    Await waiter.ExpeditedWaitAsync()
 
                    Parallel.For(0, 100, Sub(i As Integer) source.IsSupportedDiagnosticId(project.Id, "CS1002"))
                End Using
            End Using
        End Function
 
        <WpfFact>
        Public Sub TestExternalDiagnostics_SupportedIdFalseIfBuildNotStarted()
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList)
                Dim analyzer = New AnalyzerForErrorLogTest()
 
                Dim analyzerReference = New TestAnalyzerReferenceByLanguage(
                    ImmutableDictionary(Of String, ImmutableArray(Of DiagnosticAnalyzer)).Empty.Add(LanguageNames.CSharp, ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)))
 
                workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}))
 
                Dim threadingContext = workspace.ExportProvider.GetExport(Of IThreadingContext).Value
 
                Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    Dim project = workspace.CurrentSolution.Projects.First()
 
                    Assert.False(source.IsSupportedDiagnosticId(project.Id, "ID1"))
                    Assert.False(source.IsSupportedDiagnosticId(project.Id, "CA1002"))
                End Using
            End Using
        End Sub
 
        <WpfFact>
        Public Async Function TestExternalDiagnosticsReported() As Task
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList)
 
                Dim project = workspace.CurrentSolution.Projects.First()
                Dim diagnostic = GetDiagnosticData(project.Id)
 
                Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions)
                Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)
                Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    source.OnSolutionBuildStarted()
                    Await waiter.ExpeditedWaitAsync()
                    Assert.True(testServiceBroker.DiagnosticManagerService.AllDiagnosticsCleared)
 
                    Dim diagnostics = {diagnostic, GetDiagnosticData(project.Id)}.ToImmutableArray()
 
                    source.AddNewErrors(project.Id, s_projectGuid, diagnostics)
                    source.OnSolutionBuildCompleted()
                    Await waiter.ExpeditedWaitAsync()
 
                    Assert.Equal(2, testServiceBroker.DiagnosticManagerService.AllDiagnostics.Count)
                End Using
            End Using
        End Function
 
        <WpfFact>
        Public Async Function TestOnlySupportsBuildErrors() As Task
            Assert.Equal(1, DiagnosticData.PropertiesForBuildDiagnostic.Count)
 
            Dim value As String = Nothing
            Assert.True(DiagnosticData.PropertiesForBuildDiagnostic.TryGetValue(WellKnownDiagnosticPropertyNames.Origin, value))
            Assert.Equal(WellKnownDiagnosticTags.Build, value)
 
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList)
 
                Dim project = workspace.CurrentSolution.Projects.First()
 
                Dim service = New TestDiagnosticAnalyzerService(workspace.GlobalOptions)
                Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)
                Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    source.OnSolutionBuildStarted()
                    Await waiter.ExpeditedWaitAsync()
                    Assert.True(testServiceBroker.DiagnosticManagerService.AllDiagnosticsCleared)
 
                    Dim diagnostic = New DiagnosticData(
                        "id",
                        category:="Test",
                        message:="Test Message",
                        severity:=Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
                        defaultSeverity:=Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
                        isEnabledByDefault:=True,
                        warningLevel:=0,
                        projectId:=project.Id,
                        location:=New DiagnosticDataLocation(New FileLinePositionSpan("C:\DocumentDiagnostic", Nothing)),
                        customTags:=ImmutableArray(Of String).Empty,
                        properties:=ImmutableDictionary(Of String, String).Empty,
                        language:=LanguageNames.VisualBasic)
                    Assert.False(diagnostic.IsBuildDiagnostic())
#If DEBUG Then
                    Assert.Throws(Of InvalidOperationException)(Sub() source.AddNewErrors(project.Id, s_projectGuid, {diagnostic}.ToImmutableArray()))
#End If
                End Using
            End Using
        End Function
 
        <WpfFact>
        Public Async Function TestExternalDiagnostics_AddDuplicatedErrors() As Task
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim globalOptions = workspace.GetService(Of IGlobalOptionService)
                Dim waiter = workspace.GetService(Of AsynchronousOperationListenerProvider)().GetWaiter(FeatureAttribute.ErrorList)
 
                Dim project = workspace.CurrentSolution.Projects.First()
                Dim diagnostic = GetDiagnosticData(project.Id)
 
                Dim service = New TestDiagnosticAnalyzerService(globalOptions)
                Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)
                Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    source.OnSolutionBuildStarted()
                    Await waiter.ExpeditedWaitAsync()
                    Assert.True(testServiceBroker.DiagnosticManagerService.AllDiagnosticsCleared)
 
                    ' we shouldn't crash here
                    source.AddNewErrors(project.Id, s_projectGuid, {diagnostic}.ToImmutableArray())
                    source.AddNewErrors(project.Id, s_projectGuid, {diagnostic}.ToImmutableArray())
 
                    source.OnSolutionBuildCompleted()
                    Await waiter.ExpeditedWaitAsync()
 
                    Assert.Equal(2, testServiceBroker.DiagnosticManagerService.AllDiagnostics.Count)
                End Using
            End Using
        End Function
 
        <WpfFact>
        Public Async Function TestCompilerDiagnosticWithoutDocumentId() As Task
            Using workspace = EditorTestWorkspace.CreateCSharp(String.Empty, composition:=s_composition)
                Dim globalOptions = workspace.GetService(Of IGlobalOptionService)
                Dim analyzer = New CompilationAnalyzer()
                Dim compiler = DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp)
 
                Dim analyzerReference = New AnalyzerImageReference(New DiagnosticAnalyzer() {compiler, analyzer}.ToImmutableArray())
                workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}))
 
                Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider)()
                Dim waiter = TryCast(listenerProvider.GetListener(FeatureAttribute.ErrorList), AsynchronousOperationListener)
 
                Dim project = workspace.CurrentSolution.Projects.First()
 
                Dim service = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)())
                Dim registration = service.CreateIncrementalAnalyzer(workspace)
                Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)
                Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker)
                Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)()
                vsWorkspace.SetWorkspace(workspace)
                Using source = workspace.ExportProvider.GetExportedValue(Of ExternalErrorDiagnosticUpdateSource)()
 
                    Dim diagnostic = New DiagnosticData(
                        id:="CS1002",
                        category:="Test",
                        message:="Test Message",
                        severity:=Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
                        defaultSeverity:=Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
                        isEnabledByDefault:=True,
                        warningLevel:=0,
                        customTags:=ImmutableArray(Of String).Empty,
                        properties:=DiagnosticData.PropertiesForBuildDiagnostic,
                        project.Id,
                        location:=New DiagnosticDataLocation(New FileLinePositionSpan("C:\ProjectDiagnostic", New LinePosition(4, 4), New LinePosition(4, 4)), documentId:=Nothing),
                        language:=project.Language)
 
                    source.AddNewErrors(project.Id, s_projectGuid, {diagnostic}.ToImmutableArray())
                    source.OnSolutionBuildCompleted()
                    Await waiter.ExpeditedWaitAsync()
                End Using
            End Using
        End Function
 
        Private Class CompilationAnalyzer
            Inherits DiagnosticAnalyzer
 
            Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
                Get
                    Return ImmutableArray.Create(DescriptorFactory.CreateSimpleDescriptor("CompilationAnalyzer"))
                End Get
            End Property
 
            Public Overrides Sub Initialize(context As AnalysisContext)
                context.RegisterCompilationAction(
                    Sub(compilationContext)
                        ' do nothing
                    End Sub)
            End Sub
        End Class
 
        Private Shared Function GetDiagnosticData(projectId As ProjectId, Optional id As String = "id") As DiagnosticData
            Return New DiagnosticData(
                id,
                category:="Test",
                message:="Test Message",
                severity:=Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
                defaultSeverity:=Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
                isEnabledByDefault:=True,
                warningLevel:=0,
                projectId:=projectId,
                location:=New DiagnosticDataLocation(New FileLinePositionSpan("C:\DocumentDiagnostic", Nothing)),
                customTags:=ImmutableArray(Of String).Empty,
                properties:=DiagnosticData.PropertiesForBuildDiagnostic,
                language:=LanguageNames.VisualBasic)
        End Function
 
        Private Class TestDiagnosticAnalyzerService
            Implements IDiagnosticAnalyzerService
 
            Private ReadOnly _analyzerInfoCache As DiagnosticAnalyzerInfoCache
 
            Public ReadOnly Property GlobalOptions As IGlobalOptionService Implements IDiagnosticAnalyzerService.GlobalOptions
 
            Public Sub New(globalOptions As IGlobalOptionService, Optional data As ImmutableArray(Of DiagnosticData) = Nothing)
                _analyzerInfoCache = New DiagnosticAnalyzerInfoCache()
                Me.GlobalOptions = globalOptions
            End Sub
 
            Public ReadOnly Property AnalyzerInfoCache As DiagnosticAnalyzerInfoCache Implements IDiagnosticAnalyzerService.AnalyzerInfoCache
                Get
                    Return _analyzerInfoCache
                End Get
            End Property
 
            Public Sub RequestDiagnosticRefresh() Implements IDiagnosticAnalyzerService.RequestDiagnosticRefresh
            End Sub
 
            Public Function GetDiagnosticsForSpanAsync(document As TextDocument, range As TextSpan?, shouldIncludeDiagnostic As Func(Of String, Boolean), includeCompilerDiagnostics As Boolean, includeSuppressedDiagnostics As Boolean, priority As ICodeActionRequestPriorityProvider, diagnosticKinds As DiagnosticKind, isExplicit As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync
                Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)
            End Function
 
            Public Function GetCachedDiagnosticsAsync(workspace As Workspace, projectId As ProjectId, documentId As DocumentId, includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetCachedDiagnosticsAsync
                Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)()
            End Function
 
            Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), getDocuments As Func(Of Project, DocumentId, IReadOnlyList(Of DocumentId)), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync
                Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)()
            End Function
 
            Public Function GetProjectDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync
                Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)()
            End Function
 
            Public Function ForceAnalyzeProjectAsync(project As Project, cancellationToken As CancellationToken) As Task Implements IDiagnosticAnalyzerService.ForceAnalyzeProjectAsync
                Throw New NotImplementedException()
            End Function
        End Class
 
        <PartNotDiscoverable>
        <Export(GetType(SVsFullAccessServiceBroker))>
        <Export(GetType(TestServiceBroker))>
        Private Class TestServiceBroker
            Implements IServiceBroker
 
            Friend DiagnosticManagerService As DiagnosticManagerService = New DiagnosticManagerService()
 
            <ImportingConstructor>
            <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
            Public Sub New()
            End Sub
 
            Public Event AvailabilityChanged As EventHandler(Of BrokeredServicesChangedEventArgs) Implements IServiceBroker.AvailabilityChanged
 
            Public Function GetProxyAsync(Of T As Class)(serviceDescriptor As ServiceRpcDescriptor, Optional options As ServiceActivationOptions = Nothing, Optional cancellationToken As CancellationToken = Nothing) As ValueTask(Of T) Implements IServiceBroker.GetProxyAsync
                If (GetType(T) Is GetType(IDiagnosticManagerService)) Then
                    Return New ValueTask(Of T)(Task.FromResult(CType(CType(DiagnosticManagerService, Object), T)))
                End If
 
                Throw New InvalidOperationException()
            End Function
 
            Public Function GetPipeAsync(serviceMoniker As ServiceMoniker, Optional options As ServiceActivationOptions = Nothing, Optional cancellationToken As CancellationToken = Nothing) As ValueTask(Of IDuplexPipe) Implements IServiceBroker.GetPipeAsync
                Throw New NotImplementedException()
            End Function
        End Class
 
        Private Class DiagnosticManagerService
            Implements IDiagnosticManagerService
 
            Friend DiagnosticsCleared As Boolean = False
            Friend AllDiagnosticsCleared As Boolean = False
            Friend AllDiagnostics As List(Of RpcContracts.DiagnosticManagement.Diagnostic) = New List(Of RpcContracts.DiagnosticManagement.Diagnostic)()
 
            Public Sub Dispose() Implements IDisposable.Dispose
            End Sub
 
            Public Function SetDiagnosticsAsync(generatorId As String, diagnostics As IReadOnlyList(Of DiagnosticCollection), cancellationToken As CancellationToken) As Task Implements IDiagnosticManagerService.SetDiagnosticsAsync
                Throw New NotImplementedException()
            End Function
 
            Public Function AppendDiagnosticsAsync(generatorId As String, diagnostics As IReadOnlyList(Of DiagnosticCollection), cancellationToken As CancellationToken) As Task Implements IDiagnosticManagerService.AppendDiagnosticsAsync
                For Each collection In diagnostics
                    For Each diagnostic In collection.Diagnostics
                        AllDiagnostics.Add(diagnostic)
                    Next
                Next
                Return Task.CompletedTask
            End Function
 
            Public Function ClearDiagnosticsAsync(generatorId As String, cancellationToken As CancellationToken) As Task Implements IDiagnosticManagerService.ClearDiagnosticsAsync
                If (DiagnosticsCleared) Then
                    Throw New InvalidOperationException()
                End If
                DiagnosticsCleared = True
                Return Task.CompletedTask
            End Function
 
            Public Function ClearAllDiagnosticsAsync(cancellationToken As CancellationToken) As Task Implements IDiagnosticManagerService.ClearAllDiagnosticsAsync
                If (AllDiagnosticsCleared) Then
                    Throw New InvalidOperationException()
                End If
                AllDiagnosticsCleared = True
                Return Task.CompletedTask
            End Function
 
            Public Function AddBuildOnlyDiagnosticCodesAsync(diagnosticCodes As IReadOnlyList(Of String), cancellationToken As CancellationToken) As Task Implements IDiagnosticManagerService.AddBuildOnlyDiagnosticCodesAsync
                Throw New NotImplementedException()
            End Function
        End Class
 
    End Class
End Namespace