From 05e911ab9c2f4900f0cb21c84ef0fb834590a2e6 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 01:44:53 -0800 Subject: [PATCH 1/9] Added workspace virtual env watcher --- .../locators/lowLevel/fsWatchingLocator.ts | 2 +- .../lowLevel/workspaceVirtualEnvLocator.ts | 17 +- src/client/pythonEnvironments/index.ts | 2 - ...kspaceVirtualEnvLocator.testvirtualenvs.ts | 169 ++++++++++++++++++ .../workspaceVirtualEnvLocator.unit.test.ts | 11 +- 5 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 67dfc224f039..203b0d70e036 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -72,7 +72,7 @@ export abstract class FSWatchingLocator extends Locator { await sleep(this.opts.delayOnCreated); } } - const kind = await this.getKind(executable); + const kind = type === FileChangeType.Deleted ? undefined : await this.getKind(executable); this.emitter.fire({ type, kind }); }, this.opts.executableBaseGlob, diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts index 819b743bf917..097f0f0de16b 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts @@ -15,7 +15,8 @@ import { isPipenvEnvironment } from '../../../discovery/locators/services/pipEnv import { isVenvEnvironment, isVirtualenvEnvironment } from '../../../discovery/locators/services/virtualEnvironmentIdentifier'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { buildEnvInfo } from '../../info/env'; -import { IPythonEnvsIterator, Locator } from '../../locator'; +import { IDisposableLocator, IPythonEnvsIterator } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; /** * Default number of levels of sub-directories to recurse when looking for interpreters. @@ -75,9 +76,13 @@ async function buildSimpleVirtualEnvInfo(executablePath: string, kind: PythonEnv /** * Finds and resolves virtual environments created in workspace roots. */ -export class WorkspaceVirtualEnvironmentLocator extends Locator { +class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator { public constructor(private readonly root: string) { - super(); + super(() => getWorkspaceVirtualEnvDirs(this.root), getVirtualEnvKind, { + // Note detecting kind of virtual env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. + delayOnCreated: 1000, + }); } public iterEnvs(): IPythonEnvsIterator { @@ -128,3 +133,9 @@ export class WorkspaceVirtualEnvironmentLocator extends Locator { return undefined; } } + +export async function createWorkspaceVirtualEnvLocator(root: string): Promise { + const locator = new WorkspaceVirtualEnvironmentLocator(root); + await locator.initialize(); + return locator; +} diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 12b9d084d620..a540b6874612 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -11,7 +11,6 @@ import { IDisposableLocator, IPythonEnvsIterator, PythonLocatorQuery } from './base/locator'; import { CachingLocator } from './base/locators/composite/cachingLocator'; -import { WorkspaceVirtualEnvironmentLocator } from './base/locators/lowLevel/workspaceVirtualEnvLocator'; import { PythonEnvsChangedEvent } from './base/watcher'; import { getGlobalPersistentStore, initializeExternalDependencies as initializeLegacyExternalDependencies } from './common/externalDependencies'; import { ExtensionLocators, WorkspaceLocators } from './discovery/locators'; @@ -101,7 +100,6 @@ async function initLocators(): Promise { const workspaceLocators = new WorkspaceLocators([ // Add an ILocator factory func here for each kind of workspace-rooted locator. - (root: vscode.Uri) => [new WorkspaceVirtualEnvironmentLocator(root.fsPath)], ]); // Any non-workspace locator activation goes here. diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts new file mode 100644 index 000000000000..9a4b3ce230bf --- /dev/null +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { assert } from 'chai'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { traceWarning } from '../../../../../client/common/logger'; +import { FileChangeType } from '../../../../../client/common/platform/fileSystemWatcher'; +import { createDeferred, Deferred, sleep } from '../../../../../client/common/utils/async'; +import { IDisposableLocator } from '../../../../../client/pythonEnvironments/base/locator'; +import { createWorkspaceVirtualEnvLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator'; +import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; +import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; +import { getInterpreterPathFromDir } from '../../../../../client/pythonEnvironments/common/commonUtils'; +import { arePathsSame } from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import { deleteFiles, PYTHON_PATH } from '../../../../common'; +import { TEST_TIMEOUT } from '../../../../constants'; +import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; +import { run } from '../../../discovery/locators/envTestUtils'; + +class WorkspaceVenvs { + constructor(private readonly root: string, private readonly prefix = '.virtualenv-') { } + + public async create(name: string): Promise { + const envName = this.resolve(name); + const argv = [PYTHON_PATH.fileToCommandArgument(), '-m', 'virtualenv', envName]; + try { + await run(argv, { cwd: this.root }); + } catch (err) { + throw new Error(`Failed to create Env ${path.basename(envName)} Error: ${err}`); + } + const dirToLookInto = path.join(this.root, envName); + const filename = await getInterpreterPathFromDir(dirToLookInto); + if (!filename) { + throw new Error(`No environment to update exists in ${dirToLookInto}`); + } + return filename; + } + + // eslint-disable-next-line class-methods-use-this + public async update(filename: string): Promise { + try { + await fs.writeFile(filename, 'Environment has been updated'); + } catch (err) { + throw new Error(`Failed to update Workspace virtualenv executable ${filename}, Error: ${err}`); + } + } + + // eslint-disable-next-line class-methods-use-this + public async delete(filename: string): Promise { + try { + await fs.remove(filename); + } catch (err) { + traceWarning(`Failed to clean up ${filename}`); + } + } + + public async cleanUp() { + const globPattern = path.join(this.root, `${this.prefix}*`); + await deleteFiles(globPattern); + } + + private resolve(name: string): string { + // Ensure env is random to avoid conflicts in tests (corrupting test data) + const now = new Date().getTime().toString().substr(-8); + return `${this.prefix}${name}${now}`; + } +} + +suite('WorkspaceVirtualEnvironment Locator', async () => { + const testWorkspaceFolder = path.join(TEST_LAYOUT_ROOT, 'workspace', 'folder1'); + const workspaceVenvs = new WorkspaceVenvs(testWorkspaceFolder); + let locator: IDisposableLocator; + + async function waitForChangeToBeDetected(deferred: Deferred) { + const timeout = setTimeout( + () => { + clearTimeout(timeout); + deferred.reject(new Error('Environment not detected')); + }, + TEST_TIMEOUT, + ); + await deferred.promise; + } + + async function isLocated(executable: string): Promise { + const items = await getEnvs(locator.iterEnvs()); + return items.some((item) => arePathsSame(item.executable.filename, executable)); + } + + suiteSetup(async () => { + await workspaceVenvs.cleanUp(); + }); + + async function setupLocator(onChanged: (e: PythonEnvsChangedEvent) => Promise) { + locator = await createWorkspaceVirtualEnvLocator(testWorkspaceFolder); + // Wait for watchers to get ready + await sleep(1000); + locator.onChanged(onChanged); + } + + teardown(async () => { + await workspaceVenvs.cleanUp(); + locator.dispose(); + }); + + test('Detect a new environment', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + await setupLocator(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + + const executable = await workspaceVenvs.create('one'); + await waitForChangeToBeDetected(deferred); + const isFound = await isLocated(executable); + + assert.ok(isFound); + // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before + // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. + assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); + }); + + test('Detect when an environment has been deleted', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + const executable = await workspaceVenvs.create('one'); + // Wait before the change event has been sent. If both operations occur almost simultaneously no event is sent. + await sleep(100); + await setupLocator(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + + // VSCode API has a limitation where it fails to fire event when environment folder is deleted directly: + // https://github.com/microsoft/vscode/issues/110923 + // Using chokidar directly in tests work, but it has permission issues on Windows that you cannot delete a + // folder if it has a subfolder that is being watched inside: https://github.com/paulmillr/chokidar/issues/422 + // Hence we test directly deleting the executable, and not the whole folder using `workspaceVenvs.cleanUp()`. + await workspaceVenvs.delete(executable); + await waitForChangeToBeDetected(deferred); + const isFound = await isLocated(executable); + + assert.notOk(isFound); + assert.deepEqual(actualEvent!.type, FileChangeType.Deleted, 'Wrong event emitted'); + }); + + test('Detect when an environment has been updated', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + const executable = await workspaceVenvs.create('one'); + // Wait before the change event has been sent. If both operations occur almost simultaneously no event is sent. + await sleep(100); + await setupLocator(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + + await workspaceVenvs.update(executable); + await waitForChangeToBeDetected(deferred); + const isFound = await isLocated(executable); + + assert.ok(isFound); + // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before + // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. + assert.deepEqual(actualEvent!.type, FileChangeType.Changed, 'Wrong event emitted'); + }); +}); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts index 5a438b27deba..326f55a916e1 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts @@ -10,9 +10,10 @@ import { PythonEnvKind, PythonReleaseLevel, PythonVersion, - UNKNOWN_PYTHON_VERSION + UNKNOWN_PYTHON_VERSION, } from '../../../../../client/pythonEnvironments/base/info'; -import { WorkspaceVirtualEnvironmentLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator'; +import { IDisposableLocator } from '../../../../../client/pythonEnvironments/base/locator'; +import { createWorkspaceVirtualEnvLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator'; import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { assertEnvEqual, assertEnvsEqual } from '../../../discovery/locators/envTestUtils'; @@ -20,7 +21,7 @@ import { assertEnvEqual, assertEnvsEqual } from '../../../discovery/locators/env suite('WorkspaceVirtualEnvironment Locator', () => { const testWorkspaceFolder = path.join(TEST_LAYOUT_ROOT, 'workspace', 'folder1'); let getOSTypeStub: sinon.SinonStub; - let locator: WorkspaceVirtualEnvironmentLocator; + let locator: IDisposableLocator; function createExpectedEnvInfo( interpreterPath: string, @@ -53,10 +54,10 @@ suite('WorkspaceVirtualEnvironment Locator', () => { assert.deepStrictEqual(actualPaths, expectedPaths); } - setup(() => { + setup(async () => { getOSTypeStub = sinon.stub(platformUtils, 'getOSType'); getOSTypeStub.returns(platformUtils.OSType.Linux); - locator = new WorkspaceVirtualEnvironmentLocator(testWorkspaceFolder); + locator = await createWorkspaceVirtualEnvLocator(testWorkspaceFolder); }); teardown(() => { getOSTypeStub.restore(); From 9f9dd6f074d053e2e9c214afde4a4518c7c1463a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 13:16:39 -0800 Subject: [PATCH 2/9] Fix tests --- .../base/locators/lowLevel/fsWatchingLocator.ts | 14 +++++++++++--- .../locators/services/windowsStoreLocator.ts | 2 +- .../workspaceVirtualEnvLocator.testvirtualenvs.ts | 2 +- ...balVirtualEnvironmentLocator.testvirtualenvs.ts | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 203b0d70e036..be0da504f095 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -29,11 +29,17 @@ export abstract class FSWatchingLocator extends Locator { /** * Glob which represents basename of the executable to watch. */ - executableBaseGlob?: string, + executableBaseGlob?: string; /** * Time to wait before handling an environment-created event. */ - delayOnCreated?: number, // milliseconds + delayOnCreated?: number; // milliseconds + /** + * Fetching kind after deletion normally fails because the file structure around the + * executable is no longer available, so it is skipped by default. But in some cases + * the file structure is not needed, so this option becomes helpful. + */ + fetchEnvKindOnDeletion?: boolean; } = {}, ) { super(); @@ -72,7 +78,9 @@ export abstract class FSWatchingLocator extends Locator { await sleep(this.opts.delayOnCreated); } } - const kind = type === FileChangeType.Deleted ? undefined : await this.getKind(executable); + const kind = type === FileChangeType.Deleted && !this.opts.fetchEnvKindOnDeletion + ? undefined + : await this.getKind(executable); this.emitter.fire({ type, kind }); }, this.opts.executableBaseGlob, diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts index 2db4a203a611..eddf25794d0b 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts @@ -145,7 +145,7 @@ class WindowsStoreLocator extends FSWatchingLocator { super( getWindowsStoreAppsRoot, async () => this.kind, - { executableBaseGlob: pythonExeGlob }, + { executableBaseGlob: pythonExeGlob, fetchEnvKindOnDeletion: true }, ); } diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts index 9a4b3ce230bf..c8e75867e91c 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -19,7 +19,7 @@ import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { run } from '../../../discovery/locators/envTestUtils'; class WorkspaceVenvs { - constructor(private readonly root: string, private readonly prefix = '.virtualenv-') { } + constructor(private readonly root: string, private readonly prefix = '.v') { } public async create(name: string): Promise { const envName = this.resolve(name); diff --git a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts index db1a30cccc0a..fba9667f0937 100644 --- a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts @@ -19,7 +19,7 @@ const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); const testWorkOnHomePath = path.join(testVirtualHomeDir, 'workonhome'); class GlobalVenvs { - constructor(private readonly prefix = '.virtualenv-') { } + constructor(private readonly prefix = '.v') { } public async create(name: string): Promise { const envName = this.resolve(name); From ef7a05d20b1c21c55b1aea50d2fc081583bf14ed Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 15:10:03 -0800 Subject: [PATCH 3/9] Remove option --- .../base/locators/lowLevel/fsWatchingLocator.ts | 12 +++--------- .../locators/services/windowsStoreLocator.ts | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index be0da504f095..cdaa8dad1c13 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -34,12 +34,6 @@ export abstract class FSWatchingLocator extends Locator { * Time to wait before handling an environment-created event. */ delayOnCreated?: number; // milliseconds - /** - * Fetching kind after deletion normally fails because the file structure around the - * executable is no longer available, so it is skipped by default. But in some cases - * the file structure is not needed, so this option becomes helpful. - */ - fetchEnvKindOnDeletion?: boolean; } = {}, ) { super(); @@ -78,9 +72,9 @@ export abstract class FSWatchingLocator extends Locator { await sleep(this.opts.delayOnCreated); } } - const kind = type === FileChangeType.Deleted && !this.opts.fetchEnvKindOnDeletion - ? undefined - : await this.getKind(executable); + // Fetching kind after deletion normally fails because the file structure around the + // executable is no longer available, so ignore the errors. + const kind = await this.getKind(executable).catch(() => undefined); this.emitter.fire({ type, kind }); }, this.opts.executableBaseGlob, diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts index eddf25794d0b..2db4a203a611 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts @@ -145,7 +145,7 @@ class WindowsStoreLocator extends FSWatchingLocator { super( getWindowsStoreAppsRoot, async () => this.kind, - { executableBaseGlob: pythonExeGlob, fetchEnvKindOnDeletion: true }, + { executableBaseGlob: pythonExeGlob }, ); } From c9139ed561dbfa0589b39f812ddd2541c15d197f Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 20:36:39 -0800 Subject: [PATCH 4/9] Mege the two tests together --- ...kspaceVirtualEnvLocator.testvirtualenvs.ts | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts index c8e75867e91c..df4dac7f28eb 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// eslint-disable-next-line max-classes-per-file import { assert } from 'chai'; import * as fs from 'fs-extra'; import * as path from 'path'; @@ -13,13 +14,41 @@ import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUt import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; import { getInterpreterPathFromDir } from '../../../../../client/pythonEnvironments/common/commonUtils'; import { arePathsSame } from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import { createGlobalVirtualEnvironmentLocator } from '../../../../../client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator'; import { deleteFiles, PYTHON_PATH } from '../../../../common'; import { TEST_TIMEOUT } from '../../../../constants'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { run } from '../../../discovery/locators/envTestUtils'; +const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); +const testWorkOnHomePath = path.join(testVirtualHomeDir, 'workonhome'); +class GlobalVenvs { + constructor(private readonly prefix = '.v') { } + + public async create(name: string): Promise { + const envName = this.resolve(name); + const argv = [PYTHON_PATH.fileToCommandArgument(), '-m', 'virtualenv', envName]; + try { + await run(argv, { cwd: testWorkOnHomePath }); + } catch (err) { + throw new Error(`Failed to create Env ${path.basename(envName)} Error: ${err}`); + } + return envName; + } + + public async cleanUp() { + const globPattern = path.join(testWorkOnHomePath, `${this.prefix}*`); + await deleteFiles(globPattern); + } + + public resolve(name: string): string { + // Ensure env is random to avoid conflicts in tests (corrupting test data) + const now = new Date().getTime().toString().substr(-8); + return `${this.prefix}${name}${now}`; + } +} class WorkspaceVenvs { - constructor(private readonly root: string, private readonly prefix = '.v') { } + constructor(private readonly root: string, private readonly prefix = '.virtual') { } public async create(name: string): Promise { const envName = this.resolve(name); @@ -71,6 +100,27 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { const testWorkspaceFolder = path.join(TEST_LAYOUT_ROOT, 'workspace', 'folder1'); const workspaceVenvs = new WorkspaceVenvs(testWorkspaceFolder); let locator: IDisposableLocator; + const globalVenvs = new GlobalVenvs(); + + async function waitForEnvironmentToBeDetected(deferred: Deferred, envName: string) { + const timeout = setTimeout(() => { + clearTimeout(timeout); + deferred.reject(new Error('Environment not detected')); + }, TEST_TIMEOUT); + await deferred.promise; + const items = await getEnvs(locator.iterEnvs()); + const result = items.some((item) => item.executable.filename.includes(envName)); + assert.ok(result); + } + + setup(async () => { + process.env.WORKON_HOME = testWorkOnHomePath; + + locator = await createGlobalVirtualEnvironmentLocator(); + + // Wait for watchers to get ready + await sleep(1000); + }); async function waitForChangeToBeDetected(deferred: Deferred) { const timeout = setTimeout( @@ -90,6 +140,7 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { suiteSetup(async () => { await workspaceVenvs.cleanUp(); + await globalVenvs.cleanUp(); }); async function setupLocator(onChanged: (e: PythonEnvsChangedEvent) => Promise) { @@ -101,6 +152,7 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { teardown(async () => { await workspaceVenvs.cleanUp(); + await globalVenvs.cleanUp(); locator.dispose(); }); @@ -166,4 +218,18 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. assert.deepEqual(actualEvent!.type, FileChangeType.Changed, 'Wrong event emitted'); }); + + test('Detect a new Virtual Environment', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + locator.onChanged(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + const envName = await globalVenvs.create('one'); + await waitForEnvironmentToBeDetected(deferred, envName); + // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before + // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. + assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); + }); }); From 1d83fb0970cfaf0fea47f47bff9a0d7b412bcb12 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 22:42:07 -0800 Subject: [PATCH 5/9] Skip --- ...kspaceVirtualEnvLocator.testvirtualenvs.ts | 42 +++++++++++++++++++ ...rtualEnvironmentLocator.testvirtualenvs.ts | 6 ++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts index df4dac7f28eb..101c23a00bb9 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -156,6 +156,20 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { locator.dispose(); }); + test('Detect a new Virtual Environment', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + locator.onChanged(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + const envName = await globalVenvs.create('one'); + await waitForEnvironmentToBeDetected(deferred, envName); + // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before + // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. + assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); + }); + test('Detect a new environment', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); @@ -174,6 +188,20 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); }); + test('Detect a new Virtual Environment', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + locator.onChanged(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + const envName = await globalVenvs.create('one'); + await waitForEnvironmentToBeDetected(deferred, envName); + // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before + // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. + assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); + }); + test('Detect when an environment has been deleted', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); @@ -198,6 +226,20 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { assert.deepEqual(actualEvent!.type, FileChangeType.Deleted, 'Wrong event emitted'); }); + test('Detect a new Virtual Environment', async () => { + let actualEvent: PythonEnvsChangedEvent; + const deferred = createDeferred(); + locator.onChanged(async (e) => { + actualEvent = e; + deferred.resolve(); + }); + const envName = await globalVenvs.create('one'); + await waitForEnvironmentToBeDetected(deferred, envName); + // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before + // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. + assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); + }); + test('Detect when an environment has been updated', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); diff --git a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts index fba9667f0937..6c1b9459cc53 100644 --- a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts @@ -59,7 +59,11 @@ suite('GlobalVirtualEnvironment Locator', async () => { assert.ok(result); } - suiteSetup(() => globalVenvs.cleanUp()); + suiteSetup(async function () { + // tslint:disable-next-line:no-invalid-this + this.skip(); + await globalVenvs.cleanUp(); + }); setup(async () => { process.env.WORKON_HOME = testWorkOnHomePath; From 641c18667eef8f51f767974a4bf627a2b9197bfb Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 23:18:09 -0800 Subject: [PATCH 6/9] Try this out --- .../workspaceVirtualEnvLocator.testvirtualenvs.ts | 11 ++++++++++- ...globalVirtualEnvironmentLocator.testvirtualenvs.ts | 6 +----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts index 101c23a00bb9..1e11fb962109 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -13,7 +13,7 @@ import { createWorkspaceVirtualEnvLocator } from '../../../../../client/pythonEn import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; import { getInterpreterPathFromDir } from '../../../../../client/pythonEnvironments/common/commonUtils'; -import { arePathsSame } from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import { arePathsSame, readFile } from '../../../../../client/pythonEnvironments/common/externalDependencies'; import { createGlobalVirtualEnvironmentLocator } from '../../../../../client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator'; import { deleteFiles, PYTHON_PATH } from '../../../../common'; import { TEST_TIMEOUT } from '../../../../constants'; @@ -69,7 +69,13 @@ class WorkspaceVenvs { // eslint-disable-next-line class-methods-use-this public async update(filename: string): Promise { try { + // tslint:disable:no-console + console.log('File name is', filename); + const y = await fs.pathExists(filename); + console.log('Does file exist', y); await fs.writeFile(filename, 'Environment has been updated'); + const x = readFile(filename); + console.log('File content is', x); } catch (err) { throw new Error(`Failed to update Workspace virtualenv executable ${filename}, Error: ${err}`); } @@ -256,9 +262,12 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { const isFound = await isLocated(executable); assert.ok(isFound); + console.log(JSON.stringify(actualEvent!)); // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. assert.deepEqual(actualEvent!.type, FileChangeType.Changed, 'Wrong event emitted'); + locator.dispose(); + await sleep(5000); }); test('Detect a new Virtual Environment', async () => { diff --git a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts index 6c1b9459cc53..fba9667f0937 100644 --- a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts @@ -59,11 +59,7 @@ suite('GlobalVirtualEnvironment Locator', async () => { assert.ok(result); } - suiteSetup(async function () { - // tslint:disable-next-line:no-invalid-this - this.skip(); - await globalVenvs.cleanUp(); - }); + suiteSetup(() => globalVenvs.cleanUp()); setup(async () => { process.env.WORKON_HOME = testWorkOnHomePath; From 377963befb40305acea58ae7485bc73a960629f6 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Nov 2020 23:43:06 -0800 Subject: [PATCH 7/9] Let's try this fix --- .../workspaceVirtualEnvLocator.testvirtualenvs.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts index 1e11fb962109..a826ff03c28e 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import { traceWarning } from '../../../../../client/common/logger'; import { FileChangeType } from '../../../../../client/common/platform/fileSystemWatcher'; import { createDeferred, Deferred, sleep } from '../../../../../client/common/utils/async'; +import { getOSType, OSType } from '../../../../../client/common/utils/platform'; import { IDisposableLocator } from '../../../../../client/pythonEnvironments/base/locator'; import { createWorkspaceVirtualEnvLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator'; import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; @@ -66,6 +67,16 @@ class WorkspaceVenvs { return filename; } + public async createFile(filename: string): Promise { + const filepath = path.join(this.root, filename); + try { + await fs.createFile(filepath); + } catch (err) { + throw new Error(`Failed to create python executable ${filename}, Error: ${err}`); + } + return filepath; + } + // eslint-disable-next-line class-methods-use-this public async update(filename: string): Promise { try { @@ -249,7 +260,7 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { test('Detect when an environment has been updated', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); - const executable = await workspaceVenvs.create('one'); + const executable = await workspaceVenvs.createFile(getOSType() === OSType.Windows ? 'python.exe' : 'python'); // Wait before the change event has been sent. If both operations occur almost simultaneously no event is sent. await sleep(100); await setupLocator(async (e) => { From 0112d34c51ddbe1ae5ab304d4ac318c123a5dbd8 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Nov 2020 00:21:29 -0800 Subject: [PATCH 8/9] Clean up --- .../common/platform/fileSystemWatcher.ts | 10 +- ...kspaceVirtualEnvLocator.testvirtualenvs.ts | 138 ++---------------- ...rtualEnvironmentLocator.testvirtualenvs.ts | 2 +- 3 files changed, 20 insertions(+), 130 deletions(-) diff --git a/src/client/common/platform/fileSystemWatcher.ts b/src/client/common/platform/fileSystemWatcher.ts index 2486b0c0a619..c2cdc4b2a975 100644 --- a/src/client/common/platform/fileSystemWatcher.ts +++ b/src/client/common/platform/fileSystemWatcher.ts @@ -24,7 +24,7 @@ const POLLING_INTERVAL = 5000; export function watchLocationForPattern( baseDir: string, pattern: string, - callback: (type: FileChangeType, absPath: string) => void + callback: (type: FileChangeType, absPath: string) => void, ): IDisposable { // Use VSCode API iff base directory to exists within the current workspace folders const found = workspace.workspaceFolders?.find((e) => normCasePath(baseDir).startsWith(normCasePath(e.uri.fsPath))); @@ -38,7 +38,7 @@ export function watchLocationForPattern( function watchLocationUsingVSCodeAPI( baseDir: string, pattern: string, - callback: (type: FileChangeType, absPath: string) => void + callback: (type: FileChangeType, absPath: string) => void, ): IDisposable { const globPattern = new RelativePattern(baseDir, pattern); const disposables = new DisposableRegistry(); @@ -53,7 +53,7 @@ function watchLocationUsingVSCodeAPI( function watchLocationUsingChokidar( baseDir: string, pattern: string, - callback: (type: FileChangeType, absPath: string) => void + callback: (type: FileChangeType, absPath: string) => void, ): IDisposable { const watcherOpts: chokidar.WatchOptions = { cwd: baseDir, @@ -74,9 +74,9 @@ function watchLocationUsingChokidar( '/proc/**', '/sys/**', '**/lib/**', - '**/includes/**' + '**/includes/**', ], // https://github.com/microsoft/vscode/issues/23954 - followSymlinks: false + followSymlinks: true, }; traceVerbose(`Start watching: ${baseDir} with pattern ${pattern} using chokidar`); let watcher: chokidar.FSWatcher | null = chokidar.watch(pattern, watcherOpts); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts index a826ff03c28e..412fa9e88860 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.testvirtualenvs.ts @@ -14,40 +14,12 @@ import { createWorkspaceVirtualEnvLocator } from '../../../../../client/pythonEn import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; import { getInterpreterPathFromDir } from '../../../../../client/pythonEnvironments/common/commonUtils'; -import { arePathsSame, readFile } from '../../../../../client/pythonEnvironments/common/externalDependencies'; -import { createGlobalVirtualEnvironmentLocator } from '../../../../../client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator'; +import { arePathsSame } from '../../../../../client/pythonEnvironments/common/externalDependencies'; import { deleteFiles, PYTHON_PATH } from '../../../../common'; import { TEST_TIMEOUT } from '../../../../constants'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { run } from '../../../discovery/locators/envTestUtils'; -const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); -const testWorkOnHomePath = path.join(testVirtualHomeDir, 'workonhome'); -class GlobalVenvs { - constructor(private readonly prefix = '.v') { } - - public async create(name: string): Promise { - const envName = this.resolve(name); - const argv = [PYTHON_PATH.fileToCommandArgument(), '-m', 'virtualenv', envName]; - try { - await run(argv, { cwd: testWorkOnHomePath }); - } catch (err) { - throw new Error(`Failed to create Env ${path.basename(envName)} Error: ${err}`); - } - return envName; - } - - public async cleanUp() { - const globPattern = path.join(testWorkOnHomePath, `${this.prefix}*`); - await deleteFiles(globPattern); - } - - public resolve(name: string): string { - // Ensure env is random to avoid conflicts in tests (corrupting test data) - const now = new Date().getTime().toString().substr(-8); - return `${this.prefix}${name}${now}`; - } -} class WorkspaceVenvs { constructor(private readonly root: string, private readonly prefix = '.virtual') { } @@ -67,12 +39,17 @@ class WorkspaceVenvs { return filename; } - public async createFile(filename: string): Promise { - const filepath = path.join(this.root, filename); + /** + * Creates a dummy environment by creating a fake executable. + * @param name environment suffix name to create + */ + public async createDummyEnv(name: string): Promise { + const envName = this.resolve(name); + const filepath = path.join(this.root, envName, getOSType() === OSType.Windows ? 'python.exe' : 'python'); try { await fs.createFile(filepath); } catch (err) { - throw new Error(`Failed to create python executable ${filename}, Error: ${err}`); + throw new Error(`Failed to create python executable ${filepath}, Error: ${err}`); } return filepath; } @@ -80,13 +57,7 @@ class WorkspaceVenvs { // eslint-disable-next-line class-methods-use-this public async update(filename: string): Promise { try { - // tslint:disable:no-console - console.log('File name is', filename); - const y = await fs.pathExists(filename); - console.log('Does file exist', y); await fs.writeFile(filename, 'Environment has been updated'); - const x = readFile(filename); - console.log('File content is', x); } catch (err) { throw new Error(`Failed to update Workspace virtualenv executable ${filename}, Error: ${err}`); } @@ -117,27 +88,6 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { const testWorkspaceFolder = path.join(TEST_LAYOUT_ROOT, 'workspace', 'folder1'); const workspaceVenvs = new WorkspaceVenvs(testWorkspaceFolder); let locator: IDisposableLocator; - const globalVenvs = new GlobalVenvs(); - - async function waitForEnvironmentToBeDetected(deferred: Deferred, envName: string) { - const timeout = setTimeout(() => { - clearTimeout(timeout); - deferred.reject(new Error('Environment not detected')); - }, TEST_TIMEOUT); - await deferred.promise; - const items = await getEnvs(locator.iterEnvs()); - const result = items.some((item) => item.executable.filename.includes(envName)); - assert.ok(result); - } - - setup(async () => { - process.env.WORKON_HOME = testWorkOnHomePath; - - locator = await createGlobalVirtualEnvironmentLocator(); - - // Wait for watchers to get ready - await sleep(1000); - }); async function waitForChangeToBeDetected(deferred: Deferred) { const timeout = setTimeout( @@ -155,10 +105,7 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { return items.some((item) => arePathsSame(item.executable.filename, executable)); } - suiteSetup(async () => { - await workspaceVenvs.cleanUp(); - await globalVenvs.cleanUp(); - }); + suiteSetup(async () => workspaceVenvs.cleanUp()); async function setupLocator(onChanged: (e: PythonEnvsChangedEvent) => Promise) { locator = await createWorkspaceVirtualEnvLocator(testWorkspaceFolder); @@ -169,24 +116,9 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { teardown(async () => { await workspaceVenvs.cleanUp(); - await globalVenvs.cleanUp(); locator.dispose(); }); - test('Detect a new Virtual Environment', async () => { - let actualEvent: PythonEnvsChangedEvent; - const deferred = createDeferred(); - locator.onChanged(async (e) => { - actualEvent = e; - deferred.resolve(); - }); - const envName = await globalVenvs.create('one'); - await waitForEnvironmentToBeDetected(deferred, envName); - // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before - // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. - assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); - }); - test('Detect a new environment', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); @@ -205,20 +137,6 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); }); - test('Detect a new Virtual Environment', async () => { - let actualEvent: PythonEnvsChangedEvent; - const deferred = createDeferred(); - locator.onChanged(async (e) => { - actualEvent = e; - deferred.resolve(); - }); - const envName = await globalVenvs.create('one'); - await waitForEnvironmentToBeDetected(deferred, envName); - // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before - // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. - assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); - }); - test('Detect when an environment has been deleted', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); @@ -243,24 +161,13 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { assert.deepEqual(actualEvent!.type, FileChangeType.Deleted, 'Wrong event emitted'); }); - test('Detect a new Virtual Environment', async () => { - let actualEvent: PythonEnvsChangedEvent; - const deferred = createDeferred(); - locator.onChanged(async (e) => { - actualEvent = e; - deferred.resolve(); - }); - const envName = await globalVenvs.create('one'); - await waitForEnvironmentToBeDetected(deferred, envName); - // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before - // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. - assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); - }); - test('Detect when an environment has been updated', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); - const executable = await workspaceVenvs.createFile(getOSType() === OSType.Windows ? 'python.exe' : 'python'); + // Create a dummy environment so we can update its executable later. We can't choose a real environment here. + // Executables inside real environments can be symlinks, so writing on them can result in the real executable + // being updated instead of the symlink. + const executable = await workspaceVenvs.createDummyEnv('one'); // Wait before the change event has been sent. If both operations occur almost simultaneously no event is sent. await sleep(100); await setupLocator(async (e) => { @@ -273,25 +180,8 @@ suite('WorkspaceVirtualEnvironment Locator', async () => { const isFound = await isLocated(executable); assert.ok(isFound); - console.log(JSON.stringify(actualEvent!)); // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. assert.deepEqual(actualEvent!.type, FileChangeType.Changed, 'Wrong event emitted'); - locator.dispose(); - await sleep(5000); - }); - - test('Detect a new Virtual Environment', async () => { - let actualEvent: PythonEnvsChangedEvent; - const deferred = createDeferred(); - locator.onChanged(async (e) => { - actualEvent = e; - deferred.resolve(); - }); - const envName = await globalVenvs.create('one'); - await waitForEnvironmentToBeDetected(deferred, envName); - // Detecting kind of virtual env depends on the file structure around the executable, so we need to wait before - // attempting to verify it. Omitting that check as we can never deterministically say when it's ready to check. - assert.deepEqual(actualEvent!.type, FileChangeType.Created, 'Wrong event emitted'); }); }); diff --git a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts index fba9667f0937..db1a30cccc0a 100644 --- a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.testvirtualenvs.ts @@ -19,7 +19,7 @@ const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); const testWorkOnHomePath = path.join(testVirtualHomeDir, 'workonhome'); class GlobalVenvs { - constructor(private readonly prefix = '.v') { } + constructor(private readonly prefix = '.virtualenv-') { } public async create(name: string): Promise { const envName = this.resolve(name); From ea2bfe85e1fc09f6566c8ec66e50718ee9fac042 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Nov 2020 00:28:59 -0800 Subject: [PATCH 9/9] Fix lint errors --- src/client/common/platform/fileSystemWatcher.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/common/platform/fileSystemWatcher.ts b/src/client/common/platform/fileSystemWatcher.ts index c2cdc4b2a975..a7120d4e9178 100644 --- a/src/client/common/platform/fileSystemWatcher.ts +++ b/src/client/common/platform/fileSystemWatcher.ts @@ -24,7 +24,7 @@ const POLLING_INTERVAL = 5000; export function watchLocationForPattern( baseDir: string, pattern: string, - callback: (type: FileChangeType, absPath: string) => void, + callback: (type: FileChangeType, absPath: string) => void ): IDisposable { // Use VSCode API iff base directory to exists within the current workspace folders const found = workspace.workspaceFolders?.find((e) => normCasePath(baseDir).startsWith(normCasePath(e.uri.fsPath))); @@ -38,7 +38,7 @@ export function watchLocationForPattern( function watchLocationUsingVSCodeAPI( baseDir: string, pattern: string, - callback: (type: FileChangeType, absPath: string) => void, + callback: (type: FileChangeType, absPath: string) => void ): IDisposable { const globPattern = new RelativePattern(baseDir, pattern); const disposables = new DisposableRegistry(); @@ -53,7 +53,7 @@ function watchLocationUsingVSCodeAPI( function watchLocationUsingChokidar( baseDir: string, pattern: string, - callback: (type: FileChangeType, absPath: string) => void, + callback: (type: FileChangeType, absPath: string) => void ): IDisposable { const watcherOpts: chokidar.WatchOptions = { cwd: baseDir, @@ -74,9 +74,9 @@ function watchLocationUsingChokidar( '/proc/**', '/sys/**', '**/lib/**', - '**/includes/**', + '**/includes/**' ], // https://github.com/microsoft/vscode/issues/23954 - followSymlinks: true, + followSymlinks: true }; traceVerbose(`Start watching: ${baseDir} with pattern ${pattern} using chokidar`); let watcher: chokidar.FSWatcher | null = chokidar.watch(pattern, watcherOpts);