diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 83a49599469b19..01803211fe0223 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -262,18 +262,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 = { diff --git a/lib/internal/modules/esm/get_source.js b/lib/internal/modules/esm/get_source.js index 7b2a2f42eabed9..5e2cc3e09e7687 100644 --- a/lib/internal/modules/esm/get_source.js +++ b/lib/internal/modules/esm/get_source.js @@ -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']); + } + if (manifest) { + manifest.assertIntegrity(parsed, source); } - throw new ERR_INVALID_URL_SCHEME(['file', 'data']); + return { source }; } exports.defaultGetSource = defaultGetSource; diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 066047b55eb9d8..25edfee027c35b 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -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; } diff --git a/lib/internal/worker.js b/lib/internal/worker.js index a0608277158735..b62cc9382f7aea 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -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, diff --git a/test/parallel/test-policy-integrity.js b/test/parallel/test-policy-integrity.js deleted file mode 100644 index 2cc1f1280c8059..00000000000000 --- a/test/parallel/test-policy-integrity.js +++ /dev/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, - } - } -}); diff --git a/test/parallel/test-policy-parse-integrity.js b/test/parallel/test-policy-parse-integrity.js index 6fa95416390693..2443d9691c2a51 100644 --- a/test/parallel/test-policy-parse-integrity.js +++ b/test/parallel/test-policy-parse-integrity.js @@ -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"}'; diff --git a/test/pummel/test-policy-integrity.js b/test/pummel/test-policy-integrity.js new file mode 100644 index 00000000000000..998d1d43355c5d --- /dev/null +++ b/test/pummel/test-policy-integrity.js @@ -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>} 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); +}