mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
policy: increase tests via permutation matrix
PR-URL: https://github.com/nodejs/node/pull/34404 Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
@@ -276,18 +276,13 @@ function readPackage(requestPath) {
|
||||
const existing = packageJsonCache.get(jsonPath);
|
||||
if (existing !== undefined) return existing;
|
||||
|
||||
const result = packageJsonReader.read(path.toNamespacedPath(jsonPath));
|
||||
const result = packageJsonReader.read(jsonPath);
|
||||
const json = result.containsKeys === false ? '{}' : result.string;
|
||||
if (json === undefined) {
|
||||
packageJsonCache.set(jsonPath, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (manifest) {
|
||||
const jsonURL = pathToFileURL(jsonPath);
|
||||
manifest.assertIntegrity(jsonURL, json);
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSONParse(json);
|
||||
const filtered = {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const { getOptionValue } = require('internal/options');
|
||||
const manifest = getOptionValue('--experimental-policy') ?
|
||||
require('internal/process/policy').manifest :
|
||||
null;
|
||||
|
||||
const { Buffer } = require('buffer');
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -15,20 +20,22 @@ const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
|
||||
|
||||
async function defaultGetSource(url, { format } = {}, defaultGetSource) {
|
||||
const parsed = new URL(url);
|
||||
let source;
|
||||
if (parsed.protocol === 'file:') {
|
||||
return {
|
||||
source: await readFileAsync(parsed)
|
||||
};
|
||||
source = await readFileAsync(parsed);
|
||||
} else if (parsed.protocol === 'data:') {
|
||||
const match = DATA_URL_PATTERN.exec(parsed.pathname);
|
||||
if (!match) {
|
||||
throw new ERR_INVALID_URL(url);
|
||||
}
|
||||
const [ , base64, body ] = match;
|
||||
return {
|
||||
source: Buffer.from(body, base64 ? 'base64' : 'utf8')
|
||||
};
|
||||
source = Buffer.from(body, base64 ? 'base64' : 'utf8');
|
||||
} else {
|
||||
throw new ERR_INVALID_URL_SCHEME(['file', 'data']);
|
||||
}
|
||||
throw new ERR_INVALID_URL_SCHEME(['file', 'data']);
|
||||
if (manifest) {
|
||||
manifest.assertIntegrity(parsed, source);
|
||||
}
|
||||
return { source };
|
||||
}
|
||||
exports.defaultGetSource = defaultGetSource;
|
||||
|
||||
@@ -2,21 +2,35 @@
|
||||
|
||||
const { SafeMap } = primordials;
|
||||
const { internalModuleReadJSON } = internalBinding('fs');
|
||||
const { pathToFileURL } = require('url');
|
||||
const { toNamespacedPath } = require('path');
|
||||
|
||||
const cache = new SafeMap();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {string} jsonPath
|
||||
*/
|
||||
function read(path) {
|
||||
if (cache.has(path)) {
|
||||
return cache.get(path);
|
||||
function read(jsonPath) {
|
||||
if (cache.has(jsonPath)) {
|
||||
return cache.get(jsonPath);
|
||||
}
|
||||
|
||||
const [string, containsKeys] = internalModuleReadJSON(path);
|
||||
const [string, containsKeys] = internalModuleReadJSON(
|
||||
toNamespacedPath(jsonPath)
|
||||
);
|
||||
const result = { string, containsKeys };
|
||||
cache.set(path, result);
|
||||
const { getOptionValue } = require('internal/options');
|
||||
if (string !== undefined) {
|
||||
const manifest = getOptionValue('--experimental-policy') ?
|
||||
require('internal/process/policy').manifest :
|
||||
null;
|
||||
if (manifest) {
|
||||
const jsonURL = pathToFileURL(jsonPath);
|
||||
manifest.assertIntegrity(jsonURL, string);
|
||||
}
|
||||
}
|
||||
cache.set(jsonPath, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,9 @@ class Worker extends EventEmitter {
|
||||
cwdCounter: cwdCounter || workerIo.sharedCwdCounter,
|
||||
workerData: options.workerData,
|
||||
publicPort: port2,
|
||||
manifestURL: getOptionValue('--experimental-policy') ?
|
||||
require('internal/process/policy').url :
|
||||
null,
|
||||
manifestSrc: getOptionValue('--experimental-policy') ?
|
||||
require('internal/process/policy').src :
|
||||
null,
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const assert = require('assert');
|
||||
const { spawnSync } = require('child_process');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
function hash(algo, body) {
|
||||
const h = crypto.createHash(algo);
|
||||
h.update(body);
|
||||
return h.digest('base64');
|
||||
}
|
||||
|
||||
const policyFilepath = path.join(tmpdir.path, 'policy');
|
||||
|
||||
const packageFilepath = path.join(tmpdir.path, 'package.json');
|
||||
const packageURL = pathToFileURL(packageFilepath);
|
||||
const packageBody = '{"main": "dep.js"}';
|
||||
const policyToPackageRelativeURLString = `./${
|
||||
path.relative(path.dirname(policyFilepath), packageFilepath)
|
||||
}`;
|
||||
|
||||
const parentFilepath = path.join(tmpdir.path, 'parent.js');
|
||||
const parentURL = pathToFileURL(parentFilepath);
|
||||
const parentBody = 'require(\'./dep.js\')';
|
||||
|
||||
const workerSpawningFilepath = path.join(tmpdir.path, 'worker_spawner.js');
|
||||
const workerSpawningURL = pathToFileURL(workerSpawningFilepath);
|
||||
const workerSpawningBody = `
|
||||
const { Worker } = require('worker_threads');
|
||||
// make sure this is gone to ensure we don't do another fs read of it
|
||||
// will error out if we do
|
||||
require('fs').unlinkSync(${JSON.stringify(policyFilepath)});
|
||||
const w = new Worker(${JSON.stringify(parentFilepath)});
|
||||
w.on('exit', process.exit);
|
||||
`;
|
||||
|
||||
const depFilepath = path.join(tmpdir.path, 'dep.js');
|
||||
const depURL = pathToFileURL(depFilepath);
|
||||
const depBody = '';
|
||||
const policyToDepRelativeURLString = `./${
|
||||
path.relative(path.dirname(policyFilepath), depFilepath)
|
||||
}`;
|
||||
|
||||
fs.writeFileSync(parentFilepath, parentBody);
|
||||
fs.writeFileSync(depFilepath, depBody);
|
||||
|
||||
const tmpdirURL = pathToFileURL(tmpdir.path);
|
||||
if (!tmpdirURL.pathname.endsWith('/')) {
|
||||
tmpdirURL.pathname += '/';
|
||||
}
|
||||
function test({
|
||||
shouldFail = false,
|
||||
preload = [],
|
||||
entry,
|
||||
onerror = undefined,
|
||||
resources = {}
|
||||
}) {
|
||||
const manifest = {
|
||||
onerror,
|
||||
resources: {}
|
||||
};
|
||||
for (const [url, { body, match }] of Object.entries(resources)) {
|
||||
manifest.resources[url] = {
|
||||
integrity: `sha256-${hash('sha256', match ? body : body + '\n')}`,
|
||||
dependencies: true
|
||||
};
|
||||
fs.writeFileSync(new URL(url, tmpdirURL.href), body);
|
||||
}
|
||||
fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2));
|
||||
const { status } = spawnSync(process.execPath, [
|
||||
'--experimental-policy', policyFilepath,
|
||||
...preload.map((m) => ['-r', m]).flat(),
|
||||
entry
|
||||
]);
|
||||
if (shouldFail) {
|
||||
assert.notStrictEqual(status, 0);
|
||||
} else {
|
||||
assert.strictEqual(status, 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const { status } = spawnSync(process.execPath, [
|
||||
'--experimental-policy', policyFilepath,
|
||||
'--experimental-policy', policyFilepath
|
||||
], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
assert.notStrictEqual(status, 0, 'Should not allow multiple policies');
|
||||
}
|
||||
{
|
||||
const enoentFilepath = path.join(tmpdir.path, 'enoent');
|
||||
try { fs.unlinkSync(enoentFilepath); } catch {}
|
||||
const { status } = spawnSync(process.execPath, [
|
||||
'--experimental-policy', enoentFilepath, '-e', ''
|
||||
], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
assert.notStrictEqual(status, 0, 'Should not allow missing policies');
|
||||
}
|
||||
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
resources: {
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: parentFilepath,
|
||||
onerror: 'log',
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
onerror: 'exit',
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
onerror: 'throw',
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
onerror: 'unknown-onerror-value',
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: path.dirname(packageFilepath),
|
||||
resources: {
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: path.dirname(packageFilepath),
|
||||
resources: {
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: path.dirname(packageFilepath),
|
||||
onerror: 'log',
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: false,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: path.dirname(packageFilepath),
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: false,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: path.dirname(packageFilepath),
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: path.dirname(packageFilepath),
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: parentFilepath,
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
preload: [depFilepath],
|
||||
entry: parentFilepath,
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
resources: {
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: false,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
resources: {
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: parentFilepath,
|
||||
resources: {
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: depFilepath,
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: depFilepath,
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[policyToDepRelativeURLString]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: depFilepath,
|
||||
resources: {
|
||||
[policyToDepRelativeURLString]: {
|
||||
body: depBody,
|
||||
match: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: depFilepath,
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[policyToDepRelativeURLString]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: depFilepath,
|
||||
resources: {
|
||||
[policyToPackageRelativeURLString]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: true,
|
||||
entry: workerSpawningFilepath,
|
||||
resources: {
|
||||
[workerSpawningURL]: {
|
||||
body: workerSpawningBody,
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: workerSpawningFilepath,
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[workerSpawningURL]: {
|
||||
body: workerSpawningBody,
|
||||
match: true,
|
||||
},
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
test({
|
||||
shouldFail: false,
|
||||
entry: workerSpawningFilepath,
|
||||
preload: [parentFilepath],
|
||||
resources: {
|
||||
[packageURL]: {
|
||||
body: packageBody,
|
||||
match: true,
|
||||
},
|
||||
[workerSpawningURL]: {
|
||||
body: workerSpawningBody,
|
||||
match: true,
|
||||
},
|
||||
[parentURL]: {
|
||||
body: parentBody,
|
||||
match: true,
|
||||
},
|
||||
[depURL]: {
|
||||
body: depBody,
|
||||
match: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -19,24 +19,28 @@ function hash(algo, body) {
|
||||
return h.digest('base64');
|
||||
}
|
||||
|
||||
const policyFilepath = path.join(tmpdir.path, 'policy');
|
||||
const tmpdirPath = path.join(tmpdir.path, 'test-policy-parse-integrity');
|
||||
fs.rmdirSync(tmpdirPath, { maxRetries: 3, recursive: true });
|
||||
fs.mkdirSync(tmpdirPath, { recursive: true });
|
||||
|
||||
const parentFilepath = path.join(tmpdir.path, 'parent.js');
|
||||
const policyFilepath = path.join(tmpdirPath, 'policy');
|
||||
|
||||
const parentFilepath = path.join(tmpdirPath, 'parent.js');
|
||||
const parentBody = "require('./dep.js')";
|
||||
|
||||
const depFilepath = path.join(tmpdir.path, 'dep.js');
|
||||
const depFilepath = path.join(tmpdirPath, 'dep.js');
|
||||
const depURL = pathToFileURL(depFilepath);
|
||||
const depBody = '';
|
||||
|
||||
fs.writeFileSync(parentFilepath, parentBody);
|
||||
fs.writeFileSync(depFilepath, depBody);
|
||||
|
||||
const tmpdirURL = pathToFileURL(tmpdir.path);
|
||||
const tmpdirURL = pathToFileURL(tmpdirPath);
|
||||
if (!tmpdirURL.pathname.endsWith('/')) {
|
||||
tmpdirURL.pathname += '/';
|
||||
}
|
||||
|
||||
const packageFilepath = path.join(tmpdir.path, 'package.json');
|
||||
const packageFilepath = path.join(tmpdirPath, 'package.json');
|
||||
const packageURL = pathToFileURL(packageFilepath);
|
||||
const packageBody = '{"main": "dep.js"}';
|
||||
|
||||
|
||||
390
test/pummel/test-policy-integrity.js
Normal file
390
test/pummel/test-policy-integrity.js
Normal file
@@ -0,0 +1,390 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const { debuglog } = require('util');
|
||||
const debug = debuglog('test');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const assert = require('assert');
|
||||
const { spawnSync, spawn } = require('child_process');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
function hash(algo, body) {
|
||||
const values = [];
|
||||
{
|
||||
const h = crypto.createHash(algo);
|
||||
h.update(body);
|
||||
values.push(`${algo}-${h.digest('base64')}`);
|
||||
}
|
||||
{
|
||||
const h = crypto.createHash(algo);
|
||||
h.update(body.replace('\n', '\r\n'));
|
||||
values.push(`${algo}-${h.digest('base64')}`);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
const policyPath = './policy.json';
|
||||
const parentBody = {
|
||||
commonjs: `
|
||||
if (!process.env.DEP_FILE) {
|
||||
console.error(
|
||||
'missing required DEP_FILE env to determine dependency'
|
||||
);
|
||||
process.exit(33);
|
||||
}
|
||||
require(process.env.DEP_FILE)
|
||||
`,
|
||||
module: `
|
||||
if (!process.env.DEP_FILE) {
|
||||
console.error(
|
||||
'missing required DEP_FILE env to determine dependency'
|
||||
);
|
||||
process.exit(33);
|
||||
}
|
||||
import(process.env.DEP_FILE)
|
||||
`,
|
||||
};
|
||||
const workerSpawningBody = `
|
||||
const path = require('path');
|
||||
const { Worker } = require('worker_threads');
|
||||
if (!process.env.PARENT_FILE) {
|
||||
console.error(
|
||||
'missing required PARENT_FILE env to determine worker entry point'
|
||||
);
|
||||
process.exit(33);
|
||||
}
|
||||
if (!process.env.DELETABLE_POLICY_FILE) {
|
||||
console.error(
|
||||
'missing required DELETABLE_POLICY_FILE env to check reloading'
|
||||
);
|
||||
process.exit(33);
|
||||
}
|
||||
const w = new Worker(path.resolve(process.env.PARENT_FILE));
|
||||
w.on('exit', (status) => process.exit(status === 0 ? 0 : 1));
|
||||
`;
|
||||
|
||||
let nextTestId = 1;
|
||||
function newTestId() {
|
||||
return nextTestId++;
|
||||
}
|
||||
tmpdir.refresh();
|
||||
|
||||
let spawned = 0;
|
||||
const toSpawn = [];
|
||||
function queueSpawn(opts) {
|
||||
toSpawn.push(opts);
|
||||
drainQueue();
|
||||
}
|
||||
|
||||
function drainQueue() {
|
||||
if (spawned > 50) {
|
||||
return;
|
||||
}
|
||||
if (toSpawn.length) {
|
||||
const config = toSpawn.shift();
|
||||
const {
|
||||
shouldSucceed, // = (() => { throw new Error('required')})(),
|
||||
preloads, // = (() =>{ throw new Error('required')})(),
|
||||
entryPath, // = (() => { throw new Error('required')})(),
|
||||
willDeletePolicy, // = (() => { throw new Error('required')})(),
|
||||
onError, // = (() => { throw new Error('required')})(),
|
||||
resources, // = (() => { throw new Error('required')})(),
|
||||
parentPath,
|
||||
depPath,
|
||||
} = config;
|
||||
const testId = newTestId();
|
||||
const configDirPath = path.join(
|
||||
tmpdir.path,
|
||||
`test-policy-integrity-permutation-${testId}`
|
||||
);
|
||||
const tmpPolicyPath = path.join(
|
||||
tmpdir.path,
|
||||
`deletable-policy-${testId}.json`
|
||||
);
|
||||
const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath;
|
||||
fs.rmdirSync(configDirPath, { maxRetries: 3, recursive: true });
|
||||
fs.mkdirSync(configDirPath, { recursive: true });
|
||||
const manifest = {
|
||||
onerror: onError,
|
||||
resources: {},
|
||||
};
|
||||
const manifestPath = path.join(configDirPath, policyPath);
|
||||
for (const [resourcePath, { body, integrities }] of Object.entries(
|
||||
resources
|
||||
)) {
|
||||
const filePath = path.join(configDirPath, resourcePath);
|
||||
if (integrities !== null) {
|
||||
manifest.resources[pathToFileURL(filePath).href] = {
|
||||
integrity: integrities.join(' '),
|
||||
dependencies: true,
|
||||
};
|
||||
}
|
||||
fs.writeFileSync(filePath, body, 'utf8');
|
||||
}
|
||||
const manifestBody = JSON.stringify(manifest);
|
||||
fs.writeFileSync(manifestPath, manifestBody);
|
||||
if (cliPolicy === tmpPolicyPath) {
|
||||
fs.writeFileSync(tmpPolicyPath, manifestBody);
|
||||
}
|
||||
const spawnArgs = [
|
||||
process.execPath,
|
||||
[
|
||||
'--unhandled-rejections=strict',
|
||||
'--experimental-policy',
|
||||
cliPolicy,
|
||||
...preloads.flatMap((m) => ['-r', m]),
|
||||
entryPath,
|
||||
'--',
|
||||
testId,
|
||||
configDirPath,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
DELETABLE_POLICY_FILE: tmpPolicyPath,
|
||||
PARENT_FILE: parentPath,
|
||||
DEP_FILE: depPath,
|
||||
},
|
||||
cwd: configDirPath,
|
||||
stdio: 'pipe',
|
||||
},
|
||||
];
|
||||
spawned++;
|
||||
const stdout = [];
|
||||
const stderr = [];
|
||||
const child = spawn(...spawnArgs);
|
||||
child.stdout.on('data', (d) => stdout.push(d));
|
||||
child.stderr.on('data', (d) => stderr.push(d));
|
||||
child.on('exit', (status, signal) => {
|
||||
spawned--;
|
||||
try {
|
||||
if (shouldSucceed) {
|
||||
assert.strictEqual(status, 0);
|
||||
} else {
|
||||
assert.notStrictEqual(status, 0);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'permutation',
|
||||
testId,
|
||||
'failed'
|
||||
);
|
||||
console.dir(
|
||||
{ config, manifest },
|
||||
{ depth: null }
|
||||
);
|
||||
console.log('exit code:', status, 'signal:', signal);
|
||||
console.log(`stdout: ${Buffer.concat(stdout)}`);
|
||||
console.log(`stderr: ${Buffer.concat(stderr)}`);
|
||||
throw e;
|
||||
}
|
||||
fs.rmdirSync(configDirPath, { maxRetries: 3, recursive: true });
|
||||
drainQueue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const { status } = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-policy', policyPath, '--experimental-policy', policyPath],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
}
|
||||
);
|
||||
assert.notStrictEqual(status, 0, 'Should not allow multiple policies');
|
||||
}
|
||||
{
|
||||
const enoentFilepath = path.join(tmpdir.path, 'enoent');
|
||||
try {
|
||||
fs.unlinkSync(enoentFilepath);
|
||||
} catch { }
|
||||
const { status } = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-policy', enoentFilepath, '-e', ''],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
}
|
||||
);
|
||||
assert.notStrictEqual(status, 0, 'Should not allow missing policies');
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Record<string, Array<string | string[] | boolean>>} T
|
||||
* @param {T} configurations
|
||||
* @param {object} path
|
||||
* @returns {Array<{[key: keyof T]: T[keyof configurations]}>}
|
||||
*/
|
||||
function permutations(configurations, path = {}) {
|
||||
const keys = Object.keys(configurations);
|
||||
if (keys.length === 0) {
|
||||
return path;
|
||||
}
|
||||
const config = keys[0];
|
||||
const { [config]: values, ...otherConfigs } = configurations;
|
||||
return values.flatMap((value) => {
|
||||
return permutations(otherConfigs, { ...path, [config]: value });
|
||||
});
|
||||
}
|
||||
const tests = new Set();
|
||||
function fileExtensionFormat(extension, packageType) {
|
||||
if (extension === '.js') {
|
||||
return packageType === 'module' ? 'module' : 'commonjs';
|
||||
} else if (extension === '.mjs') {
|
||||
return 'module';
|
||||
} else if (extension === '.cjs') {
|
||||
return 'commonjs';
|
||||
}
|
||||
throw new Error('unknown format ' + extension);
|
||||
}
|
||||
for (const permutation of permutations({
|
||||
entry: ['worker', 'parent', 'dep'],
|
||||
preloads: [[], ['parent'], ['dep']],
|
||||
onError: ['log', 'exit'],
|
||||
parentExtension: ['.js', '.mjs', '.cjs'],
|
||||
parentIntegrity: ['match', 'invalid', 'missing'],
|
||||
depExtension: ['.js', '.mjs', '.cjs'],
|
||||
depIntegrity: ['match', 'invalid', 'missing'],
|
||||
packageType: ['no-package-json', 'module', 'commonjs'],
|
||||
packageIntegrity: ['match', 'invalid', 'missing'],
|
||||
})) {
|
||||
let shouldSucceed = true;
|
||||
const parentPath = `./parent${permutation.parentExtension}`;
|
||||
const effectivePackageType =
|
||||
permutation.packageType === 'module' ? 'module' : 'commonjs';
|
||||
const parentFormat = fileExtensionFormat(
|
||||
permutation.parentExtension,
|
||||
effectivePackageType
|
||||
);
|
||||
const depFormat = fileExtensionFormat(
|
||||
permutation.depExtension,
|
||||
effectivePackageType
|
||||
);
|
||||
// non-sensical attempt to require ESM
|
||||
if (depFormat === 'module' && parentFormat === 'commonjs') {
|
||||
continue;
|
||||
}
|
||||
const depPath = `./dep${permutation.depExtension}`;
|
||||
const workerSpawnerPath = './worker-spawner.cjs';
|
||||
const entryPath = {
|
||||
dep: depPath,
|
||||
parent: parentPath,
|
||||
worker: workerSpawnerPath,
|
||||
}[permutation.entry];
|
||||
const packageJSON = {
|
||||
main: entryPath,
|
||||
type: permutation.packageType,
|
||||
};
|
||||
if (permutation.packageType === 'no-field') {
|
||||
delete packageJSON.type;
|
||||
}
|
||||
const resources = {
|
||||
[depPath]: {
|
||||
body: '',
|
||||
integrities: hash('sha256', ''),
|
||||
},
|
||||
};
|
||||
if (permutation.depIntegrity === 'invalid') {
|
||||
resources[depPath].body += '\n// INVALID INTEGRITY';
|
||||
shouldSucceed = false;
|
||||
} else if (permutation.depIntegrity === 'missing') {
|
||||
resources[depPath].integrities = null;
|
||||
shouldSucceed = false;
|
||||
} else if (permutation.depIntegrity === 'match') {
|
||||
} else {
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
if (parentFormat !== 'commonjs') {
|
||||
permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent');
|
||||
}
|
||||
const hasParent =
|
||||
permutation.entry !== 'dep' || permutation.preloads.includes('parent');
|
||||
if (hasParent) {
|
||||
resources[parentPath] = {
|
||||
body: parentBody[parentFormat],
|
||||
integrities: hash('sha256', parentBody[parentFormat]),
|
||||
};
|
||||
if (permutation.parentIntegrity === 'invalid') {
|
||||
resources[parentPath].body += '\n// INVALID INTEGRITY';
|
||||
shouldSucceed = false;
|
||||
} else if (permutation.parentIntegrity === 'missing') {
|
||||
resources[parentPath].integrities = null;
|
||||
shouldSucceed = false;
|
||||
} else if (permutation.parentIntegrity === 'match') {
|
||||
} else {
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
}
|
||||
if (permutation.entry === 'worker') {
|
||||
resources[workerSpawnerPath] = {
|
||||
body: workerSpawningBody,
|
||||
integrities: hash('sha256', workerSpawningBody),
|
||||
};
|
||||
}
|
||||
if (permutation.packageType !== 'no-package-json') {
|
||||
let packageBody = JSON.stringify(packageJSON, null, 2);
|
||||
let packageIntegrities = hash('sha256', packageBody);
|
||||
if (
|
||||
permutation.parentExtension !== '.js' ||
|
||||
permutation.depExtension !== '.js'
|
||||
) {
|
||||
// NO PACKAGE LOOKUP
|
||||
continue;
|
||||
}
|
||||
if (permutation.packageIntegrity === 'invalid') {
|
||||
packageJSON['//'] = 'INVALID INTEGRITY';
|
||||
packageBody = JSON.stringify(packageJSON, null, 2);
|
||||
shouldSucceed = false;
|
||||
} else if (permutation.packageIntegrity === 'missing') {
|
||||
packageIntegrities = [];
|
||||
shouldSucceed = false;
|
||||
} else if (permutation.packageIntegrity === 'match') {
|
||||
} else {
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
resources['./package.json'] = {
|
||||
body: packageBody,
|
||||
integrities: packageIntegrities,
|
||||
};
|
||||
}
|
||||
const willDeletePolicy = permutation.entry === 'worker';
|
||||
if (permutation.onError === 'log') {
|
||||
shouldSucceed = true;
|
||||
}
|
||||
tests.add(
|
||||
JSON.stringify({
|
||||
// hasParent,
|
||||
// original: permutation,
|
||||
onError: permutation.onError,
|
||||
shouldSucceed,
|
||||
entryPath,
|
||||
willDeletePolicy,
|
||||
preloads: permutation.preloads
|
||||
.map((_) => {
|
||||
return {
|
||||
'': '',
|
||||
'parent': parentFormat === 'commonjs' ? parentPath : '',
|
||||
'dep': depFormat === 'commonjs' ? depPath : '',
|
||||
}[_];
|
||||
})
|
||||
.filter(Boolean),
|
||||
parentPath,
|
||||
depPath,
|
||||
resources,
|
||||
})
|
||||
);
|
||||
}
|
||||
debug(`spawning ${tests.size} policy integrity permutations`);
|
||||
debug(
|
||||
'use NODE_DEBUG=test:policy-integrity:NUMBER to log a specific permutation'
|
||||
);
|
||||
for (const config of tests) {
|
||||
const parsed = JSON.parse(config);
|
||||
tests.delete(config);
|
||||
queueSpawn(parsed);
|
||||
}
|
||||
Reference in New Issue
Block a user