提交 747392bd 编写于 作者: D Dustin Campbell

Clean up IntegrationServices

上级 0a298ee7
......@@ -19,8 +19,8 @@ internal sealed class IntegrationTestServiceCommands : IDisposable
public static readonly Guid GrpIdIntegrationTestServiceCommands = new Guid("82A24540-AEBC-4883-A717-5317F0C0DAE9");
private static readonly string DefaultPortName = string.Format(IntegrationService.PortNameFormatString, Process.GetCurrentProcess().Id);
private static readonly BinaryServerFormatterSinkProvider DefaultSinkProvider = new BinaryServerFormatterSinkProvider() {
private static readonly BinaryServerFormatterSinkProvider DefaultSinkProvider = new BinaryServerFormatterSinkProvider()
{
TypeFilterLevel = TypeFilterLevel.Full
};
......@@ -78,13 +78,19 @@ public void Dispose()
StopServiceCallback(this, EventArgs.Empty);
}
/// <summary>Starts the IPC server for the Integration Test service.</summary>
/// <summary>
/// Starts the IPC server for the Integration Test service.
/// </summary>
private void StartServiceCallback(object sender, EventArgs e)
{
if (_startServiceMenuCmd.Enabled)
{
_service = new IntegrationService();
_serviceChannel = new IpcServerChannel(null, DefaultPortName, DefaultSinkProvider);
_serviceChannel = new IpcServerChannel(
name: null,
portName: _service.PortName,
sinkProvider: DefaultSinkProvider);
var serviceType = typeof(IntegrationService);
_marshalledService = RemotingServices.Marshal(_service, serviceType.FullName, serviceType);
......
......@@ -33,6 +33,12 @@ public static Window LocateWindow(this DTE dte, string windowTitle)
}
public static Task WaitForCommandAvailabilityAsync(this DTE dte, string command)
=> IntegrationHelper.WaitForResultAsync(() => IntegrationHelper.RetryRpcCall(() => dte.Commands.Item(command).IsAvailable), expectedResult: true);
{
return IntegrationHelper.WaitForResultAsync(() =>
{
return IntegrationHelper.RetryRpcCall(() => dte.Commands.Item(command).IsAvailable);
},
expectedResult: true);
}
}
}
// 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.Reflection;
namespace Roslyn.VisualStudio.Test.Utilities
{
internal static class IntegrationServiceExtensions
{
public static void Execute(this IntegrationService integrationService, Type type, string methodName, BindingFlags bindingFlags = (BindingFlags.Public | BindingFlags.Static), params object[] parameters)
=> Execute(integrationService, type.Assembly.Location, type.FullName, methodName, bindingFlags, parameters);
public static T Execute<T>(this IntegrationService integrationService, Type type, string methodName, BindingFlags bindingFlags = (BindingFlags.Public | BindingFlags.Static), params object[] parameters)
=> Execute<T>(integrationService, type.Assembly.Location, type.FullName, methodName, bindingFlags, parameters);
public static void Execute(this IntegrationService integrationService, string assemblyFilePath, string typeFullName, string methodName, BindingFlags bindingFlags = (BindingFlags.Public | BindingFlags.Static), params object[] parameters)
{
var result = integrationService.Execute(assemblyFilePath, typeFullName, methodName, bindingFlags, parameters);
if (result != null)
{
throw new InvalidOperationException("The specified call was not expected to return a value.");
}
}
public static T Execute<T>(this IntegrationService integrationService, string assemblyFilePath, string typeFullName, string methodName, BindingFlags bindingFlags = (BindingFlags.Public | BindingFlags.Static), params object[] parameters)
{
var objectUri = integrationService.Execute(assemblyFilePath, typeFullName, methodName, bindingFlags, parameters);
if (objectUri == null)
{
throw new InvalidOperationException("The specified call was expected to return a value.");
}
return (T)(Activator.GetObject(typeof(T), $"{integrationService.Uri}/{objectUri}"));
}
}
}
......@@ -21,7 +21,9 @@
namespace Roslyn.VisualStudio.Test.Utilities
{
/// <summary>Provides some helper functions used by the other classes in the project.</summary>
/// <summary>
/// Provides some helper functions used by the other classes in the project.
/// </summary>
internal static class IntegrationHelper
{
public static bool AttachThreadInput(uint idAttach, uint idAttachTo)
......@@ -519,7 +521,7 @@ public static DTE TryLocateDteForProcess(Process process)
}
while (dte == null);
return (DTE)(dte);
return (DTE)dte;
}
public static void UnblockInput()
......
......@@ -2,26 +2,55 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Remoting;
namespace Roslyn.VisualStudio.Test.Utilities
{
/// <summary>Provides a means of executing code in the Visual Studio host process.</summary>
/// <remarks>This object exists in the Visual Studio host and is marhsalled across the process boundary.</remarks>
/// <summary>
/// Provides a means of executing code in the Visual Studio host process.
/// </summary>
/// <remarks>
/// This object exists in the Visual Studio host and is marhsalled across the process boundary.
/// </remarks>
internal class IntegrationService : MarshalByRefObject
{
// Make the channel name well known by using a static base and appending the process ID of the host
public static readonly string PortNameFormatString = $"{nameof(IntegrationService)}_{{0}}";
public string PortName { get; }
/// <summary>
/// The base Uri of the service. This resolves to a string such as <c>ipc://IntegrationService_{HostProcessId}"</c>
/// </summary>
public string BaseUri { get; }
private readonly ConcurrentDictionary<string, ObjRef> _marshalledObjects = new ConcurrentDictionary<string, ObjRef>();
public string Execute(string assemblyFilePath, string typeFullName, string methodName, BindingFlags bindingFlags, params object[] parameters)
public IntegrationService()
{
// Make the channel name well-known by using a static base and appending the process ID of the host
this.PortName = GetPortName(Process.GetCurrentProcess().Id);
this.BaseUri = "ipc://" + this.PortName;
}
private static string GetPortName(int hostProcessId)
{
// Make the channel name well-known by using a static base and appending the process ID of the host
return $"{nameof(IntegrationService)}_{{{hostProcessId}}}";
}
public static IntegrationService GetInstanceFromHostProcess(Process hostProcess)
{
var uri = $"ipc://{GetPortName(hostProcess.Id)}/{typeof(IntegrationService).FullName}";
return (IntegrationService)Activator.GetObject(typeof(IntegrationService), uri);
}
public string Execute(string assemblyFilePath, string typeFullName, string methodName)
{
var assembly = Assembly.LoadFrom(assemblyFilePath);
var type = assembly.GetType(typeFullName);
var methodInfo = type.GetMethod(methodName, bindingFlags);
var result = methodInfo.Invoke(null, parameters);
var methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
var result = methodInfo.Invoke(null, null);
if (methodInfo.ReturnType == typeof(void))
{
......@@ -30,7 +59,7 @@ public string Execute(string assemblyFilePath, string typeFullName, string metho
// Create a unique URL for each object returned, so that we can communicate with each object individually
var resultType = result.GetType();
var marshallableResult = (MarshalByRefObject)(result);
var marshallableResult = (MarshalByRefObject)result;
var objectUri = $"{resultType.FullName}_{Guid.NewGuid()}";
var marshalledObject = RemotingServices.Marshal(marshallableResult, objectUri, resultType);
......@@ -41,9 +70,5 @@ public string Execute(string assemblyFilePath, string typeFullName, string metho
return objectUri;
}
/// <summary>The base Uri of the service.</summary>
/// <remarks>This resolves to a string such as <c>ipc://IntegrationService_{HostProcessId}"</c></remarks>
public string Uri { get; set; }
}
}
......@@ -26,8 +26,12 @@
namespace Roslyn.VisualStudio.Test.Utilities.Remoting
{
/// <summary>Provides a set of helper functions for accessing services in the Visual Studio host process.</summary>
/// <remarks>This methods should be executed Visual Studio host via the <see cref="VisualStudioInstance.ExecuteOnHostProcess"/> method.</remarks>
/// <summary>
/// Provides a set of helper functions for accessing services in the Visual Studio host process.
/// </summary>
/// <remarks>
/// These methods should be executed Visual Studio host via the <see cref="VisualStudioInstance.ExecuteOnHostProcess"/> method.
/// </remarks>
internal static class RemotingHelper
{
private static readonly Guid IWpfTextViewId = new Guid("8C40265E-9FDB-4F54-A0FD-EBB72B7D0476");
......
......@@ -5,7 +5,6 @@
using System.IO;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;
using EnvDTE;
......@@ -17,8 +16,9 @@ namespace Roslyn.VisualStudio.Test.Utilities
{
public class VisualStudioInstance
{
public DTE DTE { get; }
private readonly Process _hostProcess;
private readonly DTE _dte;
private readonly IntegrationService _integrationService;
private readonly IpcClientChannel _integrationServiceChannel;
......@@ -28,24 +28,24 @@ public class VisualStudioInstance
private readonly Lazy<SolutionExplorer> _solutionExplorer;
private readonly Lazy<Workspace> _workspace;
public VisualStudioInstance(Process process, DTE dte)
public VisualStudioInstance(Process hostProcess, DTE dte)
{
_hostProcess = process;
_dte = dte;
_hostProcess = hostProcess;
this.DTE = dte;
dte.ExecuteCommandAsync(VisualStudioCommandNames.VsStartServiceCommand).GetAwaiter().GetResult();
this.DTE.ExecuteCommandAsync(VisualStudioCommandNames.VsStartServiceCommand).GetAwaiter().GetResult();
_integrationServiceChannel = new IpcClientChannel($"IPC channel client for {_hostProcess.Id}", sinkProvider: null);
ChannelServices.RegisterChannel(_integrationServiceChannel, ensureSecurity: true);
// Connect to a 'well defined, shouldn't conflict' IPC channel
var serviceUri = string.Format($"ipc://{IntegrationService.PortNameFormatString}", _hostProcess.Id);
_integrationService = (IntegrationService)(Activator.GetObject(typeof(IntegrationService), $"{serviceUri}/{typeof(IntegrationService).FullName}"));
_integrationService.Uri = serviceUri;
_integrationService = IntegrationService.GetInstanceFromHostProcess(hostProcess);
// There is a lot of VS initialization code that goes on, so we want to wait for that to 'settle' before
// we start executing any actual code.
_integrationService.Execute(typeof(RemotingHelper), nameof(RemotingHelper.WaitForSystemIdle));
ExecuteInHostProcess(
type: typeof(RemotingHelper),
methodName: nameof(RemotingHelper.WaitForSystemIdle));
_csharpInteractiveWindow = new Lazy<CSharpInteractiveWindow>(() => new CSharpInteractiveWindow(this));
_editorWindow = new Lazy<EditorWindow>(() => new EditorWindow(this));
......@@ -56,7 +56,27 @@ public VisualStudioInstance(Process process, DTE dte)
Cleanup();
}
public DTE Dte => _dte;
public void ExecuteInHostProcess(Type type, string methodName)
{
var result = _integrationService.Execute(type.Assembly.Location, type.FullName, methodName);
if (result != null)
{
throw new InvalidOperationException("The specified call was not expected to return a value.");
}
}
public T ExecuteInHostProcess<T>(Type type, string methodName)
{
var objectUri = _integrationService.Execute(type.Assembly.Location, type.FullName, methodName);
if (objectUri == null)
{
throw new InvalidOperationException("The specified call was expected to return a value.");
}
return (T)Activator.GetObject(typeof(T), $"{_integrationService.BaseUri}/{objectUri}");
}
public bool IsRunning => !_hostProcess.HasExited;
......@@ -68,10 +88,6 @@ public VisualStudioInstance(Process process, DTE dte)
public Workspace Workspace => _workspace.Value;
internal IntegrationService IntegrationService => _integrationService;
#region Automation Elements
public async Task ClickAutomationElementAsync(string elementName, bool recursive = false)
{
var element = await FindAutomationElementAsync(elementName, recursive).ConfigureAwait(false);
......@@ -111,9 +127,6 @@ private async Task<AutomationElement> FindAutomationElementAsync(string elementN
return element;
}
#endregion
#region Cleanup
public void Cleanup()
{
CleanupOpenSolution();
......@@ -124,15 +137,15 @@ public void Cleanup()
private void CleanupInteractiveWindow()
{
var csharpInteractiveWindow = _dte.LocateWindow(CSharpInteractiveWindow.DteWindowTitle);
var csharpInteractiveWindow = this.DTE.LocateWindow(CSharpInteractiveWindow.DteWindowTitle);
IntegrationHelper.RetryRpcCall(() => csharpInteractiveWindow?.Close());
}
private void CleanupOpenSolution()
{
IntegrationHelper.RetryRpcCall(() => _dte.Documents.CloseAll(vsSaveChanges.vsSaveChangesNo));
IntegrationHelper.RetryRpcCall(() => this.DTE.Documents.CloseAll(vsSaveChanges.vsSaveChangesNo));
var dteSolution = IntegrationHelper.RetryRpcCall(() => _dte.Solution);
var dteSolution = IntegrationHelper.RetryRpcCall(() => this.DTE.Solution);
if (dteSolution != null)
{
......@@ -173,27 +186,25 @@ private void CleanupOpenSolution()
private void CleanupWaitingService()
{
_integrationService.Execute(
ExecuteInHostProcess(
type: typeof(RemotingHelper),
methodName: nameof(RemotingHelper.CleanupWaitingService));
}
private void CleanupWorkspace()
{
_integrationService.Execute(
ExecuteInHostProcess(
type: typeof(RemotingHelper),
methodName: nameof(RemotingHelper.CleanupWorkspace));
}
#endregion
#region Close
public void Close()
{
if (!IsRunning)
{
return;
}
Cleanup();
CloseRemotingService();
......@@ -202,7 +213,7 @@ public void Close()
private void CloseHostProcess()
{
IntegrationHelper.RetryRpcCall(() => _dte.Quit());
IntegrationHelper.RetryRpcCall(() => this.DTE.Quit());
IntegrationHelper.KillProcess(_hostProcess);
}
......@@ -211,9 +222,9 @@ private void CloseRemotingService()
{
try
{
if ((IntegrationHelper.RetryRpcCall(() => _dte?.Commands.Item(VisualStudioCommandNames.VsStopServiceCommand).IsAvailable).GetValueOrDefault()))
if ((IntegrationHelper.RetryRpcCall(() => this.DTE?.Commands.Item(VisualStudioCommandNames.VsStopServiceCommand).IsAvailable).GetValueOrDefault()))
{
_dte.ExecuteCommandAsync(VisualStudioCommandNames.VsStopServiceCommand).GetAwaiter().GetResult();
this.DTE.ExecuteCommandAsync(VisualStudioCommandNames.VsStopServiceCommand).GetAwaiter().GetResult();
}
}
finally
......@@ -224,6 +235,5 @@ private void CloseRemotingService()
}
}
}
#endregion
}
}
......@@ -11,22 +11,20 @@ namespace Roslyn.VisualStudio.Test.Utilities
/// </summary>
public sealed class VisualStudioInstanceContext : IDisposable
{
private readonly VisualStudioInstance _instance;
public VisualStudioInstance Instance { get; }
private readonly VisualStudioInstanceFactory _instanceFactory;
internal VisualStudioInstanceContext(VisualStudioInstance instance, VisualStudioInstanceFactory instanceFactory)
{
_instance = instance;
this.Instance = instance;
_instanceFactory = instanceFactory;
}
public VisualStudioInstance Instance => _instance;
public void Dispose()
{
try
{
_instance.Cleanup();
this.Instance.Cleanup();
_instanceFactory.NotifyCurrentInstanceContextDisposed(canReuse: true);
}
catch (Exception)
......
......@@ -19,7 +19,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Extensions\DteExtensions.cs" />
<Compile Include="Extensions\IntegrationServiceExtensions.cs" />
<Compile Include="IntegrationHelper.cs" />
<Compile Include="Interop\Kernel32.cs" />
<Compile Include="Remoting\EditorWindowWrapper.cs" />
......
// 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.Reflection;
using Roslyn.VisualStudio.Test.Utilities.Remoting;
namespace Roslyn.VisualStudio.Test.Utilities
......@@ -18,10 +17,9 @@ internal EditorWindow(VisualStudioInstance visualStudioInstance)
_visualStudioInstance = visualStudioInstance;
// Create MarshalByRefObject that can be used to execute code in the VS process.
_editorWindowWrapper = _visualStudioInstance.IntegrationService.Execute<EditorWindowWrapper>(
_editorWindowWrapper = _visualStudioInstance.ExecuteInHostProcess<EditorWindowWrapper>(
type: typeof(EditorWindowWrapper),
methodName: nameof(EditorWindowWrapper.Create),
bindingFlags: BindingFlags.Public | BindingFlags.Static);
methodName: nameof(EditorWindowWrapper.Create));
}
public string GetText() => _editorWindowWrapper.GetText();
......@@ -37,7 +35,7 @@ public void Activate()
{
IntegrationHelper.RetryRpcCall(() =>
{
_visualStudioInstance.Dte.ActiveDocument.Activate();
_visualStudioInstance.DTE.ActiveDocument.Activate();
});
}
}
......
......@@ -98,7 +98,7 @@ public void SendKey(VirtualKey virtualKey, ShiftState shiftState = 0)
inputBlocked = IntegrationHelper.BlockInput();
foregroundWindow = IntegrationHelper.GetForegroundWindow();
_visualStudioInstance.IntegrationService.Execute(
_visualStudioInstance.ExecuteInHostProcess(
type: typeof(RemotingHelper),
methodName: nameof(RemotingHelper.ActivateMainWindow));
......
......@@ -25,12 +25,11 @@ internal InteractiveWindow(VisualStudioInstance visualStudioInstance, string dte
// We have to show the window at least once to ensure the interactive service is loaded.
ShowAsync(waitForPrompt: false).GetAwaiter().GetResult();
var dteWindow = IntegrationHelper.WaitForNotNullAsync(() => _visualStudioInstance.Dte.LocateWindow(dteWindowTitle)).GetAwaiter().GetResult();
var dteWindow = IntegrationHelper.WaitForNotNullAsync(() => _visualStudioInstance.DTE.LocateWindow(dteWindowTitle)).GetAwaiter().GetResult();
IntegrationHelper.RetryRpcCall(() => dteWindow.Close());
// Return a wrapper to the actual interactive window service that exists in the host process
var integrationService = _visualStudioInstance.IntegrationService;
_interactiveWindowWrapper = integrationService.Execute<InteractiveWindowWrapper>(typeof(InteractiveWindowWrapper), createMethodName);
_interactiveWindowWrapper = _visualStudioInstance.ExecuteInHostProcess<InteractiveWindowWrapper>(typeof(InteractiveWindowWrapper), createMethodName);
}
/// <summary>
......@@ -94,7 +93,7 @@ public string GetReplTextWithoutPrompt()
public async Task ResetAsync(bool waitForPrompt = true)
{
await _visualStudioInstance.Dte.ExecuteCommandAsync(DteReplResetCommand).ConfigureAwait(continueOnCapturedContext: false);
await _visualStudioInstance.DTE.ExecuteCommandAsync(DteReplResetCommand).ConfigureAwait(continueOnCapturedContext: false);
if (waitForPrompt)
{
......@@ -104,7 +103,7 @@ public async Task ResetAsync(bool waitForPrompt = true)
public async Task ShowAsync(bool waitForPrompt = true)
{
await _visualStudioInstance.Dte.ExecuteCommandAsync(_dteViewCommand).ConfigureAwait(continueOnCapturedContext: false);
await _visualStudioInstance.DTE.ExecuteCommandAsync(_dteViewCommand).ConfigureAwait(continueOnCapturedContext: false);
if (waitForPrompt)
{
......
......@@ -23,7 +23,7 @@ internal SolutionExplorer(VisualStudioInstance visualStudio)
/// <summary>Creates and loads a new solution in the host process, optionally saving the existing solution if one exists.</summary>
public Solution CreateSolution(string solutionName, bool saveExistingSolutionIfExists = false)
{
var dteSolution = IntegrationHelper.RetryRpcCall(() => _visualStudio.Dte.Solution);
var dteSolution = IntegrationHelper.RetryRpcCall(() => _visualStudio.DTE.Solution);
if (IntegrationHelper.RetryRpcCall(() => dteSolution.IsOpen))
{
......@@ -41,7 +41,7 @@ public Solution CreateSolution(string solutionName, bool saveExistingSolutionIfE
public Solution OpenSolution(string path, bool saveExistingSolutionIfExists = false)
{
var dteSolution = IntegrationHelper.RetryRpcCall(() => _visualStudio.Dte.Solution);
var dteSolution = IntegrationHelper.RetryRpcCall(() => _visualStudio.DTE.Solution);
if (IntegrationHelper.RetryRpcCall(() => dteSolution.IsOpen))
{
......@@ -54,6 +54,6 @@ public Solution OpenSolution(string path, bool saveExistingSolutionIfExists = fa
return _solution;
}
public void CloseSolution(bool saveFirst = false) => IntegrationHelper.RetryRpcCall(() => _visualStudio.Dte.Solution.Close(saveFirst));
public void CloseSolution(bool saveFirst = false) => IntegrationHelper.RetryRpcCall(() => _visualStudio.DTE.Solution.Close(saveFirst));
}
}
......@@ -13,8 +13,9 @@ internal Workspace(VisualStudioInstance visualStudioInstance)
{
_visualStudioInstance = visualStudioInstance;
var integrationService = _visualStudioInstance.IntegrationService;
_workspaceWrapper = integrationService.Execute<WorkspaceWrapper>(typeof(WorkspaceWrapper), nameof(WorkspaceWrapper.Create));
_workspaceWrapper = _visualStudioInstance.ExecuteInHostProcess<WorkspaceWrapper>(
type: typeof(WorkspaceWrapper),
methodName: nameof(WorkspaceWrapper.Create));
}
public bool UseSuggestionMode
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册