diff --git a/docs/05-command-line.md b/docs/05-command-line.md index 7e9c63484..cd88183ad 100644 --- a/docs/05-command-line.md +++ b/docs/05-command-line.md @@ -3,12 +3,13 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/docs/05-command-line.md) ```console -$ npx ava --help ava +ava debug ava reset-cache Commands: ava Run tests [default] + ava debug Activate Node.js inspector and run the test file ava reset-cache Reset AVA's compilation cache and exit Positionals: diff --git a/docs/recipes/debugging-with-chrome-devtools.md b/docs/recipes/debugging-with-chrome-devtools.md index ac98e78fd..8b5027ec8 100644 --- a/docs/recipes/debugging-with-chrome-devtools.md +++ b/docs/recipes/debugging-with-chrome-devtools.md @@ -2,12 +2,32 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/docs/recipes/debugging-with-chrome-devtools.md) -Use [inspect-process](https://github.com/jaridmargolin/inspect-process) to easily launch a debugging session with Chrome DevTools. +**This recipe describes the new `inspect` command in the upcoming AVA 3 release. See the [AVA 2](https://github.com/avajs/ava/blob/v2.4.0/docs/recipes/debugging-with-chrome-devtools.md) documentation instead.** + +You can debug your tests using [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools). + +Open Chrome, then navigate to . Click the *Open dedicated DevTools for Node* link within the *Devices* section. + +In the *DevTools for Node* window, navigate to *Sources* and in the left-hand column select *Filesystem*. Add your project directory to the workspace. Make sure to grant permission. + +Now run a specific test file: ```console -$ npm install --global inspect-process +npx ava debug test.js ``` +The DevTools should connect automatically and your tests will run. Use DevTools to set breakpoints, or use the `debugger` keyword. + +Run with the `--break` option to ensure the DevTools hit a breakpoint right before the test file is loaded: + ```console -$ inspect node_modules/ava/profile.js some/test/file.js +npx ava debug --break test.js ``` + +You can also customize the port. It defaults to `9229`: + +```console +npx ava debug --port 9230 test.js +``` + +You'll have to add a connection for this port in the *Connection* tab. AVA only binds to `localhost`. diff --git a/docs/recipes/debugging-with-vscode.md b/docs/recipes/debugging-with-vscode.md index 357b0e88c..26aeb3878 100644 --- a/docs/recipes/debugging-with-vscode.md +++ b/docs/recipes/debugging-with-vscode.md @@ -2,40 +2,43 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/docs/recipes/debugging-with-vscode.md) -## Setup - -In the sidebar click the `Debug` handle. - -Add a new configuration in the dropdown menu next to the green `Debug` button: `Add configuration`. This will open `launch.json` with all debug configurations. - -Add following to the `configurations` object: - -```json -{ - "type": "node", - "request": "launch", - "name": "Run AVA test", - "program": "${workspaceFolder}/node_modules/ava/profile.js", - "args": [ - "${file}" - ], - "skipFiles": [ - "/**/*.js" - ] -} -``` - -Save this configuration after you added it. - -## Debug - -> **Note:** The file you want to debug, must be open and active - -> **Note:** The breakpoints in VSCode are a bit buggy sometimes (especially with async code). `debugger;` always works fine. - -Set breakpoints in the code **or** write `debugger;` at the point where it should stop. - -Hit the green `Debug` button next to the list of configurations on the top left in the `Debug` view. Once the breakpoint is hit, you can evaluate variables and step through the code. +**This recipe describes the new `inspect` command in the upcoming AVA 3 release. See the [AVA 2](https://github.com/avajs/ava/blob/v2.4.0/docs/recipes/debugging-with-vscode.md) documentation instead.** + +You can debug your tests using [Visual Studio Code](https://code.visualstudio.com/). + +## Creating a launch configuration + +1. Open a workspace for your project. +1. In the sidebar click the *Debug* handle. +1. Create a `launch.json` file. +1. Select the Node.js environment. +1. Add following to the `configurations` object: + + ```json + { + "type": "node", + "request": "launch", + "name": "Debug AVA test file", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", + "runtimeArgs": [ + "debug", + "--break", + "${file}" + ], + "port": 9229, + "outputCapture": "std", + "skipFiles": [ + "/**/*.js" + ] + } + ``` +1. Save your changes to the `launch.json` file. + +## Using the debugger + +Open the file(s) you want to debug. You can set breakpoints or use the `debugger` keyword. + +Now, *with a test file open*, from the *Debug* menu run the *Debug AVA test file* configuration. ## Serial debugging @@ -43,17 +46,21 @@ By default AVA runs tests concurrently. This may complicate debugging. Add a con ```json { - "type": "node", - "request": "launch", - "name": "Run AVA test serially", - "program": "${workspaceFolder}/node_modules/ava/profile.js", - "args": [ - "${file}", - "--serial" - ], - "skipFiles": [ - "/**/*.js" - ] + "type": "node", + "request": "launch", + "name": "Debug AVA test file", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", + "runtimeArgs": [ + "debug", + "--break", + "--serial", + "${file}" + ], + "port": 9229, + "outputCapture": "std", + "skipFiles": [ + "/**/*.js" + ] } ``` diff --git a/docs/recipes/debugging-with-webstorm.md b/docs/recipes/debugging-with-webstorm.md index 7bf4237a6..bd3b0e743 100644 --- a/docs/recipes/debugging-with-webstorm.md +++ b/docs/recipes/debugging-with-webstorm.md @@ -2,6 +2,10 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/docs/recipes/debugging-with-webstorm.md) +**This recipe is outdated.** + +--- + Starting with version 2016.2, [WebStorm](https://www.jetbrains.com/webstorm/) and other JetBrains IDEs (IntelliJ IDEA Ultimate, PHPStorm, PyCharm Professional, and RubyMine with installed Node.js plugin) allow you to debug AVA tests. diff --git a/lib/api.js b/lib/api.js index 43df397ba..502120ede 100644 --- a/lib/api.js +++ b/lib/api.js @@ -9,7 +9,6 @@ const isCi = require('is-ci'); const resolveCwd = require('resolve-cwd'); const debounce = require('lodash/debounce'); const Bluebird = require('bluebird'); -const getPort = require('get-port'); const arrify = require('arrify'); const makeDir = require('make-dir'); const ms = require('ms'); @@ -269,7 +268,6 @@ class Api extends Emittery { return; } - const execArgv = await this._computeForkExecArgv(); const options = { ...apiOptions, babelState, @@ -283,7 +281,7 @@ class Api extends Emittery { options.updateSnapshots = true; } - const worker = fork(file, options, execArgv); + const worker = fork(file, options, process.execArgv); runStatus.observeWorker(worker, file); pendingWorkers.add(worker); @@ -318,31 +316,6 @@ class Api extends Emittery { return cacheDir; } - - async _computeForkExecArgv() { - const execArgv = this.options.testOnlyExecArgv || process.execArgv; - if (execArgv.length === 0) { - return Promise.resolve(execArgv); - } - - // --inspect-brk is used in addition to --inspect to break on first line and wait - const inspectArgIndex = execArgv.findIndex(arg => /^--inspect(?:-brk)?(?:$|=)/.test(arg)); - if (inspectArgIndex === -1) { - return Promise.resolve(execArgv); - } - - const port = await getPort(); - const forkExecArgv = execArgv.slice(); - let flagName = '--inspect'; - const oldValue = forkExecArgv[inspectArgIndex]; - if (oldValue.includes('brk')) { - flagName += '-brk'; - } - - forkExecArgv[inspectArgIndex] = `${flagName}=${port}`; - - return forkExecArgv; - } } module.exports = Api; diff --git a/lib/cli.js b/lib/cli.js index dd9b3b211..cabf2ab3b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -87,6 +87,7 @@ exports.run = async () => { // eslint-disable-line complexity confError = error; } + let debug = null; let resetCache = false; const {argv} = yargs .parserConfiguration({ @@ -105,6 +106,7 @@ exports.run = async () => { // eslint-disable-line complexity 'unknown-options-as-args': false }) .usage('$0 ') + .usage('$0 debug ') .usage('$0 reset-cache') .options({ color: { @@ -120,6 +122,31 @@ exports.run = async () => { // eslint-disable-line complexity describe: 'Paths to individual test files. Leave empty if you want AVA to search for files instead.', type: 'string' })) + .command( + 'debug', + 'Activate Node.js inspector and run the test file', + yargs => yargs.options(FLAGS).options({ + break: { + description: 'Break before the test file is loaded', + type: 'boolean' + }, + port: { + default: 9229, + description: 'Port on which you can connect to the inspector', + type: 'number' + } + }).positional('file', { + demand: true, + describe: 'Path to an individual test file', + type: 'string' + }), + argv => { + debug = { + break: argv.break === true, + files: argv._.slice(1), + port: argv.port + }; + }) .command( 'reset-cache', 'Reset AVA\'s compilation cache and exit', @@ -160,12 +187,28 @@ exports.run = async () => { // eslint-disable-line complexity return; } - if (argv.watch && argv.tap && !conf.tap) { - exit('The TAP reporter is not available when using watch mode.'); + if (argv.watch) { + if (argv.tap && !conf.tap) { + exit('The TAP reporter is not available when using watch mode.'); + } + + if (isCi) { + exit('Watch mode is not available in CI, as it prevents AVA from terminating.'); + } + + if (debug !== null) { + exit('Watch mode is not available when debugging.'); + } } - if (argv.watch && isCi) { - exit('Watch mode is not available in CI, as it prevents AVA from terminating.'); + if (debug !== null) { + if (argv.tap && !conf.tap) { + exit('The TAP reporter is not available when debugging.'); + } + + if (isCi) { + exit('Debugging is not available in CI.'); + } } const combined = {...conf}; @@ -243,7 +286,7 @@ exports.run = async () => { // eslint-disable-line complexity const match = combined.match === '' ? [] : arrify(combined.match); - const input = argv._; + const input = debug ? debug.files : argv._; const resolveTestsFrom = input.length === 0 ? projectDir : process.cwd(); const files = input.map(file => path.relative(resolveTestsFrom, path.resolve(process.cwd(), file))); @@ -262,12 +305,17 @@ exports.run = async () => { // eslint-disable-line complexity } } + if (debug !== null && files.length !== 1) { + exit('Provide the path to the test file you wish to debug'); + } + const api = new Api({ babelProvider, cacheEnabled: combined.cache !== false, color: combined.color, compileEnhancements: combined.compileEnhancements !== false, concurrency: combined.concurrency || 0, + debug, experiments, extensions, failFast: combined.failFast, @@ -288,12 +336,12 @@ exports.run = async () => { // eslint-disable-line complexity }); let reporter; - if (combined.tap && !combined.watch) { + if (combined.tap && !combined.watch && debug === null) { reporter = new TapReporter({ reportStream: process.stdout, stdStream: process.stderr }); - } else if (combined.verbose || isCi || !process.stdout.isTTY) { + } else if (debug !== null || combined.verbose || isCi || !process.stdout.isTTY) { reporter = new VerboseReporter({ reportStream: process.stdout, stdStream: process.stderr, diff --git a/lib/worker/subprocess.js b/lib/worker/subprocess.js index 1c20aa496..27745e821 100644 --- a/lib/worker/subprocess.js +++ b/lib/worker/subprocess.js @@ -128,6 +128,13 @@ ipc.options.then(options => { // to make sure we also track dependencies with custom require hooks dependencyTracking.install(testPath); + if (options.debug) { + require('inspector').open(options.debug.port, '127.0.0.1', true); + if (options.debug.break) { + debugger; // eslint-disable-line no-debugger + } + } + require(testPath); if (accessedRunner) { diff --git a/test/api.js b/test/api.js index 1861a80c7..f3b1508ca 100644 --- a/test/api.js +++ b/test/api.js @@ -1177,33 +1177,6 @@ test('using --match with matching tests will only report those passing tests', t }); }); -function generatePassDebugTests(execArgv) { - test(`pass ${execArgv.join(' ')} to fork`, t => { - const api = apiCreator({testOnlyExecArgv: execArgv}); - return api._computeForkExecArgv() - .then(result => { - t.true(result.length === execArgv.length); - t.true(/--inspect=\d+/.test(result[0])); - }); - }); -} - -function generatePassInspectIntegrationTests(execArgv) { - test(`pass ${execArgv.join(' ')} to fork`, t => { - const api = apiCreator({testOnlyExecArgv: execArgv}); - return api.run([path.join(__dirname, 'fixture/inspect-arg.js')]) - .then(runStatus => { - t.is(runStatus.stats.passedTests, 1); - }); - }); -} - -generatePassDebugTests(['--inspect=0']); -generatePassDebugTests(['--inspect']); - -generatePassInspectIntegrationTests(['--inspect=9229']); -generatePassInspectIntegrationTests(['--inspect']); - test('`esm` package support', t => { const api = apiCreator({ require: [require.resolve('esm')] diff --git a/test/integration/debug.js b/test/integration/debug.js new file mode 100644 index 000000000..60e95745e --- /dev/null +++ b/test/integration/debug.js @@ -0,0 +1,27 @@ +'use strict'; +const {test} = require('tap'); +const {execCli} = require('../helper/cli'); + +test('bails when using --watch while while debugging', t => { + execCli(['debug', '--watch', 'test.js'], {dirname: 'fixture/watcher', env: {CI: ''}}, (err, stdout, stderr) => { + t.is(err.code, 1); + t.match(stderr, 'Watch mode is not available when debugging.'); + t.end(); + }); +}); + +test('bails when debugging in CI', t => { + execCli(['debug', 'test.js'], {dirname: 'fixture/watcher', env: {CI: true}}, (err, stdout, stderr) => { + t.is(err.code, 1); + t.match(stderr, 'Debugging is not available in CI.'); + t.end(); + }); +}); + +test('bails when --tap reporter is used while debugging', t => { + execCli(['debug', '--tap', 'test.js'], {dirname: 'fixture/watcher', env: {CI: ''}}, (err, stdout, stderr) => { + t.is(err.code, 1); + t.match(stderr, 'The TAP reporter is not available when debugging.'); + t.end(); + }); +});