diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs index ef7b842d..3b3d11c8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs @@ -163,8 +163,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol TParams typedParams = default(TParams); if (eventMessage.Contents != null) { - // TODO: Catch parse errors! - typedParams = eventMessage.Contents.ToObject(); + try + { + typedParams = eventMessage.Contents.ToObject(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Verbose, ex.ToString()); + } } return eventHandler(typedParams, eventContext); diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs index 9e387f8c..09beae29 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs @@ -152,6 +152,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts } } + /// + /// Default constructor, used for deserializing JSON RPC only + /// + public DbColumnWrapper() + { + } + #region Properties /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultMessage.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultMessage.cs index 27e6713b..90f66015 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultMessage.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultMessage.cs @@ -40,5 +40,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts Time = DateTime.Now.ToString("o"); Message = message; } + + /// + /// Default constructor, used for deserializing JSON RPC only + /// + public ResultMessage() + { + } } } \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs index ac7e76c0..5dd7365a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs @@ -8,9 +8,12 @@ // License: https://github.com/PowerShell/PowerShellEditorServices/blob/develop/LICENSE // +using System; using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver { @@ -19,18 +22,38 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver /// public class ServiceTestDriver : TestDriverBase { - public ServiceTestDriver(string serviceHostExecutable) + /// + /// Environment variable that stores the path to the service host executable. + /// + public static string ServiceHostEnvironmentVariable { - var clientChannel = new StdioClientChannel(serviceHostExecutable); + get { return "SQLTOOLSSERVICE_EXE"; } + } + + public ServiceTestDriver() + { + string serviceHostExecutable = Environment.GetEnvironmentVariable(ServiceHostEnvironmentVariable); + + var clientChannel = new StdioClientChannel(serviceHostExecutable, "--enable-logging"); this.protocolClient = new ProtocolEndpoint(clientChannel, MessageProtocolType.LanguageServer); } + /// + /// Start the test driver, and launch the sqltoolsservice executable + /// public async Task Start() { await this.protocolClient.Start(); await Task.Delay(1000); // Wait for the service host to start + + // Setup events to queue for testing + this.QueueEventsForType(ConnectionCompleteNotification.Type); + this.QueueEventsForType(QueryExecuteCompleteEvent.Type); } + /// + /// Stop the test driver, and shutdown the sqltoolsservice executable + /// public async Task Stop() { await this.protocolClient.Stop(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs index 55b0a552..c06173fd 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Program.cs @@ -4,10 +4,10 @@ // using System; -using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver; +using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.TestDriver { @@ -17,48 +17,37 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver { 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 + + Console.WriteLine( "Microsoft.SqlTools.ServiceLayer.TestDriver.exe [tests]" + 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"); + " They are qualified within the Microsoft.SqlTools.ServiceLayer.TestDriver.Tests namespace" + Environment.NewLine + + "Be sure to set the environment variable " + ServiceTestDriver.ServiceHostEnvironmentVariable + " to the full path of the sqltoolsservice executable."); Environment.Exit(0); } + Logger.Initialize("testdriver", LogLevel.Verbose); + Task.Run(async () => { - var serviceHostExecutable = args[0]; - var tests = args.Skip(1); - - foreach (var test in tests) + foreach (var test in args) { - 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); + using (var typeInstance = (IDisposable)Activator.CreateInstance(type)) + { + MethodInfo methodInfo = type.GetMethod(methodName); - await driver.Start(); - Console.WriteLine("Running test " + test); - await (Task)methodInfo.Invoke(typeInstance, new object[] {driver}); + Console.WriteLine("Running test " + test); + await (Task)methodInfo.Invoke(typeInstance, null); + } } 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 deleted file mode 100644 index 21fe7552..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/ExampleTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -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/Tests/QueryExecutionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/QueryExecutionTests.cs new file mode 100644 index 00000000..f102eea4 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/QueryExecutionTests.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests +{ + public class QueryExecutionTests : TestBase + { + [Fact] + public async Task TestQueryingAfterCompletionRequests() + { + string ownerUri = System.IO.Path.GetTempFileName(); + string query = "SELECT * FROM sys.objects"; + List tasks = new List(); + + await Connect(ownerUri, ConnectionTestUtils.AzureTestServerConnection); + Enumerable.Range(0, 10).ToList().ForEach(arg => tasks.Add(RequestCompletion(ownerUri, query, 0, 10))); + var queryTask = RunQuery(ownerUri, query); + tasks.Add(queryTask); + await Task.WhenAll(tasks); + + Assert.NotNull(queryTask.Result); + Assert.NotNull(queryTask.Result.BatchSummaries); + + await Connect(ownerUri, ConnectionTestUtils.DataToolsTelemetryAzureConnection); + tasks.Clear(); + Enumerable.Range(0, 10).ToList().ForEach(arg => tasks.Add(RequestCompletion(ownerUri, query, 0, 10))); + queryTask = RunQuery(ownerUri, query); + tasks.Add(queryTask); + await Task.WhenAll(tasks); + + Assert.NotNull(queryTask.Result); + Assert.NotNull(queryTask.Result.BatchSummaries); + + await Connect(ownerUri, ConnectionTestUtils.SqlDataToolsAzureConnection); + tasks.Clear(); + Enumerable.Range(0, 10).ToList().ForEach(arg => tasks.Add(RequestCompletion(ownerUri, query, 0, 10))); + queryTask = RunQuery(ownerUri, query); + tasks.Add(queryTask); + await Task.WhenAll(tasks); + + Assert.NotNull(queryTask.Result); + Assert.NotNull(queryTask.Result.BatchSummaries); + + await Disconnect(ownerUri); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/TestBase.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/TestBase.cs new file mode 100644 index 00000000..43dd45ca --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Tests/TestBase.cs @@ -0,0 +1,123 @@ +// +// 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.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests +{ + /// + /// Base class for all test suites run by the test driver + /// + public class TestBase : IDisposable + { + public TestBase() + { + Driver = new ServiceTestDriver(); + Driver.Start().Wait(); + } + + public void Dispose() + { + Driver.Stop().Wait(); + } + + /// + /// The driver object used to read/write data to the service + /// + public ServiceTestDriver Driver + { + get; + private set; + } + + private object fileLock = new Object(); + + /// + /// Request a new connection to be created + /// + /// True if the connection completed successfully + protected async Task Connect(string ownerUri, ConnectParams connectParams) + { + connectParams.OwnerUri = ownerUri; + var connectResult = await Driver.SendRequest(ConnectionRequest.Type, connectParams); + if (connectResult) + { + var completeEvent = await Driver.WaitForEvent(ConnectionCompleteNotification.Type); + return !string.IsNullOrEmpty(completeEvent.ConnectionId); + } + else + { + return false; + } + } + + /// + /// Request a disconnect + /// + protected async Task Disconnect(string ownerUri) + { + var disconnectParams = new DisconnectParams(); + disconnectParams.OwnerUri = ownerUri; + + var disconnectResult = await Driver.SendRequest(DisconnectRequest.Type, disconnectParams); + return disconnectResult; + } + + /// + /// Request a list of completion items for a position in a block of text + /// + protected async Task RequestCompletion(string ownerUri, string text, int line, int character) + { + // Write the text to a backing file + lock (fileLock) + { + System.IO.File.WriteAllText(ownerUri, text); + } + + var completionParams = new TextDocumentPosition(); + completionParams.TextDocument = new TextDocumentIdentifier(); + completionParams.TextDocument.Uri = ownerUri; + completionParams.Position = new Position(); + completionParams.Position.Line = line; + completionParams.Position.Character = character; + + var result = await Driver.SendRequest(CompletionRequest.Type, completionParams); + return result; + } + + /// + /// Run a query using a given connection bound to a URI + /// + protected async Task RunQuery(string ownerUri, string query) + { + // Write the query text to a backing file + lock (fileLock) + { + System.IO.File.WriteAllText(ownerUri, query); + } + + var queryParams = new QueryExecuteParams(); + queryParams.OwnerUri = ownerUri; + queryParams.QuerySelection = null; + + var result = await Driver.SendRequest(QueryExecuteRequest.Type, queryParams); + if (result != null && string.IsNullOrEmpty(result.Messages)) + { + var eventResult = await Driver.WaitForEvent(QueryExecuteCompleteEvent.Type); + return eventResult; + } + else + { + return null; + } + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json index e2b39b6c..15c7d9a2 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/project.json @@ -6,10 +6,13 @@ "emitEntryPoint": true }, "dependencies": { + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-rc2-192208-24", "Microsoft.SqlTools.ServiceLayer": { "target": "project" } }, + "testRunner": "xunit", "frameworks": { "netcoreapp1.0": { "dependencies": {