Skip to content

Commit 4574c92

Browse files
committed
update tests
1 parent 5553a6e commit 4574c92

2 files changed

Lines changed: 138 additions & 8 deletions

File tree

src/managers/builtin/venvUtils.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,28 @@ export async function clearVenvCache(): Promise<void> {
6363
export async function getVenvForWorkspace(fsPath: string): Promise<string | undefined> {
6464
// Check persisted user selection first — this should always take priority
6565
// over process.env.VIRTUAL_ENV so that explicit selections survive reload.
66-
const state = await getWorkspacePersistentState();
67-
const data: { [key: string]: string } | undefined = await state.get(VENV_WORKSPACE_KEY);
68-
if (data) {
69-
try {
66+
try {
67+
const state = await getWorkspacePersistentState();
68+
const data: { [key: string]: string } | undefined = await state.get(VENV_WORKSPACE_KEY);
69+
if (data) {
7070
const envPath = data[fsPath];
7171
if (envPath && (await fsapi.pathExists(envPath))) {
7272
return envPath;
7373
}
7474
if (envPath) {
7575
await setVenvForWorkspace(fsPath, undefined);
7676
}
77-
} catch {
78-
// fall through to VIRTUAL_ENV check
7977
}
78+
} catch {
79+
// fall through to VIRTUAL_ENV check
8080
}
8181

8282
// Fall back to VIRTUAL_ENV only if it points to a path inside this workspace.
83-
if (process.env.VIRTUAL_ENV && normalizePath(process.env.VIRTUAL_ENV).startsWith(normalizePath(fsPath))) {
84-
return process.env.VIRTUAL_ENV;
83+
if (process.env.VIRTUAL_ENV) {
84+
const rel = path.relative(fsPath, process.env.VIRTUAL_ENV);
85+
if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {
86+
return process.env.VIRTUAL_ENV;
87+
}
8588
}
8689

8790
return undefined;

src/test/managers/builtin/venvUtils.getVenvForWorkspace.unit.test.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,96 @@ import * as sinon from 'sinon';
66
import * as persistentState from '../../../common/persistentState';
77
import { getVenvForWorkspace, VENV_WORKSPACE_KEY } from '../../../managers/builtin/venvUtils';
88

9+
/**
10+
* Helper that replicates the VIRTUAL_ENV subpath check from getVenvForWorkspace
11+
* using a specific path module, allowing cross-platform verification.
12+
*/
13+
function isVenvInsideWorkspace(
14+
fsPath: string,
15+
virtualEnv: string,
16+
pathModule: typeof path.posix | typeof path.win32,
17+
): boolean {
18+
const rel = pathModule.relative(fsPath, virtualEnv);
19+
return rel === '' || (!rel.startsWith('..') && !pathModule.isAbsolute(rel));
20+
}
21+
22+
suite('VIRTUAL_ENV subpath check - cross-platform', () => {
23+
suite('POSIX paths', () => {
24+
const p = path.posix;
25+
26+
test('venv inside workspace should match', () => {
27+
assert.strictEqual(isVenvInsideWorkspace('/proj/app', '/proj/app/.venv', p), true);
28+
});
29+
30+
test('venv deeply nested inside workspace should match', () => {
31+
assert.strictEqual(isVenvInsideWorkspace('/proj/app', '/proj/app/sub/dir/.venv', p), true);
32+
});
33+
34+
test('venv equal to workspace should match', () => {
35+
assert.strictEqual(isVenvInsideWorkspace('/proj/app', '/proj/app', p), true);
36+
});
37+
38+
test('sibling with shared prefix should NOT match', () => {
39+
assert.strictEqual(isVenvInsideWorkspace('/proj/app', '/proj/app2/.venv', p), false);
40+
});
41+
42+
test('venv in completely different location should NOT match', () => {
43+
assert.strictEqual(isVenvInsideWorkspace('/proj/app', '/other/place/.venv', p), false);
44+
});
45+
46+
test('parent directory should NOT match', () => {
47+
assert.strictEqual(isVenvInsideWorkspace('/proj/app', '/proj/.venv', p), false);
48+
});
49+
});
50+
51+
suite('Windows paths', () => {
52+
const p = path.win32;
53+
54+
test('venv inside workspace should match', () => {
55+
assert.strictEqual(isVenvInsideWorkspace('C:\\proj\\app', 'C:\\proj\\app\\.venv', p), true);
56+
});
57+
58+
test('venv deeply nested inside workspace should match', () => {
59+
assert.strictEqual(isVenvInsideWorkspace('C:\\proj\\app', 'C:\\proj\\app\\sub\\dir\\.venv', p), true);
60+
});
61+
62+
test('venv equal to workspace should match', () => {
63+
assert.strictEqual(isVenvInsideWorkspace('C:\\proj\\app', 'C:\\proj\\app', p), true);
64+
});
65+
66+
test('sibling with shared prefix should NOT match', () => {
67+
assert.strictEqual(isVenvInsideWorkspace('C:\\proj\\app', 'C:\\proj\\app2\\.venv', p), false);
68+
});
69+
70+
test('venv on a different drive should NOT match', () => {
71+
assert.strictEqual(isVenvInsideWorkspace('C:\\proj\\app', 'D:\\proj\\app\\.venv', p), false);
72+
});
73+
74+
test('parent directory should NOT match', () => {
75+
assert.strictEqual(isVenvInsideWorkspace('C:\\proj\\app', 'C:\\proj\\.venv', p), false);
76+
});
77+
78+
test('case-insensitive drive letters should match', () => {
79+
// path.win32.relative handles case-insensitive drive letters
80+
assert.strictEqual(isVenvInsideWorkspace('c:\\proj\\app', 'C:\\proj\\app\\.venv', p), true);
81+
});
82+
83+
test('UNC path inside workspace should match', () => {
84+
assert.strictEqual(
85+
isVenvInsideWorkspace('\\\\server\\share\\proj', '\\\\server\\share\\proj\\.venv', p),
86+
true,
87+
);
88+
});
89+
90+
test('UNC path sibling should NOT match', () => {
91+
assert.strictEqual(
92+
isVenvInsideWorkspace('\\\\server\\share\\proj', '\\\\server\\share\\proj2\\.venv', p),
93+
false,
94+
);
95+
});
96+
});
97+
});
98+
999
suite('getVenvForWorkspace', () => {
10100
let mockState: {
11101
get: sinon.SinonStub;
@@ -163,4 +253,41 @@ suite('getVenvForWorkspace', () => {
163253

164254
assert.ok(!mockState.set.called, 'Should not call set when there is no stale entry to clear');
165255
});
256+
257+
test('should fall back to VIRTUAL_ENV when state.get rejects', async () => {
258+
const workspacePath = path.join(tmpDir, 'projectA');
259+
const virtualEnvPath = path.join(workspacePath, '.venv');
260+
await fse.ensureDir(virtualEnvPath);
261+
process.env.VIRTUAL_ENV = virtualEnvPath;
262+
263+
mockState.get.withArgs(VENV_WORKSPACE_KEY).rejects(new Error('storage corrupted'));
264+
265+
const result = await getVenvForWorkspace(workspacePath);
266+
assert.strictEqual(result, virtualEnvPath, 'Should fall back to VIRTUAL_ENV when state.get rejects');
267+
});
268+
269+
test('should fall back to VIRTUAL_ENV when getWorkspacePersistentState rejects', async () => {
270+
const workspacePath = path.join(tmpDir, 'projectA');
271+
const virtualEnvPath = path.join(workspacePath, '.venv');
272+
await fse.ensureDir(virtualEnvPath);
273+
process.env.VIRTUAL_ENV = virtualEnvPath;
274+
275+
getWorkspacePersistentStateStub.rejects(new Error('persistent state unavailable'));
276+
277+
const result = await getVenvForWorkspace(workspacePath);
278+
assert.strictEqual(result, virtualEnvPath, 'Should fall back to VIRTUAL_ENV when persistent state fails');
279+
});
280+
281+
test('should NOT use VIRTUAL_ENV for sibling path with shared prefix', async () => {
282+
const projectA = path.join(tmpDir, 'app');
283+
const siblingVenv = path.join(tmpDir, 'app2', '.venv');
284+
await fse.ensureDir(projectA);
285+
await fse.ensureDir(siblingVenv);
286+
process.env.VIRTUAL_ENV = siblingVenv;
287+
288+
mockState.get.withArgs(VENV_WORKSPACE_KEY).resolves(undefined);
289+
290+
const result = await getVenvForWorkspace(projectA);
291+
assert.strictEqual(result, undefined, 'Should NOT match sibling path with shared prefix');
292+
});
166293
});

0 commit comments

Comments
 (0)