Skip to content

Capture and display errors returned by pipenv command #1430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/1254.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dislay errors returned by the PipEnv command when identifying the corresonding environment.
1 change: 1 addition & 0 deletions news/3 Code Health/1428.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensure custom environment variables defined in `.env` file are passed onto the `pipenv` command.
19 changes: 15 additions & 4 deletions src/client/interpreter/locators/services/pipEnvService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IApplicationShell, IWorkspaceService } from '../../../common/applicatio
import { IFileSystem } from '../../../common/platform/types';
import { IProcessService } from '../../../common/process/types';
import { ICurrentProcess } from '../../../common/types';
import { IEnvironmentVariablesProvider } from '../../../common/variables/types';
import { getPythonExecutable } from '../../../debugger/Common/Utils';
import { IServiceContainer } from '../../../ioc/types';
import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts';
Expand All @@ -22,13 +23,15 @@ export class PipEnvService extends CacheableLocatorService {
private readonly process: IProcessService;
private readonly workspace: IWorkspaceService;
private readonly fs: IFileSystem;
private readonly envVarsProvider: IEnvironmentVariablesProvider;

constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super('PipEnvService', serviceContainer);
this.versionService = this.serviceContainer.get<IInterpreterVersionService>(IInterpreterVersionService);
this.process = this.serviceContainer.get<IProcessService>(IProcessService);
this.workspace = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
this.envVarsProvider = this.serviceContainer.get<IEnvironmentVariablesProvider>(IEnvironmentVariablesProvider);
}
// tslint:disable-next-line:no-empty
public dispose() { }
Expand Down Expand Up @@ -91,14 +94,22 @@ export class PipEnvService extends CacheableLocatorService {

private async invokePipenv(arg: string, rootPath: string): Promise<string | undefined> {
try {
const result = await this.process.exec(execName, [arg], { cwd: rootPath });
if (result && result.stdout) {
return result.stdout.trim();
const env = await this.envVarsProvider.getEnvironmentVariables(Uri.file(rootPath));
const result = await this.process.exec(execName, [arg], { cwd: rootPath, env });
if (result) {
const stdout = result.stdout ? result.stdout.trim() : '';
const stderr = result.stderr ? result.stderr.trim() : '';
if (stderr.length > 0 && stdout.length === 0) {
throw new Error(stderr);
}
return stdout;
}
// tslint:disable-next-line:no-empty
} catch (error) {
console.error(error);
const errorMessage = error.message || error;
const appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with ${error}. Make sure pipenv is on the PATH.`);
appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with ${errorMessage}. Make sure pipenv is on the PATH.`);
}
}
}
28 changes: 26 additions & 2 deletions src/test/interpreters/pipEnvService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

'use strict';

// tslint:disable:max-func-body-length no-any

import { expect } from 'chai';
import * as path from 'path';
import * as TypeMoq from 'typemoq';
Expand All @@ -12,6 +14,7 @@ import { EnumEx } from '../../client/common/enumUtils';
import { IFileSystem } from '../../client/common/platform/types';
import { IProcessService } from '../../client/common/process/types';
import { ICurrentProcess, IPersistentState, IPersistentStateFactory } from '../../client/common/types';
import { IEnvironmentVariablesProvider } from '../../client/common/variables/types';
import { IInterpreterLocatorService, IInterpreterVersionService } from '../../client/interpreter/contracts';
import { PipEnvService } from '../../client/interpreter/locators/services/pipEnvService';
import { IServiceContainer } from '../../client/ioc/types';
Expand All @@ -20,7 +23,6 @@ enum OS {
Mac, Windows, Linux
}

// tslint:disable-next-line:max-func-body-length
suite('Interpreters - PipEnv', () => {
const rootWorkspace = Uri.file(path.join('usr', 'desktop', 'wkspc1')).fsPath;
EnumEx.getNamesAndValues(OS).forEach(os => {
Expand All @@ -35,6 +37,7 @@ suite('Interpreters - PipEnv', () => {
let fileSystem: TypeMoq.IMock<IFileSystem>;
let appShell: TypeMoq.IMock<IApplicationShell>;
let persistentStateFactory: TypeMoq.IMock<IPersistentStateFactory>;
let envVarsProvider: TypeMoq.IMock<IEnvironmentVariablesProvider>;
setup(() => {
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
const workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
Expand All @@ -44,6 +47,7 @@ suite('Interpreters - PipEnv', () => {
appShell = TypeMoq.Mock.ofType<IApplicationShell>();
currentProcess = TypeMoq.Mock.ofType<ICurrentProcess>();
persistentStateFactory = TypeMoq.Mock.ofType<IPersistentStateFactory>();
envVarsProvider = TypeMoq.Mock.ofType<IEnvironmentVariablesProvider>();

// tslint:disable-next-line:no-any
const persistentState = TypeMoq.Mock.ofType<IPersistentState<any>>();
Expand All @@ -64,6 +68,7 @@ suite('Interpreters - PipEnv', () => {
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object);
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object);
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => persistentStateFactory.object);
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object);

pipEnvService = new PipEnvService(serviceContainer.object);
});
Expand All @@ -74,14 +79,15 @@ suite('Interpreters - PipEnv', () => {
});
test(`Should return an empty list if there is a \'PipFile\'${testSuffix}`, async () => {
const env = {};
envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once());
currentProcess.setup(c => c.env).returns(() => env);
fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once());
const environments = await pipEnvService.getInterpreters(resource);

expect(environments).to.be.deep.equal([]);
fileSystem.verifyAll();
});
test(`Should display wanring message if there is a \'PipFile\' but \'pipenv --venv\' failes ${testSuffix}`, async () => {
test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes ${testSuffix}`, async () => {
const env = {};
currentProcess.setup(c => c.env).returns(() => env);
processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject(''));
Expand All @@ -91,10 +97,25 @@ suite('Interpreters - PipEnv', () => {

expect(environments).to.be.deep.equal([]);
appShell.verifyAll();
appShell.verifyAll();
});
test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes with stderr ${testSuffix}`, async () => {
const env = {};
envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once());
currentProcess.setup(c => c.env).returns(() => env);
processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' }));
fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true));
appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once());
const environments = await pipEnvService.getInterpreters(resource);

expect(environments).to.be.deep.equal([]);
envVarsProvider.verifyAll();
appShell.verifyAll();
});
test(`Should return interpreter information${testSuffix}`, async () => {
const env = {};
const venvDir = 'one';
envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once());
currentProcess.setup(c => c.env).returns(() => env);
processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir }));
interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz'));
Expand All @@ -104,13 +125,15 @@ suite('Interpreters - PipEnv', () => {

expect(environments).to.be.lengthOf(1);
fileSystem.verifyAll();
envVarsProvider.verifyAll();
});
test(`Should return interpreter information using PipFile defined in Env variable${testSuffix}`, async () => {
const envPipFile = 'XYZ';
const env = {
PIPENV_PIPFILE: envPipFile
};
const venvDir = 'one';
envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once());
currentProcess.setup(c => c.env).returns(() => env);
processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir }));
interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz'));
Expand All @@ -121,6 +144,7 @@ suite('Interpreters - PipEnv', () => {

expect(environments).to.be.lengthOf(1);
fileSystem.verifyAll();
envVarsProvider.verifyAll();
});
});
});
Expand Down