Skip to content

Commit 744c8d2

Browse files
Deepak Rajamohanmhdawson
Deepak Rajamohan
authored andcommitted
test: enhance the test framework
- allow subset of tests to be run more easily
1 parent 133e13d commit 744c8d2

15 files changed

+616
-43
lines changed

test/common/index.js

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ const noop = () => {};
77

88
const mustCallChecks = [];
99

10-
function runCallChecks(exitCode) {
10+
function runCallChecks (exitCode) {
1111
if (exitCode !== 0) return;
1212

13-
const failed = mustCallChecks.filter(function(context) {
13+
const failed = mustCallChecks.filter(function (context) {
1414
if ('minimum' in context) {
1515
context.messageSegment = `at least ${context.minimum}`;
1616
return context.actual < context.minimum;
@@ -20,25 +20,25 @@ function runCallChecks(exitCode) {
2020
}
2121
});
2222

23-
failed.forEach(function(context) {
23+
failed.forEach(function (context) {
2424
console.log('Mismatched %s function calls. Expected %s, actual %d.',
25-
context.name,
26-
context.messageSegment,
27-
context.actual);
25+
context.name,
26+
context.messageSegment,
27+
context.actual);
2828
console.log(context.stack.split('\n').slice(2).join('\n'));
2929
});
3030

3131
if (failed.length) process.exit(1);
3232
}
3333

34-
exports.mustCall = function(fn, exact) {
34+
exports.mustCall = function (fn, exact) {
3535
return _mustCallInner(fn, exact, 'exact');
3636
};
37-
exports.mustCallAtLeast = function(fn, minimum) {
37+
exports.mustCallAtLeast = function (fn, minimum) {
3838
return _mustCallInner(fn, minimum, 'minimum');
3939
};
4040

41-
function _mustCallInner(fn, criteria, field) {
41+
function _mustCallInner (fn, criteria, field) {
4242
if (typeof fn === 'number') {
4343
criteria = fn;
4444
fn = noop;
@@ -49,8 +49,7 @@ function _mustCallInner(fn, criteria, field) {
4949
criteria = 1;
5050
}
5151

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

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

6564
mustCallChecks.push(context);
6665

67-
return function() {
66+
return function () {
6867
context.actual++;
6968
return fn.apply(this, arguments);
7069
};
7170
}
7271

73-
exports.mustNotCall = function(msg) {
74-
return function mustNotCall() {
72+
exports.mustNotCall = function (msg) {
73+
return function mustNotCall () {
7574
assert.fail(msg || 'function should not have been called');
7675
};
7776
};
7877

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

8281
const bindings = [
8382
path.join(buildPathRoot, `../build/${buildType}/binding.node`),
8483
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`),
85-
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`),
84+
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`)
8685
].map(it => require.resolve(it));
8786

8887
for (const item of bindings) {
8988
await Promise.resolve(test(require(item)))
9089
.finally(exports.mustCall());
9190
}
92-
}
91+
};
9392

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

9796
const bindings = [
9897
path.join(buildPathRoot, `../build/${buildType}/binding.node`),
9998
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`),
100-
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`),
99+
path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`)
101100
].map(it => require.resolve(it));
102101

103102
for (const item of bindings) {
104103
await test(item);
105104
}
106-
}
105+
};
107106

108-
exports.runTestWithBuildType = async function(test, buildType) {
107+
exports.runTestWithBuildType = async function (test, buildType) {
109108
buildType = buildType || process.config.target_defaults.default_configuration || 'Release';
110109

111-
await Promise.resolve(test(buildType))
112-
.finally(exports.mustCall());
113-
}
110+
await Promise.resolve(test(buildType))
111+
.finally(exports.mustCall());
112+
};

test/index.js

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const majorNodeVersion = process.versions.node.split('.')[0];
44

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

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

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

30+
const testModules = [];
31+
3032
const fs = require('fs');
3133
const path = require('path');
3234

33-
let testModules = [];
35+
let filterCondition = process.env.npm_config_filter || '';
36+
let filterConditionFiles = [];
37+
38+
if (filterCondition !== '') {
39+
filterCondition = require('../unit-test/matchModules').matchWildCards(process.env.npm_config_filter);
40+
filterConditionFiles = filterCondition.split(' ').length > 0 ? filterCondition.split(' ') : [filterCondition];
41+
}
42+
43+
const filterConditionsProvided = filterConditionFiles.length > 0;
44+
45+
function checkFilterCondition (fileName, parsedFilepath) {
46+
let result = false;
47+
48+
if (!filterConditionsProvided) return true;
49+
if (filterConditionFiles.includes(parsedFilepath)) result = true;
50+
if (filterConditionFiles.includes(fileName)) result = true;
51+
return result;
52+
}
3453

3554
// TODO(RaisinTen): Update this when the test filenames
3655
// are changed into test_*.js.
37-
function loadTestModules(currentDirectory = __dirname, pre = '') {
56+
function loadTestModules (currentDirectory = __dirname, pre = '') {
3857
fs.readdirSync(currentDirectory).forEach((file) => {
3958
if (currentDirectory === __dirname && (
40-
file === 'binding.cc' ||
59+
file === 'binding.cc' ||
4160
file === 'binding.gyp' ||
4261
file === 'build' ||
4362
file === 'common' ||
@@ -50,15 +69,19 @@ function loadTestModules(currentDirectory = __dirname, pre = '') {
5069
return;
5170
}
5271
const absoluteFilepath = path.join(currentDirectory, file);
72+
const parsedFilepath = path.parse(file);
73+
const parsedPath = path.parse(currentDirectory);
74+
5375
if (fs.statSync(absoluteFilepath).isDirectory()) {
5476
if (fs.existsSync(absoluteFilepath + '/index.js')) {
55-
testModules.push(pre + file);
77+
if (checkFilterCondition(parsedFilepath.name, parsedPath.base)) {
78+
testModules.push(pre + file);
79+
}
5680
} else {
5781
loadTestModules(absoluteFilepath, pre + file + '/');
5882
}
5983
} else {
60-
const parsedFilepath = path.parse(file);
61-
if (parsedFilepath.ext === '.js') {
84+
if (parsedFilepath.ext === '.js' && checkFilterCondition(parsedFilepath.name, parsedPath.base)) {
6285
testModules.push(pre + parsedFilepath.name);
6386
}
6487
}
@@ -69,7 +92,7 @@ loadTestModules();
6992

7093
process.config.target_defaults.default_configuration =
7194
fs
72-
.readdirSync(path.join(__dirname, 'build'))
95+
.readdirSync(path.join(__dirname, process.env.REL_BUILD_PATH || '', 'build'))
7396
.filter((item) => (item === 'Debug' || item === 'Release'))[0];
7497

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

90-
if (napiVersion < 4) {
113+
if (napiVersion < 4 && !filterConditionsProvided) {
91114
testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1);
92115
testModules.splice(testModules.indexOf('asyncprogressworker'), 1);
93116
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1);
@@ -98,36 +121,36 @@ if (napiVersion < 4) {
98121
testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1);
99122
}
100123

101-
if (napiVersion < 5) {
124+
if (napiVersion < 5 && !filterConditionsProvided) {
102125
testModules.splice(testModules.indexOf('date'), 1);
103126
}
104127

105-
if (napiVersion < 6) {
128+
if (napiVersion < 6 && !filterConditionsProvided) {
106129
testModules.splice(testModules.indexOf('addon'), 1);
107130
testModules.splice(testModules.indexOf('addon_data'), 1);
108131
testModules.splice(testModules.indexOf('bigint'), 1);
109132
testModules.splice(testModules.indexOf('typedarray-bigint'), 1);
110133
}
111134

112-
if (majorNodeVersion < 12) {
135+
if (majorNodeVersion < 12 && !filterConditionsProvided) {
113136
testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1);
114137
testModules.splice(testModules.indexOf('error_terminating_environment'), 1);
115138
}
116139

117-
if (napiVersion < 8) {
140+
if (napiVersion < 8 && !filterConditionsProvided) {
118141
testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1);
119142
}
120143

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

124-
console.log('Starting test suite\n');
147+
if (filterConditionsProvided) { console.log('Starting test suite\n', testModules); } else { console.log('Starting test suite\n'); }
125148

126149
// Requiring each module runs tests in the module.
127150
for (const name of testModules) {
128151
console.log(`Running test '${name}'`);
129152
await require('./' + name);
130-
};
153+
}
131154

132155
console.log('\nAll tests passed!');
133156
})().catch((error) => {

test/typed_threadsafe_function/typed_threadsafe_function_ctx.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ const assert = require('assert');
44

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

7-
async function test(binding) {
7+
async function test (binding) {
88
const ctx = { };
9-
const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx);
9+
const tsfn = new binding.typed_threadsafe_function_ctx.TSFNWrap(ctx);
1010
assert(tsfn.getContext() === ctx);
1111
await tsfn.release();
1212
}

unit-test/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules
2+
/build
3+
/generated

unit-test/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
# Enable running tests with specific filter conditions:
3+
4+
### Example:
5+
6+
- compile ad run only tests on objectwrap.cc and objectwrap.js
7+
```
8+
npm run unit --filter=objectwrap
9+
```
10+
11+
12+
# Wildcards are also possible:
13+
14+
### Example:
15+
16+
- compile and run all tests files ending with reference -> function_reference.cc object_reference.cc reference.cc
17+
```
18+
npm run unit --filter=*reference
19+
```
20+
21+
# Multiple filter conditions are also allowed
22+
23+
### Example:
24+
25+
- compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file
26+
```
27+
npm run unit --filter='*function objectwrap'
28+
```

unit-test/binding-file-template.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const path = require('path');
2+
const fs = require('fs');
3+
4+
/**
5+
* @param bindingConfigurations
6+
* This method acts as a template to generate the content of binding.cc file
7+
*/
8+
module.exports.generateFileContent = function (bindingConfigurations) {
9+
const content = [];
10+
const inits = [];
11+
const exports = [];
12+
13+
for (const config of bindingConfigurations) {
14+
inits.push(`Object Init${config.objectName}(Env env);`);
15+
exports.push(`exports.Set("${config.propertyName}", Init${config.objectName}(env));`);
16+
}
17+
18+
content.push('#include "napi.h"');
19+
content.push('using namespace Napi;');
20+
21+
inits.forEach(init => content.push(init));
22+
23+
content.push('Object Init(Env env, Object exports) {');
24+
25+
exports.forEach(exp => content.push(exp));
26+
27+
content.push('return exports;');
28+
content.push('}');
29+
content.push('NODE_API_MODULE(addon, Init);');
30+
31+
return Promise.resolve(content.join('\r\n'));
32+
};
33+
34+
module.exports.writeToBindingFile = function writeToBindingFile (content) {
35+
const generatedFilePath = path.join(__dirname, 'generated', 'binding.cc');
36+
fs.writeFileSync(generatedFilePath, '');
37+
fs.writeFileSync(generatedFilePath, content, { flag: 'a' });
38+
console.log('generated binding file ', generatedFilePath, new Date());
39+
};

0 commit comments

Comments
 (0)