Skip to content

Commit

Permalink
test: enhance the test framework
Browse files Browse the repository at this point in the history
- allow subset of tests to be run more easily
  • Loading branch information
Deepak Rajamohan authored and mhdawson committed Jan 31, 2022
1 parent 133e13d commit 744c8d2
Show file tree
Hide file tree
Showing 15 changed files with 616 additions and 43 deletions.
47 changes: 23 additions & 24 deletions test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const noop = () => {};

const mustCallChecks = [];

function runCallChecks(exitCode) {
function runCallChecks (exitCode) {
if (exitCode !== 0) return;

const failed = mustCallChecks.filter(function(context) {
const failed = mustCallChecks.filter(function (context) {
if ('minimum' in context) {
context.messageSegment = `at least ${context.minimum}`;
return context.actual < context.minimum;
Expand All @@ -20,25 +20,25 @@ function runCallChecks(exitCode) {
}
});

failed.forEach(function(context) {
failed.forEach(function (context) {
console.log('Mismatched %s function calls. Expected %s, actual %d.',
context.name,
context.messageSegment,
context.actual);
context.name,
context.messageSegment,
context.actual);
console.log(context.stack.split('\n').slice(2).join('\n'));
});

if (failed.length) process.exit(1);
}

exports.mustCall = function(fn, exact) {
exports.mustCall = function (fn, exact) {
return _mustCallInner(fn, exact, 'exact');
};
exports.mustCallAtLeast = function(fn, minimum) {
exports.mustCallAtLeast = function (fn, minimum) {
return _mustCallInner(fn, minimum, 'minimum');
};

function _mustCallInner(fn, criteria, field) {
function _mustCallInner (fn, criteria, field) {
if (typeof fn === 'number') {
criteria = fn;
fn = noop;
Expand All @@ -49,8 +49,7 @@ function _mustCallInner(fn, criteria, field) {
criteria = 1;
}

if (typeof criteria !== 'number')
throw new TypeError(`Invalid ${field} value: ${criteria}`);
if (typeof criteria !== 'number') { throw new TypeError(`Invalid ${field} value: ${criteria}`); }

const context = {
[field]: criteria,
Expand All @@ -64,50 +63,50 @@ function _mustCallInner(fn, criteria, field) {

mustCallChecks.push(context);

return function() {
return function () {
context.actual++;
return fn.apply(this, arguments);
};
}

exports.mustNotCall = function(msg) {
return function mustNotCall() {
exports.mustNotCall = function (msg) {
return function mustNotCall () {
assert.fail(msg || 'function should not have been called');
};
};

exports.runTest = async function(test, buildType, buildPathRoot = process.env.REL_BUILD_PATH || '') {
exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') {
buildType = buildType || process.config.target_defaults.default_configuration || 'Release';

const bindings = [
path.join(buildPathRoot, `../build/${buildType}/binding.node`),
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`),
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`),
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`)
].map(it => require.resolve(it));

for (const item of bindings) {
await Promise.resolve(test(require(item)))
.finally(exports.mustCall());
}
}
};

exports.runTestWithBindingPath = async function(test, buildType, buildPathRoot = process.env.REL_BUILD_PATH || '') {
exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') {
buildType = buildType || process.config.target_defaults.default_configuration || 'Release';

const bindings = [
path.join(buildPathRoot, `../build/${buildType}/binding.node`),
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`),
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`),
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`)
].map(it => require.resolve(it));

for (const item of bindings) {
await test(item);
}
}
};

exports.runTestWithBuildType = async function(test, buildType) {
exports.runTestWithBuildType = async function (test, buildType) {
buildType = buildType || process.config.target_defaults.default_configuration || 'Release';

await Promise.resolve(test(buildType))
.finally(exports.mustCall());
}
await Promise.resolve(test(buildType))
.finally(exports.mustCall());
};
57 changes: 40 additions & 17 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const majorNodeVersion = process.versions.node.split('.')[0];

if (typeof global.gc !== 'function') {
// Construct the correct (version-dependent) command-line args.
let args = ['--expose-gc'];
const args = ['--expose-gc'];
const majorV8Version = process.versions.v8.split('.')[0];
if (majorV8Version < 9) {
args.push('--no-concurrent-array-buffer-freeing');
Expand All @@ -15,7 +15,7 @@ if (typeof global.gc !== 'function') {
args.push(__filename);

const child = require('./napi_child').spawnSync(process.argv[0], args, {
stdio: 'inherit',
stdio: 'inherit'
});

if (child.signal) {
Expand All @@ -27,17 +27,36 @@ if (typeof global.gc !== 'function') {
process.exit(process.exitCode);
}

const testModules = [];

const fs = require('fs');
const path = require('path');

let testModules = [];
let filterCondition = process.env.npm_config_filter || '';
let filterConditionFiles = [];

if (filterCondition !== '') {
filterCondition = require('../unit-test/matchModules').matchWildCards(process.env.npm_config_filter);
filterConditionFiles = filterCondition.split(' ').length > 0 ? filterCondition.split(' ') : [filterCondition];
}

const filterConditionsProvided = filterConditionFiles.length > 0;

function checkFilterCondition (fileName, parsedFilepath) {
let result = false;

if (!filterConditionsProvided) return true;
if (filterConditionFiles.includes(parsedFilepath)) result = true;
if (filterConditionFiles.includes(fileName)) result = true;
return result;
}

// TODO(RaisinTen): Update this when the test filenames
// are changed into test_*.js.
function loadTestModules(currentDirectory = __dirname, pre = '') {
function loadTestModules (currentDirectory = __dirname, pre = '') {
fs.readdirSync(currentDirectory).forEach((file) => {
if (currentDirectory === __dirname && (
file === 'binding.cc' ||
file === 'binding.cc' ||
file === 'binding.gyp' ||
file === 'build' ||
file === 'common' ||
Expand All @@ -50,15 +69,19 @@ function loadTestModules(currentDirectory = __dirname, pre = '') {
return;
}
const absoluteFilepath = path.join(currentDirectory, file);
const parsedFilepath = path.parse(file);
const parsedPath = path.parse(currentDirectory);

if (fs.statSync(absoluteFilepath).isDirectory()) {
if (fs.existsSync(absoluteFilepath + '/index.js')) {
testModules.push(pre + file);
if (checkFilterCondition(parsedFilepath.name, parsedPath.base)) {
testModules.push(pre + file);
}
} else {
loadTestModules(absoluteFilepath, pre + file + '/');
}
} else {
const parsedFilepath = path.parse(file);
if (parsedFilepath.ext === '.js') {
if (parsedFilepath.ext === '.js' && checkFilterCondition(parsedFilepath.name, parsedPath.base)) {
testModules.push(pre + parsedFilepath.name);
}
}
Expand All @@ -69,7 +92,7 @@ loadTestModules();

process.config.target_defaults.default_configuration =
fs
.readdirSync(path.join(__dirname, 'build'))
.readdirSync(path.join(__dirname, process.env.REL_BUILD_PATH || '', 'build'))
.filter((item) => (item === 'Debug' || item === 'Release'))[0];

let napiVersion = Number(process.versions.napi);
Expand All @@ -87,7 +110,7 @@ if (napiVersion < 3) {
testModules.splice(testModules.indexOf('version_management'), 1);
}

if (napiVersion < 4) {
if (napiVersion < 4 && !filterConditionsProvided) {
testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1);
testModules.splice(testModules.indexOf('asyncprogressworker'), 1);
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1);
Expand All @@ -98,36 +121,36 @@ if (napiVersion < 4) {
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1);
}

if (napiVersion < 5) {
if (napiVersion < 5 && !filterConditionsProvided) {
testModules.splice(testModules.indexOf('date'), 1);
}

if (napiVersion < 6) {
if (napiVersion < 6 && !filterConditionsProvided) {
testModules.splice(testModules.indexOf('addon'), 1);
testModules.splice(testModules.indexOf('addon_data'), 1);
testModules.splice(testModules.indexOf('bigint'), 1);
testModules.splice(testModules.indexOf('typedarray-bigint'), 1);
}

if (majorNodeVersion < 12) {
if (majorNodeVersion < 12 && !filterConditionsProvided) {
testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1);
testModules.splice(testModules.indexOf('error_terminating_environment'), 1);
}

if (napiVersion < 8) {
if (napiVersion < 8 && !filterConditionsProvided) {
testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1);
}

(async function() {
(async function () {
console.log(`Testing with Node-API Version '${napiVersion}'.`);

console.log('Starting test suite\n');
if (filterConditionsProvided) { console.log('Starting test suite\n', testModules); } else { console.log('Starting test suite\n'); }

// Requiring each module runs tests in the module.
for (const name of testModules) {
console.log(`Running test '${name}'`);
await require('./' + name);
};
}

console.log('\nAll tests passed!');
})().catch((error) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ const assert = require('assert');

module.exports = require('../common').runTest(test);

async function test(binding) {
async function test (binding) {
const ctx = { };
const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx);
const tsfn = new binding.typed_threadsafe_function_ctx.TSFNWrap(ctx);
assert(tsfn.getContext() === ctx);
await tsfn.release();
}
3 changes: 3 additions & 0 deletions unit-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/node_modules
/build
/generated
28 changes: 28 additions & 0 deletions unit-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

# Enable running tests with specific filter conditions:

### Example:

- compile ad run only tests on objectwrap.cc and objectwrap.js
```
npm run unit --filter=objectwrap
```


# Wildcards are also possible:

### Example:

- compile and run all tests files ending with reference -> function_reference.cc object_reference.cc reference.cc
```
npm run unit --filter=*reference
```

# Multiple filter conditions are also allowed

### Example:

- compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file
```
npm run unit --filter='*function objectwrap'
```
39 changes: 39 additions & 0 deletions unit-test/binding-file-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const path = require('path');
const fs = require('fs');

/**
* @param bindingConfigurations
* This method acts as a template to generate the content of binding.cc file
*/
module.exports.generateFileContent = function (bindingConfigurations) {
const content = [];
const inits = [];
const exports = [];

for (const config of bindingConfigurations) {
inits.push(`Object Init${config.objectName}(Env env);`);
exports.push(`exports.Set("${config.propertyName}", Init${config.objectName}(env));`);
}

content.push('#include "napi.h"');
content.push('using namespace Napi;');

inits.forEach(init => content.push(init));

content.push('Object Init(Env env, Object exports) {');

exports.forEach(exp => content.push(exp));

content.push('return exports;');
content.push('}');
content.push('NODE_API_MODULE(addon, Init);');

return Promise.resolve(content.join('\r\n'));
};

module.exports.writeToBindingFile = function writeToBindingFile (content) {
const generatedFilePath = path.join(__dirname, 'generated', 'binding.cc');
fs.writeFileSync(generatedFilePath, '');
fs.writeFileSync(generatedFilePath, content, { flag: 'a' });
console.log('generated binding file ', generatedFilePath, new Date());
};
Loading

0 comments on commit 744c8d2

Please sign in to comment.