From b389d275a2ffa9c6dd8ac0c9767add6df42cec25 Mon Sep 17 00:00:00 2001 From: Mitchell Sternke Date: Fri, 21 Oct 2016 17:49:37 -0700 Subject: [PATCH] Added test driver program for service host (#113) * Added test driver program for service host * Fix typo --- .../Driver/ServiceTestDriver.cs | 39 ++++ .../Driver/TestDriverBase.cs | 173 ++++++++++++++++++ .../Program.cs | 66 +++++++ .../Tests/ExampleTests.cs | 38 ++++ .../project.json | 29 +++ 5 files changed, 345 insertions(+) create mode 100644 test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/TestDriverBase.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/ExampleTests.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs new file mode 100644 index 00000000..ac7e76c0 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// The following is based upon code from PowerShell Editor Services +// License: https://github.com/PowerShell/PowerShellEditorServices/blob/develop/LICENSE +// + +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel; + +namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver +{ + /// + /// Test driver for the service host + /// + public class ServiceTestDriver : TestDriverBase + { + public ServiceTestDriver(string serviceHostExecutable) + { + var clientChannel = new StdioClientChannel(serviceHostExecutable); + this.protocolClient = new ProtocolEndpoint(clientChannel, MessageProtocolType.LanguageServer); + } + + public async Task Start() + { + await this.protocolClient.Start(); + await Task.Delay(1000); // Wait for the service host to start + } + + public async Task Stop() + { + await this.protocolClient.Stop(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/TestDriverBase.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/TestDriverBase.cs new file mode 100644 index 00000000..7f86764a --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/TestDriverBase.cs @@ -0,0 +1,173 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// The following is based upon code from PowerShell Editor Services +// License: https://github.com/PowerShell/PowerShellEditorServices/blob/develop/LICENSE +// + +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver +{ + /// + /// Wraps the ProtocolEndpoint class with queues to handle events/requests + /// + public class TestDriverBase + { + protected ProtocolEndpoint protocolClient; + + private ConcurrentDictionary> eventQueuePerType = + new ConcurrentDictionary>(); + + private ConcurrentDictionary> requestQueuePerType = + new ConcurrentDictionary>(); + + public Task SendRequest( + RequestType requestType, + TParams requestParams) + { + return + this.protocolClient.SendRequest( + requestType, + requestParams); + } + + public Task SendEvent(EventType eventType, TParams eventParams) + { + return + this.protocolClient.SendEvent( + eventType, + eventParams); + } + + public void QueueEventsForType(EventType eventType) + { + var eventQueue = + this.eventQueuePerType.AddOrUpdate( + eventType.MethodName, + new AsyncQueue(), + (key, queue) => queue); + + this.protocolClient.SetEventHandler( + eventType, + (p, ctx) => + { + return eventQueue.EnqueueAsync(p); + }); + } + + public async Task WaitForEvent( + EventType eventType, + int timeoutMilliseconds = 5000) + { + Task eventTask = null; + + // Use the event queue if one has been registered + AsyncQueue eventQueue = null; + if (this.eventQueuePerType.TryGetValue(eventType.MethodName, out eventQueue)) + { + eventTask = + eventQueue + .DequeueAsync() + .ContinueWith( + task => (TParams)task.Result); + } + else + { + TaskCompletionSource eventTaskSource = new TaskCompletionSource(); + + this.protocolClient.SetEventHandler( + eventType, + (p, ctx) => + { + if (!eventTaskSource.Task.IsCompleted) + { + eventTaskSource.SetResult(p); + } + + return Task.FromResult(true); + }, + true); // Override any existing handler + + eventTask = eventTaskSource.Task; + } + + await + Task.WhenAny( + eventTask, + Task.Delay(timeoutMilliseconds)); + + if (!eventTask.IsCompleted) + { + throw new TimeoutException( + string.Format( + "Timed out waiting for '{0}' event!", + eventType.MethodName)); + } + + return await eventTask; + } + + public async Task>> WaitForRequest( + RequestType requestType, + int timeoutMilliseconds = 5000) + { + Task>> requestTask = null; + + // Use the request queue if one has been registered + AsyncQueue requestQueue = null; + if (this.requestQueuePerType.TryGetValue(requestType.MethodName, out requestQueue)) + { + requestTask = + requestQueue + .DequeueAsync() + .ContinueWith( + task => (Tuple>)task.Result); + } + else + { + var requestTaskSource = + new TaskCompletionSource>>(); + + this.protocolClient.SetRequestHandler( + requestType, + (p, ctx) => + { + if (!requestTaskSource.Task.IsCompleted) + { + requestTaskSource.SetResult( + new Tuple>(p, ctx)); + } + + return Task.FromResult(true); + }); + + requestTask = requestTaskSource.Task; + } + + await + Task.WhenAny( + requestTask, + Task.Delay(timeoutMilliseconds)); + + if (!requestTask.IsCompleted) + { + throw new TimeoutException( + string.Format( + "Timed out waiting for '{0}' request!", + requestType.MethodName)); + } + + return await requestTask; + } + } +} + diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs new file mode 100644 index 00000000..55b0a552 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver; + +namespace Microsoft.SqlTools.ServiceLayer.TestDriver +{ + internal class Program + { + internal static void Main(string[] args) + { + if (args.Length < 1) + { + Console.WriteLine( "Microsoft.SqlTools.ServiceLayer.TestDriver.exe [tests]" + Environment.NewLine + + " is the path to the Microsoft.SqlTools.ServiceLayer.exe executable" + Environment.NewLine + + " [tests] is a space-separated list of tests to run." + Environment.NewLine + + " They are qualified within the Microsoft.SqlTools.ServiceLayer.TestDriver.Tests namespace"); + Environment.Exit(0); + } + + Task.Run(async () => + { + var serviceHostExecutable = args[0]; + var tests = args.Skip(1); + + foreach (var test in tests) + { + ServiceTestDriver driver = null; + + try + { + driver = new ServiceTestDriver(serviceHostExecutable); + + var className = test.Substring(0, test.LastIndexOf('.')); + var methodName = test.Substring(test.LastIndexOf('.') + 1); + + var type = Type.GetType("Microsoft.SqlTools.ServiceLayer.TestDriver.Tests." + className); + var typeInstance = Activator.CreateInstance(type); + MethodInfo methodInfo = type.GetMethod(methodName); + + await driver.Start(); + Console.WriteLine("Running test " + test); + await (Task)methodInfo.Invoke(typeInstance, new object[] {driver}); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + finally + { + if (driver != null) + { + await driver.Stop(); + } + } + } + }).Wait(); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/ExampleTests.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/ExampleTests.cs new file mode 100644 index 00000000..21fe7552 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/ExampleTests.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver; + +namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests +{ + public class ExampleTests + { + /// + /// Example test that performs a connect, then disconnect. + /// All tests must have the same signature of returning an async Task + /// and taking in a ServiceTestDriver as a parameter. + /// + public async Task ConnectDisconnectTest(ServiceTestDriver driver) + { + var connectParams = new ConnectParams(); + connectParams.OwnerUri = "file"; + connectParams.Connection = new ConnectionDetails(); + connectParams.Connection.ServerName = "localhost"; + connectParams.Connection.AuthenticationType = "Integrated"; + + var result = await driver.SendRequest(ConnectionRequest.Type, connectParams); + if (result) + { + await driver.WaitForEvent(ConnectionCompleteNotification.Type); + + var disconnectParams = new DisconnectParams(); + disconnectParams.OwnerUri = "file"; + var result2 = await driver.SendRequest(DisconnectRequest.Type, disconnectParams); + if (result2) + { + Console.WriteLine("success"); + } + } + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json new file mode 100644 index 00000000..e2b39b6c --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json @@ -0,0 +1,29 @@ +{ + "name": "Microsoft.SqlTools.ServiceLayer.TestDriver", + "version": "1.0.0-*", + "buildOptions": { + "debugType": "portable", + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.SqlTools.ServiceLayer": { + "target": "project" + } + }, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0" + } + }, + "imports": [ + "dotnet5.4", + "portable-net451+win8" + ], + } + }, + "runtimes": { + "win7-x64": {} + } +}