CodeModelTestHelpers.vb 13.8 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
' 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.
4 5 6

Imports System.Runtime.CompilerServices
Imports System.Runtime.ExceptionServices
7
Imports System.Runtime.InteropServices
C
Cyrus Najmabadi 已提交
8 9
Imports EnvDTE
Imports EnvDTE80
10
Imports Microsoft.CodeAnalysis
C
Cyrus Najmabadi 已提交
11
Imports Microsoft.CodeAnalysis.Editor
12
Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities
13
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
C
Cyrus Najmabadi 已提交
14
Imports Microsoft.CodeAnalysis.Shared.TestHooks
15
Imports Microsoft.CodeAnalysis.Test.Utilities
16 17
Imports Microsoft.VisualStudio.ComponentModelHost
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel
18
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.ExternalElements
19
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.InternalElements
20 21 22
Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Interop
Imports Microsoft.VisualStudio.LanguageServices.Implementation.Interop
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks
23
Imports Microsoft.VisualStudio.Shell.Interop
24
Imports Roslyn.Test.Utilities
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel
    Friend Module CodeModelTestHelpers

        Public SystemWindowsFormsPath As String
        Public SystemDrawingPath As String

        Sub New()
            SystemWindowsFormsPath = GetType(System.Windows.Forms.Form).Assembly.Location
            SystemDrawingPath = GetType(System.Drawing.Point).Assembly.Location
        End Sub

        ' If something is *really* wrong with our COM marshalling stuff, the creation of the CodeModel will probably
        ' throw some sort of AV or other Very Bad exception. We still want to be able to catch them, so we can clean up
        ' the workspace. If we don't, we leak the workspace and it'll take down the process when it throws in a
        ' finalizer complaining we didn't clean it up. Catching AVs is of course not safe, but this is balancing
        ' "probably not crash" as an improvement over "will crash when the finalizer throws."

        <HandleProcessCorruptedStateExceptions()>
C
CyrusNajmabadi 已提交
44
        Public Function CreateCodeModelTestState(definition As XElement) As CodeModelTestState
45
            Dim workspace = TestWorkspace.Create(definition, exportProvider:=VisualStudioTestExportProvider.Factory.CreateExportProvider())
46 47 48 49 50 51

            Dim result As CodeModelTestState = Nothing
            Try
                Dim mockComponentModel = New MockComponentModel(workspace.ExportProvider)
                Dim mockServiceProvider = New MockServiceProvider(mockComponentModel)
                Dim mockVisualStudioWorkspace = New MockVisualStudioWorkspace(workspace)
52
                WrapperPolicy.s_ComWrapperFactory = MockComWrapperFactory.Instance
53

54 55 56 57
                ' The Code Model test infrastructure assumes that a test workspace only ever contains a single project.
                ' If tests are written that require multiple projects, additional support will need to be added.
                Dim project = workspace.CurrentSolution.Projects.Single()

58
                Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext)
C
Cyrus Najmabadi 已提交
59 60
                Dim notificationService = workspace.ExportProvider.GetExportedValue(Of IForegroundNotificationService)
                Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider)()
61

62
                Dim state = New CodeModelState(
63
                    threadingContext,
64 65
                    mockServiceProvider,
                    project.LanguageServices,
66
                    mockVisualStudioWorkspace,
C
Cyrus Najmabadi 已提交
67 68 69 70 71 72
                    New ProjectCodeModelFactory(
                        mockVisualStudioWorkspace,
                        mockServiceProvider,
                        threadingContext,
                        notificationService,
                        listenerProvider))
73

74 75 76
                Dim projectCodeModel = DirectCast(state.ProjectCodeModelFactory.CreateProjectCodeModel(project.Id, Nothing), ProjectCodeModel)

                For Each document In project.Documents
77
                    ' Note that a parent is not specified below. In Visual Studio, this would normally be an EnvDTE.Project instance.
78
                    Dim fcm = projectCodeModel.GetOrCreateFileCodeModel(document.FilePath, parent:=Nothing)
79
                    fcm.Object.TextManagerAdapter = New MockTextManagerAdapter()
80
                    mockVisualStudioWorkspace.SetFileCodeModel(document.Id, fcm)
81
                Next
82 83

                Dim root = New ComHandle(Of EnvDTE.CodeModel, RootCodeModel)(RootCodeModel.Create(state, Nothing, project.Id))
84
                Dim firstFCM = mockVisualStudioWorkspace.GetFileCodeModelComHandle(project.DocumentIds.First())
85

86
                result = New CodeModelTestState(workspace, mockVisualStudioWorkspace, root, firstFCM, state.CodeModelService)
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
            Finally
                If result Is Nothing Then
                    workspace.Dispose()
                End If
            End Try

            Return result
        End Function

        Public Class MockServiceProvider
            Implements IServiceProvider

            Private ReadOnly _componentModel As MockComponentModel

            Public Sub New(componentModel As MockComponentModel)
C
Cyrus Najmabadi 已提交
102
                _componentModel = componentModel
103 104 105 106 107 108 109
            End Sub

            Public Function GetService(serviceType As Type) As Object Implements IServiceProvider.GetService
                If serviceType = GetType(SComponentModel) Then
                    Return Me._componentModel
                End If

C
Cyrus Najmabadi 已提交
110 111
                If serviceType = GetType(EnvDTE.IVsExtensibility) Then
                    Return Nothing
C
Cyrus Najmabadi 已提交
112 113
                End If

114
                Throw New NotImplementedException($"No service exists for {serviceType.FullName}")
115 116 117
            End Function
        End Class

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
        Friend Class MockComWrapperFactory
            Implements IComWrapperFactory

            Public Shared ReadOnly Instance As IComWrapperFactory = New MockComWrapperFactory

            Public Function CreateAggregatedObject(managedObject As Object) As Object Implements IComWrapperFactory.CreateAggregatedObject
                Dim wrapperUnknown = BlindAggregatorFactory.CreateWrapper()
                Try
                    Dim innerUnknown = Marshal.CreateAggregatedObject(wrapperUnknown, managedObject)
                    Try
                        Dim handle = GCHandle.Alloc(managedObject, GCHandleType.Normal)
                        Dim freeHandle = True
                        Try
                            BlindAggregatorFactory.SetInnerObject(wrapperUnknown, innerUnknown, GCHandle.ToIntPtr(handle))
                            freeHandle = False
                        Finally
                            If freeHandle Then handle.Free()
                        End Try

                        Dim wrapperRCW = Marshal.GetObjectForIUnknown(wrapperUnknown)
                        Return CType(wrapperRCW, IComWrapper)
                    Finally
                        Marshal.Release(innerUnknown)
                    End Try
                Finally
                    Marshal.Release(wrapperUnknown)
                End Try
            End Function

        End Class

149
        <Extension()>
C
Cyrus Najmabadi 已提交
150
        Public Function GetDocumentAtCursor(state As CodeModelTestState) As Microsoft.CodeAnalysis.Document
151 152 153 154 155 156 157 158 159 160 161 162 163
            Dim cursorDocument = state.Workspace.Documents.First(Function(d) d.CursorPosition.HasValue)

            Dim document = state.Workspace.CurrentSolution.GetDocument(cursorDocument.Id)
            Assert.NotNull(document)
            Return document
        End Function

        <Extension()>
        Public Function GetCodeElementAtCursor(Of T As Class)(state As CodeModelTestState, Optional scope As EnvDTE.vsCMElement = EnvDTE.vsCMElement.vsCMElementOther) As T
            Dim cursorPosition = state.Workspace.Documents.First(Function(d) d.CursorPosition.HasValue).CursorPosition.Value

            ' Here we use vsCMElementOther to mean "Figure out the scope from the type parameter".
            Dim candidateScopes = If(scope = EnvDTE.vsCMElement.vsCMElementOther,
B
beep boop 已提交
164
                                     s_map(GetType(T)),
165 166 167 168 169
                                     {scope})

            Dim result As EnvDTE.CodeElement = Nothing

            For Each candidateScope In candidateScopes
170
                WpfTestRunner.RequireWpfFact($"{NameOf(GetCodeElementAtCursor)} creates {NameOf(EnvDTE.CodeElement)}s and thus uses the affinited {NameOf(CleanableWeakComHandleTable(Of SyntaxNodeKey, EnvDTE.CodeElement))}")
171

172 173
                Try
                    result = state.FileCodeModelObject.CodeElementFromPosition(cursorPosition, candidateScope)
174
                Catch ex As COMException
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
                    ' Loop around and try the next candidate scope
                    result = Nothing
                End Try

                If result IsNot Nothing Then
                    Exit For
                End If
            Next

            If result Is Nothing Then
                Assert.True(False, "Could not locate code element")
            End If

            Return CType(result, T)
        End Function

191 192 193 194 195 196 197 198 199 200
        ''' <summary>
        ''' Creates an "external" version of the given code element.
        ''' </summary>
        <Extension()>
        Public Function AsExternal(Of T As Class)(element As T) As T
            Dim codeElement = TryCast(element, EnvDTE.CodeElement)

            Assert.True(codeElement IsNot Nothing, "Expected code element")
            Assert.True(codeElement.InfoLocation = EnvDTE.vsCMInfoLocation.vsCMInfoLocationProject, "Expected internal code element")

201 202 203 204 205 206 207 208
            If TypeOf codeElement Is EnvDTE.CodeParameter Then
                Dim codeParameter = DirectCast(codeElement, EnvDTE.CodeParameter)
                Dim externalParentCodeElement = codeParameter.Parent.AsExternal()
                Dim externalParentCodeElementImpl = ComAggregate.GetManagedObject(Of AbstractExternalCodeMember)(externalParentCodeElement)

                Return DirectCast(externalParentCodeElementImpl.Parameters.Item(codeParameter.Name), T)
            End If

209 210 211 212 213 214 215 216 217 218 219 220 221 222
            Dim codeElementImpl = ComAggregate.GetManagedObject(Of AbstractCodeElement)(codeElement)
            Dim state = codeElementImpl.State
            Dim projectId = codeElementImpl.FileCodeModel.GetProjectId()
            Dim symbol = codeElementImpl.LookupSymbol()

            Dim externalCodeElement = codeElementImpl.CodeModelService.CreateExternalCodeElement(state, projectId, symbol)
            Assert.True(externalCodeElement IsNot Nothing, "Could not create external code element")

            Dim result = TryCast(externalCodeElement, T)
            Assert.True(result IsNot Nothing, $"Created external code element was not of type, {GetType(T).FullName}")

            Return result
        End Function

223 224 225 226 227 228 229 230 231
        <Extension()>
        Public Function GetMethodXML(func As EnvDTE.CodeFunction) As XElement
            Dim methodXml = TryCast(func, IMethodXML)
            Assert.NotNull(methodXml)

            Dim xml = methodXml.GetXML()
            Return XElement.Parse(xml)
        End Function

B
beep boop 已提交
232
        Private s_map As New Dictionary(Of Type, EnvDTE.vsCMElement()) From
233 234 235 236 237 238 239 240 241
            {{GetType(EnvDTE.CodeAttribute), {EnvDTE.vsCMElement.vsCMElementAttribute}},
             {GetType(EnvDTE80.CodeAttribute2), {EnvDTE.vsCMElement.vsCMElementAttribute}},
             {GetType(EnvDTE.CodeClass), {EnvDTE.vsCMElement.vsCMElementClass, EnvDTE.vsCMElement.vsCMElementModule}},
             {GetType(EnvDTE80.CodeClass2), {EnvDTE.vsCMElement.vsCMElementClass, EnvDTE.vsCMElement.vsCMElementModule}},
             {GetType(EnvDTE.CodeDelegate), {EnvDTE.vsCMElement.vsCMElementDelegate}},
             {GetType(EnvDTE80.CodeDelegate2), {EnvDTE.vsCMElement.vsCMElementDelegate}},
             {GetType(EnvDTE80.CodeElement2), {EnvDTE.vsCMElement.vsCMElementOptionStmt, EnvDTE.vsCMElement.vsCMElementInheritsStmt, EnvDTE.vsCMElement.vsCMElementImplementsStmt}},
             {GetType(EnvDTE.CodeEnum), {EnvDTE.vsCMElement.vsCMElementEnum}},
             {GetType(EnvDTE80.CodeEvent), {EnvDTE.vsCMElement.vsCMElementEvent}},
242 243
             {GetType(EnvDTE.CodeFunction), {EnvDTE.vsCMElement.vsCMElementFunction, EnvDTE.vsCMElement.vsCMElementDeclareDecl}},
             {GetType(EnvDTE80.CodeFunction2), {EnvDTE.vsCMElement.vsCMElementFunction, EnvDTE.vsCMElement.vsCMElementDeclareDecl}},
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
             {GetType(EnvDTE80.CodeImport), {EnvDTE.vsCMElement.vsCMElementImportStmt}},
             {GetType(EnvDTE.CodeInterface), {EnvDTE.vsCMElement.vsCMElementInterface}},
             {GetType(EnvDTE80.CodeInterface2), {EnvDTE.vsCMElement.vsCMElementInterface}},
             {GetType(EnvDTE.CodeNamespace), {EnvDTE.vsCMElement.vsCMElementNamespace}},
             {GetType(EnvDTE.CodeParameter), {EnvDTE.vsCMElement.vsCMElementParameter}},
             {GetType(EnvDTE80.CodeParameter2), {EnvDTE.vsCMElement.vsCMElementParameter}},
             {GetType(EnvDTE.CodeProperty), {EnvDTE.vsCMElement.vsCMElementProperty}},
             {GetType(EnvDTE80.CodeProperty2), {EnvDTE.vsCMElement.vsCMElementProperty}},
             {GetType(EnvDTE.CodeStruct), {EnvDTE.vsCMElement.vsCMElementStruct}},
             {GetType(EnvDTE80.CodeStruct2), {EnvDTE.vsCMElement.vsCMElementStruct}},
             {GetType(EnvDTE.CodeVariable), {EnvDTE.vsCMElement.vsCMElementVariable}},
             {GetType(EnvDTE80.CodeVariable2), {EnvDTE.vsCMElement.vsCMElementVariable}}}

        <Extension>
        Public Function Find(Of T)(elements As EnvDTE.CodeElements, name As String) As T
            For Each element As EnvDTE.CodeElement In elements
                If element.Name = name Then
                    Return CType(element, T)
                End If
            Next

            Return Nothing
        End Function

    End Module
End Namespace