|
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +import { expect, use } from 'chai'; |
| 5 | +import * as chaiAsPromised from 'chai-as-promised'; |
| 6 | +import { ChildProcess } from 'child_process'; |
| 7 | +import * as getFreePort from 'get-port'; |
| 8 | +import { EOL } from 'os'; |
| 9 | +import * as path from 'path'; |
| 10 | +import { ThreadEvent } from 'vscode-debugadapter'; |
| 11 | +import { DebugClient } from 'vscode-debugadapter-testsupport'; |
| 12 | +import { createDeferred } from '../../client/common/helpers'; |
| 13 | +import { BufferDecoder } from '../../client/common/process/decoder'; |
| 14 | +import { ProcessService } from '../../client/common/process/proc'; |
| 15 | +import { AttachRequestArguments } from '../../client/debugger/Common/Contracts'; |
| 16 | +import { initialize } from '../initialize'; |
| 17 | + |
| 18 | +use(chaiAsPromised); |
| 19 | + |
| 20 | +const fileToDebug = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger.py'); |
| 21 | +const ptvsdPath = path.join(__dirname, '..', '..', '..', 'pythonFiles', 'PythonTools'); |
| 22 | +const DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'debugger', 'Main.js'); |
| 23 | + |
| 24 | +// tslint:disable-next-line:max-func-body-length |
| 25 | +suite('Attach Debugger', () => { |
| 26 | + let debugClient: DebugClient; |
| 27 | + let procToKill: ChildProcess; |
| 28 | + suiteSetup(function () { |
| 29 | + // tslint:disable-next-line:no-invalid-this |
| 30 | + this.skip(); |
| 31 | + return initialize(); |
| 32 | + }); |
| 33 | + |
| 34 | + setup(async () => { |
| 35 | + await new Promise(resolve => setTimeout(resolve, 1000)); |
| 36 | + debugClient = new DebugClient('node', DEBUG_ADAPTER, 'python'); |
| 37 | + await debugClient.start(); |
| 38 | + }); |
| 39 | + teardown(async () => { |
| 40 | + // Wait for a second before starting another test (sometimes, sockets take a while to get closed). |
| 41 | + await new Promise(resolve => setTimeout(resolve, 1000)); |
| 42 | + try { |
| 43 | + debugClient.stop(); |
| 44 | + // tslint:disable-next-line:no-empty |
| 45 | + } catch (ex) { } |
| 46 | + if (procToKill) { |
| 47 | + procToKill.kill(); |
| 48 | + } |
| 49 | + }); |
| 50 | + test('Confirm we are able to attach to a running program', async () => { |
| 51 | + const port = await getFreePort({ host: 'localhost', port: 3000 }); |
| 52 | + const args: AttachRequestArguments = { |
| 53 | + localRoot: path.dirname(fileToDebug), |
| 54 | + remoteRoot: path.dirname(fileToDebug), |
| 55 | + port: port, |
| 56 | + host: 'localhost', |
| 57 | + secret: 'super_secret' |
| 58 | + }; |
| 59 | + |
| 60 | + const customEnv = { ...process.env }; |
| 61 | + |
| 62 | + // Set the path for PTVSD to be picked up. |
| 63 | + // tslint:disable-next-line:no-string-literal |
| 64 | + customEnv['PYTHONPATH'] = ptvsdPath; |
| 65 | + const procService = new ProcessService(new BufferDecoder()); |
| 66 | + const result = procService.execObservable('python', [fileToDebug, port.toString()], { env: customEnv, cwd: path.dirname(fileToDebug) }); |
| 67 | + procToKill = result.proc; |
| 68 | + |
| 69 | + const completed = createDeferred(); |
| 70 | + const expectedOutputs = [ |
| 71 | + { value: 'start', deferred: createDeferred() }, |
| 72 | + { value: 'Peter Smith', deferred: createDeferred() }, |
| 73 | + { value: 'end', deferred: createDeferred() } |
| 74 | + ]; |
| 75 | + const startOutputReceived = expectedOutputs[0].deferred.promise; |
| 76 | + const firstOutputReceived = expectedOutputs[1].deferred.promise; |
| 77 | + const secondOutputReceived = expectedOutputs[2].deferred.promise; |
| 78 | + |
| 79 | + result.out.subscribe(output => { |
| 80 | + if (expectedOutputs[0].value === output.out) { |
| 81 | + expectedOutputs.shift()!.deferred.resolve(); |
| 82 | + } |
| 83 | + }, ex => { |
| 84 | + completed.reject(ex); |
| 85 | + }, () => { |
| 86 | + completed.resolve(); |
| 87 | + }); |
| 88 | + |
| 89 | + await startOutputReceived; |
| 90 | + |
| 91 | + const threadIdPromise = createDeferred<number>(); |
| 92 | + debugClient.on('thread', (data: ThreadEvent) => { |
| 93 | + if (data.body.reason === 'started') { |
| 94 | + threadIdPromise.resolve(data.body.threadId); |
| 95 | + } |
| 96 | + }); |
| 97 | + |
| 98 | + const initializePromise = debugClient.initializeRequest({ |
| 99 | + adapterID: 'python', |
| 100 | + linesStartAt1: true, |
| 101 | + columnsStartAt1: true, |
| 102 | + supportsRunInTerminalRequest: true, |
| 103 | + pathFormat: 'path' |
| 104 | + }); |
| 105 | + await debugClient.attachRequest(args); |
| 106 | + await initializePromise; |
| 107 | + |
| 108 | + // Wait till we get the thread of the program. |
| 109 | + const threadId = await threadIdPromise.promise; |
| 110 | + expect(threadId).to.be.greaterThan(0, 'ThreadId not received'); |
| 111 | + |
| 112 | + // Continue the program. |
| 113 | + await debugClient.continueRequest({ threadId }); |
| 114 | + |
| 115 | + // Value for input prompt. |
| 116 | + result.proc.stdin.write(`Peter Smith${EOL}`); |
| 117 | + await firstOutputReceived; |
| 118 | + |
| 119 | + result.proc.stdin.write(`${EOL}`); |
| 120 | + await secondOutputReceived; |
| 121 | + await completed.promise; |
| 122 | + |
| 123 | + await debugClient.waitForEvent('terminated'); |
| 124 | + }); |
| 125 | +}); |
0 commit comments