diff --git a/src/client/application/diagnostics/checks/pythonInterpreter.ts b/src/client/application/diagnostics/checks/pythonInterpreter.ts index e6e4eb810a30..dcff919abf7e 100644 --- a/src/client/application/diagnostics/checks/pythonInterpreter.ts +++ b/src/client/application/diagnostics/checks/pythonInterpreter.ts @@ -54,9 +54,9 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService { } const interpreterService = this.serviceContainer.get(IInterpreterService); - const interpreters = await interpreterService.getInterpreters(); + const hasInterpreters = await interpreterService.hasInterpreters; - if (interpreters.length === 0) { + if (!hasInterpreters) { return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoPythonInterpretersDiagnostic)]; } @@ -77,6 +77,7 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService { if (!currentInterpreter || currentInterpreter.type !== InterpreterType.Unknown) { return []; } + const interpreters = await interpreterService.getInterpreters(); if (interpreters.filter(i => !helper.isMacDefaultPythonPath(i.path)).length === 0) { return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic)]; } diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index f348ef542384..6dc2a6f8b7f3 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -56,7 +56,7 @@ export abstract class ModuleInstaller { // If installing pylint on python 2.x, then use pylint~=1.9.0 const interpreterService = this.serviceContainer.get(IInterpreterService); const currentInterpreter = await interpreterService.getActiveInterpreter(resource); - if (currentInterpreter && currentInterpreter.version_info && currentInterpreter.version_info[0] === 2) { + if (currentInterpreter && currentInterpreter.version && currentInterpreter.version.major === 2) { const newArgs = [...args]; // This command could be sent to the terminal, hence '<' needs to be escaped for UNIX. newArgs[indexOfPylint] = '"pylint<2.0.0"'; diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 6c57965ceeef..13b633c3f7dd 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -10,6 +10,7 @@ import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError'; import { traceError } from '../logger'; import { IFileSystem } from '../platform/types'; import { Architecture } from '../utils/platform'; +import { convertPythonVersionToSemver } from '../utils/version'; import { ExecutionResult, InterpreterInfomation, IProcessService, IPythonExecutionService, ObservableExecutionResult, PythonVersionInfo, SpawnOptions } from './types'; @injectable() @@ -27,12 +28,8 @@ export class PythonExecutionService implements IPythonExecutionService { public async getInterpreterInformation(): Promise { const file = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'interpreterInfo.py'); try { - const [version, jsonValue] = await Promise.all([ - this.procService.exec(this.pythonPath, ['--version'], { mergeStdOutErr: true }) - .then(output => output.stdout.trim()), - this.procService.exec(this.pythonPath, [file], { mergeStdOutErr: true }) - .then(output => output.stdout.trim()) - ]); + const jsonValue = await this.procService.exec(this.pythonPath, [file], { mergeStdOutErr: true }) + .then(output => output.stdout.trim()); let json: { versionInfo: PythonVersionInfo; sysPrefix: string; sysVersion: string; is64Bit: boolean }; try { @@ -41,22 +38,12 @@ export class PythonExecutionService implements IPythonExecutionService { traceError(`Failed to parse interpreter information for '${this.pythonPath}' with JSON ${jsonValue}`, ex); return; } - const version_info = json.versionInfo; - // Exclude PII from `version_info` to ensure we don't send this up via telemetry. - for (let index = 0; index < 3; index += 1) { - if (typeof version_info[index] !== 'number') { - version_info[index] = 0; - } - } - if (['alpha', 'beta', 'candidate', 'final'].indexOf(version_info[3]) === -1) { - version_info[3] = 'unknown'; - } + const versionValue = json.versionInfo.length === 4 ? `${json.versionInfo.slice(0, 3).join('.')}-${json.versionInfo[3]}` : json.versionInfo.join('.'); return { architecture: json.is64Bit ? Architecture.x64 : Architecture.x86, path: this.pythonPath, - version, + version: convertPythonVersionToSemver(versionValue), sysVersion: json.sysVersion, - version_info: json.versionInfo, sysPrefix: json.sysPrefix }; } catch (ex) { diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index ef52703227ab..6b0124b5c72a 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -4,6 +4,7 @@ import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } f import { Observable } from 'rxjs/Observable'; import { CancellationToken, Uri } from 'vscode'; +import { SemVer } from 'semver'; import { ExecutionInfo } from '../types'; import { Architecture } from '../utils/platform'; import { EnvironmentVariables } from '../variables/types'; @@ -20,7 +21,7 @@ export type Output = { export type ObservableExecutionResult = { proc: ChildProcess | undefined; out: Observable>; - dispose() : void; + dispose(): void; }; // tslint:disable-next-line:interface-name @@ -32,7 +33,7 @@ export type SpawnOptions = ChildProcessSpawnOptions & { }; // tslint:disable-next-line:interface-name -export type ShellOptions = ExecOptions & { throwOnStdErr? : boolean }; +export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean }; export type ExecutionResult = { stdout: T; @@ -60,14 +61,12 @@ export interface IPythonExecutionFactory { create(options: ExecutionFactoryCreationOptions): Promise; } export type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final' | 'unknown'; -// tslint:disable-next-line:interface-name export type PythonVersionInfo = [number, number, number, ReleaseLevel]; export type InterpreterInfomation = { path: string; - version: string; + version?: SemVer; sysVersion: string; architecture: Architecture; - version_info: PythonVersionInfo; sysPrefix: string; }; export const IPythonExecutionService = Symbol('IPythonExecutionService'); diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 398af106931b..7e6e60d4a91d 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -81,7 +81,7 @@ export class TerminalService implements ITerminalService, Disposable { private async sendTelemetry() { const pythonPath = this.serviceContainer.get(IConfigurationService).getSettings(this.resource).pythonPath; const interpreterInfo = await this.serviceContainer.get(IInterpreterService).getInterpreterDetails(pythonPath); - const pythonVersion = (interpreterInfo && interpreterInfo.version_info) ? interpreterInfo.version_info.join('.') : undefined; + const pythonVersion = (interpreterInfo && interpreterInfo.version) ? interpreterInfo.version.raw : undefined; const interpreterType = interpreterInfo ? interpreterInfo.type : undefined; captureTelemetry(TERMINAL_CREATE, { terminal: this.terminalShellType, pythonVersion, interpreterType }); } diff --git a/src/client/common/utils/version.ts b/src/client/common/utils/version.ts index 35346c86c5ab..c418e3d1ba95 100644 --- a/src/client/common/utils/version.ts +++ b/src/client/common/utils/version.ts @@ -24,6 +24,33 @@ export function convertToSemver(version: string) { return versionParts.join('.'); } +export function convertPythonVersionToSemver(version: string): semver.SemVer | undefined { + if (!version || version.trim().length === 0) { + return; + } + const versionParts = (version || '') + .split('.') + .map(item => item.trim()) + .filter(item => item.length > 0) + .filter((_, index) => index < 4); + + if (versionParts.length > 0 && versionParts[versionParts.length - 1].indexOf('-') > 0) { + const lastPart = versionParts[versionParts.length - 1]; + versionParts[versionParts.length - 1] = lastPart.split('-')[0].trim(); + versionParts.push(lastPart.split('-')[1].trim()); + } + while (versionParts.length < 4) { + versionParts.push(''); + } + // Exclude PII from `version_info` to ensure we don't send this up via telemetry. + for (let index = 0; index < 3; index += 1) { + versionParts[index] = /^\d+$/.test(versionParts[index]) ? versionParts[index] : '0'; + } + versionParts[3] = ['alpha', 'beta', 'candidate', 'final'].indexOf(versionParts[3]) === -1 ? 'unknown' : versionParts[3]; + + return new semver.SemVer(`${versionParts[0]}.${versionParts[1]}.${versionParts[2]}-${versionParts[3]}`); +} + export function compareVersion(versionA: string, versionB: string) { try { versionA = convertToSemver(versionA); diff --git a/src/client/datascience/jupyterExecution.ts b/src/client/datascience/jupyterExecution.ts index f6de94939312..4190d8b4b9a1 100644 --- a/src/client/datascience/jupyterExecution.ts +++ b/src/client/datascience/jupyterExecution.ts @@ -92,8 +92,8 @@ class JupyterCommand { public mainVersion = async (): Promise => { const interpreter = await this.interpreterPromise; - if (interpreter) { - return interpreter.version_info[0]; + if (interpreter && interpreter.version) { + return interpreter.version.major; } else { return this.execVersion(); } @@ -417,14 +417,18 @@ export class JupyterExecution implements IJupyterExecution, Disposable { score += 1; // Then start matching based on version - if (list[i].version_info[0] === active.version_info[0]) { - score += 32; - if (list[i].version_info[1] === active.version_info[1]) { - score += 16; - if (list[i].version_info[2] === active.version_info[2]) { - score += 8; - if (list[i].version_info[3] === active.version_info[3]) { - score += 4; + const listVersion = list[i].version; + const activeVersion = active.version; + if (listVersion && activeVersion) { + if (listVersion.major === activeVersion.major) { + score += 32; + if (listVersion.minor === activeVersion.minor) { + score += 16; + if (listVersion.patch === activeVersion.patch) { + score += 8; + if (listVersion.raw === activeVersion.raw) { + score += 4; + } } } } @@ -632,25 +636,25 @@ export class JupyterExecution implements IJupyterExecution, Disposable { score += 1; // See if the version is the same - if (info && info.version_info && specDetails[i]) { + if (info && info.version && specDetails[i]) { const details = specDetails[i]; - if (details && details.version_info) { - if (details.version_info[0] === info.version_info[0]) { + if (details && details.version) { + if (details.version.major === info.version.major) { // Major version match score += 4; - if (details.version_info[1] === info.version_info[1]) { + if (details.version.minor === info.version.minor) { // Minor version match score += 2; - if (details.version_info[2] === info.version_info[2]) { + if (details.version.patch === info.version.patch) { // Minor version match score += 1; } } } } - } else if (info && info.version_info && spec && spec.path && spec.path.toLocaleLowerCase() === 'python' && spec.name) { + } else if (info && info.version && spec && spec.path && spec.path.toLocaleLowerCase() === 'python' && spec.name) { // This should be our current python. // Search for a digit on the end of the name. It should match our major version @@ -658,7 +662,7 @@ export class JupyterExecution implements IJupyterExecution, Disposable { if (match && match !== null && match.length > 0) { // See if the version number matches const nameVersion = parseInt(match[0], 10); - if (nameVersion && nameVersion === info.version_info[0]) { + if (nameVersion && nameVersion === info.version.major) { score += 4; } } diff --git a/src/client/datascience/jupyterExporter.ts b/src/client/datascience/jupyterExporter.ts index 5618c65072ff..7ba1f3ac9cd7 100644 --- a/src/client/datascience/jupyterExporter.ts +++ b/src/client/datascience/jupyterExporter.ts @@ -19,7 +19,7 @@ import { CellState, ICell, IJupyterExecution, INotebookExporter, ISysInfo } from export class JupyterExporter implements INotebookExporter { constructor( - @inject(IJupyterExecution) private jupyterExecution : IJupyterExecution, + @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, @inject(ILogger) private logger: ILogger, @inject(IWorkspaceService) private workspaceService: IWorkspaceService, @inject(IFileSystem) private fileSystem: IFileSystem) { @@ -29,7 +29,7 @@ export class JupyterExporter implements INotebookExporter { noop(); } - public async translateToNotebook(cells: ICell[], changeDirectory?: string) : Promise { + public async translateToNotebook(cells: ICell[], changeDirectory?: string): Promise { // If requested, add in a change directory cell to fix relative paths if (changeDirectory) { cells = await this.addDirectoryChangeCell(cells, changeDirectory); @@ -95,18 +95,17 @@ export class JupyterExporter implements INotebookExporter { // When we export we want to our change directory back to the first real file that we saw run from any workspace folder private firstWorkspaceFolder = async (cells: ICell[]): Promise => { for (const cell of cells) { - const filename = cell.file; + const filename = cell.file; - // First check that this is an absolute file that exists (we add in temp files to run system cell) - if (path.isAbsolute(filename) && await this.fileSystem.fileExists(filename)) { + // First check that this is an absolute file that exists (we add in temp files to run system cell) + if (path.isAbsolute(filename) && await this.fileSystem.fileExists(filename)) { // We've already check that workspace folders above for (const folder of this.workspaceService.workspaceFolders!) { - if (filename.toLowerCase().startsWith(folder.uri.fsPath.toLowerCase())) - { + if (filename.toLowerCase().startsWith(folder.uri.fsPath.toLowerCase())) { return folder.uri.fsPath; } } - } + } } return undefined; @@ -134,22 +133,22 @@ export class JupyterExporter implements INotebookExporter { } } - private pruneCells = (cells : ICell[]) : nbformat.IBaseCell[] => { + private pruneCells = (cells: ICell[]): nbformat.IBaseCell[] => { // First filter out sys info cells. Jupyter doesn't understand these return cells.filter(c => c.data.cell_type !== 'sys_info') // Then prune each cell down to just the cell data. .map(this.pruneCell); } - private pruneCell = (cell : ICell) : nbformat.IBaseCell => { + private pruneCell = (cell: ICell): nbformat.IBaseCell => { // Remove the #%% of the top of the source if there is any. We don't need // this to end up in the exported ipynb file. - const copy = {...cell.data}; + const copy = { ...cell.data }; copy.source = this.pruneSource(cell.data.source); return copy; } - private pruneSource = (source : nbformat.MultilineString) : nbformat.MultilineString => { + private pruneSource = (source: nbformat.MultilineString): nbformat.MultilineString => { if (Array.isArray(source) && source.length > 0) { if (RegExpValues.PythonCellMarker.test(source[0])) { @@ -168,7 +167,7 @@ export class JupyterExporter implements INotebookExporter { private extractPythonMainVersion = async (cells: ICell[]): Promise => { let pythonVersion; const sysInfoCells = cells.filter((targetCell: ICell) => { - return targetCell.data.cell_type === 'sys_info'; + return targetCell.data.cell_type === 'sys_info'; }); if (sysInfoCells.length > 0) { @@ -184,6 +183,6 @@ export class JupyterExporter implements INotebookExporter { // In this case, let's check the version on the active interpreter const usableInterpreter = await this.jupyterExecution.getUsableJupyterPython(); - return usableInterpreter ? usableInterpreter.version_info[0] : 3; + return usableInterpreter && usableInterpreter.version ? usableInterpreter.version.major : 3; } } diff --git a/src/client/extension.ts b/src/client/extension.ts index 2ce85e65649f..5ec9b79e9c7e 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -297,10 +297,10 @@ async function sendStartupTelemetry(activatedPromise: Promise, serviceConta interpreterService.getInterpreters(mainWorkspaceUri).catch(() => []) ]); const workspaceFolderCount = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.length : 0; - const pythonVersion = interpreter ? interpreter.version_info.join('.') : undefined; + const pythonVersion = interpreter && interpreter.version ? interpreter.version.raw : undefined; const interpreterType = interpreter ? interpreter.type : undefined; const hasPython3 = interpreters - .filter(item => item && Array.isArray(item.version_info) ? item.version_info[0] === 3 : false) + .filter(item => item && item.version ? item.version.major === 3 : false) .length > 0; const props = { condaVersion, terminal: terminalShellType, pythonVersion, interpreterType, workspaceFolderCount, hasPython3 }; diff --git a/src/client/interpreter/configuration/interpreterComparer.ts b/src/client/interpreter/configuration/interpreterComparer.ts index a2a0fd3e78c7..9d0c1cdf6912 100644 --- a/src/client/interpreter/configuration/interpreterComparer.ts +++ b/src/client/interpreter/configuration/interpreterComparer.ts @@ -29,8 +29,8 @@ export class InterpreterComparer implements IInterpreterComparer { // * Architecture // * Interpreter Type // * Environment name - if (info.version_info && info.version_info.length > 0) { - sortNameParts.push(info.version_info.slice(0, 3).join('.')); + if (info.version) { + sortNameParts.push(info.version.raw); } if (info.architecture) { sortNameParts.push(getArchitectureDisplayName(info.architecture)); diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts index 38687cf45721..13f5210d9c93 100644 --- a/src/client/interpreter/configuration/pythonPathUpdaterService.ts +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -49,8 +49,8 @@ export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManage .then(value => value.length === 0 ? undefined : value) .catch(() => ''); const [info, pipVersion] = await Promise.all([infoPromise, pipVersionPromise]); - if (info) { - telemtryProperties.pythonVersion = info.version_info.join('.'); + if (info && info.version) { + telemtryProperties.pythonVersion = info.version.raw; } if (pipVersion) { telemtryProperties.pipVersion = pipVersion; diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index a1b180200c7a..53a95711eb6d 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -29,6 +29,7 @@ export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService'); export interface IInterpreterLocatorService extends Disposable { readonly onLocating: Event>; + readonly hasInterpreters: Promise; getInterpreters(resource?: Uri, ignoreCache?: boolean): Promise; } @@ -81,6 +82,7 @@ export type WorkspacePythonPath = { export const IInterpreterService = Symbol('IInterpreterService'); export interface IInterpreterService { onDidChangeInterpreter: Event; + hasInterpreters: Promise; getInterpreters(resource?: Uri): Promise; autoSetInterpreter(): Promise; getActiveInterpreter(resource?: Uri): Promise; diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index ba00d71c16df..af9def470b35 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -36,6 +36,9 @@ export class InterpreterService implements Disposable, IInterpreterService { this.fs = this.serviceContainer.get(IFileSystem); this.persistentStateFactory = this.serviceContainer.get(IPersistentStateFactory); } + public get hasInterpreters(): Promise { + return this.locator.hasInterpreters; + } public async refresh(resource?: Uri) { const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); @@ -83,7 +86,7 @@ export class InterpreterService implements Disposable, IInterpreterService { return; } // Always pick the highest version by default. - interpretersInWorkspace.sort((a, b) => a.version! > b.version! ? 1 : -1); + interpretersInWorkspace.sort((a, b) => (a.version && b.version) ? a.version.compare(b.version) : 0); const pythonPath = interpretersInWorkspace[0].path; // Ensure this new environment is at the same level as the current workspace. // In windows the interpreter is under scripts/python.exe on linux it is under bin/python. @@ -193,8 +196,8 @@ export class InterpreterService implements Disposable, IInterpreterService { const displayNameParts: string[] = ['Python']; const envSuffixParts: string[] = []; - if (info.version_info && info.version_info.length > 0) { - displayNameParts.push(info.version_info.slice(0, 3).join('.')); + if (info.version) { + displayNameParts.push(`${info.version.major}.${info.version.minor}.${info.version.patch}`); } if (info.architecture) { displayNameParts.push(getArchitectureDisplayName(info.architecture)); diff --git a/src/client/interpreter/locators/helpers.ts b/src/client/interpreter/locators/helpers.ts index d15d1e56115d..401aa02d18f0 100644 --- a/src/client/interpreter/locators/helpers.ts +++ b/src/client/interpreter/locators/helpers.ts @@ -29,11 +29,11 @@ export class InterpreterLocatorHelper implements IInterpreterLocatorHelper { .map(item => { return { ...item }; }) .map(item => { item.path = path.normalize(item.path); return item; }) .reduce((accumulator, current) => { - const currentVersion = Array.isArray(current.version_info) ? current.version_info.join('.') : undefined; + const currentVersion = current && current.version ? current.version.raw : undefined; const existingItem = accumulator.find(item => { // If same version and same base path, then ignore. // Could be Python 3.6 with path = python.exe, and Python 3.6 and path = python3.exe. - if (Array.isArray(item.version_info) && item.version_info.join('.') === currentVersion && + if (item.version && item.version.raw === currentVersion && item.path && current.path && this.fs.arePathsSame(path.dirname(item.path), path.dirname(current.path))) { return true; @@ -49,7 +49,7 @@ export class InterpreterLocatorHelper implements IInterpreterLocatorHelper { existingItem.type = current.type; } const props: (keyof PythonInterpreter)[] = ['envName', 'envPath', 'path', 'sysPrefix', - 'architecture', 'sysVersion', 'version', 'version_info']; + 'architecture', 'sysVersion', 'version']; for (const prop of props) { if (!existingItem[prop] && current[prop]) { existingItem[prop] = current[prop]; diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index ff86a0d8da98..8111210e3d85 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -2,6 +2,7 @@ import { inject, injectable } from 'inversify'; import { Disposable, Event, EventEmitter, Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IDisposableRegistry } from '../../common/types'; +import { createDeferred, Deferred } from '../../common/utils/async'; import { OSType } from '../../common/utils/platform'; import { IServiceContainer } from '../../ioc/types'; import { @@ -28,9 +29,11 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi private readonly disposables: Disposable[] = []; private readonly platform: IPlatformService; private readonly interpreterLocatorHelper: IInterpreterLocatorHelper; + private readonly _hasInterpreters: Deferred; constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer ) { + this._hasInterpreters = createDeferred(); serviceContainer.get(IDisposableRegistry).push(this); this.platform = serviceContainer.get(IPlatformService); this.interpreterLocatorHelper = serviceContainer.get(IInterpreterLocatorHelper); @@ -46,6 +49,9 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi public get onLocating(): Event> { return new EventEmitter>().event; } + public get hasInterpreters(): Promise { + return this._hasInterpreters.promise; + } /** * Release any held resources. @@ -65,11 +71,19 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi public async getInterpreters(resource?: Uri): Promise { const locators = this.getLocators(); const promises = locators.map(async provider => provider.getInterpreters(resource)); + locators.forEach(locator => { + locator.hasInterpreters.then(found => { + if (found) { + this._hasInterpreters.resolve(true); + } + }).ignoreErrors(); + }); const listOfInterpreters = await Promise.all(promises); const items = flatten(listOfInterpreters) .filter(item => !!item) .map(item => item!); + this._hasInterpreters.resolve(items.length > 0); return this.interpreterLocatorHelper.mergeInterpreters(items); } diff --git a/src/client/interpreter/locators/services/KnownPathsService.ts b/src/client/interpreter/locators/services/KnownPathsService.ts index 3b22576ba8e6..b4328186c5a6 100644 --- a/src/client/interpreter/locators/services/KnownPathsService.ts +++ b/src/client/interpreter/locators/services/KnownPathsService.ts @@ -61,6 +61,7 @@ export class KnownPathsService extends CacheableLocatorService { if (!details) { return; } + this._hasInterpreters.resolve(true); return { ...(details as PythonInterpreter), path: interpreter, diff --git a/src/client/interpreter/locators/services/baseVirtualEnvService.ts b/src/client/interpreter/locators/services/baseVirtualEnvService.ts index 45ee7adb8b79..22a356312d50 100644 --- a/src/client/interpreter/locators/services/baseVirtualEnvService.ts +++ b/src/client/interpreter/locators/services/baseVirtualEnvService.ts @@ -77,6 +77,7 @@ export class BaseVirtualEnvService extends CacheableLocatorService { if (!details) { return; } + this._hasInterpreters.resolve(true); return { ...(details as PythonInterpreter), envName: virtualEnvName, diff --git a/src/client/interpreter/locators/services/cacheableLocatorService.ts b/src/client/interpreter/locators/services/cacheableLocatorService.ts index 3cf387454c9c..5ea4fcc0e6b9 100644 --- a/src/client/interpreter/locators/services/cacheableLocatorService.ts +++ b/src/client/interpreter/locators/services/cacheableLocatorService.ts @@ -16,6 +16,7 @@ import { IInterpreterLocatorService, IInterpreterWatcher, PythonInterpreter } fr @injectable() export abstract class CacheableLocatorService implements IInterpreterLocatorService { + protected readonly _hasInterpreters: Deferred; private readonly promisesPerResource = new Map>(); private readonly handlersAddedToResource = new Set(); private readonly cacheKeyPrefix: string; @@ -23,11 +24,15 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ constructor(@unmanaged() name: string, @unmanaged() protected readonly serviceContainer: IServiceContainer, @unmanaged() private cachePerWorkspace: boolean = false) { + this._hasInterpreters = createDeferred(); this.cacheKeyPrefix = `INTERPRETERS_CACHE_v2_${name}`; } public get onLocating(): Event> { return this.locating.event; } + public get hasInterpreters(): Promise { + return this._hasInterpreters.promise; + } public abstract dispose(); public async getInterpreters(resource?: Uri, ignoreCache?: boolean): Promise { const cacheKey = this.getCacheKey(resource); @@ -49,6 +54,10 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ this.locating.fire(deferred.promise); } + deferred.promise + .then(items => this._hasInterpreters.resolve(items.length > 0)) + .catch(_ => this._hasInterpreters.resolve(false)); + if (deferred.completed) { return deferred.promise; } diff --git a/src/client/interpreter/locators/services/condaEnvFileService.ts b/src/client/interpreter/locators/services/condaEnvFileService.ts index b16c76ed204e..196f9ba045d8 100644 --- a/src/client/interpreter/locators/services/condaEnvFileService.ts +++ b/src/client/interpreter/locators/services/condaEnvFileService.ts @@ -41,7 +41,7 @@ export class CondaEnvFileService extends CacheableLocatorService { * * This is used by CacheableLocatorService.getInterpreters(). */ - protected getInterpretersImplementation(resource?: Uri): Promise { + protected getInterpretersImplementation(_resource?: Uri): Promise { return this.getSuggestionsFromConda(); } @@ -103,6 +103,7 @@ export class CondaEnvFileService extends CacheableLocatorService { return; } const envName = details.envName ? details.envName : path.basename(environmentPath); + this._hasInterpreters.resolve(true); return { ...(details as PythonInterpreter), path: interpreter, diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index 25655269f8c6..ffc0c32fd5fc 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -58,6 +58,7 @@ export class CondaEnvService extends CacheableLocatorService { this.fileSystem, this.helper ); + this._hasInterpreters.resolve(interpreters.length > 0); const environments = await this.condaService.getCondaEnvironments(true); if (Array.isArray(environments) && environments.length > 0) { interpreters diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index 520fc228c98b..a1c10872a18b 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -1,13 +1,11 @@ import { inject, injectable, named, optional } from 'inversify'; import * as path from 'path'; import { parse, SemVer } from 'semver'; - import { Logger } from '../../../common/logger'; import { IFileSystem, IPlatformService } from '../../../common/platform/types'; import { ExecutionResult, IProcessServiceFactory } from '../../../common/process/types'; import { ITerminalActivationCommandProvider, TerminalShellType } from '../../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry, ILogger, IPersistentStateFactory } from '../../../common/types'; -import { compareVersion } from '../../../common/utils/version'; import { IServiceContainer } from '../../../ioc/types'; import { CondaInfo, @@ -52,9 +50,9 @@ export class CondaService implements ICondaService { private condaFile!: Promise; private isAvailable: boolean | undefined; private readonly condaHelper = new CondaHelper(); - private activatedEnvironmentCache : { [key: string] : NodeJS.ProcessEnv } = {}; - private activationProvider : ITerminalActivationCommandProvider; - private shellType : TerminalShellType; + private activatedEnvironmentCache: { [key: string]: NodeJS.ProcessEnv } = {}; + private activationProvider: ITerminalActivationCommandProvider; + private shellType: TerminalShellType; constructor( @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, @@ -280,10 +278,10 @@ export class CondaService implements ICondaService { const activateCommands = this.activationProvider.getActivationCommandsForInterpreter ? await this.activationProvider.getActivationCommandsForInterpreter(condaPath, this.shellType) : this.platform.isWindows ? - [`"${path.join(path.dirname(condaPath), 'activate')}"`] : - [`. "${path.join(path.dirname(condaPath), 'activate')}"`]; + [`"${path.join(path.dirname(condaPath), 'activate')}"`] : + [`. "${path.join(path.dirname(condaPath), 'activate')}"`]; - const result = {...input}; + const result = { ...input }; const processService = await this.processServiceFactory.create(); // Run the activate command collect the environment from it. @@ -333,7 +331,7 @@ export class CondaService implements ICondaService { } private parseEnvironmentOutput(output: string, result: NodeJS.ProcessEnv) { - const lines = output.splitLines({trim: true, removeEmptyEntries: true}); + const lines = output.splitLines({ trim: true, removeEmptyEntries: true }); let foundDummyOutput = false; for (let i = 0; i < lines.length; i += 1) { if (foundDummyOutput) { @@ -387,15 +385,15 @@ export class CondaService implements ICondaService { * Return the highest Python version from the given list. */ private getLatestVersion(interpreters: PythonInterpreter[]) { - const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0); + const sortedInterpreters = interpreters.slice(); // tslint:disable-next-line:no-non-null-assertion - sortedInterpreters.sort((a, b) => compareVersion(a.version!, b.version!)); + sortedInterpreters.sort((a, b) => (a.version && b.version) ? a.version.compare(b.version) : 0); if (sortedInterpreters.length > 0) { return sortedInterpreters[sortedInterpreters.length - 1]; } } - private async getCondaFileFromInterpreter(interpreter: PythonInterpreter | undefined) : Promise { + private async getCondaFileFromInterpreter(interpreter: PythonInterpreter | undefined): Promise { const condaExe = this.platform.isWindows ? 'conda.exe' : 'conda'; const scriptsDir = this.platform.isWindows ? 'Scripts' : 'bin'; const interpreterDir = interpreter ? path.dirname(interpreter.path) : ''; @@ -479,7 +477,7 @@ export class CondaService implements ICondaService { /** * Called when the user changes the current interpreter. */ - private onInterpreterChanged() : void { + private onInterpreterChanged(): void { // Clear our activated environment cache as it can't match the current one anymore this.activatedEnvironmentCache = {}; } diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index cf74e1c13072..ee2f3be47a39 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -72,6 +72,7 @@ export class CurrentPathService extends CacheableLocatorService { if (!details) { return; } + this._hasInterpreters.resolve(true); return { ...(details as PythonInterpreter), path: pythonPath, diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index 4d6459392342..ffd55ff4f1b7 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -5,6 +5,7 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../common/application/types'; +import { traceError } from '../../../common/logger'; import { IFileSystem, IPlatformService } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; import { ICurrentProcess, ILogger } from '../../../common/types'; @@ -62,6 +63,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer if (!details) { return; } + this._hasInterpreters.resolve(true); return { ...(details as PythonInterpreter), path: interpreterPath, @@ -86,11 +88,10 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer } try { const pythonPath = await this.invokePipenv('--py', cwd); - // TODO: Why do we need to do this? - return pythonPath && await this.fs.fileExists(pythonPath) ? pythonPath : undefined; + return (pythonPath && await this.fs.fileExists(pythonPath)) ? pythonPath : undefined; // tslint:disable-next-line:no-empty } catch (error) { - console.error(error); + traceError('PipEnv identification failed', error); if (ignoreErrors) { return; } diff --git a/src/client/interpreter/locators/services/windowsRegistryService.ts b/src/client/interpreter/locators/services/windowsRegistryService.ts index c72d62ff6f77..a9817dd9141d 100644 --- a/src/client/interpreter/locators/services/windowsRegistryService.ts +++ b/src/client/interpreter/locators/services/windowsRegistryService.ts @@ -6,6 +6,7 @@ import { Uri } from 'vscode'; import { IPlatformService, IRegistry, RegistryHive } from '../../../common/platform/types'; import { IPathUtils } from '../../../common/types'; import { Architecture } from '../../../common/utils/platform'; +import { convertPythonVersionToSemver } from '../../../common/utils/version'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterHelper, InterpreterType, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; @@ -125,11 +126,12 @@ export class WindowsRegistryService extends CacheableLocatorService { return; } const version = interpreterInfo.version ? this.pathUtils.basename(interpreterInfo.version) : this.pathUtils.basename(tagKey); + this._hasInterpreters.resolve(true); // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion return { ...(details as PythonInterpreter), path: executablePath, - version, + version: convertPythonVersionToSemver(version), companyDisplayName: interpreterInfo.companyDisplayName, type: InterpreterType.Unknown } as PythonInterpreter; diff --git a/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts b/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts index d1605bcdd190..d45e66cbb98e 100644 --- a/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts +++ b/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts @@ -108,8 +108,8 @@ suite('Application Diagnostics - Checks Python Interpreter', () => { .returns(() => false) .verifiable(typemoq.Times.once()); interpreterService - .setup(i => i.getInterpreters(typemoq.It.isAny())) - .returns(() => Promise.resolve([])) + .setup(i => i.hasInterpreters) + .returns(() => Promise.resolve(false)) .verifiable(typemoq.Times.once()); const diagnostics = await diagnosticService.diagnose(); @@ -122,10 +122,14 @@ suite('Application Diagnostics - Checks Python Interpreter', () => { .setup(s => s.disableInstallationChecks) .returns(() => false) .verifiable(typemoq.Times.once()); + interpreterService + .setup(i => i.hasInterpreters) + .returns(() => Promise.resolve(true)) + .verifiable(typemoq.Times.once()); interpreterService .setup(i => i.getInterpreters(typemoq.It.isAny())) .returns(() => Promise.resolve([{} as any])) - .verifiable(typemoq.Times.once()); + .verifiable(typemoq.Times.never()); interpreterService .setup(i => i.getActiveInterpreter(typemoq.It.isAny())) .returns(() => { return Promise.resolve({ type: InterpreterType.Unknown } as any); }) @@ -146,10 +150,14 @@ suite('Application Diagnostics - Checks Python Interpreter', () => { .setup(s => s.disableInstallationChecks) .returns(() => false) .verifiable(typemoq.Times.once()); + interpreterService + .setup(i => i.hasInterpreters) + .returns(() => Promise.resolve(true)) + .verifiable(typemoq.Times.once()); interpreterService .setup(i => i.getInterpreters(typemoq.It.isAny())) .returns(() => Promise.resolve([{} as any])) - .verifiable(typemoq.Times.once()); + .verifiable(typemoq.Times.never()); interpreterService .setup(i => i.getActiveInterpreter(typemoq.It.isAny())) .returns(() => { return Promise.resolve({ type: InterpreterType.Unknown } as any); }) diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index d9bc9dfb2125..c98df67839c5 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -6,6 +6,7 @@ // tslint:disable:no-any max-func-body-length no-invalid-this import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Disposable, OutputChannel, Uri, WorkspaceConfiguration } from 'vscode'; import { IWorkspaceService } from '../../../client/common/application/types'; @@ -14,7 +15,6 @@ import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pi import { PipInstaller } from '../../../client/common/installer/pipInstaller'; import { ProductInstaller } from '../../../client/common/installer/productInstaller'; import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; -import { PythonVersionInfo } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IDisposableRegistry, IPythonSettings, ModuleNamePurpose, Product } from '../../../client/common/types'; import { getNamesAndValues } from '../../../client/common/utils/enum'; @@ -123,9 +123,9 @@ suite('Module Installer', () => { if (product.value === Product.pylint) { // tslint:disable-next-line:no-shadowed-variable generatePythonInterpreterVersions().forEach(interpreterInfo => { - const majorVersion = interpreterInfo.version_info[0]; + const majorVersion = interpreterInfo.version ? interpreterInfo.version.major : 0; if (majorVersion === 2) { - const testTitle = `Ensure install arg is \'pylint<2.0.0\' in ${interpreterInfo.version_info.join('.')}`; + const testTitle = `Ensure install arg is \'pylint<2.0.0\' in ${interpreterInfo.version ? interpreterInfo.version.raw : ''}`; if (installerClass === PipInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); @@ -157,7 +157,7 @@ suite('Module Installer', () => { }); } } else { - const testTitle = `Ensure install arg is \'pylint\' in ${interpreterInfo.version_info.join('.')}`; + const testTitle = `Ensure install arg is \'pylint\' in ${interpreterInfo.version ? interpreterInfo.version.raw : ''}`; if (installerClass === PipInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); @@ -244,12 +244,12 @@ suite('Module Installer', () => { }); function generatePythonInterpreterVersions() { - const versions: PythonVersionInfo[] = [[2, 7, 0, 'final'], [3, 4, 0, 'final'], [3, 5, 0, 'final'], [3, 6, 0, 'final'], [3, 7, 0, 'final']]; + const versions: SemVer[] = ['2.7.0-final', '3.4.0-final', '3.5.0-final', '3.6.0-final', '3.7.0-final'].map(ver => new SemVer(ver)); return versions.map(version => { const info = TypeMoq.Mock.ofType(); info.setup((t: any) => t.then).returns(() => undefined); info.setup(t => t.type).returns(() => InterpreterType.VirtualEnv); - info.setup(t => t.version_info).returns(() => version); + info.setup(t => t.version).returns(() => version); return info.object; }); } diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 72751f78d650..000f822d7c6a 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; import { IWorkspaceService } from '../../client/common/application/types'; @@ -37,8 +38,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('0.0.0-alpha'), sysPrefix: '', sysVersion: '' }; @@ -162,7 +162,9 @@ suite('Module Installer', () => { ioc.serviceManager.addSingletonInstance(IModuleInstaller, new MockModuleInstaller('mock', true)); const pythonPath = await getCurrentPythonPath(); const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, architecture: Architecture.Unknown, companyDisplayName: '', displayName: '', envName: '', path: pythonPath, type: InterpreterType.Conda, version: '' }])); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([ + { ...info, architecture: Architecture.Unknown, companyDisplayName: '', displayName: '', envName: '', path: pythonPath, type: InterpreterType.Conda, version: new SemVer('1.0.0') } + ])); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); diff --git a/src/test/common/terminals/pyenvActivationProvider.unit.test.ts b/src/test/common/terminals/pyenvActivationProvider.unit.test.ts index 99db68accbd6..5acc4880f854 100644 --- a/src/test/common/terminals/pyenvActivationProvider.unit.test.ts +++ b/src/test/common/terminals/pyenvActivationProvider.unit.test.ts @@ -4,6 +4,7 @@ 'use strict'; import { expect } from 'chai'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import '../../../client/common/extensions'; import { PyEnvActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; @@ -47,8 +48,7 @@ suite('Terminal Environment Activation pyenv', () => { architecture: Architecture.Unknown, path: '', sysPrefix: '', - version: '', - version_info: [1, 1, 1, 'alpha'], + version: new SemVer('1.1.1-alpha'), sysVersion: '', type: InterpreterType.Unknown }; @@ -66,8 +66,7 @@ suite('Terminal Environment Activation pyenv', () => { architecture: Architecture.Unknown, path: '', sysPrefix: '', - version: '', - version_info: [1, 1, 1, 'alpha'], + version: new SemVer('1.1.1-alpha'), sysVersion: '', type: InterpreterType.Pyenv }; @@ -85,8 +84,7 @@ suite('Terminal Environment Activation pyenv', () => { architecture: Architecture.Unknown, path: '', sysPrefix: '', - version: '', - version_info: [1, 1, 1, 'alpha'], + version: new SemVer('1.1.1-alpha'), sysVersion: '', type: InterpreterType.Pyenv, envName: 'my env name' diff --git a/src/test/configuration/interpreterSelector.unit.test.ts b/src/test/configuration/interpreterSelector.unit.test.ts index 019bfcb3494f..7fcf12024f67 100644 --- a/src/test/configuration/interpreterSelector.unit.test.ts +++ b/src/test/configuration/interpreterSelector.unit.test.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import * as assert from 'assert'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri } from 'vscode'; import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; @@ -20,8 +21,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('1.0.0-alpha'), sysPrefix: '', sysVersion: '' }; diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index be0bb2a97f6a..6bd9d3ea2efd 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -13,6 +13,7 @@ import * as TypeMoq from 'typemoq'; import * as uuid from 'uuid/v4'; import { Disposable, EventEmitter } from 'vscode'; +import { SemVer } from 'semver'; import { PythonSettings } from '../../client/common/configSettings'; import { ConfigurationService } from '../../client/common/configuration/service'; import { Logger } from '../../client/common/logger'; @@ -150,42 +151,38 @@ suite('Jupyter Execution', async () => { const workingPython: PythonInterpreter = { path: '/foo/bar/python.exe', - version: '3.6.6.6', + version: new SemVer('3.6.6-final'), sysVersion: '1.0.0.0', sysPrefix: 'Python', type: InterpreterType.Unknown, - architecture: Architecture.x64, - version_info: [3, 6, 6, 'final'] + architecture: Architecture.x64 }; const missingKernelPython: PythonInterpreter = { path: '/foo/baz/python.exe', - version: '3.1.1.1', + version: new SemVer('3.1.1-final'), sysVersion: '1.0.0.0', sysPrefix: 'Python', type: InterpreterType.Unknown, - architecture: Architecture.x64, - version_info: [3, 1, 1, 'final'] + architecture: Architecture.x64 }; const missingNotebookPython: PythonInterpreter = { path: '/bar/baz/python.exe', - version: '2.1.1.1', + version: new SemVer('2.1.1-final'), sysVersion: '1.0.0.0', sysPrefix: 'Python', type: InterpreterType.Unknown, - architecture: Architecture.x64, - version_info: [2, 1, 1, 'final'] + architecture: Architecture.x64 }; const missingNotebookPython2: PythonInterpreter = { path: '/two/baz/python.exe', - version: '2.1.1.1', + version: new SemVer('2.1.1'), sysVersion: '1.0.0.0', sysPrefix: 'Python', type: InterpreterType.Unknown, - architecture: Architecture.x64, - version_info: [2, 1, 1, 'final'] + architecture: Architecture.x64 }; let workingKernelSpec: string; @@ -512,8 +509,8 @@ suite('Jupyter Execution', async () => { assert.isOk(usableInterpreter, 'Usable intepreter not found'); if (usableInterpreter) { // Linter assert.notEqual(usableInterpreter.path, missingKernelPython.path); - assert.equal(usableInterpreter.version_info[0], missingKernelPython.version_info[0], 'Found interpreter should match on major'); - assert.notEqual(usableInterpreter.version_info[1], missingKernelPython.version_info[1], 'Found interpreter should not match on minor'); + assert.equal(usableInterpreter.version!.major, missingKernelPython.version!.major, 'Found interpreter should match on major'); + assert.notEqual(usableInterpreter.version!.minor, missingKernelPython.version!.minor, 'Found interpreter should not match on minor'); } }).timeout(10000); diff --git a/src/test/datascience/executionServiceMock.ts b/src/test/datascience/executionServiceMock.ts index 1d4ab293e0f2..aefb095b50cb 100644 --- a/src/test/datascience/executionServiceMock.ts +++ b/src/test/datascience/executionServiceMock.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import { SemVer } from 'semver'; import { ErrorUtils } from '../../client/common/errors/errorUtils'; import { ModuleNotInstalledError } from '../../client/common/errors/moduleNotInstalledError'; import { BufferDecoder } from '../../client/common/process/decoder'; @@ -10,7 +11,6 @@ import { InterpreterInfomation, IPythonExecutionService, ObservableExecutionResult, - PythonVersionInfo, SpawnOptions } from '../../client/common/process/types'; import { Architecture } from '../../client/common/utils/platform'; @@ -27,11 +27,10 @@ export class MockPythonExecutionService implements IPythonExecutionService { return Promise.resolve( { path: '', - version: '3.6', + version: new SemVer('3.6.0-beta'), sysVersion: '1.0', sysPrefix: '1.0', - architecture: Architecture.x64, - version_info: [ 3, 6, 0, 'beta'] as PythonVersionInfo + architecture: Architecture.x64 }); } diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index e5b2284a3040..e96713767fcf 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -56,7 +56,7 @@ suite('Jupyter notebook tests', () => { const python = await getNotebookCapableInterpreter(); const procService = await processFactory.create(); if (procService && python) { - await procService.exec(python.path, ['-m', 'jupyter', 'notebook', '--generate-config', '-y'], {env: process.env}); + await procService.exec(python.path, ['-m', 'jupyter', 'notebook', '--generate-config', '-y'], { env: process.env }); } } for (let i = 0; i < disposables.length; i += 1) { @@ -80,16 +80,16 @@ suite('Jupyter notebook tests', () => { return path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'datascience'); } - async function assertThrows(func : () => Promise, message: string) { - try { + async function assertThrows(func: () => Promise, message: string) { + try { await func(); assert.fail(message); - // tslint:disable-next-line:no-empty + // tslint:disable-next-line:no-empty } catch { } } - async function verifySimple(jupyterServer: INotebookServer | undefined, code: string, expectedValue: any) : Promise { + async function verifySimple(jupyterServer: INotebookServer | undefined, code: string, expectedValue: any): Promise { const cells = await jupyterServer!.execute(code, path.join(srcDirectory(), 'foo.py'), 2); assert.equal(cells.length, 1, `Wrong number of cells returned`); assert.equal(cells[0].data.cell_type, 'code', `Wrong type of cell returned`); @@ -108,7 +108,7 @@ suite('Jupyter notebook tests', () => { } } - async function verifyError(jupyterServer: INotebookServer | undefined, code: string, errorString: string) : Promise { + async function verifyError(jupyterServer: INotebookServer | undefined, code: string, errorString: string): Promise { const cells = await jupyterServer!.execute(code, path.join(srcDirectory(), 'foo.py'), 2); assert.equal(cells.length, 1, `Wrong number of cells returned`); assert.equal(cells[0].data.cell_type, 'code', `Wrong type of cell returned`); @@ -121,7 +121,7 @@ suite('Jupyter notebook tests', () => { } } - async function verifyCell(jupyterServer: INotebookServer | undefined, index: number, code: string, mimeType: string, cellType: string, verifyValue : (data: any) => void) : Promise { + async function verifyCell(jupyterServer: INotebookServer | undefined, index: number, code: string, mimeType: string, cellType: string, verifyValue: (data: any) => void): Promise { // Verify results of an execute const cells = await jupyterServer!.execute(code, path.join(srcDirectory(), 'foo.py'), 2); assert.equal(cells.length, 1, `${index}: Wrong number of cells returned`); @@ -154,7 +154,7 @@ suite('Jupyter notebook tests', () => { } } - function testMimeTypes(types : {code: string; mimeType: string; cellType: string; verifyValue(data: any): void}[]) { + function testMimeTypes(types: { code: string; mimeType: string; cellType: string; verifyValue(data: any): void }[]) { runTest('MimeTypes', async () => { // Test all mime types together so we don't have to startup and shutdown between // each @@ -204,7 +204,7 @@ suite('Jupyter notebook tests', () => { if (procService && python) { const connectionFound = createDeferred(); - const exeResult = procService.execObservable(python.path, ['-m', 'jupyter', 'notebook', '--no-browser'], {env: process.env, throwOnStdErr: false}); + const exeResult = procService.execObservable(python.path, ['-m', 'jupyter', 'notebook', '--no-browser'], { env: process.env, throwOnStdErr: false }); disposables.push(exeResult); exeResult.out.subscribe((output: Output) => { @@ -225,8 +225,8 @@ suite('Jupyter notebook tests', () => { } }); - function getConnectionInfo(output: string) : string | undefined { - const UrlPatternRegEx = /(https?:\/\/[^\s]+)/ ; + function getConnectionInfo(output: string): string | undefined { + const UrlPatternRegEx = /(https?:\/\/[^\s]+)/; const urlMatch = UrlPatternRegEx.exec(output); if (urlMatch) { @@ -238,7 +238,7 @@ suite('Jupyter notebook tests', () => { runTest('Failure', async () => { // Make a dummy class that will fail during launch class FailedProcess extends JupyterExecution { - public isNotebookSupported = () : Promise => { + public isNotebookSupported = (): Promise => { return Promise.resolve(false); } } @@ -252,6 +252,9 @@ suite('Jupyter notebook tests', () => { test('Not installed', async () => { // Rewire our data we use to search for processes class EmptyInterpreterService implements IInterpreterService { + public get hasInterpreters(): Promise { + return Promise.resolve(true); + } public onDidChangeInterpreter(_listener: (e: void) => any, _thisArgs?: any, _disposables?: Disposable[]): Disposable { return { dispose: noop }; } @@ -281,7 +284,7 @@ suite('Jupyter notebook tests', () => { } } class EmptyPathService implements IKnownSearchPathsForInterpreters { - public getSearchPaths() : string [] { + public getSearchPaths(): string[] { return []; } } @@ -372,7 +375,7 @@ suite('Jupyter notebook tests', () => { } } - async function testCancelableCall(method: (t: CancellationToken) => Promise, messageFormat: string, timeout: number) : Promise { + async function testCancelableCall(method: (t: CancellationToken) => Promise, messageFormat: string, timeout: number): Promise { const tokenSource = new TaggedCancellationTokenSource(messageFormat.format(timeout.toString())); const disp = setTimeout((s) => { tokenSource.cancel(); @@ -393,7 +396,7 @@ suite('Jupyter notebook tests', () => { return true; } - async function testCancelableMethod(method: (t: CancellationToken) => Promise, messageFormat: string, short?: boolean) : Promise { + async function testCancelableMethod(method: (t: CancellationToken) => Promise, messageFormat: string, short?: boolean): Promise { const timeouts = short ? [10, 20, 30, 100] : [100, 200, 300, 1000]; for (let i = 0; i < timeouts.length; i += 1) { await testCancelableCall(method, messageFormat, timeouts[i]); @@ -421,14 +424,14 @@ suite('Jupyter notebook tests', () => { assert.ok(await testCancelableMethod((t: CancellationToken) => jupyterExecution.isNotebookSupported(t), 'Cancel did not cancel isNotebook after {0}ms', true)); assert.ok(await testCancelableMethod((t: CancellationToken) => jupyterExecution.isKernelCreateSupported(t), 'Cancel did not cancel isKernel after {0}ms', true)); assert.ok(await testCancelableMethod((t: CancellationToken) => jupyterExecution.isImportSupported(t), 'Cancel did not cancel isImport after {0}ms', true)); - }); + }); - async function interruptExecute(server: INotebookServer, code: string, interruptMs: number, sleepMs: number) : Promise { + async function interruptExecute(server: INotebookServer, code: string, interruptMs: number, sleepMs: number): Promise { let interrupted = false; let finishedBefore = false; const finishedPromise = createDeferred(); const observable = server!.executeObservable(code, 'foo.py', 0); - let cells : ICell[] = []; + let cells: ICell[] = []; observable.subscribe(c => { cells = c; if (c.length > 0 && c[0].state === CellState.error) { @@ -465,7 +468,7 @@ suite('Jupyter notebook tests', () => { // Try with something we can interrupt let interruptResult = await interruptExecute(server!, -`import signal + `import signal import _thread import time @@ -491,7 +494,7 @@ while keep_going: // The tough one, somethign that causes a kernel reset. interruptResult = await interruptExecute(server!, -`import signal + `import signal import time import os @@ -549,7 +552,7 @@ df.head()`, { // Test relative directories too. code: - `import pandas as pd + `import pandas as pd df = pd.read_csv("./DefaultSalesReport.csv") df.head()`, mimeType: 'text/html', @@ -573,13 +576,13 @@ plt.show()`, ] ); - async function getNotebookCapableInterpreter() : Promise { + async function getNotebookCapableInterpreter(): Promise { const is = ioc.serviceContainer.get(IInterpreterService); const list = await is.getInterpreters(); const procService = await processFactory.create(); if (procService) { for (let i = 0; i < list.length; i += 1) { - const result = await procService.exec(list[i].path, ['-m', 'jupyter', 'notebook', '--version'], {env: process.env}); + const result = await procService.exec(list[i].path, ['-m', 'jupyter', 'notebook', '--version'], { env: process.env }); if (!result.stderr) { return list[i]; } @@ -595,7 +598,7 @@ plt.show()`, // Manually generate an invalid jupyter config const procService = await processFactory.create(); assert.ok(procService, 'Can not get a process service'); - const results = await procService!.exec(usable!.path, ['-m', 'jupyter', 'notebook', '--generate-config', '-y'], {env: process.env}); + const results = await procService!.exec(usable!.path, ['-m', 'jupyter', 'notebook', '--generate-config', '-y'], { env: process.env }); // Results should have our path to the config. const match = /^.*\s+(.*jupyter_notebook_config.py)\s+.*$/m.exec(results.stdout); diff --git a/src/test/install/channelManager.channels.test.ts b/src/test/install/channelManager.channels.test.ts index 09dd787abe88..a01fda507ee7 100644 --- a/src/test/install/channelManager.channels.test.ts +++ b/src/test/install/channelManager.channels.test.ts @@ -3,6 +3,7 @@ import * as assert from 'assert'; import { Container } from 'inversify'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { QuickPickOptions } from 'vscode'; import { IApplicationShell } from '../../client/common/application/types'; @@ -22,8 +23,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('0.0.0-alpha'), sysPrefix: '', sysVersion: '' }; diff --git a/src/test/install/channelManager.messages.test.ts b/src/test/install/channelManager.messages.test.ts index d1a11f1e6131..d6e9b25ffda6 100644 --- a/src/test/install/channelManager.messages.test.ts +++ b/src/test/install/channelManager.messages.test.ts @@ -3,6 +3,7 @@ import * as assert from 'assert'; import { Container } from 'inversify'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { IApplicationShell } from '../../client/common/application/types'; import { InstallationChannelManager } from '../../client/common/installer/channelManager'; @@ -22,8 +23,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('0.0.0-alpha'), sysPrefix: '', sysVersion: '' }; diff --git a/src/test/interpreters/condaEnvFileService.unit.test.ts b/src/test/interpreters/condaEnvFileService.unit.test.ts index 60fcd1d42dc5..6dd925ddf753 100644 --- a/src/test/interpreters/condaEnvFileService.unit.test.ts +++ b/src/test/interpreters/condaEnvFileService.unit.test.ts @@ -5,7 +5,7 @@ import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../client/common/platform/types'; import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { ICondaService, IInterpreterHelper, IInterpreterLocatorService, InterpreterType } from '../../client/interpreter/contracts'; -import { AnacondaCompanyName, AnacondaCompanyNames } from '../../client/interpreter/locators/services/conda'; +import { AnacondaCompanyName } from '../../client/interpreter/locators/services/conda'; import { CondaEnvFileService } from '../../client/interpreter/locators/services/condaEnvFileService'; import { IServiceContainer } from '../../client/ioc/types'; import { MockState } from './mocks'; @@ -35,7 +35,7 @@ suite('Interpreters from Conda Environments Text File', () => { }); test('Must return an empty list if environment file cannot be found', async () => { condaService.setup(c => c.condaEnvironmentsFile).returns(() => undefined); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'Mock Name' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await condaFileProvider.getInterpreters(); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); @@ -43,7 +43,7 @@ suite('Interpreters from Conda Environments Text File', () => { condaService.setup(c => c.condaEnvironmentsFile).returns(() => environmentsFilePath); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve('')); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'Mock Name' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await condaFileProvider.getInterpreters(); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); @@ -78,7 +78,7 @@ suite('Interpreters from Conda Environments Text File', () => { }); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(interpreterPaths.join(EOL))); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'Mock Name' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await condaFileProvider.getInterpreters(); @@ -96,23 +96,4 @@ suite('Interpreters from Conda Environments Text File', () => { await filterFilesInEnvironmentsFileAndReturnValidItems(true); }); - test('Must strip company name from version info', async () => { - const interpreterPaths = [ - path.join(environmentsPath, 'conda', 'envs', 'numpy') - ]; - const pythonPath = path.join(interpreterPaths[0], 'pythonPath'); - condaService.setup(c => c.condaEnvironmentsFile).returns(() => environmentsFilePath); - condaService.setup(c => c.getInterpreterPath(TypeMoq.It.isAny())).returns(() => pythonPath); - fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); - fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); - fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(interpreterPaths.join(EOL))); - - for (const companyName of AnacondaCompanyNames) { - const versionWithCompanyName = `Mock Version :: ${companyName}`; - interpreterHelper.setup(c => c.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: versionWithCompanyName })); - const interpreters = await condaFileProvider.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - } - }); }); diff --git a/src/test/interpreters/condaEnvService.unit.test.ts b/src/test/interpreters/condaEnvService.unit.test.ts index b3ec2de6caae..2b89594f19b4 100644 --- a/src/test/interpreters/condaEnvService.unit.test.ts +++ b/src/test/interpreters/condaEnvService.unit.test.ts @@ -68,7 +68,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await parseCondaInfo( info, @@ -108,7 +108,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); condaService.setup(c => c.getCondaInfo()).returns(() => Promise.resolve(info)); condaService.setup(c => c.getCondaEnvironments(TypeMoq.It.isAny())).returns(() => Promise.resolve([ @@ -151,7 +151,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await parseCondaInfo( info, @@ -179,7 +179,7 @@ suite('Interpreters from Conda Environments', () => { default_prefix: '', 'sys.version': '3.6.1 |Anaonda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]' }; - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); condaService.setup(c => c.getCondaInfo()).returns(() => Promise.resolve(info)); condaService.setup(c => c.getCondaEnvironments(TypeMoq.It.isAny())).returns(() => Promise.resolve([ { name: 'base', path: environmentsPath }, @@ -222,7 +222,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await parseCondaInfo( info, @@ -256,7 +256,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); condaService.setup(c => c.getCondaInfo()).returns(() => Promise.resolve(info)); condaService.setup(c => c.getCondaEnvironments(TypeMoq.It.isAny())).returns(() => Promise.resolve([ @@ -290,7 +290,7 @@ suite('Interpreters from Conda Environments', () => { }); const pythonPath = isWindows ? path.join(info.default_prefix, 'python.exe') : path.join(info.default_prefix, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); const interpreters = await parseCondaInfo( info, @@ -322,7 +322,7 @@ suite('Interpreters from Conda Environments', () => { path.join(environmentsPath, 'path3', 'three.exe')] }; const validPaths = info.envs.filter((_, index) => index % 2 === 0); - interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: undefined })); validPaths.forEach(envPath => { condaService.setup(c => c.getInterpreterPath(TypeMoq.It.isValue(envPath))).returns(environmentPath => { return isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'); diff --git a/src/test/interpreters/condaService.unit.test.ts b/src/test/interpreters/condaService.unit.test.ts index d7f126f0f567..3a999336a961 100644 --- a/src/test/interpreters/condaService.unit.test.ts +++ b/src/test/interpreters/condaService.unit.test.ts @@ -3,7 +3,7 @@ import * as assert from 'assert'; import { expect } from 'chai'; import { EOL } from 'os'; import * as path from 'path'; -import { parse } from 'semver'; +import { parse, SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Disposable, EventEmitter } from 'vscode'; @@ -33,8 +33,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('0.0.0-alpha'), sysPrefix: '', sysVersion: '' }; @@ -54,8 +53,8 @@ suite('Interpreters Conda Service', () => { let condaPathSetting: string; let disposableRegistry: Disposable[]; let interpreterService: TypeMoq.IMock; - let mockState : MockState; - let terminalProvider : TypeMoq.IMock; + let mockState: MockState; + let terminalProvider: TypeMoq.IMock; setup(async () => { condaPathSetting = ''; logger = TypeMoq.Mock.ofType(); @@ -312,9 +311,9 @@ suite('Interpreters Conda Service', () => { test('Must use Conda env from Registry to locate conda.exe', async () => { const condaPythonExePath = path.join('dumyPath', 'environments', 'conda', 'Scripts', 'python.exe'); const registryInterpreters: PythonInterpreter[] = [ - { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: '1.11.0', type: InterpreterType.Conda }, - { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, + { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: new SemVer('1.0.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: new SemVer('1.11.0'), type: InterpreterType.Conda }, + { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: new SemVer('2.10.1'), type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } ].map(item => { return { ...info, ...item }; @@ -333,12 +332,12 @@ suite('Interpreters Conda Service', () => { test('Must use Conda env from Registry to latest version of locate conda.exe', async () => { const condaPythonExePath = path.join('dumyPath', 'environments'); const registryInterpreters: PythonInterpreter[] = [ - { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda1', 'Scripts', 'python.exe'), companyDisplayName: 'Two 1', version: '1.11.0', type: InterpreterType.Conda }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda211', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.11', version: '2.11.0', type: InterpreterType.Conda }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda231', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.31', version: '2.31.0', type: InterpreterType.Conda }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.21', version: '2.21.0', type: InterpreterType.Conda }, - { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, + { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: new SemVer('1.0.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda1', 'Scripts', 'python.exe'), companyDisplayName: 'Two 1', version: new SemVer('1.11.0'), type: InterpreterType.Conda }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda211', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.11', version: new SemVer('2.11.0'), type: InterpreterType.Conda }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda231', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.31', version: new SemVer('2.31.0'), type: InterpreterType.Conda }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.21', version: new SemVer('2.21.0'), type: InterpreterType.Conda }, + { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: new SemVer('2.10.1'), type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } ].map(item => { return { ...info, ...item }; @@ -357,12 +356,12 @@ suite('Interpreters Conda Service', () => { test('Must use \'conda\' if conda.exe cannot be located using registry entries', async () => { const condaPythonExePath = path.join('dumyPath', 'environments'); const registryInterpreters: PythonInterpreter[] = [ - { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda1', 'Scripts', 'python.exe'), companyDisplayName: 'Two 1', version: '1.11.0', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda211', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.11', version: '2.11.0', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda231', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.31', version: '2.31.0', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.21', version: '2.21.0', type: InterpreterType.Unknown }, - { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, + { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: new SemVer('1.0.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda1', 'Scripts', 'python.exe'), companyDisplayName: 'Two 1', version: new SemVer('1.11.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda211', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.11', version: new SemVer('2.11.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda231', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.31', version: new SemVer('2.31.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.21', version: new SemVer('2.21.0'), type: InterpreterType.Unknown }, + { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: new SemVer('2.10.1'), type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } ].map(item => { return { ...info, ...item }; }); platformService.setup(p => p.isWindows).returns(() => true); @@ -559,9 +558,9 @@ suite('Interpreters Conda Service', () => { test('Must use Conda env from Registry to locate conda.exe', async () => { const condaPythonExePath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments', 'conda', 'Scripts', 'python.exe'); const registryInterpreters: PythonInterpreter[] = [ - { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: '1.11.0', type: InterpreterType.Unknown }, - { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, + { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: new SemVer('1.0.0'), type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: new SemVer('1.11.0'), type: InterpreterType.Unknown }, + { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: new SemVer('2.10.1'), type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } ].map(item => { return { ...info, ...item }; @@ -647,7 +646,7 @@ suite('Interpreters Conda Service', () => { await testFailureOfGettingCondaEnvironments(false, true, false, pythonPath); }); test('Create activated conda environment for Windows', async () => { - const pythonInterpreter: PythonInterpreter = {...info, type: InterpreterType.Conda, envPath: 'C:\\Anaconda', envName: 'Anaconda'}; + const pythonInterpreter: PythonInterpreter = { ...info, type: InterpreterType.Conda, envPath: 'C:\\Anaconda', envName: 'Anaconda' }; const environment: any = { Path: 'C:\\test' }; platformService.setup(p => p.isWindows).returns(() => true); @@ -662,7 +661,7 @@ suite('Interpreters Conda Service', () => { expect(newEnvironment.CONDA_DEFAULT_ENV).to.be.equal(pythonInterpreter.envName, 'Incorrect Windows CONDA_DEFAULT_ENV Value'); }); test('Create activated conda environment for Non-Windows', async () => { - const pythonInterpreter: PythonInterpreter = {...info, type: InterpreterType.Conda, envPath: 'usr/Anaconda', envName: 'Anaconda'}; + const pythonInterpreter: PythonInterpreter = { ...info, type: InterpreterType.Conda, envPath: 'usr/Anaconda', envName: 'Anaconda' }; const environment: any = { PATH: 'usr/test' }; platformService.setup(p => p.isWindows).returns(() => false); @@ -677,19 +676,19 @@ suite('Interpreters Conda Service', () => { expect(newEnvironment.CONDA_DEFAULT_ENV).to.be.equal(pythonInterpreter.envName, 'Incorrect Non-Windows CONDA_DEFAULT_ENV Value'); }); test('Create activated conda environment for using shell', async () => { - const pythonInterpreter: PythonInterpreter = {...info, type: InterpreterType.Conda, envPath: 'C:\\Anaconda\\Foo\\envs\\test', envName: 'Anaconda'}; + const pythonInterpreter: PythonInterpreter = { ...info, type: InterpreterType.Conda, envPath: 'C:\\Anaconda\\Foo\\envs\\test', envName: 'Anaconda' }; const environment: any = { Path: 'C:\\test' }; platformService.setup(p => p.isWindows).returns(() => true); fileSystem.setup(f => f.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(true)); - processService.setup(p => p.shellExec(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({stdout: `${CondaGetEnvironmentPrefix}\r\nCONDA_PREFIX=TEST_PREFIX` })); + processService.setup(p => p.shellExec(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: `${CondaGetEnvironmentPrefix}\r\nCONDA_PREFIX=TEST_PREFIX` })); const newEnvironment = await condaService.getActivatedCondaEnvironment(pythonInterpreter, environment); expect(newEnvironment.CONDA_PREFIX).to.be.equal('TEST_PREFIX', 'Incorrect shell exec CONDA_PREFIX Value'); }); test('Create activated conda environment for using shell that fails all', async () => { - const pythonInterpreter: PythonInterpreter = {...info, type: InterpreterType.Conda, envPath: 'C:\\Anaconda\\Foo\\envs\\test', envName: 'Anaconda'}; + const pythonInterpreter: PythonInterpreter = { ...info, type: InterpreterType.Conda, envPath: 'C:\\Anaconda\\Foo\\envs\\test', envName: 'Anaconda' }; const environment: any = { Path: 'C:\\test' }; platformService.setup(p => p.isWindows).returns(() => true); diff --git a/src/test/interpreters/currentPathService.unit.test.ts b/src/test/interpreters/currentPathService.unit.test.ts index b0f54009cdf7..b079d339495d 100644 --- a/src/test/interpreters/currentPathService.unit.test.ts +++ b/src/test/interpreters/currentPathService.unit.test.ts @@ -6,6 +6,7 @@ // tslint:disable:max-func-body-length no-any import { expect } from 'chai'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; @@ -61,7 +62,7 @@ suite('Interpreters CurrentPath Service', () => { [true, false].forEach(isWindows => { test(`Interpreters that do not exist on the file system are not excluded from the list (${isWindows ? 'windows' : 'not windows'})`, async () => { // Specific test for 1305 - const version = 'mockVersion'; + const version = new SemVer('1.0.0'); platformService.setup(p => p.isWindows).returns(() => isWindows); platformService.setup(p => p.osType).returns(() => isWindows ? OSType.Windows : OSType.Linux); interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version })); diff --git a/src/test/interpreters/display.unit.test.ts b/src/test/interpreters/display.unit.test.ts index 8b08756d7814..9ae7752a208c 100644 --- a/src/test/interpreters/display.unit.test.ts +++ b/src/test/interpreters/display.unit.test.ts @@ -1,5 +1,6 @@ import { expect } from 'chai'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Disposable, StatusBarAlignment, StatusBarItem, Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; @@ -20,8 +21,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('0.0.0-alpha'), sysPrefix: '', sysVersion: '' }; diff --git a/src/test/interpreters/interpreterService.unit.test.ts b/src/test/interpreters/interpreterService.unit.test.ts index 4efa40ae688f..70f810376b5f 100644 --- a/src/test/interpreters/interpreterService.unit.test.ts +++ b/src/test/interpreters/interpreterService.unit.test.ts @@ -10,6 +10,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import { EventEmitter } from 'events'; import { Container } from 'inversify'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Disposable, TextDocument, TextEditor, Uri, WorkspaceConfiguration } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; @@ -46,8 +47,7 @@ const info: PythonInterpreter = { envName: '', path: '', type: InterpreterType.Unknown, - version: '', - version_info: [0, 0, 0, 'alpha'], + version: new SemVer('0.0.0-alpha'), sysPrefix: '', sysVersion: '' }; @@ -587,7 +587,7 @@ suite('Interpreters service', () => { suite('Display Format (with all permutations)', () => { setup(setupSuite); [undefined, Uri.file('xyz')].forEach(resource => { - [undefined, [1, 2, 3, 'alpha']].forEach(versionInfo => { + [undefined, new SemVer('1.2.3-alpha')].forEach(version => { // Forced cast to ignore TS warnings. (EnumEx.getNamesAndValues(Architecture) as ({ name: string; value: Architecture } | undefined)[]).concat(undefined).forEach(arch => { [undefined, path.join('a', 'b', 'c', 'd', 'bin', 'python')].forEach(pythonPath => { @@ -596,7 +596,7 @@ suite('Interpreters service', () => { [undefined, 'my env name'].forEach(envName => { ['', 'my pipenv name'].forEach(pipEnvName => { const testName = [`${resource ? 'With' : 'Without'} a workspace`, - `${versionInfo ? 'with' : 'without'} version information`, + `${version ? 'with' : 'without'} version information`, `${arch ? arch.name : 'without'} architecture`, `${pythonPath ? 'with' : 'without'} python Path`, `${interpreterType ? `${interpreterType.name} interpreter type` : 'without interpreter type'}`, @@ -606,7 +606,7 @@ suite('Interpreters service', () => { test(testName, async () => { const interpreterInfo: Partial = { - version_info: versionInfo as any, + version, architecture: arch ? arch.value : undefined, envName, type: interpreterType ? interpreterType.value : undefined, @@ -635,8 +635,8 @@ suite('Interpreters service', () => { const displayNameParts: string[] = ['Python']; const envSuffixParts: string[] = []; - if (interpreterInfo.version_info && interpreterInfo.version_info.length > 0) { - displayNameParts.push(interpreterInfo.version_info.slice(0, 3).join('.')); + if (interpreterInfo.version) { + displayNameParts.push(`${interpreterInfo.version.major}.${interpreterInfo.version.minor}.${interpreterInfo.version.patch}`); } if (interpreterInfo.architecture) { displayNameParts.push(getArchitectureDisplayName(interpreterInfo.architecture)); diff --git a/src/test/interpreters/locators/helpers.unit.test.ts b/src/test/interpreters/locators/helpers.unit.test.ts index 740d6d3ff549..f94d8b93752b 100644 --- a/src/test/interpreters/locators/helpers.unit.test.ts +++ b/src/test/interpreters/locators/helpers.unit.test.ts @@ -7,9 +7,9 @@ import { expect } from 'chai'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { PythonVersionInfo } from '../../../client/common/process/types'; import { getNamesAndValues } from '../../../client/common/utils/enum'; import { Architecture } from '../../../client/common/utils/platform'; import { IInterpreterHelper, IInterpreterLocatorHelper, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; @@ -59,8 +59,7 @@ suite('Interpreters - Locators Helper', () => { sysPrefix: name, sysVersion: name, type: InterpreterType.Unknown, - version: name, - version_info: [0, 0, 0, 'alpha'] as PythonVersionInfo + version: new SemVer('0.0.0-alpha') }; interpreters.push(interpreter); @@ -105,8 +104,7 @@ suite('Interpreters - Locators Helper', () => { sysPrefix: name, sysVersion: name, type: InterpreterType.Unknown, - version: name, - version_info: [3, parseInt(name.substr(-1), 10), 0, 'final'] as PythonVersionInfo + version: new SemVer(`3.${parseInt(name.substr(-1), 10)}.0-final`) }; interpreters.push(interpreter); expectedInterpreters.push(interpreter); @@ -120,8 +118,7 @@ suite('Interpreters - Locators Helper', () => { sysPrefix: name, sysVersion: name, type: InterpreterType.Unknown, - version: name, - version_info: [3, parseInt(name.substr(-1), 10), 0, 'final'] as PythonVersionInfo + version: new SemVer(`3.${parseInt(name.substr(-1), 10)}.0-final`) }; const duplicateInterpreter = { @@ -131,8 +128,7 @@ suite('Interpreters - Locators Helper', () => { sysPrefix: name, sysVersion: name, type: InterpreterType.Unknown, - version: name, - version_info: interpreter.version_info + version: new SemVer(interpreter.version.raw) }; interpreters.push(interpreter); @@ -177,8 +173,7 @@ suite('Interpreters - Locators Helper', () => { sysPrefix: name, sysVersion: name, type, - version: name, - version_info: [3, parseInt(name.substr(-1), 10), 0, 'final'] as PythonVersionInfo + version: new SemVer(`3.${parseInt(name.substr(-1), 10)}.0-final`) }; interpreters.push(interpreter); diff --git a/src/test/interpreters/locators/index.unit.test.ts b/src/test/interpreters/locators/index.unit.test.ts index 4ab8533c7858..fc4fa6c7c6f5 100644 --- a/src/test/interpreters/locators/index.unit.test.ts +++ b/src/test/interpreters/locators/index.unit.test.ts @@ -6,6 +6,7 @@ // tslint:disable:max-func-body-length import { expect } from 'chai'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Uri } from 'vscode'; import { IPlatformService } from '../../../client/common/platform/types'; @@ -63,11 +64,14 @@ suite('Interpreters - Locators Index', () => { sysPrefix: typeName, sysVersion: typeName, type: InterpreterType.Unknown, - version: typeName, - version_info: [0, 0, 0, 'alpha'] + version: new SemVer('0.0.0-alpha') }; const typeLocator = TypeMoq.Mock.ofType(); + typeLocator + .setup(l => l.hasInterpreters) + .returns(() => Promise.resolve(true)) + .verifiable(TypeMoq.Times.once()); typeLocator .setup(l => l.getInterpreters(TypeMoq.It.isValue(resource))) .returns(() => Promise.resolve([interpreter])) @@ -120,11 +124,14 @@ suite('Interpreters - Locators Index', () => { sysPrefix: typeName, sysVersion: typeName, type: InterpreterType.Unknown, - version: typeName, - version_info: [0, 0, 0, 'alpha'] + version: new SemVer('0.0.0-alpha') }; const typeLocator = TypeMoq.Mock.ofType(); + typeLocator + .setup(l => l.hasInterpreters) + .returns(() => Promise.resolve(true)) + .verifiable(TypeMoq.Times.once()); typeLocator .setup(l => l.getInterpreters(TypeMoq.It.isValue(resource))) .returns(() => Promise.resolve([interpreter])) diff --git a/src/test/interpreters/locators/progressService.unit.test.ts b/src/test/interpreters/locators/progressService.unit.test.ts index 6551f655e44a..75c719c6c000 100644 --- a/src/test/interpreters/locators/progressService.unit.test.ts +++ b/src/test/interpreters/locators/progressService.unit.test.ts @@ -17,6 +17,9 @@ import { sleep } from '../../core'; suite('Interpreters - Locator Progress', () => { class Locator implements IInterpreterLocatorService { + public get hasInterpreters(): Promise { + return Promise.resolve(true); + } public locatingCallback?: (e: Promise) => any; public onLocating(listener: (e: Promise) => any, thisArgs?: any, disposables?: Disposable[]): Disposable { this.locatingCallback = listener; diff --git a/src/test/interpreters/pipEnvService.unit.test.ts b/src/test/interpreters/pipEnvService.unit.test.ts index 2291d9b17598..84d9069e05e5 100644 --- a/src/test/interpreters/pipEnvService.unit.test.ts +++ b/src/test/interpreters/pipEnvService.unit.test.ts @@ -7,6 +7,7 @@ import { expect } from 'chai'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; @@ -129,7 +130,7 @@ suite('Interpreters - PipEnv', () => { envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); - interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'xyz' })); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: new SemVer('1.0.0') })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)).verifiable(); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)).verifiable(); const environments = await pipEnvService.getInterpreters(resource); @@ -146,7 +147,7 @@ suite('Interpreters - PipEnv', () => { envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); - interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'xyz' })); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: new SemVer('1.0.0') })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.never()); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)).verifiable(); diff --git a/src/test/interpreters/windowsRegistryService.unit.test.ts b/src/test/interpreters/windowsRegistryService.unit.test.ts index 4d3b4a736b41..cc59274419b2 100644 --- a/src/test/interpreters/windowsRegistryService.unit.test.ts +++ b/src/test/interpreters/windowsRegistryService.unit.test.ts @@ -11,7 +11,8 @@ import { MockRegistry, MockState } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); -// tslint:disable-next-line:max-func-body-length +// tslint:disable:max-func-body-length no-octal-literal + suite('Interpreters from Windows Registry (unit)', () => { let serviceContainer: TypeMoq.IMock; let interpreterHelper: TypeMoq.IMock; @@ -58,7 +59,7 @@ suite('Interpreters from Windows Registry (unit)', () => { { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'one.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: '9.9.9.final', name: 'SysVersion' }, { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' } ]; const registry = new MockRegistry(registryKeys, registryValues); @@ -73,15 +74,15 @@ suite('Interpreters from Windows Registry (unit)', () => { assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'one.exe'), 'Incorrect executable path'); - assert.equal(interpreters[0].version, 'Version.Tag1', 'Incorrect version'); + assert.equal(interpreters[0].version!.raw, '9.9.9-final', 'Incorrect version'); }); test('Must default names for PythonCore and exe', async () => { const registryKeys = [ { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore'] }, - { key: '\\Software\\Python\\PythonCore', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore\\Tag1'] } + { key: '\\Software\\Python\\PythonCore', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore\\9.9.9-final'] } ]; const registryValues = [ - { key: '\\Software\\Python\\PythonCore\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } + { key: '\\Software\\Python\\PythonCore\\9.9.9-final\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } ]; const registry = new MockRegistry(registryKeys, registryValues); const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); @@ -95,7 +96,7 @@ suite('Interpreters from Windows Registry (unit)', () => { assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[0].companyDisplayName, 'Python Software Foundation', 'Incorrect company name'); assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + assert.equal(interpreters[0].version!.raw, '9.9.9-final', 'Incorrect version'); }); test('Must ignore company \'PyLauncher\'', async () => { const registryKeys = [ @@ -115,10 +116,10 @@ suite('Interpreters from Windows Registry (unit)', () => { test('Must return a single entry and when registry contains only the InstallPath', async () => { const registryKeys = [ { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1'] } + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\9.9.9-final'] } ]; const registryValues = [ - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } + { key: '\\Software\\Python\\Company One\\9.9.9-final\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } ]; const registry = new MockRegistry(registryKeys, registryValues); const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); @@ -131,37 +132,38 @@ suite('Interpreters from Windows Registry (unit)', () => { assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[0].companyDisplayName, 'Company One', 'Incorrect company name'); assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + assert.equal(interpreters[0].version!.raw, '9.9.9-final', 'Incorrect version'); }); test('Must return multiple entries', async () => { const registryKeys = [ { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, - { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, - { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, - { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, - { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\1.0.0', '\\Software\\Python\\Company One\\2.0.0'] }, + { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\3.0.0', '\\Software\\Python\\Company Two\\4.0.0', '\\Software\\Python\\Company Two\\5.0.0'] }, + { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\6.0.0'] }, + { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['7.0.0'] }, + { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['8.0.0'] } ]; const registryValues = [ { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2'), name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2'), name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + + { key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, + { key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path3') }, + { key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: '3.0.0', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path3') }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, + { key: '\\Software\\Python\\Company Two\\5.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company Three\\6.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe') } + { key: '\\Software\\Python\\Company A\\8.0.0\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe') } ]; const registry = new MockRegistry(registryKeys, registryValues); const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); @@ -174,52 +176,58 @@ suite('Interpreters from Windows Registry (unit)', () => { assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + assert.equal(interpreters[0].version!.raw, '1.0.0-unknown', 'Incorrect version'); assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[1].version, 'Tag2', 'Incorrect version'); + assert.equal(interpreters[1].version!.raw, '2.0.0-unknown', 'Incorrect version'); assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); assert.equal(interpreters[2].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[2].version, 'Tag B', 'Incorrect version'); + assert.equal(interpreters[2].version!.raw, '4.0.0-unknown', 'Incorrect version'); + + assert.equal(interpreters[3].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[3].companyDisplayName, 'Company Two', 'Incorrect company name'); + assert.equal(interpreters[3].path, path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[3].version!.raw, '5.0.0-unknown', 'Incorrect version'); }); test('Must return multiple entries excluding the invalid registry items and duplicate paths', async () => { const registryKeys = [ { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three', '\\Software\\Python\\Company Four', '\\Software\\Python\\Company Five', 'Missing Tag'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, - { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, - { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, - { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\Four !'] }, - { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\Five !'] }, - { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, - { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\1.0.0', '\\Software\\Python\\Company One\\2.0.0'] }, + { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\3.0.0', '\\Software\\Python\\Company Two\\4.0.0', '\\Software\\Python\\Company Two\\5.0.0'] }, + { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\6.0.0'] }, + { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\7.0.0'] }, + { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\8.0.0'] }, + { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['9.0.0'] }, + { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['10.0.0'] } ]; const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: '1.0.0-final', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, + { key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, + { key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: '3.0.0', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, + { key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, + + { key: '\\Software\\Python\\Company Two\\5.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, // tslint:disable-next-line:no-any - { key: '\\Software\\Python\\Company Five\\Five !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: undefined }, + { key: '\\Software\\Python\\Company Five\\8.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: undefined }, - { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company Three\\6.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') } + { key: '\\Software\\Python\\Company A\\10.0.0\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') } ]; const registry = new MockRegistry(registryKeys, registryValues); const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); @@ -232,23 +240,28 @@ suite('Interpreters from Windows Registry (unit)', () => { assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); assert.equal(interpreters[0].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + assert.equal(interpreters[0].version!.raw, '1.0.0-unknown', 'Incorrect version'); assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); assert.equal(interpreters[1].path, path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[1].version, 'Tag2', 'Incorrect version'); + assert.equal(interpreters[1].version!.raw, '2.0.0-unknown', 'Incorrect version'); assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); assert.equal(interpreters[2].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[2].version, 'Tag A', 'Incorrect version'); + assert.equal(interpreters[2].version!.raw, '3.0.0-unknown', 'Incorrect version'); + + assert.equal(interpreters[3].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[3].companyDisplayName, 'Company Two', 'Incorrect company name'); + assert.equal(interpreters[3].path, path.join(environmentsPath, 'path2', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[3].version!.raw, '4.0.0-unknown', 'Incorrect version'); }); test('Must return multiple entries excluding the invalid registry items and nonexistent paths', async () => { const registryKeys = [ { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three', '\\Software\\Python\\Company Four', '\\Software\\Python\\Company Five', 'Missing Tag'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, - { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\1.0.0', '\\Software\\Python\\Company One\\Tag2'] }, + { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\2.0.0', '\\Software\\Python\\Company Two\\Tag C'] }, { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\Four !'] }, { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\Five !'] }, @@ -257,19 +270,20 @@ suite('Interpreters from Windows Registry (unit)', () => { ]; const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy') }, { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path') }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: '2.0.0', name: 'SysVersion' }, + + { key: '\\Software\\Python\\Company Two\\2.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, + { key: '\\Software\\Python\\Company Two\\2.0.0\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') }, // tslint:disable-next-line:no-any @@ -291,11 +305,11 @@ suite('Interpreters from Windows Registry (unit)', () => { assert.equal(interpreters[0].architecture, Architecture.x86, '1. Incorrect arhictecture'); assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', '1. Incorrect company name'); assert.equal(interpreters[0].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), '1. Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', '1. Incorrect version'); + assert.equal(interpreters[0].version!.raw, '1.0.0-unknown', '1. Incorrect version'); assert.equal(interpreters[1].architecture, Architecture.x86, '2. Incorrect arhictecture'); assert.equal(interpreters[1].companyDisplayName, 'Company Two', '2. Incorrect company name'); assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), '2. Incorrect path'); - assert.equal(interpreters[1].version, 'Tag B', '2. Incorrect version'); + assert.equal(interpreters[1].version!.raw, '2.0.0-unknown', '2. Incorrect version'); }); });