|  | 
|  | 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