diff --git a/.github/workflows/pr_datascience.yml b/.github/workflows/pr_datascience.yml index e33c72c7932a..481340e18add 100644 --- a/.github/workflows/pr_datascience.yml +++ b/.github/workflows/pr_datascience.yml @@ -1,9 +1,6 @@ name: Pull Request DataScience on: - push: - branches: - - main pull_request: branches: - main diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 98c64072ca51..87d22406897b 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -20,6 +20,7 @@ Microsoft Python extension for Visual Studio Code incorporates third party mater 16. ipywidgets (https://github.com/jupyter-widgets) 17. vscode-cpptools (https://github.com/microsoft/vscode-cpptools) 18. font-awesome (https://github.com/FortAwesome/Font-Awesome) +19. mocha (https://github.com/mochajs/mocha) %% Go for Visual Studio Code NOTICES, INFORMATION, AND LICENSE BEGIN HERE @@ -1195,4 +1196,33 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF jedi-language-server NOTICES, INFORMATION, AND LICENSE \ No newline at end of file +END OF jedi-language-server NOTICES, INFORMATION, AND LICENSE + +%% mocha NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= + +(The MIT License) + +Copyright (c) 2011-2020 OpenJS Foundation and contributors, https://openjsf.org + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +========================================= +END OF mocha NOTICES, INFORMATION, AND LICENSE diff --git a/build/.mocha-multi-reporters.config b/build/.mocha-multi-reporters.config index 539aa1c15b60..abe46f117f5b 100644 --- a/build/.mocha-multi-reporters.config +++ b/build/.mocha-multi-reporters.config @@ -1,3 +1,3 @@ { - "reporterEnabled": "spec,mocha-junit-reporter" + "reporterEnabled": "./build/ci/scripts/spec_with_pid,mocha-junit-reporter" } diff --git a/build/ci/scripts/spec_with_pid.js b/build/ci/scripts/spec_with_pid.js new file mode 100644 index 000000000000..52cb064446cc --- /dev/null +++ b/build/ci/scripts/spec_with_pid.js @@ -0,0 +1,96 @@ +'use strict'; +/** + * @module Spec + */ +/** + * Module dependencies. + */ + +var Base = require('mocha/lib/reporters/base'); +var constants = require('mocha/lib/runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var inherits = require('mocha/lib/utils').inherits; +var color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Constructs a new `Spec` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Spec(runner, options) { + Base.call(this, runner, options); + + var self = this; + var indents = 0; + var n = 0; + + function indent() { + return Array(indents).join(' '); + } + + runner.on(EVENT_RUN_BEGIN, function () { + Base.consoleLog(); + }); + + runner.on(EVENT_SUITE_BEGIN, function (suite) { + ++indents; + Base.consoleLog(color('suite', `${process.pid} %s%s`), indent(), suite.title); + }); + + runner.on(EVENT_SUITE_END, function () { + --indents; + if (indents === 1) { + Base.consoleLog(); + } + }); + + runner.on(EVENT_TEST_PENDING, function (test) { + var fmt = indent() + color('pending', `${process.pid} - %s`); + Base.consoleLog(fmt, test.title); + }); + + runner.on(EVENT_TEST_PASS, function (test) { + var fmt; + if (test.speed === 'fast') { + fmt = indent() + color('checkmark', `${process.pid} ` + Base.symbols.ok) + color('pass', ' %s'); + Base.consoleLog(fmt, test.title); + } else { + fmt = + indent() + + color('checkmark', `${process.pid} ` + Base.symbols.ok) + + color('pass', ' %s') + + color(test.speed, ' (%dms)'); + Base.consoleLog(fmt, test.title, test.duration); + } + }); + + runner.on(EVENT_TEST_FAIL, function (test) { + Base.consoleLog(indent() + color('fail', `${process.pid} %d) %s`), ++n, test.title); + }); + + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Spec, Base); + +Spec.description = 'hierarchical & verbose [default]'; diff --git a/news/3 Code Health/14290.md b/news/3 Code Health/14290.md new file mode 100644 index 000000000000..8e7fac32f023 --- /dev/null +++ b/news/3 Code Health/14290.md @@ -0,0 +1 @@ +Functional test failures related to kernel ports overlapping. \ No newline at end of file diff --git a/pythonFiles/vscode_datascience_helpers/tests/logParser.py b/pythonFiles/vscode_datascience_helpers/tests/logParser.py new file mode 100644 index 000000000000..1192ef97b235 --- /dev/null +++ b/pythonFiles/vscode_datascience_helpers/tests/logParser.py @@ -0,0 +1,84 @@ +import sys +import argparse +import os + +os.system("color") +from pathlib import Path +import re + +parser = argparse.ArgumentParser(description="Parse a test log into its parts") +parser.add_argument("testlog", type=str, nargs=1, help="Log to parse") +parser.add_argument( + "--testoutput", action="store_true", help="Show all failures and passes" +) +parser.add_argument( + "--split", + action="store_true", + help="Split into per process files. Each file will have the pid appended", +) +ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") +pid_regex = re.compile(r"(\d+).*") + + +def printTestOutput(testlog): + # Find all the lines that don't have a PID in them. These are the test output + p = Path(testlog[0]) + with p.open() as f: + for line in f.readlines(): + stripped = line.strip() + if len(stripped) > 2 and stripped[0] == "\x1B" and stripped[1] == "[": + print(line.rstrip()) # Should be a test line as it has color encoding + + +def splitByPid(testlog): + # Split testlog into prefixed logs based on pid + baseFile = os.path.splitext(testlog[0])[0] + p = Path(testlog[0]) + pids = set() + logs = {} + pid = None + with p.open() as f: + for line in f.readlines(): + stripped = ansi_escape.sub("", line.strip()) + # See if starts with a pid + if len(stripped) > 0 and stripped[0] <= "9" and stripped[0] >= "0": + # Pull out the pid + match = pid_regex.match(stripped) + + # Pids are at least two digits + if match != None and len(match.group(1)) > 2: + # Pid is found + pid = int(match.group(1)) + + # See if we've created a log for this pid or not + if not pid in pids: + pids.add(pid) + logFile = "{}_{}.log".format(baseFile, pid) + print("Writing to new log: " + logFile) + logs[pid] = Path(logFile).open(mode="w") + + # Add this line to the log + if pid != None: + logs[pid].write(line) + # Close all of the open logs + for key in logs: + logs[key].close() + + +def doWork(args): + if not args.testlog: + print("Test log should be passed") + elif args.testoutput: + printTestOutput(args.testlog) + elif args.split: + splitByPid(args.testlog) + else: + parser.print_usage() + + +def main(): + doWork(parser.parse_args()) + + +if __name__ == "__main__": + main() diff --git a/src/client/common/process/baseDaemon.ts b/src/client/common/process/baseDaemon.ts index abd93668115d..b95991952fe4 100644 --- a/src/client/common/process/baseDaemon.ts +++ b/src/client/common/process/baseDaemon.ts @@ -177,14 +177,20 @@ export abstract class BasePythonDaemon { return Object.keys(options).every((item) => daemonSupportedSpawnOptions.indexOf(item as any) >= 0); } protected sendRequestWithoutArgs(type: RequestType0): Thenable { - return Promise.race([this.connection.sendRequest(type), this.connectionClosedDeferred.promise]); + if (this.proc && typeof this.proc.exitCode !== 'number') { + return Promise.race([this.connection.sendRequest(type), this.connectionClosedDeferred.promise]); + } + return this.connectionClosedDeferred.promise; } protected sendRequest(type: RequestType, params?: P): Thenable { - if (!this.isAlive) { + if (!this.isAlive || typeof this.proc.exitCode === 'number') { traceError('Daemon is handling a request after death.'); } - // Throw an error if the connection has been closed. - return Promise.race([this.connection.sendRequest(type, params), this.connectionClosedDeferred.promise]); + if (this.proc && typeof this.proc.exitCode !== 'number') { + // Throw an error if the connection has been closed. + return Promise.race([this.connection.sendRequest(type, params), this.connectionClosedDeferred.promise]); + } + return this.connectionClosedDeferred.promise; } protected throwIfRPCConnectionIsDead() { if (!this.isAlive) { diff --git a/src/client/datascience/kernel-launcher/kernelLauncher.ts b/src/client/datascience/kernel-launcher/kernelLauncher.ts index 50894ab93a10..34db9842f653 100644 --- a/src/client/datascience/kernel-launcher/kernelLauncher.ts +++ b/src/client/datascience/kernel-launcher/kernelLauncher.ts @@ -2,10 +2,15 @@ // Licensed under the MIT License. 'use strict'; +import * as fsextra from 'fs-extra'; import { inject, injectable } from 'inversify'; +import * as os from 'os'; +import * as path from 'path'; import * as portfinder from 'portfinder'; import { promisify } from 'util'; import * as uuid from 'uuid/v4'; +import { isTestExecution } from '../../common/constants'; +import { traceInfo } from '../../common/logger'; import { IProcessServiceFactory } from '../../common/process/types'; import { Resource } from '../../common/types'; import { captureTelemetry } from '../../telemetry'; @@ -16,20 +21,62 @@ import { KernelDaemonPool } from './kernelDaemonPool'; import { KernelProcess } from './kernelProcess'; import { IKernelConnection, IKernelLauncher, IKernelProcess } from './types'; -const PortToStartFrom = 9_000; +const PortFormatString = `kernelLauncherPortStart_{0}.tmp`; // Launches and returns a kernel process given a resource or python interpreter. // If the given interpreter is undefined, it will try to use the selected interpreter. // If the selected interpreter doesn't have a kernel, it will find a kernel on disk and use that. @injectable() export class KernelLauncher implements IKernelLauncher { - private static nextFreePortToTryAndUse = PortToStartFrom; + private static startPortPromise = KernelLauncher.computeStartPort(); + private static nextFreePortToTryAndUsePromise = KernelLauncher.startPortPromise; constructor( @inject(IProcessServiceFactory) private processExecutionFactory: IProcessServiceFactory, @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, @inject(KernelDaemonPool) private readonly daemonPool: KernelDaemonPool ) {} + // This function is public so it can be called when a test shuts down + public static async cleanupStartPort() { + try { + // Destroy the file + const port = await KernelLauncher.startPortPromise; + traceInfo(`Cleaning up port start file : ${port}`); + + const filePath = path.join(os.tmpdir(), PortFormatString.format(port.toString())); + await fsextra.remove(filePath); + } catch (exc) { + // If it fails it doesn't really matter. Just a temp file + traceInfo(`Kernel port mutex failed to cleanup: `, exc); + } + } + + private static async computeStartPort(): Promise { + if (isTestExecution()) { + // Since multiple instances of a test may be running, write our best guess to a shared file + let portStart = 9_000; + let result = 0; + while (result === 0 && portStart < 65_000) { + try { + // Try creating a file with the port in the name + const filePath = path.join(os.tmpdir(), PortFormatString.format(portStart.toString())); + await fsextra.open(filePath, 'wx'); + + // If that works, we have our port + result = portStart; + } catch { + // If that fails, it should mean the file already exists + portStart += 1_000; + } + } + traceInfo(`Computed port start for KernelLauncher is : ${result}`); + + return result; + } else { + return 9_000; + } + } + @captureTelemetry(Telemetry.KernelLauncherPerf) public async launch( kernelConnectionMetadata: KernelSpecConnectionMetadata | PythonKernelConnectionMetadata, @@ -49,18 +96,28 @@ export class KernelLauncher implements IKernelLauncher { return kernelProcess; } - private async getKernelConnection(): Promise { + private async getConnectionPorts(): Promise { const getPorts = promisify(portfinder.getPorts); + + // Have to wait for static port lookup (it handles case where two VS code instances are running) + const nextFreePort = await KernelLauncher.nextFreePortToTryAndUsePromise; + const startPort = await KernelLauncher.startPortPromise; + // Ports may have been freed, hence start from begining. - const port = - KernelLauncher.nextFreePortToTryAndUse > PortToStartFrom + 1_000 - ? PortToStartFrom - : KernelLauncher.nextFreePortToTryAndUse; + const port = nextFreePort > startPort + 1_000 ? startPort : nextFreePort; + + // Then get the next set starting at that point const ports = await getPorts(5, { host: '127.0.0.1', port }); + // We launch restart kernels in the background, its possible other session hasn't started. // Ensure we do not use same ports. - KernelLauncher.nextFreePortToTryAndUse = Math.max(...ports) + 1; + KernelLauncher.nextFreePortToTryAndUsePromise = Promise.resolve(Math.max(...ports) + 1); + return ports; + } + + private async getKernelConnection(): Promise { + const ports = await this.getConnectionPorts(); return { version: 1, key: uuid(), diff --git a/src/client/logging/formatters.ts b/src/client/logging/formatters.ts index b3dd4e52761f..488c4c91ccaf 100644 --- a/src/client/logging/formatters.ts +++ b/src/client/logging/formatters.ts @@ -3,6 +3,7 @@ 'use strict'; import { format } from 'winston'; +import { isTestExecution } from '../common/constants'; import { getLevel, LogLevel, LogLevelName } from './levels'; const TIMESTAMP = 'YYYY-MM-DD HH:mm:ss'; @@ -37,13 +38,17 @@ function normalizeLevel(name: LogLevelName): string { // Return a log entry that can be emitted as-is. function formatMessage(level: LogLevelName, timestamp: string, message: string): string { const levelFormatted = normalizeLevel(level); - return `${levelFormatted} ${timestamp}: ${message}`; + return isTestExecution() + ? `${process.pid} ${levelFormatted} ${timestamp}: ${message}` + : `${levelFormatted} ${timestamp}: ${message}`; } // Return a log entry that can be emitted as-is. function formatLabeledMessage(level: LogLevelName, timestamp: string, label: string, message: string): string { const levelFormatted = normalizeLevel(level); - return `${levelFormatted} ${label} ${timestamp}: ${message}`; + return isTestExecution() + ? `${process.pid} ${levelFormatted} ${label} ${timestamp}: ${message}` + : `${levelFormatted} ${label} ${timestamp}: ${message}`; } // Return a minimal format object that can be used with a "winston" diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index d10c18e81f6c..5bacad6dff5f 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -95,10 +95,6 @@ suite('DataScience DataViewer tests', () => { delete (global as any).ascquireVsCodeApi; }); - suiteTeardown(() => { - // asyncDump(); - }); - function createJupyterVariable(variable: string, type: string): IJupyterVariable { return { name: variable, diff --git a/src/test/datascience/debugger.functional.test.tsx b/src/test/datascience/debugger.functional.test.tsx index 435fa3e01701..f9655dcf36b1 100644 --- a/src/test/datascience/debugger.functional.test.tsx +++ b/src/test/datascience/debugger.functional.test.tsx @@ -129,10 +129,6 @@ suite('DataScience Debugger tests', () => { } }); - suiteTeardown(() => { - // asyncDump(); - }); - async function debugCell( type: 'notebook' | 'interactive', code: string, diff --git a/src/test/datascience/intellisense.functional.test.tsx b/src/test/datascience/intellisense.functional.test.tsx index daa822cb5adf..1ded9989b75e 100644 --- a/src/test/datascience/intellisense.functional.test.tsx +++ b/src/test/datascience/intellisense.functional.test.tsx @@ -31,14 +31,13 @@ import { ITestNativeEditorProvider } from './testNativeEditorProvider'; suiteSetup(() => { snapshot = takeSnapshot(); }); - setup(async () => { ioc = new DataScienceIocContainer(); ioc.registerDataScienceTypes(false, languageServerType); return ioc.activate(); }); - suiteTeardown(() => { + suiteTeardown(async () => { writeDiffSnapshot(snapshot, 'Intellisense'); }); @@ -56,10 +55,6 @@ import { ITestNativeEditorProvider } from './testNativeEditorProvider'; await ioc.dispose(); }); - // suiteTeardown(() => { - // asyncDump(); - // }); - function getIntellisenseTextLines(wrapper: ReactWrapper, React.Component>): string[] { assert.ok(wrapper); const editor = getInteractiveEditor(wrapper); diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 3b1d3e19ef16..a0dc8b706575 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -122,11 +122,6 @@ suite('DataScience Interactive Window output tests', () => { verifyHtmlOnCell(iw, 'InteractiveCell', html, cellIndex); } - // Uncomment this to debug hangs on exit - // suiteTeardown(() => { - // asyncDump(); - // }); - runTest( 'Simple text', async () => { diff --git a/src/test/datascience/liveshare.functional.test.tsx b/src/test/datascience/liveshare.functional.test.tsx index 55d45c04742c..c3cf4e2170ba 100644 --- a/src/test/datascience/liveshare.functional.test.tsx +++ b/src/test/datascience/liveshare.functional.test.tsx @@ -68,10 +68,6 @@ suite('DataScience LiveShare tests', () => { lastErrorMessage = undefined; }); - suiteTeardown(() => { - //asyncDump(); - }); - function createContainer(role: vsls.Role): DataScienceIocContainer { const result = new DataScienceIocContainer(); result.registerDataScienceTypes(); diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index 095c5b7c8d5b..5f5388716772 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -53,7 +53,6 @@ import { IMonacoEditorState, MonacoEditor } from '../../datascience-ui/react-com import { waitForCondition } from '../common'; import { createTemporaryFile } from '../utils/fs'; import { DataScienceIocContainer } from './dataScienceIocContainer'; -import { takeSnapshot, writeDiffSnapshot } from './helpers'; import { MockCustomEditorService } from './mockCustomEditorService'; import { MockDocumentManager } from './mockDocumentManager'; import { IMountedWebView, WaitForMessageOptions } from './mountedWebView'; @@ -113,7 +112,6 @@ suite('DataScience Native Editor', () => { [false, true].forEach((useCustomEditorApi) => { //import { asyncDump } from '../common/asyncDump'; - let snapshot: any; suite(`${useCustomEditorApi ? 'With' : 'Without'} Custom Editor API`, () => { function createFileCell(cell: any, data: any): ICell { const newCell = { @@ -135,12 +133,6 @@ suite('DataScience Native Editor', () => { return newCell; } - suiteSetup(() => { - snapshot = takeSnapshot(); - }); - suiteTeardown(() => { - writeDiffSnapshot(snapshot, `Native ${useCustomEditorApi}`); - }); suite('Editor tests', () => { const disposables: Disposable[] = []; let ioc: DataScienceIocContainer; @@ -212,11 +204,6 @@ suite('DataScience Native Editor', () => { } }); - // Uncomment this to debug hangs on exit - // suiteTeardown(() => { - // asyncDump(); - // }); - runMountedTest('Simple text', async () => { // Create an editor so something is listening to messages const { mount } = await createNewEditor(ioc); diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index b163bcdb8f87..e25a4b46f59b 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -50,7 +50,6 @@ import { concatMultilineString } from '../../datascience-ui/common'; import { generateTestState, ICellViewModel } from '../../datascience-ui/interactive-common/mainState'; import { sleep } from '../core'; import { DataScienceIocContainer } from './dataScienceIocContainer'; -import { takeSnapshot, writeDiffSnapshot } from './helpers'; import { SupportedCommands } from './mockJupyterManager'; import { MockPythonService } from './mockPythonService'; import { createPythonService, startRemoteServer } from './remoteTestHelpers'; @@ -65,7 +64,6 @@ suite('DataScience notebook tests', () => { let ioc: DataScienceIocContainer; let modifiedConfig = false; const baseUri = Uri.file('foo.py'); - let snapshot: any; // tslint:disable-next-line: no-function-expression setup(async function () { @@ -82,14 +80,6 @@ suite('DataScience notebook tests', () => { notebookProvider = ioc.get(INotebookProvider); }); - suiteSetup(() => { - snapshot = takeSnapshot(); - }); - - suiteTeardown(() => { - writeDiffSnapshot(snapshot, `Notebook ${useRawKernel}`); - }); - teardown(async () => { try { if (modifiedConfig) { @@ -478,7 +468,7 @@ suite('DataScience notebook tests', () => { runTest('Remote Password', async () => { const pythonService = await createPythonService(ioc); - if (pythonService && !useRawKernel && os.platform() !== 'darwin') { + if (pythonService && !useRawKernel && os.platform() !== 'darwin' && os.platform() !== 'linux') { const configFile = path.join( EXTENSION_ROOT_DIR, 'src', @@ -680,8 +670,12 @@ suite('DataScience notebook tests', () => { // Make sure we have a cell in our results assert.ok(/#\s*%%/.test(results), 'No cells in returned import'); } finally { - importer.dispose(); - temp.dispose(); + try { + importer.dispose(); + temp.dispose(); + } catch { + // Don't care if they don't delete + } } }); diff --git a/src/test/datascience/plotViewer.functional.test.tsx b/src/test/datascience/plotViewer.functional.test.tsx index 970d3b15d357..4ce147a5437b 100644 --- a/src/test/datascience/plotViewer.functional.test.tsx +++ b/src/test/datascience/plotViewer.functional.test.tsx @@ -137,10 +137,6 @@ suite('DataScience PlotViewer tests', () => { delete (global as any).ascquireVsCodeApi; }); - suiteTeardown(() => { - // asyncDump(); - }); - async function waitForPlot(wrapper: ReactWrapper, React.Component>, svg: string): Promise { // Get a render promise with the expected number of renders const renderPromise = waitForUpdate(wrapper, MainPanel, 1); diff --git a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts index 2c5dbc8b7825..991a70b16b6b 100644 --- a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts +++ b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts @@ -16,7 +16,6 @@ import { Disposable } from 'vscode'; import { LocalZMQKernel } from '../../../client/common/experiments/groups'; import { sleep } from '../../../client/common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../client/constants'; -import { IS_CI_SERVER } from '../../ciConstants'; import { retryIfFail as retryIfFailOriginal } from '../../common'; import { mockedVSCodeNamespaces } from '../../vscode-mock'; import { DataScienceIocContainer } from '../dataScienceIocContainer'; @@ -39,12 +38,6 @@ use(chaiAsPromised); let ioc: DataScienceIocContainer; suiteSetup(function () { - // Skip all tests until flakiness can be resolved. - // See issue: https://github.com/microsoft/vscode-python/issues/13936 - if (IS_CI_SERVER) { - this.skip(); - } - // These are UI tests, hence nothing to do with platforms. this.timeout(30_000); // UI Tests, need time to start jupyter. this.retries(3); // UI tests can be flaky. diff --git a/src/test/datascience/variableexplorer.functional.test.tsx b/src/test/datascience/variableexplorer.functional.test.tsx index 59830bcda74c..88d81e797ff4 100644 --- a/src/test/datascience/variableexplorer.functional.test.tsx +++ b/src/test/datascience/variableexplorer.functional.test.tsx @@ -10,7 +10,6 @@ import { RunByLine } from '../../client/common/experiments/groups'; import { InteractiveWindowMessages } from '../../client/datascience/interactive-common/interactiveWindowTypes'; import { IJupyterVariable } from '../../client/datascience/types'; import { DataScienceIocContainer } from './dataScienceIocContainer'; -import { takeSnapshot, writeDiffSnapshot } from './helpers'; import { addCode, getOrCreateInteractiveWindow } from './interactiveWindowTestHelpers'; import { addCell, createNewEditor } from './nativeEditorTestHelpers'; import { openVariableExplorer, runDoubleTest, runInteractiveTest, waitForVariablesUpdated } from './testHelpers'; @@ -25,10 +24,8 @@ const rangeInclusive = require('range-inclusive'); const disposables: Disposable[] = []; let ioc: DataScienceIocContainer; let createdNotebook = false; - let snapshot: any; suiteSetup(function () { - snapshot = takeSnapshot(); // These test require python, so only run with a non-mocked jupyter const isRollingBuild = process.env ? process.env.VSCODE_PYTHON_ROLLING !== undefined : false; if (!isRollingBuild) { @@ -38,7 +35,6 @@ const rangeInclusive = require('range-inclusive'); this.skip(); } }); - setup(async () => { ioc = new DataScienceIocContainer(); ioc.setExperimentState(RunByLine.experiment, runByLine); @@ -61,12 +57,6 @@ const rangeInclusive = require('range-inclusive'); await ioc.dispose(); }); - // Uncomment this to debug hangs on exit - suiteTeardown(() => { - // asyncDump(); - writeDiffSnapshot(snapshot, `Variable Explorer ${runByLine}`); - }); - async function addCodeImpartial( wrapper: ReactWrapper, React.Component>, code: string, diff --git a/src/test/startPage/startPage.functional.test.tsx b/src/test/startPage/startPage.functional.test.tsx index 803a43688f81..a83fd7fc0a4e 100644 --- a/src/test/startPage/startPage.functional.test.tsx +++ b/src/test/startPage/startPage.functional.test.tsx @@ -19,7 +19,6 @@ suite('StartPage tests', () => { ioc.registerDataScienceTypes(); await ioc.activate(); }); - teardown(async () => { await ioc.dispose(); }); diff --git a/src/test/unittests.ts b/src/test/unittests.ts index 066d4b628012..e3a3d0046133 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -82,4 +82,13 @@ if (process.argv.indexOf('--fast') === -1) { setupTranspile(); } +exports.mochaHooks = { + afterAll() { + const kernelLauncherMod = require('../client/datascience/kernel-launcher/kernelLauncher'); + + // After all tests run, clean up the kernel launcher mutex files + return kernelLauncherMod.KernelLauncher.cleanupStartPort(); + } +}; + initialize();