Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build/.mocha.functional.perf.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
./out/test/**/*.functional.test.js
--require=out/test/unittests.js
--exclude=out/**/*.jsx
--ui=tdd
--recursive
--colors
--exit
--timeout=180000
--reporter spec
137 changes: 137 additions & 0 deletions build/ci/vscode-python-nightly-flake-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Nightly build

name: '$(Year:yyyy).$(Month).0.$(BuildID)-nightly-flake'

# Not the CI build, see `vscode-python-nightly-flake-ci.yaml`.
trigger: none

# Not the PR build for merges to master and release.
pr: none

schedules:
- cron: "0 8 * * 1-5"
# Daily midnight PST build, runs Monday - Friday always
displayName: Nightly Flake build
branches:
include:
- master
- release*
always: true

# Variables that are available for the entire pipeline.
variables:
- template: templates/globals.yml

stages:
- stage: Build
jobs:
- template: templates/jobs/build_compile.yml

# Each item in each matrix has a number of possible values it may
# define. They are detailed in templates/test_phases.yml. The only
# required value is "TestsToRun".

- stage: Linux
dependsOn:
- Build
jobs:
- job: 'Py3x'
dependsOn: []
timeoutInMinutes: 120
strategy:
matrix:
'Functional':
TestsToRun: 'testfunctional'
NeedsPythonTestReqs: true
NeedsPythonFunctionalReqs: true
VSCODE_PYTHON_ROLLING: true
pool:
vmImage: 'ubuntu-16.04'
steps:
- template: templates/test_phases.yml

- job: 'Py36'
dependsOn: []
timeoutInMinutes: 120
strategy:
matrix:
'Functional':
PythonVersion: '3.6'
TestsToRun: 'testfunctional'
NeedsPythonTestReqs: true
NeedsPythonFunctionalReqs: true
VSCODE_PYTHON_ROLLING: true
pool:
vmImage: 'ubuntu-16.04'
steps:
- template: templates/test_phases.yml

- stage: Mac
dependsOn:
- Build
jobs:
- job: 'Py3x'
dependsOn: []
timeoutInMinutes: 120
strategy:
matrix:
'Functional':
TestsToRun: 'testfunctional'
NeedsPythonTestReqs: true
NeedsPythonFunctionalReqs: true
VSCODE_PYTHON_ROLLING: true
pool:
vmImage: 'macos-10.13'
steps:
- template: templates/test_phases.yml

- job: 'Py36'
dependsOn: []
timeoutInMinutes: 120
strategy:
matrix:
'Functional':
PythonVersion: '3.6'
TestsToRun: 'testfunctional'
NeedsPythonTestReqs: true
NeedsPythonFunctionalReqs: true
VSCODE_PYTHON_ROLLING: true
pool:
vmImage: 'macos-10.13'
steps:
- template: templates/test_phases.yml

- stage: Windows
dependsOn:
- Build
jobs:
- job: 'Py3x'
dependsOn: []
timeoutInMinutes: 120
strategy:
matrix:
'Functional':
TestsToRun: 'testfunctional'
NeedsPythonTestReqs: true
NeedsPythonFunctionalReqs: true
VSCODE_PYTHON_ROLLING: true
pool:
vmImage: 'vs2017-win2016'
steps:
- template: templates/test_phases.yml

- job: 'Py36'
dependsOn: []
timeoutInMinutes: 120
strategy:
matrix:
'Functional':
PythonVersion: '3.6'
TestsToRun: 'testfunctional'
NeedsPythonTestReqs: true
NeedsPythonFunctionalReqs: true
VSCODE_PYTHON_ROLLING: true
pool:
vmImage: 'vs2017-win2016'
steps:
- template: templates/test_phases.yml
1 change: 1 addition & 0 deletions news/3 Code Health/7997.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Functional tests using real jupyter can take 30-90 seconds each. Most of this time is searching for interpreters. Cache the interpreter search.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2844,6 +2844,7 @@
"test:unittests": "mocha --opts ./build/.mocha.unittests.js.opts",
"test:unittests:cover": "nyc --no-clean --nycrc-path build/.nycrc mocha --opts ./build/.mocha.unittests.ts.opts",
"test:functional": "mocha --require source-map-support/register --opts ./build/.mocha.functional.opts",
"test:functional:perf": "node --inspect-brk ./node_modules/mocha/bin/_mocha --require source-map-support/register --opts ./build/.mocha.functional.perf.opts",
"test:functional:cover": "npm run test:functional",
"test:cover:report": "nyc --nycrc-path build/.nycrc report --reporter=text --reporter=html --reporter=text-summary --reporter=cobertura",
"testDebugger": "node ./out/test/testBootstrap.js ./out/test/debuggerTest.js",
Expand Down
59 changes: 32 additions & 27 deletions src/client/interpreter/locators/services/cacheableLocatorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,44 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
@traceDecorators.verbose('Get Interpreters in CacheableLocatorService')
public async getInterpreters(resource?: Uri, ignoreCache?: boolean): Promise<PythonInterpreter[]> {
const cacheKey = this.getCacheKey(resource);
let cachedInterpreters = ignoreCache ? undefined : this.getCachedInterpreters(resource);
let deferred = this.promisesPerResource.get(cacheKey);

if (!deferred || ignoreCache) {
deferred = createDeferred<PythonInterpreter[]>();
this.promisesPerResource.set(cacheKey, deferred);
if (Array.isArray(cachedInterpreters)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are these checking for Array here specifically? If there was a refactor where .getCachedInterpreters returned a class wrapping the array list of Interpreters we would want to return that.

deferred.resolve(cachedInterpreters);
} else {
this.addHandlersForInterpreterWatchers(cacheKey, resource).ignoreErrors();

this.addHandlersForInterpreterWatchers(cacheKey, resource).ignoreErrors();

const stopWatch = new StopWatch();
this.getInterpretersImplementation(resource)
.then(async items => {
await this.cacheInterpreters(items, resource);
traceVerbose(
`Interpreters returned by ${this.name} are of count ${Array.isArray(items) ? items.length : 0}`
);
traceVerbose(`Interpreters returned by ${this.name} are ${JSON.stringify(items)}`);
sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, {
locator: this.name,
interpreters: Array.isArray(items) ? items.length : 0
const stopWatch = new StopWatch();
this.getInterpretersImplementation(resource)
.then(async items => {
await this.cacheInterpreters(items, resource);
traceVerbose(
`Interpreters returned by ${this.name} are of count ${
Array.isArray(items) ? items.length : 0
}`
);
traceVerbose(`Interpreters returned by ${this.name} are ${JSON.stringify(items)}`);
sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, {
locator: this.name,
interpreters: Array.isArray(items) ? items.length : 0
});
deferred!.resolve(items);
})
.catch(ex => {
sendTelemetryEvent(
EventName.PYTHON_INTERPRETER_DISCOVERY,
stopWatch.elapsedTime,
{ locator: this.name },
ex
);
deferred!.reject(ex);
});
deferred!.resolve(items);
})
.catch(ex => {
sendTelemetryEvent(
EventName.PYTHON_INTERPRETER_DISCOVERY,
stopWatch.elapsedTime,
{ locator: this.name },
ex
);
deferred!.reject(ex);
});

this.locating.fire(deferred.promise);
this.locating.fire(deferred.promise);
}
}
deferred.promise
.then(items => this._hasInterpreters.resolve(items.length > 0))
Expand All @@ -84,7 +89,7 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
return deferred.promise;
}

const cachedInterpreters = ignoreCache ? undefined : this.getCachedInterpreters(resource);
cachedInterpreters = ignoreCache ? undefined : this.getCachedInterpreters(resource);
return Array.isArray(cachedInterpreters) ? cachedInterpreters : deferred.promise;
}
protected async addHandlersForInterpreterWatchers(cacheKey: string, resource: Uri | undefined): Promise<void> {
Expand Down
18 changes: 15 additions & 3 deletions src/test/datascience/dataScienceIocContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Event,
EventEmitter,
FileSystemWatcher,
Memento,
Uri,
WorkspaceConfiguration,
WorkspaceFolder,
Expand Down Expand Up @@ -99,7 +100,6 @@ import {
} from '../../client/common/installer/productPath';
import { ProductService } from '../../client/common/installer/productService';
import { IInstallationChannelManager, IProductPathService, IProductService } from '../../client/common/installer/types';
import { PersistentStateFactory } from '../../client/common/persistentState';
import { IS_WINDOWS } from '../../client/common/platform/constants';
import { PathUtils } from '../../client/common/platform/pathUtils';
import { RegistryImplementation } from '../../client/common/platform/registry';
Expand Down Expand Up @@ -130,6 +130,7 @@ import {
} from '../../client/common/terminal/types';
import {
BANNER_NAME_LS_SURVEY,
GLOBAL_MEMENTO,
IAsyncDisposableRegistry,
IConfigurationService,
ICryptoUtils,
Expand All @@ -139,12 +140,14 @@ import {
IExtensionContext,
IExtensions,
IInstaller,
IMemento,
IOutputChannel,
IPathUtils,
IPersistentStateFactory,
IPythonExtensionBanner,
IsWindows,
ProductType
ProductType,
WORKSPACE_MEMENTO
} from '../../client/common/types';
import { Deferred, sleep } from '../../client/common/utils/async';
import { noop } from '../../client/common/utils/misc';
Expand Down Expand Up @@ -333,6 +336,7 @@ import { MockWorkspaceConfiguration } from './mockWorkspaceConfig';
import { blurWindow, createMessageEvent } from './reactHelpers';
import { TestInteractiveWindowProvider } from './testInteractiveWindowProvider';
import { TestNativeEditorProvider } from './testNativeEditorProvider';
import { TestPersistentStateFactory } from './testPersistentStateFactory';

export class DataScienceIocContainer extends UnitTestIocContainer {
public webPanelListener: IWebPanelMessageListener | undefined;
Expand Down Expand Up @@ -914,7 +918,15 @@ export class DataScienceIocContainer extends UnitTestIocContainer {
IInterpreterVersionService,
InterpreterVersionService
);
this.serviceManager.addSingleton<IPersistentStateFactory>(IPersistentStateFactory, PersistentStateFactory);

const globalStorage = this.serviceManager.get<Memento>(IMemento, GLOBAL_MEMENTO);
const localStorage = this.serviceManager.get<Memento>(IMemento, WORKSPACE_MEMENTO);

// Create a custom persistent state factory that remembers specific things between tests

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems kinda risky, hope this doesn't break anything. Guess we'll find out in our tests.
E.g. setting something into persistent state and checking that flag to change behaviour of code.

this.serviceManager.addSingletonInstance<IPersistentStateFactory>(
IPersistentStateFactory,
new TestPersistentStateFactory(globalStorage, localStorage)
);
this.serviceManager.addSingletonInstance<IInterpreterDisplay>(IInterpreterDisplay, interpreterDisplay.object);

this.serviceManager.addSingleton<IPythonPathUpdaterServiceFactory>(
Expand Down
53 changes: 53 additions & 0 deletions src/test/datascience/testPersistentStateFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Memento } from 'vscode';
import { PersistentStateFactory } from '../../client/common/persistentState';
import { IPersistentState, IPersistentStateFactory } from '../../client/common/types';

const PrefixesToStore = ['INTERPRETERS_CACHE'];

// tslint:disable-next-line: no-any
const persistedState = new Map<string, any>();

class TestPersistentState<T> implements IPersistentState<T> {
constructor(private key: string, defaultValue?: T | undefined) {
if (defaultValue) {
persistedState.set(key, defaultValue);
}
}
public get value(): T {
return persistedState.get(this.key);
}
public async updateValue(value: T): Promise<void> {
persistedState.set(this.key, value);
}
}

// This class is used to make certain values persist across tests.
export class TestPersistentStateFactory implements IPersistentStateFactory {
private realStateFactory: PersistentStateFactory;
constructor(globalState: Memento, localState: Memento) {
this.realStateFactory = new PersistentStateFactory(globalState, localState);
}

public createGlobalPersistentState<T>(
key: string,
defaultValue?: T | undefined,
expiryDurationMs?: number | undefined
): IPersistentState<T> {
if (PrefixesToStore.find(p => key.startsWith(p))) {
return new TestPersistentState(key, defaultValue);
}

return this.realStateFactory.createGlobalPersistentState(key, defaultValue, expiryDurationMs);
}
public createWorkspacePersistentState<T>(
key: string,
defaultValue?: T | undefined,
expiryDurationMs?: number | undefined
): IPersistentState<T> {
if (PrefixesToStore.find(p => key.startsWith(p))) {
return new TestPersistentState(key, defaultValue);
}

return this.realStateFactory.createWorkspacePersistentState(key, defaultValue, expiryDurationMs);
}
}