Skip to content

Commit

Permalink
Allow Node arguments to be configured
Browse files Browse the repository at this point in the history
Supports `nodeArguments` configuration and `--node-arguments` on the CLI. Fixes #2090.

Co-authored-by: Mark Wubben <mark@novemberborn.net>
  • Loading branch information
maximelkin and novemberborn committed Jan 18, 2020
1 parent ad27246 commit 7b20f6c
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 5 deletions.
16 changes: 16 additions & 0 deletions docs/05-command-line.md
Expand Up @@ -29,6 +29,8 @@ Options:
--fail-fast Stop after first test failure [boolean]
--match, -m Only run tests with matching title (can be repeated)
[string]
--node-arguments Additional Node.js arguments for launching worker
processes (specify as a single string) [string]
--serial, -s Run tests serially [boolean]
--tap, -t Generate TAP output [boolean]
--timeout, -T Set global timeout (milliseconds or human-readable,
Expand Down Expand Up @@ -178,3 +180,17 @@ $ npx ava --tap | npx tap-nyan
<img src="../media/tap-reporter.png" width="420">

Please note that the TAP reporter is unavailable when using [watch mode](./recipes/watch-mode.md).

## Node arguments

The `--node-arguments` argument may be used to specify additional arguments for launching worker processes. These are combined with the `nodeArguments` configuration and any arguments passed to the `node` binary when starting AVA.

**Only pass trusted values.**

Specify the arguments as a single string:

```console
npx ava --node-arguments="--throw-deprecation --zero-fill-buffers"
```

**Only pass trusted values.**
9 changes: 9 additions & 0 deletions docs/06-configuration.md
Expand Up @@ -31,6 +31,10 @@ To ignore files, prefix the pattern with an `!` (exclamation mark).
"verbose": true,
"require": [
"./my-helper-module.js"
],
"nodeArguments": [
"--trace-deprecation",
"--napi-modules"
]
}
}
Expand All @@ -53,6 +57,7 @@ Arguments passed to the CLI will always take precedence over the CLI options con
- `extensions`: extensions of test files. Setting this overrides the default `["cjs", "mjs", "js"]` value, so make sure to include those extensions in the list
- `require`: extra modules to require before tests are run. Modules are required in the [worker processes](./01-writing-tests.md#process-isolation)
- `timeout`: Timeouts in AVA behave differently than in other test frameworks. AVA resets a timer after each test, forcing tests to quit if no new test results were received within the specified timeout. This can be used to handle stalled tests. See our [timeout documentation](./07-test-timeouts.md) for more options.
- `nodeArguments`: Configure Node.js arguments used to launch worker processes.

Note that providing files on the CLI overrides the `files` option.

Expand Down Expand Up @@ -219,4 +224,8 @@ export default {
};
```

## Node arguments

The `nodeArguments` configuration may be used to specify additional arguments for launching worker processes. These are combined with `--node-arguments` passed on the CLI and any arguments passed to the `node` binary when starting AVA.

[CLI]: ./05-command-line.md
2 changes: 1 addition & 1 deletion lib/api.js
Expand Up @@ -219,7 +219,7 @@ class Api extends Emittery {
options.updateSnapshots = true;
}

const worker = fork(file, options, process.execArgv);
const worker = fork(file, options, apiOptions.nodeArguments);
runStatus.observeWorker(worker, file);

pendingWorkers.add(worker);
Expand Down
16 changes: 15 additions & 1 deletion lib/cli.js
Expand Up @@ -35,6 +35,11 @@ const FLAGS = {
description: 'Only run tests with matching title (can be repeated)',
type: 'string'
},
'node-arguments': {
coerce: coerceLastValue,
description: 'Additional Node.js arguments for launching worker processes (specify as a single string)',
type: 'string'
},
serial: {
alias: 's',
coerce: coerceLastValue,
Expand Down Expand Up @@ -161,7 +166,7 @@ exports.run = async () => { // eslint-disable-line complexity
combined.failFast = argv[flag];
} else if (flag === 'update-snapshots') {
combined.updateSnapshots = argv[flag];
} else {
} else if (flag !== 'node-arguments') {
combined[flag] = argv[flag];
}
}
Expand Down Expand Up @@ -254,6 +259,7 @@ exports.run = async () => { // eslint-disable-line complexity
const babelManager = require('./babel-manager');
const normalizeExtensions = require('./extensions');
const {normalizeGlobs, normalizePatterns} = require('./globs');
const normalizeNodeArguments = require('./node-arguments');
const validateEnvironmentVariables = require('./environment-variables');

let pkg;
Expand Down Expand Up @@ -303,6 +309,13 @@ exports.run = async () => { // eslint-disable-line complexity
exit(error.message);
}

let nodeArguments;
try {
nodeArguments = normalizeNodeArguments(conf.nodeArguments, argv['node-arguments']);
} catch (error) {
exit(error.message);
}

let parallelRuns = null;
if (isCi && ciParallelVars) {
const {index: currentIndex, total: totalRuns} = ciParallelVars;
Expand All @@ -328,6 +341,7 @@ exports.run = async () => { // eslint-disable-line complexity
moduleTypes,
environmentVariables,
match,
nodeArguments,
parallelRuns,
projectDir,
ranFromCli: true,
Expand Down
4 changes: 2 additions & 2 deletions lib/fork.js
Expand Up @@ -14,7 +14,7 @@ const AVA_PATH = path.resolve(__dirname, '..');

const workerPath = require.resolve('./worker/subprocess');

module.exports = (file, opts, execArgv) => {
module.exports = (file, opts, execArgv = process.execArgv) => {
let finished = false;

const emitter = new Emittery();
Expand All @@ -34,7 +34,7 @@ module.exports = (file, opts, execArgv) => {
cwd: opts.projectDir,
silent: true,
env: {NODE_ENV: 'test', ...process.env, ...opts.environmentVariables, AVA_PATH},
execArgv: execArgv || process.execArgv
execArgv
});

subprocess.stdout.on('data', chunk => {
Expand Down
17 changes: 17 additions & 0 deletions lib/node-arguments.js
@@ -0,0 +1,17 @@
'use strict';
const arrgv = require('arrgv');

function normalizeNodeArguments(fromConf = [], fromArgv = '') {
let parsedArgv = [];
if (fromArgv !== '') {
try {
parsedArgv = arrgv(fromArgv);
} catch {
throw new Error('Could not parse `--node-arguments` value. Make sure all strings are closed and backslashes are used correctly.');
}
}

return [...process.execArgv, ...fromConf, ...parsedArgv];
}

module.exports = normalizeNodeArguments;
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -59,6 +59,7 @@
"dependencies": {
"@concordance/react": "^2.0.0",
"ansi-styles": "^4.2.1",
"arrgv": "^1.0.2",
"arrify": "^2.0.1",
"chalk": "^3.0.0",
"chokidar": "^3.3.1",
Expand Down
7 changes: 7 additions & 0 deletions test/fixture/node-arguments.js
@@ -0,0 +1,7 @@
const test = require('../..');

test('exec arguments includes --throw-deprecation and --zero-fill-buffers', t => {
t.plan(2);
t.truthy(process.execArgv.includes('--throw-deprecation'));
t.truthy(process.execArgv.includes('--zero-fill-buffers'));
});
8 changes: 8 additions & 0 deletions test/fixture/node-arguments/package.json
@@ -0,0 +1,8 @@
{
"ava": {
"nodeArguments": [
"--require",
"./setup.js"
]
}
}
1 change: 1 addition & 0 deletions test/fixture/node-arguments/setup.js
@@ -0,0 +1 @@
global.SETUP_CALLED = true;
7 changes: 7 additions & 0 deletions test/fixture/node-arguments/test.js
@@ -0,0 +1,7 @@
const test = require('../../..');

test('works', t => {
t.plan(2);
t.truthy(global.SETUP_CALLED, 'setup variable set');
t.truthy(process.execArgv.some(argv => argv.startsWith('--require')), 'require passed');
});
2 changes: 1 addition & 1 deletion test/helper/cli.js
Expand Up @@ -26,7 +26,7 @@ function execCli(args, opts, cb) {
const processPromise = new Promise(resolve => {
// Spawning a child with piped IO means that the CLI will never see a TTY.
// Inserting a shim here allows us to fake a TTY.
child = childProcess.spawn(process.execPath, ['-r', ttySimulator, cliPath].concat(args), {
child = childProcess.spawn(process.execPath, ['--require', ttySimulator, cliPath].concat(args), {
cwd: dirname,
env: {CI: '1', ...env}, // Force CI to ensure the correct reporter is selected
// env,
Expand Down
24 changes: 24 additions & 0 deletions test/integration/node-arguments.js
@@ -0,0 +1,24 @@
'use strict';
const {test} = require('tap');
const {execCli} = require('../helper/cli');

test('passes node arguments to workers', t => {
t.plan(1);
execCli(['--node-arguments="--throw-deprecation --zero-fill-buffers"', 'node-arguments.js'],
(err, stdout, stderr) => t.ifError(err, null, {stdout, stderr}));
});

test('reads node arguments from config', t => {
t.plan(1);
execCli(['test.js'], {
dirname: 'fixture/node-arguments'
}, (err, stdout, stderr) => t.ifError(err, null, {stdout, stderr}));
});

test('detects incomplete --node-arguments', t => {
t.plan(2);
execCli(['--node-arguments="--foo=\'bar"', 'node-arguments.js'], (err, stdout, stderr) => {
t.ok(err);
t.match(stderr, /Could not parse `--node-arguments` value. Make sure all strings are closed and backslashes are used correctly./);
});
});
12 changes: 12 additions & 0 deletions test/node-arguments.js
@@ -0,0 +1,12 @@
'use strict';

const {test} = require('tap');
const normalizeNodeArguments = require('../lib/node-arguments');

test('combines arguments', async t => {
t.deepEqual(
await normalizeNodeArguments(['--require setup.js'], '--throw-deprecation --zero-fill-buffers'),
[...process.execArgv, '--require setup.js', '--throw-deprecation', '--zero-fill-buffers']
);
t.end();
});

0 comments on commit 7b20f6c

Please sign in to comment.