Move SQL 2019 extension's notebook code into Azure Data Studio (#4090)

This commit is contained in:
Cory Rivera
2019-02-20 10:55:49 -08:00
committed by GitHub
parent 2dd71cbe26
commit 70838c3e24
66 changed files with 8098 additions and 14 deletions

View 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

View 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));
});
});

View 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);
}
});
});

View 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.');
}
}

View 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);
}
}

View 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;

View 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();
});
});

View 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');
}
});

View 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.');
}
}

View 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];
}
});

View 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');
});
});