Files
sqltoolsservice/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs
Sharon Ravindran 5d2bf7fbe4 Feature/delete peek scripts (#174)
* Delete temp script folder

* Delete folder and refactor code

* Add unit tests

* create folder per workspace

* Refactor and move creation to FileUtils

* Separate multiple assignment
2016-12-15 13:34:33 -08:00

516 lines
23 KiB
C#

//
// 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.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.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.Test.Utility;
using Moq;
using Xunit;
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
{
/// <summary>
/// Tests for the language service peek definition/ go to definition feature
/// </summary>
public class PeekDefinitionTests
{
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 const string OwnerUri = "testFile1";
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(Common.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>()))
.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<DefinitionError>())).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>
[Fact]
public async Task DefinitionsHandlerWithNoConnectionTest()
{
TestObjects.InitializeTestServices();
InitializeTestObjects();
// 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<Location[]>()), Times.Never());
requestContext.Verify(m => m.SendError(It.IsAny<DefinitionError>()), Times.Once());
}
/// <summary>
/// Tests creating location objects on windows and non-windows systems
/// </summary>
[Fact]
public void GetLocationFromFileForValidFilePathTest()
{
String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql";
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";
Assert.Equal(locations[0].Uri, expectedFilePath);
}
/// <summary>
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid database name
/// </summary>
[Fact]
public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest()
{
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);
}
/// <summary>
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid object name and no schema
/// </summary>
[Fact]
public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest()
{
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);
}
/// <summary>
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a invalid database name
/// </summary>
[Fact]
public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest()
{
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);
}
/// <summary>
/// Test Deletion of peek definition scripts for a valid temp folder that exists
/// </summary>
[Fact]
public void DeletePeekDefinitionScriptsTest()
{
PeekDefinition peekDefinition = new PeekDefinition(null, null);
var languageService = LanguageService.Instance;
Assert.True(Directory.Exists(FileUtils.PeekDefinitionTempFolder));
languageService.DeletePeekDefinitionScripts();
Assert.False(Directory.Exists(FileUtils.PeekDefinitionTempFolder));
}
/// <summary>
/// Test Deletion of peek definition scripts for a temp folder that does not exist
/// </summary>
[Fact]
public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest()
{
var languageService = LanguageService.Instance;
PeekDefinition peekDefinition = new PeekDefinition(null, null);
FileUtils.SafeDirectoryDelete(FileUtils.PeekDefinitionTempFolder, true);
Assert.False(Directory.Exists(FileUtils.PeekDefinitionTempFolder));
// Expected not to throw any exception
languageService.DeletePeekDefinitionScripts();
}
#if LIVE_CONNECTION_TESTS
/// <summary>
/// Test get definition for a table object with active connection
/// </summary>
[Fact]
public void GetValidTableDefinitionTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "spt_monitor";
string schemaName = null;
string objectType = "TABLE";
// Get locations for valid table object
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Test get definition for a invalid table object with active connection
/// </summary>
[Fact]
public void GetTableDefinitionInvalidObjectTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "test_invalid";
string schemaName = null;
string objectType = "TABLE";
// Get locations for invalid table object
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
Assert.Null(locations);
}
/// <summary>
/// Test get definition for a valid table object with schema and active connection
/// </summary>
[Fact]
public void GetTableDefinitionWithSchemaTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
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);
}
/// <summary>
/// Test GetDefinition with an unsupported type(schema - dbo). Expect a error result.
/// </summary>
[Fact]
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);
}
/// <summary>
/// Get Definition for a object with no definition. Expect a error result
/// </summary>
[Fact]
public void GetDefinitionWithNoResultsFoundError()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "from";
List<Declaration> declarations = new List<Declaration>();
DefinitionResult result = peekDefinition.GetScript(declarations, objectName, null);
Assert.NotNull(result);
Assert.True(result.IsErrorResult);
Assert.Equal(SR.PeekDefinitionNoResultsError, result.Message);
}
/// <summary>
/// Test GetDefinition with a forced timeout. Expect a error result.
/// </summary>
[Fact]
public void GetDefinitionTimeoutTest()
{
// Given a binding queue that will automatically time out
var languageService = new LanguageService();
Mock<ConnectedBindingQueue> queueMock = new Mock<ConnectedBindingQueue>();
languageService.BindingQueue = queueMock.Object;
ManualResetEvent mre = new ManualResetEvent(true); // Do not block
Mock<QueueItem> itemMock = new Mock<QueueItem>();
itemMock.Setup(i => i.ItemProcessed).Returns(mre);
DefinitionResult timeoutResult = null;
queueMock.Setup(q => q.QueueBindingOperation(
It.IsAny<string>(),
It.IsAny<Func<IBindingContext, CancellationToken, object>>(),
It.IsAny<Func<IBindingContext, object>>(),
It.IsAny<int?>(),
It.IsAny<int?>()))
.Callback<string, Func<IBindingContext, CancellationToken, object>, Func<IBindingContext, object>, 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
{
TextDocument = new TextDocumentIdentifier { Uri = OwnerUri },
Position = new Position
{
Line = 0,
Character = 20
}
};
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
scriptFile.Contents = "select * from dbo.func ()";
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);
// Check timeout message
Assert.Equal(SR.PeekDefinitionTimedoutError, result.Message);
}
/// <summary>
/// Test get definition for a view object with active connection
/// </summary>
[Fact]
public void GetValidViewDefinitionTest()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "objects";
string schemaName = "sys";
string objectType = "VIEW";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Test get definition for an invalid view object with no schema name and with active connection
/// </summary>
[Fact]
public void GetViewDefinitionInvalidObjectTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "objects";
string schemaName = null;
string objectType = "VIEW";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType);
Assert.Null(locations);
}
/// <summary>
/// Test get definition for a stored procedure object with active connection
/// </summary>
[Fact]
public void GetStoredProcedureDefinitionTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "sp_MSrepl_startup";
string schemaName = "dbo";
string objectType = "PROCEDURE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Test get definition for a stored procedure object that does not exist with active connection
/// </summary>
[Fact]
public void GetStoredProcedureDefinitionFailureTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "SP2";
string schemaName = "dbo";
string objectType = "PROCEDURE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
Assert.Null(locations);
}
/// <summary>
/// Test get definition for a stored procedure object with active connection and no schema
/// </summary>
[Fact]
public void GetStoredProcedureDefinitionWithoutSchemaTest()
{
// Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "sp_MSrepl_startup";
string schemaName = null;
string objectType = "PROCEDURE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Helper method to clean up script files
/// </summary>
private void Cleanup(Location[] locations)
{
Uri fileUri = new Uri(locations[0].Uri);
if (File.Exists(fileUri.LocalPath))
{
try
{
File.Delete(fileUri.LocalPath);
}
catch(Exception)
{
}
}
}
#endif
}
}