mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Move SQL 2019 extension's notebook code into Azure Data Studio (#4090)
This commit is contained in:
259
extensions/notebook/src/test/common.ts
Normal file
259
extensions/notebook/src/test/common.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
|
||||
import { IServerInstance } from '../jupyter/common';
|
||||
import { Session, Kernel, KernelMessage, ServerConnection } from '@jupyterlab/services';
|
||||
import { ISignal } from '@phosphor/signaling';
|
||||
|
||||
export class JupyterServerInstanceStub implements IServerInstance {
|
||||
public get port(): string {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get uri(): vscode.Uri {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public configure(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public start(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
stop(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#region sesion and kernel stubs (long)
|
||||
export class SessionStub implements Session.ISession {
|
||||
public get terminated(): ISignal<this, void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get kernelChanged(): ISignal<this, Session.IKernelChangedArgs> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get statusChanged(): ISignal<this, Kernel.Status> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get propertyChanged(): ISignal<this, 'path' | 'name' | 'type'> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get iopubMessage(): ISignal<this, KernelMessage.IIOPubMessage> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get unhandledMessage(): ISignal<this, KernelMessage.IMessage> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get anyMessage(): ISignal<this, Kernel.IAnyMessageArgs> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get id(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get path(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get name(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get type(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get serverSettings(): ServerConnection.ISettings {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get model(): Session.IModel {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get kernel(): Kernel.IKernelConnection {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get status(): Kernel.Status {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public get isDisposed(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setPath(path: string): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setName(name: string): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setType(type: string): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
changeKernel(options: Partial<Kernel.IModel>): Promise<Kernel.IKernelConnection> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
shutdown(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class KernelStub implements Kernel.IKernel {
|
||||
get terminated(): ISignal<this, void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get statusChanged(): ISignal<this, Kernel.Status> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get iopubMessage(): ISignal<this, KernelMessage.IIOPubMessage> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get unhandledMessage(): ISignal<this, KernelMessage.IMessage> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get anyMessage(): ISignal<this, Kernel.IAnyMessageArgs> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get serverSettings(): ServerConnection.ISettings {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get id(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get name(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get model(): Kernel.IModel {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get username(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get clientId(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get status(): Kernel.Status {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get info(): KernelMessage.IInfoReply {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get isReady(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get ready(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get isDisposed(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
shutdown(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSpec(): Promise<Kernel.ISpecModel> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
sendShellMessage(msg: KernelMessage.IShellMessage, expectReply?: boolean, disposeOnDone?: boolean): Kernel.IFuture {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reconnect(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
interrupt(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
restart(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestKernelInfo(): Promise<KernelMessage.IInfoReplyMsg> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestComplete(content: KernelMessage.ICompleteRequest): Promise<KernelMessage.ICompleteReplyMsg> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestInspect(content: KernelMessage.IInspectRequest): Promise<KernelMessage.IInspectReplyMsg> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestHistory(content: KernelMessage.IHistoryRequest): Promise<KernelMessage.IHistoryReplyMsg> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestExecute(content: KernelMessage.IExecuteRequest, disposeOnDone?: boolean): Kernel.IFuture {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestIsComplete(content: KernelMessage.IIsCompleteRequest): Promise<KernelMessage.IIsCompleteReplyMsg> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
requestCommInfo(content: KernelMessage.ICommInfoRequest): Promise<KernelMessage.ICommInfoReplyMsg> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
sendInputReply(content: KernelMessage.IInputReply): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
connectToComm(targetName: string, commId?: string): Kernel.IComm {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerCommTarget(targetName: string, callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike<void>): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
removeCommTarget(targetName: string, callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike<void>): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
removeMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class FutureStub implements Kernel.IFuture {
|
||||
get msg(): KernelMessage.IShellMessage {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get done(): Promise<KernelMessage.IShellMessage> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get isDisposed(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get onReply(): (msg: KernelMessage.IShellMessage) => void | PromiseLike<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
set onReply(handler: (msg: KernelMessage.IShellMessage) => void | PromiseLike<void>) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get onStdin(): (msg: KernelMessage.IStdinMessage) => void | PromiseLike<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
set onStdin(handler: (msg: KernelMessage.IStdinMessage) => void | PromiseLike<void>) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
get onIOPub(): (msg: KernelMessage.IIOPubMessage) => void | PromiseLike<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
set onIOPub(handler: (msg: KernelMessage.IIOPubMessage) => void | PromiseLike<void>) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerMessageHook(hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
removeMessageHook(hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
sendInputReply(content: KernelMessage.IInputReply): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
66
extensions/notebook/src/test/common/port.test.ts
Normal file
66
extensions/notebook/src/test/common/port.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// This code is originally from https://github.com/Microsoft/vscode/blob/master/src/vs/base/test/node/port.test.ts
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as net from 'net';
|
||||
import 'mocha';
|
||||
|
||||
import * as ports from '../../common/ports';
|
||||
|
||||
describe('Ports', () => {
|
||||
it('Should Find a free port (no timeout)', function (done): void {
|
||||
this.timeout(1000 * 10); // higher timeout for this test
|
||||
|
||||
// get an initial freeport >= 7000
|
||||
ports.findFreePort(7000, 100, 300000).then(initialPort => {
|
||||
assert.ok(initialPort >= 7000);
|
||||
|
||||
// create a server to block this port
|
||||
const server = net.createServer();
|
||||
server.listen(initialPort, undefined, undefined, () => {
|
||||
|
||||
// once listening, find another free port and assert that the port is different from the opened one
|
||||
ports.findFreePort(7000, 50, 300000).then(freePort => {
|
||||
assert.ok(freePort >= 7000 && freePort !== initialPort);
|
||||
server.close();
|
||||
|
||||
done();
|
||||
}, err => done(err));
|
||||
});
|
||||
}, err => done(err));
|
||||
});
|
||||
|
||||
it('Should Find a free port in strict mode', function (done): void {
|
||||
this.timeout(1000 * 10); // higher timeout for this test
|
||||
|
||||
// get an initial freeport >= 7000
|
||||
let options = new ports.StrictPortFindOptions(7000, 7100, 7200);
|
||||
options.timeout = 300000;
|
||||
ports.strictFindFreePort(options).then(initialPort => {
|
||||
assert.ok(initialPort >= 7000);
|
||||
|
||||
// create a server to block this port
|
||||
const server = net.createServer();
|
||||
server.listen(initialPort, undefined, undefined, () => {
|
||||
|
||||
// once listening, find another free port and assert that the port is different from the opened one
|
||||
options.startPort = initialPort;
|
||||
options.maxRetriesPerStartPort = 1;
|
||||
options.totalRetryLoops = 50;
|
||||
ports.strictFindFreePort(options).then(freePort => {
|
||||
assert.ok(freePort >= 7100 && freePort !== initialPort);
|
||||
server.close();
|
||||
|
||||
done();
|
||||
}, err => done(err));
|
||||
});
|
||||
}, err => done(err));
|
||||
});
|
||||
});
|
||||
|
||||
24
extensions/notebook/src/test/common/querybookUtils.test.ts
Normal file
24
extensions/notebook/src/test/common/querybookUtils.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import 'mocha';
|
||||
|
||||
import * as notebookUtils from '../../common/notebookUtils';
|
||||
|
||||
describe('Random Token', () => {
|
||||
it('Should have default length and be hex only', async function (): Promise<void> {
|
||||
|
||||
let token = await notebookUtils.getRandomToken();
|
||||
should(token).have.length(48);
|
||||
let validChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
for (let i = 0; i < token.length; i++) {
|
||||
let char = token.charAt(i);
|
||||
should(validChars.indexOf(char)).be.greaterThan(-1);
|
||||
}
|
||||
});
|
||||
});
|
||||
46
extensions/notebook/src/test/common/stubs.ts
Normal file
46
extensions/notebook/src/test/common/stubs.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class MockExtensionContext implements vscode.ExtensionContext {
|
||||
logger: undefined;
|
||||
logDirectory: './';
|
||||
subscriptions: { dispose(): any; }[];
|
||||
workspaceState: vscode.Memento;
|
||||
globalState: vscode.Memento;
|
||||
extensionPath: string;
|
||||
asAbsolutePath(relativePath: string): string {
|
||||
return relativePath;
|
||||
}
|
||||
storagePath: string;
|
||||
|
||||
constructor() {
|
||||
this.subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class MockOutputChannel implements vscode.OutputChannel {
|
||||
name: string;
|
||||
|
||||
append(value: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
appendLine(value: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
clear(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
show(preserveFocus?: boolean): void;
|
||||
show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
|
||||
show(column?: any, preserveFocus?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
hide(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
22
extensions/notebook/src/test/common/testUtils.ts
Normal file
22
extensions/notebook/src/test/common/testUtils.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 assert from 'assert';
|
||||
|
||||
export async function assertThrowsAsync(fn, regExp): Promise<void> {
|
||||
let f = () => {
|
||||
// Empty
|
||||
};
|
||||
try {
|
||||
await fn();
|
||||
} catch (e) {
|
||||
f = () => { throw e; };
|
||||
} finally {
|
||||
assert.throws(f, regExp);
|
||||
}
|
||||
}
|
||||
32
extensions/notebook/src/test/index.ts
Normal file
32
extensions/notebook/src/test/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// import * as vscode from 'vscode';
|
||||
// import { context } from './testContext';
|
||||
|
||||
const path = require('path');
|
||||
const testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
const suite = 'Notebook Tests';
|
||||
|
||||
const options: any = {
|
||||
ui: 'bdd',
|
||||
useColors: true,
|
||||
timeout: 600000
|
||||
};
|
||||
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
options.reporter = 'mocha-multi-reporters';
|
||||
options.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testRunner.configure(options);
|
||||
|
||||
export = testRunner;
|
||||
97
extensions/notebook/src/test/model/contentManagers.test.ts
Normal file
97
extensions/notebook/src/test/model/contentManagers.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as path from 'path';
|
||||
import { ContentsManager, Contents } from '@jupyterlab/services';
|
||||
import { nb } from 'sqlops';
|
||||
import 'mocha';
|
||||
|
||||
import { INotebook, CellTypes } from '../../contracts/content';
|
||||
import { RemoteContentManager } from '../../jupyter/remoteContentManager';
|
||||
import * as testUtils from '../common/testUtils';
|
||||
|
||||
let expectedNotebookContent: 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.INotebookContents): 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('Remote Content Manager', function (): void {
|
||||
let mockJupyterManager = TypeMoq.Mock.ofType(ContentsManager);
|
||||
let contentManager = new RemoteContentManager(mockJupyterManager.object);
|
||||
|
||||
// TODO re-enable when we bring in usage of remote content managers / binders
|
||||
// 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();
|
||||
// content = await contentManager.getNotebookContents(vscode.Uri.file(''));
|
||||
// should(content).be.undefined();
|
||||
// });
|
||||
|
||||
it('Should throw if API call throws', async function (): Promise<void> {
|
||||
let exception = new Error('Path was wrong');
|
||||
mockJupyterManager.setup(c => c.get(TypeMoq.It.isAny(), TypeMoq.It.isAny())).throws(exception);
|
||||
await testUtils.assertThrowsAsync(async () => await contentManager.getNotebookContents(vscode.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 valid request to the notebook server
|
||||
let remotePath = '/remote/path/that/exists.ipynb';
|
||||
let contentsModel: Contents.IModel = {
|
||||
name: path.basename(remotePath),
|
||||
content: expectedNotebookContent,
|
||||
path: remotePath,
|
||||
type: 'notebook',
|
||||
writable: false,
|
||||
created: undefined,
|
||||
last_modified: undefined,
|
||||
mimetype: 'json',
|
||||
format: 'json'
|
||||
};
|
||||
mockJupyterManager.setup(c => c.get(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(contentsModel));
|
||||
// when I read the content
|
||||
let notebook = await contentManager.getNotebookContents(vscode.Uri.file(remotePath));
|
||||
// then I expect notebook format to match
|
||||
verifyMatchesExpectedNotebook(notebook);
|
||||
});
|
||||
|
||||
it('Should return undefined if service does not return anything', async function (): Promise<void> {
|
||||
// Given a valid request to the notebook server
|
||||
let remotePath = '/remote/path/that/does/not/exist.ipynb';
|
||||
mockJupyterManager.setup(c => c.get(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
|
||||
// when I read the content
|
||||
let notebook = await contentManager.getNotebookContents(vscode.Uri.file(remotePath));
|
||||
// then I expect notebook format to match
|
||||
should(notebook).be.undefined();
|
||||
});
|
||||
});
|
||||
203
extensions/notebook/src/test/model/kernel.test.ts
Normal file
203
extensions/notebook/src/test/model/kernel.test.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Kernel, KernelMessage } from '@jupyterlab/services';
|
||||
import 'mocha';
|
||||
|
||||
import { KernelStub, FutureStub } from '../common';
|
||||
import { JupyterKernel, JupyterFuture } from '../../jupyter/jupyterKernel';
|
||||
|
||||
describe('Jupyter Session', function (): void {
|
||||
let mockJupyterKernel: TypeMoq.IMock<KernelStub>;
|
||||
let kernel: JupyterKernel;
|
||||
|
||||
beforeEach(() => {
|
||||
mockJupyterKernel = TypeMoq.Mock.ofType(KernelStub);
|
||||
kernel = new JupyterKernel(mockJupyterKernel.object);
|
||||
});
|
||||
|
||||
it('should pass through most properties', function (done): void {
|
||||
// Given values for the passthrough properties
|
||||
mockJupyterKernel.setup(s => s.id).returns(() => 'id');
|
||||
mockJupyterKernel.setup(s => s.name).returns(() => 'name');
|
||||
mockJupyterKernel.setup(s => s.isReady).returns(() => true);
|
||||
let readyPromise = Promise.reject('err');
|
||||
mockJupyterKernel.setup(s => s.ready).returns(() => readyPromise);
|
||||
// Should return those values when called
|
||||
should(kernel.id).equal('id');
|
||||
should(kernel.name).equal('name');
|
||||
should(kernel.isReady).be.true();
|
||||
|
||||
kernel.ready.then((fulfilled) => done('Err: should not succeed'), (err) => done());
|
||||
});
|
||||
|
||||
it('should passthrough spec with expected name and display name', async function (): Promise<void> {
|
||||
let spec: Kernel.ISpecModel = {
|
||||
name: 'python',
|
||||
display_name: 'Python 3',
|
||||
language: 'python',
|
||||
argv: undefined,
|
||||
resources: undefined
|
||||
};
|
||||
mockJupyterKernel.setup(k => k.getSpec()).returns(() => Promise.resolve(spec));
|
||||
|
||||
let actualSpec = await kernel.getSpec();
|
||||
should(actualSpec.name).equal('python');
|
||||
should(actualSpec.display_name).equal('Python 3');
|
||||
});
|
||||
|
||||
it('should return code completions on requestComplete', async function (): Promise<void> {
|
||||
should(kernel.supportsIntellisense).be.true();
|
||||
let completeMsg: KernelMessage.ICompleteReplyMsg = {
|
||||
channel: 'shell',
|
||||
content: {
|
||||
cursor_start: 0,
|
||||
cursor_end: 2,
|
||||
matches: ['print'],
|
||||
metadata: {},
|
||||
status: 'ok'
|
||||
},
|
||||
header: undefined,
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
mockJupyterKernel.setup(k => k.requestComplete(TypeMoq.It.isAny())).returns(() => Promise.resolve(completeMsg));
|
||||
|
||||
let msg = await kernel.requestComplete({
|
||||
code: 'pr',
|
||||
cursor_pos: 2
|
||||
});
|
||||
should(msg.type).equal('shell');
|
||||
should(msg.content).equal(completeMsg.content);
|
||||
});
|
||||
|
||||
it('should return a simple future on requestExecute', async function (): Promise<void> {
|
||||
let futureMock = TypeMoq.Mock.ofType(FutureStub);
|
||||
const code = 'print("hello")';
|
||||
let msg: KernelMessage.IShellMessage = {
|
||||
channel: 'shell',
|
||||
content: { code: code },
|
||||
header: undefined,
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
futureMock.setup(f => f.msg).returns(() => msg);
|
||||
let executeRequest: KernelMessage.IExecuteRequest;
|
||||
let shouldDispose: KernelMessage.IExecuteRequest;
|
||||
mockJupyterKernel.setup(k => k.requestExecute(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((request, disposeOnDone) => {
|
||||
executeRequest = request;
|
||||
shouldDispose = disposeOnDone;
|
||||
return futureMock.object;
|
||||
});
|
||||
|
||||
// When I request execute
|
||||
let future = kernel.requestExecute({
|
||||
code: code
|
||||
}, true);
|
||||
|
||||
// Then expect wrapper to be returned
|
||||
should(future).be.instanceof(JupyterFuture);
|
||||
should(future.msg.type).equal('shell');
|
||||
should(future.msg.content.code).equal(code);
|
||||
should(executeRequest.code).equal(code);
|
||||
should(shouldDispose).be.true();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Jupyter Future', function (): void {
|
||||
let mockJupyterFuture: TypeMoq.IMock<FutureStub>;
|
||||
let future: JupyterFuture;
|
||||
|
||||
beforeEach(() => {
|
||||
mockJupyterFuture = TypeMoq.Mock.ofType(FutureStub);
|
||||
future = new JupyterFuture(mockJupyterFuture.object);
|
||||
});
|
||||
|
||||
it('should return message on done', async function (): Promise<void> {
|
||||
let msg: KernelMessage.IShellMessage = {
|
||||
channel: 'shell',
|
||||
content: { code: 'exec' },
|
||||
header: undefined,
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
|
||||
mockJupyterFuture.setup(f => f.done).returns(() => Promise.resolve(msg));
|
||||
|
||||
let actualMsg = await future.done;
|
||||
should(actualMsg.content.code).equal('exec');
|
||||
});
|
||||
|
||||
it('should relay reply message', async function (): Promise<void> {
|
||||
let handler: (msg: KernelMessage.IShellMessage) => void | PromiseLike<void>;
|
||||
mockJupyterFuture.setup(f => f.onReply = TypeMoq.It.isAny()).callback(h => handler = h);
|
||||
|
||||
// When I set a reply handler and a message is sent
|
||||
let msg: nb.IShellMessage;
|
||||
future.setReplyHandler({
|
||||
handle: (message => {
|
||||
msg = message;
|
||||
})
|
||||
});
|
||||
should(handler).not.be.undefined();
|
||||
verifyRelayMessage('shell', handler, () => msg);
|
||||
|
||||
});
|
||||
|
||||
it('should relay StdIn message', async function (): Promise<void> {
|
||||
let handler: (msg: KernelMessage.IStdinMessage) => void | PromiseLike<void>;
|
||||
mockJupyterFuture.setup(f => f.onStdin = TypeMoq.It.isAny()).callback(h => handler = h);
|
||||
|
||||
// When I set a reply handler and a message is sent
|
||||
let msg: nb.IStdinMessage;
|
||||
future.setStdInHandler({
|
||||
handle: (message => {
|
||||
msg = message;
|
||||
})
|
||||
});
|
||||
should(handler).not.be.undefined();
|
||||
verifyRelayMessage('stdin', handler, () => msg);
|
||||
});
|
||||
|
||||
it('should relay IOPub message', async function (): Promise<void> {
|
||||
let handler: (msg: KernelMessage.IIOPubMessage) => void | PromiseLike<void>;
|
||||
mockJupyterFuture.setup(f => f.onIOPub = TypeMoq.It.isAny()).callback(h => handler = h);
|
||||
|
||||
// When I set a reply handler and a message is sent
|
||||
let msg: nb.IIOPubMessage;
|
||||
future.setIOPubHandler({
|
||||
handle: (message => {
|
||||
msg = message;
|
||||
})
|
||||
});
|
||||
should(handler).not.be.undefined();
|
||||
verifyRelayMessage('iopub', handler, () => msg);
|
||||
});
|
||||
|
||||
function verifyRelayMessage(channel: nb.Channel | KernelMessage.Channel, handler: (msg: KernelMessage.IMessage) => void | PromiseLike<void>, getMessage: () => nb.IMessage): void {
|
||||
handler({
|
||||
channel: <any>channel,
|
||||
content: { value: 'test' },
|
||||
metadata: { value: 'test' },
|
||||
header: { username: 'test', version: '1', msg_id: undefined, msg_type: undefined, session: undefined },
|
||||
parent_header: { username: 'test', version: '1', msg_id: undefined, msg_type: undefined, session: undefined }
|
||||
});
|
||||
let msg = getMessage();
|
||||
// Then the value should be relayed
|
||||
should(msg.type).equal(channel);
|
||||
should(msg.content).have.property('value', 'test');
|
||||
should(msg.metadata).have.property('value', 'test');
|
||||
should(msg.header).have.property('username', 'test');
|
||||
should(msg.parent_header).have.property('username', 'test');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
236
extensions/notebook/src/test/model/serverInstance.test.ts
Normal file
236
extensions/notebook/src/test/model/serverInstance.test.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as stream from 'stream';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import 'mocha';
|
||||
|
||||
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
||||
import { ApiWrapper } from '../..//common/apiWrapper';
|
||||
import { PerNotebookServerInstance, ServerInstanceUtils } from '../../jupyter/serverInstance';
|
||||
import { MockOutputChannel } from '../common/stubs';
|
||||
import * as testUtils from '../common/testUtils';
|
||||
import { LocalJupyterServerManager } from '../../jupyter/jupyterServerManager';
|
||||
|
||||
const successMessage = `[I 14:00:38.811 NotebookApp] The Jupyter Notebook is running at:
|
||||
[I 14:00:38.812 NotebookApp] http://localhost:8891/?token=...
|
||||
[I 14:00:38.812 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
|
||||
`;
|
||||
|
||||
describe('Jupyter server instance', function (): void {
|
||||
let expectedPath = 'mydir/notebook.ipynb';
|
||||
let mockInstall: TypeMoq.IMock<JupyterServerInstallation>;
|
||||
let mockOutputChannel: TypeMoq.IMock<MockOutputChannel>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockUtils: TypeMoq.IMock<ServerInstanceUtils>;
|
||||
let serverInstance: PerNotebookServerInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny()));
|
||||
mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined);
|
||||
mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
|
||||
mockOutputChannel = TypeMoq.Mock.ofType(MockOutputChannel);
|
||||
mockInstall.setup(i => i.outputChannel).returns(() => mockOutputChannel.object);
|
||||
mockInstall.setup(i => i.pythonExecutable).returns(() => 'python3');
|
||||
mockUtils = TypeMoq.Mock.ofType(ServerInstanceUtils);
|
||||
mockUtils.setup(u => u.checkProcessDied(TypeMoq.It.isAny())).returns(() => undefined);
|
||||
serverInstance = new PerNotebookServerInstance({
|
||||
documentPath: expectedPath,
|
||||
install: mockInstall.object
|
||||
}, mockUtils.object);
|
||||
});
|
||||
|
||||
|
||||
it('Should not be started initially', function (): void {
|
||||
// Given a new instance It should not be started
|
||||
should(serverInstance.isStarted).be.false();
|
||||
should(serverInstance.port).be.undefined();
|
||||
});
|
||||
|
||||
it('Should create config and data directories on configure', async function (): Promise<void> {
|
||||
// Given a server instance
|
||||
mockUtils.setup(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
mockUtils.setup(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve());
|
||||
mockUtils.setup(u => u.existsSync(TypeMoq.It.isAnyString())).returns(() => false);
|
||||
|
||||
// When I run configure
|
||||
await serverInstance.configure();
|
||||
|
||||
// Then I expect a folder to have been created with config and data subdirs
|
||||
mockUtils.verify(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(5));
|
||||
mockUtils.verify(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3));
|
||||
mockUtils.verify(u => u.existsSync(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1));
|
||||
});
|
||||
|
||||
it('Should have URI info after start', async function (): Promise<void> {
|
||||
// Given startup will succeed
|
||||
let process = setupSpawn({
|
||||
sdtout: (listener) => undefined,
|
||||
stderr: (listener) => listener(successMessage)
|
||||
});
|
||||
mockUtils.setup(u => u.spawn(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns(() => <ChildProcess>process.object);
|
||||
|
||||
// When I call start
|
||||
await serverInstance.start();
|
||||
|
||||
// Then I expect all parts of the URI to be defined
|
||||
should(serverInstance.uri).not.be.undefined();
|
||||
should(serverInstance.uri.scheme).equal('http');
|
||||
let settings = LocalJupyterServerManager.getLocalConnectionSettings(serverInstance.uri);
|
||||
// Verify a token with expected length was generated
|
||||
should(settings.token).have.length(48);
|
||||
let hostAndPort = serverInstance.uri.authority.split(':');
|
||||
// verify port was set as expected
|
||||
should(hostAndPort[1]).length(4);
|
||||
|
||||
// And I expect it to be started
|
||||
should(serverInstance.isStarted).be.true();
|
||||
|
||||
// And I expect listeners to be cleaned up
|
||||
process.verify(p => p.on(TypeMoq.It.isValue('error'), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
process.verify(p => p.on(TypeMoq.It.isValue('exit'), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('Should throw if error before startup', async function (): Promise<void> {
|
||||
let error = 'myerr';
|
||||
let process = setupSpawn({
|
||||
sdtout: (listener) => undefined,
|
||||
stderr: (listener) => listener(successMessage),
|
||||
error: (listener) => setTimeout(() => listener(new Error(error)), 10)
|
||||
});
|
||||
mockUtils.setup(u => u.spawn(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns(() => <ChildProcess>process.object);
|
||||
|
||||
// When I call start then I expect it to pass
|
||||
await serverInstance.start();
|
||||
});
|
||||
|
||||
it('Should throw if exit before startup', async function (): Promise<void> {
|
||||
let code = -1234;
|
||||
let process = setupSpawn({
|
||||
exit: (listener) => listener(code)
|
||||
});
|
||||
mockUtils.setup(u => u.spawn(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns(() => <ChildProcess>process.object);
|
||||
|
||||
// When I call start then I expect the error to be thrown
|
||||
await testUtils.assertThrowsAsync(() => serverInstance.start(), undefined);
|
||||
should(serverInstance.isStarted).be.false();
|
||||
});
|
||||
|
||||
it('Should call stop with correct port on close', async function (): Promise<void> {
|
||||
// Given startup will succeed
|
||||
let process = setupSpawn({
|
||||
sdtout: (listener) => undefined,
|
||||
stderr: (listener) => listener(successMessage)
|
||||
});
|
||||
mockUtils.setup(u => u.spawn(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns(() => <ChildProcess>process.object);
|
||||
|
||||
let actualCommand: string = undefined;
|
||||
mockUtils.setup(u => u.executeBufferedCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns((cmd) => {
|
||||
actualCommand = cmd;
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
mockUtils.setup(u => u.pathExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false));
|
||||
mockUtils.setup(u => u.removeDir(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
// When I call start and then stop
|
||||
await serverInstance.start();
|
||||
await serverInstance.stop();
|
||||
|
||||
// Then I expect stop to be called on the child process
|
||||
should(actualCommand.indexOf(`jupyter notebook stop ${serverInstance.port}`)).be.greaterThan(-1);
|
||||
mockUtils.verify(u => u.removeDir(TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
});
|
||||
|
||||
it('Should remove directory on close', async function (): Promise<void> {
|
||||
// Given configure and startup are done
|
||||
mockUtils.setup(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
mockUtils.setup(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve());
|
||||
|
||||
let process = setupSpawn({
|
||||
sdtout: (listener) => undefined,
|
||||
stderr: (listener) => listener(successMessage)
|
||||
});
|
||||
mockUtils.setup(u => u.spawn(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns(() => <ChildProcess>process.object);
|
||||
mockUtils.setup(u => u.executeBufferedCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns((cmd) => Promise.resolve(undefined));
|
||||
mockUtils.setup(u => u.pathExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
|
||||
mockUtils.setup(u => u.removeDir(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
|
||||
await serverInstance.configure();
|
||||
await serverInstance.start();
|
||||
|
||||
// When I call stop
|
||||
await serverInstance.stop();
|
||||
|
||||
// Then I expect the directory to be cleaned up
|
||||
mockUtils.verify(u => u.removeDir(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
function setupSpawn(callbacks: IProcessCallbacks): TypeMoq.IMock<ChildProcessStub> {
|
||||
|
||||
let stdoutMock = TypeMoq.Mock.ofType(stream.Readable);
|
||||
stdoutMock.setup(s => s.on(TypeMoq.It.isValue('data'), TypeMoq.It.isAny()))
|
||||
.returns((event, listener) => runIfExists(listener, callbacks.sdtout));
|
||||
let stderrMock = TypeMoq.Mock.ofType(stream.Readable);
|
||||
stderrMock.setup(s => s.on(TypeMoq.It.isValue('data'), TypeMoq.It.isAny()))
|
||||
.returns((event, listener) => runIfExists(listener, callbacks.stderr));
|
||||
let mockProcess = TypeMoq.Mock.ofType(ChildProcessStub);
|
||||
mockProcess.setup(p => p.stdout).returns(() => stdoutMock.object);
|
||||
mockProcess.setup(p => p.stderr).returns(() => stderrMock.object);
|
||||
mockProcess.setup(p => p.on(TypeMoq.It.isValue('exit'), TypeMoq.It.isAny()))
|
||||
.returns((event, listener) => runIfExists(listener, callbacks.exit));
|
||||
mockProcess.setup(p => p.on(TypeMoq.It.isValue('error'), TypeMoq.It.isAny()))
|
||||
.returns((event, listener) => runIfExists(listener, callbacks.error));
|
||||
mockProcess.setup(p => p.removeListener(TypeMoq.It.isAny(), TypeMoq.It.isAny()));
|
||||
mockProcess.setup(p => p.addListener(TypeMoq.It.isAny(), TypeMoq.It.isAny()));
|
||||
return mockProcess;
|
||||
}
|
||||
|
||||
function runIfExists(listener: any, callback: Function, delay: number = 5): stream.Readable {
|
||||
setTimeout(() => {
|
||||
if (callback) {
|
||||
callback(listener);
|
||||
}
|
||||
}, delay);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
interface IProcessCallbacks {
|
||||
sdtout?: Function;
|
||||
stderr?: Function;
|
||||
exit?: Function;
|
||||
error?: Function;
|
||||
}
|
||||
|
||||
class ChildProcessStub {
|
||||
public get stdout(): stream.Readable {
|
||||
return undefined;
|
||||
}
|
||||
public get stderr(): stream.Readable {
|
||||
return undefined;
|
||||
}
|
||||
// tslint:disable-next-line:typedef
|
||||
on(event: any, listener: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addListener(event: string, listener: Function): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
removeListener(event: string, listener: Function): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
123
extensions/notebook/src/test/model/serverManager.test.ts
Normal file
123
extensions/notebook/src/test/model/serverManager.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 assert from 'assert';
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { JupyterServerInstanceStub } from '../common';
|
||||
import { LocalJupyterServerManager, ServerInstanceFactory } from '../../jupyter/jupyterServerManager';
|
||||
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
||||
import { Deferred } from '../../common/promise';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as testUtils from '../common/testUtils';
|
||||
import { IServerInstance } from '../../jupyter/common';
|
||||
import { MockExtensionContext } from '../common/stubs';
|
||||
|
||||
describe('Local Jupyter Server Manager', function (): void {
|
||||
let expectedPath = 'my/notebook.ipynb';
|
||||
let serverManager: LocalJupyterServerManager;
|
||||
let deferredInstall: Deferred<JupyterServerInstallation>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockExtensionContext: MockExtensionContext;
|
||||
let mockFactory: TypeMoq.IMock<ServerInstanceFactory>;
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = new MockExtensionContext();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny()));
|
||||
mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined);
|
||||
mockFactory = TypeMoq.Mock.ofType(ServerInstanceFactory);
|
||||
deferredInstall = new Deferred<JupyterServerInstallation>();
|
||||
serverManager = new LocalJupyterServerManager({
|
||||
documentPath: expectedPath,
|
||||
jupyterInstallation: deferredInstall.promise,
|
||||
extensionContext: mockExtensionContext,
|
||||
apiWrapper: mockApiWrapper.object,
|
||||
factory: mockFactory.object
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not be started initially', function (): void {
|
||||
should(serverManager.isStarted).be.false();
|
||||
should(serverManager.serverSettings).be.undefined();
|
||||
});
|
||||
|
||||
it('Should show error message on install failure', async function (): Promise<void> {
|
||||
let error = 'Error!!';
|
||||
deferredInstall.reject(error);
|
||||
await testUtils.assertThrowsAsync(() => serverManager.startServer(), undefined);
|
||||
mockApiWrapper.verify(a => a.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('Should configure and start install', async function (): Promise<void> {
|
||||
// Given an install and instance that start with no issues
|
||||
let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk');
|
||||
let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri);
|
||||
deferredInstall.resolve(mockInstall.object);
|
||||
|
||||
// When I start the server
|
||||
let notified = false;
|
||||
serverManager.onServerStarted(() => notified = true);
|
||||
await serverManager.startServer();
|
||||
|
||||
// Then I expect the port to be included in settings
|
||||
should(serverManager.serverSettings.baseUrl.indexOf('1234') > -1).be.true();
|
||||
should(serverManager.serverSettings.token).equal('abcdefghijk');
|
||||
// And a notification to be sent
|
||||
should(notified).be.true();
|
||||
// And the key methods to have been called
|
||||
mockServerInstance.verify(s => s.configure(), TypeMoq.Times.once());
|
||||
mockServerInstance.verify(s => s.start(), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('Should not fail on stop if never started', async function (): Promise<void> {
|
||||
await serverManager.stopServer();
|
||||
});
|
||||
|
||||
it('Should call stop on server instance', async function (): Promise<void> {
|
||||
// Given an install and instance that start with no issues
|
||||
let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk');
|
||||
let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri);
|
||||
mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve());
|
||||
deferredInstall.resolve(mockInstall.object);
|
||||
|
||||
// When I start and then the server
|
||||
await serverManager.startServer();
|
||||
await serverManager.stopServer();
|
||||
|
||||
// Then I expect stop to have been called on the server instance
|
||||
mockServerInstance.verify(s => s.stop(), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('Should call stop when extension is disposed', async function (): Promise<void> {
|
||||
// Given an install and instance that start with no issues
|
||||
let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk');
|
||||
let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri);
|
||||
mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve());
|
||||
deferredInstall.resolve(mockInstall.object);
|
||||
|
||||
// When I start and then dispose the extension
|
||||
await serverManager.startServer();
|
||||
should(mockExtensionContext.subscriptions).have.length(1);
|
||||
mockExtensionContext.subscriptions[0].dispose();
|
||||
|
||||
// Then I expect stop to have been called on the server instance
|
||||
mockServerInstance.verify(s => s.stop(), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
function initInstallAndInstance(uri: vscode.Uri): [TypeMoq.IMock<JupyterServerInstallation>, TypeMoq.IMock<IServerInstance>] {
|
||||
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
|
||||
let mockServerInstance = TypeMoq.Mock.ofType(JupyterServerInstanceStub);
|
||||
mockFactory.setup(f => f.createInstance(TypeMoq.It.isAny())).returns(() => mockServerInstance.object);
|
||||
mockServerInstance.setup(s => s.configure()).returns(() => Promise.resolve());
|
||||
mockServerInstance.setup(s => s.start()).returns(() => Promise.resolve());
|
||||
mockServerInstance.setup(s => s.uri).returns(() => uri);
|
||||
return [mockInstall, mockServerInstance];
|
||||
}
|
||||
});
|
||||
171
extensions/notebook/src/test/model/sessionManager.test.ts
Normal file
171
extensions/notebook/src/test/model/sessionManager.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { SessionManager, Session, Kernel } from '@jupyterlab/services';
|
||||
import 'mocha';
|
||||
|
||||
import { JupyterSessionManager, JupyterSession } from '../../jupyter/jupyterSessionManager';
|
||||
import { Deferred } from '../../common/promise';
|
||||
import { SessionStub, KernelStub } from '../common';
|
||||
|
||||
describe('Jupyter Session Manager', function (): void {
|
||||
let mockJupyterManager = TypeMoq.Mock.ofType<SessionManager>();
|
||||
let sessionManager = new JupyterSessionManager();
|
||||
|
||||
it('isReady should only be true after ready promise completes', function (done): void {
|
||||
// Given
|
||||
let deferred = new Deferred<void>();
|
||||
mockJupyterManager.setup(m => m.ready).returns(() => deferred.promise);
|
||||
|
||||
// When I call before resolve I expect it'll be false
|
||||
sessionManager.setJupyterSessionManager(mockJupyterManager.object);
|
||||
should(sessionManager.isReady).be.false();
|
||||
|
||||
// When I call a after resolve, it'll be true
|
||||
deferred.resolve();
|
||||
sessionManager.ready.then(() => {
|
||||
should(sessionManager.isReady).be.true();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should passthrough the ready calls', function (done): void {
|
||||
// Given
|
||||
let deferred = new Deferred<void>();
|
||||
mockJupyterManager.setup(m => m.ready).returns(() => deferred.promise);
|
||||
|
||||
// When I wait on the ready method before completing
|
||||
sessionManager.setJupyterSessionManager(mockJupyterManager.object);
|
||||
sessionManager.ready.then(() => done());
|
||||
|
||||
// Then session manager should eventually resolve
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
it('should handle null specs', function (): void {
|
||||
mockJupyterManager.setup(m => m.specs).returns(() => undefined);
|
||||
let specs = sessionManager.specs;
|
||||
should(specs).be.undefined();
|
||||
});
|
||||
|
||||
it('should map specs to named kernels', function (): void {
|
||||
let internalSpecs: Kernel.ISpecModels = {
|
||||
default: 'mssql',
|
||||
kernelspecs: {
|
||||
'mssql': <Kernel.ISpecModel>{ language: 'sql' },
|
||||
'python': <Kernel.ISpecModel>{ language: 'python' }
|
||||
}
|
||||
};
|
||||
mockJupyterManager.setup(m => m.specs).returns(() => internalSpecs);
|
||||
let specs = sessionManager.specs;
|
||||
should(specs.defaultKernel).equal('mssql');
|
||||
should(specs.kernels).have.length(2);
|
||||
});
|
||||
|
||||
|
||||
it('Should call to startSession with correct params', async function (): Promise<void> {
|
||||
// Given a session request that will complete OK
|
||||
let sessionOptions: nb.ISessionOptions = { path: 'mypath.ipynb' };
|
||||
let expectedSessionInfo = <Session.ISession>{
|
||||
path: sessionOptions.path,
|
||||
id: 'id',
|
||||
name: 'sessionName',
|
||||
type: 'type',
|
||||
kernel: {
|
||||
name: 'name'
|
||||
}
|
||||
};
|
||||
mockJupyterManager.setup(m => m.startNew(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedSessionInfo));
|
||||
|
||||
// When I call startSession
|
||||
let session = await sessionManager.startNew(sessionOptions);
|
||||
// Then I expect the parameters passed to be correct
|
||||
should(session.path).equal(sessionOptions.path);
|
||||
should(session.canChangeKernels).be.true();
|
||||
should(session.id).equal(expectedSessionInfo.id);
|
||||
should(session.name).equal(expectedSessionInfo.name);
|
||||
should(session.type).equal(expectedSessionInfo.type);
|
||||
should(session.kernel.name).equal(expectedSessionInfo.kernel.name);
|
||||
});
|
||||
|
||||
it('Should call to shutdown with correct id', async function (): Promise<void> {
|
||||
let id = 'session1';
|
||||
mockJupyterManager.setup(m => m.shutdown(TypeMoq.It.isValue(id))).returns(() => Promise.resolve());
|
||||
mockJupyterManager.setup(m => m.isDisposed).returns(() => false);
|
||||
await sessionManager.shutdown(id);
|
||||
mockJupyterManager.verify(m => m.shutdown(TypeMoq.It.isValue(id)), TypeMoq.Times.once());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Jupyter Session', function (): void {
|
||||
let mockJupyterSession: TypeMoq.IMock<SessionStub>;
|
||||
let session: JupyterSession;
|
||||
|
||||
beforeEach(() => {
|
||||
mockJupyterSession = TypeMoq.Mock.ofType(SessionStub);
|
||||
session = new JupyterSession(mockJupyterSession.object);
|
||||
});
|
||||
|
||||
it('should always be able to change kernels', function (): void {
|
||||
should(session.canChangeKernels).be.true();
|
||||
});
|
||||
it('should pass through most properties', function (): void {
|
||||
// Given values for the passthrough properties
|
||||
mockJupyterSession.setup(s => s.id).returns(() => 'id');
|
||||
mockJupyterSession.setup(s => s.name).returns(() => 'name');
|
||||
mockJupyterSession.setup(s => s.path).returns(() => 'path');
|
||||
mockJupyterSession.setup(s => s.type).returns(() => 'type');
|
||||
mockJupyterSession.setup(s => s.status).returns(() => 'starting');
|
||||
// Should return those values when called
|
||||
should(session.id).equal('id');
|
||||
should(session.name).equal('name');
|
||||
should(session.path).equal('path');
|
||||
should(session.type).equal('type');
|
||||
should(session.status).equal('starting');
|
||||
});
|
||||
|
||||
it('should handle null kernel', function (): void {
|
||||
mockJupyterSession.setup(s => s.kernel).returns(() => undefined);
|
||||
should(session.kernel).be.undefined();
|
||||
});
|
||||
|
||||
it('should passthrough kernel', function (): void {
|
||||
// Given a kernel with an ID
|
||||
let kernelMock = TypeMoq.Mock.ofType(KernelStub);
|
||||
kernelMock.setup(k => k.id).returns(() => 'id');
|
||||
mockJupyterSession.setup(s => s.kernel).returns(() => kernelMock.object);
|
||||
|
||||
// When I get a wrapper for the kernel
|
||||
let kernel = session.kernel;
|
||||
kernel = session.kernel;
|
||||
// Then I expect it to have the ID, and only be called once
|
||||
should(kernel.id).equal('id');
|
||||
mockJupyterSession.verify(s => s.kernel, TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
it('should send name in changeKernel request', async function (): Promise<void> {
|
||||
// Given change kernel returns something
|
||||
let kernelMock = TypeMoq.Mock.ofType(KernelStub);
|
||||
kernelMock.setup(k => k.id).returns(() => 'id');
|
||||
let options: Partial<Kernel.IModel>;
|
||||
mockJupyterSession.setup(s => s.changeKernel(TypeMoq.It.isAny())).returns((opts) => {
|
||||
options = opts;
|
||||
return Promise.resolve(kernelMock.object);
|
||||
});
|
||||
|
||||
// When I call changeKernel on the wrapper
|
||||
let kernel = await session.changeKernel({
|
||||
name: 'python'
|
||||
});
|
||||
// Then I expect it to have the ID, and only be called once
|
||||
should(kernel.id).equal('id');
|
||||
should(options.name).equal('python');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user