diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1ccae023 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +sudo: required +dist: trusty + +os: + - linux + - osx + +dotnet: 1.0.0-preview2-003131 + +# safelist +branches: + only: + - master + - dev + +language: csharp +solution: sqltoolsservice.sln + +before_install: + - if [ $TRAVIS_OS_NAME == "linux" ]; then + sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'; + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893; + sudo apt-get update; + sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177; + else + brew update; + brew install openssl; + mkdir -p /usr/local/lib; + ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; + ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; + brew cask install dotnet; + export PATH="/usr/local/share/dotnet/:$PATH"; + fi +install: + - dotnet restore + +script: + - dotnet build src/Microsoft.SqlTools.ServiceLayer + - dotnet test test/Microsoft.SqlTools.ServiceLayer.Test + \ No newline at end of file diff --git a/README.md b/README.md index 8282a7ff..c85f7883 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +[![Travis CI](https://travis-ci.org/Microsoft/sqltoolsservice.svg?branch=dev)](https://travis-ci.org/Microsoft/sqltoolsservice) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/Microsoft/sqltoolsservice?svg=true&retina=true&branch=dev)](https://ci.appveyor.com/project/kburtram/sqltoolsservice) +[![Coverage Status](https://coveralls.io/repos/github/Microsoft/sqltoolsservice/badge.svg?branch=dev)](https://coveralls.io/github/Microsoft/sqltoolsservice?branch=dev) + # Microsoft SQL Tools Service The SQL Tools Service is an application that provides core functionality for various SQL Server tools. These features include the following: * Connection management diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..86a04482 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,31 @@ +# Note: This file isn't used in current setup. Instead its parts have been +# set directly in the AppVeyor project settings - Environment, General, Build, Test sections. +# This allows us to get Coveralls to run on PRs, but is more work than just using this file. +# Preserving this here so that it's easy to replicate for others +environment: + COVERALLS_REPO_TOKEN: + secure: KjiClJjgj/eB1zo52GBz/CHCmdxj6ut+q6+LD5G3sYhy9Hi4kEF6CWi8vOQPH1oy + +# safelist +branches: + only: + - master + - dev + +before_build: +- appveyor-retry dotnet restore -v Minimal + +build_script: + - dotnet build src/Microsoft.SqlTools.ServiceLayer + +test_script: + - dotnet test test/Microsoft.SqlTools.ServiceLayer.Test + +after_test: + - cd test/CodeCoverage + - npm install -g gulp-cli + - runintegration.bat + - cmd: packages\coveralls.io.1.3.4\tools\coveralls.net.exe --opencover coverage.xml + +cache: +- '%USERPROFILE%\.nuget\packages' diff --git a/bin/nuget/Microsoft.SqlServer.Smo.140.1.11.nupkg b/bin/nuget/Microsoft.SqlServer.Smo.140.1.11.nupkg deleted file mode 100644 index f0280385..00000000 Binary files a/bin/nuget/Microsoft.SqlServer.Smo.140.1.11.nupkg and /dev/null differ diff --git a/bin/nuget/Microsoft.SqlServer.Smo.140.1.12.nupkg b/bin/nuget/Microsoft.SqlServer.Smo.140.1.12.nupkg new file mode 100644 index 00000000..4eab97ea Binary files /dev/null and b/bin/nuget/Microsoft.SqlServer.Smo.140.1.12.nupkg differ diff --git a/nuget.config b/nuget.config index 6a020190..52296d60 100644 --- a/nuget.config +++ b/nuget.config @@ -3,7 +3,8 @@ - + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 0ace6c92..1b28daba 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -360,19 +360,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection if (ServiceHost != null) { - // Send a telemetry notification for intellisense performance metrics - ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() + try { - Params = new TelemetryProperties + // Send a telemetry notification for intellisense performance metrics + ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() { - Properties = new Dictionary - { - { "IsAzure", info.IsAzure ? "1" : "0" } - }, - EventName = TelemetryEventNames.IntellisenseQuantile, - Measures = info.IntellisenseMetrics.Quantile - } - }); + Params = new TelemetryProperties + { + Properties = new Dictionary + { + { "IsAzure", info.IsAzure ? "1" : "0" } + }, + EventName = TelemetryEventNames.IntellisenseQuantile, + Measures = info.IntellisenseMetrics.Quantile + } + }); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Verbose, "Could not send Connection telemetry event " + ex.ToString()); + } } // Close the connection diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/AmbientSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/AmbientSettings.cs index fc8e7302..f1022ecb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/AmbientSettings.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/AmbientSettings.cs @@ -48,12 +48,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection internal const string DoNotSerializeQueryStoreSettingsIndex = "DoNotSerializeQueryStoreSettings"; internal const string AlwaysEncryptedWizardMigrationIndex = "AlwaysEncryptedWizardMigration"; - private static readonly AmbientData _defaultSettings; + internal static AmbientData _defaultSettings; static AmbientSettings() { _defaultSettings = new AmbientData(); - } + } /// /// Access to the default ambient settings. Access to these settings is made available diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs index 4f31fcf3..3ce8c9e8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs @@ -801,7 +801,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection public string DatabaseName; } - private static bool TryGetConnectionStringBuilder(string connectionString, out SqlConnectionStringBuilder builder) + internal static bool TryGetConnectionStringBuilder(string connectionString, out SqlConnectionStringBuilder builder) { builder = null; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableSqlCommand.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableSqlCommand.cs index 4f051688..eaacdfb0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableSqlCommand.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableSqlCommand.cs @@ -235,7 +235,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection return _command; } - private void ValidateConnectionIsSet() + internal void ValidateConnectionIsSet() { if (_connection == null) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryLimitExceededException.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryLimitExceededException.cs index 0ae8b4e7..c4f99e59 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryLimitExceededException.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryLimitExceededException.cs @@ -34,5 +34,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection [Serializable] internal sealed class RetryLimitExceededException : Exception { + internal RetryLimitExceededException() : base() + { + } + + internal RetryLimitExceededException(string m, Exception e) : base(m, e) + { + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryPolicy.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryPolicy.cs index 23159d3e..37be0b15 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryPolicy.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/RetryPolicy.cs @@ -292,7 +292,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection } */ - private static int? GetErrorNumber(Exception ex) + internal static int? GetErrorNumber(Exception ex) { SqlException sqlEx = ex as SqlException; if (sqlEx == null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Constants.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Constants.cs index 14f3d762..7daa6b97 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Constants.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Constants.cs @@ -13,6 +13,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n"; public static readonly JsonSerializerSettings JsonSerializerSettings; + public static readonly string SqlLoginAuthenticationType = "SqlLogin"; + static Constants() { JsonSerializerSettings = new JsonSerializerSettings(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs index 3b3d11c8..4c711cdb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/MessageDispatcher.cs @@ -23,10 +23,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol private AsyncContextThread messageLoopThread; - private Dictionary> requestHandlers = + internal Dictionary> requestHandlers = new Dictionary>(); - private Dictionary> eventHandlers = + internal Dictionary> eventHandlers = new Dictionary>(); private Action responseHandler; @@ -218,10 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol { string message = string.Format("Exception occurred while parsing message: {0}", e.Message); Logger.Write(LogLevel.Error, message); - await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams - { - Message = message - }); + await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams { Message = message }); // Continue the loop continue; @@ -236,10 +233,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol // Log the error and send an error event to the client string message = string.Format("Exception occurred while receiving message: {0}", e.Message); Logger.Write(LogLevel.Error, message); - await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams - { - Message = message - }); + await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams { Message = message }); // Continue the loop continue; @@ -273,10 +267,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol { handlerToAwait = requestHandler(messageToDispatch, messageWriter); } - else - { - // TODO: Message not supported error - } + // else + // { + // // TODO: Message not supported error + // } } else if (messageToDispatch.MessageType == MessageType.Response) { @@ -297,10 +291,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol // TODO: Message not supported error } } - else - { - // TODO: Return message not supported - } + // else + // { + // // TODO: Return message not supported + // } if (handlerToAwait != null) { @@ -325,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol } } - private void OnListenTaskCompleted(Task listenTask) + internal void OnListenTaskCompleted(Task listenTask) { if (listenTask.IsFaulted) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Serializers/V8MessageSerializer.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Serializers/V8MessageSerializer.cs index f1385e00..f0e144e6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Serializers/V8MessageSerializer.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/Serializers/V8MessageSerializer.cs @@ -89,10 +89,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers messageJson.GetValue("message")); } } - else - { - // TODO: Parse error - } + // else + // { + // // TODO: Parse error + // } } else if (string.Equals("event", messageType, StringComparison.CurrentCultureIgnoreCase)) diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs index 07623afb..b7808509 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs @@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Queue a binding request item /// - public QueueItem QueueBindingOperation( + public virtual QueueItem QueueBindingOperation( string key, Func bindOperation, Func timeoutOperation = null, diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs index d17930e1..61ba3f82 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs @@ -14,5 +14,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts RequestType Type = RequestType.Create("textDocument/definition"); } + + /// + /// Error object for Definition + /// + public class DefinitionError + { + /// + /// Error message + /// + public string message { get; set; } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/StatusChangedNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/StatusChangedNotification.cs new file mode 100644 index 00000000..4e5d1b92 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/StatusChangedNotification.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts +{ + /// + /// Parameters sent back with an status change event + /// + public class StatusChangeParams + { + /// + /// URI identifying the text document + /// + public string OwnerUri { get; set; } + + /// + /// The new status for the document + /// + public string Status { get; set; } + } + + /// + /// Event sent for language service status change notification + /// + public class StatusChangedNotification + { + public static readonly + EventType Type = + EventType.Create("textDocument/statusChanged"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DocumentStatusHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DocumentStatusHelper.cs new file mode 100644 index 00000000..8e580196 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DocumentStatusHelper.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Hosting; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices +{ + /// + /// Helper class to send events to the client + /// + public class DocumentStatusHelper + { + public const string DefinitionRequested = "DefinitionRequested"; + public const string DefinitionRequestCompleted = "DefinitionRequestCompleted"; + + /// + /// Sends an event for specific document using the existing request context + /// + public static void SendStatusChange(RequestContext requestContext, TextDocumentPosition textDocumentPosition, string status) + { + Task.Factory.StartNew(async () => + { + if (requestContext != null) + { + string ownerUri = textDocumentPosition != null && textDocumentPosition.TextDocument != null ? textDocumentPosition.TextDocument.Uri : ""; + await requestContext.SendEvent(StatusChangedNotification.Type, new StatusChangeParams() + { + OwnerUri = ownerUri, + Status = status + }); + } + }); + } + + /// + /// Sends a telemetry event for specific document using the existing request context + /// + public static void SendTelemetryEvent(RequestContext requestContext, string telemetryEvent) + { + Task.Factory.StartNew(async () => + { + await requestContext.SendEvent(TelemetryNotification.Type, new TelemetryParams() + { + Params = new TelemetryProperties + { + EventName = telemetryEvent + } + }); + }); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index c5f3ae3b..b123c76c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -236,6 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // Store the SqlToolsContext for future use Context = context; + } #endregion @@ -282,7 +283,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// - private static async Task HandleCompletionResolveRequest( + internal static async Task HandleCompletionResolveRequest( CompletionItem completionItem, RequestContext requestContext) { @@ -300,28 +301,37 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices internal static async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext requestContext) { + // Send a notification to signal that definition is sent + await requestContext.SendEvent(TelemetryNotification.Type, new TelemetryParams() + { + Params = new TelemetryProperties + { + EventName = TelemetryEventNames.PeekDefinitionRequested + } + }); + DocumentStatusHelper.SendTelemetryEvent(requestContext, TelemetryEventNames.PeekDefinitionRequested); + DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequested); + if (WorkspaceService.Instance.CurrentSettings.IsIntelliSenseEnabled) { // Retrieve document and connection ConnectionInfo connInfo; var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri); LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); - - Location[] locations = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo); - if (locations != null) - { - await requestContext.SendResult(locations); - - // Send a notification to signal that definition is sent - await ServiceHost.Instance.SendEvent(TelemetryNotification.Type, new TelemetryParams() + DefinitionResult definitionResult = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo); + if (definitionResult != null) + { + if (definitionResult.IsErrorResult) { - Params = new TelemetryProperties - { - EventName = TelemetryEventNames.PeekDefinitionRequested - } - }); + await requestContext.SendError( new DefinitionError { message = definitionResult.Message }); + } + else + { + await requestContext.SendResult(definitionResult.Locations); + } } } + DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequestCompleted); } // turn off this code until needed (10/28/2016) @@ -341,7 +351,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } #endif - private static async Task HandleSignatureHelpRequest( + internal static async Task HandleSignatureHelpRequest( TextDocumentPosition textDocumentPosition, RequestContext requestContext) { @@ -685,7 +695,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// Location with the URI of the script file - internal Location[] GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo) + internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo) { // Parse sql ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); @@ -723,29 +733,57 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices int parserLine = textDocumentPosition.Position.Line + 1; int parserColumn = textDocumentPosition.Position.Character + 1; IEnumerable declarationItems = Resolver.FindCompletions( - scriptParseInfo.ParseResult, - parserLine, parserColumn, + scriptParseInfo.ParseResult, + parserLine, parserColumn, bindingContext.MetadataDisplayInfoProvider); // Match token with the suggestions(declaration items) returned - string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); - return peekDefinition.GetScript(declarationItems, tokenText, schemaName); - + string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile); + PeekDefinition peekDefinition = new PeekDefinition(bindingContext.ServerConnection, connInfo); + return peekDefinition.GetScript(declarationItems, tokenText, schemaName); + }, + timeoutOperation: (bindingContext) => + { + // return error result + return new DefinitionResult + { + IsErrorResult = true, + Message = SR.PeekDefinitionTimedoutError, + Locations = null + }; }); // wait for the queue item queueItem.ItemProcessed.WaitOne(); - return queueItem.GetResultAsT(); + return queueItem.GetResultAsT(); + } + catch (Exception ex) + { + // if any exceptions are raised return error result with message + Logger.Write(LogLevel.Error, "Exception in GetDefinition " + ex.ToString()); + return new DefinitionResult + { + IsErrorResult = true, + Message = SR.PeekDefinitionError(ex.Message), + Locations = null + }; } finally { Monitor.Exit(scriptParseInfo.BuildingMetadataLock); } } - - return null; + else + { + // User is not connected. + return new DefinitionResult + { + IsErrorResult = true, + Message = SR.PeekDefinitionNotConnectedError, + Locations = null + }; + } } /// @@ -754,7 +792,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// - /// schema nama + /// schema name private string GetSchemaName(ScriptParseInfo scriptParseInfo, Position position, ScriptFile scriptFile) { // Offset index by 1 for sql parser @@ -1040,11 +1078,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } catch (Exception e) { - Logger.Write( - LogLevel.Error, - string.Format( - "Exception while cancelling analysis task:\n\n{0}", - e.ToString())); + Logger.Write(LogLevel.Error, string.Format("Exception while cancelling analysis task:\n\n{0}", e.ToString())); TaskCompletionSource cancelTask = new TaskCompletionSource(); cancelTask.SetCanceled(); @@ -1168,7 +1202,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } - private bool RemoveScriptParseInfo(string uri) + internal bool RemoveScriptParseInfo(string uri) { lock (this.parseMapLock) { @@ -1187,7 +1221,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// Returns a flag indicating if the ScriptFile refers to the output window. /// /// - private bool IsPreviewWindow(ScriptFile scriptFile) + internal bool IsPreviewWindow(ScriptFile scriptFile) { if (scriptFile != null && !string.IsNullOrWhiteSpace(scriptFile.ClientFilePath)) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs index 07293ba6..d01d1d26 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs @@ -7,12 +7,12 @@ using System.IO; using System.Collections.Generic; using System.Collections.Specialized; using System.Data.SqlClient; -using System.Runtime.InteropServices; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices @@ -23,7 +23,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// internal class PeekDefinition { + private bool error; + private string errorMessage; + private ServerConnection serverConnection; private ConnectionInfo connectionInfo; + private Database database; private string tempPath; internal delegate StringCollection ScriptGetter(string objectName, string schemaName); @@ -35,38 +39,56 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // Dictionary that holds the object name (as appears on the TSQL create statement) private Dictionary sqlObjectTypes = new Dictionary(); + /// + /// Initialize a Peek Definition helper object + /// + /// SMO Server connection + internal PeekDefinition(ServerConnection serverConnection, ConnectionInfo connInfo) + { + this.serverConnection = serverConnection; + this.connectionInfo = connInfo; + + DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition"); + this.tempPath = tempScriptDirectory.FullName; + Initialize(); + } + private Database Database { get { - if (this.connectionInfo.SqlConnection != null) + if (this.database == null) { - try + if (this.serverConnection != null && !string.IsNullOrEmpty(this.serverConnection.DatabaseName)) { - // Get server object from connection - string connectionString = ConnectionService.BuildConnectionString(this.connectionInfo.ConnectionDetails); - SqlConnection sqlConn = new SqlConnection(connectionString); - sqlConn.Open(); - ServerConnection serverConn = new ServerConnection(sqlConn); - Server server = new Server(serverConn); - return server.Databases[this.connectionInfo.SqlConnection.Database]; + try + { + // Get server object from connection + SqlConnection sqlConn = new SqlConnection(this.serverConnection.ConnectionString); + sqlConn.Open(); + ServerConnection peekConnection = new ServerConnection(sqlConn); + Server server = new Server(peekConnection); + this.database = new Database(server, peekConnection.DatabaseName); + } + catch (ConnectionFailureException cfe) + { + Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + cfe.Message); + this.error = true; + this.errorMessage = (connectionInfo != null && connectionInfo.IsAzure)? SR.PeekDefinitionAzureError(cfe.Message) : SR.PeekDefinitionError(cfe.Message); + return null; + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + ex.Message); + this.error = true; + this.errorMessage = SR.PeekDefinitionError(ex.Message); + return null; + } } - catch(Exception ex) - { - Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + ex.Message); - return null; - } - } - return null; - } - } - internal PeekDefinition(ConnectionInfo connInfo) - { - this.connectionInfo = connInfo; - DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition"); - this.tempPath = tempScriptDirectory.FullName; - Initialize(); + } + return this.database; + } } /// @@ -103,7 +125,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { if (Path.DirectorySeparatorChar.Equals('/')) { - tempFileName = "file:" + tempFileName; + tempFileName = "file:" + tempFileName; } else { @@ -129,7 +151,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) { - if (lines[lineNumber].IndexOf( createString, StringComparison.OrdinalIgnoreCase) >= 0) + if (lines[lineNumber].IndexOf(createString, StringComparison.OrdinalIgnoreCase) >= 0) { return lineNumber; } @@ -144,7 +166,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// Location object of the script file - internal Location[] GetScript(IEnumerable declarationItems, string tokenText, string schemaName) + internal DefinitionResult GetScript(IEnumerable declarationItems, string tokenText, string schemaName) { foreach (Declaration declarationItem in declarationItems) { @@ -156,29 +178,38 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices if (declarationItem.Title.Equals(tokenText)) { // Script object using SMO based on type - DeclarationType type = declarationItem.Type; + DeclarationType type = declarationItem.Type; if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type)) { // On *nix and mac systems, the defaultSchema property throws an Exception when accessed. // This workaround ensures that a schema name is present by attempting // to get the schema name from the declaration item // If all fails, the default schema name is assumed to be "dbo" - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && string.IsNullOrEmpty(schemaName)) + if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) { string fullObjectName = declarationItem.DatabaseQualifiedName; schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); } - return GetSqlObjectDefinition( + Location[] locations = GetSqlObjectDefinition( sqlScriptGetters[type], tokenText, schemaName, sqlObjectTypes[type] ); + DefinitionResult result = new DefinitionResult + { + IsErrorResult = this.error, + Message = this.errorMessage, + Locations = locations + }; + return result; } - return null; + // sql object type is currently not supported + return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); } } - return null; + // no definition found + return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError); } /// @@ -192,9 +223,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices string[] tokens = fullObjectName.Split('.'); for (int i = tokens.Length - 1; i > 0; i--) { - if(tokens[i].Equals(objectName)) + if (tokens[i].Equals(objectName)) { - return tokens[i-1]; + return tokens[i - 1]; } } return "dbo"; @@ -208,8 +239,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// String collection of scripts internal StringCollection GetTableScripts(string tableName, string schemaName) { - return (schemaName != null) ? Database?.Tables[tableName, schemaName]?.Script() - : Database?.Tables[tableName]?.Script(); + try + { + Table table = string.IsNullOrEmpty(schemaName) + ? new Table(this.Database, tableName) + : new Table(this.Database, tableName, schemaName); + + table.Refresh(); + + return table.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Exception at PeekDefinition GetTableScripts : " + ex.Message); + return null; + } } /// @@ -220,8 +264,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// String collection of scripts internal StringCollection GetViewScripts(string viewName, string schemaName) { - return (schemaName != null) ? Database?.Views[viewName, schemaName]?.Script() - : Database?.Views[viewName]?.Script(); + try + { + View view = string.IsNullOrEmpty(schemaName) + ? new View(this.Database, viewName) + : new View(this.Database, viewName, schemaName); + + view.Refresh(); + + return view.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Exception at PeekDefinition GetViewScripts : " + ex.Message); + return null; + } } /// @@ -230,10 +287,23 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// Stored Procedure name /// Schema Name /// String collection of scripts - internal StringCollection GetStoredProcedureScripts(string viewName, string schemaName) + internal StringCollection GetStoredProcedureScripts(string sprocName, string schemaName) { - return (schemaName != null) ? Database?.StoredProcedures[viewName, schemaName]?.Script() - : Database?.StoredProcedures[viewName]?.Script(); + try + { + StoredProcedure sproc = string.IsNullOrEmpty(schemaName) + ? new StoredProcedure(this.Database, sprocName) + : new StoredProcedure(this.Database, sprocName, schemaName); + + sproc.Refresh(); + + return sproc.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Exception at PeekDefinition GetStoredProcedureScripts : " + ex.Message); + return null; + } } /// @@ -250,30 +320,49 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices string schemaName, string objectType) { - StringCollection scripts = sqlScriptGetter(objectName, schemaName); - string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName)) - : Path.Combine(this.tempPath, string.Format("{0}.sql", objectName)); + StringCollection scripts = sqlScriptGetter(objectName, schemaName); + string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName)) + : Path.Combine(this.tempPath, string.Format("{0}.sql", objectName)); - if (scripts != null) + if (scripts != null) + { + int lineNumber = 0; + using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite))) { - int lineNumber = 0; - using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite))) - { - foreach (string script in scripts) + foreach (string script in scripts) + { + string createSyntax = string.Format("CREATE {0}", objectType); + if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0) { - string createSyntax = string.Format("CREATE {0}", objectType); - if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0) - { - scriptFile.WriteLine(script); - lineNumber = GetStartOfCreate(script, createSyntax); - } + scriptFile.WriteLine(script); + lineNumber = GetStartOfCreate(script, createSyntax); } } - return GetLocationFromFile(tempFileName, lineNumber); } + return GetLocationFromFile(tempFileName, lineNumber); + } + else + { + this.error = true; + this.errorMessage = SR.PeekDefinitionNoResultsError; + return null; + } + } - return null; + /// + /// Helper method to create definition error result object + /// + /// Error message + /// DefinitionResult + internal DefinitionResult GetDefinitionErrorResult(string errorMessage) + { + return new DefinitionResult + { + IsErrorResult = true, + Message = errorMessage, + Locations = null + }; } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionResult.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionResult.cs new file mode 100644 index 00000000..2234d6e8 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionResult.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices +{ + /// + /// /// Result object for PeekDefinition + /// + public class DefinitionResult + { + /// + /// True, if definition error occured + /// + public bool IsErrorResult; + + /// + /// Error message, if any + /// + public string Message { get; set; } + + /// + /// Location object representing the definition script file + /// + public Location[] Locations; + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/QueueItem.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/QueueItem.cs index a320f842..2b45de8b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/QueueItem.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/QueueItem.cs @@ -39,7 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Gets or sets an event to signal when this queue item has been processed /// - public ManualResetEvent ItemProcessed { get; set; } + public virtual ManualResetEvent ItemProcessed { get; set; } /// /// Gets or sets the result of the queued task diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs index fd15ad82..2735abea 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptDocumentInfo.cs @@ -92,7 +92,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion /// /// The token text in the file content used for completion list /// - public string TokenText + public virtual string TokenText { get { diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index 7336cec2..c7c379c9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -48,7 +48,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// Internal representation of the messages so we can modify internally /// - private readonly List resultMessages; + internal readonly List resultMessages; /// /// Internal representation of the result sets so we can modify internally @@ -379,7 +379,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// Sender of the event /// Arguments for the event - private void StatementCompletedHandler(object sender, StatementCompletedEventArgs args) + internal void StatementCompletedHandler(object sender, StatementCompletedEventArgs args) { // Add a message for the number of rows the query returned string message; @@ -414,7 +414,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// cannot be converted to SqlException, the message is written to the messages list. /// /// The exception to unwrap - private void UnwrapDbException(DbException dbe) + internal void UnwrapDbException(DbException dbe) { SqlException se = dbe as SqlException; if (se != null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs index 09beae29..3ab13705 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/DbColumnWrapper.cs @@ -19,7 +19,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts /// /// All types supported by the server, stored as a hash set to provide O(1) lookup /// - private static readonly HashSet AllServerDataTypes = new HashSet + internal static readonly HashSet AllServerDataTypes = new HashSet { "bigint", "binary", diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/LongList.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/LongList.cs index afacc98f..306019df 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/LongList.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/LongList.cs @@ -20,15 +20,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// /// Type of the values to store public class LongList : IEnumerable - { + { #region Member Variables - + + private int expandListSize = int.MaxValue; private List> expandedList; private readonly List shortList; #endregion - /// + /// /// Creates a new long list /// public LongList() @@ -46,7 +47,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility public T this[long index] { - get { return GetItem(index); } + get + { + return GetItem(index); + } + } + + public int ExpandListSize + { + get + { + return this.expandListSize; + } + internal set + { + this.expandListSize = value; + } } #endregion @@ -60,7 +76,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// Index of the item that was just added public long Add(T val) { - if (Count <= int.MaxValue) + if (Count <= this.ExpandListSize) { shortList.Add(val); } @@ -73,7 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility expandedList = new List> {shortList}; } - int arrayIndex = (int)(Count/int.MaxValue); // 0 based + int arrayIndex = (int)(Count / this.ExpandListSize); // 0 based List arr; if (expandedList.Count <= arrayIndex) // need to make a new array @@ -99,19 +115,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility { T val = default(T); - if (Count <= int.MaxValue) + if (Count <= this.ExpandListSize) { int i32Index = Convert.ToInt32(index); val = shortList[i32Index]; } else { - int iArray32Index = (int) (Count/int.MaxValue); + int iArray32Index = (int) (Count / this.ExpandListSize); if (expandedList.Count > iArray32Index) { List arr = expandedList[iArray32Index]; - int i32Index = (int) (Count%int.MaxValue); + int i32Index = (int) (Count % this.ExpandListSize); if (arr.Count > i32Index) { val = arr[i32Index]; @@ -128,7 +144,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// The index to remove from the list public void RemoveAt(long index) { - if (Count <= int.MaxValue) + if (Count <= this.ExpandListSize) { int iArray32MemberIndex = Convert.ToInt32(index); // 0 based shortList.RemoveAt(iArray32MemberIndex); @@ -136,21 +152,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility else // handle the case of multiple arrays { // find out which array it is in - int arrayIndex = (int) (index/int.MaxValue); + int arrayIndex = (int) (index / this.ExpandListSize); List arr = expandedList[arrayIndex]; // find out index into this array - int iArray32MemberIndex = (int) (index%int.MaxValue); + int iArray32MemberIndex = (int) (index % this.ExpandListSize); arr.RemoveAt(iArray32MemberIndex); // now shift members of the array back one - int iArray32TotalIndex = (int) (Count/Int32.MaxValue); + int iArray32TotalIndex = (int) (Count / this.ExpandListSize); for (int i = arrayIndex + 1; i < iArray32TotalIndex; i++) { List arr1 = expandedList[i - 1]; List arr2 = expandedList[i]; - arr1.Add(arr2[int.MaxValue - 1]); + arr1.Add(arr2[this.ExpandListSize - 1]); arr2.RemoveAt(0); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs index 1c798a5d..e3954d39 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs @@ -194,7 +194,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace this.workspaceFiles.Remove(scriptFile.Id); } - private string GetBaseFilePath(string filePath) + internal string GetBaseFilePath(string filePath) { if (IsPathInMemory(filePath)) { @@ -215,7 +215,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace return Path.GetDirectoryName(filePath); } - private string ResolveRelativeScriptPath(string baseFilePath, string relativePath) + internal string ResolveRelativeScriptPath(string baseFilePath, string relativePath) { if (Path.IsPathRooted(relativePath)) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/project.json b/src/Microsoft.SqlTools.ServiceLayer/project.json index c8e55c35..9cad9751 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/project.json +++ b/src/Microsoft.SqlTools.ServiceLayer/project.json @@ -19,7 +19,7 @@ "Newtonsoft.Json": "9.0.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.1.11", + "Microsoft.SqlServer.Smo": "140.1.12", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", "System.ComponentModel.TypeConverter": "4.1.0", diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/sr.cs index c8abeed3..9c162533 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.cs @@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class SR { - protected SR() + internal SR() { } public static CultureInfo Culture @@ -341,6 +341,38 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string PeekDefinitionNoResultsError + { + get + { + return Keys.GetString(Keys.PeekDefinitionNoResultsError); + } + } + + public static string PeekDefinitionNotConnectedError + { + get + { + return Keys.GetString(Keys.PeekDefinitionNotConnectedError); + } + } + + public static string PeekDefinitionTimedoutError + { + get + { + return Keys.GetString(Keys.PeekDefinitionTimedoutError); + } + } + + public static string PeekDefinitionTypeNotSupportedError + { + get + { + return Keys.GetString(Keys.PeekDefinitionTypeNotSupportedError); + } + } + public static string WorkspaceServicePositionLineOutOfRange { get @@ -384,6 +416,16 @@ namespace Microsoft.SqlTools.ServiceLayer return Keys.GetString(Keys.QueryServiceQueryFailed, message); } + public static string PeekDefinitionAzureError(string errorMessage) + { + return Keys.GetString(Keys.PeekDefinitionAzureError, errorMessage); + } + + public static string PeekDefinitionError(string errorMessage) + { + return Keys.GetString(Keys.PeekDefinitionError, errorMessage); + } + public static string WorkspaceServicePositionColumnOutOfRange(int line) { return Keys.GetString(Keys.WorkspaceServicePositionColumnOutOfRange, line); @@ -397,7 +439,7 @@ namespace Microsoft.SqlTools.ServiceLayer [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Keys { - static ResourceManager resourceManager = new ResourceManager(typeof(SR)); + static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.SR", typeof(SR).GetTypeInfo().Assembly); static CultureInfo _culture = null; @@ -540,6 +582,24 @@ namespace Microsoft.SqlTools.ServiceLayer public const string QueryServiceResultSetNoColumnSchema = "QueryServiceResultSetNoColumnSchema"; + public const string PeekDefinitionAzureError = "PeekDefinitionAzureError"; + + + public const string PeekDefinitionError = "PeekDefinitionError"; + + + public const string PeekDefinitionNoResultsError = "PeekDefinitionNoResultsError"; + + + public const string PeekDefinitionNotConnectedError = "PeekDefinitionNotConnectedError"; + + + public const string PeekDefinitionTimedoutError = "PeekDefinitionTimedoutError"; + + + public const string PeekDefinitionTypeNotSupportedError = "PeekDefinitionTypeNotSupportedError"; + + public const string WorkspaceServicePositionLineOutOfRange = "WorkspaceServicePositionLineOutOfRange"; @@ -549,7 +609,7 @@ namespace Microsoft.SqlTools.ServiceLayer public const string WorkspaceServiceBufferPositionOutOfOrder = "WorkspaceServiceBufferPositionOutOfOrder"; - private Keys() + internal Keys() { } public static CultureInfo Culture diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/sr.resx index 5494a742..bf5ed353 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.resx @@ -308,6 +308,32 @@ Could not retrieve column schema for result set + + This feature is currently not supported on Azure SQL DB and Data Warehouse: {0} + . + Parameters: 0 - errorMessage (string) + + + An unexpected error occurred during Peek Definition execution: {0} + . + Parameters: 0 - errorMessage (string) + + + No results were found. + + + + Please connect to a server. + + + + Operation timed out. + + + + This object type is currently not supported by this feature. + + Position is outside of file line range diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/sr.strings index 67534aed..fee7f907 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.strings @@ -139,6 +139,21 @@ QueryServiceResultSetRowCountOutOfRange = Row count must be a positive integer QueryServiceResultSetNoColumnSchema = Could not retrieve column schema for result set +############################################################################ +# Language Service + +PeekDefinitionAzureError(string errorMessage) = This feature is currently not supported on Azure SQL DB and Data Warehouse: {0} + +PeekDefinitionError(string errorMessage) = An unexpected error occurred during Peek Definition execution: {0} + +PeekDefinitionNoResultsError = No results were found. + +PeekDefinitionNotConnectedError = Please connect to a server. + +PeekDefinitionTimedoutError = Operation timed out. + +PeekDefinitionTypeNotSupportedError = This object type is currently not supported by this feature. + ############################################################################ # Workspace Service diff --git a/test/CodeCoverage/package.json b/test/CodeCoverage/package.json index 0d4efd10..003b9c85 100644 --- a/test/CodeCoverage/package.json +++ b/test/CodeCoverage/package.json @@ -4,11 +4,13 @@ "description": "SQL Tools Service Layer", "main": "gulpfile.js", "dependencies": { - "gulp": "github:gulpjs/gulp#4.0", "del": "^2.2.1", + "gulp": "github:gulpjs/gulp#4.0", "gulp-hub": "frankwallis/gulp-hub#registry-init", "gulp-install": "^0.6.0", - "request": "^2.73.0" + "gulp-util": "^3.0.7", + "request": "^2.73.0", + "through2": "^2.0.3" }, "devDependencies": {}, "author": "Microsoft", diff --git a/test/CodeCoverage/packages.config b/test/CodeCoverage/packages.config index 2b659d64..5d0c66b9 100644 --- a/test/CodeCoverage/packages.config +++ b/test/CodeCoverage/packages.config @@ -3,4 +3,5 @@ + diff --git a/test/Microsoft.SqlTools.ServiceLayer.PerfTests/Tests/IntellisenseTests.cs b/test/Microsoft.SqlTools.ServiceLayer.PerfTests/Tests/IntellisenseTests.cs index cffe764c..3f84df16 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.PerfTests/Tests/IntellisenseTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.PerfTests/Tests/IntellisenseTests.cs @@ -122,7 +122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.PerfTests TestServerType serverType = TestServerType.OnPrem; using (TestHelper testHelper = new TestHelper()) { - await VerifyBindingLoadScenario(testHelper, TestServerType.OnPrem, Scripts.TestDbSimpleSelectQuery, false); + await VerifyBindingLoadScenario(testHelper, serverType, Scripts.TestDbSimpleSelectQuery, false); } } @@ -170,10 +170,10 @@ namespace Microsoft.SqlTools.ServiceLayer.PerfTests [CreateTestDb(TestServerType.Azure)] public async Task BindingCacheColdOnPremComplexQuery() { - TestServerType serverType = TestServerType.Azure; + TestServerType serverType = TestServerType.OnPrem; using (TestHelper testHelper = new TestHelper()) { - await VerifyBindingLoadScenario(testHelper, TestServerType.OnPrem, Scripts.TestDbComplexSelectQueries, false); + await VerifyBindingLoadScenario(testHelper, serverType, Scripts.TestDbComplexSelectQueries, false); } } @@ -257,7 +257,10 @@ namespace Microsoft.SqlTools.ServiceLayer.PerfTests { string query = Scripts.SelectQuery; CompletionItem[] completions = await testHelper.RequestCompletion(ownerUri, query, 0, query.Length + 1); - return completions != null && completions.Any(x => x.Label == databaseName); + return completions != null && + (completions.Any(x => string.Compare(x.Label, databaseName, StringComparison.OrdinalIgnoreCase) == 0 || + string.Compare(x.Label, $"[{databaseName}]", StringComparison.OrdinalIgnoreCase) == 0 || + string.Compare(x.Label, $"\"{databaseName}\"", StringComparison.OrdinalIgnoreCase) == 0)); } else { diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs index 3bc7b14c..9ead2576 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs @@ -536,6 +536,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.True(connectionString.Contains(connectionStringMarker)); } + /// + /// Build connection string with an invalid auth type + /// + [Fact] + public void BuildConnectionStringWithInvalidAuthType() + { + ConnectionDetails details = TestObjects.GetTestConnectionDetails(); + details.AuthenticationType = "NotAValidAuthType"; + Assert.Throws(() => ConnectionService.BuildConnectionString(details)); + } + /// /// Verify that a connection changed event is fired when the database context changes. /// @@ -892,6 +903,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection }); } + // + /// Test that cancel connection with a null connection parameter + /// + [Fact] + public void TestCancelConnectionNullParam() + { + var service = TestObjects.GetTestConnectionService(); + Assert.False(service.CancelConnect(null)); + } + + // + /// Test that cancel connection with a null connection parameter + /// + [Fact] + public void TestListDatabasesInvalidParams() + { + var service = TestObjects.GetTestConnectionService(); + var listParams = new ListDatabasesParams(); + Assert.Throws(() => service.ListDatabases(listParams)); + listParams.OwnerUri = "file://notmyfile.sql"; + Assert.Throws(() => service.ListDatabases(listParams)); + } + /// /// Test that the connection complete notification type can be created. /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ReliableConnectionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ReliableConnectionTests.cs index 95194a55..65ff5eaf 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ReliableConnectionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ReliableConnectionTests.cs @@ -10,8 +10,11 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; +using System.Threading; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; +using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Test.Utility; @@ -70,6 +73,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection { return ShouldRetryImpl(retryStateObj); } + + public void DoOnIgnoreErrorOccurred(RetryState retryState) + { + OnIgnoreErrorOccurred(retryState); + } } internal class TestProgressiveRetryPolicy : ProgressiveRetryPolicy @@ -126,10 +134,46 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection strategy: new NetworkConnectivityErrorDetectionStrategy(), maxRetryCount: 3, intervalBetweenRetries: TimeSpan.FromMilliseconds(100)); - bool shouldRety = policy.InvokeShouldRetryImpl(new RetryStateEx()); + var retryState = new RetryStateEx(); + bool shouldRety = policy.InvokeShouldRetryImpl(retryState); + policy.DoOnIgnoreErrorOccurred(retryState); Assert.True(shouldRety); } + [Fact] + public void FixedDelayPolicyExecuteActionTest() + { + TestFixedDelayPolicy policy = new TestFixedDelayPolicy( + strategy: new NetworkConnectivityErrorDetectionStrategy(), + maxRetryCount: 3, + intervalBetweenRetries: TimeSpan.FromMilliseconds(20)); + + // execute an action that throws a retry limit exception + CancellationToken token = new CancellationToken(); + Assert.Equal(policy.ExecuteAction((s) => { throw new RetryLimitExceededException(); }, token), default(int)); + + // execute an action that throws a retry limit exeception with an inner exception + Assert.Throws(() => + { + policy.ExecuteAction((s) => + { + var e = new RetryLimitExceededException("retry", new Exception()); + throw e; + }); + }); + } + + [Fact] + public void IsRetryableExceptionTest() + { + TestFixedDelayPolicy policy = new TestFixedDelayPolicy( + strategy: new NetworkConnectivityErrorDetectionStrategy(), + maxRetryCount: 3, + intervalBetweenRetries: TimeSpan.FromMilliseconds(20)); + + Assert.False(policy.IsRetryableException(new Exception())); + } + [Fact] public void ProgressiveRetryPolicyTest() { @@ -140,6 +184,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection increment: TimeSpan.FromMilliseconds(100)); bool shouldRety = policy.InvokeShouldRetryImpl(new RetryStateEx()); Assert.True(shouldRety); + Assert.NotNull(policy.CommandTimeoutInSeconds); + policy.ShouldIgnoreOnFirstTry = false; + Assert.False(policy.ShouldIgnoreOnFirstTry); } [Fact] @@ -157,6 +204,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.True(shouldRety); } + + [Fact] + public void GetErrorNumberWithNullExceptionTest() + { + Assert.Null(RetryPolicy.GetErrorNumber(null)); + } + /// /// Environment variable that stores the name of the test server hosting the SQL Server instance. /// @@ -260,6 +314,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.True(serverInfo.ServerEdition == serverInfo2.ServerEdition); Assert.True(serverInfo.IsCloud == serverInfo2.IsCloud); Assert.True(serverInfo.AzureVersion == serverInfo2.AzureVersion); + Assert.True(serverInfo.IsAzureV1 == serverInfo2.IsAzureV1); } }); } @@ -290,6 +345,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.False(isReadOnly); } + /// + /// /// Tests ReliableConnectionHelper.IsDatabaseReadonly() with null builder parameter + /// + [Fact] + public void TestIsDatabaseReadonlyWithNullBuilder() + { + Assert.Throws(() => ReliableConnectionHelper.IsDatabaseReadonly(null)); + } + /// /// Verify ANSI_NULL and QUOTED_IDENTIFIER settings can be set and retrieved for a session /// @@ -475,6 +539,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection }); } + /// + /// Test that TryGetServerVersion() fails with invalid connection string + /// + [Fact] + public void TestTryGetServerVersionInvalidConnectionString() + { + TestUtils.RunIfWindows(() => + { + ReliableConnectionHelper.ServerInfo info = null; + Assert.False(ReliableConnectionHelper.TryGetServerVersion("this is not a valid connstr", out info)); + }); + } /// /// Validate ambient static settings @@ -561,6 +637,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection data.TraceSettings(); } + [Fact] + public void RaiseAmbientRetryMessageTest() + { + bool handlerCalled = false; + var data = new AmbientSettings.AmbientData(); + data.ConnectionRetryMessageHandler = (a) => handlerCalled = true; + AmbientSettings._defaultSettings = data; + RetryPolicyUtils.RaiseAmbientRetryMessage(new RetryStateEx() { LastError = new Exception() }, 100); + Assert.True(handlerCalled); + } + + [Fact] + public void RaiseAmbientIgnoreMessageTest() + { + bool handlerCalled = false; + var data = new AmbientSettings.AmbientData(); + data.ConnectionRetryMessageHandler = (a) => handlerCalled = true; + AmbientSettings._defaultSettings = data; + RetryPolicyUtils.RaiseAmbientIgnoreMessage(new RetryStateEx() { LastError = new Exception() }, 100); + Assert.True(handlerCalled); + } + [Fact] public void RetryPolicyFactoryTest() { @@ -681,6 +779,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection var detectionStrategy2 = new TestSqlAzureTemporaryAndIgnorableErrorDetectionStrategy(); Assert.NotNull(detectionStrategy2.InvokeCanRetrySqlException(sqlException)); Assert.NotNull(detectionStrategy2.InvokeShouldIgnoreSqlException(sqlException)); + + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory(null)); + batch.UnwrapDbException(sqlException); } var unknownCodeReason = RetryPolicy.ThrottlingReason.FromReasonCode(-1); @@ -736,6 +837,119 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.Equal(exception, args.Exception); Assert.Equal(timespan, args.Delay); } + + [Fact] + public void CheckStaticVariables() + { + Assert.NotNull(ReliableConnectionHelper.BuilderWithDefaultApplicationName); + } + + [Fact] + public void SetLockAndCommandTimeoutThrowsOnNull() + { + Assert.Throws(typeof(ArgumentNullException), () => ReliableConnectionHelper.SetLockAndCommandTimeout(null)); + } + + [Fact] + public void StandardExceptionHandlerTests() + { + Assert.True(ReliableConnectionHelper.StandardExceptionHandler(new InvalidCastException())); + Assert.False(ReliableConnectionHelper.StandardExceptionHandler(new Exception())); + } + + [Fact] + public void GetConnectionStringBuilderNullConnectionString() + { + SqlConnectionStringBuilder builder; + Assert.False(ReliableConnectionHelper.TryGetConnectionStringBuilder(null, out builder)); + } + + [Fact] + public void GetConnectionStringBuilderExceptionTests() + { + SqlConnectionStringBuilder builder; + + // throws ArgumentException + Assert.False(ReliableConnectionHelper.TryGetConnectionStringBuilder("IntegratedGoldFish=True", out builder)); + + // throws FormatException + Assert.False(ReliableConnectionHelper.TryGetConnectionStringBuilder("rabbits**frogs**lizards", out builder)); + } + + [Fact] + public void GetCompleteServerNameTests() + { + Assert.Null(ReliableConnectionHelper.GetCompleteServerName(null)); + + Assert.NotNull(ReliableConnectionHelper.GetCompleteServerName("localhost")); + + Assert.NotNull(ReliableConnectionHelper.GetCompleteServerName("mytestservername")); + } + + [Fact] + public void ReliableSqlCommandConstructorTests() + { + // verify default constructor doesn't throw + Assert.NotNull(new ReliableSqlConnection.ReliableSqlCommand()); + + // verify constructor with null connection doesn't throw + Assert.NotNull(new ReliableSqlConnection.ReliableSqlCommand(null)); + } + + [Fact] + public void ReliableSqlCommandProperties() + { + var command = new ReliableSqlConnection.ReliableSqlCommand(); + command.CommandText = "SELECT 1"; + Assert.Equal(command.CommandText, "SELECT 1"); + Assert.NotNull(command.CommandTimeout); + Assert.NotNull(command.CommandType); + command.DesignTimeVisible = true; + Assert.True(command.DesignTimeVisible); + command.UpdatedRowSource = UpdateRowSource.None; + Assert.Equal(command.UpdatedRowSource, UpdateRowSource.None); + Assert.NotNull(command.GetUnderlyingCommand()); + Assert.Throws(() => command.ValidateConnectionIsSet()); + command.Prepare(); + Assert.NotNull(command.CreateParameter()); + command.Cancel(); + } + + [Fact] + public void ReliableConnectionResourcesTests() + { + Assert.NotNull(Resources.ConnectionPassedToIsCloudShouldBeOpen); + Assert.NotNull(Resources.ExceptionCannotBeRetried); + Assert.NotNull(Resources.FailedToCacheIsCloud); + Assert.NotNull(Resources.FailedToParseConnectionString); + Assert.NotNull(Resources.InvalidCommandType); + Assert.NotNull(Resources.InvalidConnectionType); + Assert.NotNull(Resources.OnlyReliableConnectionSupported); + Assert.NotNull(Resources.UnableToAssignValue); + Assert.NotNull(Resources.UnableToRetrieveAzureSessionId); + } + + [Fact] + public void CalcExponentialRetryDelayWithSchemaDefaultsTest() + { + Assert.NotNull(RetryPolicyUtils.CalcExponentialRetryDelayWithSchemaDefaults(1)); + } + + [Fact] + public void IsSupportedCommandNullCommandTest() + { + Assert.False(DbCommandWrapper.IsSupportedCommand(null)); + } + + [Fact] + public void StatementCompletedTests() + { + bool handlerCalled = false; + StatementCompletedEventHandler handler = (s, e) => handlerCalled = true; + var command = new DbCommandWrapper(new SqlCommand()); + command.StatementCompleted += handler; + command.StatementCompleted -= handler; + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs index a9e346cd..b034609a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs @@ -39,8 +39,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices private Mock> requestContext; + private Mock scriptFile; + private Mock binder; + private ScriptParseInfo scriptParseInfo; + private TextDocumentPosition textDocument; private void InitializeTestObjects() @@ -60,14 +64,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); // set up file for returning the query - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery); - fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); + scriptFile = new Mock(); + scriptFile.SetupGet(file => file.Contents).Returns(Common.StandardQuery); + scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); // set up workspace mock workspaceService = new Mock>(); workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); + .Returns(scriptFile.Object); // setup binding queue mock bindingQueue = new Mock(); @@ -93,16 +97,113 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices It.IsAny(), It.IsAny())); - var testScriptParseInfo = new ScriptParseInfo(); - LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo); - testScriptParseInfo.IsConnected = true; - testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); + scriptParseInfo = new ScriptParseInfo(); + LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, scriptParseInfo); + scriptParseInfo.IsConnected = true; + scriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); // setup the binding context object ConnectedBindingContext bindingContext = new ConnectedBindingContext(); bindingContext.Binder = binder.Object; bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); - LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext); + LanguageService.Instance.BindingQueue.BindingContextMap.Add(scriptParseInfo.ConnectionKey, bindingContext); + } + + [Fact] + public void HandleCompletionRequestDisabled() + { + InitializeTestObjects(); + WorkspaceService.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; + Assert.NotNull(LanguageService.HandleCompletionRequest(null, null)); + } + + [Fact] + public void HandleCompletionResolveRequestDisabled() + { + InitializeTestObjects(); + WorkspaceService.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; + Assert.NotNull(LanguageService.HandleCompletionResolveRequest(null, null)); + } + + [Fact] + public void HandleSignatureHelpRequestDisabled() + { + InitializeTestObjects(); + WorkspaceService.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; + Assert.NotNull(LanguageService.HandleSignatureHelpRequest(null, null)); + } + + [Fact] + public void AddOrUpdateScriptParseInfoNullUri() + { + InitializeTestObjects(); + LanguageService.Instance.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo); + Assert.True(LanguageService.Instance.ScriptParseInfoMap.ContainsKey("abracadabra")); + } + + [Fact] + public void GetDefinitionInvalidTextDocument() + { + InitializeTestObjects(); + textDocument.TextDocument.Uri = "invaliduri"; + Assert.Null(LanguageService.Instance.GetDefinition(textDocument, null, null)); + } + + [Fact] + public void RemoveScriptParseInfoNullUri() + { + InitializeTestObjects(); + Assert.False(LanguageService.Instance.RemoveScriptParseInfo("abc123")); + } + + [Fact] + public void IsPreviewWindowNullScriptFileTest() + { + InitializeTestObjects(); + Assert.False(LanguageService.Instance.IsPreviewWindow(null)); + } + + [Fact] + public void GetCompletionItemsInvalidTextDocument() + { + InitializeTestObjects(); + textDocument.TextDocument.Uri = "somethinggoeshere"; + Assert.True(LanguageService.Instance.GetCompletionItems(textDocument, scriptFile.Object, null).Length > 0); + } + + [Fact] + public void GetDiagnosticFromMarkerTest() + { + var scriptFileMarker = new ScriptFileMarker() + { + Message = "Message", + Level = ScriptFileMarkerLevel.Error, + ScriptRegion = new ScriptRegion() + { + File = "file://nofile.sql", + StartLineNumber = 1, + StartColumnNumber = 1, + StartOffset = 0, + EndLineNumber = 1, + EndColumnNumber = 1, + EndOffset = 0 + } + }; + var diagnostic = DiagnosticsHelper.GetDiagnosticFromMarker(scriptFileMarker); + Assert.Equal(diagnostic.Message, scriptFileMarker.Message); + } + + [Fact] + public void MapDiagnosticSeverityTest() + { + var level = ScriptFileMarkerLevel.Error; + Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Error); + level = ScriptFileMarkerLevel.Warning; + Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Warning); + level = ScriptFileMarkerLevel.Information; + Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Information); + level = (ScriptFileMarkerLevel)100; + Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Error); } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs index 841196f5..326a962f 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs @@ -3,8 +3,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.LanguageServices; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Test.Utility; @@ -147,6 +149,56 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer Assert.Null(signatureHelp); } + [Fact] + public void EmptyCompletionListTest() + { + Assert.Equal(AutoCompleteHelper.EmptyCompletionList.Length, 0); + } + + [Fact] + public void SetWorkspaceServiceInstanceTest() + { + AutoCompleteHelper.WorkspaceServiceInstance = null; + // workspace will be recreated if it's set to null + Assert.NotNull(AutoCompleteHelper.WorkspaceServiceInstance); + } + + internal class TestScriptDocumentInfo : ScriptDocumentInfo + { + public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo) + :base(textDocumentPosition, scriptFile, scriptParseInfo) + { + + } + + public override string TokenText + { + get + { + return "doesntmatchanythingintheintellisensedefaultlist"; + } + } + } + + [Fact] + public void GetDefaultCompletionListWithNoMatchesTest() + { + var scriptFile = new ScriptFile(); + scriptFile.SetFileContents("koko wants a bananas"); + + ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = false }; + + var scriptDocumentInfo = new TestScriptDocumentInfo( + new TextDocumentPosition() + { + TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri }, + Position = new Position() { Line = 0, Character = 0 } + }, scriptFile, scriptInfo); + + AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false); + + } + #endregion #region "General Language Service tests" @@ -170,11 +222,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile); } - + /// /// Test the service initialization code path and verify nothing throws /// - // Test is causing failures in build lab..investigating to reenable [Fact] public void ServiceInitialization() { @@ -190,12 +241,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer Assert.True(LanguageService.ConnectionServiceInstance != null); Assert.True(LanguageService.Instance.CurrentSettings != null); Assert.True(LanguageService.Instance.CurrentWorkspace != null); - } + } /// /// Test the service initialization code path and verify nothing throws /// - // Test is causing failures in build lab..investigating to reenable [Fact] public void PrepopulateCommonMetadata() { diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs index b70c6c54..ac55d9cd 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs @@ -5,22 +5,27 @@ using System; using System.IO; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices; +using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.SqlParser.Binder; +using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Test.Utility; -using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; using Moq; using Xunit; +using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices { @@ -89,6 +94,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices requestContext = new Mock>(); requestContext.Setup(rc => rc.SendResult(It.IsAny())) .Returns(Task.FromResult(0)); + requestContext.Setup(rc => rc.SendError(It.IsAny())).Returns(Task.FromResult(0));; + requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0));; + requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0));; // setup the IBinder mock binder = new Mock(); @@ -99,7 +107,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices var testScriptParseInfo = new ScriptParseInfo(); LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo); - testScriptParseInfo.IsConnected = true; + testScriptParseInfo.IsConnected = false; testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); // setup the binding context object @@ -111,18 +119,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices /// - /// Tests the definition event handler. When called with no active connection, no definition is sent + /// Tests the definition event handler. When called with no active connection, an error is sent /// [Fact] - public void DefinitionsHandlerWithNoConnectionTest() + public async Task DefinitionsHandlerWithNoConnectionTest() { + TestObjects.InitializeTestServices(); InitializeTestObjects(); - // request the completion list - Task handleCompletion = LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object); - handleCompletion.Wait(TaskTimeout); - - // verify that send result was not called + // request definition + var definitionTask = await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout)); + await definitionTask; + // verify that send result was not called and send error was called requestContext.Verify(m => m.SendResult(It.IsAny()), Times.Never()); + requestContext.Verify(m => m.SendError(It.IsAny()), Times.Once()); } /// @@ -132,7 +141,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices public void GetLocationFromFileForValidFilePathTest() { String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql"; - PeekDefinition peekDefinition = new PeekDefinition(null); + PeekDefinition peekDefinition = new PeekDefinition(null, null); Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0); String expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql"; @@ -145,11 +154,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest() { - PeekDefinition peekDefinition = new PeekDefinition(null); + PeekDefinition peekDefinition = new PeekDefinition(null, null); string validDatabaseQualifiedName = "master.test.test_table"; string objectName = "test_table"; string expectedSchemaName = "test"; - + string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } @@ -161,11 +170,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest() { - PeekDefinition peekDefinition = new PeekDefinition(null); + PeekDefinition peekDefinition = new PeekDefinition(null, null); string validDatabaseQualifiedName = "test_table"; string objectName = "test_table"; string expectedSchemaName = "dbo"; - + string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } @@ -176,15 +185,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest() { - PeekDefinition peekDefinition = new PeekDefinition(null); + PeekDefinition peekDefinition = new PeekDefinition(null, null); string validDatabaseQualifiedName = "x.y.z"; string objectName = "test_table"; string expectedSchemaName = "dbo"; - + string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } - + #if LIVE_CONNECTION_TESTS /// /// Test get definition for a table object with active connection @@ -192,10 +201,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetValidTableDefinitionTest() { - // Get live connectionInfo + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); - string objectName = "test_table"; + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "spt_monitor"; + string schemaName = null; string objectType = "TABLE"; @@ -211,9 +223,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetTableDefinitionInvalidObjectTest() { - // Get live connectionInfo + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); string objectName = "test_invalid"; string schemaName = null; string objectType = "TABLE"; @@ -229,25 +243,102 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetTableDefinitionWithSchemaTest() { - // Get live connectionInfo + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); - string objectName = "test_table"; + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "spt_monitor"; + string schemaName = "dbo"; string objectType = "TABLE"; // Get locations for valid table object with schema name Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType); Assert.NotNull(locations); - Cleanup(locations); + Cleanup(locations); } /// - /// Test GetDefinition with an unsupported type(function) + /// Test GetDefinition with an unsupported type(schema - dbo). Expect a error result. /// [Fact] - public void GetUnsupportedDefinitionForFullScript() + public void GetUnsupportedDefinitionErrorTest() { + ScriptFile scriptFile; + TextDocumentPosition textDocument = new TextDocumentPosition + { + TextDocument = new TextDocumentIdentifier { Uri = OwnerUri }, + Position = new Position + { + Line = 0, + // test for 'dbo' + Character = 16 + } + }; + ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile); + scriptFile.Contents = "select * from dbo.func ()"; + var languageService = new LanguageService(); + ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; + languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo); + + // When I call the language service + var result = languageService.GetDefinition(textDocument, scriptFile, connInfo); + + // Then I expect null locations and an error to be reported + Assert.NotNull(result); + Assert.True(result.IsErrorResult); + } + + /// + /// Get Definition for a object with no definition. Expect a error result + /// + [Fact] + public void GetDefinitionWithNoResultsFoundError() + { + ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "from"; + + List declarations = new List(); + DefinitionResult result = peekDefinition.GetScript(declarations, objectName, null); + + Assert.NotNull(result); + Assert.True(result.IsErrorResult); + Assert.Equal(SR.PeekDefinitionNoResultsError, result.Message); + } + + /// + /// Test GetDefinition with a forced timeout. Expect a error result. + /// + [Fact] + public void GetDefinitionTimeoutTest() + { + // Given a binding queue that will automatically time out + var languageService = new LanguageService(); + Mock queueMock = new Mock(); + languageService.BindingQueue = queueMock.Object; + ManualResetEvent mre = new ManualResetEvent(true); // Do not block + Mock itemMock = new Mock(); + itemMock.Setup(i => i.ItemProcessed).Returns(mre); + + DefinitionResult timeoutResult = null; + + queueMock.Setup(q => q.QueueBindingOperation( + It.IsAny(), + It.IsAny>(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Callback, Func, int?, int?>( + (key, bindOperation, timeoutOperation, blah, blah2) => + { + timeoutResult = (DefinitionResult) timeoutOperation((IBindingContext)null); + itemMock.Object.Result = timeoutResult; + }) + .Returns(() => itemMock.Object); ScriptFile scriptFile; TextDocumentPosition textDocument = new TextDocumentPosition @@ -262,12 +353,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile); scriptFile.Contents = "select * from dbo.func ()"; - var languageService = LanguageService.Instance; ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo); - var locations = languageService.GetDefinition(textDocument, scriptFile, connInfo); - Assert.Null(locations); + // When I call the language service + var result = languageService.GetDefinition(textDocument, scriptFile, connInfo); + + // Then I expect null locations and an error to be reported + Assert.NotNull(result); + Assert.True(result.IsErrorResult); + // Check timeout message + Assert.Equal(SR.PeekDefinitionTimedoutError, result.Message); } /// @@ -277,7 +373,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices public void GetValidViewDefinitionTest() { ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); string objectName = "objects"; string schemaName = "sys"; string objectType = "VIEW"; @@ -293,8 +391,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetViewDefinitionInvalidObjectTest() { + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); string objectName = "objects"; string schemaName = null; string objectType = "VIEW"; @@ -309,9 +410,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetStoredProcedureDefinitionTest() { + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); - string objectName = "SP1"; + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "sp_MSrepl_startup"; + string schemaName = "dbo"; string objectType = "PROCEDURE"; @@ -326,8 +431,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetStoredProcedureDefinitionFailureTest() { + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); string objectName = "SP2"; string schemaName = "dbo"; string objectType = "PROCEDURE"; @@ -342,9 +450,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetStoredProcedureDefinitionWithoutSchemaTest() { + // Get live connectionInfo and serverConnection ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); - PeekDefinition peekDefinition = new PeekDefinition(connInfo); - string objectName = "SP1"; + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "sp_MSrepl_startup"; string schemaName = null; string objectType = "PROCEDURE"; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageDispatcherTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageDispatcherTests.cs new file mode 100644 index 00000000..673b0884 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageDispatcherTests.cs @@ -0,0 +1,88 @@ +// +// 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.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Messaging +{ + public class MessageDispatcherTests + { + [Fact] + public void SetRequestHandlerWithOverrideTest() + { + RequestType requestType = RequestType.Create("test/requestType"); + var dispatcher = new MessageDispatcher(new Mock().Object); + dispatcher.SetRequestHandler( + requestType, + (i, j) => + { + return Task.FromResult(0); + }, + true); + Assert.True(dispatcher.requestHandlers.Count > 0); + } + + [Fact] + public void SetEventHandlerTest() + { + EventType eventType = EventType.Create("test/eventType"); + var dispatcher = new MessageDispatcher(new Mock().Object); + dispatcher.SetEventHandler( + eventType, + (i, j) => + { + return Task.FromResult(0); + }); + Assert.True(dispatcher.eventHandlers.Count > 0); + } + + [Fact] + public void SetEventHandlerWithOverrideTest() + { + EventType eventType = EventType.Create("test/eventType"); + var dispatcher = new MessageDispatcher(new Mock().Object); + dispatcher.SetEventHandler( + eventType, + (i, j) => + { + return Task.FromResult(0); + }, + true); + Assert.True(dispatcher.eventHandlers.Count > 0); + } + + [Fact] + public void OnListenTaskCompletedFaultedTaskTest() + { + Task t = null; + + try + { + t = Task.Run(() => + { + throw new Exception(); + }); + t.Wait(); + } + catch + { + } + finally + { + bool handlerCalled = false; + var dispatcher = new MessageDispatcher(new Mock().Object); + dispatcher.UnhandledException += (s, e) => handlerCalled = true; + dispatcher.OnListenTaskCompleted(t); + Assert.True(handlerCalled); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageWriterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageWriterTests.cs index 3c007a85..4cc99592 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageWriterTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Messaging/MessageWriterTests.cs @@ -7,7 +7,9 @@ using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers; +using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.Messaging @@ -21,6 +23,40 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Messaging this.messageSerializer = new V8MessageSerializer(); } + [Fact] + public void SerializeMessageTest() + { + // serialize\deserialize a request + var message = new Message(); + message.MessageType = MessageType.Request; + message.Id = "id"; + message.Method = "method"; + message.Contents = null; + var serializedMessage = this.messageSerializer.SerializeMessage(message); + Assert.NotNull(serializedMessage); + var deserializedMessage = this.messageSerializer.DeserializeMessage(serializedMessage); + Assert.Equal(message.Id, deserializedMessage.Id); + + // serialize\deserialize a response + message.MessageType = MessageType.Response; + serializedMessage = this.messageSerializer.SerializeMessage(message); + Assert.NotNull(serializedMessage); + deserializedMessage = this.messageSerializer.DeserializeMessage(serializedMessage); + Assert.Equal(message.Id, deserializedMessage.Id); + + // serialize\deserialize a response with an error + message.Error = JToken.FromObject("error"); + serializedMessage = this.messageSerializer.SerializeMessage(message); + Assert.NotNull(serializedMessage); + deserializedMessage = this.messageSerializer.DeserializeMessage(serializedMessage); + Assert.Equal(message.Error, deserializedMessage.Error); + + // serialize\deserialize an unknown response type + serializedMessage.Remove("type"); + serializedMessage.Add("type", JToken.FromObject("dontknowthisone")); + Assert.Equal(this.messageSerializer.DeserializeMessage(serializedMessage).MessageType, MessageType.Unknown); + } + [Fact] public async Task WritesMessage() { diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/StorageDataReaderTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/StorageDataReaderTests.cs index e29c8394..567e1a7a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/StorageDataReaderTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/StorageDataReaderTests.cs @@ -5,6 +5,7 @@ #if LIVE_CONNECTION_TESTS +using System; using System.Data.Common; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; @@ -60,8 +61,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage reader.Read(); Assert.False(storageReader.IsDBNull(0)); + Assert.NotNull(storageReader.GetValue(0)); + string shortName = storageReader.GetCharsWithMaxCapacity(0, 2); Assert.True(shortName.Length == 2); + + Assert.Throws(() => storageReader.GetBytesWithMaxCapacity(0, 0)); + Assert.Throws(() => storageReader.GetCharsWithMaxCapacity(0, 0)); } /// @@ -88,10 +94,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage [Fact] public void StringWriterWithMaxCapacityTest() { - var writer = new StorageDataReader.StringWriterWithMaxCapacity(null, 100); + var writer = new StorageDataReader.StringWriterWithMaxCapacity(null, 4); string output = "..."; writer.Write(output); Assert.True(writer.ToString().Equals(output)); + writer.Write('.'); + Assert.True(writer.ToString().Equals(output + '.')); + writer.Write(output); + writer.Write('.'); + Assert.True(writer.ToString().Equals(output + '.')); } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs index 12b99a3b..9ac1f6c2 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; +using System.Data; using System.Data.Common; +using System.Data.SqlClient; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -388,8 +390,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // Then: // ... It should throw an exception Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, -1, Common.GetFileStreamFactory(null))); - } + } + [Fact] + public void StatementCompletedHandlerTest() + { + // If: + // ... I call the StatementCompletedHandler + // Then: + // ... a ResultMaessage should be logged in the resultsMessages collection + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory(null)); + batch.StatementCompletedHandler(null, new StatementCompletedEventArgs(1)); + Assert.True(batch.ResultMessages.Count() == 1); + batch.StatementCompletedHandler(null, new StatementCompletedEventArgs(2)); + Assert.True(batch.ResultMessages.Count() == 2); + } + private static DbConnection GetConnection(ConnectionInfo info) { return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails)); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/DbColumnWrapperTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/DbColumnWrapperTests.cs new file mode 100644 index 00000000..ca88699b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/DbColumnWrapperTests.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Data.Common; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution +{ + /// + /// DbColumnWrapper tests + /// + public class DbColumnWrapperTests + { + /// + /// Test DbColumn derived class + /// + class TestColumn : DbColumn + { + public TestColumn( + string dataTypeName = null, + int? columnSize = null, + string columnName = null, + string udtAssemblyQualifiedName = null) + { + if (!string.IsNullOrEmpty(dataTypeName)) + { + this.DataTypeName = dataTypeName; + } + + if (columnSize.HasValue) + { + this.ColumnSize = columnSize; + } + + if (!string.IsNullOrEmpty(columnName)) + { + this.ColumnName = columnName; + } + + if (!string.IsNullOrEmpty(udtAssemblyQualifiedName)) + { + this.UdtAssemblyQualifiedName = udtAssemblyQualifiedName; + } + } + } + + /// + /// Basic data type and properites test + /// + [Fact] + public void DataTypeAndPropertiesTest() + { + // check that data types array contains items + var serverDataTypes = DbColumnWrapper.AllServerDataTypes; + Assert.True(serverDataTypes.Count > 0); + + // check default constructor doesn't throw + Assert.NotNull(new DbColumnWrapper()); + + // check various properties are either null or not null + var column = new TestColumn(); + var wrapper = new DbColumnWrapper(column); + Assert.NotNull(wrapper.DataType); + Assert.Null(wrapper.AllowDBNull); + Assert.Null(wrapper.BaseCatalogName); + Assert.Null(wrapper.BaseColumnName); + Assert.Null(wrapper.BaseServerName); + Assert.Null(wrapper.BaseTableName); + Assert.Null(wrapper.ColumnOrdinal); + Assert.Null(wrapper.ColumnSize); + Assert.Null(wrapper.IsAliased); + Assert.Null(wrapper.IsAutoIncrement); + Assert.Null(wrapper.IsExpression); + Assert.Null(wrapper.IsHidden); + Assert.Null(wrapper.IsIdentity); + Assert.Null(wrapper.IsKey); + Assert.Null(wrapper. IsReadOnly); + Assert.Null(wrapper.IsUnique); + Assert.Null(wrapper.NumericPrecision); + Assert.Null(wrapper.NumericScale); + Assert.Null(wrapper.UdtAssemblyQualifiedName); + Assert.Null(wrapper.DataTypeName); + } + + /// + /// constructor test + /// + [Fact] + public void DbColumnConstructorTests() + { + // check that various constructor parameters initial the wrapper correctly + var w1 = new DbColumnWrapper(new TestColumn("varchar", int.MaxValue, "Microsoft SQL Server 2005 XML Showplan")); + Assert.True(w1.IsXml); + + var w2 = new DbColumnWrapper(new TestColumn("binary")); + Assert.True(w2.IsBytes); + + var w3 = new DbColumnWrapper(new TestColumn("varbinary", int.MaxValue)); + Assert.True(w3.IsBytes); + + var w4 = new DbColumnWrapper(new TestColumn("sql_variant")); + Assert.True(w4.IsSqlVariant); + + var w5 = new DbColumnWrapper(new TestColumn("my_udt")); + Assert.True(w5.IsUdt); + + var w6 = new DbColumnWrapper(new TestColumn("my_hieracrchy", null, null, "MICROSOFT.SQLSERVER.TYPES.SQLHIERARCHYID")); + Assert.True(w6.IsUdt); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/LongListTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/LongListTests.cs index a28714bc..330214ca 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/LongListTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/LongListTests.cs @@ -25,5 +25,34 @@ namespace Microsoft.SqlTools.Test.Utility longList.RemoveAt(0); Assert.True(longList.Count == 0); } + + /// + /// Add and remove and item in a LongList causing an expansion + /// + [Fact] + public void LongListExpandTest() + { + var longList = new LongList(); + longList.ExpandListSize = 3; + for (int i = 0; i < 6; ++i) + { + longList.Add(i); + } + Assert.Equal(longList.Count, 6); + Assert.NotNull(longList.GetItem(4)); + + bool didEnum = false; + foreach (var j in longList) + { + didEnum = true; + break; + } + + Assert.True(didEnum); + + longList.RemoveAt(4); + Assert.Equal(longList.Count, 5); + } } } + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/SrTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/SrTests.cs new file mode 100644 index 00000000..8b15df20 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/SrTests.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer; +using Xunit; + +namespace Microsoft.SqlTools.Test.Utility +{ + public class SrTests + { + /// + /// Add and remove and item in a LongList + /// + [Fact] + public void SrPropertiesTest() + { + Assert.NotNull(SR.QueryServiceSubsetBatchNotCompleted); + Assert.NotNull(SR.QueryServiceFileWrapperWriteOnly); + Assert.NotNull(SR.QueryServiceFileWrapperNotInitialized); + Assert.NotNull(SR.QueryServiceColumnNull); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs index 00fc7f13..7af19715 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestObjects.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Data.SqlClient; using System.IO; using System.Reflection; +using Microsoft.SqlServer.Management.Common; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Credentials; @@ -228,6 +230,12 @@ namespace Microsoft.SqlTools.Test.Utility } }; } + + public static ServerConnection InitLiveServerConnectionForDefinition(ConnectionInfo connInfo) + { + SqlConnection sqlConn = new SqlConnection(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails)); + return new ServerConnection(sqlConn); + } } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/ValidateTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/ValidateTests.cs new file mode 100644 index 00000000..8f4f790d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/ValidateTests.cs @@ -0,0 +1,38 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.Utility; +using Xunit; + +namespace Microsoft.SqlTools.Test.Utility +{ + public class ValidateTests + { + [Fact] + public void IsWithinRangeTest() + { + Assert.Throws(() => Validate.IsWithinRange("parameterName", 1, 2, 3)); + } + + [Fact] + public void IsLessThanTest() + { + Assert.Throws(() => Validate.IsLessThan("parameterName", 2, 1)); + } + + [Fact] + public void IsNotEqualTest() + { + Assert.Throws(() => Validate.IsNotEqual("parameterName", 1, 1)); + } + + [Fact] + public void IsNullOrWhiteSpaceTest() + { + Assert.Throws(() => Validate.IsNotNullOrWhitespaceString("parameterName", null)); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Workspace/WorkspaceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Workspace/WorkspaceTests.cs index ad5c3b3d..df63e816 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Workspace/WorkspaceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Workspace/WorkspaceTests.cs @@ -3,10 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.IO; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Test.Utility; @@ -90,5 +92,58 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Workspace Assert.Empty(workspace.GetOpenedFiles()); Assert.False(callbackCalled); } + + [Fact] + public void BufferRangeNoneNotNull() + { + Assert.NotNull(BufferRange.None); + } + + [Fact] + public void BufferRangeStartGreaterThanEnd() + { + Assert.Throws(() => + new BufferRange(new BufferPosition(2, 2), new BufferPosition(1, 1))); + } + + [Fact] + public void BufferRangeEquals() + { + var range = new BufferRange(new BufferPosition(1, 1), new BufferPosition(2, 2)); + Assert.False(range.Equals(null)); + Assert.True(range.Equals(range)); + Assert.NotNull(range.GetHashCode()); + } + + [Fact] + public void UnescapePath() + { + Assert.NotNull(Microsoft.SqlTools.ServiceLayer.Workspace.Workspace.UnescapePath("`/path/`")); + } + + [Fact] + public void GetBaseFilePath() + { + TestUtils.RunIfWindows(() => + { + using (var workspace = new ServiceLayer.Workspace.Workspace()) + { + Assert.Throws(() => workspace.GetBaseFilePath("path")); + Assert.NotNull(workspace.GetBaseFilePath(@"c:\path\file.sql")); + Assert.Equal(workspace.GetBaseFilePath("tsqloutput://c:/path/file.sql"), workspace.WorkspacePath); + } + }); + } + + [Fact] + public void ResolveRelativeScriptPath() + { + TestUtils.RunIfWindows(() => + { + var workspace = new ServiceLayer.Workspace.Workspace(); + Assert.NotNull(workspace.ResolveRelativeScriptPath(null, @"c:\path\file.sql")); + Assert.NotNull(workspace.ResolveRelativeScriptPath(@"c:\path\", "file.sql")); + }); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/project.json b/test/Microsoft.SqlTools.ServiceLayer.Test/project.json index 977de2ab..d294d099 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/project.json +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/project.json @@ -19,7 +19,7 @@ "System.Runtime.Serialization.Primitives": "4.1.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.4.0-sqltools-24613-04", - "Microsoft.SqlServer.Smo": "140.1.11", + "Microsoft.SqlServer.Smo": "140.1.12", "System.Security.SecureString": "4.0.0", "System.Collections.Specialized": "4.0.1", "System.ComponentModel.TypeConverter": "4.1.0",