xref: /third_party/node/test/common/debugger.js (revision 1cb0ef41)
1'use strict';
2const common = require('../common');
3const spawn = require('child_process').spawn;
4
5const BREAK_MESSAGE = new RegExp('(?:' + [
6  'assert', 'break', 'break on start', 'debugCommand',
7  'exception', 'other', 'promiseRejection',
8].join('|') + ') in', 'i');
9
10let TIMEOUT = common.platformTimeout(5000);
11if (common.isWindows) {
12  // Some of the windows machines in the CI need more time to receive
13  // the outputs from the client.
14  // https://github.com/nodejs/build/issues/3014
15  TIMEOUT = common.platformTimeout(15000);
16}
17
18function isPreBreak(output) {
19  return /Break on start/.test(output) && /1 \(function \(exports/.test(output);
20}
21
22function startCLI(args, flags = [], spawnOpts = {}) {
23  let stderrOutput = '';
24  const child =
25    spawn(process.execPath, [...flags, 'inspect', ...args], spawnOpts);
26
27  const outputBuffer = [];
28  function bufferOutput(chunk) {
29    if (this === child.stderr) {
30      stderrOutput += chunk;
31    }
32    outputBuffer.push(chunk);
33  }
34
35  function getOutput() {
36    return outputBuffer.join('\n').replaceAll('\b', '');
37  }
38
39  child.stdout.setEncoding('utf8');
40  child.stdout.on('data', bufferOutput);
41  child.stderr.setEncoding('utf8');
42  child.stderr.on('data', bufferOutput);
43
44  if (process.env.VERBOSE === '1') {
45    child.stdout.pipe(process.stdout);
46    child.stderr.pipe(process.stderr);
47  }
48
49  return {
50    flushOutput() {
51      const output = this.output;
52      outputBuffer.length = 0;
53      return output;
54    },
55
56    waitFor(pattern) {
57      function checkPattern(str) {
58        if (Array.isArray(pattern)) {
59          return pattern.every((p) => p.test(str));
60        }
61        return pattern.test(str);
62      }
63
64      return new Promise((resolve, reject) => {
65        function checkOutput() {
66          if (checkPattern(getOutput())) {
67            tearDown();
68            resolve();
69          }
70        }
71
72        function onChildClose(code, signal) {
73          tearDown();
74          let message = 'Child exited';
75          if (code) {
76            message += `, code ${code}`;
77          }
78          if (signal) {
79            message += `, signal ${signal}`;
80          }
81          message += ` while waiting for ${pattern}; found: ${this.output}`;
82          if (stderrOutput) {
83            message += `\n STDERR: ${stderrOutput}`;
84          }
85          reject(new Error(message));
86        }
87
88        const timer = setTimeout(() => {
89          tearDown();
90          reject(new Error([
91            `Timeout (${TIMEOUT}) while waiting for ${pattern}`,
92            `found: ${this.output}`,
93          ].join('; ')));
94        }, TIMEOUT);
95
96        function tearDown() {
97          clearTimeout(timer);
98          child.stdout.removeListener('data', checkOutput);
99          child.removeListener('close', onChildClose);
100        }
101
102        child.on('close', onChildClose);
103        child.stdout.on('data', checkOutput);
104        checkOutput();
105      });
106    },
107
108    waitForPrompt() {
109      return this.waitFor(/>\s+$/);
110    },
111
112    async waitForInitialBreak() {
113      await this.waitFor(/break (?:on start )?in/i);
114
115      if (isPreBreak(this.output)) {
116        await this.command('next', false);
117        return this.waitFor(/break in/);
118      }
119    },
120
121    get breakInfo() {
122      const output = this.output;
123      const breakMatch =
124        output.match(/break (?:on start )?in ([^\n]+):(\d+)\n/i);
125
126      if (breakMatch === null) {
127        throw new Error(
128          `Could not find breakpoint info in ${JSON.stringify(output)}`);
129      }
130      return { filename: breakMatch[1], line: +breakMatch[2] };
131    },
132
133    ctrlC() {
134      return this.command('.interrupt');
135    },
136
137    get output() {
138      return getOutput();
139    },
140
141    get rawOutput() {
142      return outputBuffer.join('').toString();
143    },
144
145    parseSourceLines() {
146      return getOutput().split('\n')
147        .map((line) => line.match(/(?:\*|>)?\s*(\d+)/))
148        .filter((match) => match !== null)
149        .map((match) => +match[1]);
150    },
151
152    writeLine(input, flush = true) {
153      if (flush) {
154        this.flushOutput();
155      }
156      if (process.env.VERBOSE === '1') {
157        process.stderr.write(`< ${input}\n`);
158      }
159      child.stdin.write(input);
160      child.stdin.write('\n');
161    },
162
163    command(input, flush = true) {
164      this.writeLine(input, flush);
165      return this.waitForPrompt();
166    },
167
168    stepCommand(input) {
169      this.writeLine(input, true);
170      return this
171        .waitFor(BREAK_MESSAGE)
172        .then(() => this.waitForPrompt());
173    },
174
175    quit() {
176      return new Promise((resolve) => {
177        child.stdin.end();
178        child.on('close', resolve);
179      });
180    },
181  };
182}
183module.exports = startCLI;
184