Skip to content

Commit 77bf701

Browse files
Run extension-internal scripts "isolated". (#10941)
For #10681. This makes it so that none of the extension's internal scripts get run with sys.path[0] set to CWD. Note that test adapter script is the only one that is not run isolated.
1 parent acb2d8d commit 77bf701

File tree

10 files changed

+58
-29
lines changed

10 files changed

+58
-29
lines changed

pythonFiles/pyvsc-run-isolated.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
del sys.path[0]
1414
del sys.argv[0]
1515
module = sys.argv[0]
16-
if module.endswith(".py"):
17-
runpy.run_path(module)
16+
if module.startswith("-"):
17+
raise NotImplementedError(sys.argv)
18+
elif module.endswith(".py"):
19+
runpy.run_path(module, run_name="__main__")
1820
else:
19-
runpy.run_module(module, alter_sys=True)
21+
runpy.run_module(module, run_name="__main__", alter_sys=True)

src/client/common/process/internal/scripts/index.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { PythonVersionInfo } from '../../types';
88
// It is simpler to hard-code it instead of using vscode.ExtensionContext.extensionPath.
99
export const _SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'pythonFiles');
1010
const SCRIPTS_DIR = _SCRIPTS_DIR;
11+
export const _ISOLATED = path.join(_SCRIPTS_DIR, 'pyvsc-run-isolated.py');
12+
const ISOLATED = _ISOLATED;
1113

1214
// "scripts" contains everything relevant to the scripts found under
1315
// the top-level "pythonFiles" directory. Each of those scripts has
@@ -48,7 +50,7 @@ type PythonEnvInfo = {
4850

4951
export function interpreterInfo(): [string[], (out: string) => PythonEnvInfo] {
5052
const script = path.join(SCRIPTS_DIR, 'interpreterInfo.py');
51-
const args = [script];
53+
const args = [ISOLATED, script];
5254

5355
function parse(out: string): PythonEnvInfo {
5456
let json: PythonEnvInfo;
@@ -158,7 +160,7 @@ namespace _completion {
158160

159161
export function completion(jediPath?: string): [string[], (out: string) => _completion.Response[]] {
160162
const script = path.join(SCRIPTS_DIR, 'completion.py');
161-
const args = [script];
163+
const args = [ISOLATED, script];
162164
if (jediPath) {
163165
args.push('custom');
164166
args.push(jediPath);
@@ -176,7 +178,7 @@ export function completion(jediPath?: string): [string[], (out: string) => _comp
176178

177179
export function sortImports(filename: string, sortArgs?: string[]): [string[], (out: string) => string] {
178180
const script = path.join(SCRIPTS_DIR, 'sortImports.py');
179-
const args = [script, filename, '--diff'];
181+
const args = [ISOLATED, script, filename, '--diff'];
180182
if (sortArgs) {
181183
args.push(...sortArgs);
182184
}
@@ -194,7 +196,7 @@ export function sortImports(filename: string, sortArgs?: string[]): [string[], (
194196

195197
export function refactor(root: string): [string[], (out: string) => object[]] {
196198
const script = path.join(SCRIPTS_DIR, 'refactor.py');
197-
const args = [script, root];
199+
const args = [ISOLATED, script, root];
198200

199201
// tslint:disable-next-line:no-suspicious-comment
200202
// TODO: Make the return type more specific, like we did
@@ -216,7 +218,7 @@ export function refactor(root: string): [string[], (out: string) => object[]] {
216218

217219
export function normalizeForInterpreter(code: string): [string[], (out: string) => string] {
218220
const script = path.join(SCRIPTS_DIR, 'normalizeForInterpreter.py');
219-
const args = [script, code];
221+
const args = [ISOLATED, script, code];
220222

221223
function parse(out: string) {
222224
// The text will be used as-is.
@@ -256,7 +258,7 @@ export function symbolProvider(
256258
text?: string
257259
): [string[], (out: string) => _symbolProvider.Symbols] {
258260
const script = path.join(SCRIPTS_DIR, 'symbolProvider.py');
259-
const args = [script, filename];
261+
const args = [ISOLATED, script, filename];
260262
if (text) {
261263
args.push(text);
262264
}
@@ -273,7 +275,7 @@ export function symbolProvider(
273275

274276
export function printEnvVariables(): [string[], (out: string) => NodeJS.ProcessEnv] {
275277
const script = path.join(SCRIPTS_DIR, 'printEnvVariables.py').fileToCommandArgument();
276-
const args = [script];
278+
const args = [ISOLATED, script];
277279

278280
function parse(out: string): NodeJS.ProcessEnv {
279281
return JSON.parse(out);
@@ -287,7 +289,7 @@ export function printEnvVariables(): [string[], (out: string) => NodeJS.ProcessE
287289

288290
export function printEnvVariablesToFile(filename: string): [string[], (out: string) => NodeJS.ProcessEnv] {
289291
const script = path.join(SCRIPTS_DIR, 'printEnvVariablesToFile.py');
290-
const args = [script, filename.fileToCommandArgument()];
292+
const args = [ISOLATED, script, filename.fileToCommandArgument()];
291293

292294
function parse(out: string): NodeJS.ProcessEnv {
293295
return JSON.parse(out);
@@ -304,6 +306,7 @@ export function shell_exec(command: string, lockfile: string, shellArgs: string[
304306
// We don't bother with a "parse" function since the output
305307
// could be anything.
306308
return [
309+
ISOLATED,
307310
script,
308311
command.fileToCommandArgument(),
309312
// The shell args must come after the command
@@ -319,7 +322,7 @@ export function shell_exec(command: string, lockfile: string, shellArgs: string[
319322
export function testlauncher(testArgs: string[]): string[] {
320323
const script = path.join(SCRIPTS_DIR, 'testlauncher.py');
321324
// There is no output to parse, so we do not return a function.
322-
return [script, ...testArgs];
325+
return [ISOLATED, script, ...testArgs];
323326
}
324327

325328
//============================
@@ -328,5 +331,5 @@ export function testlauncher(testArgs: string[]): string[] {
328331
export function visualstudio_py_testlauncher(testArgs: string[]): string[] {
329332
const script = path.join(SCRIPTS_DIR, 'visualstudio_py_testlauncher.py');
330333
// There is no output to parse, so we do not return a function.
331-
return [script, ...testArgs];
334+
return [ISOLATED, script, ...testArgs];
332335
}

src/client/common/process/internal/scripts/testing_tools.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ export type DiscoveredTests = {
4747

4848
export function run_adapter(adapterArgs: string[]): [string[], (out: string) => DiscoveredTests[]] {
4949
const script = path.join(SCRIPTS_DIR, 'run_adapter.py');
50+
// Note that we for now we do not run this "isolated". The
51+
// script relies on some magic that conflicts with the
52+
// isolated script.
5053
const args = [script, ...adapterArgs];
5154

5255
function parse(out: string): DiscoveredTests[] {

src/client/common/process/internal/scripts/vscode_datascience_helpers.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
import * as path from 'path';
5-
import { _SCRIPTS_DIR } from './index';
5+
import { _ISOLATED as ISOLATED, _SCRIPTS_DIR } from './index';
66

77
const SCRIPTS_DIR = path.join(_SCRIPTS_DIR, 'vscode_datascience_helpers');
88

@@ -12,7 +12,7 @@ const SCRIPTS_DIR = path.join(_SCRIPTS_DIR, 'vscode_datascience_helpers');
1212
export function getJupyterVariableDataFrameInfo(): string[] {
1313
const script = path.join(SCRIPTS_DIR, 'getJupyterVariableDataFrameInfo.py');
1414
// There is no script-specific output to parse, so we do not return a function.
15-
return [script];
15+
return [ISOLATED, script];
1616
}
1717

1818
//============================
@@ -21,7 +21,7 @@ export function getJupyterVariableDataFrameInfo(): string[] {
2121
export function getJupyterVariableDataFrameRows(): string[] {
2222
const script = path.join(SCRIPTS_DIR, 'getJupyterVariableDataFrameRows.py');
2323
// There is no script-specific output to parse, so we do not return a function.
24-
return [script];
24+
return [ISOLATED, script];
2525
}
2626

2727
//============================
@@ -41,7 +41,7 @@ type JupyterServerInfo = {
4141

4242
export function getServerInfo(): [string[], (out: string) => JupyterServerInfo[]] {
4343
const script = path.join(SCRIPTS_DIR, 'getServerInfo.py');
44-
const args = [script];
44+
const args = [ISOLATED, script];
4545

4646
function parse(out: string): JupyterServerInfo[] {
4747
return JSON.parse(out.trim());
@@ -56,7 +56,7 @@ export function getServerInfo(): [string[], (out: string) => JupyterServerInfo[]
5656
export function getJupyterKernels(): string[] {
5757
const script = path.join(SCRIPTS_DIR, 'getJupyterKernels.py');
5858
// There is no script-specific output to parse, so we do not return a function.
59-
return [script];
59+
return [ISOLATED, script];
6060
}
6161

6262
//============================
@@ -65,15 +65,15 @@ export function getJupyterKernels(): string[] {
6565
export function getJupyterKernelspecVersion(): string[] {
6666
const script = path.join(SCRIPTS_DIR, 'getJupyterKernelspecVersion.py');
6767
// For now we do not worry about parsing the output here.
68-
return [script];
68+
return [ISOLATED, script];
6969
}
7070

7171
//============================
7272
// jupyter_nbInstalled.py
7373

7474
export function jupyter_nbInstalled(): [string[], (out: string) => boolean] {
7575
const script = path.join(SCRIPTS_DIR, 'jupyter_nbInstalled.py');
76-
const args = [script];
76+
const args = [ISOLATED, script];
7777

7878
function parse(out: string): boolean {
7979
return out.toLowerCase().includes('available');

src/test/common/terminals/synchronousTerminalService.unit.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ suite('Terminal Service (synchronous)', () => {
6767
});
6868
});
6969
suite('sendCommand', () => {
70+
const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py');
7071
const shellExecFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'shell_exec.py');
7172

7273
test('run sendCommand in terminalService if there is no cancellation token', async () => {
@@ -107,6 +108,7 @@ suite('Terminal Service (synchronous)', () => {
107108
terminalService.sendCommand(
108109
'python',
109110
deepEqual([
111+
isolated.fileToCommandArgument(),
110112
shellExecFile.fileToCommandArgument(),
111113
'cmd'.fileToCommandArgument(),
112114
'1',
@@ -149,6 +151,7 @@ suite('Terminal Service (synchronous)', () => {
149151
terminalService.sendCommand(
150152
'python',
151153
deepEqual([
154+
isolated.fileToCommandArgument(),
152155
shellExecFile.fileToCommandArgument(),
153156
'cmd'.fileToCommandArgument(),
154157
'1',

src/test/interpreters/activation/service.unit.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,13 @@ suite('Interpreters Activation - Python Environment Variables', () => {
140140

141141
const shellCmd = capture(processService.shellExec).first()[0];
142142

143+
const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py');
143144
const printEnvPyFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'printEnvVariables.py');
144-
const expectedCommand = `${cmd.join(
145-
' && '
146-
)} && echo '${getEnvironmentPrefix}' && python ${printEnvPyFile.fileToCommandArgument()}`;
145+
const expectedCommand = [
146+
...cmd,
147+
`echo '${getEnvironmentPrefix}'`,
148+
`python ${isolated} ${printEnvPyFile.fileToCommandArgument()}`
149+
].join(' && ');
147150

148151
expect(shellCmd).to.equal(expectedCommand);
149152
});

src/test/interpreters/activation/terminalEnvironmentActivationService.unit.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ suite('Interpreters Activation - Python Environment Variables (using terminals)'
9595
});
9696
test('Should execute python file in terminal (that is what dumps variables into json)', async () => {
9797
when(envVarsProvider.getCustomEnvironmentVariables(resource)).thenResolve(undefined);
98+
const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py');
9899
const pyFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'printEnvVariablesToFile.py');
99100

100101
await envActivationService.getActivatedEnvironmentVariables(resource, interpreter);
@@ -103,7 +104,11 @@ suite('Interpreters Activation - Python Environment Variables (using terminals)'
103104
verify(
104105
terminal.sendCommand(
105106
cmd,
106-
deepEqual([pyFile.fileToCommandArgument(), jsonFile.fileToCommandArgument()]),
107+
deepEqual([
108+
isolated.fileToCommandArgument(),
109+
pyFile.fileToCommandArgument(),
110+
jsonFile.fileToCommandArgument()
111+
]),
107112
anything(),
108113
false
109114
)

src/test/providers/importSortProvider.unit.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { IServiceContainer } from '../../client/ioc/types';
3131
import { SortImportsEditingProvider } from '../../client/providers/importSortProvider';
3232
import { ISortImportsEditingProvider } from '../../client/providers/types';
3333

34+
const ISOLATED = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py');
35+
3436
suite('Import Sort Provider', () => {
3537
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
3638
let shell: TypeMoq.IMock<IApplicationShell>;
@@ -398,7 +400,7 @@ suite('Import Sort Provider', () => {
398400
.returns(() => Promise.resolve(processExeService.object))
399401
.verifiable(TypeMoq.Times.once());
400402
const importScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'sortImports.py');
401-
const expectedArgs = [importScript, tmpFile.filePath, '--diff', '1', '2'];
403+
const expectedArgs = [ISOLATED, importScript, tmpFile.filePath, '--diff', '1', '2'];
402404
processExeService
403405
.setup((p) =>
404406
p.exec(TypeMoq.It.isValue(expectedArgs), TypeMoq.It.isValue({ throwOnStdErr: true, token: undefined }))

src/test/testing/common/debugLauncher.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ suite('Unit Tests - Debug Launcher', () => {
207207
expected = getDefaultDebugConfig();
208208
}
209209
expected.rules = [{ path: path.join(EXTENSION_ROOT_DIR, 'pythonFiles'), include: false }];
210-
expected.program = testLaunchScript;
211-
expected.args = options.args;
210+
expected.program = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py');
211+
expected.args = [testLaunchScript, ...options.args];
212212
if (!expected.cwd) {
213213
expected.cwd = workspaceFolders[0].uri.fsPath;
214214
}

src/test/testing/navigation/symbolNavigator.unit.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ suite('Unit Tests - Navigation Command Handler', () => {
8686
});
8787
test('Ensure no symbols are returned when there are no symbols to be returned', async () => {
8888
const docUri = Uri.file(__filename);
89-
const args = [path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'symbolProvider.py'), docUri.fsPath];
89+
const args = [
90+
path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py'),
91+
path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'symbolProvider.py'),
92+
docUri.fsPath
93+
];
9094
const proc: ExecutionResult<string> = {
9195
stdout: JSON.stringify({ classes: [], methods: [], functions: [] })
9296
};
@@ -114,7 +118,11 @@ suite('Unit Tests - Navigation Command Handler', () => {
114118
});
115119
test('Ensure symbols are returned', async () => {
116120
const docUri = Uri.file(__filename);
117-
const args = [path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'symbolProvider.py'), docUri.fsPath];
121+
const args = [
122+
path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py'),
123+
path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'symbolProvider.py'),
124+
docUri.fsPath
125+
];
118126
const proc: ExecutionResult<string> = {
119127
stdout: JSON.stringify({
120128
classes: [

0 commit comments

Comments
 (0)