Skip to content

Commit

Permalink
✅ tests to ensure resource is passed into module installer (#855)
Browse files Browse the repository at this point in the history
Fixes #690
  • Loading branch information
DonJayamanne authored Feb 22, 2018
1 parent 59f428c commit e9eeb80
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 9 deletions.
6 changes: 3 additions & 3 deletions src/client/common/installer/installer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inject, injectable, named } from 'inversify';
import * as os from 'os';
import * as path from 'path';
import { ConfigurationTarget, QuickPickItem, Uri, window, workspace } from 'vscode';
import { ConfigurationTarget, Uri, window, workspace } from 'vscode';
import * as vscode from 'vscode';
import { IFormatterHelper } from '../../formatters/types';
import { IServiceContainer } from '../../ioc/types';
Expand All @@ -13,7 +13,7 @@ import { IPlatformService } from '../platform/types';
import { IProcessService, IPythonExecutionFactory } from '../process/types';
import { ITerminalServiceFactory } from '../terminal/types';
import { IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product } from '../types';
import { IInstallationChannelManager, IModuleInstaller } from './types';
import { IInstallationChannelManager } from './types';

export { Product } from '../types';

Expand Down Expand Up @@ -144,7 +144,7 @@ export class Installer implements IInstaller {

const moduleName = this.translateProductToModuleName(product, ModuleNamePurpose.install);
const logger = this.serviceContainer.get<ILogger>(ILogger);
await installer.installModule(moduleName)
await installer.installModule(moduleName, resource)
.catch(logger.logError.bind(logger, `Error in installing the module '${moduleName}'`));

return this.isInstalled(product)
Expand Down
6 changes: 3 additions & 3 deletions src/client/common/installer/productInstaller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inject, injectable, named } from 'inversify';
import * as os from 'os';
import * as path from 'path';
import { OutputChannel, QuickPickItem, Uri, window } from 'vscode';
import { OutputChannel, Uri } from 'vscode';
import * as vscode from 'vscode';
import { IFormatterHelper } from '../../formatters/types';
import { IServiceContainer } from '../../ioc/types';
Expand All @@ -14,7 +14,7 @@ import { IProcessService, IPythonExecutionFactory } from '../process/types';
import { ITerminalServiceFactory } from '../terminal/types';
import { IConfigurationService, IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product } from '../types';
import { ProductNames } from './productNames';
import { IInstallationChannelManager, IModuleInstaller } from './types';
import { IInstallationChannelManager } from './types';

export { Product } from '../types';

Expand Down Expand Up @@ -53,7 +53,7 @@ abstract class BaseInstaller {

const moduleName = translateProductToModule(product, ModuleNamePurpose.install);
const logger = this.serviceContainer.get<ILogger>(ILogger);
await installer.installModule(moduleName)
await installer.installModule(moduleName, resource)
.catch(logger.logError.bind(logger, `Error in installing the module '${moduleName}'`));

return this.isInstalled(product)
Expand Down
2 changes: 1 addition & 1 deletion src/client/common/installer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Product } from '../types';
export const IModuleInstaller = Symbol('IModuleInstaller');
export interface IModuleInstaller {
readonly displayName: string;
installModule(name: string): Promise<void>;
installModule(name: string, resource?: Uri): Promise<void>;
isSupported(resource?: Uri): Promise<boolean>;
}

Expand Down
103 changes: 103 additions & 0 deletions src/test/common/installer/installer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as TypeMoq from 'typemoq';
import { Disposable, OutputChannel, Uri } from 'vscode';
import { EnumEx } from '../../../client/common/enumUtils';
import { Installer } from '../../../client/common/installer/installer';
import { ProductInstaller } from '../../../client/common/installer/productInstaller';
import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types';
import { IDisposableRegistry, ILogger, InstallerResponse, ModuleNamePurpose, Product } from '../../../client/common/types';
import { IServiceContainer } from '../../../client/ioc/types';

use(chaiAsPromised);

// tslint:disable-next-line:max-func-body-length
suite('Module Installerx', () => {
[undefined, Uri.file('resource')].forEach(resource => {
EnumEx.getNamesAndValues<Product>(Product).forEach(product => {
let disposables: Disposable[] = [];
let installer: Installer;
let installationChannel: TypeMoq.IMock<IInstallationChannelManager>;
let moduleInstaller: TypeMoq.IMock<IModuleInstaller>;
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
let productInstallerFactory: ProductInstaller;
setup(() => {
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
const outputChannel = TypeMoq.Mock.ofType<OutputChannel>();

installer = new Installer(serviceContainer.object, outputChannel.object);

disposables = [];
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables);

installationChannel = TypeMoq.Mock.ofType<IInstallationChannelManager>();
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object);

moduleInstaller = TypeMoq.Mock.ofType<IModuleInstaller>();
// tslint:disable-next-line:no-any
moduleInstaller.setup((x: any) => x.then).returns(() => undefined);
installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object));
installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object));

productInstallerFactory = new ProductInstaller(serviceContainer.object, outputChannel.object);
});
teardown(() => {
disposables.forEach(disposable => {
if (disposable) {
disposable.dispose();
}
});
});

switch (product.value) {
case Product.isort:
case Product.ctags: {
return;
}
case Product.unittest: {
test(`Ensure resource info is passed into the module installer ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => {
const response = await installer.install(product.value, resource);
expect(response).to.be.equal(InstallerResponse.Installed);
});
test(`Ensure resource info is passed into the module installer (created using ProductInstaller) ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => {
const response = await installer.install(product.value, resource);
expect(response).to.be.equal(InstallerResponse.Installed);
});
}
default: {
test(`Ensure resource info is passed into the module installer ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => {
const moduleName = installer.translateProductToModuleName(product.value, ModuleNamePurpose.install);
const logger = TypeMoq.Mock.ofType<ILogger>();
logger.setup(l => l.logError(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => new Error('UnitTesting'));
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger), TypeMoq.It.isAny())).returns(() => logger.object);

moduleInstaller.setup(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource))).returns(() => Promise.reject(new Error('UnitTesting')));

try {
await installer.install(product.value, resource);
} catch (ex) {
moduleInstaller.verify(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource)), TypeMoq.Times.once());
}
});
test(`Ensure resource info is passed into the module installer (created using ProductInstaller) ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => {
const moduleName = installer.translateProductToModuleName(product.value, ModuleNamePurpose.install);
const logger = TypeMoq.Mock.ofType<ILogger>();
logger.setup(l => l.logError(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => new Error('UnitTesting'));
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger), TypeMoq.It.isAny())).returns(() => logger.object);

moduleInstaller.setup(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource))).returns(() => Promise.reject(new Error('UnitTesting')));

try {
await productInstallerFactory.install(product.value, resource);
} catch (ex) {
moduleInstaller.verify(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource)), TypeMoq.Times.once());
}
});
}
}
});
});
});
3 changes: 1 addition & 2 deletions src/test/mocks/moduleInstaller.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { EventEmitter } from 'events';
import { Uri } from 'vscode';
import { createDeferred, Deferred } from '../../client/common/helpers';
import { IModuleInstaller } from '../../client/common/installer/types';

export class MockModuleInstaller extends EventEmitter implements IModuleInstaller {
constructor(public readonly displayName: string, private supported: boolean) {
super();
}
public async installModule(name: string): Promise<void> {
public async installModule(name: string, resource?: Uri): Promise<void> {
this.emit('installModule', name);
}
public async isSupported(resource?: Uri): Promise<boolean> {
Expand Down

0 comments on commit e9eeb80

Please sign in to comment.