mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
73d53fe9f5
Detect the Raspberry Pi devices in the Node.js CI and only skip the slow tests on those instead of all armv7l devices. PR-URL: https://github.com/nodejs/node/pull/42645 Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Stewart X Addison <sxa@redhat.com> Reviewed-By: Darshan Sen <raisinten@gmail.com>
353 lines
9.2 KiB
JavaScript
353 lines
9.2 KiB
JavaScript
'use strict';
|
|
|
|
const common = require('../common');
|
|
|
|
if (!common.hasCrypto) {
|
|
common.skip('missing crypto');
|
|
}
|
|
|
|
if (common.isPi) {
|
|
common.skip('Too slow for Raspberry Pi devices');
|
|
}
|
|
|
|
common.requireNoPackageJSONAbove();
|
|
|
|
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');
|
|
|
|
const cpus = require('os').cpus().length;
|
|
|
|
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)
|
|
`,
|
|
};
|
|
|
|
let nextTestId = 1;
|
|
function newTestId() {
|
|
return nextTestId++;
|
|
}
|
|
tmpdir.refresh();
|
|
common.requireNoPackageJSONAbove(tmpdir.path);
|
|
|
|
let spawned = 0;
|
|
const toSpawn = [];
|
|
function queueSpawn(opts) {
|
|
toSpawn.push(opts);
|
|
drainQueue();
|
|
}
|
|
|
|
function drainQueue() {
|
|
if (spawned > cpus) {
|
|
return;
|
|
}
|
|
if (toSpawn.length) {
|
|
const config = toSpawn.shift();
|
|
const {
|
|
shouldSucceed,
|
|
preloads,
|
|
entryPath,
|
|
willDeletePolicy,
|
|
onError,
|
|
resources,
|
|
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.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: 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.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: 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 {
|
|
// Continue regardless of error.
|
|
}
|
|
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) {
|
|
if (extension === '.js') {
|
|
return 'module';
|
|
} else if (extension === '.mjs') {
|
|
return 'module';
|
|
} else if (extension === '.cjs') {
|
|
return 'commonjs';
|
|
}
|
|
throw new Error('unknown format ' + extension);
|
|
}
|
|
for (const permutation of permutations({
|
|
preloads: [[], ['parent'], ['dep']],
|
|
onError: ['log', 'exit'],
|
|
parentExtension: ['.js', '.mjs', '.cjs'],
|
|
parentIntegrity: ['match', 'invalid', 'missing'],
|
|
depExtension: ['.js', '.mjs', '.cjs'],
|
|
depIntegrity: ['match', 'invalid', 'missing'],
|
|
packageIntegrity: ['match', 'invalid', 'missing'],
|
|
})) {
|
|
let shouldSucceed = true;
|
|
const parentPath = `./parent${permutation.parentExtension}`;
|
|
const parentFormat = fileExtensionFormat(permutation.parentExtension);
|
|
const depFormat = fileExtensionFormat(permutation.depExtension);
|
|
|
|
// non-sensical attempt to require ESM
|
|
if (depFormat === 'module' && parentFormat === 'commonjs') {
|
|
continue;
|
|
}
|
|
const depPath = `./dep${permutation.depExtension}`;
|
|
const entryPath = parentPath;
|
|
const packageJSON = {
|
|
main: entryPath,
|
|
type: 'module',
|
|
};
|
|
|
|
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') {
|
|
throw new Error('unreachable');
|
|
}
|
|
if (parentFormat !== 'commonjs') {
|
|
permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent');
|
|
}
|
|
|
|
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') {
|
|
throw new Error('unreachable');
|
|
}
|
|
|
|
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') {
|
|
throw new Error('unreachable');
|
|
}
|
|
resources['./package.json'] = {
|
|
body: packageBody,
|
|
integrities: packageIntegrities,
|
|
};
|
|
|
|
if (permutation.onError === 'log') {
|
|
shouldSucceed = true;
|
|
}
|
|
tests.add(
|
|
JSON.stringify({
|
|
onError: permutation.onError,
|
|
shouldSucceed,
|
|
entryPath,
|
|
willDeletePolicy: false,
|
|
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`);
|
|
|
|
for (const config of tests) {
|
|
const parsed = JSON.parse(config);
|
|
queueSpawn(parsed);
|
|
}
|