Signed-off-by: Marco Ippolito <marcoippolito54@gmail.com> PR-URL: https://github.com/nodejs/node/pull/63033 Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
916 lines
34 KiB
JavaScript
916 lines
34 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
isWindows,
|
|
spawnPromisified,
|
|
skipIfSQLiteMissing,
|
|
} = require('../common');
|
|
skipIfSQLiteMissing();
|
|
const fixtures = require('../common/fixtures');
|
|
const tmpdir = require('../common/tmpdir');
|
|
const assert = require('node:assert');
|
|
const { test, it, describe } = require('node:test');
|
|
const { chmodSync, writeFileSync, constants } = require('node:fs');
|
|
const { join } = require('node:path');
|
|
|
|
const onlyIfNodeOptionsSupport = { skip: process.config.variables.node_without_node_options };
|
|
const onlyWithAmaro = { skip: !process.config.variables.node_use_amaro };
|
|
const onlyWithAmaroAndNodeOptions = {
|
|
skip: !process.config.variables.node_use_amaro || process.config.variables.node_without_node_options,
|
|
};
|
|
const onlyWithInspectorAndNodeOptions = {
|
|
skip: !process.features.inspector || process.config.variables.node_without_node_options,
|
|
};
|
|
|
|
test('should handle non existing json', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--experimental-config-file=i-do-not-exist.json',
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Cannot read configuration from i-do-not-exist\.json: no such file or directory/);
|
|
assert.match(result.stderr, /i-do-not-exist\.json: not found/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should handle empty json', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
`--experimental-config-file=${fixtures.path('rc/empty.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Can't parse/);
|
|
assert.match(result.stderr, /empty\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should handle empty object json', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/empty-object.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.match(result.stdout, /Hello, World!/);
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
describe('runtime version checks', () => {
|
|
const currentMajor = Number(process.versions.node.split('.')[0]);
|
|
|
|
async function runConfig(config, filename = 'version-config.json') {
|
|
tmpdir.refresh();
|
|
const configPath = join(tmpdir.path, filename);
|
|
writeFileSync(configPath, JSON.stringify(config));
|
|
|
|
return spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${configPath}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
]);
|
|
}
|
|
|
|
it('should accept a top-level config without nodeVersion', async () => {
|
|
const result = await runConfig({
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
}, 'top-level-without-version.json');
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should accept a config file matching the current Node.js version', async () => {
|
|
const result = await runConfig({
|
|
nodeVersion: currentMajor,
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
}, 'matching-version.json');
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should reject a config file targeting another Node.js version', async () => {
|
|
const result = await runConfig({
|
|
nodeVersion: currentMajor + 1,
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
}, 'mismatching-version.json');
|
|
assert.match(result.stderr, /"nodeVersion" \d+ does not match current Node\.js version \d+/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should select a matching config from configs', async () => {
|
|
const result = await runConfig({
|
|
configs: [
|
|
{
|
|
nodeVersion: currentMajor + 1,
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 20 },
|
|
},
|
|
},
|
|
{
|
|
nodeVersion: currentMajor,
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
},
|
|
],
|
|
}, 'versioned-configs.json');
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should reject configs without an entry for the current version', async () => {
|
|
const result = await runConfig({
|
|
configs: [
|
|
{
|
|
nodeVersion: currentMajor + 1,
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
},
|
|
],
|
|
}, 'missing-versioned-config.json');
|
|
assert.match(result.stderr, /No config found for current Node\.js version \d+ in "configs"/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should ignore invalid config payloads for non-matching versions', async () => {
|
|
const result = await runConfig({
|
|
configs: [
|
|
{
|
|
nodeVersion: currentMajor + 1,
|
|
config: false,
|
|
},
|
|
{
|
|
nodeVersion: currentMajor,
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
},
|
|
],
|
|
}, 'ignored-non-matching-config.json');
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should use the first matching config from configs', async () => {
|
|
const result = await runConfig({
|
|
configs: [
|
|
{
|
|
nodeVersion: currentMajor,
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
},
|
|
{
|
|
nodeVersion: currentMajor,
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 20 },
|
|
},
|
|
},
|
|
],
|
|
}, 'first-matching-versioned-config.json');
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should allow $schema with configs', async () => {
|
|
const result = await runConfig({
|
|
$schema: 'https://nodejs.org/dist/vX.Y.Z/docs/node-config-schema.json',
|
|
configs: [
|
|
{
|
|
nodeVersion: currentMajor,
|
|
config: {
|
|
$schema: 'https://nodejs.org/dist/vX.Y.Z/docs/node-config-schema.json',
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
},
|
|
],
|
|
}, 'schema-with-versioned-config.json');
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
for (const { name, config, error } of [
|
|
{
|
|
name: 'configs is empty',
|
|
config: { configs: [] },
|
|
error: /No config found for current Node\.js version \d+ in "configs"/,
|
|
},
|
|
{
|
|
name: 'configs is not an array',
|
|
config: { configs: {} },
|
|
error: /"configs" value unexpected .* \(should be an array\)/,
|
|
},
|
|
{
|
|
name: 'configs contains a non-object entry',
|
|
config: { configs: [false] },
|
|
error: /"configs\[0\]" value unexpected .* \(should be an object\)/,
|
|
},
|
|
{
|
|
name: 'configs entry is missing nodeVersion',
|
|
config: { configs: [{ config: {} }] },
|
|
error: /"configs\[0\]\.nodeVersion" is required/,
|
|
},
|
|
{
|
|
name: 'configs entry has a non-integer nodeVersion',
|
|
config: { configs: [{ nodeVersion: `${currentMajor}`, config: {} }] },
|
|
error: /"configs\[0\]\.nodeVersion" value unexpected .* \(should be an integer\)/,
|
|
},
|
|
{
|
|
name: 'matching configs entry is missing config',
|
|
config: { configs: [{ nodeVersion: currentMajor }] },
|
|
error: /"configs\[0\]\.config" is required/,
|
|
},
|
|
{
|
|
name: 'matching configs entry has a non-object config',
|
|
config: { configs: [{ nodeVersion: currentMajor, config: false }] },
|
|
error: /"configs\[0\]\.config" value unexpected .* \(should be an object\)/,
|
|
},
|
|
{
|
|
name: 'configs is mixed with preceding config fields',
|
|
config: {
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
configs: [{ nodeVersion: currentMajor, config: {} }],
|
|
},
|
|
error: /"configs" cannot be mixed with other configuration fields/,
|
|
},
|
|
{
|
|
name: 'configs is mixed with following config fields',
|
|
config: {
|
|
configs: [{ nodeVersion: currentMajor, config: {} }],
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
error: /"configs" cannot be mixed with other configuration fields/,
|
|
},
|
|
{
|
|
name: 'configs is nested inside a selected config',
|
|
config: {
|
|
configs: [{
|
|
nodeVersion: currentMajor,
|
|
config: { configs: [] },
|
|
}],
|
|
},
|
|
error: /"configs" is not allowed inside a versioned config/,
|
|
},
|
|
{
|
|
name: 'selected config targets another version',
|
|
config: {
|
|
configs: [{
|
|
nodeVersion: currentMajor,
|
|
config: {
|
|
nodeVersion: currentMajor + 1,
|
|
nodeOptions: { 'max-http-header-size': 10 },
|
|
},
|
|
}],
|
|
},
|
|
error: /"nodeVersion" \d+ does not match current Node\.js version \d+/,
|
|
},
|
|
]) {
|
|
it(`should reject when ${name}`, async () => {
|
|
const result = await runConfig(config);
|
|
assert.match(result.stderr, error);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
}
|
|
});
|
|
|
|
test('should parse boolean flag', onlyWithAmaroAndNodeOptions, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
`--experimental-config-file=${fixtures.path('rc/strip-types.json')}`,
|
|
fixtures.path('typescript/ts/test-typescript.ts'),
|
|
]);
|
|
assert.match(result.stderr, /--experimental-config-file is an experimental feature and might change at any time/);
|
|
assert.match(result.stdout, /Hello, TypeScript!/);
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should parse boolean flag defaulted to true', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
`--experimental-config-file=${fixtures.path('rc/warnings-false.json')}`,
|
|
'-p', 'process.emitWarning("A warning")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'undefined\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should throw an error when a flag is declared twice', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/override-property.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Option --strip-types is already defined/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should not override env-file', onlyWithAmaroAndNodeOptions, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/strip-types.json')}`,
|
|
'--env-file', fixtures.path('dotenv/node-options-no-tranform.env'),
|
|
fixtures.path('typescript/ts/test-typescript.ts'),
|
|
]);
|
|
assert.match(result.stderr, /SyntaxError/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 1);
|
|
});
|
|
|
|
test('should not override NODE_OPTIONS', onlyWithAmaro, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/strip-types.json')}`,
|
|
fixtures.path('typescript/ts/test-typescript.ts'),
|
|
], {
|
|
env: {
|
|
...process.env,
|
|
NODE_OPTIONS: '--no-strip-types',
|
|
},
|
|
});
|
|
assert.match(result.stderr, /SyntaxError/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 1);
|
|
});
|
|
|
|
test('should not override CLI flags', onlyWithAmaro, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--no-strip-types',
|
|
`--experimental-config-file=${fixtures.path('rc/strip-types.json')}`,
|
|
fixtures.path('typescript/ts/test-typescript.ts'),
|
|
]);
|
|
assert.match(result.stderr, /SyntaxError/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 1);
|
|
});
|
|
|
|
test('should parse array flag correctly', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/import.json')}`,
|
|
'--eval', 'setTimeout(() => console.log("D"),99)',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'A\nB\nC\nD\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should validate invalid array flag', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/invalid-import.json')}`,
|
|
'--eval', 'setTimeout(() => console.log("D"),99)',
|
|
]);
|
|
assert.match(result.stderr, /invalid-import\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should validate array flag as string', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/import-as-string.json')}`,
|
|
'--eval', 'setTimeout(() => console.log("B"),99)',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'A\nB\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should throw at unknown flag', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/unknown-flag.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Unknown or not allowed option some-unknown-flag for namespace nodeOptions/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should throw at flag not available in NODE_OPTIONS', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/not-node-options-flag.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Unknown or not allowed option test for namespace nodeOptions/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('unsigned flag should be parsed correctly', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/numeric.json')}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '4294967295\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('numeric flag should not allow negative values', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/negative-numeric.json')}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
]);
|
|
assert.match(result.stderr, /Invalid value for --max-http-header-size/);
|
|
assert.match(result.stderr, /negative-numeric\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('v8 flag should not be allowed in config file', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/v8-flag.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /V8 flag --abort-on-uncaught-exception is currently not supported/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('string flag should be parsed correctly', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--test',
|
|
`--experimental-config-file=${fixtures.path('rc/string.json')}`,
|
|
fixtures.path('rc/test.js'),
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '.\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('host port flag should be parsed correctly', onlyWithInspectorAndNodeOptions, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/host-port.json')}`,
|
|
'-p', 'require("internal/options").getOptionValue("--inspect-port").port',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '65535\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('--inspect=true should be parsed correctly', onlyWithInspectorAndNodeOptions, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/inspect-true.json')}`,
|
|
'--inspect-port', '0',
|
|
'-p', 'require("node:inspector").url()',
|
|
]);
|
|
assert.match(result.stderr, /^Debugger listening on (ws:\/\/[^\s]+)/);
|
|
assert.match(result.stdout, /ws:\/\/[^\s]+/);
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('--inspect=false should be parsed correctly', { skip: !process.features.inspector }, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/inspect-false.json')}`,
|
|
'-p', 'require("node:inspector").url()',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'undefined\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('no op flag should throw', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/no-op.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /No-op flag --http-parser is currently not supported/);
|
|
assert.match(result.stderr, /no-op\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should not allow users to sneak in a flag', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/sneaky-flag.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /The number of NODE_OPTIONS doesn't match the number of flags in the config file/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('non object root', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/non-object-root.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Root value unexpected not an object for/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('non object node options', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/non-object-node-options.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /"nodeOptions" value unexpected for/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should throw correct error when a json is broken', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/broken.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Can't parse/);
|
|
assert.match(result.stderr, /broken\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('broken value in node_options', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/broken-node-options.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Can't parse/);
|
|
assert.match(result.stderr, /broken-node-options\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should use node.config.json as default', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-default-config-file',
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should use node.config.json when --experimental-config-file has no argument',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-config-file',
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should not treat the script path as a config file argument',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-config-file',
|
|
fixtures.path('printA.js'),
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'A\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should treat a space-separated config file path as the script',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-config-file',
|
|
fixtures.path('rc/empty.json'),
|
|
fixtures.path('printA.js'),
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.match(result.stderr, /SyntaxError/);
|
|
assert.match(result.stderr, /Unexpected end of JSON input/);
|
|
assert.doesNotMatch(result.stderr, /Can't parse/);
|
|
assert.doesNotMatch(result.stderr, /requires an argument/);
|
|
assert.strictEqual(result.code, 1);
|
|
});
|
|
|
|
test('should error when --experimental-config-file= has empty argument',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-config-file=',
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.match(result.stderr, /--experimental-config-file= requires an argument/);
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should error when --experimental-default-config-file has an explicit argument',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-default-config-file=node.config.json',
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.match(result.stderr, /--experimental-default-config-file does not take an argument/);
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
test('should override node.config.json when specified', onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-default-config-file',
|
|
`--experimental-config-file=${fixtures.path('rc/default/override.json')}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '20\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should work with --experimental-config-file=path',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/default/node.config.json')}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should use last config file when multiple are specified',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/default/node.config.json')}`,
|
|
`--experimental-config-file=${fixtures.path('rc/default/override.json')}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '20\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
test('should use default when next argument starts with dash',
|
|
onlyIfNodeOptionsSupport, async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-config-file',
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: fixtures.path('rc/default'),
|
|
});
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
// Skip on windows because it doesn't support chmod changing read permissions
|
|
// Also skip if user is root because it would have read permissions anyway
|
|
test('should throw an error when the file is non readable', {
|
|
skip: isWindows || process.getuid() === 0,
|
|
}, async () => {
|
|
tmpdir.refresh();
|
|
const dest = join(tmpdir.path, 'node.config.json');
|
|
writeFileSync(dest, JSON.stringify({
|
|
nodeOptions: { 'max-http-header-size': 10 }
|
|
}));
|
|
chmodSync(dest, constants.O_RDONLY);
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--experimental-default-config-file',
|
|
'-p', 'http.maxHeaderSize',
|
|
], {
|
|
cwd: tmpdir.path,
|
|
});
|
|
assert.match(result.stderr, /Cannot read configuration from node\.config\.json: permission denied/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
describe('namespace-scoped options', () => {
|
|
it('should parse a namespace-scoped option correctly', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/namespaced/node.config.json')}`,
|
|
'--no-test',
|
|
'-p', 'require("internal/options").getOptionValue("--test-isolation")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'none\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should throw an error when a namespace-scoped option is not recognised', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/unknown-flag-namespace.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Unknown or not allowed option unknown-flag for namespace test/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should throw an error when a namespace is not recognised', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/unknown-namespace.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /Unknown namespace an-invalid-namespace/);
|
|
assert.match(result.stderr, /unknown-namespace\.json: invalid content/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should allow the $schema field', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/schema.json')}`,
|
|
'-p', 'http.maxHeaderSize',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '10\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should handle an empty namespace valid namespace', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/empty-valid-namespace.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'Hello, World!\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should throw an error if a namespace-scoped option has already been set in node options', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/override-node-option-with-namespace.json')}`,
|
|
'-p', 'require("internal/options").getOptionValue("--test-isolation")',
|
|
]);
|
|
assert.match(result.stderr, /Option --test-isolation is already defined/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should throw an error if a node option has already been set in a namespace-scoped option', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/override-namespace.json')}`,
|
|
'-p', 'require("internal/options").getOptionValue("--test-isolation")',
|
|
]);
|
|
assert.match(result.stderr, /Option --test-isolation is already defined/);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should prioritise CLI namespace-scoped options over config file options', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
'--test-isolation', 'process',
|
|
`--experimental-config-file=${fixtures.path('rc/namespaced/node.config.json')}`,
|
|
'--no-test',
|
|
'-p', 'require("internal/options").getOptionValue("--test-isolation")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'process\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should append namespace-scoped config file options with CLI options in case of array', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
'--test-coverage-exclude', 'cli-pattern1',
|
|
'--test-coverage-exclude', 'cli-pattern2',
|
|
`--experimental-config-file=${fixtures.path('rc/namespace-with-array.json')}`,
|
|
'--no-test',
|
|
'-p', 'JSON.stringify(require("internal/options").getOptionValue("--test-coverage-exclude"))',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
const excludePatterns = JSON.parse(result.stdout);
|
|
const expected = [
|
|
'config-pattern1',
|
|
'config-pattern2',
|
|
'cli-pattern1',
|
|
'cli-pattern2',
|
|
];
|
|
assert.deepStrictEqual(excludePatterns, expected);
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should allow setting kDisallowedInEnvvar in the config file if part of a namespace', async () => {
|
|
// This test assumes that the --test-concurrency flag is configured as kDisallowedInEnvVar
|
|
// and that it is part of at least one namespace.
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/namespace-with-disallowed-envvar.json')}`,
|
|
'--no-test',
|
|
'-p', 'require("internal/options").getOptionValue("--test-concurrency")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '1\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should override namespace-scoped config file options with CLI options', async () => {
|
|
// This test assumes that the --test-concurrency flag is configured as kDisallowedInEnvVar
|
|
// and that it is part of at least one namespace.
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
'--test-concurrency', '2',
|
|
`--experimental-config-file=${fixtures.path('rc/namespace-with-disallowed-envvar.json')}`,
|
|
'--no-test',
|
|
'-p', 'require("internal/options").getOptionValue("--test-concurrency")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, '2\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should throw an error for removed "testRunner" namespace', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/deprecated-testrunner-namespace.json')}`,
|
|
'-p', '"Hello, World!"',
|
|
]);
|
|
assert.match(result.stderr, /the "testRunner" namespace has been removed\. Use "test" instead\./);
|
|
assert.strictEqual(result.stdout, '');
|
|
assert.strictEqual(result.code, 9);
|
|
});
|
|
|
|
it('should automatically enable --test flag when test namespace is present', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
`--experimental-config-file=${fixtures.path('rc/namespaced/node.config.json')}`,
|
|
fixtures.path('rc/test.js'),
|
|
]);
|
|
assert.strictEqual(result.code, 0);
|
|
assert.match(result.stdout, /tests 1/);
|
|
});
|
|
|
|
it('should automatically enable --permission flag when permission namespace is present', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/permission-namespace.json')}`,
|
|
'-p', 'require("internal/options").getOptionValue("--permission")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'true\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
|
|
it('should respect explicit test: false in test namespace', async () => {
|
|
const result = await spawnPromisified(process.execPath, [
|
|
'--no-warnings',
|
|
'--expose-internals',
|
|
`--experimental-config-file=${fixtures.path('rc/test-namespace-explicit-false.json')}`,
|
|
'-p', 'require("internal/options").getOptionValue("--test")',
|
|
]);
|
|
assert.strictEqual(result.stderr, '');
|
|
assert.strictEqual(result.stdout, 'false\n');
|
|
assert.strictEqual(result.code, 0);
|
|
});
|
|
});
|