Skip to content

Commit 02c500a

Browse files
Copilotkarthiknadig
andcommitted
Address PR review: revert package-lock.json, make env var injectable, use guard clause, add nativeToPythonEnv integration tests
Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com>
1 parent 6fb1e8a commit 02c500a

3 files changed

Lines changed: 135 additions & 57 deletions

File tree

package-lock.json

Lines changed: 2 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/managers/poetry/poetryUtils.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import { getShellActivationCommands, shortVersion, sortEnvironments } from '../c
2121
* Checks if the POETRY_VIRTUALENVS_IN_PROJECT environment variable is set to a truthy value.
2222
* When true, Poetry creates virtualenvs in the project's `.venv` directory.
2323
* Mirrors the PET server logic in `pet-poetry/src/env_variables.rs`.
24+
* @param envValue Optional override for the env var value (used for testing).
2425
*/
25-
export function isPoetryVirtualenvsInProject(): boolean {
26-
const value = process.env.POETRY_VIRTUALENVS_IN_PROJECT;
26+
export function isPoetryVirtualenvsInProject(envValue?: string): boolean {
27+
const value = envValue ?? process.env.POETRY_VIRTUALENVS_IN_PROJECT;
2728
if (value === undefined) {
2829
return false;
2930
}
@@ -243,7 +244,7 @@ export async function getPoetryVersion(poetry: string): Promise<string | undefin
243244
return undefined;
244245
}
245246
}
246-
async function nativeToPythonEnv(
247+
export async function nativeToPythonEnv(
247248
info: NativeEnvInfo,
248249
api: PythonEnvironmentApi,
249250
manager: EnvironmentManager,
@@ -265,11 +266,8 @@ async function nativeToPythonEnv(
265266
// Determine if the environment is in Poetry's global virtualenvs directory
266267
let isGlobalPoetryEnv = false;
267268

268-
// If POETRY_VIRTUALENVS_IN_PROJECT is set, environments are created in-project (.venv)
269-
// and should not be classified as global
270-
if (isPoetryVirtualenvsInProject() && info.project) {
271-
isGlobalPoetryEnv = false;
272-
} else {
269+
// If POETRY_VIRTUALENVS_IN_PROJECT is set and env has a project, it's an in-project env
270+
if (!isPoetryVirtualenvsInProject() || !info.project) {
273271
const virtualenvsPath = poetryVirtualenvsPath; // Use the cached value if available
274272
if (virtualenvsPath) {
275273
const normalizedVirtualenvsPath = path.normalize(virtualenvsPath);
Lines changed: 127 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,159 @@
11
import assert from 'node:assert';
2-
import { isPoetryVirtualenvsInProject } from '../../../managers/poetry/poetryUtils';
2+
import * as sinon from 'sinon';
3+
import { isPoetryVirtualenvsInProject, nativeToPythonEnv } from '../../../managers/poetry/poetryUtils';
4+
import * as utils from '../../../managers/common/utils';
5+
import { EnvironmentManager, PythonEnvironment, PythonEnvironmentApi, PythonEnvironmentInfo } from '../../../api';
6+
import { NativeEnvInfo } from '../../../managers/common/nativePythonFinder';
37

48
suite('isPoetryVirtualenvsInProject', () => {
9+
test('should return false when env var is not set', () => {
10+
assert.strictEqual(isPoetryVirtualenvsInProject(undefined), false);
11+
});
12+
13+
test('should return true when env var is "true"', () => {
14+
assert.strictEqual(isPoetryVirtualenvsInProject('true'), true);
15+
});
16+
17+
test('should return true when env var is "True" (case insensitive)', () => {
18+
assert.strictEqual(isPoetryVirtualenvsInProject('True'), true);
19+
});
20+
21+
test('should return true when env var is "TRUE" (case insensitive)', () => {
22+
assert.strictEqual(isPoetryVirtualenvsInProject('TRUE'), true);
23+
});
24+
25+
test('should return true when env var is "1"', () => {
26+
assert.strictEqual(isPoetryVirtualenvsInProject('1'), true);
27+
});
28+
29+
test('should return false when env var is "false"', () => {
30+
assert.strictEqual(isPoetryVirtualenvsInProject('false'), false);
31+
});
32+
33+
test('should return false when env var is "0"', () => {
34+
assert.strictEqual(isPoetryVirtualenvsInProject('0'), false);
35+
});
36+
37+
test('should return false when env var is empty string', () => {
38+
assert.strictEqual(isPoetryVirtualenvsInProject(''), false);
39+
});
40+
41+
test('should return false when env var is arbitrary string', () => {
42+
assert.strictEqual(isPoetryVirtualenvsInProject('yes'), false);
43+
});
44+
45+
test('should read from process.env when no argument given', () => {
46+
const original = process.env.POETRY_VIRTUALENVS_IN_PROJECT;
47+
try {
48+
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'true';
49+
assert.strictEqual(isPoetryVirtualenvsInProject(), true);
50+
51+
delete process.env.POETRY_VIRTUALENVS_IN_PROJECT;
52+
assert.strictEqual(isPoetryVirtualenvsInProject(), false);
53+
} finally {
54+
if (original === undefined) {
55+
delete process.env.POETRY_VIRTUALENVS_IN_PROJECT;
56+
} else {
57+
process.env.POETRY_VIRTUALENVS_IN_PROJECT = original;
58+
}
59+
}
60+
});
61+
});
62+
63+
suite('nativeToPythonEnv - POETRY_VIRTUALENVS_IN_PROJECT integration', () => {
64+
let capturedInfo: PythonEnvironmentInfo | undefined;
565
let originalEnv: string | undefined;
666

67+
const mockApi = {
68+
createPythonEnvironmentItem: (info: PythonEnvironmentInfo, _manager: EnvironmentManager) => {
69+
capturedInfo = info;
70+
return { ...info, envId: { id: 'test-id', managerId: 'test-manager' } } as PythonEnvironment;
71+
},
72+
} as unknown as PythonEnvironmentApi;
73+
74+
const mockManager = {} as EnvironmentManager;
75+
76+
const baseEnvInfo: NativeEnvInfo = {
77+
prefix: '/home/user/myproject/.venv',
78+
executable: '/home/user/myproject/.venv/bin/python',
79+
version: '3.12.0',
80+
name: 'myproject-venv',
81+
project: '/home/user/myproject',
82+
};
83+
784
setup(() => {
85+
capturedInfo = undefined;
886
originalEnv = process.env.POETRY_VIRTUALENVS_IN_PROJECT;
87+
88+
sinon.stub(utils, 'getShellActivationCommands').resolves({
89+
shellActivation: new Map(),
90+
shellDeactivation: new Map(),
91+
});
992
});
1093

1194
teardown(() => {
95+
sinon.restore();
1296
if (originalEnv === undefined) {
1397
delete process.env.POETRY_VIRTUALENVS_IN_PROJECT;
1498
} else {
1599
process.env.POETRY_VIRTUALENVS_IN_PROJECT = originalEnv;
16100
}
17101
});
18102

19-
test('should return false when env var is not set', () => {
20-
delete process.env.POETRY_VIRTUALENVS_IN_PROJECT;
21-
assert.strictEqual(isPoetryVirtualenvsInProject(), false);
22-
});
23-
24-
test('should return true when env var is "true"', () => {
103+
test('env var set + project present → environment is NOT classified as global', async () => {
25104
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'true';
26-
assert.strictEqual(isPoetryVirtualenvsInProject(), true);
27-
});
28105

29-
test('should return true when env var is "True" (case insensitive)', () => {
30-
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'True';
31-
assert.strictEqual(isPoetryVirtualenvsInProject(), true);
32-
});
106+
const result = await nativeToPythonEnv(baseEnvInfo, mockApi, mockManager, '/usr/bin/poetry');
33107

34-
test('should return true when env var is "TRUE" (case insensitive)', () => {
35-
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'TRUE';
36-
assert.strictEqual(isPoetryVirtualenvsInProject(), true);
108+
assert.ok(result, 'Should return a PythonEnvironment');
109+
assert.ok(capturedInfo, 'Should have captured environment info');
110+
assert.strictEqual(capturedInfo!.group, undefined, 'In-project env should not have POETRY_GLOBAL group');
37111
});
38112

39-
test('should return true when env var is "1"', () => {
113+
test('env var set to "1" + project present → environment is NOT classified as global', async () => {
40114
process.env.POETRY_VIRTUALENVS_IN_PROJECT = '1';
41-
assert.strictEqual(isPoetryVirtualenvsInProject(), true);
42-
});
43115

44-
test('should return false when env var is "false"', () => {
45-
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'false';
46-
assert.strictEqual(isPoetryVirtualenvsInProject(), false);
116+
const result = await nativeToPythonEnv(baseEnvInfo, mockApi, mockManager, '/usr/bin/poetry');
117+
118+
assert.ok(result, 'Should return a PythonEnvironment');
119+
assert.ok(capturedInfo, 'Should have captured environment info');
120+
assert.strictEqual(capturedInfo!.group, undefined, 'In-project env should not have POETRY_GLOBAL group');
47121
});
48122

49-
test('should return false when env var is "0"', () => {
50-
process.env.POETRY_VIRTUALENVS_IN_PROJECT = '0';
51-
assert.strictEqual(isPoetryVirtualenvsInProject(), false);
123+
test('env var set + project absent → falls through to normal global check', async () => {
124+
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'true';
125+
const envWithoutProject: NativeEnvInfo = {
126+
...baseEnvInfo,
127+
project: undefined,
128+
};
129+
130+
const result = await nativeToPythonEnv(envWithoutProject, mockApi, mockManager, '/usr/bin/poetry');
131+
132+
assert.ok(result, 'Should return a PythonEnvironment');
133+
assert.ok(capturedInfo, 'Should have captured environment info');
134+
// Without project, falls through to global check; since prefix is not in global dir, group is undefined
135+
assert.strictEqual(capturedInfo!.group, undefined, 'Non-global path without project should not be global');
52136
});
53137

54-
test('should return false when env var is empty string', () => {
55-
process.env.POETRY_VIRTUALENVS_IN_PROJECT = '';
56-
assert.strictEqual(isPoetryVirtualenvsInProject(), false);
138+
test('env var NOT set → original classification behavior is preserved', async () => {
139+
delete process.env.POETRY_VIRTUALENVS_IN_PROJECT;
140+
141+
const result = await nativeToPythonEnv(baseEnvInfo, mockApi, mockManager, '/usr/bin/poetry');
142+
143+
assert.ok(result, 'Should return a PythonEnvironment');
144+
assert.ok(capturedInfo, 'Should have captured environment info');
145+
// Prefix is not in global virtualenvs dir, so not classified as global
146+
assert.strictEqual(capturedInfo!.group, undefined, 'Non-global path should not be global');
57147
});
58148

59-
test('should return false when env var is arbitrary string', () => {
60-
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'yes';
61-
assert.strictEqual(isPoetryVirtualenvsInProject(), false);
149+
test('env var set to "false" → original classification behavior is preserved', async () => {
150+
process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'false';
151+
152+
const result = await nativeToPythonEnv(baseEnvInfo, mockApi, mockManager, '/usr/bin/poetry');
153+
154+
assert.ok(result, 'Should return a PythonEnvironment');
155+
assert.ok(capturedInfo, 'Should have captured environment info');
156+
// Falls through to normal check since env var is falsy
157+
assert.strictEqual(capturedInfo!.group, undefined, 'Non-global path should not be global');
62158
});
63159
});

0 commit comments

Comments
 (0)