diff --git a/.gitignore b/.gitignore index 0335c480b8bf..466b005af61d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ npm-debug.log coverage/ .vscode-test/** .venv +pythonFiles/experimental/ptvsd/** diff --git a/.travis.yml b/.travis.yml index c3a4faf04958..f81ec0a06242 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ before_install: | export TRAVIS_PYTHON_PATH=`which python` install: - pip install --upgrade -r requirements.txt + - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - yarn script: @@ -33,6 +34,7 @@ script: - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi + - rm -rf ./pythonFiles/experimental/ptvsd - yarn run clean - yarn run vscode:prepublish - yarn run cover:enable diff --git a/appveyor.yml b/appveyor.yml index 9616eb46eeab..b4eca06c93b4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,6 +15,7 @@ install: - yarn - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - pip install -U pip + - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - python --version - python -m easy_install -U setuptools - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" diff --git a/news/3 Code Health/742.md b/news/3 Code Health/742.md new file mode 100644 index 000000000000..d35d1ffd391e --- /dev/null +++ b/news/3 Code Health/742.md @@ -0,0 +1 @@ +Enable unit testing of the experimental debugger on CI servers diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index cfbdd9584ac0..2995452cb2f7 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -1,4 +1,4 @@ - +import * as path from 'path'; export const PythonLanguage = { language: 'python' }; export namespace Commands { @@ -69,3 +69,5 @@ export function isTestExecution(): boolean { // tslint:disable-next-line:interface-name no-string-literal return process.env['VSC_PYTHON_CI_TEST'] === '1'; } + +export const EXTENSION_ROOT_DIR = path.join(__dirname, '..', '..', '..'); diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index c82b92acd932..10b8b2de24c9 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -3,6 +3,7 @@ import { ChildProcess } from 'child_process'; import * as path from 'path'; import { DebugSession, OutputEvent } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; +import { EXTENSION_ROOT_DIR } from '../../common/constants'; import { open } from '../../common/open'; import { PathUtils } from '../../common/platform/pathUtils'; import { CurrentProcess } from '../../common/process/currentProcess'; @@ -82,8 +83,14 @@ export class LocalDebugClient extends DebugClient { public async LaunchApplicationToDebug(dbgServer: IDebugServer): Promise { const pathUtils = new PathUtils(IS_WINDOWS); const currentProcess = new CurrentProcess(); - const helper = new DebugClientHelper(new EnvironmentVariablesService(pathUtils), pathUtils, currentProcess); + const environmentVariablesService = new EnvironmentVariablesService(pathUtils); + const helper = new DebugClientHelper(environmentVariablesService, pathUtils, currentProcess); const environmentVariables = await helper.getEnvironmentVariables(this.args); + if (this.args.type === 'pythonExperimental') { + // Import the PTVSD debugger, allowing users to use their own latest copies. + const experimentalPTVSDPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + environmentVariablesService.appendPythonPath(environmentVariables, experimentalPTVSDPath); + } // tslint:disable-next-line:max-func-body-length cyclomatic-complexity no-any return new Promise((resolve, reject) => { const fileDir = this.args && this.args.program ? path.dirname(this.args.program) : ''; diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index a0547f4fc5d9..885ea746ef53 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -9,9 +9,10 @@ import * as path from 'path'; import { ThreadEvent } from 'vscode-debugadapter'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { DebugProtocol } from 'vscode-debugprotocol'; +import { noop } from '../../client/common/core.utils'; import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; -import { IS_CI_SERVER, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; const isProcessRunning = require('is-running') as (number) => boolean; @@ -34,10 +35,6 @@ const THREAD_TIMEOUT = 10000; if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { this.skip(); } - // Temporary, untill new version of PTVSD is bundled we cannot run tests - if (debuggerType !== 'python' && IS_CI_SERVER) { - return this.skip(); - } await new Promise(resolve => setTimeout(resolve, 1000)); debugClient = new DebugClient('node', testAdapterFilePath, debuggerType); await debugClient.start(); @@ -46,26 +43,20 @@ const THREAD_TIMEOUT = 10000; // Wait for a second before starting another test (sometimes, sockets take a while to get closed). await sleep(1000); try { - // tslint:disable-next-line:no-empty - await debugClient.stop().catch(() => { }); + await debugClient.stop().catch(noop); // tslint:disable-next-line:no-empty } catch (ex) { } await sleep(1000); }); - function buildLauncArgs(pythonFile: string, stopOnEntry: boolean = false): LaunchRequestArguments { - // Temporary, untill new version of PTVSD is bundled we cannot run tests. - // For now lets run test locally. - const pythonPath = debuggerType === 'python' ? 'python' : '/Users/donjayamanne/Desktop/Development/PythonStuff/IssueRepos/debuggerTests/.envp36/bin/python'; - const env = debuggerType === 'python' ? {} : { PYTHONPATH: '/Users/donjayamanne/Desktop/Development/PythonStuff/IssueRepos/expPTVSD/ptvsd' }; return { program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, stopOnEntry, debugOptions: ['RedirectOutput'], - pythonPath, + pythonPath: 'python', args: [], - env, + env: {}, envFile: '', logToFile: false, type: debuggerType @@ -325,17 +316,23 @@ const THREAD_TIMEOUT = 10000; const threadId = ((await threadIdPromise) as ThreadEvent).body.threadId; await debugClient.assertStoppedLocation('breakpoint', breakpointLocation); - await debugClient.nextRequest({ threadId }); const functionLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 7 }; - await debugClient.assertStoppedLocation('step', functionLocation); + await Promise.all([ + debugClient.nextRequest({ threadId }), + debugClient.assertStoppedLocation('step', functionLocation) + ]); - await debugClient.nextRequest({ threadId }); const functionInvocationLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 11 }; - await debugClient.assertStoppedLocation('step', functionInvocationLocation); + await Promise.all([ + debugClient.nextRequest({ threadId }), + debugClient.assertStoppedLocation('step', functionInvocationLocation) + ]); - await debugClient.nextRequest({ threadId }); const printLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 13 }; - await debugClient.assertStoppedLocation('step', printLocation); + await Promise.all([ + debugClient.nextRequest({ threadId }), + debugClient.assertStoppedLocation('step', printLocation) + ]); }); test('Test stepin and stepout', async () => { const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); @@ -357,28 +354,37 @@ const THREAD_TIMEOUT = 10000; await debugClient.assertStoppedLocation('breakpoint', breakpointLocation); const threadId = ((await threadIdPromise) as ThreadEvent).body.threadId; - await debugClient.nextRequest({ threadId }); const functionLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 7 }; - await debugClient.assertStoppedLocation('step', functionLocation); + await Promise.all([ + debugClient.nextRequest({ threadId }), + debugClient.assertStoppedLocation('step', functionLocation) + ]); - await debugClient.nextRequest({ threadId }); const functionInvocationLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 11 }; - await debugClient.assertStoppedLocation('step', functionInvocationLocation); + await Promise.all([ + debugClient.nextRequest({ threadId }), + debugClient.assertStoppedLocation('step', functionInvocationLocation) + ]); - await debugClient.stepInRequest({ threadId }); const loopPrintLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 8 }; - await debugClient.assertStoppedLocation('step', loopPrintLocation); + await Promise.all([ + debugClient.stepInRequest({ threadId }), + debugClient.assertStoppedLocation('step', loopPrintLocation) + ]); - await debugClient.stepOutRequest({ threadId }); - await debugClient.assertStoppedLocation('step', functionInvocationLocation); + await Promise.all([ + debugClient.stepOutRequest({ threadId }), + debugClient.assertStoppedLocation('step', functionInvocationLocation) + ]); - await debugClient.nextRequest({ threadId }); const printLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 13 }; - await debugClient.assertStoppedLocation('step', printLocation); + await Promise.all([ + debugClient.nextRequest({ threadId }), + debugClient.assertStoppedLocation('step', printLocation) + ]); }); test('Test pausing', async function () { - // TODO: re-enable for new debugger once it's running on CI - if (debuggerType !== 'pythonExperimental' || IS_CI_SERVER) { + if (debuggerType !== 'pythonExperimental') { return this.skip(); } diff --git a/src/test/debugger/portAndHost.test.ts b/src/test/debugger/portAndHost.test.ts index 920c59f486dd..7bbee91fb728 100644 --- a/src/test/debugger/portAndHost.test.ts +++ b/src/test/debugger/portAndHost.test.ts @@ -7,6 +7,7 @@ import * as getFreePort from 'get-port'; import * as net from 'net'; import * as path from 'path'; import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { noop } from '../../client/common/core.utils'; import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; @@ -28,10 +29,6 @@ const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'd // tslint:disable-next-line:no-invalid-this this.skip(); } - if (debuggerType !== 'python') { - // tslint:disable-next-line:no-invalid-this - return this.skip(); - } await new Promise(resolve => setTimeout(resolve, 1000)); debugClient = new DebugClient('node', testAdapterFilePath, debuggerType); await debugClient.start(); @@ -40,14 +37,12 @@ const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'd // Wait for a second before starting another test (sometimes, sockets take a while to get closed). await new Promise(resolve => setTimeout(resolve, 1000)); try { - // tslint:disable-next-line:no-empty - debugClient.stop().catch(() => { }); + debugClient.stop().catch(noop); // tslint:disable-next-line:no-empty } catch (ex) { } }); function buildLauncArgs(pythonFile: string, stopOnEntry: boolean = false, port?: number, host?: string): LaunchRequestArguments { - // pythonPath: '/Users/donjayamanne/anaconda3/envs/py36/bin/python', return { program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, @@ -103,8 +98,7 @@ const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'd expect(exception!.message).contains('ENOTFOUND', 'Debugging failed for some other reason'); }); test('Confirm debuggig fails when provided port is in use', async () => { - // tslint:disable-next-line:no-empty - const server = net.createServer((s) => { }); + const server = net.createServer(noop); const port = await new Promise((resolve, reject) => server.listen({ host: 'localhost', port: 0 }, () => resolve(server.address().port))); let exception: Error | undefined; try {