mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Update Configure Python dialog to allow packages to be installed for specific kernels. (#10286)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { IServerInstance } from '../jupyter/common';
|
||||
@@ -255,3 +256,160 @@ export class FutureStub implements Kernel.IFuture {
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region test modelView components
|
||||
class TestComponentBase implements azdata.Component {
|
||||
id: string = '';
|
||||
updateProperties(properties: { [key: string]: any; }): Thenable<void> {
|
||||
Object.assign(this, properties);
|
||||
return Promise.resolve();
|
||||
}
|
||||
updateProperty(key: string, value: any): Thenable<void> {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
updateCssStyles(cssStyles: { [key: string]: string; }): Thenable<void> {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
onValidityChanged: vscode.Event<boolean> = undefined;
|
||||
valid: boolean = true;
|
||||
validate(): Thenable<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
focus(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
class TestDropdownComponent extends TestComponentBase implements azdata.DropDownComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onValueChanged: vscode.Event<any> = this.onClick.event;
|
||||
}
|
||||
|
||||
class TestDeclarativeTableComponent extends TestComponentBase implements azdata.DeclarativeTableComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onDataChanged: vscode.Event<any> = this.onClick.event;
|
||||
data: any[][];
|
||||
columns: azdata.DeclarativeTableColumn[];
|
||||
}
|
||||
|
||||
class TestButtonComponent extends TestComponentBase implements azdata.ButtonComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onDidClick: vscode.Event<any> = this.onClick.event;
|
||||
}
|
||||
|
||||
class TestRadioButtonComponent extends TestComponentBase implements azdata.RadioButtonComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onDidClick: vscode.Event<any> = this.onClick.event;
|
||||
}
|
||||
|
||||
class TestTextComponent extends TestComponentBase implements azdata.TextComponent {
|
||||
}
|
||||
|
||||
class TestLoadingComponent extends TestComponentBase implements azdata.LoadingComponent {
|
||||
loading: boolean;
|
||||
component: azdata.Component;
|
||||
}
|
||||
|
||||
class TestFormContainer extends TestComponentBase implements azdata.FormContainer {
|
||||
items: azdata.Component[] = [];
|
||||
clearItems(): void {
|
||||
}
|
||||
addItems(itemConfigs: azdata.Component[], itemLayout?: azdata.FormItemLayout): void {
|
||||
}
|
||||
addItem(component: azdata.Component, itemLayout?: azdata.FormItemLayout): void {
|
||||
}
|
||||
insertItem(component: azdata.Component, index: number, itemLayout?: azdata.FormItemLayout): void {
|
||||
}
|
||||
removeItem(component: azdata.Component): boolean {
|
||||
return true;
|
||||
}
|
||||
setLayout(layout: azdata.FormLayout): void {
|
||||
}
|
||||
setItemLayout(component: azdata.Component, layout: azdata.FormItemLayout): void {
|
||||
}
|
||||
}
|
||||
|
||||
class TestComponentBuilder<T extends azdata.Component> implements azdata.ComponentBuilder<T> {
|
||||
constructor(private _component: T) {
|
||||
}
|
||||
component(): T {
|
||||
return this._component;
|
||||
}
|
||||
withProperties<U>(properties: U): azdata.ComponentBuilder<T> {
|
||||
this._component.updateProperties(properties);
|
||||
return this;
|
||||
}
|
||||
withValidation(validation: (component: T) => boolean): azdata.ComponentBuilder<T> {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class TestLoadingBuilder extends TestComponentBuilder<azdata.LoadingComponent> implements azdata.LoadingComponentBuilder {
|
||||
withItem(component: azdata.Component): azdata.LoadingComponentBuilder {
|
||||
this.component().component = component;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function createViewContext(): TestContext {
|
||||
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
|
||||
let form: azdata.FormContainer = new TestFormContainer();
|
||||
let textBuilder: azdata.ComponentBuilder<azdata.TextComponent> = new TestComponentBuilder(new TestTextComponent());
|
||||
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = new TestComponentBuilder(new TestButtonComponent(onClick));
|
||||
let radioButtonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = new TestComponentBuilder(new TestRadioButtonComponent(onClick));
|
||||
let declarativeTableBuilder: azdata.ComponentBuilder<azdata.DeclarativeTableComponent> = new TestComponentBuilder(new TestDeclarativeTableComponent(onClick));
|
||||
let loadingBuilder: azdata.LoadingComponentBuilder = new TestLoadingBuilder(new TestLoadingComponent());
|
||||
let dropdownBuilder: azdata.ComponentBuilder<azdata.DropDownComponent> = new TestComponentBuilder(new TestDropdownComponent(onClick));
|
||||
|
||||
let formBuilder: azdata.FormBuilder = Object.assign({}, {
|
||||
component: () => form,
|
||||
addFormItem: () => { },
|
||||
insertFormItem: () => { },
|
||||
removeFormItem: () => true,
|
||||
addFormItems: () => { },
|
||||
withFormItems: () => formBuilder,
|
||||
withProperties: () => formBuilder,
|
||||
withValidation: () => formBuilder,
|
||||
withItems: () => formBuilder,
|
||||
withLayout: () => formBuilder
|
||||
});
|
||||
|
||||
let view: azdata.ModelView = {
|
||||
onClosed: undefined!,
|
||||
connection: undefined!,
|
||||
serverInfo: undefined!,
|
||||
valid: true,
|
||||
onValidityChanged: undefined!,
|
||||
validate: undefined!,
|
||||
initializeModel: () => { return Promise.resolve(); },
|
||||
modelBuilder: <azdata.ModelBuilder>{
|
||||
radioButton: () => radioButtonBuilder,
|
||||
text: () => textBuilder,
|
||||
button: () => buttonBuilder,
|
||||
dropDown: () => dropdownBuilder,
|
||||
declarativeTable: () => declarativeTableBuilder,
|
||||
formContainer: () => formBuilder,
|
||||
loadingComponent: () => loadingBuilder
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
view: view,
|
||||
onClick: onClick,
|
||||
};
|
||||
}
|
||||
|
||||
export interface TestContext {
|
||||
view: azdata.ModelView;
|
||||
onClick: vscode.EventEmitter<any>;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
133
extensions/notebook/src/test/configurePython.test.ts
Normal file
133
extensions/notebook/src/test/configurePython.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import { ConfigurePythonWizard, ConfigurePythonModel } from '../dialog/configurePython/configurePythonWizard';
|
||||
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
|
||||
import { ConfigurePathPage } from '../dialog/configurePython/configurePathPage';
|
||||
import * as should from 'should';
|
||||
import { PickPackagesPage } from '../dialog/configurePython/pickPackagesPage';
|
||||
import { python3DisplayName, allKernelsName } from '../common/constants';
|
||||
import { TestContext, createViewContext } from './common';
|
||||
|
||||
describe('Configure Python Wizard', function () {
|
||||
let apiWrapper: ApiWrapper = new ApiWrapper();
|
||||
let testWizard: ConfigurePythonWizard;
|
||||
let viewContext: TestContext;
|
||||
let testInstallation: JupyterServerInstallation;
|
||||
|
||||
beforeEach(() => {
|
||||
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation);
|
||||
mockInstall.setup(i => i.getInstalledPipPackages(TypeMoq.It.isAnyString())).returns(() => Promise.resolve([]));
|
||||
testInstallation = mockInstall.object;
|
||||
|
||||
let mockWizard = TypeMoq.Mock.ofType(ConfigurePythonWizard);
|
||||
mockWizard.setup(w => w.showErrorMessage(TypeMoq.It.isAnyString()));
|
||||
testWizard = mockWizard.object;
|
||||
|
||||
viewContext = createViewContext();
|
||||
});
|
||||
|
||||
// These wizard tests are disabled due to errors with disposable objects
|
||||
//
|
||||
// it('Start wizard test', async () => {
|
||||
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
|
||||
// await wizard.start();
|
||||
// await wizard.close();
|
||||
// await should(wizard.setupComplete).be.resolved();
|
||||
// });
|
||||
|
||||
// it('Reject setup on cancel test', async () => {
|
||||
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
|
||||
// await wizard.start(undefined, true);
|
||||
// await wizard.close();
|
||||
// await should(wizard.setupComplete).be.rejected();
|
||||
// });
|
||||
|
||||
// it('Error message test', async () => {
|
||||
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
|
||||
// await wizard.start();
|
||||
|
||||
// should(wizard.wizard.message).be.undefined();
|
||||
|
||||
// let testMsg = 'Test message';
|
||||
// wizard.showErrorMessage(testMsg);
|
||||
// should(wizard.wizard.message.text).be.equal(testMsg);
|
||||
// should(wizard.wizard.message.level).be.equal(azdata.window.MessageLevel.Error);
|
||||
|
||||
// wizard.clearStatusMessage();
|
||||
// should(wizard.wizard.message).be.undefined();
|
||||
|
||||
// await wizard.close();
|
||||
// });
|
||||
|
||||
it('Configure Path Page test', async () => {
|
||||
let testPythonLocation = '/not/a/real/path';
|
||||
let model = <ConfigurePythonModel>{
|
||||
useExistingPython: true,
|
||||
pythonPathsPromise: Promise.resolve([{
|
||||
installDir: testPythonLocation,
|
||||
version: '4000'
|
||||
}])
|
||||
};
|
||||
|
||||
let page = azdata.window.createWizardPage('Page 1');
|
||||
let configurePathPage = new ConfigurePathPage(apiWrapper, testWizard, page, model, viewContext.view);
|
||||
|
||||
should(await configurePathPage.initialize()).be.true();
|
||||
|
||||
// First page, so onPageEnter should do nothing
|
||||
await should(configurePathPage.onPageEnter()).be.resolved();
|
||||
|
||||
should(await configurePathPage.onPageLeave()).be.true();
|
||||
should(model.useExistingPython).be.true();
|
||||
should(model.pythonLocation).be.equal(testPythonLocation);
|
||||
});
|
||||
|
||||
it('Pick Packages Page test', async () => {
|
||||
let model = <ConfigurePythonModel>{
|
||||
kernelName: allKernelsName,
|
||||
installation: testInstallation,
|
||||
pythonLocation: '/not/a/real/path',
|
||||
useExistingPython: true
|
||||
};
|
||||
|
||||
let page = azdata.window.createWizardPage('Page 2');
|
||||
let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view);
|
||||
|
||||
should(await pickPackagesPage.initialize()).be.true();
|
||||
|
||||
should((<any>pickPackagesPage).kernelLabel).not.be.undefined();
|
||||
should((<any>pickPackagesPage).kernelDropdown).be.undefined();
|
||||
|
||||
// Last page, so onPageLeave should do nothing
|
||||
should(await pickPackagesPage.onPageLeave()).be.true();
|
||||
|
||||
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
||||
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(allKernelsName));
|
||||
});
|
||||
|
||||
it('Undefined kernel test', async () => {
|
||||
let model = <ConfigurePythonModel>{
|
||||
kernelName: undefined,
|
||||
installation: testInstallation,
|
||||
pythonLocation: '/not/a/real/path',
|
||||
useExistingPython: true
|
||||
};
|
||||
|
||||
let page = azdata.window.createWizardPage('Page 2');
|
||||
let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view);
|
||||
|
||||
should(await pickPackagesPage.initialize()).be.true();
|
||||
|
||||
should((<any>pickPackagesPage).kernelLabel).be.undefined();
|
||||
should((<any>pickPackagesPage).kernelDropdown).not.be.undefined();
|
||||
|
||||
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
||||
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(python3DisplayName));
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import * as azdata from 'azdata';
|
||||
import * as should from 'should';
|
||||
import 'mocha';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||
import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation, PythonInstallSettings } from '../../jupyter/jupyterServerInstallation';
|
||||
import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider';
|
||||
import * as constants from '../../common/constants';
|
||||
import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider';
|
||||
@@ -188,7 +188,7 @@ describe('Manage Package Providers', () => {
|
||||
serverInstallation: {
|
||||
installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
|
||||
configurePackagePaths: () => { return Promise.resolve(); },
|
||||
startInstallProcess: (forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }) => { return Promise.resolve(); },
|
||||
startInstallProcess: (forceInstall: boolean, installSettings?: PythonInstallSettings) => { return Promise.resolve(); },
|
||||
getInstalledPipPackages: () => { return Promise.resolve([]); },
|
||||
installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
|
||||
uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import 'mocha';
|
||||
|
||||
import { JupyterServerInstanceStub } from '../common';
|
||||
@@ -18,6 +19,10 @@ import { IServerInstance } from '../../jupyter/common';
|
||||
import { MockExtensionContext } from '../common/stubs';
|
||||
|
||||
describe('Local Jupyter Server Manager', function (): void {
|
||||
const pythonKernelSpec: azdata.nb.IKernelSpec = {
|
||||
name: 'python3',
|
||||
display_name: 'Python 3'
|
||||
};
|
||||
let expectedPath = 'my/notebook.ipynb';
|
||||
let serverManager: LocalJupyterServerManager;
|
||||
let deferredInstall: Deferred<void>;
|
||||
@@ -33,7 +38,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
|
||||
deferredInstall = new Deferred<void>();
|
||||
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
|
||||
mockInstall.setup(j => j.promptForPythonInstall()).returns(() => deferredInstall.promise);
|
||||
mockInstall.setup(j => j.promptForPythonInstall(TypeMoq.It.isAny())).returns(() => deferredInstall.promise);
|
||||
mockInstall.object.execOptions = { env: Object.assign({}, process.env) };
|
||||
|
||||
serverManager = new LocalJupyterServerManager({
|
||||
@@ -53,7 +58,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
it('Should show error message on install failure', async function (): Promise<void> {
|
||||
let error = 'Error!!';
|
||||
deferredInstall.reject(error);
|
||||
await testUtils.assertThrowsAsync(() => serverManager.startServer(), undefined);
|
||||
await testUtils.assertThrowsAsync(() => serverManager.startServer(pythonKernelSpec), undefined);
|
||||
});
|
||||
|
||||
it('Should configure and start install', async function (): Promise<void> {
|
||||
@@ -65,7 +70,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
// When I start the server
|
||||
let notified = false;
|
||||
serverManager.onServerStarted(() => notified = true);
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(pythonKernelSpec);
|
||||
|
||||
// Then I expect the port to be included in settings
|
||||
should(serverManager.serverSettings.baseUrl.indexOf('1234') > -1).be.true();
|
||||
@@ -89,7 +94,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
deferredInstall.resolve();
|
||||
|
||||
// When I start and then the server
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(pythonKernelSpec);
|
||||
await serverManager.stopServer();
|
||||
|
||||
// Then I expect stop to have been called on the server instance
|
||||
@@ -104,7 +109,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
deferredInstall.resolve();
|
||||
|
||||
// When I start and then dispose the extension
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(pythonKernelSpec);
|
||||
should(mockExtensionContext.subscriptions).have.length(1);
|
||||
mockExtensionContext.subscriptions[0].dispose();
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Jupyter Session', function (): void {
|
||||
|
||||
beforeEach(() => {
|
||||
mockJupyterSession = TypeMoq.Mock.ofType(SessionStub);
|
||||
session = new JupyterSession(mockJupyterSession.object);
|
||||
session = new JupyterSession(mockJupyterSession.object, undefined);
|
||||
});
|
||||
|
||||
it('should always be able to change kernels', function (): void {
|
||||
|
||||
Reference in New Issue
Block a user