Query Runner Tests (#10252)

* rework some code and write an inital test

* fix strict

* add more to standard test

* add to existing workflow test

* fix tests

* simplify the code

* add more tests

* remove bad import

* fix compile

* fix timestampiong
This commit is contained in:
Anthony Dresser
2020-05-06 13:38:12 -07:00
committed by GitHub
parent 4199cec393
commit df5df38a55
25 changed files with 856 additions and 430 deletions

View File

@@ -0,0 +1,229 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as sinon from 'sinon';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import { BatchSummary, ResultSetSummary, IResultMessage, ResultSetSubset, CompleteBatchSummary } from 'sql/workbench/services/query/common/query';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService } from 'sql/workbench/test/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { Event } from 'vs/base/common/event';
import { range } from 'vs/base/common/arrays';
suite('Query Runner', () => {
test('does execute a standard selection query workflow', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryStartPromise = Event.toPromise(runner.onQueryStart);
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQuery(rangeSelection);
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, rangeSelection, undefined));
await queryStartPromise;
assert(runner.queryStartTime instanceof Date);
assert(runner.isExecuting);
assert(!runner.hasCompleted);
// start batch
const batch: BatchSummary = { id: 0, hasError: false, range: rangeSelection, resultSetSummaries: [], executionStart: '' };
const returnBatch = await trigger(batch, arg => runner.handleBatchStart(arg), runner.onBatchStart);
assert.deepEqual(returnBatch, batch);
// we expect the query runner to create a message sense we sent a selection
assert(runner.messages.length === 1);
// start result set
const result1: ResultSetSummary = { batchId: 0, id: 0, complete: false, rowCount: 0, columnInfo: [{ columnName: 'column' }] };
const returnResult = await trigger(result1, arg => runner.handleResultSetAvailable(arg), runner.onResultSet);
assert.deepEqual(returnResult, result1);
assert.deepEqual(runner.batchSets[0].resultSetSummaries[0], result1);
// update result set
const result1Update: ResultSetSummary = { batchId: 0, id: 0, complete: true, rowCount: 100, columnInfo: [{ columnName: 'column' }] };
const returnResultUpdate = await trigger(result1Update, arg => runner.handleResultSetUpdated(arg), runner.onResultSetUpdate);
assert.deepEqual(returnResultUpdate, result1Update);
assert.deepEqual(runner.batchSets[0].resultSetSummaries[0], result1Update);
// post message
const message: IResultMessage = { message: 'some message', isError: false, batchId: 0 };
const messageReturn = await trigger([message], arg => runner.handleMessage(arg), runner.onMessage);
assert.deepEqual(messageReturn[0], message);
assert.deepEqual(runner.messages[1], message);
// get query rows
const rowResults: ResultSetSubset = { rowCount: 100, rows: range(100).map(r => range(1).map(c => ({ displayValue: `${r}${c}` }))) };
const getRowStub = sinon.stub().returns(Promise.resolve(rowResults));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'getQueryRows', getRowStub);
const resultReturn = await runner.getQueryRows(0, 100, 0, 0);
assert(getRowStub.calledWithExactly({ ownerUri: uri, batchIndex: 0, resultSetIndex: 0, rowsStartIndex: 0, rowsCount: 100 }));
assert.deepStrictEqual(resultReturn, rowResults);
// batch complete
const batchComplete: CompleteBatchSummary = { ...batch, executionEnd: 'endstring', executionElapsed: 'elapsedstring' };
const batchCompleteReturn = await trigger(batchComplete, arg => runner.handleBatchComplete(arg), runner.onBatchEnd);
assert.deepStrictEqual(batchCompleteReturn, batchComplete);
// query complete
await trigger([batchComplete], arg => runner.handleQueryComplete(arg), runner.onQueryEnd);
assert(!runner.isExecuting);
assert(runner.hasCompleted);
await runner.disposeQuery();
});
test('does handle inital query failure', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.reject(new Error('some error')));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryCompletePromise = Event.toPromise(runner.onQueryEnd);
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQuery(rangeSelection);
await queryCompletePromise;
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, rangeSelection, undefined));
assert(runner.messages.length === 2);
assert(runner.messages[0].message.includes('some error'));
assert(runner.messages[0].isError);
});
test('does handle cancel query', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
assert(!runner.isExecuting);
// start query
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQuery(rangeSelection);
assert(runner.isExecuting);
// cancel query
const cancelQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'cancelQuery', cancelQueryStub);
await runner.cancelQuery();
assert(cancelQueryStub.calledOnce);
await trigger([], () => runner.handleQueryComplete(), runner.onQueryEnd);
assert(!runner.isExecuting);
});
test('does handle query plan in inital data set', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
await runner.runQuery(undefined, { displayEstimatedQueryPlan: true });
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, undefined, { displayEstimatedQueryPlan: true }));
const xmlPlan = 'xml plan';
const getRowsStub = sinon.stub().returns(Promise.resolve({ rowCount: 1, rows: [[{ displayValue: xmlPlan }]] } as ResultSetSubset));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'getQueryRows', getRowsStub);
runner.handleBatchStart({ id: 0, executionStart: '' });
runner.handleResultSetAvailable({ id: 0, batchId: 0, complete: true, rowCount: 1, columnInfo: [{ columnName: 'Microsoft SQL Server 2005 XML Showplan' }] });
const plan = await runner.planXml;
assert(getRowsStub.calledOnce);
assert.equal(plan, xmlPlan);
assert(runner.isQueryPlan);
});
test('does handle query plan in update', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQuery', runQueryStub);
await runner.runQuery(undefined, { displayEstimatedQueryPlan: true });
assert(runQueryStub.calledOnce);
assert(runQueryStub.calledWithExactly(uri, undefined, { displayEstimatedQueryPlan: true }));
runner.handleBatchStart({ id: 0, executionStart: '' });
runner.handleResultSetAvailable({ id: 0, batchId: 0, complete: false, rowCount: 0, columnInfo: [{ columnName: 'Microsoft SQL Server 2005 XML Showplan' }] });
const xmlPlan = 'xml plan';
const getRowsStub = sinon.stub().returns(Promise.resolve({ rowCount: 1, rows: [[{ displayValue: xmlPlan }]] } as ResultSetSubset));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'getQueryRows', getRowsStub);
runner.handleResultSetUpdated({ id: 0, batchId: 0, complete: true, rowCount: 1, columnInfo: [{ columnName: 'Microsoft SQL Server 2005 XML Showplan' }] });
const plan = await runner.planXml;
assert(getRowsStub.calledOnce);
assert.equal(plan, xmlPlan);
assert(runner.isQueryPlan);
});
test('does run query string', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStringStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryString', runQueryStringStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
await runner.runQuery('some query');
assert(runQueryStringStub.calledOnce);
assert(runQueryStringStub.calledWithExactly(uri, 'some query'));
assert(runner.isExecuting);
});
test('does handle run query string error', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStringStub = sinon.stub().returns(Promise.reject(new Error('some error')));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryString', runQueryStringStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryCompletePromise = Event.toPromise(runner.onQueryEnd);
await runner.runQuery('some query');
await queryCompletePromise;
assert(runQueryStringStub.calledOnce);
assert(runQueryStringStub.calledWithExactly(uri, 'some query'));
assert(runner.messages.length === 2);
assert(runner.messages[0].message.includes('some error'));
assert(runner.messages[0].isError);
});
test('does run query statement', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStatementStub = sinon.stub().returns(Promise.resolve());
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryStatement', runQueryStatementStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQueryStatement(rangeSelection);
assert(runQueryStatementStub.calledOnce);
assert(runQueryStatementStub.calledWithExactly(uri, rangeSelection.startLineNumber, rangeSelection.startColumn));
assert(runner.isExecuting);
});
test('does handle run query statement error', async () => {
const instantiationService = workbenchInstantiationService();
const uri = URI.parse('test:uri').toString();
const runner = instantiationService.createInstance(QueryRunner, uri);
const runQueryStatementStub = sinon.stub().returns(Promise.reject(new Error('some error')));
(instantiationService as TestInstantiationService).stub(IQueryManagementService, 'runQueryStatement', runQueryStatementStub);
assert(!runner.isExecuting);
assert(!runner.hasCompleted);
// start query
const queryCompletePromise = Event.toPromise(runner.onQueryEnd);
const rangeSelection = { endColumn: 1, endLineNumber: 1, startColumn: 1, startLineNumber: 1 };
await runner.runQueryStatement(rangeSelection);
await queryCompletePromise;
assert(runQueryStatementStub.calledOnce);
assert(runQueryStatementStub.calledWithExactly(uri, rangeSelection.startLineNumber, rangeSelection.startColumn));
assert(runner.messages.length === 2);
assert(runner.messages[0].message.includes('some error'));
assert(runner.messages[0].isError);
});
});
function trigger<T, V = T>(arg: T, func: (arg: T) => void, event: Event<V>): Promise<V> {
const promise = Event.toPromise(event);
func(arg);
return promise;
}

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQueryManagementService, IQueryRequestHandler, ExecutionPlanOptions } from 'sql/workbench/services/query/common/queryManagement';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
import * as azdata from 'azdata';
import { IRange } from 'vs/editor/common/core/range';
import { ResultSetSubset } from 'sql/workbench/services/query/common/query';
export class TestQueryManagementService implements IQueryManagementService {
_serviceBrand: undefined;
onHandlerAdded: Event<string>;
addQueryRequestHandler(queryType: string, runner: IQueryRequestHandler): IDisposable {
throw new Error('Method not implemented.');
}
isProviderRegistered(providerId: string): boolean {
throw new Error('Method not implemented.');
}
getRegisteredProviders(): string[] {
throw new Error('Method not implemented.');
}
registerRunner(runner: QueryRunner, uri: string): void {
return;
}
async cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult> {
return { messages: undefined };
}
async runQuery(ownerUri: string, range: IRange, runOptions?: ExecutionPlanOptions): Promise<void> {
return;
}
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void> {
throw new Error('Method not implemented.');
}
runQueryString(ownerUri: string, queryString: string): Promise<void> {
throw new Error('Method not implemented.');
}
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult> {
throw new Error('Method not implemented.');
}
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult> {
throw new Error('Method not implemented.');
}
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<ResultSetSubset> {
throw new Error('Method not implemented.');
}
async disposeQuery(ownerUri: string): Promise<void> {
return;
}
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult> {
throw new Error('Method not implemented.');
}
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void> {
throw new Error('Method not implemented.');
}
onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
throw new Error('Method not implemented.');
}
onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
throw new Error('Method not implemented.');
}
onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
throw new Error('Method not implemented.');
}
onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
throw new Error('Method not implemented.');
}
onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
throw new Error('Method not implemented.');
}
onMessage(message: Map<string, azdata.QueryExecuteMessageParams[]>): void {
throw new Error('Method not implemented.');
}
onEditSessionReady(ownerUri: string, success: boolean, message: string): void {
throw new Error('Method not implemented.');
}
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
throw new Error('Method not implemented.');
}
disposeEdit(ownerUri: string): Promise<void> {
throw new Error('Method not implemented.');
}
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
throw new Error('Method not implemented.');
}
commitEdit(ownerUri: string): Promise<void> {
throw new Error('Method not implemented.');
}
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
throw new Error('Method not implemented.');
}
deleteRow(ownerUri: string, rowId: number): Promise<void> {
throw new Error('Method not implemented.');
}
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
throw new Error('Method not implemented.');
}
revertRow(ownerUri: string, rowId: number): Promise<void> {
throw new Error('Method not implemented.');
}
getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult> {
throw new Error('Method not implemented.');
}
}

View File

@@ -9,6 +9,7 @@ import * as azdata from 'azdata';
import { Event } from 'vs/base/common/event';
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
import { DataService } from 'sql/workbench/services/query/common/dataService';
import { IRange } from 'vs/editor/common/core/range';
export class TestQueryModelService implements IQueryModelService {
_serviceBrand: any;
@@ -25,10 +26,10 @@ export class TestQueryModelService implements IQueryModelService {
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<azdata.ResultSetSubset> {
throw new Error('Method not implemented.');
}
runQuery(uri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): void {
runQuery(uri: string, range: IRange, runOptions?: azdata.ExecutionPlanOptions): void {
throw new Error('Method not implemented.');
}
runQueryStatement(uri: string, selection: azdata.ISelectionData): void {
runQueryStatement(uri: string, range: IRange): void {
throw new Error('Method not implemented.');
}
runQueryString(uri: string, selection: string) {