Add LanguageFlavorNotification handling and refactor LanguageService (#375)

* Add LanguageFlavorNotification handling and refactor LanguageService
- Added new notification handler for language flavor changed
- Refactored the LanguageService so that it no longer relies on so many intertwined static calls, which meant it was impossible to test without modifying the static instance. This will help with test reliability in the future, and prep for replacing the instance with a service provider.

* Skip if not an MSSQL doc and add test

* Handle definition requests

* Fix diagnostics handling
This commit is contained in:
Kevin Cunnane
2017-06-12 13:28:24 -07:00
committed by GitHub
parent b0263f8867
commit 869cd1439f
13 changed files with 537 additions and 434 deletions

View File

@@ -19,126 +19,71 @@ using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common;
using Moq;
using Xunit;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{
/// <summary>
/// Tests for the language service autocomplete component
/// </summary>
public class AutocompleteTests
public class AutocompleteTests : LanguageServiceTestBase<CompletionItem>
{
private const int TaskTimeout = 60000;
private readonly string testScriptUri = TestObjects.ScriptUri;
private readonly string testConnectionKey = "testdbcontextkey";
private Mock<ConnectedBindingQueue> bindingQueue;
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
private Mock<RequestContext<CompletionItem[]>> requestContext;
private Mock<ScriptFile> scriptFile;
private Mock<IBinder> binder;
private ScriptParseInfo scriptParseInfo;
private TextDocumentPosition textDocument;
private void InitializeTestObjects()
{
// initial cursor position in the script file
textDocument = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
Position = new Position
{
Line = 0,
Character = 0
}
};
// default settings are stored in the workspace service
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
scriptFile = new Mock<ScriptFile>();
scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery);
scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
// set up workspace mock
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(scriptFile.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
.Returns(this.testConnectionKey);
// inject mock instances into the Language Service
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
LanguageService.Instance.BindingQueue = bindingQueue.Object;
// setup the mock for SendResult
requestContext = new Mock<RequestContext<CompletionItem[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<CompletionItem[]>()))
.Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
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(scriptParseInfo.ConnectionKey, bindingContext);
}
[Fact]
public void HandleCompletionRequestDisabled()
{
InitializeTestObjects();
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(LanguageService.HandleCompletionRequest(null, null));
langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(langService.HandleCompletionRequest(null, null));
}
[Fact]
public void HandleCompletionResolveRequestDisabled()
{
InitializeTestObjects();
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(LanguageService.HandleCompletionResolveRequest(null, null));
langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(langService.HandleCompletionResolveRequest(null, null));
}
[Fact]
public void HandleSignatureHelpRequestDisabled()
{
InitializeTestObjects();
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(LanguageService.HandleSignatureHelpRequest(null, null));
langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(langService.HandleSignatureHelpRequest(null, null));
}
[Fact]
public void HandleSignatureHelpRequestNonMssqlFile()
{
InitializeTestObjects();
// setup the mock for SendResult
var signatureRequestContext = new Mock<RequestContext<SignatureHelp>>();
signatureRequestContext.Setup(rc => rc.SendResult(It.IsAny<SignatureHelp>()))
.Returns(Task.FromResult(0));
signatureRequestContext.Setup(rc => rc.SendError(It.IsAny<string>(), It.IsAny<int>())).Returns(Task.FromResult(0));
langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = true;
langService.HandleDidChangeLanguageFlavorNotification(new LanguageFlavorChangeParams {
Uri = textDocument.TextDocument.Uri,
Language = LanguageService.SQL_LANG.ToLower(),
Flavor = "NotMSSQL"
}, null);
Assert.NotNull(langService.HandleSignatureHelpRequest(textDocument, signatureRequestContext.Object));
// verify that no events were sent
signatureRequestContext.Verify(m => m.SendResult(It.IsAny<SignatureHelp>()), Times.Never());
signatureRequestContext.Verify(m => m.SendError(It.IsAny<string>(), It.IsAny<int>()), Times.Never());
}
[Fact]
public void AddOrUpdateScriptParseInfoNullUri()
{
InitializeTestObjects();
LanguageService.Instance.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo);
Assert.True(LanguageService.Instance.ScriptParseInfoMap.ContainsKey("abracadabra"));
langService.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo);
Assert.True(langService.ScriptParseInfoMap.ContainsKey("abracadabra"));
}
[Fact]
@@ -146,21 +91,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{
InitializeTestObjects();
textDocument.TextDocument.Uri = "invaliduri";
Assert.Null(LanguageService.Instance.GetDefinition(textDocument, null, null));
Assert.Null(langService.GetDefinition(textDocument, null, null));
}
[Fact]
public void RemoveScriptParseInfoNullUri()
{
InitializeTestObjects();
Assert.False(LanguageService.Instance.RemoveScriptParseInfo("abc123"));
Assert.False(langService.RemoveScriptParseInfo("abc123"));
}
[Fact]
public void IsPreviewWindowNullScriptFileTest()
{
InitializeTestObjects();
Assert.False(LanguageService.Instance.IsPreviewWindow(null));
Assert.False(langService.IsPreviewWindow(null));
}
[Fact]
@@ -168,7 +113,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{
InitializeTestObjects();
textDocument.TextDocument.Uri = "somethinggoeshere";
Assert.True(LanguageService.Instance.GetCompletionItems(textDocument, scriptFile.Object, null).Length > 0);
Assert.True(langService.GetCompletionItems(textDocument, scriptFile.Object, null).Length > 0);
}
[Fact]
@@ -215,7 +160,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
InitializeTestObjects();
// request the completion list
Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object);
Task handleCompletion = langService.HandleCompletionRequest(textDocument, requestContext.Object);
handleCompletion.Wait(TaskTimeout);
// verify that send result was called with a completion array

View File

@@ -0,0 +1,119 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common;
using Moq;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{
/// <summary>
/// Tests for the language service autocomplete component
/// </summary>
public abstract class LanguageServiceTestBase<T>
{
protected const int TaskTimeout = 60000;
protected readonly string testScriptUri = TestObjects.ScriptUri;
protected readonly string testConnectionKey = "testdbcontextkey";
protected LanguageService langService;
protected Mock<ConnectedBindingQueue> bindingQueue;
protected Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
protected Mock<RequestContext<T[]>> requestContext;
protected Mock<ScriptFile> scriptFile;
protected Mock<IBinder> binder;
internal ScriptParseInfo scriptParseInfo;
protected TextDocumentPosition textDocument;
protected void InitializeTestObjects()
{
// initial cursor position in the script file
textDocument = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier { Uri = this.testScriptUri },
Position = new Position
{
Line = 0,
Character = 23
}
};
// default settings are stored in the workspace service
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
scriptFile = new Mock<ScriptFile>();
scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery);
scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
// set up workspace mock
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(scriptFile.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
.Returns(this.testConnectionKey);
langService = new LanguageService();
// inject mock instances into the Language Service
langService.WorkspaceServiceInstance = workspaceService.Object;
langService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
langService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
langService.BindingQueue = bindingQueue.Object;
// setup the mock for SendResult
requestContext = new Mock<RequestContext<T[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<T[]>()))
.Returns(Task.FromResult(0));
requestContext.Setup(rc => rc.SendError(It.IsAny<string>(), It.IsAny<int>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())).Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
scriptParseInfo = new ScriptParseInfo();
langService.AddOrUpdateScriptParseInfo(this.testScriptUri, scriptParseInfo);
scriptParseInfo.IsConnected = true;
scriptParseInfo.ConnectionKey = langService.BindingQueue.AddConnectionContext(connectionInfo);
// setup the binding context object
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
bindingContext.Binder = binder.Object;
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
langService.BindingQueue.BindingContextMap.Add(scriptParseInfo.ConnectionKey, bindingContext);
}
}
}

View File

@@ -150,14 +150,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
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,

View File

@@ -33,90 +33,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
/// <summary>
/// Tests for the language service peek definition/ go to definition feature
/// </summary>
public class PeekDefinitionTests
public class PeekDefinitionTests : LanguageServiceTestBase<Location>
{
private const int TaskTimeout = 30000;
private readonly string testScriptUri = TestObjects.ScriptUri;
private readonly string testConnectionKey = "testdbcontextkey";
private Mock<ConnectedBindingQueue> bindingQueue;
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
private Mock<RequestContext<Location[]>> requestContext;
private Mock<IBinder> binder;
private TextDocumentPosition textDocument;
private void InitializeTestObjects()
{
// initial cursor position in the script file
textDocument = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
Position = new Position
{
Line = 0,
Character = 23
}
};
// default settings are stored in the workspace service
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
var fileMock = new Mock<ScriptFile>();
fileMock.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery);
fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
// set up workspace mock
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(fileMock.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
.Returns(this.testConnectionKey);
// inject mock instances into the Language Service
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
LanguageService.Instance.BindingQueue = bindingQueue.Object;
// setup the mock for SendResult
requestContext = new Mock<RequestContext<Location[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
.Returns(Task.FromResult(0));
requestContext.Setup(rc => rc.SendError(It.IsAny<string>(), It.IsAny<int>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())).Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
var testScriptParseInfo = new ScriptParseInfo();
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
testScriptParseInfo.IsConnected = false;
testScriptParseInfo.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);
}
/// <summary>
/// Tests the definition event handler. When called with no active connection, an error is sent
/// </summary>
@@ -124,8 +42,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
public async Task DefinitionsHandlerWithNoConnectionTest()
{
InitializeTestObjects();
scriptParseInfo.IsConnected = false;
// request definition
var definitionTask = await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout));
var definitionTask = await Task.WhenAny(langService.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<Location[]>()), Times.Never());
@@ -199,9 +118,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
public void DeletePeekDefinitionScriptsTest()
{
Scripter peekDefinition = new Scripter(null, null);
var languageService = LanguageService.Instance;
Assert.True(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
languageService.DeletePeekDefinitionScripts();
LanguageService.Instance.DeletePeekDefinitionScripts();
Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
}
@@ -211,12 +129,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
[Fact]
public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest()
{
var languageService = LanguageService.Instance;
Scripter peekDefinition = new Scripter(null, null);
FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true);
Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
// Expected not to throw any exception
languageService.DeletePeekDefinitionScripts();
LanguageService.Instance.DeletePeekDefinitionScripts();
}
/// <summary>