Skip to content

Commit b21b2e1

Browse files
authored
Virtualenv locator (#13893)
* Add virtualenv locator * Tests * Use stub.resolves() instead of stub.callsFake()
1 parent 37b83be commit b21b2e1

File tree

7 files changed

+79
-0
lines changed

7 files changed

+79
-0
lines changed

src/client/pythonEnvironments/common/environmentIdentifier.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { isCondaEnvironment } from '../discovery/locators/services/condaLocator';
55
import { isVenvEnvironment } from '../discovery/locators/services/venvLocator';
6+
import { isVirtualenvEnvironment } from '../discovery/locators/services/virtualenvLocator';
67
import { isWindowsStoreEnvironment } from '../discovery/locators/services/windowsStoreLocator';
78
import { EnvironmentType } from '../info';
89

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

46+
if (await isVirtualenvEnvironment(interpreterPath)) {
47+
return EnvironmentType.VirtualEnv;
48+
}
49+
4550
// additional identifiers go here
4651

4752
return EnvironmentType.Unknown;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as fsapi from 'fs-extra';
5+
import * as path from 'path';
6+
7+
/**
8+
* Checks if the given interpreter belongs to a virtualenv based environment.
9+
* @param {string} interpreterPath: Absolute path to the python interpreter.
10+
* @returns {boolean} : Returns true if the interpreter belongs to a virtualenv environment.
11+
*/
12+
export async function isVirtualenvEnvironment(interpreterPath:string): Promise<boolean> {
13+
// Check if there are any activate.* files in the same directory as the interpreter.
14+
//
15+
// env
16+
// |__ activate, activate.* <--- check if any of these files exist
17+
// |__ python <--- interpreterPath
18+
const directory = path.dirname(interpreterPath);
19+
const files = await fsapi.readdir(directory);
20+
const regex = /^activate(\.([A-z]|\d)+)?$/;
21+
22+
return files.find((file) => regex.test(file)) !== undefined;
23+
}

src/test/pythonEnvironments/common/environmentIdentifier.unit.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,21 @@ suite('Environment Identifier', () => {
109109
assert.deepEqual(envType, EnvironmentType.Venv);
110110
});
111111
});
112+
113+
suite('Virtualenv', () => {
114+
const activateFiles = [
115+
{ folder: 'virtualenv1', file: 'activate' },
116+
{ folder: 'virtualenv2', file: 'activate.sh' },
117+
{ folder: 'virtualenv3', file: 'activate.ps1' },
118+
];
119+
120+
activateFiles.forEach(({ folder, file }) => {
121+
test(`Folder contains ${file}`, async () => {
122+
const interpreterPath = path.join(TEST_LAYOUT_ROOT, folder, 'bin', 'python');
123+
const envType = await identifyEnvironment(interpreterPath);
124+
125+
assert.deepStrictEqual(envType, EnvironmentType.VirtualEnv);
126+
});
127+
});
128+
});
112129
});

src/test/pythonEnvironments/common/envlayouts/virtualenv1/bin/activate

Whitespace-only changes.

src/test/pythonEnvironments/common/envlayouts/virtualenv2/bin/activate.sh

Whitespace-only changes.

src/test/pythonEnvironments/common/envlayouts/virtualenv3/bin/activate.ps1

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as assert from 'assert';
5+
import * as fsapi from 'fs-extra';
6+
import * as path from 'path';
7+
import * as sinon from 'sinon';
8+
import { isVirtualenvEnvironment } from '../../../../client/pythonEnvironments/discovery/locators/services/virtualenvLocator';
9+
10+
suite('Virtualenv Locator Tests', () => {
11+
const envRoot = path.join('path', 'to', 'env');
12+
const interpreter = path.join(envRoot, 'python');
13+
let readDirStub: sinon.SinonStub;
14+
15+
setup(() => {
16+
readDirStub = sinon.stub(fsapi, 'readdir');
17+
});
18+
19+
teardown(() => {
20+
readDirStub.restore();
21+
});
22+
23+
test('Interpreter folder contains an activate file', async () => {
24+
readDirStub.resolves(['activate', 'python']);
25+
26+
assert.ok(await isVirtualenvEnvironment(interpreter));
27+
});
28+
29+
test('Interpreter folder does not contain any activate.* files', async () => {
30+
readDirStub.resolves(['mymodule', 'python']);
31+
32+
assert.strictEqual(await isVirtualenvEnvironment(interpreter), false);
33+
});
34+
});

0 commit comments

Comments
 (0)