// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System; using System.Runtime.CompilerServices; using System.Threading; 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.ObjectExplorer.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.TestDriver.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using NUnit.Framework; 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(TestRunner.Instance.ExecutableFilePath); 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 list of completion items for a position in a block of text /// public async Task RequestRebuildIntelliSense(string ownerUri) { var rebuildIntelliSenseParams = new RebuildIntelliSenseParams(); rebuildIntelliSenseParams.OwnerUri = ownerUri; await Driver.SendEvent(RebuildIntelliSenseNotification.Type, rebuildIntelliSenseParams); } /// /// 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); return await RunQueryAndWaitToComplete(ownerUri, timeoutMilliseconds); } /// /// Run a query using a given connection bound to a URI /// public async Task RunQueryAndWaitToComplete(string ownerUri, int timeoutMilliseconds = 5000) { 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 /// public async Task RunQueryAndWaitToStart(string ownerUri, string query, int timeoutMilliseconds = 5000) { // Write the query text to a backing file WriteToFile(ownerUri, query); return await RunQueryAndWaitToStart(ownerUri, timeoutMilliseconds); } /// /// Run a query using a given connection bound to a URI /// public async Task RunQueryAndWaitToStart(string ownerUri, int timeoutMilliseconds = 5000) { var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = ownerUri, QuerySelection = null }; var result = await Driver.SendRequest(ExecuteDocumentSelectionRequest.Type, queryParams); if (result != null) { var eventResult = await Driver.WaitForEvent(BatchStartEvent.Type, timeoutMilliseconds); return eventResult; } else { return null; } } public async Task RequestObjectExplorerCreateSession(ConnectionDetails connectionDetails, int timeoutMilliseconds = 5000) { var result = await Driver.SendRequest(CreateSessionRequest.Type, connectionDetails); if (result != null) { var eventResult = await Driver.WaitForEvent(CreateSessionCompleteNotification.Type, timeoutMilliseconds); return eventResult; } else { return null; } } public async Task RequestObjectExplorerExpand(ExpandParams expandParams, int timeoutMilliseconds = 5000) { var result = await Driver.SendRequest(ExpandRequest.Type, expandParams); if (result) { var eventResult = await Driver.WaitForEvent(ExpandCompleteNotification.Type, timeoutMilliseconds); return eventResult; } else { return null; } } public async Task RequestScript(ScriptingParams scriptingParams, int timeoutMilliseconds = 5000) { var result = await Driver.SendRequest(ScriptingRequest.Type, scriptingParams); return result; } public async Task RequestObjectExplorerCloseSession(CloseSessionParams closeSessionParams, int timeoutMilliseconds = 5000) { return await Driver.SendRequest(CloseSessionRequest.Type, closeSessionParams); } /// /// 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)); Assert.NotNull(queryResult); Assert.NotNull(queryResult.BatchSummaries); await Disconnect(queryTempFile.FilePath); } } public static async Task RunTestIterations(Func testToRun, [CallerMemberName] string testName = "") { TestTimer timer = new TestTimer() { PrintResult = true }; for (int i = 0; i < TestRunner.Instance.NumberOfRuns; i++) { Console.WriteLine("Iteration Number: " + i); try { await testToRun(timer); } catch(Exception ex) { Console.WriteLine("Iteration Failed: " + ex.Message); } Thread.Sleep(5000); } timer.Print(testName); } public async Task CalculateRunTime(Func> testToRun, TestTimer timer = null) { if (timer != null) { timer.Start(); } T result = await testToRun(); if (timer != null) { timer.End(); } return result; } public async Task ExecuteWithTimeout(TestTimer timer, int timeout, Func> repeatedCode, TimeSpan? delay = null, [CallerMemberName] string testName = "") { timer.Start(); while (true) { if (await repeatedCode()) { timer.End(); 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 to save query results as XML /// public async Task SaveAsXml(string ownerUri, string filename, int batchIndex, int resultSetIndex) { var saveParams = new SaveResultsAsXmlRequestParams { OwnerUri = ownerUri, BatchIndex = batchIndex, ResultSetIndex = resultSetIndex, FilePath = filename }; var result = await Driver.SendRequest(SaveResultsAsXmlRequest.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())); } } } }