Skip to content
5 changes: 5 additions & 0 deletions src/client/pythonEnvironments/common/environmentIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { isCondaEnvironment } from '../discovery/locators/services/condaLocator';
import { isVenvEnvironment } from '../discovery/locators/services/venvLocator';
import { isVirtualenvwrapperEnvironment } from '../discovery/locators/services/virtualenvwrapperLocator';
import { isWindowsStoreEnvironment } from '../discovery/locators/services/windowsStoreLocator';
import { EnvironmentType } from '../info';

Expand Down Expand Up @@ -42,6 +43,10 @@ export async function identifyEnvironment(interpreterPath: string): Promise<Envi
return EnvironmentType.Venv;
}

if (isVirtualenvwrapperEnvironment(interpreterPath)) {
return EnvironmentType.VirtualEnvWrapper;
}

// additional identifiers go here

return EnvironmentType.Unknown;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as path from 'path';
import {
getEnvironmentVariable, getOSType, getUserHomeDir, OSType,
} from '../../../../common/utils/platform';

export function getDefaultVirtualenvwrapperDir(): string {
const homeDir = getUserHomeDir() || '';

// In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs.
if (getOSType() === OSType.Windows) {
return path.join(homeDir, 'Envs');
}
return path.join(homeDir, '.virtualenvs');
}

/**
* Checks if the given interpreter belongs to a virtualenvWrapper based environment.
* @param {string} interpreterPath: Absolute path to the python interpreter.
* @returns {boolean} : Returns true if the interpreter belongs to a virtualenvWrapper environment.
*/
export function isVirtualenvwrapperEnvironment(interpreterPath:string): boolean {
// The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments.
// If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment.

const workonHomeFolder = getEnvironmentVariable('WORKON_HOME') || getDefaultVirtualenvwrapperDir();
Comment thread
karrtikr marked this conversation as resolved.
Resolved

return interpreterPath.startsWith(workonHomeFolder);
Comment thread
kimadeline marked this conversation as resolved.
Resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as sinon from 'sinon';
import * as platformApis from '../../../client/common/utils/platform';
import { identifyEnvironment } from '../../../client/pythonEnvironments/common/environmentIdentifier';
import { EnvironmentType } from '../../../client/pythonEnvironments/info';
import { getOSType as getOSTypeForTest, OSType } from '../../common';
import { TEST_LAYOUT_ROOT } from './commonTestConstants';

suite('Environment Identifier', () => {
Expand Down Expand Up @@ -109,4 +110,64 @@ suite('Environment Identifier', () => {
assert.deepEqual(envType, EnvironmentType.Venv);
});
});

suite('Virtualenvwrapper', () => {
const homeDir = platformApis.getUserHomeDir() || path.join('path', 'to', 'home');

let getEnvVarStub: sinon.SinonStub;
let getOsTypeStub: sinon.SinonStub;

suiteSetup(() => {
getEnvVarStub = sinon.stub(platformApis, 'getEnvironmentVariable');
getOsTypeStub = sinon.stub(platformApis, 'getOSType');
});

suiteTeardown(() => {
getEnvVarStub.restore();
getOsTypeStub.restore();
});

test('WORKON_HOME is set to its default value ~/.virtualenvs on non-Windows', async function () {
if (getOSTypeForTest() === OSType.Windows) {
// tslint:disable-next-line: no-invalid-this
return this.skip();
}

const interpreterPath = path.join(homeDir, '.virtualenvs', 'myenv', 'python');

getEnvVarStub.withArgs('WORKON_HOME').returns(undefined);

const envType = await identifyEnvironment(interpreterPath);
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
Comment thread
karrtikr marked this conversation as resolved.
Resolved

return undefined;
Comment thread
karrtikr marked this conversation as resolved.
Resolved
});

test('WORKON_HOME is set to its default value %USERPROFILE%\\Envs on Windows', async function () {
if (getOSTypeForTest() !== OSType.Windows) {
// tslint:disable-next-line: no-invalid-this
return this.skip();
}

const interpreterPath = path.join(homeDir, 'Envs', 'myenv', 'python');

getEnvVarStub.withArgs('WORKON_HOME').returns(undefined);
getOsTypeStub.returns(platformApis.OSType.Windows);

const envType = await identifyEnvironment(interpreterPath);
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);

return undefined;
});

test('WORKON_HOME is set to a custom value', async () => {
const workonHomeDir = path.join('path', 'to', 'envs');
const interpreterPath = path.join(workonHomeDir, 'myenv', 'python');

getEnvVarStub.withArgs('WORKON_HOME').returns(workonHomeDir);

const envType = await identifyEnvironment(interpreterPath);
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnvWrapper);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as assert from 'assert';
import * as path from 'path';
import * as sinon from 'sinon';
import * as platformUtils from '../../../../client/common/utils/platform';
import { getDefaultVirtualenvwrapperDir, isVirtualenvwrapperEnvironment } from '../../../../client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator';

suite('Virtualenvwrapper Locator Tests', () => {
const envDirectory = 'myenv';
const homeDir = path.join('path', 'to', 'home');

let getOsTypeStub: sinon.SinonStub;
let getEnvVariableStub: sinon.SinonStub;
let getHomeDirStub: sinon.SinonStub;

setup(() => {
getOsTypeStub = sinon.stub(platformUtils, 'getOSType');
getEnvVariableStub = sinon.stub(platformUtils, 'getEnvironmentVariable');
getHomeDirStub = sinon.stub(platformUtils, 'getUserHomeDir');

getHomeDirStub.returns(homeDir);
});

teardown(() => {
getOsTypeStub.restore();
getEnvVariableStub.restore();
getHomeDirStub.restore();
});

test('WORKON_HOME is set to a custom value, and the interpreter is is in a subfolder', () => {
const workonHomeDirectory = path.join('path', 'to', 'workonHome');
const interpreter = path.join(workonHomeDirectory, envDirectory, 'python');

getEnvVariableStub.withArgs('WORKON_HOME').returns(workonHomeDirectory);

assert.ok(isVirtualenvwrapperEnvironment(interpreter));
});

test('The interpreter is not in a subfolder of WORKON_HOME', () => {
const workonHomeDirectory = path.join('path', 'to', 'workonHome');
const interpreter = path.join('some', 'path', envDirectory, 'python');

getEnvVariableStub.withArgs('WORKON_HOME').returns(workonHomeDirectory);

assert.deepStrictEqual(isVirtualenvwrapperEnvironment(interpreter), false);
});

test('Default virtualenvwrapper directory on non-Windows should be ~/.virtualenvs', () => {
getOsTypeStub.returns(platformUtils.OSType.Linux);

const directory = getDefaultVirtualenvwrapperDir();

assert.deepStrictEqual(directory, path.join(homeDir, '.virtualenvs'));
});

test('Default virtualenvwrapper directory on Windows should be %USERPROFILE%\\Envs', () => {
getOsTypeStub.returns(platformUtils.OSType.Windows);

const directory = getDefaultVirtualenvwrapperDir();

assert.deepStrictEqual(directory, path.join(homeDir, 'Envs'));
});
});