diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 6899e248da85..6fd7d3ffa818 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -5,7 +5,6 @@ import { EventEmitter } from 'events'; import * as path from 'path'; import * as vscode from 'vscode'; import { Uri } from 'vscode'; -import { InterpreterInfoCache } from './interpreterInfoCache'; import { SystemVariables } from './variables/systemVariables'; // tslint:disable-next-line:no-require-imports no-var-requires @@ -188,12 +187,10 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { // tslint:disable-next-line:no-unsafe-any this.disposables.forEach(disposable => disposable.dispose()); this.disposables = []; - InterpreterInfoCache.clear(); } // tslint:disable-next-line:cyclomatic-complexity max-func-body-length private initializeSettings() { - InterpreterInfoCache.clear(); const workspaceRoot = this.workspaceRoot.fsPath; const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); diff --git a/src/client/common/interpreterInfoCache.ts b/src/client/common/interpreterInfoCache.ts deleted file mode 100644 index f23f6ef7563b..000000000000 --- a/src/client/common/interpreterInfoCache.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Uri, workspace } from 'vscode'; - -type InterpreterCache = { - pythonInterpreterDirectory?: string; - pythonInterpreterPath?: string; - pythonSettingsPath?: string; - // tslint:disable-next-line:no-any - customEnvVariables?: any; -}; - -const cache = new Map(); - -// tslint:disable-next-line:no-stateless-class -export class InterpreterInfoCache { - // tslint:disable-next-line:function-name - public static clear(): void { - cache.clear(); - } - // tslint:disable-next-line:function-name - public static get(resource?: Uri) { - const cacheKey = InterpreterInfoCache.getCacheKey(resource) || ''; - return cache.has(cacheKey) ? cache.get(cacheKey) : {}; - } - // tslint:disable-next-line:function-name - public static setPaths(resource?: Uri, pythonSettingsPath?: string, pythonInterpreterPath?: string, pythonInterpreterDirectory?: string) { - InterpreterInfoCache.setCacheData('pythonInterpreterDirectory', resource, pythonInterpreterDirectory); - InterpreterInfoCache.setCacheData('pythonInterpreterPath', resource, pythonInterpreterPath); - InterpreterInfoCache.setCacheData('pythonSettingsPath', resource, pythonSettingsPath); - } - - // tslint:disable-next-line:no-any function-name - public static setCustomEnvVariables(resource?: Uri, envVars?: any) { - // tslint:disable-next-line:no-any - InterpreterInfoCache.setCacheData('customEnvVariables', resource, envVars); - } - // tslint:disable-next-line:no-any function-name - private static setCacheData(property: keyof InterpreterCache, resource?: Uri, value?: any) { - const cacheKey = InterpreterInfoCache.getCacheKey(resource) || ''; - // tslint:disable-next-line:prefer-type-cast - const data = cache.has(cacheKey) ? cache.get(cacheKey) : {} as InterpreterCache; - data[property] = value; - cache.set(cacheKey, data); - } - private static getCacheKey(resource?: Uri): string { - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { - return ''; - } - if (!resource || workspace.workspaceFolders.length === 1) { - return workspace.workspaceFolders[0].uri.fsPath; - } - const folder = workspace.getWorkspaceFolder(resource); - return folder ? folder.uri.fsPath : ''; - } -} diff --git a/src/client/common/utils.ts b/src/client/common/utils.ts index 25e9a720ca01..a8cd76e79628 100644 --- a/src/client/common/utils.ts +++ b/src/client/common/utils.ts @@ -8,11 +8,9 @@ import * as fs from 'fs'; import * as fsExtra from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; -import { CancellationToken, Range, TextDocument, Uri } from 'vscode'; +import { Position, Range, TextDocument, Uri } from 'vscode'; import * as settings from './configSettings'; -import { mergeEnvVariables, parseEnvFile } from './envFileParser'; -import { isNotInstalledError } from './helpers'; -import { InterpreterInfoCache } from './interpreterInfoCache'; +import { parseEnvFile } from './envFileParser'; export const IS_WINDOWS = /^win/.test(process.platform); export const Is_64Bit = os.arch() === 'x64'; @@ -53,51 +51,6 @@ export function fsReaddirAsync(root: string): Promise { }); } -async function getPythonInterpreterDirectory(resource?: Uri): Promise { - const cache = InterpreterInfoCache.get(resource); - const pythonFileName = settings.PythonSettings.getInstance(resource).pythonPath; - - // If we already have it and the python path hasn't changed, yay - if (cache.pythonInterpreterDirectory && cache.pythonInterpreterDirectory.length > 0 - && cache.pythonSettingsPath === pythonFileName) { - return cache.pythonInterpreterDirectory; - } - - // Check if we have the path - if (path.basename(pythonFileName) === pythonFileName) { - try { - const pythonInterpreterPath = await getPathFromPythonCommand(pythonFileName); - const pythonInterpreterDirectory = path.dirname(pythonInterpreterPath); - InterpreterInfoCache.setPaths(resource, pythonFileName, pythonInterpreterPath, pythonInterpreterDirectory); - return pythonInterpreterDirectory; - // tslint:disable-next-line:variable-name - } catch (_ex) { - InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, ''); - return ''; - } - } - - return new Promise(resolve => { - // If we can execute the python, then get the path from the fully qualified name - child_process.execFile(pythonFileName, ['-c', 'print(1234)'], (error, stdout, stderr) => { - // Yes this is a valid python path - if (stdout.startsWith('1234')) { - const pythonInterpreterDirectory = path.dirname(pythonFileName); - InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, pythonInterpreterDirectory); - resolve(pythonInterpreterDirectory); - } else { - // No idea, didn't work, hence don't reject, but return empty path - InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, ''); - resolve(''); - } - }); - }); -} -export async function getFullyQualifiedPythonInterpreterPath(resource?: Uri): Promise { - const pyDir = await getPythonInterpreterDirectory(resource); - const cache = InterpreterInfoCache.get(resource); - return cache.pythonInterpreterPath; -} export async function getPathFromPythonCommand(pythonPath: string): Promise { return await new Promise((resolve, reject) => { child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { @@ -110,197 +63,6 @@ export async function getPathFromPythonCommand(pythonPath: string): Promise { - const cache = InterpreterInfoCache.get(resource); - if (cache.customEnvVariables) { - return cache.customEnvVariables; - } - - const pyPath = await getPythonInterpreterDirectory(resource); - let customEnvVariables = await getCustomEnvVars(resource) || {}; - - if (pyPath.length > 0) { - // Ensure to include the path of the current python. - let newPath = ''; - const currentPath = typeof customEnvVariables[PATH_VARIABLE_NAME] === 'string' ? customEnvVariables[PATH_VARIABLE_NAME] : process.env[PATH_VARIABLE_NAME]; - if (IS_WINDOWS) { - newPath = `${pyPath}\\${path.delimiter}${path.join(pyPath, 'Scripts\\')}${path.delimiter}${currentPath}`; - // This needs to be done for windows. - process.env[PATH_VARIABLE_NAME] = newPath; - } else { - newPath = `${pyPath}${path.delimiter}${currentPath}`; - } - customEnvVariables = mergeEnvVariables(customEnvVariables, process.env); - customEnvVariables[PATH_VARIABLE_NAME] = newPath; - } - - InterpreterInfoCache.setCustomEnvVariables(resource, customEnvVariables); - return customEnvVariables; -} -export async function execPythonFile(resource: string | Uri | undefined, file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken): Promise { - const resourceUri = typeof resource === 'string' ? Uri.file(resource) : resource; - const env = await getEnvVariables(resourceUri); - const options = { cwd, env }; - - if (stdOut) { - return spawnFileInternal(file, args, options, includeErrorAsResponse, stdOut, token); - } - - const fileIsPythonInterpreter = (file.toUpperCase() === 'PYTHON' || file === settings.PythonSettings.getInstance(resourceUri).pythonPath); - const execAsModule = fileIsPythonInterpreter && args.length > 0 && args[0] === '-m'; - - if (execAsModule) { - return getFullyQualifiedPythonInterpreterPath(resourceUri) - .then(p => execPythonModule(p, args, options, includeErrorAsResponse, token)); - } - return execFileInternal(file, args, options, includeErrorAsResponse, token); -} - -function handleResponse(file: string, includeErrorAsResponse: boolean, error: Error, stdout: string, stderr: string, token?: CancellationToken): Promise { - if (token && token.isCancellationRequested) { - return Promise.resolve(undefined); - } - if (isNotInstalledError(error)) { - return Promise.reject(error); - } - - // pylint: - // In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr - // These error messages are useless when using pylint - if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) { - return Promise.resolve(stdout + '\n' + stderr); - } - - let hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0); - if (hasErrors && (typeof stdout !== 'string' || stdout.length === 0)) { - let errorMsg = (error && error.message) ? error.message : (stderr && stderr.length > 0 ? stderr + '' : ''); - return Promise.reject(errorMsg); - } - else { - return Promise.resolve(stdout + ''); - } -} -function handlePythonModuleResponse(includeErrorAsResponse: boolean, error: Error, stdout: string, stderr: string, token?: CancellationToken): Promise { - if (token && token.isCancellationRequested) { - return Promise.resolve(undefined); - } - if (isNotInstalledError(error)) { - return Promise.reject(error); - } - - // pylint: - // In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr - // These error messages are useless when using pylint - if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) { - return Promise.resolve(stdout + '\n' + stderr); - } - if (!includeErrorAsResponse && stderr.length > 0) { - return Promise.reject(stderr); - } - - return Promise.resolve(stdout + ''); -} -function execPythonModule(file: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean, token?: CancellationToken): Promise { - options.maxBuffer = options.maxBuffer ? options.maxBuffer : 1024 * 102400; - return new Promise((resolve, reject) => { - let proc = child_process.execFile(file, args, options, (error, stdout, stderr) => { - handlePythonModuleResponse(includeErrorAsResponse, error, stdout, stderr, token) - .then(resolve) - .catch(reject); - }); - if (token && token.onCancellationRequested) { - token.onCancellationRequested(() => { - if (proc) { - proc.kill(); - proc = null; - } - }); - } - }); -} - -function execFileInternal(file: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean, token?: CancellationToken): Promise { - options.maxBuffer = options.maxBuffer ? options.maxBuffer : 1024 * 102400; - return new Promise((resolve, reject) => { - let proc = child_process.execFile(file, args, options, (error, stdout, stderr) => { - handleResponse(file, includeErrorAsResponse, error, stdout, stderr, token) - .then(data => resolve(data)) - .catch(err => reject(err)); - }); - if (token && token.onCancellationRequested) { - token.onCancellationRequested(() => { - if (proc) { - proc.kill(); - proc = null; - } - }); - } - }); -} -function spawnFileInternal(file: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean, stdOut: (line: string) => void, token?: CancellationToken): Promise { - return new Promise((resolve, reject) => { - options.env = options.env || {}; - options.env['PYTHONIOENCODING'] = 'UTF-8'; - let proc = child_process.spawn(file, args, options); - let error = ''; - let exited = false; - if (token && token.onCancellationRequested) { - token.onCancellationRequested(() => { - if (!exited && proc) { - proc.kill(); - proc = null; - } - }); - } - proc.on('error', error => { - reject(error); - }); - proc.stdout.setEncoding('utf8'); - proc.stderr.setEncoding('utf8'); - proc.stdout.on('data', function (data: string) { - if (token && token.isCancellationRequested) { - return; - } - stdOut(data); - }); - - proc.stderr.on('data', function (data: string) { - if (token && token.isCancellationRequested) { - return; - } - if (includeErrorAsResponse) { - stdOut(data); - } - else { - error += data; - } - }); - - proc.on('exit', function (code) { - exited = true; - - if (token && token.isCancellationRequested) { - return reject(); - } - if (error.length > 0) { - return reject(error); - } - - resolve(); - }); - - }); -} -function execInternal(command: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean): Promise { - return new Promise((resolve, reject) => { - child_process.exec([command].concat(args).join(' '), options, (error, stdout, stderr) => { - handleResponse(command, includeErrorAsResponse, error, stdout, stderr) - .then(data => resolve(data)) - .catch(err => reject(err)); - }); - }); -} - export function formatErrorForLogging(error: Error | string): string { let message: string = ''; if (typeof error === 'string') { @@ -332,7 +94,7 @@ export function getSubDirectories(rootDir: string): Promise { if (error) { return resolve([]); } - const subDirs = []; + const subDirs: string[] = []; files.forEach(name => { const fullPath = path.join(rootDir, name); try { @@ -341,7 +103,7 @@ export function getSubDirectories(rootDir: string): Promise { } } // tslint:disable-next-line:no-empty - catch (ex) {} + catch (ex) { } }); resolve(subDirs); }); @@ -396,7 +158,7 @@ export function getWindowsLineEndingCount(document: TextDocument, offset: Number // In order to prevent the one-time loading of large files from taking up too much memory for (let pos = 0; pos < offset; pos += readBlock) { let startAt = document.positionAt(pos); - let endAt = null; + let endAt: Position; if (offsetDiff >= readBlock) { endAt = document.positionAt(pos + readBlock); @@ -405,7 +167,7 @@ export function getWindowsLineEndingCount(document: TextDocument, offset: Number endAt = document.positionAt(pos + offsetDiff); } - let text = document.getText(new Range(startAt, endAt)); + let text = document.getText(new Range(startAt, endAt!)); let cr = text.match(eolPattern); count += cr ? cr.length : 0; @@ -422,13 +184,3 @@ export function arePathsSame(path1: string, path2: string) { return path1 === path2; } } - -export async function getInterpreterVersion(pythonPath: string) { - return await new Promise((resolve, reject) => { - child_process.execFile(pythonPath, ['--version'], (error, stdout, stdErr) => { - const out = (typeof stdErr === 'string' ? stdErr : '') + os.EOL + (typeof stdout === 'string' ? stdout : ''); - const lines = out.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0); - resolve(lines.length > 0 ? lines[0] : ''); - }); - }); -} diff --git a/src/client/extension.ts b/src/client/extension.ts index d2f753dbef60..61a6c5b74d5d 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -21,9 +21,8 @@ import { SimpleConfigurationProvider } from './debugger'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { InterpreterManager } from './interpreter'; import { SetInterpreterProvider } from './interpreter/configuration/setInterpreterProvider'; -import { ICondaLocatorService } from './interpreter/contracts'; +import { ICondaLocatorService, IInterpreterVersionService } from './interpreter/contracts'; import { ShebangCodeLensProvider } from './interpreter/display/shebangCodeLensProvider'; -import { InterpreterVersionService } from './interpreter/interpreterVersion'; import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; @@ -94,12 +93,12 @@ export async function activate(context: vscode.ExtensionContext) { interpreterManager.refresh() .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); context.subscriptions.push(interpreterManager); - const interpreterVersionService = new InterpreterVersionService(); + const interpreterVersionService = serviceContainer.get(IInterpreterVersionService); context.subscriptions.push(new SetInterpreterProvider(interpreterManager, interpreterVersionService)); context.subscriptions.push(...activateExecInTerminalProvider()); context.subscriptions.push(activateUpdateSparkLibraryProvider()); activateSimplePythonRefactorProvider(context, standardOutputChannel, serviceContainer); - const jediFactory = new JediFactory(context.asAbsolutePath('.')); + const jediFactory = new JediFactory(context.asAbsolutePath('.'), serviceContainer); context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); context.subscriptions.push(new ReplProvider()); diff --git a/src/client/interpreter/interpreterVersion.ts b/src/client/interpreter/interpreterVersion.ts index 12a7aa60e139..dfb7ce7ec494 100644 --- a/src/client/interpreter/interpreterVersion.ts +++ b/src/client/interpreter/interpreterVersion.ts @@ -1,34 +1,30 @@ -import * as child_process from 'child_process'; -import { injectable } from 'inversify'; -import { getInterpreterVersion } from '../common/utils'; +import { inject, injectable } from 'inversify'; +import '../common/extensions'; +import { IProcessService } from '../common/process/types'; import { IInterpreterVersionService } from './contracts'; const PIP_VERSION_REGEX = '\\d\\.\\d(\\.\\d)+'; @injectable() export class InterpreterVersionService implements IInterpreterVersionService { + constructor( @inject(IProcessService) private processService: IProcessService) { } public async getVersion(pythonPath: string, defaultValue: string): Promise { - return getInterpreterVersion(pythonPath) + return this.processService.exec(pythonPath, ['--version'], { mergeStdOutErr: true }) + .then(output => output.stdout.splitLines()[0]) .then(version => version.length === 0 ? defaultValue : version) .catch(() => defaultValue); } public async getPipVersion(pythonPath: string): Promise { - return new Promise((resolve, reject) => { - child_process.execFile(pythonPath, ['-m', 'pip', '--version'], (error, stdout, stdErr) => { - if (stdout && stdout.length > 0) { - // Take the first available version number, see below example. - // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). - // Take the second part, see below example. - // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). - const re = new RegExp(PIP_VERSION_REGEX, 'g'); - const matches = re.exec(stdout); - if (matches && matches.length > 0) { - resolve(matches[0].trim()); - return; - } - } - reject(); - }); - }); + const output = await this.processService.exec(pythonPath, ['-m', 'pip', '--version'], { mergeStdOutErr: true }); + if (output.stdout.length > 0) { + // Here's a sample output: + // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). + const re = new RegExp(PIP_VERSION_REGEX, 'g'); + const matches = re.exec(output.stdout); + if (matches && matches.length > 0) { + return matches[0].trim(); + } + } + throw new Error(`Unable to determine pip version from output '${output.stdout}'`); } } diff --git a/src/client/languageServices/jediProxyFactory.ts b/src/client/languageServices/jediProxyFactory.ts index ea1745b59b3c..8ac6b04349d5 100644 --- a/src/client/languageServices/jediProxyFactory.ts +++ b/src/client/languageServices/jediProxyFactory.ts @@ -1,11 +1,12 @@ import { Disposable, Uri, workspace } from 'vscode'; +import { IServiceContainer } from '../ioc/types'; import { ICommandResult, JediProxy, JediProxyHandler } from '../providers/jediProxy'; export class JediFactory implements Disposable { private disposables: Disposable[]; private jediProxyHandlers: Map>; - constructor(private extensionRootPath: string) { + constructor(private extensionRootPath: string, private serviceContainer: IServiceContainer) { this.disposables = []; this.jediProxyHandlers = new Map>(); } @@ -26,7 +27,7 @@ export class JediFactory implements Disposable { } if (!this.jediProxyHandlers.has(workspacePath)) { - const jediProxy = new JediProxy(this.extensionRootPath, workspacePath); + const jediProxy = new JediProxy(this.extensionRootPath, workspacePath, this.serviceContainer); const jediProxyHandler = new JediProxyHandler(jediProxy); this.disposables.push(jediProxy, jediProxyHandler); this.jediProxyHandlers.set(workspacePath, jediProxyHandler); diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index a5916c6a9b15..f63b831b898e 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -1,14 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; import * as child_process from 'child_process'; import * as path from 'path'; import * as vscode from 'vscode'; +import { Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; -import { mergeEnvVariables } from '../common/envFileParser'; +import '../common/extensions'; import { createDeferred, Deferred } from '../common/helpers'; -import { execPythonFile, getCustomEnvVarsSync, validatePath } from '../common/utils'; +import { IPythonExecutionFactory } from '../common/process/types'; +import { getCustomEnvVarsSync, validatePath } from '../common/utils'; +import { IServiceContainer } from '../ioc/types'; import * as logger from './../common/logger'; const IS_WINDOWS = /^win/.test(process.platform); @@ -135,8 +137,9 @@ export class JediProxy implements vscode.Disposable { private lastKnownPythonPath: string; private additionalAutoCopletePaths: string[] = []; private workspacePath: string; + private initialized: Deferred; - public constructor(extensionRootDir: string, workspacePath: string) { + public constructor(extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) { this.workspacePath = workspacePath; this.pythonSettings = PythonSettings.getInstance(vscode.Uri.file(workspacePath)); this.lastKnownPythonInterpreter = this.pythonSettings.pythonPath; @@ -160,7 +163,8 @@ export class JediProxy implements vscode.Disposable { return result; } - public sendCommand(cmd: ICommand): Promise { + public async sendCommand(cmd: ICommand): Promise { + await this.initialized.promise; if (!this.proc) { return Promise.reject(new Error('Python proc not initialized')); } @@ -190,7 +194,13 @@ export class JediProxy implements vscode.Disposable { // keep track of the directory so we can re-spawn the process. private initialize(dir: string) { this.pythonProcessCWD = dir; - this.spawnProcess(path.join(dir, 'pythonFiles')); + this.spawnProcess(path.join(dir, 'pythonFiles')) + .catch(ex => { + if (this.initialized) { + this.initialized.reject(ex); + } + this.handleError('spawnProcess', ex); + }); } // Check if settings changes. @@ -228,45 +238,31 @@ export class JediProxy implements vscode.Disposable { } // tslint:disable-next-line:max-func-body-length - private spawnProcess(dir: string) { - try { - let environmentVariables: Object & { [key: string]: string } = { PYTHONUNBUFFERED: '1' }; - const customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(dir)); - if (customEnvironmentVars) { - environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars); - } - environmentVariables = mergeEnvVariables(environmentVariables); - - const args = ['completion.py']; - if (typeof this.pythonSettings.jediPath !== 'string' || this.pythonSettings.jediPath.length === 0) { - if (Array.isArray(this.pythonSettings.devOptions) && - this.pythonSettings.devOptions.some(item => item.toUpperCase().trim() === 'USERELEASEAUTOCOMP')) { - // Use standard version of jedi library. - args.push('std'); - } else { - // Use preview version of jedi library. - args.push('preview'); - } + private async spawnProcess(cwd: string) { + this.initialized = createDeferred(); + const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(this.workspacePath)); + const args = ['completion.py']; + if (typeof this.pythonSettings.jediPath !== 'string' || this.pythonSettings.jediPath.length === 0) { + if (Array.isArray(this.pythonSettings.devOptions) && + this.pythonSettings.devOptions.some(item => item.toUpperCase().trim() === 'USERELEASEAUTOCOMP')) { + // Use standard version of jedi. + args.push('std'); } else { - args.push('custom'); - args.push(this.pythonSettings.jediPath); + // Use preview version of jedi. + args.push('preview'); } - if (Array.isArray(this.pythonSettings.autoComplete.preloadModules) && - this.pythonSettings.autoComplete.preloadModules.length > 0) { - const modules = this.pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(','); - args.push(modules); - } - this.proc = child_process.spawn(this.pythonSettings.pythonPath, args, { - cwd: dir, - env: environmentVariables - }); - } catch (ex) { - return this.handleError('spawnProcess', ex.message); + } else { + args.push('custom'); + args.push(this.pythonSettings.jediPath); } - this.proc.stderr.setEncoding('utf8'); - this.proc.stderr.on('data', (data: string) => { - this.handleError('stderr', data); - }); + if (Array.isArray(this.pythonSettings.autoComplete.preloadModules) && + this.pythonSettings.autoComplete.preloadModules.length > 0) { + const modules = this.pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(','); + args.push(modules); + } + const result = pythonProcess.execObservable(args, { cwd }); + this.proc = result.proc; + this.initialized.resolve(); this.proc.on('end', (end) => { logger.error('spawnProcess.end', `End - ${end}`); }); @@ -275,89 +271,93 @@ export class JediProxy implements vscode.Disposable { this.spawnRetryAttempts += 1; if (this.spawnRetryAttempts < 10 && error && error.message && error.message.indexOf('This socket has been ended by the other party') >= 0) { - this.spawnProcess(dir); + this.spawnProcess(cwd) + .catch(ex => { + if (this.initialized) { + this.initialized.reject(ex); + } + this.handleError('spawnProcess', ex); + }); } }); - this.proc.stdout.setEncoding('utf8'); - // tslint:disable-next-line:max-func-body-length - this.proc.stdout.on('data', (data: string) => { - // Possible there was an exception in parsing the data returned, - // so append the data then parse it. - const dataStr = this.previousData = `${this.previousData}${data}`; - // tslint:disable-next-line:no-any - let responses: any[]; - try { - responses = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp)); - this.previousData = ''; - } catch (ex) { - // Possible we've only received part of the data, hence don't clear previousData. - // Don't log errors when we haven't received the entire response. - if (ex.message.indexOf('Unexpected end of input') === -1 && - ex.message.indexOf('Unexpected end of JSON input') === -1 && - ex.message.indexOf('Unexpected token') === -1) { - this.handleError('stdout', ex.message); - } - return; - } - - responses.forEach((response) => { - // What's this, can't remember, - // Great example of poorly written code (this whole file is a mess). - // I think this needs to be removed, because this is misspelt, it is argments, 'U' is missing, - // And that case is handled further down - // case CommandType.Arguments: { - - const responseId = JediProxy.getProperty(response, 'id'); - const cmd = >this.commands.get(responseId); - if (cmd === null) { + result.out.subscribe(output => { + if (output.source === 'stderr') { + this.handleError('stderr', output.out); + } else { + const data = output.out; + // Possible there was an exception in parsing the data returned, + // so append the data and then parse it. + const dataStr = this.previousData = `${this.previousData}${data}`; + // tslint:disable-next-line:no-any + let responses: any[]; + try { + responses = dataStr.splitLines().map(resp => JSON.parse(resp)); + this.previousData = ''; + } catch (ex) { + // Possible we've only received part of the data, hence don't clear previousData. + // Don't log errors when we haven't received the entire response. + if (ex.message.indexOf('Unexpected end of input') === -1 && + ex.message.indexOf('Unexpected end of JSON input') === -1 && + ex.message.indexOf('Unexpected token') === -1) { + this.handleError('stdout', ex.message); + } return; } - if (JediProxy.getProperty(response, 'arguments')) { - this.commandQueue.splice(this.commandQueue.indexOf(cmd.id), 1); - return; - } + responses.forEach((response) => { + const responseId = JediProxy.getProperty(response, 'id'); + const cmd = >this.commands.get(responseId); + if (cmd === null) { + return; + } - this.commands.delete(responseId); - const index = this.commandQueue.indexOf(cmd.id); - if (index) { - this.commandQueue.splice(index, 1); - } + if (JediProxy.getProperty(response, 'arguments')) { + this.commandQueue.splice(this.commandQueue.indexOf(cmd.id), 1); + return; + } - // Check if this command has expired. - if (cmd.token.isCancellationRequested) { - this.safeResolve(cmd, undefined); - return; - } + this.commands.delete(responseId); + const index = this.commandQueue.indexOf(cmd.id); + if (index) { + this.commandQueue.splice(index, 1); + } - switch (cmd.command) { - case CommandType.Completions: - this.onCompletion(cmd, response); - break; - case CommandType.Definitions: - this.onDefinition(cmd, response); - break; - case CommandType.Hover: - this.onHover(cmd, response); - break; - case CommandType.Symbols: - this.onSymbols(cmd, response); - break; - case CommandType.Usages: - this.onUsages(cmd, response); - break; - case CommandType.Arguments: - this.onArguments(cmd, response); - break; - default: - break; - } - // Check if too many pending requets. - this.checkQueueLength(); - }); - }); - } + // Check if this command has expired. + if (cmd.token.isCancellationRequested) { + this.safeResolve(cmd, undefined); + return; + } + const handler = this.getCommandHandler(cmd.command); + if (handler) { + handler.call(this, cmd, response); + } + // Check if too many pending requests. + this.checkQueueLength(); + }); + } + }, + error => this.handleError('subscription.error', `${error}`) + ); + } + private getCommandHandler(command: CommandType): undefined | ((command: IExecutionCommand, response: object) => void) { + switch (command) { + case CommandType.Completions: + return this.onCompletion; + case CommandType.Definitions: + return this.onDefinition; + case CommandType.Hover: + return this.onHover; + case CommandType.Symbols: + return this.onSymbols; + case CommandType.Usages: + return this.onUsages; + case CommandType.Arguments: + return this.onArguments; + default: + return; + } + } private onCompletion(command: IExecutionCommand, response: object): void { let results = JediProxy.getProperty(response, 'results'); results = Array.isArray(results) ? results : []; @@ -484,7 +484,7 @@ export class JediProxy implements vscode.Disposable { items.forEach(id => { if (this.commands.has(id)) { const cmd1 = this.commands.get(id); - try { + try { this.safeResolve(cmd1, undefined); // tslint:disable-next-line:no-empty } catch (ex) { @@ -517,16 +517,18 @@ export class JediProxy implements vscode.Disposable { return payload; } - private getPathFromPythonCommand(args: string[]): Promise { - return execPythonFile(this.workspacePath, this.pythonSettings.pythonPath, args, this.workspacePath).then(stdout => { - if (stdout.length === 0) { + private async getPathFromPythonCommand(args: string[]): Promise { + try { + const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(this.workspacePath)); + const result = await pythonProcess.exec(args, { cwd: this.workspacePath }); + const lines = result.stdout.trim().splitLines(); + if (lines.length === 0) { return ''; } - const lines = stdout.split(/\r?\n/g).filter(line => line.length > 0); - return validatePath(lines[0]); - }).catch(() => { + return await validatePath(lines[0]); + } catch { return ''; - }); + } } private onConfigChanged() { diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index e9efcfa8e15e..100e009e042f 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -1,21 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. - -// The module 'assert' provides assertion methods from node +// tslint:disable:no-unused-variable import * as assert from 'assert'; import { EOL } from 'os'; import * as path from 'path'; -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it import * as vscode from 'vscode'; -import * as settings from '../../client/common/configSettings'; -import { PythonSettings } from '../../client/common/configSettings'; -import { execPythonFile } from '../../client/common/utils'; import { rootWorkspaceUri } from '../common'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); const fileOne = path.join(autoCompPath, 'one.py'); @@ -29,29 +22,37 @@ const fileSuppress = path.join(autoCompPath, 'suppress.py'); // tslint:disable-next-line:max-func-body-length suite('Autocomplete', () => { - let isPython3: Promise; + let isPython2: boolean; + let ioc: UnitTestIocContainer; suiteSetup(async () => { await initialize(); - const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); - isPython3 = Promise.resolve(version.indexOf('3.') >= 0); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; }); setup(initializeTest); suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); + }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } test('For "sys."', done => { - let textEditor: vscode.TextEditor; let textDocument: vscode.TextDocument; vscode.workspace.openTextDocument(fileOne).then(document => { textDocument = document; return vscode.window.showTextDocument(textDocument); }).then(editor => { assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; const position = new vscode.Position(3, 10); return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); }).then(list => { - assert.equal(list.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); }).then(done, done); }); @@ -61,7 +62,7 @@ suite('Autocomplete', () => { await vscode.window.showTextDocument(textDocument); const position = new vscode.Position(1, 4); const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.equal(list.items.filter(item => item.label === 'fstat').length, 1, 'fstat not found'); + assert.equal(list!.items.filter(item => item.label === 'fstat').length, 1, 'fstat not found'); }); // https://github.com/DonJayamanne/pythonVSCode/issues/898 @@ -77,17 +78,19 @@ suite('Autocomplete', () => { }); // https://github.com/DonJayamanne/pythonVSCode/issues/265 - test('For "lambda"', async () => { - if (!await isPython3) { + test('For "lambda"', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } const textDocument = await vscode.workspace.openTextDocument(fileLambda); await vscode.window.showTextDocument(textDocument); const position = new vscode.Position(1, 19); const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'append').length, 0, 'append not found'); - assert.notEqual(list.items.filter(item => item.label === 'clear').length, 0, 'clear not found'); - assert.notEqual(list.items.filter(item => item.label === 'count').length, 0, 'cound not found'); + assert.notEqual(list!.items.filter(item => item.label === 'append').length, 0, 'append not found'); + assert.notEqual(list!.items.filter(item => item.label === 'clear').length, 0, 'clear not found'); + assert.notEqual(list!.items.filter(item => item.label === 'count').length, 0, 'cound not found'); }); // https://github.com/DonJayamanne/pythonVSCode/issues/630 @@ -96,18 +99,18 @@ suite('Autocomplete', () => { await vscode.window.showTextDocument(textDocument); let position = new vscode.Position(3, 9); let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); position = new vscode.Position(4, 9); list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); position = new vscode.Position(2, 30); list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); }); // https://github.com/DonJayamanne/pythonVSCode/issues/727 @@ -119,42 +122,38 @@ suite('Autocomplete', () => { const position = new vscode.Position(10, 9); const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - const items = list.items.filter(item => item.label === 'sleep'); + const items = list!.items.filter(item => item.label === 'sleep'); assert.notEqual(items.length, 0, 'sleep not found'); checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); }); test('For custom class', done => { - let textEditor: vscode.TextEditor; let textDocument: vscode.TextDocument; vscode.workspace.openTextDocument(fileOne).then(document => { textDocument = document; return vscode.window.showTextDocument(textDocument); }).then(editor => { assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; const position = new vscode.Position(30, 4); return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); }).then(list => { - assert.notEqual(list.items.filter(item => item.label === 'method1').length, 0, 'method1 not found'); - assert.notEqual(list.items.filter(item => item.label === 'method2').length, 0, 'method2 not found'); + assert.notEqual(list!.items.filter(item => item.label === 'method1').length, 0, 'method1 not found'); + assert.notEqual(list!.items.filter(item => item.label === 'method2').length, 0, 'method2 not found'); }).then(done, done); }); test('With Unicode Characters', done => { - let textEditor: vscode.TextEditor; let textDocument: vscode.TextDocument; vscode.workspace.openTextDocument(fileEncoding).then(document => { textDocument = document; return vscode.window.showTextDocument(textDocument); }).then(editor => { assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; const position = new vscode.Position(25, 4); return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); }).then(list => { - const items = list.items.filter(item => item.label === 'bar'); + const items = list!.items.filter(item => item.label === 'bar'); assert.equal(items.length, 1, 'bar not found'); const expected = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; @@ -163,22 +162,20 @@ suite('Autocomplete', () => { }); test('Across files With Unicode Characters', done => { - let textEditor: vscode.TextEditor; let textDocument: vscode.TextDocument; vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { textDocument = document; return vscode.window.showTextDocument(textDocument); }).then(editor => { assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; const position = new vscode.Position(1, 5); return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); }).then(list => { - let items = list.items.filter(item => item.label === 'Foo'); + let items = list!.items.filter(item => item.label === 'Foo'); assert.equal(items.length, 1, 'Foo not found'); checkDocumentation(items[0], '说明'); - items = list.items.filter(item => item.label === 'showMessage'); + items = list!.items.filter(item => item.label === 'showMessage'); assert.equal(items.length, 1, 'showMessage not found'); const expected = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; @@ -207,7 +204,7 @@ suite('Autocomplete', () => { await vscode.window.showTextDocument(textDocument); for (let i = 0; i < positions.length; i += 1) { const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, positions[i]); - const result = list.items.filter(item => item.label === 'abs').length; + const result = list!.items.filter(item => item.label === 'abs').length; assert.equal(result > 0, expected[i], `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); } @@ -220,6 +217,6 @@ function checkDocumentation(item: vscode.CompletionItem, expectedContains: strin assert.notEqual(documentation, null, 'Documentation is not MarkdownString'); const inDoc = documentation.value.indexOf(expectedContains) >= 0; - const inDetails = item.detail.indexOf(expectedContains) >= 0; + const inDetails = item.detail!.indexOf(expectedContains) >= 0; assert.equal(inDoc !== inDetails, true, 'Documentation incorrect'); } diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index c0c327bdf7d1..1eb13a792f1c 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -1,58 +1,60 @@ - -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. - - -// The module 'assert' provides assertion methods from node import * as assert from 'assert'; -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it -import * as vscode from 'vscode'; import * as path from 'path'; -import * as settings from '../../client/common/configSettings'; -import { execPythonFile } from '../../client/common/utils'; -import { initialize, closeActiveWindows, initializeTest } from '../initialize'; -import { PythonSettings } from '../../client/common/configSettings'; +import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); const filePep484 = path.join(autoCompPath, 'pep484.py'); suite('Autocomplete PEP 484', () => { - let isPython3: Promise; + let isPython2: boolean; + let ioc: UnitTestIocContainer; suiteSetup(async () => { await initialize(); - const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); - isPython3 = Promise.resolve(version.indexOf('3.') >= 0); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); }); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); - teardown(() => closeActiveWindows()); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } - test('argument', async () => { - if (!await isPython3) { + test('argument', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } - let textDocument = await vscode.workspace.openTextDocument(filePep484); + const textDocument = await vscode.workspace.openTextDocument(filePep484); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const position = new vscode.Position(2, 27); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); + assert.notEqual(list!.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); + assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); test('return value', async () => { - if (!await isPython3) { + if (isPython2) { return; } - let textDocument = await vscode.workspace.openTextDocument(filePep484); + const textDocument = await vscode.workspace.openTextDocument(filePep484); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const position = new vscode.Position(8, 6); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'bit_length').length, 0, 'bit_length not found'); - assert.notEqual(list.items.filter(item => item.label === 'from_bytes').length, 0, 'from_bytes not found'); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'bit_length').length, 0, 'bit_length not found'); + assert.notEqual(list!.items.filter(item => item.label === 'from_bytes').length, 0, 'from_bytes not found'); }); }); diff --git a/src/test/autocomplete/pep526.test.ts b/src/test/autocomplete/pep526.test.ts index 01dc988c000f..099df5af10ab 100644 --- a/src/test/autocomplete/pep526.test.ts +++ b/src/test/autocomplete/pep526.test.ts @@ -1,101 +1,110 @@ - -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. - - -// The module 'assert' provides assertion methods from node import * as assert from 'assert'; -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it -import * as vscode from 'vscode'; import * as path from 'path'; -import * as settings from '../../client/common/configSettings'; -import { execPythonFile } from '../../client/common/utils'; -import { initialize, closeActiveWindows, initializeTest } from '../initialize'; -import { PythonSettings } from '../../client/common/configSettings'; +import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); const filePep526 = path.join(autoCompPath, 'pep526.py'); suite('Autocomplete PEP 526', () => { - let isPython3: Promise; + let isPython2: boolean; + let ioc: UnitTestIocContainer; suiteSetup(async () => { await initialize(); - const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); - isPython3 = Promise.resolve(version.indexOf('3.') >= 0); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); }); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); - teardown(() => closeActiveWindows()); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } - test('variable (abc:str)', async () => { - if (!await isPython3) { + test('variable (abc:str)', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } - let textDocument = await vscode.workspace.openTextDocument(filePep526); + const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const position = new vscode.Position(9, 8); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); + assert.notEqual(list!.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); + assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('variable (abc: str = "")', async () => { - if (!await isPython3) { - return; + test('variable (abc: str = "")', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); } - let textDocument = await vscode.workspace.openTextDocument(filePep526); + const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const position = new vscode.Position(8, 14); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); + assert.notEqual(list!.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); + assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('variable (abc = UNKNOWN # type: str)', async () => { - if (!await isPython3) { + test('variable (abc = UNKNOWN # type: str)', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } - let textDocument = await vscode.workspace.openTextDocument(filePep526); + const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const position = new vscode.Position(7, 14); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); - assert.notEqual(list.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); - assert.notEqual(list.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'capitalize').length, 0, 'capitalize not found'); + assert.notEqual(list!.items.filter(item => item.label === 'upper').length, 0, 'upper not found'); + assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('class methods', async () => { - if (!await isPython3) { + test('class methods', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } - let textDocument = await vscode.workspace.openTextDocument(filePep526); + const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); let position = new vscode.Position(20, 4); let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'a').length, 0, 'method a not found'); + assert.notEqual(list!.items.filter(item => item.label === 'a').length, 0, 'method a not found'); position = new vscode.Position(21, 4); list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'b').length, 0, 'method b not found'); + assert.notEqual(list!.items.filter(item => item.label === 'b').length, 0, 'method b not found'); }); - test('class method types', async () => { - if (!await isPython3) { + test('class method types', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } - let textDocument = await vscode.workspace.openTextDocument(filePep526); + const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const position = new vscode.Position(21, 6); const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'bit_length').length, 0, 'bit_length not found'); + assert.notEqual(list!.items.filter(item => item.label === 'bit_length').length, 0, 'bit_length not found'); }); }); diff --git a/src/test/common/common.test.ts b/src/test/common/common.test.ts deleted file mode 100644 index 0b9070514fe7..000000000000 --- a/src/test/common/common.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as vscode from 'vscode'; -import { createDeferred } from '../../client/common/helpers'; -import { execPythonFile, getInterpreterVersion } from '../../client/common/utils'; -import { initialize } from './../initialize'; - -// Defines a Mocha test suite to group tests of similar kind together -suite('ChildProc', () => { - setup(initialize); - teardown(initialize); - test('Standard Response', done => { - execPythonFile(undefined, 'python', ['-c', 'print(1)'], __dirname, false).then(data => { - assert.ok(data === `1${EOL}`); - }).then(done).catch(done); - }); - test('Error Response', done => { - // tslint:disable-next-line:no-any - const def = createDeferred(); - execPythonFile(undefined, 'python', ['-c', 'print(1'], __dirname, false).then(() => { - def.reject('Should have failed'); - }).catch(() => { - def.resolve(); - }); - - def.promise.then(done).catch(done); - }); - - test('Stream Stdout', done => { - const output: string[] = []; - function handleOutput(data: string) { - if (data.trim().length > 0) { - output.push(data.trim()); - } - } - execPythonFile(undefined, 'python', ['-c', 'print(1)'], __dirname, false, handleOutput).then(() => { - assert.equal(output.length, 1, 'Ouput length incorrect'); - assert.equal(output[0], '1', 'Ouput value incorrect'); - }).then(done).catch(done); - }); - - test('Stream Stdout (Unicode)', async () => { - const output: string[] = []; - function handleOutput(data: string) { - if (data.trim().length > 0) { - output.push(data.trim()); - } - } - await execPythonFile(undefined, 'python', ['-c', 'print(\'öä\')'], __dirname, false, handleOutput); - assert.equal(output.length, 1, 'Ouput length incorrect'); - assert.equal(output[0], 'öä', 'Ouput value incorrect'); - }); - - test('Stream Stdout with Threads', function (done) { - // tslint:disable-next-line:no-invalid-this - this.timeout(6000); - const output: string[] = []; - function handleOutput(data: string) { - if (data.trim().length > 0) { - output.push(data.trim()); - } - } - execPythonFile(undefined, 'python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput).then(() => { - assert.equal(output.length, 2, 'Ouput length incorrect'); - assert.equal(output[0], '1', 'First Ouput value incorrect'); - assert.equal(output[1], '2', 'Second Ouput value incorrect'); - }).then(done).catch(done); - }); - - test('Kill', done => { - // tslint:disable-next-line:no-any - const def = createDeferred(); - const output: string[] = []; - function handleOutput(data: string) { - if (data.trim().length > 0) { - output.push(data.trim()); - } - } - const cancellation = new vscode.CancellationTokenSource(); - execPythonFile(undefined, 'python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput, cancellation.token).then(() => { - def.reject('Should not have completed'); - }).catch(() => { - def.resolve(); - }); - - setTimeout(() => { - cancellation.cancel(); - }, 1000); - - def.promise.then(done).catch(done); - }); - - test('Get Python display name', async () => { - const displayName = await getInterpreterVersion('python'); - assert.equal(typeof displayName, 'string', 'Display name not returned'); - assert.notEqual(displayName.length, 0, 'Display name cannot be empty'); - }); -}); diff --git a/src/test/definitions/code.test.ts b/src/test/definitions/code.test.ts deleted file mode 100644 index 90427872e61c..000000000000 --- a/src/test/definitions/code.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -// // Note: This example test is leveraging the Mocha test framework. -// // Please refer to their documentation on https://mochajs.org/ for help. - - -// // The module 'assert' provides assertion methods from node -// import * as assert from 'assert'; -// // You can import and use all API from the 'vscode' module -// // as well as import your extension to test it -// import * as vscode from 'vscode'; -// import * as path from 'path'; -// import * as settings from '../../client/common/configSettings'; -// import { execPythonFile } from '../../client/common/utils'; -// import { initialize, closeActiveWindows } from '../initialize'; - -// const pythonSettings = settings.PythonSettings.getInstance(); -// const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'definition'); -// const fileOne = path.join(autoCompPath, 'one.py'); -// const fileTwo = path.join(autoCompPath, 'two.py'); -// const fileThree = path.join(autoCompPath, 'three.py'); -// const fileDecorator = path.join(autoCompPath, 'decorators.py'); -// const fileAwait = path.join(autoCompPath, 'await.test.py'); -// const fileEncoding = path.join(autoCompPath, 'four.py'); -// const fileEncodingUsed = path.join(autoCompPath, 'five.py'); - - -// suite('Code Definition', () => { -// let isPython3: Promise; -// suiteSetup(async () => { -// await initialize(); -// let version = await execPythonFile(pythonSettings.pythonPath, ['--version'], __dirname, true); -// isPython3 = Promise.resolve(version.indexOf('3.') >= 0); -// }); - -// suiteTeardown(() => closeActiveWindows()); -// teardown(() => closeActiveWindows()); - -// test('Go to method', done => { -// let textEditor: vscode.TextEditor; -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(fileOne).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// textEditor = editor; -// const position = new vscode.Position(30, 5); -// return vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// }).then(def => { -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(def[0].uri.fsPath, fileOne, 'Incorrect file'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '17,4', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '21,11', 'End position is incorrect'); -// }).then(done, done); -// }); - -// test('Go to function', done => { -// let textEditor: vscode.TextEditor; -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(fileOne).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// textEditor = editor; -// const position = new vscode.Position(45, 5); -// return vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// }).then(def => { -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(def[0].uri.fsPath, fileOne, 'Incorrect file'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '32,0', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '33,21', 'End position is incorrect'); -// }).then(done, done); -// }); - -// test('Go to function with decorator', async () => { -// const textDocument = await vscode.workspace.openTextDocument(fileDecorator); -// await vscode.window.showTextDocument(textDocument); -// const position = new vscode.Position(7, 2); -// const def = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(def[0].uri.fsPath, fileDecorator, 'Incorrect file'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '4,0', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '5,22', 'End position is incorrect'); -// }); - -// test('Go to function with decorator (jit)', async () => { -// const textDocument = await vscode.workspace.openTextDocument(fileDecorator); -// await vscode.window.showTextDocument(textDocument); -// const position = new vscode.Position(27, 2); -// const def = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(def[0].uri.fsPath, fileDecorator, 'Incorrect file'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '19,0', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '26,42', 'End position is incorrect'); -// }); - -// test('Go to function with decorator (fabric)', async () => { -// const textDocument = await vscode.workspace.openTextDocument(fileDecorator); -// await vscode.window.showTextDocument(textDocument); -// const position = new vscode.Position(13, 2); -// const def = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// if (!def[0].uri.fsPath.endsWith('operations.py')) { -// assert.fail(def[0].uri.fsPath, 'operations.py', 'Source of sudo is incorrect', 'file source'); -// } -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '1094,0', 'Start position is incorrect (3rd part operations.py could have changed)'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '1148,4', 'End position is incorrect (3rd part operations.py could have changed)'); -// }); - -// test('Go to function decorator', async () => { -// const textDocument = await vscode.workspace.openTextDocument(fileDecorator); -// await vscode.window.showTextDocument(textDocument); -// const position = new vscode.Position(3, 3); -// const def = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '0,0', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '1,12', 'End position is incorrect'); -// }); - -// test('Go to async method', async () => { -// if (!await isPython3) { -// return; -// } -// const textDocument = await vscode.workspace.openTextDocument(fileAwait); -// await vscode.window.showTextDocument(textDocument); -// const position = new vscode.Position(10, 22); -// const def = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// assert.equal(def.length, 1, 'Definition length is incorrect (currently not working)'); -// assert.equal(def[0].uri.fsPath, fileAwait, 'Wrong file (currently not working)'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '6,10', 'Start position is incorrect (currently not working)'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '1,12', 'End position is incorrect (currently not working)'); -// }); - -// test('Go to async function', async () => { -// if (!await isPython3) { -// return; -// } -// const textDocument = await vscode.workspace.openTextDocument(fileAwait); -// await vscode.window.showTextDocument(textDocument); -// const position = new vscode.Position(18, 12); -// const def = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// assert.equal(def.length, 1, 'Definition length is incorrect (currently not working)'); -// assert.equal(def[0].uri.fsPath, fileAwait, 'Wrong file (currently not working)'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '6,10', 'Start position is incorrect (currently not working)'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '1,12', 'End position is incorrect (currently not working)'); -// }); - -// test('Across files', done => { -// let textEditor: vscode.TextEditor; -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(fileThree).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// textEditor = editor; -// const position = new vscode.Position(1, 5); -// return vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// }).then(def => { -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '0,0', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '5,11', 'End position is incorrect'); -// assert.equal(def[0].uri.fsPath, fileTwo, 'File is incorrect'); -// }).then(done, done); -// }); - -// test('With Unicode Characters', done => { -// let textEditor: vscode.TextEditor; -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(fileEncoding).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// textEditor = editor; -// const position = new vscode.Position(25, 6); -// return vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// }).then(def => { -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '10,4', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '16,35', 'End position is incorrect'); -// assert.equal(def[0].uri.fsPath, fileEncoding, 'File is incorrect'); -// }).then(done, done); -// }); - -// test('Across files with Unicode Characters', done => { -// let textEditor: vscode.TextEditor; -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// textEditor = editor; -// const position = new vscode.Position(1, 11); -// return vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); -// }).then(def => { -// assert.equal(def.length, 1, 'Definition length is incorrect'); -// assert.equal(`${def[0].range.start.line},${def[0].range.start.character}`, '18,0', 'Start position is incorrect'); -// assert.equal(`${def[0].range.end.line},${def[0].range.end.character}`, '23,16', 'End position is incorrect'); -// assert.equal(def[0].uri.fsPath, fileEncoding, 'File is incorrect'); -// }).then(done, done); -// }); -// }); diff --git a/src/test/interpreters/interpreterVersion.test.ts b/src/test/interpreters/interpreterVersion.test.ts index c0d294fee060..5478edab591e 100644 --- a/src/test/interpreters/interpreterVersion.test.ts +++ b/src/test/interpreters/interpreterVersion.test.ts @@ -1,31 +1,50 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + import { assert, expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { execPythonFile } from '../../client/common/utils'; -import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; -import { InterpreterVersionService } from '../../client/interpreter/interpreterVersion'; +import '../../client/common/extensions'; +import { IProcessService } from '../../client/common/process/types'; +import { IInterpreterVersionService } from '../../client/interpreter/contracts'; import { initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; use(chaiAsPromised); suite('Interpreters display version', () => { - const interpreterVersion = new InterpreterVersionService(); + let ioc: UnitTestIocContainer; suiteSetup(initialize); - setup(initializeTest); + setup(async () => { + initializeDI(); + await initializeTest(); + }); + teardown(() => ioc.dispose()); + + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerProcessTypes(); + ioc.registerVariableTypes(); + ioc.registerInterpreterTypes(); + } test('Must return the Python Version', async () => { - const output = await execPythonFile(undefined, 'python', ['--version'], __dirname, true); - const version = getFirstNonEmptyLineFromMultilineString(output); + const pythonProcess = ioc.serviceContainer.get(IProcessService); + const output = await pythonProcess.exec('python', ['--version'], { cwd: __dirname, mergeStdOutErr: true }); + const version = output.stdout.splitLines()[0]; + const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); const pyVersion = await interpreterVersion.getVersion('python', 'DEFAULT_TEST_VALUE'); assert.equal(pyVersion, version, 'Incorrect version'); }); test('Must return the default value when Python path is invalid', async () => { + const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); const pyVersion = await interpreterVersion.getVersion('INVALID_INTERPRETER', 'DEFAULT_TEST_VALUE'); assert.equal(pyVersion, 'DEFAULT_TEST_VALUE', 'Incorrect version'); }); test('Must return the pip Version', async () => { - const output = await execPythonFile(undefined, 'python', ['-m', 'pip', '--version'], __dirname, true); + const pythonProcess = ioc.serviceContainer.get(IProcessService); + const result = await pythonProcess.exec('python', ['-m', 'pip', '--version'], { cwd: __dirname, mergeStdOutErr: true }); + const output = result.stdout.splitLines()[0]; // Take the second part, see below example. // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). const re = new RegExp('\\d\\.\\d(\\.\\d)+', 'g'); @@ -34,11 +53,13 @@ suite('Interpreters display version', () => { // tslint:disable-next-line:no-non-null-assertion assert.isAtLeast(matches!.length, 1, 'Version number not found'); + const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); const pipVersionPromise = interpreterVersion.getPipVersion('python'); // tslint:disable-next-line:no-non-null-assertion await expect(pipVersionPromise).to.eventually.equal(matches![0].trim()); }); test('Must throw an exceptionn when pip version cannot be determine', async () => { + const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); const pipVersionPromise = interpreterVersion.getPipVersion('INVALID_INTERPRETER'); await expect(pipVersionPromise).to.be.rejectedWith(); }); diff --git a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts index f110790224d2..2e189529acb3 100644 --- a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts +++ b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts @@ -6,14 +6,16 @@ import { PythonPathUpdaterService } from '../../client/interpreter/configuration import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; import { WorkspaceFolderPythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceFolderUpdaterService'; import { WorkspacePythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceUpdaterService'; -import { InterpreterVersionService } from '../../client/interpreter/interpreterVersion'; +import { IInterpreterVersionService } from '../../client/interpreter/contracts'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); // tslint:disable-next-line:max-func-body-length suite('Multiroot Python Path Settings Updater', () => { + let ioc: UnitTestIocContainer; suiteSetup(async function () { if (!IS_MULTI_ROOT_TEST) { // tslint:disable-next-line:no-invalid-this @@ -21,7 +23,10 @@ suite('Multiroot Python Path Settings Updater', () => { } await initialize(); }); - setup(initializeTest); + setup(async () => { + await initializeTest(); + initializeDI(); + }); suiteTeardown(async () => { await closeActiveWindows(); await initializeTest(); @@ -29,8 +34,17 @@ suite('Multiroot Python Path Settings Updater', () => { teardown(async () => { await closeActiveWindows(); await initializeTest(); + ioc.dispose(); }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerProcessTypes(); + ioc.registerVariableTypes(); + ioc.registerInterpreterTypes(); + } + test('Updating Workspace Folder Python Path should work', async () => { const workspaceUri = workspace3Uri; const workspaceUpdater = new WorkspaceFolderPythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri)!.uri); @@ -75,7 +89,8 @@ suite('Multiroot Python Path Settings Updater', () => { test('Updating Workspace Python Path using the PythonPathUpdaterService should work', async () => { const workspaceUri = workspace3Uri; - const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory(), new InterpreterVersionService()); + const interpreterVersionService = ioc.serviceContainer.get(IInterpreterVersionService); + const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory(), interpreterVersionService); const pythonPath = `xWorkspacePythonPathFromUpdater${new Date().getMilliseconds()}`; await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.WorkspaceFolder, 'ui', workspace.getWorkspaceFolder(workspaceUri)!.uri); const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath')!.workspaceFolderValue!; diff --git a/src/test/interpreters/pythonPathUpdater.test.ts b/src/test/interpreters/pythonPathUpdater.test.ts index 33099dc8c701..2aef98eeb427 100644 --- a/src/test/interpreters/pythonPathUpdater.test.ts +++ b/src/test/interpreters/pythonPathUpdater.test.ts @@ -4,17 +4,21 @@ import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; import { PythonPathUpdaterService } from '../../client/interpreter/configuration/pythonPathUpdaterService'; import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; -import { GlobalPythonPathUpdaterService } from '../../client/interpreter/configuration/services/globalUpdaterService'; import { WorkspacePythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceUpdaterService'; -import { InterpreterVersionService } from '../../client/interpreter/interpreterVersion'; +import { IInterpreterVersionService } from '../../client/interpreter/contracts'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const workspaceRoot = path.join(__dirname, '..', '..', '..', 'src', 'test'); // tslint:disable-next-line:max-func-body-length suite('Python Path Settings Updater', () => { + let ioc: UnitTestIocContainer; suiteSetup(initialize); - setup(initializeTest); + setup(async () => { + await initializeTest(); + initializeDI(); + }); suiteTeardown(async () => { await closeActiveWindows(); await initializeTest(); @@ -22,8 +26,17 @@ suite('Python Path Settings Updater', () => { teardown(async () => { await closeActiveWindows(); await initializeTest(); + ioc.dispose(); }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerProcessTypes(); + ioc.registerVariableTypes(); + ioc.registerInterpreterTypes(); + } + // Create Github issue VS Code bug (global changes not reflected immediately) // test('Updating Global Python Path should work', async () => { @@ -52,39 +65,40 @@ suite('Python Path Settings Updater', () => { test('Updating Workspace Python Path should work', async () => { const workspaceUri = Uri.file(workspaceRoot); - const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri).uri); + const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri)!.uri); const pythonPath = `xWorkspacePythonPath${new Date().getMilliseconds()}`; await workspaceUpdater.updatePythonPath(pythonPath); - const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath')!.workspaceValue!; assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); }); test('Updating Workspace Python Path using the factor service should work', async () => { const workspaceUri = Uri.file(workspaceRoot); const factory = new PythonPathUpdaterServiceFactory(); - const workspaceUpdater = factory.getWorkspacePythonPathConfigurationService(workspace.getWorkspaceFolder(workspaceUri).uri); + const workspaceUpdater = factory.getWorkspacePythonPathConfigurationService(workspace.getWorkspaceFolder(workspaceUri)!.uri); const pythonPath = `xWorkspacePythonPathFromFactory${new Date().getMilliseconds()}`; await workspaceUpdater.updatePythonPath(pythonPath); - const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath')!.workspaceValue!; assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); }); test('Updating Workspace Python Path using the PythonPathUpdaterService should work', async () => { const workspaceUri = Uri.file(workspaceRoot); - const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory(), new InterpreterVersionService()); + const interpreterVersionService = ioc.serviceContainer.get(IInterpreterVersionService); + const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory(), interpreterVersionService); const pythonPath = `xWorkspacePythonPathFromUpdater${new Date().getMilliseconds()}`; - await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.Workspace, 'ui', workspace.getWorkspaceFolder(workspaceUri).uri); - const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.Workspace, 'ui', workspace.getWorkspaceFolder(workspaceUri)!.uri); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath')!.workspaceValue!; assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); }); test('Python Path should be relative to workspaceFolder', async () => { - const workspaceUri = workspace.getWorkspaceFolder(Uri.file(workspaceRoot)).uri; + const workspaceUri = workspace.getWorkspaceFolder(Uri.file(workspaceRoot))!.uri; const pythonInterpreter = `xWorkspacePythonPath${new Date().getMilliseconds()}`; const pythonPath = path.join(workspaceUri.fsPath, 'x', 'y', 'z', pythonInterpreter); const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspaceUri); await workspaceUpdater.updatePythonPath(pythonPath); - const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath')!.workspaceValue!; // tslint:disable-next-line:no-invalid-template-strings assert.equal(workspaceValue, path.join('${workspaceFolder}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); const resolvedPath = PythonSettings.getInstance(Uri.file(workspaceRoot)).pythonPath; diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index ddd6ef04f628..ac67620d300c 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -1,27 +1,26 @@ import * as assert from 'assert'; +import * as fs from 'fs-extra'; import * as path from 'path'; -import { OutputChannel } from 'vscode'; +import { OutputChannel, Uri } from 'vscode'; import * as vscode from 'vscode'; -import { PythonSettings } from '../../client/common/configSettings'; import { STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; -import { createDeferred } from '../../client/common/helpers'; -import { SettingToDisableProduct } from '../../client/common/installer/installer'; -import { IInstaller, ILogger, IOutputChannel, Product } from '../../client/common/types'; -import { execPythonFile } from '../../client/common/utils'; +import { Product, SettingToDisableProduct } from '../../client/common/installer/installer'; +import { IInstaller, ILogger, IOutputChannel } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; -import * as baseLinter from '../../client/linters/baseLinter'; import { BaseLinter } from '../../client/linters/baseLinter'; +import * as baseLinter from '../../client/linters/baseLinter'; import * as flake8 from '../../client/linters/flake8'; import * as pep8 from '../../client/linters/pep8Linter'; import * as prospector from '../../client/linters/prospector'; import * as pydocstyle from '../../client/linters/pydocstyle'; import * as pyLint from '../../client/linters/pylint'; import { ILinterHelper } from '../../client/linters/types'; -import { PythonSettingKeys, rootWorkspaceUri, updateSetting } from '../common'; +import { deleteFile, PythonSettingKeys, rootWorkspaceUri, updateSetting } from '../common'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockOutputChannel } from '../mockClasses'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; +const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'linting'); const flake8ConfigPath = path.join(pythoFilesPath, 'flake8config'); const pep8ConfigPath = path.join(pythoFilesPath, 'pep8config'); @@ -92,38 +91,17 @@ const pydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 80, type: '', provider: 'pydocstyle' } ]; -const filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ - { line: 26, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blop\' member', provider: '', type: '' }, - { line: 36, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, - { line: 46, column: 18, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, - { line: 61, column: 18, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, - { line: 72, column: 18, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, - { line: 75, column: 18, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, - { line: 77, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, - { line: 83, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' } -]; -const filteredPylint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ -]; const filteredFlake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; const filteredPep88MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -const fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ - { code: 'D102', severity: baseLinter.LintMessageSeverity.Information, message: 'Missing docstring in public method', column: 4, line: 8, type: '', provider: 'pydocstyle' } -]; // tslint:disable-next-line:max-func-body-length suite('Linting', () => { let ioc: UnitTestIocContainer; - const isPython3Deferred = createDeferred(); - const isPython3 = isPython3Deferred.promise; - suiteSetup(async () => { - await initialize(); - const version = await execPythonFile(fileToLint, PythonSettings.getInstance(vscode.Uri.file(fileToLint)).pythonPath, ['--version'], __dirname, true); - isPython3Deferred.resolve(version.indexOf('3.') >= 0); - }); + suiteSetup(initialize); setup(async () => { initializeDI(); await initializeTest(); @@ -134,6 +112,8 @@ suite('Linting', () => { ioc.dispose(); await closeActiveWindows(); await resetSettings(); + await deleteFile(path.join(workspaceUri.fsPath, '.pylintrc')); + await deleteFile(path.join(workspaceUri.fsPath, '.pydocstyle')); }); function initializeDI() { @@ -283,26 +263,18 @@ suite('Linting', () => { test('Pydocstyle', async () => { await testLinterMessages(Product.pydocstyle, fileToLint, pydocstyleMessagseToBeReturned); }); - isPython3 - .then(value => { - const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; - test('PyLint with config in root', async () => { - await testLinterMessages(Product.pylint, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned); - }); - }) - .catch(ex => console.error('Python Extension Tests: isPython3', ex)); + test('PyLint with config in root', async () => { + await fs.copy(path.join(pylintConfigPath, '.pylintrc'), path.join(workspaceUri.fsPath, '.pylintrc')); + await testLinterMessages(Product.pylint, path.join(pylintConfigPath, 'file.py'), []); + }); test('Flake8 with config in root', async () => { await testLinterMessages(Product.flake8, path.join(flake8ConfigPath, 'file.py'), filteredFlake8MessagesToBeReturned); }); test('Pep8 with config in root', async () => { await testLinterMessages(Product.pep8, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned); }); - isPython3 - .then(value => { - const messagesToBeReturned = value ? [] : fiteredPydocstyleMessagseToBeReturned; - test('Pydocstyle with config in root', async () => { - await testLinterMessages(Product.pydocstyle, path.join(pydocstyleConfigPath27, 'file.py'), messagesToBeReturned); - }); - }) - .catch(ex => console.error('Python Extension Tests: isPython3', ex)); + test('Pydocstyle with config in root', async () => { + await fs.copy(path.join(pydocstyleConfigPath27, '.pydocstyle'), path.join(workspaceUri.fsPath, '.pydocstyle')); + await testLinterMessages(Product.pydocstyle, path.join(pydocstyleConfigPath27, 'file.py'), []); + }); }); diff --git a/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle b/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle index 07a3cffbaaab..ad9ca74f73c6 100644 --- a/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle +++ b/src/test/pythonFiles/linting/pydocstyleconfig27/.pydocstyle @@ -1,2 +1,2 @@ [pydocstyle] -ignore=D400,D401,D402,D403,D203 \ No newline at end of file +ignore=D400,D401,D402,D403,D203,D102 diff --git a/src/test/pythonFiles/linting/pylintconfig/.pylintrc b/src/test/pythonFiles/linting/pylintconfig/.pylintrc index d89aa6aa33f2..59444d78c3a3 100644 --- a/src/test/pythonFiles/linting/pylintconfig/.pylintrc +++ b/src/test/pythonFiles/linting/pylintconfig/.pylintrc @@ -1,2 +1,2 @@ [MESSAGES CONTROL] -disable=I0011,I0012,C0304,C0103,W0613,E0001 \ No newline at end of file +disable=I0011,I0012,C0304,C0103,W0613,E0001,E1101 diff --git a/src/test/serviceRegistry.ts b/src/test/serviceRegistry.ts index ab787ff35eb9..b00465b356de 100644 --- a/src/test/serviceRegistry.ts +++ b/src/test/serviceRegistry.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { Container } from 'inversify'; -import { Disposable, Memento, OutputChannel } from 'vscode'; +import { Disposable, Memento, OutputChannel, Uri } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../client/common/constants'; import { BufferDecoder } from '../client/common/process/decoder'; import { ProcessService } from '../client/common/process/proc'; @@ -13,6 +13,7 @@ import { registerTypes as commonRegisterTypes } from '../client/common/serviceRe import { GLOBAL_MEMENTO, IDisposableRegistry, IMemento, IOutputChannel, WORKSPACE_MEMENTO } from '../client/common/types'; import { registerTypes as variableRegisterTypes } from '../client/common/variables/serviceRegistry'; import { registerTypes as formattersRegisterTypes } from '../client/formatters/serviceRegistry'; +import { registerTypes as interpretersRegisterTypes } from '../client/interpreter/serviceRegistry'; import { ServiceContainer } from '../client/ioc/container'; import { ServiceManager } from '../client/ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../client/ioc/types'; @@ -46,7 +47,11 @@ export class IocContainer { this.disposables.push(testOutputChannel); this.serviceManager.addSingletonInstance(IOutputChannel, testOutputChannel, TEST_OUTPUT_CHANNEL); } - + public async getPythonVersion(resource?: string | Uri): Promise { + const factory = this.serviceContainer.get(IPythonExecutionFactory); + const resourceToUse = (typeof resource === 'string') ? Uri.file(resource as string) : (resource as Uri); + return factory.create(resourceToUse).then(pythonProc => pythonProc.getVersion()); + } public dispose() { this.disposables.forEach(disposable => disposable.dispose()); } @@ -69,7 +74,9 @@ export class IocContainer { public registerFormatterTypes() { formattersRegisterTypes(this.serviceManager); } - + public registerInterpreterTypes() { + interpretersRegisterTypes(this.serviceManager); + } public registerMockProcessTypes() { this.serviceManager.addSingleton(IBufferDecoder, BufferDecoder); this.serviceManager.addSingleton(IOriginalProcessService, ProcessService); diff --git a/src/test/signature/signature.test.ts b/src/test/signature/signature.test.ts index b42ecd115b16..a4d002e4e750 100644 --- a/src/test/signature/signature.test.ts +++ b/src/test/signature/signature.test.ts @@ -1,14 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import { PythonSettings } from '../../client/common/configSettings'; -import { execPythonFile } from '../../client/common/utils'; import { rootWorkspaceUri } from '../common'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'signature'); @@ -23,15 +21,25 @@ class SignatureHelpResult { // tslint:disable-next-line:max-func-body-length suite('Signatures', () => { - let isPython3: Promise; + let isPython2: boolean; + let ioc: UnitTestIocContainer; suiteSetup(async () => { await initialize(); - const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); - isPython3 = Promise.resolve(version.indexOf('3.') >= 0); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; }); setup(initializeTest); suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); + }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } test('For ctor', async () => { const expected = [ @@ -76,8 +84,10 @@ suite('Signatures', () => { } }); - test('For ellipsis', async () => { - if (!await isPython3) { + test('For ellipsis', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); return; } const expected = [ @@ -99,10 +109,10 @@ suite('Signatures', () => { test('For pow', async () => { let expected: SignatureHelpResult; - if (await isPython3) { - expected = new SignatureHelpResult(0, 4, 1, 0, null); - } else { + if (isPython2) { expected = new SignatureHelpResult(0, 4, 1, 0, 'x'); + } else { + expected = new SignatureHelpResult(0, 4, 1, 0, null); } const document = await openDocument(path.join(autoCompPath, 'noSigPy3.py')); diff --git a/src/test/unittests/serviceRegistry.ts b/src/test/unittests/serviceRegistry.ts index 5243d6b77936..725dba422e81 100644 --- a/src/test/unittests/serviceRegistry.ts +++ b/src/test/unittests/serviceRegistry.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { Uri } from 'vscode'; +import { IPythonExecutionFactory } from '../../client/common/process/types'; import { IServiceContainer } from '../../client/ioc/types'; import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../../client/unittests/common/constants'; import { TestCollectionStorageService } from '../../client/unittests/common/services/storageService'; @@ -11,8 +12,8 @@ import { TestsHelper } from '../../client/unittests/common/testUtils'; import { TestFlatteningVisitor } from '../../client/unittests/common/testVisitors/flatteningVisitor'; import { TestFolderGenerationVisitor } from '../../client/unittests/common/testVisitors/folderGenerationVisitor'; import { TestResultResetVisitor } from '../../client/unittests/common/testVisitors/resultResetVisitor'; -import { ITestCollectionStorageService, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory } from '../../client/unittests/common/types'; import { ITestResultsService, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, TestProvider } from '../../client/unittests/common/types'; +import { ITestCollectionStorageService, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory } from '../../client/unittests/common/types'; import { TestManager as NoseTestManager } from '../../client/unittests/nosetest/main'; import { TestDiscoveryService as NoseTestDiscoveryService } from '../../client/unittests/nosetest/services/discoveryService'; import { TestsParser as NoseTestTestsParser } from '../../client/unittests/nosetest/services/parserService'; @@ -29,7 +30,11 @@ export class UnitTestIocContainer extends IocContainer { constructor() { super(); } - + public getPythonMajorVersion(resource: Uri) { + return this.serviceContainer.get(IPythonExecutionFactory).create(resource) + .then(pythonProcess => pythonProcess.exec(['-c', 'import sys;print(sys.version_info[0])'], {})) + .then(output => parseInt(output.stdout.trim(), 10)); + } public registerTestVisitors() { this.serviceManager.add(ITestVisitor, TestFlatteningVisitor, 'TestFlatteningVisitor'); this.serviceManager.add(ITestVisitor, TestFolderGenerationVisitor, 'TestFolderGenerationVisitor');