From e9eeb80e5b8b653e871d3ed85e58f56d40d5379c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 21 Feb 2018 20:53:20 -0800 Subject: [PATCH] :white_check_mark: tests to ensure resource is passed into module installer (#855) Fixes #690 --- src/client/common/installer/installer.ts | 6 +- .../common/installer/productInstaller.ts | 6 +- src/client/common/installer/types.ts | 2 +- src/test/common/installer/installer.test.ts | 103 ++++++++++++++++++ src/test/mocks/moduleInstaller.ts | 3 +- 5 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/test/common/installer/installer.test.ts diff --git a/src/client/common/installer/installer.ts b/src/client/common/installer/installer.ts index 473305f59bc3..f01113fa3ab0 100644 --- a/src/client/common/installer/installer.ts +++ b/src/client/common/installer/installer.ts @@ -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'; @@ -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'; @@ -144,7 +144,7 @@ export class Installer implements IInstaller { const moduleName = this.translateProductToModuleName(product, ModuleNamePurpose.install); const logger = this.serviceContainer.get(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) diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index d54c7a95ae31..1d6f34637ea8 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -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'; @@ -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'; @@ -53,7 +53,7 @@ abstract class BaseInstaller { const moduleName = translateProductToModule(product, ModuleNamePurpose.install); const logger = this.serviceContainer.get(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) diff --git a/src/client/common/installer/types.ts b/src/client/common/installer/types.ts index 7058dbe1c463..9ac61714511a 100644 --- a/src/client/common/installer/types.ts +++ b/src/client/common/installer/types.ts @@ -7,7 +7,7 @@ import { Product } from '../types'; export const IModuleInstaller = Symbol('IModuleInstaller'); export interface IModuleInstaller { readonly displayName: string; - installModule(name: string): Promise; + installModule(name: string, resource?: Uri): Promise; isSupported(resource?: Uri): Promise; } diff --git a/src/test/common/installer/installer.test.ts b/src/test/common/installer/installer.test.ts new file mode 100644 index 000000000000..2d080f672983 --- /dev/null +++ b/src/test/common/installer/installer.test.ts @@ -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).forEach(product => { + let disposables: Disposable[] = []; + let installer: Installer; + let installationChannel: TypeMoq.IMock; + let moduleInstaller: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let productInstallerFactory: ProductInstaller; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + const outputChannel = TypeMoq.Mock.ofType(); + + 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(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); + + moduleInstaller = TypeMoq.Mock.ofType(); + // 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(); + 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(); + 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()); + } + }); + } + } + }); + }); +}); diff --git a/src/test/mocks/moduleInstaller.ts b/src/test/mocks/moduleInstaller.ts index 998a719ed9af..e6ab25ac4f57 100644 --- a/src/test/mocks/moduleInstaller.ts +++ b/src/test/mocks/moduleInstaller.ts @@ -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 { + public async installModule(name: string, resource?: Uri): Promise { this.emit('installModule', name); } public async isSupported(resource?: Uri): Promise {