Support Notebook integration testing by adding APIs & fixing others (#3848)

- Added `runCell` API. Updated runCell button to listen to events on the model so it'll reflect run cell when called from other sources
- Plumbed through kernelspec info to the extension side so when changed, it's updated
- Fixed bug in ConnectionProfile where it didn't copy from options but instead overrode with empty wrapper functions

Here's the rough test code (it's in the sql-vnext extension and will be out in a separate PR)
```ts

    it('Should connect to local notebook server with result 2', async function() {
        this.timeout(60000);
        let pythonNotebook = Object.assign({}, expectedNotebookContent, { metadata: { kernelspec: { name: "python3", display_name: "Python 3" }}});
        let uri = writeNotebookToFile(pythonNotebook);
        await ensureJupyterInstalled();

        let notebook = await sqlops.nb.showNotebookDocument(uri);
        should(notebook.document.cells).have.length(1);
        let ran = await notebook.runCell(notebook.document.cells[0]);
        should(ran).be.true('Notebook runCell failed');
        let cellOutputs = notebook.document.cells[0].contents.outputs;
        should(cellOutputs).have.length(1);
        let result = (<sqlops.nb.IExecuteResult>cellOutputs[0]).data['text/plain'];
        should(result).equal('2');

        try {
            // TODO support closing the editor. Right now this prompts and there's no override for this. Need to fix in core
            // Close the editor using the recommended vscode API
            //await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
        }
        catch (e) {}
    });

    it('Should connect to remote spark server with result 2', async function() {
        this.timeout(240000);
        let uri = writeNotebookToFile(expectedNotebookContent);
        await ensureJupyterInstalled();

        // Given a connection to a server exists
        let connectionId = await connectToSparkIntegrationServer();

        // When I open a Spark notebook and run the cell
        let notebook = await sqlops.nb.showNotebookDocument(uri, {
            connectionId: connectionId
        });
        should(notebook.document.cells).have.length(1);
        let ran = await notebook.runCell(notebook.document.cells[0]);
        should(ran).be.true('Notebook runCell failed');

        // Then I expect to get the output result of 1+1, executed remotely against the Spark endpoint
        let cellOutputs = notebook.document.cells[0].contents.outputs;
        should(cellOutputs).have.length(4);
        let sparkResult = (<sqlops.nb.IStreamResult>cellOutputs[3]).text;
        should(sparkResult).equal('2');

        try {
            // TODO support closing the editor. Right now this prompts and there's no override for this. Need to fix in core
            // Close the editor using the recommended vscode API
            //await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
        }
        catch (e) {}
    });
});
```
This commit is contained in:
Kevin Cunnane
2019-01-30 14:24:14 -08:00
committed by GitHub
parent 3ddc5e7846
commit d1fef24723
21 changed files with 321 additions and 111 deletions

View File

@@ -28,7 +28,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { disposed } from 'vs/base/common/errors';
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
import { NotebookChangeType } from 'sql/parts/notebook/models/contracts';
class MainThreadNotebookEditor extends Disposable {
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
@@ -38,6 +39,12 @@ class MainThreadNotebookEditor extends Disposable {
super();
editor.modelReady.then(model => {
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
this._register(model.kernelChanged((e) => {
let changeEvent: NotebookContentChange = {
changeType: NotebookChangeType.KernelChanged
};
this._contentChangedEmitter.fire(changeEvent);
}));
});
}
@@ -65,6 +72,10 @@ class MainThreadNotebookEditor extends Disposable {
return this.editor.cells;
}
public get model(): INotebookModel | null {
return this.editor.model;
}
public save(): Thenable<boolean> {
return this.editor.save();
}
@@ -100,6 +111,14 @@ class MainThreadNotebookEditor extends Disposable {
// }
return true;
}
public runCell(cell: ICellModel): Promise<boolean> {
if (!this.editor) {
return Promise.resolve(false);
}
return this.editor.runCell(cell);
}
}
function wait(timeMs: number): Promise<void> {
@@ -266,7 +285,6 @@ class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
@extHostNamedCustomer(SqlMainContext.MainThreadNotebookDocumentsAndEditors)
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
private _modelToDisposeMap = new Map<string, IDisposable>();
@@ -308,6 +326,22 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
}
return TPromise.as(editor.applyEdits(modelVersionId, edits, opts));
}
$runCell(id: string, cellUri: UriComponents): TPromise<boolean, any> {
// Requires an editor and the matching cell in that editor
let editor = this.getEditor(id);
if (!editor) {
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
}
let uriString = URI.revive(cellUri).toString();
let cell = editor.cells.find(c => c.cellUri.toString() === uriString);
if (!cell) {
return TPromise.wrapError<boolean>(disposed(`TextEditorCell(${uriString})`));
}
return TPromise.wrap(editor.runCell(cell));
}
//#endregion
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
@@ -480,11 +514,17 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
isDirty: e.isDirty,
providerId: editor.providerId,
providers: editor.providers,
uri: editor.uri
uri: editor.uri,
kernelSpec: this.getKernelSpec(editor)
};
return changeData;
}
private getKernelSpec(editor: MainThreadNotebookEditor): sqlops.nb.IKernelSpec {
let spec = editor && editor.model && editor.model.clientSession ? editor.model.clientSession.cachedKernelSpec : undefined;
return spec;
}
private convertCellModelToNotebookCell(cells: ICellModel | ICellModel[]): sqlops.nb.NotebookCell[] {
let notebookCells: sqlops.nb.NotebookCell[] = [];
if (Array.isArray(cells)) {
@@ -497,8 +537,8 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
metadata: {
language: cell.language
},
source: undefined
source: undefined,
outputs: [...cell.outputs]
}
});
}