// // 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.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests; using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.Common { /// /// Service class to execute SQL tools commands using the test driver or calling the service classed directly /// public sealed class TestServiceDriverProvider : IDisposable { private bool isRunning = false; private TestConnectionProfileService testConnectionService; public TestServiceDriverProvider() { Driver = new ServiceTestDriver(); Driver.Start().Wait(); this.isRunning = true; } public void Dispose() { if (this.isRunning) { WaitForExit(); } } public void WaitForExit() { try { this.isRunning = false; Driver.Stop().Wait(); Console.WriteLine("Successfully killed process."); } catch (Exception e) { Console.WriteLine($"Exception while waiting for service exit: {e.Message}"); } } /// /// The driver object used to read/write data to the service /// public ServiceTestDriver Driver { get; private set; } public TestConnectionProfileService TestConnectionService { get { return (testConnectionService = testConnectionService ?? TestConnectionProfileService.Instance); } } private object fileLock = new Object(); /// /// Request a new connection to be created /// /// True if the connection completed successfully public async Task Connect(string ownerUri, ConnectParams connectParams, int timeout = 15000) { connectParams.OwnerUri = ownerUri; var connectResult = await Driver.SendRequest(ConnectionRequest.Type, connectParams); if (connectResult) { var completeEvent = await Driver.WaitForEvent(ConnectionCompleteNotification.Type, timeout); return !string.IsNullOrEmpty(completeEvent.ConnectionId); } else { return false; } } /// /// Request a new connection to be created for given query /// public async Task ConnectForQuery(TestServerType serverType, string query, string ownerUri, string databaseName = null, int timeout = 15000) { if (!string.IsNullOrEmpty(query)) { WriteToFile(ownerUri, query); } return await Connect(serverType, ownerUri, databaseName, timeout); } /// /// Request a new connection to be created for url /// public async Task Connect(TestServerType serverType, string ownerUri, string databaseName = null, int timeout = 15000) { var connectParams = GetConnectionParameters(serverType, databaseName); bool connected = await Connect(ownerUri, connectParams, timeout); Assert.True(connected, "Connection is successful"); Console.WriteLine($"Connection to {connectParams.Connection.ServerName} is successful"); return connected; } /// /// Request a disconnect /// public async Task Disconnect(string ownerUri) { var disconnectParams = new DisconnectParams(); disconnectParams.OwnerUri = ownerUri; var disconnectResult = await Driver.SendRequest(DisconnectRequest.Type, disconnectParams); return disconnectResult; } /// /// Request a cancel connect /// public async Task CancelConnect(string ownerUri) { var cancelParams = new CancelConnectParams(); cancelParams.OwnerUri = ownerUri; return await Driver.SendRequest(CancelConnectRequest.Type, cancelParams); } /// /// Request a cancel connect /// public async Task ListDatabases(string ownerUri) { var listParams = new ListDatabasesParams(); listParams.OwnerUri = ownerUri; return await Driver.SendRequest(ListDatabasesRequest.Type, listParams); } /// /// Request the active SQL script is parsed for errors /// public async Task RequestQueryExecuteSubset(SubsetParams subsetParams) { return await Driver.SendRequest(SubsetRequest.Type, subsetParams); } /// /// Request the active SQL script is parsed for errors /// public async Task RequestOpenDocumentNotification(DidOpenTextDocumentNotification openParams) { await Driver.SendEvent(DidOpenTextDocumentNotification.Type, openParams); } /// /// Request a configuration change notification /// public async Task RequestChangeConfigurationNotification(DidChangeConfigurationParams configParams) { await Driver.SendEvent(DidChangeConfigurationNotification.Type, configParams); } /// /// /// Request the active SQL script is parsed for errors /// public async Task RequestChangeTextDocumentNotification(DidChangeTextDocumentParams changeParams) { await Driver.SendEvent(DidChangeTextDocumentNotification.Type, changeParams); } /// /// Request completion item resolve to look-up additional info /// public async Task RequestResolveCompletion(CompletionItem item) { var result = await Driver.SendRequest(CompletionResolveRequest.Type, item); return result; } /// /// Returns database connection parameters for given server type /// public ConnectParams GetConnectionParameters(TestServerType serverType, string databaseName = null) { return TestConnectionService.GetConnectionParameters(serverType, databaseName); } /// /// Request a list of completion items for a position in a block of text /// public 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; } /// /// Request a a hover tooltop /// public async Task RequestHover(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 { TextDocument = new TextDocumentIdentifier { Uri = ownerUri }, Position = new Position { Line = line, Character = character } }; var result = await Driver.SendRequest(HoverRequest.Type, completionParams); return result; } /// /// Request definition( peek definition/go to definition) for a sql object in a sql string /// public async Task RequestDefinition(string ownerUri, string text, int line, int character) { // Write the text to a backing file lock (fileLock) { System.IO.File.WriteAllText(ownerUri, text); } var definitionParams = new TextDocumentPosition(); definitionParams.TextDocument = new TextDocumentIdentifier(); definitionParams.TextDocument.Uri = ownerUri; definitionParams.Position = new Position(); definitionParams.Position.Line = line; definitionParams.Position.Character = character; // Send definition request var result = await Driver.SendRequest(DefinitionRequest.Type, definitionParams); return result; } /// /// Run a query using a given connection bound to a URI /// public async Task RunQueryAndWaitToComplete(string ownerUri, string query, int timeoutMilliseconds = 5000) { // Write the query text to a backing file WriteToFile(ownerUri, query); var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = ownerUri, QuerySelection = null }; var result = await Driver.SendRequest(ExecuteDocumentSelectionRequest.Type, queryParams); if (result != null) { var eventResult = await Driver.WaitForEvent(QueryCompleteEvent.Type, timeoutMilliseconds); return eventResult; } else { return null; } } /// /// Run a query using a given connection bound to a URI. This method only waits for the initial response from query /// execution (QueryExecuteResult). It is up to the caller to wait for the QueryCompleteEvent if they are interested. /// public async Task RunQueryAsync(string ownerUri, string query, int timeoutMilliseconds = 5000) { WriteToFile(ownerUri, query); var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = ownerUri, QuerySelection = null }; return await Driver.SendRequest(ExecuteDocumentSelectionRequest.Type, queryParams); } public async Task RunQuery(TestServerType serverType, string databaseName, string query) { using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) { await ConnectForQuery(serverType, query, queryTempFile.FilePath, databaseName); var queryResult = await CalculateRunTime(() => RunQueryAndWaitToComplete(queryTempFile.FilePath, query, 50000), false); Assert.NotNull(queryResult); Assert.NotNull(queryResult.BatchSummaries); await Disconnect(queryTempFile.FilePath); } } public async Task CalculateRunTime(Func> testToRun, bool printResult, [CallerMemberName] string testName = "") { TestTimer timer = new TestTimer() { PrintResult = printResult }; T result = await testToRun(); timer.EndAndPrint(testName); return result; } public async Task ExecuteWithTimeout(TestTimer timer, int timeout, Func> repeatedCode, TimeSpan? delay = null, [CallerMemberName] string testName = "") { while (true) { if (await repeatedCode()) { timer.EndAndPrint(testName); break; } if (timer.TotalMilliSecondsUntilNow >= timeout) { Assert.True(false, $"{testName} timed out after {timeout} milliseconds"); break; } if (delay.HasValue) { await Task.Delay(delay.Value); } } } /// /// Request to cancel an executing query /// public async Task CancelQuery(string ownerUri) { var cancelParams = new QueryCancelParams { OwnerUri = ownerUri }; var result = await Driver.SendRequest(QueryCancelRequest.Type, cancelParams); return result; } /// /// Request to save query results as CSV /// public async Task SaveAsCsv(string ownerUri, string filename, int batchIndex, int resultSetIndex) { var saveParams = new SaveResultsAsCsvRequestParams { OwnerUri = ownerUri, BatchIndex = batchIndex, ResultSetIndex = resultSetIndex, FilePath = filename }; var result = await Driver.SendRequest(SaveResultsAsCsvRequest.Type, saveParams); return result; } /// /// Request to save query results as JSON /// public async Task SaveAsJson(string ownerUri, string filename, int batchIndex, int resultSetIndex) { var saveParams = new SaveResultsAsJsonRequestParams { OwnerUri = ownerUri, BatchIndex = batchIndex, ResultSetIndex = resultSetIndex, FilePath = filename }; var result = await Driver.SendRequest(SaveResultsAsJsonRequest.Type, saveParams); return result; } /// /// Request a subset of results from a query /// public async Task ExecuteSubset(string ownerUri, int batchIndex, int resultSetIndex, int rowStartIndex, int rowCount) { var subsetParams = new SubsetParams(); subsetParams.OwnerUri = ownerUri; subsetParams.BatchIndex = batchIndex; subsetParams.ResultSetIndex = resultSetIndex; subsetParams.RowsStartIndex = rowStartIndex; subsetParams.RowsCount = rowCount; var result = await Driver.SendRequest(SubsetRequest.Type, subsetParams); return result; } public async Task ListScriptingObjects(ScriptingListObjectsParams parameters) { return await Driver.SendRequest(ScriptingListObjectsRequest.Type, parameters); } public async Task Script(ScriptingParams parameters) { return await Driver.SendRequest(ScriptingRequest.Type, parameters); } public async Task CancelScript(string operationId) { return await Driver.SendRequest(ScriptingCancelRequest.Type, new ScriptingCancelParams { OperationId = operationId }); } /// /// Waits for a message to be returned by the service /// /// A message from the service layer public async Task WaitForMessage() { return await Driver.WaitForEvent(MessageEvent.Type); } public void WriteToFile(string ownerUri, string query) { lock (fileLock) { System.IO.File.WriteAllText(ownerUri, query); } } public bool TryGetEvent(EventType eventType, out T value) { value = default(T); try { Task t = this.Driver.WaitForEvent(eventType, TimeSpan.Zero); value = t.Result; return true; } catch (Exception) { return false; } } public void AssertEventNotQueued(EventType eventType) { T temp; if (TryGetEvent(eventType, out temp)) { Assert.True(false, string.Format("Event of type {0} was found in the queue.", eventType.GetType().FullName, temp.ToString())); } } } }