Files
sqltoolsservice/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs
Benjamin Russell 60edcc3057 Make query execution truly asynchronous (#83)
The two main changes in this pull request:
Launching query execution as an asynchronous task that performs a callback upon completion or failure of a query. (Which also sets us up for callbacks progressive results)
Moving away from using the Result of a query execution to return an error. Instead we'll use an error event to return an error
Additionally, some nice refactoring and cleaning up of the unit tests to take advantage of the cool RequestContext mock tooling by @kevcunnane

* Initial commit of refactor to run execution truely asynchronously

* Moving the storage of the task into Query class

Callbacks for completion of a query and failure of a query are setup as
events in the Query class. This actually sets us up for a very nice
framework for adding batch and resultset completion callbacks.

However, this also exposes a problem with cancelling queries and returning
errors -- we don't properly handle errors during execution of a query
(aside from DB errors).

* Wrapping things up in order to submit for code review

* Adding fixes as per comments
2016-10-11 10:51:52 -07:00

361 lines
18 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.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Moq;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
{
/// <summary>
/// Tests for saving a result set to a file
/// </summary>
public class SaveResultsTests
{
/// <summary>
/// Test save results to a file as CSV with correct parameters
/// </summary>
[Fact]
public async void SaveResultsAsCsvSuccessTest()
{
// Execute a query
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as csv with correct parameters
var saveParams = new SaveResultsAsCsvRequestParams
{
OwnerUri = Common.OwnerUri,
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = "testwrite_1.csv",
IncludeHeaders = true
};
SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
// Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages);
Assert.True(File.Exists(saveParams.FilePath));
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
// Delete temp file after test
if (File.Exists(saveParams.FilePath))
{
File.Delete(saveParams.FilePath);
}
}
/// <summary>
/// Test save results to a file as CSV with a selection of cells and correct parameters
/// </summary>
[Fact]
public async void SaveResultsAsCsvWithSelectionSuccessTest()
{
// Execute a query
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as csv with correct parameters
var saveParams = new SaveResultsAsCsvRequestParams
{
OwnerUri = Common.OwnerUri,
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = "testwrite_2.csv",
IncludeHeaders = true,
RowStartIndex = 0,
RowEndIndex = 0,
ColumnStartIndex = 0,
ColumnEndIndex = 0
};
SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
// Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages);
Assert.True(File.Exists(saveParams.FilePath));
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
// Delete temp file after test
if (File.Exists(saveParams.FilePath))
{
File.Delete(saveParams.FilePath);
}
}
/// <summary>
/// Test handling exception in saving results to CSV file
/// </summary>
[Fact]
public async void SaveResultsAsCsvExceptionTest()
{
// Execute a query
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as csv with incorrect filepath
var saveParams = new SaveResultsAsCsvRequestParams
{
OwnerUri = Common.OwnerUri,
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.csv" : "/test.csv"
};
string errMessage = null;
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
// Expect to see error message
Assert.NotNull(errMessage);
VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once());
Assert.False(File.Exists(saveParams.FilePath));
}
/// <summary>
/// Test saving results to CSV file when the requested result set is no longer active
/// </summary>
[Fact]
public async void SaveResultsAsCsvQueryNotFoundTest()
{
// Create a query execution service
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
// Request to save the results as csv with query that is no longer active
var saveParams = new SaveResultsAsCsvRequestParams
{
OwnerUri = "falseuri",
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = "testwrite_3.csv"
};
SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
// Expect message that save failed
Assert.NotNull(result.Messages);
Assert.False(File.Exists(saveParams.FilePath));
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
}
/// <summary>
/// Test save results to a file as JSON with correct parameters
/// </summary>
[Fact]
public async void SaveResultsAsJsonSuccessTest()
{
// Execute a query
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as json with correct parameters
var saveParams = new SaveResultsAsJsonRequestParams
{
OwnerUri = Common.OwnerUri,
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = "testwrite_4.json"
};
SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
// Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages);
Assert.True(File.Exists(saveParams.FilePath));
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
// Delete temp file after test
if (File.Exists(saveParams.FilePath))
{
File.Delete(saveParams.FilePath);
}
}
/// <summary>
/// Test save results to a file as JSON with a selection of cells and correct parameters
/// </summary>
[Fact]
public async void SaveResultsAsJsonWithSelectionSuccessTest()
{
// Execute a query
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as json with correct parameters
var saveParams = new SaveResultsAsJsonRequestParams
{
OwnerUri = Common.OwnerUri,
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = "testwrite_5.json",
RowStartIndex = 0,
RowEndIndex = 0,
ColumnStartIndex = 0,
ColumnEndIndex = 0
};
SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
// Expect to see a file successfully created in filepath and a success message
Assert.Null(result.Messages);
Assert.True(File.Exists(saveParams.FilePath));
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
// Delete temp file after test
if (File.Exists(saveParams.FilePath))
{
File.Delete(saveParams.FilePath);
}
}
/// <summary>
/// Test handling exception in saving results to JSON file
/// </summary>
[Fact]
public async void SaveResultsAsJsonExceptionTest()
{
// Execute a query
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
// Request to save the results as json with incorrect filepath
var saveParams = new SaveResultsAsJsonRequestParams
{
OwnerUri = Common.OwnerUri,
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.json" : "/test.json"
};
// SaveResultRequestResult result = null;
string errMessage = null;
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err);
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
// Expect to see error message
Assert.NotNull(errMessage);
VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once());
Assert.False(File.Exists(saveParams.FilePath));
}
/// <summary>
/// Test saving results to JSON file when the requested result set is no longer active
/// </summary>
[Fact]
public async void SaveResultsAsJsonQueryNotFoundTest()
{
// Create a query service
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
// Request to save the results as json with query that is no longer active
var saveParams = new SaveResultsAsJsonRequestParams
{
OwnerUri = "falseuri",
ResultSetIndex = 0,
BatchIndex = 0,
FilePath = "testwrite_6.json"
};
SaveResultRequestResult result = null;
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
// Expect message that save failed
Assert.Equal("Failed to save results, ID not found.", result.Messages);
Assert.False(File.Exists(saveParams.FilePath));
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
}
#region Mocking
/// <summary>
/// Mock the requestContext for saving a result set
/// </summary>
/// <param name="resultCallback"></param>
/// <param name="errorCallback"></param>
/// <returns></returns>
private static Mock<RequestContext<SaveResultRequestResult>> GetSaveResultsContextMock(
Action<SaveResultRequestResult> resultCallback,
Action<object> errorCallback)
{
var requestContext = new Mock<RequestContext<SaveResultRequestResult>>();
// Setup the mock for SendResult
var sendResultFlow = requestContext
.Setup(rc => rc.SendResult(It.IsAny<SaveResultRequestResult> ()))
.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;
}
/// <summary>
/// Verify the call count for sendResult and error
/// </summary>
/// <param name="mock"></param>
/// <param name="sendResultCalls"></param>
/// <param name="sendErrorCalls"></param>
private static void VerifySaveResultsCallCount(Mock<RequestContext<SaveResultRequestResult>> mock,
Times sendResultCalls, Times sendErrorCalls)
{
mock.Verify(rc => rc.SendResult(It.IsAny<SaveResultRequestResult>()), sendResultCalls);
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
}
#endregion
}
}