Skip to content

Add conda run without output #17982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2037cd7
Add conda run command
paulacamargo25 Sep 28, 2021
d6b57dc
Fix code run
paulacamargo25 Oct 21, 2021
4111210
Fix linting
paulacamargo25 Nov 3, 2021
e21243c
Fix tests
paulacamargo25 Nov 9, 2021
0ca6cfc
Add news
paulacamargo25 Nov 9, 2021
efe6155
Fix tests in linter
paulacamargo25 Nov 10, 2021
0bd6ddb
Fix strings
paulacamargo25 Nov 22, 2021
d2944bd
Fix linters
paulacamargo25 Nov 23, 2021
11680ae
Fix conda run unit test
paulacamargo25 Nov 23, 2021
3c2ae1c
Fix funtional tests
paulacamargo25 Nov 25, 2021
bc2713c
Fix single workspace tests
paulacamargo25 Nov 29, 2021
e879116
update minimum conda versionvalue
paulacamargo25 Dec 7, 2021
5ce2665
Change sorting timeput
paulacamargo25 Dec 7, 2021
619a9b7
Add timeout time in sorting test
paulacamargo25 Dec 7, 2021
2c4d9d4
Fix test wothout config
paulacamargo25 Dec 8, 2021
2fdcb6a
Add more time to timeout in sorting tests
paulacamargo25 Dec 9, 2021
355179c
Add conda run command
paulacamargo25 Sep 28, 2021
caf5ada
Fix code run
paulacamargo25 Oct 21, 2021
c666e09
Fix linting
paulacamargo25 Nov 3, 2021
2a7b343
Fix tests
paulacamargo25 Nov 9, 2021
df16208
Add news
paulacamargo25 Nov 9, 2021
0ba12cb
Fix tests in linter
paulacamargo25 Nov 10, 2021
fee7cbd
Fix strings
paulacamargo25 Nov 22, 2021
51ce1e4
Fix linters
paulacamargo25 Nov 23, 2021
a3a067a
Fix conda run unit test
paulacamargo25 Nov 23, 2021
cf146a2
Fix funtional tests
paulacamargo25 Nov 25, 2021
9a1052c
Fix single workspace tests
paulacamargo25 Nov 29, 2021
8c5d1c0
update minimum conda versionvalue
paulacamargo25 Dec 7, 2021
bbb3700
Change sorting timeput
paulacamargo25 Dec 7, 2021
efeef07
Add timeout time in sorting test
paulacamargo25 Dec 7, 2021
90a8349
Add more time to timeout in sorting tests
paulacamargo25 Dec 9, 2021
96d5d3c
Update news/1 Enhancements/7696.md
paulacamargo25 Dec 15, 2021
6f1c112
Fix merge
paulacamargo25 Dec 15, 2021
995f54c
Merge branch 'Add-Conda-run-without-output' of github.com:paulacamarg…
paulacamargo25 Dec 15, 2021
7a36818
Rix pylint test
paulacamargo25 Dec 15, 2021
d11654b
Fix lint test
paulacamargo25 Dec 15, 2021
ac7c626
Remove unnecessary interpreter check
paulacamargo25 Dec 16, 2021
e167a03
Fix timeout test errors
paulacamargo25 Dec 21, 2021
d767c26
Fix unit test pip installer
paulacamargo25 Dec 21, 2021
7fb6003
Fix IModuleInstaller test
paulacamargo25 Dec 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/1 Enhancements/7696.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for conda run without output, using `--no-capture-output` flag.
27 changes: 21 additions & 6 deletions src/client/common/installer/productInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ abstract class BaseInstaller {
resource?: InterpreterUri,
cancel?: CancellationToken,
flags?: ModuleInstallFlags,
bypassCondaExecution?: boolean,
): Promise<InstallerResponse> {
if (product === Product.unittest) {
return InstallerResponse.Installed;
Expand All @@ -113,7 +114,7 @@ abstract class BaseInstaller {
.installModule(product, resource, cancel, flags)
.catch((ex) => traceError(`Error in installing the product '${ProductNames.get(product)}', ${ex}`));

return this.isInstalled(product, resource).then((isInstalled) => {
return this.isInstalled(product, resource, bypassCondaExecution).then((isInstalled) => {
sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, {
installer: installer.displayName,
productName: ProductNames.get(product),
Expand Down Expand Up @@ -178,7 +179,11 @@ abstract class BaseInstaller {
}
}

public async isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean> {
public async isInstalled(
product: Product,
resource?: InterpreterUri,
bypassCondaExecution?: boolean,
): Promise<boolean> {
if (product === Product.unittest) {
return true;
}
Expand All @@ -191,7 +196,12 @@ abstract class BaseInstaller {
if (isModule) {
const pythonProcess = await this.serviceContainer
.get<IPythonExecutionFactory>(IPythonExecutionFactory)
.createActivatedEnvironment({ resource: uri, interpreter, allowEnvironmentFetchExceptions: true });
.createActivatedEnvironment({
resource: uri,
interpreter,
allowEnvironmentFetchExceptions: true,
bypassCondaExecution,
});
return pythonProcess.isModuleInstalled(executableName);
}
const process = await this.serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory).create(uri);
Expand Down Expand Up @@ -595,12 +605,17 @@ export class ProductInstaller implements IInstaller {
resource?: InterpreterUri,
cancel?: CancellationToken,
flags?: ModuleInstallFlags,
bypassCondaExecution?: boolean,
): Promise<InstallerResponse> {
return this.createInstaller(product).install(product, resource, cancel, flags);
return this.createInstaller(product).install(product, resource, cancel, flags, bypassCondaExecution);
}

public async isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean> {
return this.createInstaller(product).isInstalled(product, resource);
public async isInstalled(
product: Product,
resource?: InterpreterUri,
bypassCondaExecution?: boolean,
): Promise<boolean> {
return this.createInstaller(product).isInstalled(product, resource, bypassCondaExecution);
}

// eslint-disable-next-line class-methods-use-this
Expand Down
8 changes: 2 additions & 6 deletions src/client/common/process/pythonEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,11 @@ export function createCondaEnv(
} else {
runArgs.push('-n', condaInfo.name);
}
const pythonArgv = [condaFile, ...runArgs, 'python'];
const pythonArgv = [condaFile, ...runArgs, '--no-capture-output', 'python'];
const deps = createDeps(
async (filename) => fs.pathExists(filename),
pythonArgv,

// TODO: Use pythonArgv here once 'conda run' can be
// run without buffering output.
// See https://github.com/microsoft/vscode-python/issues/8473.
undefined,
pythonArgv,
(file, args, opts) => procs.exec(file, args, opts),
(command, opts) => procs.shellExec(command, opts),
);
Expand Down
24 changes: 20 additions & 4 deletions src/client/common/process/pythonExecutionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import { IInterpreterAutoSelectionService } from '../../interpreter/autoSelectio
import { sleep } from '../utils/async';
import { traceError } from '../../logging';

// Minimum version number of conda required to be able to use 'conda run'
export const CONDA_RUN_VERSION = '4.6.0';
// Minimum version number of conda required to be able to use 'conda run' and '--no-capture--output' option
export const CONDA_RUN_VERSION = '4.9.0';

@injectable()
export class PythonExecutionFactory implements IPythonExecutionFactory {
Expand Down Expand Up @@ -83,6 +83,14 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
}
const processService: IProcessService = await this.processServiceFactory.create(options.resource);

// Allow parts of the code to ignore conda run.
if (!options.bypassCondaExecution) {
const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService);
if (condaExecutionService) {
return condaExecutionService;
}
}

const windowsStoreInterpreterCheck = this.pyenvs.isWindowsStoreInterpreter.bind(this.pyenvs);

return createPythonService(
Expand All @@ -108,6 +116,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
return this.create({
resource: options.resource,
pythonPath: options.interpreter ? options.interpreter.path : undefined,
bypassCondaExecution: options.bypassCondaExecution,
});
}
const pythonPath = options.interpreter
Expand All @@ -117,11 +126,17 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
processService.on('exec', this.logger.logProcess.bind(this.logger));
this.disposables.push(processService);

// Allow parts of the code to ignore conda run.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR it seems it's only used for tests, can you elaborate where this is needed?

if (!options.bypassCondaExecution) {
const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService);
if (condaExecutionService) {
return condaExecutionService;
}
}

return createPythonService(pythonPath, processService, this.fileSystem);
}

// Not using this function for now because there are breaking issues with conda run (conda 4.8, PVSC 2020.1).
// See https://github.com/microsoft/vscode-python/issues/9490
public async createCondaExecutionService(
pythonPath: string,
processService?: IProcessService,
Expand All @@ -144,6 +159,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
procService.on('exec', this.logger.logProcess.bind(this.logger));
this.disposables.push(procService);
}

return createPythonService(
pythonPath,
procService,
Expand Down
1 change: 1 addition & 0 deletions src/client/common/process/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const IPythonExecutionFactory = Symbol('IPythonExecutionFactory');
export type ExecutionFactoryCreationOptions = {
resource?: Uri;
pythonPath?: string;
bypassCondaExecution?: boolean;
};
export type ExecutionFactoryCreateWithEnvironmentOptions = {
resource?: Uri;
Expand Down
3 changes: 2 additions & 1 deletion src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ export interface IInstaller {
resource?: InterpreterUri,
cancel?: CancellationToken,
flags?: ModuleInstallFlags,
bypassCondaExecution?: boolean,
): Promise<InstallerResponse>;
isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean>;
isInstalled(product: Product, resource?: InterpreterUri, bypassCondaExecution?: boolean): Promise<boolean>;
isProductVersionCompatible(
product: Product,
semVerRequirement: string,
Expand Down
2 changes: 1 addition & 1 deletion src/client/linters/flake8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class Flake8 extends BaseLinter {

protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
const messages = await this.run(
['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath],
['--format= %(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does adding a space here (and in src/client/linters/pycodestyle.ts) do?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it makes all the value inside "", "--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns the single quote into double quotes? Could you explain how adding a space in the argument does that? I don't understand 😅

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very sure, but internally when you pass a parameter by array to the run function, if it has any special character or spaces it renders it with "". The same thing happens with path directory, when the name has a space, it appears in the console with "".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that happens because we quote arguments if there is a space in them. We have code that adds quote if the path or arguments have some characters.

document,
cancellation,
);
Expand Down
2 changes: 1 addition & 1 deletion src/client/linters/pycodestyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class Pycodestyle extends BaseLinter {

protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
const messages = await this.run(
['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath],
['--format= %(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath],
document,
cancellation,
);
Expand Down
4 changes: 2 additions & 2 deletions src/test/common/installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ suite('Installer', () => {
}
callback({ stdout: '' });
});
await installer.isInstalled(product, resource);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you do not bypass conda execution here, ideally I would expect the tests to pass even in that case. Can you attempt to fix the test instead of using this option?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests were throwing a timeout error, I tried to fix it but it wasn't consistent and it was throwing another error, that's why I put the timeout.

Copy link

@karrtikr karrtikr Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests were throwing a timeout error

I assume this is on CI as tests seem to be passing locally for me. It's fine to put the timeout or even increase timeout, but I think we shouldn't need to bypass conda execution. It's weird as conda execution should not even be used if we're not using a conda interpreter.

await installer.isInstalled(product, resource, true);
await checkInstalledDef.promise;
}
getNamesAndValues<Product>(Product).forEach((prod) => {
Expand Down Expand Up @@ -333,7 +333,7 @@ suite('Installer', () => {
checkInstalledDef.resolve();
}
});
await installer.install(product);
await installer.install(product, undefined, undefined, undefined, true);
await checkInstalledDef.promise;
}
getNamesAndValues<Product>(Product).forEach((prod) => {
Expand Down
26 changes: 18 additions & 8 deletions src/test/common/process/pythonEnvironment.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ suite('CondaEnvironment', () => {

expect(result).to.deep.equal({
command: condaFile,
args: ['run', '-n', condaInfo.name, 'python', ...args],
python: [condaFile, 'run', '-n', condaInfo.name, 'python'],
args: ['run', '-n', condaInfo.name, '--no-capture-output', 'python', ...args],
python: [condaFile, 'run', '-n', condaInfo.name, '--no-capture-output', 'python'],
pythonExecutable: 'python',
});
});
Expand All @@ -309,25 +309,35 @@ suite('CondaEnvironment', () => {

expect(result).to.deep.equal({
command: condaFile,
args: ['run', '-p', condaInfo.path, 'python', ...args],
python: [condaFile, 'run', '-p', condaInfo.path, 'python'],
args: ['run', '-p', condaInfo.path, '--no-capture-output', 'python', ...args],
python: [condaFile, 'run', '-p', condaInfo.path, '--no-capture-output', 'python'],
pythonExecutable: 'python',
});
});

test('getExecutionObservableInfo with a named environment should return execution info using pythonPath only', () => {
const expected = { command: pythonPath, args, python: [pythonPath], pythonExecutable: pythonPath };
test('getExecutionObservableInfo with a named environment should return execution info using conda full path with the name', () => {
const condaInfo = { name: 'foo', path: 'bar' };
const expected = {
command: condaFile,
args: ['run', '-n', condaInfo.name, '--no-capture-output', 'python', ...args],
python: [condaFile, 'run', '-n', condaInfo.name, '--no-capture-output', 'python'],
pythonExecutable: 'python',
};
const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object);

const result = env.getExecutionObservableInfo(args);

expect(result).to.deep.equal(expected);
});

test('getExecutionObservableInfo with a non-named environment should return execution info using pythonPath only', () => {
const expected = { command: pythonPath, args, python: [pythonPath], pythonExecutable: pythonPath };
test('getExecutionObservableInfo with a non-named environment should return execution info using conda full path', () => {
const condaInfo = { name: '', path: 'bar' };
const expected = {
command: condaFile,
args: ['run', '-p', condaInfo.path, '--no-capture-output', 'python', ...args],
python: [condaFile, 'run', '-p', condaInfo.path, '--no-capture-output', 'python'],
pythonExecutable: 'python',
};
const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object);

const result = env.getExecutionObservableInfo(args);
Expand Down
16 changes: 4 additions & 12 deletions src/test/common/process/pythonExecutionFactory.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,7 @@ suite('Process - PythonExecutionFactory', () => {
assert.strictEqual(createInvoked, false);
});

test('Ensure `create` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async function () {
return this.skip();

test('Ensure `create` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async () => {
const pythonPath = 'path/to/python';
const pythonSettings = mock(PythonSettings);

Expand All @@ -284,9 +282,7 @@ suite('Process - PythonExecutionFactory', () => {
verify(condaService.getCondaFile()).once();
});

test('Ensure `create` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async function () {
return this.skip();

test('Ensure `create` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => {
const pythonPath = 'path/to/python';
const pythonSettings = mock(PythonSettings);
when(processFactory.create(resource)).thenResolve(processService.object);
Expand All @@ -305,9 +301,7 @@ suite('Process - PythonExecutionFactory', () => {
verify(condaService.getCondaFile()).once();
});

test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async function () {
return this.skip();

test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async () => {
const pythonPath = 'path/to/python';
const pythonSettings = mock(PythonSettings);

Expand Down Expand Up @@ -336,9 +330,7 @@ suite('Process - PythonExecutionFactory', () => {
}
});

test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async function () {
return this.skip();

test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => {
let createInvoked = false;
const pythonPath = 'path/to/python';
const mockExecService = 'mockService';
Expand Down
6 changes: 3 additions & 3 deletions src/test/format/extension.sort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ suite('Sorting', () => {
await window.showTextDocument(textDocument);
await commands.executeCommand(Commands.Sort_Imports);
assert.notStrictEqual(originalContent, textDocument.getText(), 'Contents have not changed');
});
}).timeout(TEST_TIMEOUT * 3);

test('With Config', async () => {
const textDocument = await workspace.openTextDocument(fileToFormatWithConfig);
Expand All @@ -130,7 +130,7 @@ suite('Sorting', () => {
await window.showTextDocument(textDocument);
await commands.executeCommand(Commands.Sort_Imports);
assert.notStrictEqual(originalContent, textDocument.getText(), 'Contents have not changed');
});
}).timeout(TEST_TIMEOUT * 3);

test('With Changes and Config in Args', async () => {
await updateSetting(
Expand Down Expand Up @@ -164,7 +164,7 @@ suite('Sorting', () => {
const originalContent = textDocument.getText();
await commands.executeCommand(Commands.Sort_Imports);
assert.notStrictEqual(originalContent, textDocument.getText(), 'Contents have not changed');
}).timeout(TEST_TIMEOUT * 2);
}).timeout(TEST_TIMEOUT * 3);

test('With Changes and Config implicit from cwd', async () => {
const textDocument = await workspace.openTextDocument(fileToFormatWithConfig);
Expand Down
4 changes: 2 additions & 2 deletions src/test/linters/lint.args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ suite('Linting - Arguments', () => {
}
test('Flake8', async () => {
const linter = new Flake8(serviceContainer);
const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath];
const expectedArgs = ['--format= %(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath];
await testLinter(linter, expectedArgs);
});
test('Pycodestyle', async () => {
const linter = new Pycodestyle(serviceContainer);
const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath];
const expectedArgs = ['--format= %(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath];
await testLinter(linter, expectedArgs);
});
test('Prospector', async () => {
Expand Down
8 changes: 8 additions & 0 deletions src/test/linters/lint.functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,11 @@ class TestFixture extends BaseTestFixture {
const serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>(undefined, TypeMoq.MockBehavior.Strict);
const configService = TypeMoq.Mock.ofType<IConfigurationService>(undefined, TypeMoq.MockBehavior.Strict);
const processLogger = TypeMoq.Mock.ofType<IProcessLogger>(undefined, TypeMoq.MockBehavior.Strict);
const componentAdapter = TypeMoq.Mock.ofType<IComponentAdapter>(undefined, TypeMoq.MockBehavior.Strict);
componentAdapter
.setup((c) => c.getCondaEnvironment(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(undefined));

const filesystem = new FileSystem();
processLogger
.setup((p) => p.logProcess(TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
Expand All @@ -637,6 +642,9 @@ class TestFixture extends BaseTestFixture {
serviceContainer
.setup((s) => s.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny()))
.returns(() => filesystem);
serviceContainer
.setup((s) => s.get(TypeMoq.It.isValue(IComponentAdapter), TypeMoq.It.isAny()))
.returns(() => componentAdapter.object);

const platformService = new PlatformService();

Expand Down