mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
Fixing a bug where in various situations, the files used for temporary storage of query results would be leftover. In particular, the following changes were made: * When the dispose query request is submitted, the corresponding query is now disposed in addition from being removed from the list of active queries * When a query is cancelled, it is disposed after it is cancelled * If a query already exists for a given ownerURI, the existing query is disposed before creating a new query * All queries are disposed when the query execution service is disposed (ie, at shutdown of the service) A unit test to verify the action of the dispose method for a ResultSet was added. * Ensuring queries are disposed Adding logic to dispose any queries when: * URI that already has a query executes another query * A request to dispose a query is submitted * A request to cancel a query is submitted * Small tweaks for cleanup of query execution service
162 lines
7.0 KiB
C#
162 lines
7.0 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.Data.Common;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|
{
|
|
public class DisposeTests
|
|
{
|
|
[Fact]
|
|
public void DisposeResultSet()
|
|
{
|
|
// Setup: Mock file stream factory, mock db reader
|
|
var mockFileStreamFactory = new Mock<IFileStreamFactory>();
|
|
var mockDataReader = Common.CreateTestConnection(null, false).CreateCommand().ExecuteReaderAsync().Result;
|
|
|
|
// If: I setup a single resultset and then dispose it
|
|
ResultSet rs = new ResultSet(mockDataReader, mockFileStreamFactory.Object);
|
|
rs.Dispose();
|
|
|
|
// Then: The file that was created should have been deleted
|
|
mockFileStreamFactory.Verify(fsf => fsf.DisposeFile(It.IsAny<string>()), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void DisposeExecutedQuery()
|
|
{
|
|
// Set up file for returning the query
|
|
var fileMock = new Mock<ScriptFile>();
|
|
fileMock.SetupGet(file => file.Contents).Returns("doesn't matter");
|
|
// Set up workspace mock
|
|
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
|
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
|
.Returns(fileMock.Object);
|
|
// If:
|
|
// ... I request a query (doesn't matter what kind)
|
|
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
|
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
|
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
|
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
|
|
|
// ... And then I dispose of the query
|
|
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri};
|
|
QueryDisposeResult result = null;
|
|
var disposeRequest = GetQueryDisposeResultContextMock(qdr => {
|
|
result = qdr;
|
|
}, null);
|
|
queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait();
|
|
|
|
// Then:
|
|
// ... I should have seen a successful result
|
|
// ... And the active queries should be empty
|
|
VerifyQueryDisposeCallCount(disposeRequest, Times.Once(), Times.Never());
|
|
Assert.Null(result.Messages);
|
|
Assert.Empty(queryService.ActiveQueries);
|
|
}
|
|
|
|
[Fact]
|
|
public void QueryDisposeMissingQuery()
|
|
{
|
|
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
|
// If:
|
|
// ... I attempt to dispose a query that doesn't exist
|
|
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
|
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri};
|
|
QueryDisposeResult result = null;
|
|
var disposeRequest = GetQueryDisposeResultContextMock(qdr => result = qdr, null);
|
|
queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait();
|
|
|
|
// Then:
|
|
// ... I should have gotten an error result
|
|
VerifyQueryDisposeCallCount(disposeRequest, Times.Once(), Times.Never());
|
|
Assert.NotNull(result.Messages);
|
|
Assert.NotEmpty(result.Messages);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ServiceDispose()
|
|
{
|
|
// Setup:
|
|
// ... We need a workspace service that returns a file
|
|
var fileMock = new Mock<ScriptFile>();
|
|
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
|
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
|
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
|
.Returns(fileMock.Object);
|
|
// ... We need a query service
|
|
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true,
|
|
workspaceService.Object);
|
|
|
|
// If:
|
|
// ... I execute some bogus query
|
|
var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
|
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
|
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
|
|
|
// ... And it sticks around as an active query
|
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
|
|
|
// ... The query execution service is disposed, like when the service is shutdown
|
|
queryService.Dispose();
|
|
|
|
// Then:
|
|
// ... There should no longer be an active query
|
|
Assert.Empty(queryService.ActiveQueries);
|
|
}
|
|
|
|
#region Mocking
|
|
|
|
private Mock<RequestContext<QueryDisposeResult>> GetQueryDisposeResultContextMock(
|
|
Action<QueryDisposeResult> resultCallback,
|
|
Action<object> errorCallback)
|
|
{
|
|
var requestContext = new Mock<RequestContext<QueryDisposeResult>>();
|
|
|
|
// Setup the mock for SendResult
|
|
var sendResultFlow = requestContext
|
|
.Setup(rc => rc.SendResult(It.IsAny<QueryDisposeResult>()))
|
|
.Returns(Task.FromResult(0));
|
|
if (resultCallback != null)
|
|
{
|
|
sendResultFlow.Callback(resultCallback);
|
|
}
|
|
|
|
// Setup the mock for SendError
|
|
var sendErrorFlow = requestContext
|
|
.Setup(rc => rc.SendError(It.IsAny<object>()))
|
|
.Returns(Task.FromResult(0));
|
|
if (errorCallback != null)
|
|
{
|
|
sendErrorFlow.Callback(errorCallback);
|
|
}
|
|
|
|
return requestContext;
|
|
}
|
|
|
|
private void VerifyQueryDisposeCallCount(Mock<RequestContext<QueryDisposeResult>> mock, Times sendResultCalls,
|
|
Times sendErrorCalls)
|
|
{
|
|
mock.Verify(rc => rc.SendResult(It.IsAny<QueryDisposeResult>()), sendResultCalls);
|
|
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|