// // 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; 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.UnitTests.LanguageServer { /// /// Test class for the test binding context /// public class TestBindingContext : IBindingContext { public TestBindingContext() { this.BindingLock = new ManualResetEvent(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 BindingLock { 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; } } /// /// Tests for the Binding Queue /// 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 bindingQueue = null; private void InitializeTestSettings() { this.bindCallCount = 0; this.timeoutCallCount = 0; this.bindCallbackDelay = 10; this.isCancelationRequested = false; this.bindingContext = GetMockBindingContext(); this.bindingQueue = new BindingQueue(); } private IBindingContext GetMockBindingContext() { return new TestBindingContext(); } /// /// Test bind operation callback /// private object TestBindOperation( IBindingContext bindContext, CancellationToken cancelToken) { cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay); this.isCancelationRequested = cancelToken.IsCancellationRequested; if (!this.isCancelationRequested) { ++this.bindCallCount; } return new CompletionItem[0]; } /// /// Test callback for the bind timeout operation /// private object TestTimeoutOperation( IBindingContext bindingContext) { ++this.timeoutCallCount; return new CompletionItem[0]; } /// /// Queues a single task /// [Fact] public void QueueOneBindingOperationTest() { InitializeTestSettings(); this.bindingQueue.QueueBindingOperation( key: "testkey", bindOperation: TestBindOperation, timeoutOperation: TestTimeoutOperation); Thread.Sleep(1000); this.bindingQueue.StopQueueProcessor(15000); Assert.Equal(1, this.bindCallCount); Assert.Equal(0, this.timeoutCallCount); Assert.False(this.isCancelationRequested); } /// /// Queues a single task /// [Fact] public void QueueWithUnhandledExceptionTest() { InitializeTestSettings(); bool isExceptionHandled = false; object defaultReturnObject = new object(); var queueItem = this.bindingQueue.QueueBindingOperation( key: "testkey", bindOperation: (context, CancellationToken) => { throw new Exception("Unhandled!!"); }, timeoutOperation: TestTimeoutOperation, errorHandler: (exception) => { isExceptionHandled = true; return defaultReturnObject; }); queueItem.ItemProcessed.WaitOne(10000); this.bindingQueue.StopQueueProcessor(15000); Assert.True(isExceptionHandled); var result = queueItem.GetResultAsT(); Assert.Equal(defaultReturnObject, result); } /// /// Queue a 100 short tasks /// // Disable flaky test (mairvine - 3/15/2018) // [Fact] public void Queue100BindingOperationTest() { InitializeTestSettings(); for (int i = 0; i < 100; ++i) { this.bindingQueue.QueueBindingOperation( key: "testkey", bindOperation: TestBindOperation, timeoutOperation: TestTimeoutOperation); } Thread.Sleep(2000); this.bindingQueue.StopQueueProcessor(15000); Assert.Equal(100, this.bindCallCount); Assert.Equal(0, this.timeoutCallCount); Assert.False(this.isCancelationRequested); } /// /// Queue an task with a long operation causing a timeout /// [Fact] public void QueueWithTimeout() { InitializeTestSettings(); this.bindCallbackDelay = 1000; this.bindingQueue.QueueBindingOperation( key: "testkey", bindingTimeout: bindCallbackDelay / 2, bindOperation: TestBindOperation, timeoutOperation: TestTimeoutOperation); Thread.Sleep(this.bindCallbackDelay + 100); this.bindingQueue.StopQueueProcessor(15000); Assert.Equal(0, this.bindCallCount); Assert.Equal(1, this.timeoutCallCount); Assert.True(this.isCancelationRequested); } /// /// Queue an task with a long operation causing a timeout and make sure subsequent tasks still execute /// [Fact] public void QueueWithTimeoutRunsNextTask() { string operationKey = "testkey"; ManualResetEvent firstEventExecuted = new ManualResetEvent(false); ManualResetEvent secondEventExecuted = new ManualResetEvent(false); bool firstOperationCanceled = false; bool secondOperationExecuted = false; InitializeTestSettings(); this.bindCallbackDelay = 1000; var totalTimeout = (this.bindCallbackDelay + this.bindingContext.BindingTimeout) * 2; this.bindingQueue.QueueBindingOperation( key: operationKey, bindingTimeout: bindCallbackDelay / 2, bindOperation: (bindingContext, cancellationToken) => { secondEventExecuted.WaitOne(); if (cancellationToken.IsCancellationRequested) { firstOperationCanceled = true; } firstEventExecuted.Set(); return null; }, timeoutOperation: TestTimeoutOperation); this.bindingQueue.QueueBindingOperation( key: operationKey, bindingTimeout: bindCallbackDelay, bindOperation: (bindingContext, cancellationToken) => { secondOperationExecuted = true; secondEventExecuted.Set(); return null; }, waitForLockTimeout: totalTimeout ); var result = firstEventExecuted.WaitOne(totalTimeout); Assert.True(result); this.bindingQueue.StopQueueProcessor(15000); Assert.Equal(1, this.timeoutCallCount); Assert.True(firstOperationCanceled); Assert.True(secondOperationExecuted); } } }