Skip to content

Commit

Permalink
module: check .node_preloadrc for preload modules
Browse files Browse the repository at this point in the history
Adds a new feature that checks .node_preloadrc for a list of modules
to preload.  One module per line and each one can be the absolute
path or just the name of the module.

Two locations will be check for the rc file: HOMEDIR or CWD.
The one in HOMEDIR must specify modules under the global node_modules
dir only, and the one in CWD must specify modules under the dir
CWD/node_modules only.

This is smilar to what can be specified by the -r command line
option except the modules are limited to the global or local
node_modules only.

This feature must be opted-in with setting the env var
NODE_PRELOADRC_ENABLE to a non-falsy value.

Implements: nodejs#11853
  • Loading branch information
jchip committed Mar 18, 2017
1 parent 5bda5fa commit 863e027
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 2 deletions.
45 changes: 43 additions & 2 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,49 @@

// Load preload modules
function preloadModules() {
if (process._preload_modules) {
NativeModule.require('module')._preloadModules(process._preload_modules);
var allPreloads = process._preload_modules;
var isSetUidRoot = false;

if (process.platform !== 'win32') {
isSetUidRoot = process.getuid() !== process.geteuid() ||
process.getgid() !== process.getegid();
}

if (!isSetUidRoot && process.env.NODE_PRELOADRC_ENABLE) {
const path = NativeModule.require('path');
const os = NativeModule.require('os');
const fs = NativeModule.require('fs');

const addRcPreloads = (nmDir, preloads) => {
const addNmDir = (m) => {
return path.isAbsolute(m) ? m : path.join(nmDir, m);
};

if (preloads && preloads.length > 0) {
preloads = preloads.map((m) => addNmDir(m.trim()))
.filter((m) => (m.length > nmDir.length && m.startsWith(nmDir)));

if (preloads.length > 0) {
allPreloads = (allPreloads || []).concat(preloads);
}
}
};

const readRc = (dir) => {
const filename = path.join(dir, '.node_preloadrc');
return fs.existsSync(filename) &&
fs.readFileSync(filename).toString().trim().split('\n');
};

addRcPreloads(
path.join(process.argv[0], '../../lib/node_modules/'),
readRc(os.homedir()));
addRcPreloads(
path.join(process.cwd(), 'node_modules/'), readRc(process.cwd()));
}

if (allPreloads) {
NativeModule.require('module')._preloadModules(allPreloads);
}
}

Expand Down
175 changes: 175 additions & 0 deletions test/parallel/test-preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const common = require('../common');
const assert = require('assert');
const path = require('path');
const childProcess = require('child_process');
const fs = require('fs');
const os = require('os');

// Refs: https://github.com/nodejs/node/pull/2253
if (common.isSunOS) {
Expand All @@ -12,6 +14,8 @@ if (common.isSunOS) {
}

const nodeBinary = process.argv[0];
const globalLibDir = path.join(nodeBinary, '../../lib');
const globalNmDir = path.join(globalLibDir, 'node_modules');

const preloadOption = function(preloads) {
let option = '';
Expand All @@ -25,12 +29,21 @@ const fixture = function(name) {
return path.join(common.fixturesDir, name);
};

const globalNmFixture = function(name) {
return path.join(globalNmDir, name);
};

const fixtureA = fixture('printA.js');
const fixtureB = fixture('printB.js');
const fixtureC = fixture('printC.js');
const fixtureD = fixture('define-global.js');
const fixtureThrows = fixture('throws_error4.js');

const globalNmFixtureA = globalNmFixture('printA.js');
const globalNmFixtureB = globalNmFixture('printB.js');
const globalNmFixtureC = globalNmFixture('printC.js');
const globalNmFixtureThrows = globalNmFixture('throws_error4.js');

// test preloading a single module works
childProcess.exec(nodeBinary + ' ' + preloadOption([fixtureA]) + ' ' + fixtureB,
function(err, stdout, stderr) {
Expand Down Expand Up @@ -146,3 +159,165 @@ childProcess.exec(
assert.ok(/worker terminated with code 43/.test(stdout));
}
);

//
// Test preloadrc
//
let preloadTestsCount = 0;
let savedRc;

const copyFixturesToGlobalNm = function() {
try {
fs.mkdirSync(globalLibDir);
fs.mkdirSync(globalNmDir);
} catch (e) {
}
fs.writeFileSync(globalNmFixtureA, fs.readFileSync(fixtureA));
fs.writeFileSync(globalNmFixtureB, fs.readFileSync(fixtureB));
fs.writeFileSync(globalNmFixtureC, fs.readFileSync(fixtureC));
fs.writeFileSync(globalNmFixtureThrows, fs.readFileSync(fixtureThrows));
};

const cleanGlobalNmFixtures = function() {
preloadTestsCount--;
if (preloadTestsCount > 0) {
return;
}

fs.unlinkSync(globalNmFixtureA);
fs.unlinkSync(globalNmFixtureB);
fs.unlinkSync(globalNmFixtureC);
fs.unlinkSync(globalNmFixtureThrows);

try {
fs.rmdirSync(globalNmDir);
fs.rmdirSync(globalLibDir);
} catch (e) {
}
};

const rcFile = path.join(os.homedir(), '.node_preloadrc');

const restorePreloadRc = function() {
if (savedRc) {
fs.writeFileSync(rcFile, savedRc);
savedRc = undefined;
} else {
fs.unlinkSync(rcFile);
}
};

const updatePreloadRc = function(modules) {
if (fs.existsSync(rcFile)) {
savedRc = fs.readFileSync(rcFile);
}
const rc = modules.reduce((a, m) => `${a}${m}\n`, '');
fs.writeFileSync(rcFile, rc);
};

// test preloadrc with a single module works
const nodePreloadTests = [
function(cleanCb, next) {
updatePreloadRc([globalNmFixtureA]);
childProcess.exec(nodeBinary + ' ' + fixtureB,
function(err, stdout, stderr) {
cleanCb();
assert.ifError(err);
assert.strictEqual(stdout, 'A\nB\n');
next();
});
},

// test preloadrc with a relative module works
function(cleanCb, next) {
updatePreloadRc(['printA']);
childProcess.exec(nodeBinary + ' ' + fixtureB,
function(err, stdout, stderr) {
cleanCb();
assert.ifError(err);
assert.strictEqual(stdout, 'A\nB\n');
next();
});
},

// test preloadrc with multiple modules works
function(cleanCb, next) {
updatePreloadRc([globalNmFixtureA, globalNmFixtureB]);
childProcess.exec(
nodeBinary + ' ' + fixtureC,
function(err, stdout, stderr) {
cleanCb();
assert.ifError(err);
assert.strictEqual(stdout, 'A\nB\nC\n');
next();
}
);
},

// test preloadrc with a throwing module aborts
function(cleanCb, next) {
updatePreloadRc([globalNmFixtureA, globalNmFixtureThrows]);
childProcess.exec(
nodeBinary + ' ' + fixtureB,
function(err, stdout, stderr) {
cleanCb();
if (err) {
assert.strictEqual(stdout, 'A\n');
next();
} else {
throw new Error('preloadrc Preload should have failed');
}
}
);
},

// test mixing preloadrc with -r works
function(cleanCb, next) {
updatePreloadRc([globalNmFixtureB]);
childProcess.exec(
nodeBinary + ' ' + preloadOption([fixtureA]) + ' ' + fixtureC,
function(err, stdout, stderr) {
cleanCb();
assert.ifError(err);
assert.strictEqual(stdout, 'A\nB\nC\n');
next();
}
);
},

// test ignoring preloadrc modules not under global node_modules works
function(cleanCb, next) {
updatePreloadRc([globalNmFixtureA, fixtureB]);
childProcess.exec(
nodeBinary + ' ' + fixtureC,
function(err, stdout, stderr) {
cleanCb();
assert.ifError(err);
assert.strictEqual(stdout, 'A\nC\n');
next();
}
);
}
];


const runPreloadTest = function() {
let i = 0;
copyFixturesToGlobalNm();
const runNextTest = function() {
if (i >= nodePreloadTests.length) {
cleanGlobalNmFixtures();
delete process.env.NODE_PRELOADRC_ENABLE;
} else {
process.env.NODE_PRELOADRC_ENABLE = 'true';
nodePreloadTests[i](restorePreloadRc, () => {
i++;
runNextTest();
});
}
};

runNextTest();
};

runPreloadTest();

0 comments on commit 863e027

Please sign in to comment.