mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-23 01:25:42 -05:00
Add IntelliSense binding queue (#73)
* Initial code for binding queue * Fix-up some of the timeout wait code * Add some initial test code * Add missing test file * Update the binding queue tests * Add more test coverage and refactor a bit. Disable reliabile connection until we can fix it..it's holding an open data reader connection. * A few more test updates * Initial integrate queue with language service. * Hook up the connected binding queue into al binding calls. * Cleanup comments and remove dead code * More missing comments * Fix build break. Reenable ReliabileConnection. * Revert all changes to SqlConnectionFactory * Resolve merge conflicts * Cleanup some more of the timeouts and sync code * Address code review feedback * Address more code review feedback
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// 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.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
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 Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the language service autocomplete component
|
||||
/// </summary>
|
||||
public class AutocompleteTests
|
||||
{
|
||||
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<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 = 0
|
||||
}
|
||||
};
|
||||
|
||||
// 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<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>()));
|
||||
|
||||
var testScriptParseInfo = new ScriptParseInfo();
|
||||
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
|
||||
testScriptParseInfo.IsConnected = true;
|
||||
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 primary completion list event handler
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCompletionsHandlerTest()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
|
||||
// request the completion list
|
||||
Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object);
|
||||
handleCompletion.Wait(TaskTimeout);
|
||||
|
||||
// verify that send result was called with a completion array
|
||||
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test class for the test binding context
|
||||
/// </summary>
|
||||
public class TestBindingContext : IBindingContext
|
||||
{
|
||||
public TestBindingContext()
|
||||
{
|
||||
this.BindingLocked = new ManualResetEvent(initialState: true);
|
||||
this.BindingTimeout = 3000;
|
||||
}
|
||||
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
public ServerConnection ServerConnection { get; set; }
|
||||
|
||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
public SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||
|
||||
public IBinder Binder { get; set; }
|
||||
|
||||
public ManualResetEvent BindingLocked { get; set; }
|
||||
|
||||
public int BindingTimeout { get; set; }
|
||||
|
||||
public ParseOptions ParseOptions { get; }
|
||||
|
||||
public ServerVersion ServerVersion { get; }
|
||||
|
||||
public DatabaseEngineType DatabaseEngineType { get; }
|
||||
|
||||
public TransactSqlVersion TransactSqlVersion { get; }
|
||||
|
||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Binding Queue
|
||||
/// </summary>
|
||||
public class BindingQueueTests
|
||||
{
|
||||
private int bindCallCount = 0;
|
||||
|
||||
private int timeoutCallCount = 0;
|
||||
|
||||
private int bindCallbackDelay = 0;
|
||||
|
||||
private bool isCancelationRequested = false;
|
||||
|
||||
private IBindingContext bindingContext = null;
|
||||
|
||||
private BindingQueue<TestBindingContext> bindingQueue = null;
|
||||
|
||||
private void InitializeTestSettings()
|
||||
{
|
||||
this.bindCallCount = 0;
|
||||
this.timeoutCallCount = 0;
|
||||
this.bindCallbackDelay = 10;
|
||||
this.isCancelationRequested = false;
|
||||
this.bindingContext = GetMockBindingContext();
|
||||
this.bindingQueue = new BindingQueue<TestBindingContext>();
|
||||
}
|
||||
|
||||
private IBindingContext GetMockBindingContext()
|
||||
{
|
||||
return new TestBindingContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test bind operation callback
|
||||
/// </summary>
|
||||
private Task<object> TestBindOperation(
|
||||
IBindingContext bindContext,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay);
|
||||
this.isCancelationRequested = cancelToken.IsCancellationRequested;
|
||||
if (!this.isCancelationRequested)
|
||||
{
|
||||
++this.bindCallCount;
|
||||
}
|
||||
return new CompletionItem[0] as object;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test callback for the bind timeout operation
|
||||
/// </summary>
|
||||
private Task<object> TestTimeoutOperation(
|
||||
IBindingContext bindingContext)
|
||||
{
|
||||
++this.timeoutCallCount;
|
||||
return Task.FromResult(new CompletionItem[0] as object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs for a few seconds to allow the queue to pump any requests
|
||||
/// </summary>
|
||||
private void WaitForQueue(int delay = 5000)
|
||||
{
|
||||
int step = 50;
|
||||
int steps = delay / step + 1;
|
||||
for (int i = 0; i < steps; ++i)
|
||||
{
|
||||
Thread.Sleep(step);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a single task
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void QueueOneBindingOperationTest()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
|
||||
WaitForQueue();
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 1);
|
||||
Assert.True(this.timeoutCallCount == 0);
|
||||
Assert.False(this.isCancelationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a 100 short tasks
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Queue100BindingOperationTest()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
}
|
||||
|
||||
WaitForQueue();
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 100);
|
||||
Assert.True(this.timeoutCallCount == 0);
|
||||
Assert.False(this.isCancelationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an task with a long operation causing a timeout
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void QueueWithTimeout()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
this.bindCallbackDelay = 10000;
|
||||
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
|
||||
WaitForQueue(this.bindCallbackDelay + 2000);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 0);
|
||||
Assert.True(this.timeoutCallCount == 1);
|
||||
Assert.True(this.isCancelationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
#region "Diagnostics tests"
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the latest SqlParser (2016 as of this writing) is used by default
|
||||
/// </summary>
|
||||
@@ -154,12 +155,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
InitializeTestServices();
|
||||
|
||||
Assert.True(LanguageService.Instance.Context != null);
|
||||
Assert.True(LanguageService.Instance.ConnectionServiceInstance != null);
|
||||
Assert.True(LanguageService.ConnectionServiceInstance != null);
|
||||
Assert.True(LanguageService.Instance.CurrentSettings != null);
|
||||
Assert.True(LanguageService.Instance.CurrentWorkspace != null);
|
||||
|
||||
LanguageService.Instance.ConnectionServiceInstance = null;
|
||||
Assert.True(LanguageService.Instance.ConnectionServiceInstance == null);
|
||||
LanguageService.ConnectionServiceInstance = null;
|
||||
Assert.True(LanguageService.ConnectionServiceInstance == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,7 +168,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void UpdateLanguageServiceOnConnection()
|
||||
{
|
||||
{
|
||||
string ownerUri = "file://my/sample/file.sql";
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
@@ -177,7 +178,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
OwnerUri = ownerUri,
|
||||
Connection = TestObjects.GetTestConnectionDetails()
|
||||
});
|
||||
|
||||
|
||||
// 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(ownerUri);
|
||||
|
||||
// set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object;
|
||||
|
||||
ConnectionInfo connInfo = null;
|
||||
connectionService.TryFindConnection(ownerUri, out connInfo);
|
||||
|
||||
@@ -212,7 +225,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||
scriptInfo.IsConnected = true;
|
||||
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo);
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo, null);
|
||||
}
|
||||
|
||||
private string GetTestSqlFile()
|
||||
|
||||
Reference in New Issue
Block a user