mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Add basic notebook model tests (#3396)
- Ported from the extension - Only adding tests that related to the internally implemented functionality, not to anything provider-specific.
This commit is contained in:
95
src/sqltest/parts/notebook/common.ts
Normal file
95
src/sqltest/parts/notebook/common.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb, IConnectionProfile } from 'sqlops';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { INotebookModel, ICellModel, IClientSession, IDefaultConnection } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
|
||||
export class NotebookModelStub implements INotebookModel {
|
||||
constructor(private _languageInfo?: nb.ILanguageInfo) {
|
||||
}
|
||||
public trustedMode: boolean;
|
||||
|
||||
public get languageInfo(): nb.ILanguageInfo {
|
||||
return this._languageInfo;
|
||||
}
|
||||
onCellChange(cell: ICellModel, change: NotebookChangeType): void {
|
||||
// Default: do nothing
|
||||
}
|
||||
get cells(): ReadonlyArray<ICellModel> {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get clientSession(): IClientSession {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get notebookManager(): INotebookManager {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get kernelsChanged(): Event<nb.IKernelSpec> {
|
||||
throw new Error('method not implemented.');
|
||||
} get defaultKernel(): nb.IKernelSpec {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get contextsChanged(): Event<void> {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get specs(): nb.IAllKernels {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
get contexts(): IDefaultConnection {
|
||||
throw new Error('method not implemented.');
|
||||
}
|
||||
changeKernel(displayName: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
changeContext(host: string, connection?: IConnectionProfile): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
findCellIndex(cellModel: ICellModel): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addCell(cellType: CellType, index?: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
deleteCell(cellModel: ICellModel): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
saveModel(): Promise<boolean> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookManagerStub implements INotebookManager {
|
||||
providerId: string;
|
||||
contentManager: nb.ContentManager;
|
||||
sessionManager: nb.SessionManager;
|
||||
serverManager: nb.ServerManager;
|
||||
}
|
||||
|
||||
export class ServerManagerStub implements nb.ServerManager {
|
||||
public onServerStartedEmitter = new Emitter<void>();
|
||||
onServerStarted: Event<void> = this.onServerStartedEmitter.event;
|
||||
isStarted: boolean = false;
|
||||
calledStart: boolean = false;
|
||||
calledEnd: boolean = false;
|
||||
public result: Promise<void> = undefined;
|
||||
|
||||
startServer(): Promise<void> {
|
||||
this.calledStart = true;
|
||||
return this.result;
|
||||
}
|
||||
stopServer(): Promise<void> {
|
||||
this.calledEnd = true;
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
262
src/sqltest/parts/notebook/model/cell.test.ts
Normal file
262
src/sqltest/parts/notebook/model/cell.test.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
|
||||
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||
import { NotebookModelStub } from '../common';
|
||||
import { EmptyFuture } from 'sql/services/notebook/sessionManager';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
describe('Cell Model', function (): void {
|
||||
let factory = new ModelFactory();
|
||||
it('Should set default values if none defined', async function (): Promise<void> {
|
||||
let cell = factory.createCell(undefined, undefined);
|
||||
should(cell.cellType).equal(CellTypes.Code);
|
||||
should(cell.source).equal('');
|
||||
});
|
||||
|
||||
it('Should update values', async function (): Promise<void> {
|
||||
let cell = factory.createCell(undefined, undefined);
|
||||
cell.language = 'sql';
|
||||
should(cell.language).equal('sql');
|
||||
cell.source = 'abcd';
|
||||
should(cell.source).equal('abcd');
|
||||
});
|
||||
|
||||
it('Should match ICell values if defined', async function (): Promise<void> {
|
||||
let output: nb.IStreamResult = {
|
||||
output_type: 'stream',
|
||||
text: 'Some output',
|
||||
name: 'stdout'
|
||||
};
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Markdown,
|
||||
source: 'some *markdown*',
|
||||
outputs: [output],
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
let cell = factory.createCell(cellData, undefined);
|
||||
should(cell.cellType).equal(cellData.cell_type);
|
||||
should(cell.source).equal(cellData.source);
|
||||
should(cell.outputs).have.length(1);
|
||||
should(cell.outputs[0].output_type).equal('stream');
|
||||
should((<nb.IStreamResult>cell.outputs[0]).text).equal('Some output');
|
||||
});
|
||||
|
||||
|
||||
it('Should set cell language to python if defined as python in languageInfo', async function (): Promise<void> {
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'print(\'1\')',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
|
||||
let notebookModel = new NotebookModelStub({
|
||||
name: 'python',
|
||||
version: '',
|
||||
mimetype: ''
|
||||
});
|
||||
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||
should(cell.language).equal('python');
|
||||
});
|
||||
|
||||
it('Should set cell language to python if defined as pyspark in languageInfo', async function (): Promise<void> {
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'print(\'1\')',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
|
||||
let notebookModel = new NotebookModelStub({
|
||||
name: 'pyspark',
|
||||
version: '',
|
||||
mimetype: ''
|
||||
});
|
||||
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||
should(cell.language).equal('python');
|
||||
});
|
||||
|
||||
it('Should set cell language to scala if defined as scala in languageInfo', async function (): Promise<void> {
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'print(\'1\')',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
|
||||
let notebookModel = new NotebookModelStub({
|
||||
name: 'scala',
|
||||
version: '',
|
||||
mimetype: ''
|
||||
});
|
||||
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||
should(cell.language).equal('scala');
|
||||
});
|
||||
|
||||
it('Should set cell language to python if no language defined', async function (): Promise<void> {
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'print(\'1\')',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
|
||||
let notebookModel = new NotebookModelStub({
|
||||
name: '',
|
||||
version: '',
|
||||
mimetype: ''
|
||||
});
|
||||
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||
should(cell.language).equal('python');
|
||||
});
|
||||
|
||||
it('Should match cell language to language specified if unknown language defined in languageInfo', async function (): Promise<void> {
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'std::cout << "hello world";',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
|
||||
let notebookModel = new NotebookModelStub({
|
||||
name: 'cplusplus',
|
||||
version: '',
|
||||
mimetype: ''
|
||||
});
|
||||
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||
should(cell.language).equal('cplusplus');
|
||||
});
|
||||
|
||||
it('Should match cell language to mimetype name is not supplied in languageInfo', async function (): Promise<void> {
|
||||
let cellData: nb.ICell = {
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'print(\'1\')',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
};
|
||||
|
||||
let notebookModel = new NotebookModelStub({
|
||||
name: '',
|
||||
version: '',
|
||||
mimetype: 'x-scala'
|
||||
});
|
||||
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||
should(cell.language).equal('scala');
|
||||
});
|
||||
|
||||
describe('Model Future handling', function(): void {
|
||||
let future: TypeMoq.Mock<EmptyFuture>;
|
||||
let cell: ICellModel;
|
||||
beforeEach(() => {
|
||||
future = TypeMoq.Mock.ofType(EmptyFuture);
|
||||
cell = factory.createCell({
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'print "Hello"',
|
||||
metadata: { language: 'python'},
|
||||
execution_count: 1
|
||||
}, {
|
||||
notebook: new NotebookModelStub({
|
||||
name: '',
|
||||
version: '',
|
||||
mimetype: 'x-scala'
|
||||
}),
|
||||
isTrusted: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should send and handle incoming messages', async () => {
|
||||
// Given a future
|
||||
let onReply: nb.MessageHandler<nb.IShellMessage>;
|
||||
let onIopub: nb.MessageHandler<nb.IIOPubMessage>;
|
||||
future.setup(f => f.setReplyHandler(TypeMoq.It.isAny())).callback((handler) => onReply = handler);
|
||||
future.setup(f => f.setIOPubHandler(TypeMoq.It.isAny())).callback((handler) => onIopub = handler);
|
||||
let outputs: ReadonlyArray<nb.ICellOutput> = undefined;
|
||||
cell.onOutputsChanged((o => outputs = o));
|
||||
|
||||
// When I set it on the cell
|
||||
cell.setFuture(future.object);
|
||||
|
||||
// Then I expect outputs to have been cleared
|
||||
should(outputs).have.length(0);
|
||||
should(onReply).not.be.undefined();
|
||||
// ... And when I send an IoPub message
|
||||
let message: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
parent_header: undefined,
|
||||
metadata: undefined,
|
||||
header: <nb.IHeader> {
|
||||
msg_type: 'stream'
|
||||
},
|
||||
content: {
|
||||
text: 'Printed hello world'
|
||||
}
|
||||
};
|
||||
onIopub.handle(message);
|
||||
// Then I expect an output to be added
|
||||
should(outputs).have.length(1);
|
||||
should(outputs[0].output_type).equal('stream');
|
||||
|
||||
message = objects.deepClone(message);
|
||||
message.header.msg_type = 'display_data';
|
||||
onIopub.handle(message);
|
||||
should(outputs[1].output_type).equal('display_data');
|
||||
|
||||
// ... TODO: And when I sent a reply I expect it to be processed.
|
||||
});
|
||||
|
||||
it('should delete transient tag while handling incoming messages', async () => {
|
||||
// Given a future
|
||||
let onIopub: nb.MessageHandler<nb.IIOPubMessage>;
|
||||
future.setup(f => f.setIOPubHandler(TypeMoq.It.isAny())).callback((handler) => onIopub = handler);
|
||||
let outputs: ReadonlyArray<nb.ICellOutput> = undefined;
|
||||
cell.onOutputsChanged((o => outputs = o));
|
||||
|
||||
//Set the future
|
||||
cell.setFuture(future.object);
|
||||
|
||||
// ... And when I send an IoPub message
|
||||
let message: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
parent_header: undefined,
|
||||
metadata: undefined,
|
||||
header: <nb.IHeader> {
|
||||
msg_type: 'display_data'
|
||||
},
|
||||
content: {
|
||||
text: 'Printed hello world',
|
||||
transient: 'transient data'
|
||||
}
|
||||
};
|
||||
onIopub.handle(message);
|
||||
//Output array's length should be 1
|
||||
//'transient' tag should no longer exist in the output
|
||||
should(outputs).have.length(1);
|
||||
should(outputs[0]['transient']).be.undefined();
|
||||
});
|
||||
|
||||
it('should dispose old future', async () => {
|
||||
let oldFuture = TypeMoq.Mock.ofType(EmptyFuture);
|
||||
cell.setFuture(oldFuture.object);
|
||||
|
||||
cell.setFuture(future.object);
|
||||
|
||||
oldFuture.verify(f => f.dispose(), TypeMoq.Times.once());
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
202
src/sqltest/parts/notebook/model/clientSession.test.ts
Normal file
202
src/sqltest/parts/notebook/model/clientSession.test.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
import { ClientSession } from 'sql/parts/notebook/models/clientSession';
|
||||
import { SessionManager, EmptySession } from 'sql/services/notebook/sessionManager';
|
||||
import { NotebookManagerStub, ServerManagerStub } from 'sqltest/parts/notebook/common';
|
||||
|
||||
describe('Client Session', function(): void {
|
||||
let path = URI.file('my/notebook.ipynb');
|
||||
let notebookManager: NotebookManagerStub;
|
||||
let serverManager: ServerManagerStub;
|
||||
let mockSessionManager: TypeMoq.Mock<nb.SessionManager>;
|
||||
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||
let session: ClientSession;
|
||||
let remoteSession: ClientSession;
|
||||
|
||||
beforeEach(() => {
|
||||
serverManager = new ServerManagerStub();
|
||||
mockSessionManager = TypeMoq.Mock.ofType(SessionManager);
|
||||
notebookManager = new NotebookManagerStub();
|
||||
notebookManager.serverManager = serverManager;
|
||||
notebookManager.sessionManager = mockSessionManager.object;
|
||||
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||
|
||||
session = new ClientSession({
|
||||
notebookManager: notebookManager,
|
||||
notebookUri: path,
|
||||
notificationService: notificationService.object
|
||||
});
|
||||
|
||||
let serverlessNotebookManager = new NotebookManagerStub();
|
||||
serverlessNotebookManager.sessionManager = mockSessionManager.object;
|
||||
remoteSession = new ClientSession({
|
||||
notebookManager: serverlessNotebookManager,
|
||||
notebookUri: path,
|
||||
notificationService: notificationService.object
|
||||
});
|
||||
});
|
||||
|
||||
it('Should set path, isReady and ready on construction', function(): void {
|
||||
should(session.notebookUri).equal(path);
|
||||
should(session.ready).not.be.undefined();
|
||||
should(session.isReady).be.false();
|
||||
should(session.status).equal('starting');
|
||||
should(session.isInErrorState).be.false();
|
||||
should(session.errorMessage).be.undefined();
|
||||
});
|
||||
|
||||
it('Should call on serverManager startup if set', async function(): Promise<void> {
|
||||
// Given I have a serverManager that starts successfully
|
||||
serverManager.result = Promise.resolve();
|
||||
should(session.isReady).be.false();
|
||||
|
||||
// When I kick off initialization
|
||||
await session.initialize();
|
||||
|
||||
// Then I expect ready to be completed too
|
||||
await session.ready;
|
||||
should(serverManager.calledStart).be.true();
|
||||
should(session.isReady).be.true();
|
||||
});
|
||||
|
||||
it('Should go to error state if serverManager startup fails', async function(): Promise<void> {
|
||||
// Given I have a serverManager that fails to start
|
||||
serverManager.result = Promise.reject('error');
|
||||
should(session.isInErrorState).be.false();
|
||||
|
||||
// When I initialize
|
||||
await session.initialize();
|
||||
|
||||
// Then I expect ready to complete, but isInErrorState to be true
|
||||
await session.ready;
|
||||
should(session.isReady).be.true();
|
||||
should(serverManager.calledStart).be.true();
|
||||
should(session.isInErrorState).be.true();
|
||||
should(session.errorMessage).equal('error');
|
||||
});
|
||||
|
||||
it('Should be ready when session manager is ready', async function(): Promise<void> {
|
||||
serverManager.result = new Promise((resolve) => {
|
||||
serverManager.isStarted = true;
|
||||
resolve();
|
||||
});
|
||||
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
|
||||
|
||||
// When I call initialize
|
||||
await session.initialize();
|
||||
|
||||
// Then
|
||||
should(session.isReady).be.true();
|
||||
should(session.isInErrorState).be.false();
|
||||
await session.ready;
|
||||
});
|
||||
|
||||
it('Should be in error state if server fails to start', async function(): Promise<void> {
|
||||
serverManager.result = new Promise((resolve) => {
|
||||
serverManager.isStarted = false;
|
||||
resolve();
|
||||
});
|
||||
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
|
||||
|
||||
// When I call initialize
|
||||
await session.initialize();
|
||||
|
||||
// Then
|
||||
await session.ready;
|
||||
should(session.isReady).be.true();
|
||||
should(session.isInErrorState).be.true();
|
||||
});
|
||||
|
||||
it('Should go to error state if sessionManager fails', async function(): Promise<void> {
|
||||
serverManager.isStarted = true;
|
||||
mockSessionManager.setup(s => s.isReady).returns(() => false);
|
||||
mockSessionManager.setup(s => s.ready).returns(() => Promise.reject('error'));
|
||||
|
||||
// When I call initialize
|
||||
await session.initialize();
|
||||
|
||||
// Then
|
||||
should(session.isReady).be.true();
|
||||
should(session.isInErrorState).be.true();
|
||||
should(session.errorMessage).equal('error');
|
||||
});
|
||||
|
||||
it('Should start session automatically if kernel preference requests it', async function(): Promise<void> {
|
||||
serverManager.isStarted = true;
|
||||
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
|
||||
let sessionMock = TypeMoq.Mock.ofType(EmptySession);
|
||||
let startOptions: nb.ISessionOptions = undefined;
|
||||
mockSessionManager.setup(s => s.startNew(TypeMoq.It.isAny())).returns((options) => {
|
||||
startOptions = options;
|
||||
return Promise.resolve(sessionMock.object);
|
||||
});
|
||||
|
||||
// When I call initialize after defining kernel preferences
|
||||
session.kernelPreference = {
|
||||
shouldStart: true,
|
||||
name: 'python'
|
||||
};
|
||||
await session.initialize();
|
||||
|
||||
// Then
|
||||
should(session.isReady).be.true();
|
||||
should(session.isInErrorState).be.false();
|
||||
should(startOptions.kernelName).equal('python');
|
||||
should(startOptions.path).equal(path.fsPath);
|
||||
});
|
||||
|
||||
it('Should shutdown session even if no serverManager is set', async function(): Promise<void> {
|
||||
// Given a session against a remote server
|
||||
let expectedId = 'abc';
|
||||
mockSessionManager.setup(s => s.isReady).returns(() => true);
|
||||
mockSessionManager.setup(s => s.shutdown(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
let sessionMock = TypeMoq.Mock.ofType(EmptySession);
|
||||
sessionMock.setup(s => s.id).returns(() => expectedId);
|
||||
mockSessionManager.setup(s => s.startNew(TypeMoq.It.isAny())).returns(() => Promise.resolve(sessionMock.object));
|
||||
|
||||
remoteSession.kernelPreference = {
|
||||
shouldStart: true,
|
||||
name: 'python'
|
||||
};
|
||||
await remoteSession.initialize();
|
||||
|
||||
// When I call shutdown
|
||||
await remoteSession.shutdown();
|
||||
|
||||
// Then
|
||||
mockSessionManager.verify(s => s.shutdown(TypeMoq.It.isValue(expectedId)), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
|
||||
it('Should stop server if server is set', async function(): Promise<void> {
|
||||
// Given a kernel has been started
|
||||
serverManager.isStarted = true;
|
||||
serverManager.result = Promise.resolve();
|
||||
mockSessionManager.setup(s => s.isReady).returns(() => true);
|
||||
mockSessionManager.setup(s => s.shutdown(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
|
||||
await session.initialize();
|
||||
|
||||
// When I call shutdown
|
||||
await session.shutdown();
|
||||
|
||||
// Then
|
||||
should(serverManager.calledEnd).be.true();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
77
src/sqltest/parts/notebook/model/contentManagers.test.ts
Normal file
77
src/sqltest/parts/notebook/model/contentManagers.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as path from 'path';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as tempWrite from 'temp-write';
|
||||
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||
import * as testUtils from '../../../utils/testUtils';
|
||||
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
|
||||
let expectedNotebookContent: nb.INotebook = {
|
||||
cells: [{
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'insert into t1 values (c1, c2)',
|
||||
metadata: { language: 'python' },
|
||||
execution_count: 1
|
||||
}],
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
name: 'mssql',
|
||||
language: 'sql'
|
||||
}
|
||||
},
|
||||
nbformat: 5,
|
||||
nbformat_minor: 0
|
||||
};
|
||||
let notebookContentString = JSON.stringify(expectedNotebookContent);
|
||||
|
||||
function verifyMatchesExpectedNotebook(notebook: nb.INotebook): void {
|
||||
should(notebook.cells).have.length(1, 'Expected 1 cell');
|
||||
should(notebook.cells[0].cell_type).equal(CellTypes.Code);
|
||||
should(notebook.cells[0].source).equal(expectedNotebookContent.cells[0].source);
|
||||
should(notebook.metadata.kernelspec.name).equal(expectedNotebookContent.metadata.kernelspec.name);
|
||||
should(notebook.nbformat).equal(expectedNotebookContent.nbformat);
|
||||
should(notebook.nbformat_minor).equal(expectedNotebookContent.nbformat_minor);
|
||||
}
|
||||
|
||||
describe('Local Content Manager', function(): void {
|
||||
let contentManager = new LocalContentManager();
|
||||
|
||||
it('Should return undefined if path is undefined', async function(): Promise<void> {
|
||||
let content = await contentManager.getNotebookContents(undefined);
|
||||
should(content).be.undefined();
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
content = await contentManager.getNotebookContents(null);
|
||||
should(content).be.undefined();
|
||||
});
|
||||
|
||||
it('Should throw if file does not exist', async function(): Promise<void> {
|
||||
await testUtils.assertThrowsAsync(async () => await contentManager.getNotebookContents(URI.file('/path/doesnot/exist.ipynb')), undefined);
|
||||
});
|
||||
it('Should return notebook contents parsed as INotebook when valid notebook file parsed', async function(): Promise<void> {
|
||||
// Given a file containing a valid notebook
|
||||
let localFile = tempWrite.sync(notebookContentString, 'notebook.ipynb');
|
||||
// when I read the content
|
||||
let notebook = await contentManager.getNotebookContents(URI.file(localFile));
|
||||
// then I expect notebook format to match
|
||||
verifyMatchesExpectedNotebook(notebook);
|
||||
});
|
||||
it('Should ignore invalid content in the notebook file', async function(): Promise<void> {
|
||||
// Given a file containing a notebook with some garbage properties
|
||||
let invalidContent = notebookContentString + '\\nasddfdsafasdf';
|
||||
let localFile = tempWrite.sync(invalidContent, 'notebook.ipynb');
|
||||
// when I read the content
|
||||
let notebook = await contentManager.getNotebookContents(URI.file(localFile));
|
||||
// then I expect notebook format to still be valid
|
||||
verifyMatchesExpectedNotebook(notebook);
|
||||
});
|
||||
});
|
||||
251
src/sqltest/parts/notebook/model/notebookModel.test.ts
Normal file
251
src/sqltest/parts/notebook/model/notebookModel.test.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||
import * as testUtils from '../../../utils/testUtils';
|
||||
import { NotebookManagerStub } from '../common';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||
import { IClientSession, ICellModel, INotebookModelOptions } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ClientSession } from 'sql/parts/notebook/models/clientSession';
|
||||
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { ConnectionManagementService } from 'sql/parts/connection/common/connectionManagementService';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
let expectedNotebookContent: nb.INotebook = {
|
||||
cells: [{
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'insert into t1 values (c1, c2)',
|
||||
metadata: { language: 'python' },
|
||||
execution_count: 1
|
||||
}, {
|
||||
cell_type: CellTypes.Markdown,
|
||||
source: 'I am *markdown*',
|
||||
metadata: { language: 'python' },
|
||||
execution_count: 1
|
||||
}],
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
name: 'mssql',
|
||||
language: 'sql'
|
||||
}
|
||||
},
|
||||
nbformat: 5,
|
||||
nbformat_minor: 0
|
||||
};
|
||||
|
||||
let expectedNotebookContentOneCell: nb.INotebook = {
|
||||
cells: [{
|
||||
cell_type: CellTypes.Code,
|
||||
source: 'insert into t1 values (c1, c2)',
|
||||
metadata: { language: 'python' },
|
||||
execution_count: 1
|
||||
}],
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
name: 'mssql',
|
||||
language: 'sql'
|
||||
}
|
||||
},
|
||||
nbformat: 5,
|
||||
nbformat_minor: 0
|
||||
};
|
||||
|
||||
let defaultUri = URI.file('/some/path.ipynb');
|
||||
|
||||
let mockClientSession: TypeMoq.Mock<IClientSession>;
|
||||
let sessionReady: Deferred<void>;
|
||||
let mockModelFactory: TypeMoq.Mock<ModelFactory>;
|
||||
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||
|
||||
describe('notebook model', function(): void {
|
||||
let notebookManager = new NotebookManagerStub();
|
||||
let memento: TypeMoq.Mock<Memento>;
|
||||
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
|
||||
let defaultModelOptions: INotebookModelOptions;
|
||||
beforeEach(() => {
|
||||
sessionReady = new Deferred<void>();
|
||||
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
|
||||
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
|
||||
queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined);
|
||||
queryConnectionService.callBase = true;
|
||||
defaultModelOptions = {
|
||||
notebookUri: defaultUri,
|
||||
factory: new ModelFactory(),
|
||||
notebookManager,
|
||||
notificationService: notificationService.object,
|
||||
connectionService: queryConnectionService.object };
|
||||
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
|
||||
mockClientSession.setup(c => c.initialize(TypeMoq.It.isAny())).returns(() => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
mockClientSession.setup(c => c.ready).returns(() => sessionReady.promise);
|
||||
mockModelFactory = TypeMoq.Mock.ofType(ModelFactory);
|
||||
mockModelFactory.callBase = true;
|
||||
mockModelFactory.setup(f => f.createClientSession(TypeMoq.It.isAny())).returns(() => {
|
||||
return mockClientSession.object;
|
||||
});
|
||||
});
|
||||
|
||||
it('Should create single cell if model has no contents', async function(): Promise<void> {
|
||||
// Given an empty notebook
|
||||
let emptyNotebook: nb.INotebook = {
|
||||
cells: [],
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
name: 'mssql',
|
||||
language: 'sql'
|
||||
}
|
||||
},
|
||||
nbformat: 5,
|
||||
nbformat_minor: 0
|
||||
};
|
||||
|
||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
|
||||
notebookManager.contentManager = mockContentManager.object;
|
||||
|
||||
// When I initialize the model
|
||||
let model = new NotebookModel(defaultModelOptions);
|
||||
await model.requestModelLoad();
|
||||
|
||||
// Then I expect to have 1 code cell as the contents
|
||||
should(model.cells).have.length(1);
|
||||
should(model.cells[0].source).be.empty();
|
||||
});
|
||||
|
||||
it('Should throw if model load fails', async function(): Promise<void> {
|
||||
// Given a call to get Contents fails
|
||||
let error = new Error('File not found');
|
||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
|
||||
notebookManager.contentManager = mockContentManager.object;
|
||||
|
||||
// When I initalize the model
|
||||
// Then it should throw
|
||||
let model = new NotebookModel(defaultModelOptions);
|
||||
should(model.inErrorState).be.false();
|
||||
await testUtils.assertThrowsAsync(() => model.requestModelLoad(), error.message);
|
||||
should(model.inErrorState).be.true();
|
||||
});
|
||||
|
||||
it('Should convert cell info to CellModels', async function(): Promise<void> {
|
||||
// Given a notebook with 2 cells
|
||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
|
||||
notebookManager.contentManager = mockContentManager.object;
|
||||
|
||||
// When I initalize the model
|
||||
let model = new NotebookModel(defaultModelOptions);
|
||||
await model.requestModelLoad();
|
||||
|
||||
// Then I expect all cells to be in the model
|
||||
should(model.cells).have.length(2);
|
||||
should(model.cells[0].source).be.equal(expectedNotebookContent.cells[0].source);
|
||||
should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source);
|
||||
});
|
||||
|
||||
it('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
|
||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||
notebookManager.contentManager = mockContentManager.object;
|
||||
|
||||
// Given I have a session that fails to start
|
||||
mockClientSession.setup(c => c.isInErrorState).returns(() => true);
|
||||
mockClientSession.setup(c => c.errorMessage).returns(() => 'Error');
|
||||
sessionReady.resolve();
|
||||
let sessionFired = false;
|
||||
|
||||
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
||||
factory: mockModelFactory.object
|
||||
});
|
||||
let model = new NotebookModel(options);
|
||||
model.onClientSessionReady((session) => sessionFired = true);
|
||||
await model.requestModelLoad();
|
||||
model.backgroundStartSession();
|
||||
|
||||
// Then I expect load to succeed
|
||||
shouldHaveOneCell(model);
|
||||
should(model.clientSession).not.be.undefined();
|
||||
// but on server load completion I expect error state to be set
|
||||
// Note: do not expect serverLoad event to throw even if failed
|
||||
await model.sessionLoadFinished;
|
||||
should(model.inErrorState).be.true();
|
||||
should(sessionFired).be.false();
|
||||
});
|
||||
|
||||
it('Should not be in error state if client session initialization succeeds', async function(): Promise<void> {
|
||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||
notebookManager.contentManager = mockContentManager.object;
|
||||
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
|
||||
|
||||
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
|
||||
mockClientSession.setup(c => c.isReady).returns(() => true);
|
||||
mockClientSession.setup(c => c.kernelChanged).returns(() => kernelChangedEmitter.event);
|
||||
|
||||
queryConnectionService.setup(c => c.getActiveConnections(TypeMoq.It.isAny())).returns(() => null);
|
||||
|
||||
sessionReady.resolve();
|
||||
let actualSession: IClientSession = undefined;
|
||||
|
||||
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
||||
factory: mockModelFactory.object
|
||||
});
|
||||
let model = new NotebookModel(options, false);
|
||||
model.onClientSessionReady((session) => actualSession = session);
|
||||
await model.requestModelLoad();
|
||||
model.backgroundStartSession();
|
||||
|
||||
// Then I expect load to succeed
|
||||
should(model.clientSession).not.be.undefined();
|
||||
// but on server load completion I expect error state to be set
|
||||
// Note: do not expect serverLoad event to throw even if failed
|
||||
let kernelChangedArg: nb.IKernelChangedArgs = undefined;
|
||||
model.kernelChanged((kernel) => kernelChangedArg = kernel);
|
||||
await model.sessionLoadFinished;
|
||||
should(model.inErrorState).be.false();
|
||||
should(actualSession).equal(mockClientSession.object);
|
||||
should(model.clientSession).equal(mockClientSession.object);
|
||||
});
|
||||
|
||||
it('Should sanitize kernel display name when IP is included', async function(): Promise<void> {
|
||||
let model = new NotebookModel(defaultModelOptions);
|
||||
let displayName = 'PySpark (1.1.1.1)';
|
||||
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
||||
should(sanitizedDisplayName).equal('PySpark');
|
||||
});
|
||||
|
||||
it('Should sanitize kernel display name properly when IP is not included', async function(): Promise<void> {
|
||||
let model = new NotebookModel(defaultModelOptions);
|
||||
let displayName = 'PySpark';
|
||||
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
||||
should(sanitizedDisplayName).equal('PySpark');
|
||||
});
|
||||
|
||||
function shouldHaveOneCell(model: NotebookModel): void {
|
||||
should(model.cells).have.length(1);
|
||||
verifyCellModel(model.cells[0], { cell_type: CellTypes.Code, source: 'insert into t1 values (c1, c2)', metadata: { language: 'python' }, execution_count: 1 });
|
||||
}
|
||||
|
||||
function verifyCellModel(cellModel: ICellModel, expected: nb.ICell): void {
|
||||
should(cellModel.cellType).equal(expected.cell_type);
|
||||
should(cellModel.source).equal(expected.source);
|
||||
}
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user