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:
Karl Burtram
2016-10-04 14:55:59 -07:00
committed by GitHub
parent 1b8e9c1e86
commit 62525b9c98
18 changed files with 1409 additions and 360 deletions

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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()