@lumjs/tests 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -1
- package/README.md +0 -9
- package/TODO.md +3 -1
- package/bin/lumtest.js +6 -4
- package/lib/harness/index.js +76 -48
- package/lib/harness/parser.js +4 -4
- package/lib/harness/{plugin.js → plugin/index.js} +11 -22
- package/lib/harness/{node.js → plugin/node.js} +35 -7
- package/lib/test/functional.js +14 -3
- package/lib/test/index.js +9 -8
- package/lib/test/log.js +9 -8
- package/lib/test/stats.js +130 -21
- package/package.json +5 -2
- package/lib/harness/browser.js +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,61 @@ and a reference to a property of a module will be in `@tests.propName` format.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [2.1.0] - 2025-11-26
|
|
15
|
+
### Changed
|
|
16
|
+
- Bumped core version to newest release with changes to _stringify_.
|
|
17
|
+
- Updated `test/stats` to support new stringify API changes.
|
|
18
|
+
- Updated `test/log` to use stringify from the test instance.
|
|
19
|
+
- Changed how `harness/node` tests to see if a file should be used as a test.
|
|
20
|
+
- addDir() method now composes the Harness options with opts argument.
|
|
21
|
+
- A new `matchFiles` option can specify a custom function that will
|
|
22
|
+
be passed the file information object and the return value will be
|
|
23
|
+
used in a boolean context to determine if the file is a test or not.
|
|
24
|
+
- If specified, `matchFiles` will be used _instead of_ the `ext` option.
|
|
25
|
+
- The `ext` option now checks for `/`, `(` or `[` characters,
|
|
26
|
+
which would indicate it is a RegExp pattern of some sort.
|
|
27
|
+
- `\.[cm]?js$` would match `.js`, `.cjs`, and `.mjs` files.
|
|
28
|
+
- `/\.js$/i` would match `.js`, `.JS`, `.Js`, and `.jS` files.
|
|
29
|
+
- If no special characters are found, the value is assumed to be
|
|
30
|
+
a single, case-sensitive file extension.
|
|
31
|
+
- The default is still `.js` if the `ext` option was not specified.
|
|
32
|
+
|
|
33
|
+
## [2.0.0] - 2024-08-14
|
|
34
|
+
### Added
|
|
35
|
+
- The `Stats` class (and `Test` which extends it) now has async support.
|
|
36
|
+
- A new `.waiting` integer property indicates if async calls are in use.
|
|
37
|
+
- New `opts.async` nested options added to constructor options.
|
|
38
|
+
- The `async()` method takes an async function that returns a Promise,
|
|
39
|
+
increments the `.waiting` property, and then decrements it once the
|
|
40
|
+
returned Promise is either resolved or rejected.
|
|
41
|
+
- A `wait()` method is used by the `done()` method, and the Harness
|
|
42
|
+
to wait for all async calls to finish.
|
|
43
|
+
- A new `registerGlobal` option for the `Harness` constructor that if `true`
|
|
44
|
+
will make it assign the instance to a special global variable.
|
|
45
|
+
### Changed
|
|
46
|
+
- The `Harness` constructor now requires a `Plugin` as a mandatory
|
|
47
|
+
parameter. No more auto-detection or other crufty magic.
|
|
48
|
+
- The `bin/lumtest.js` script was updated with the API changes.
|
|
49
|
+
- The `Plugin` constructor no longer takes the Harness instance as
|
|
50
|
+
a paramter. The `harness` property will be assigned directly by
|
|
51
|
+
the harness instance instead.
|
|
52
|
+
- `Plugin` class no longer extends the deprecated `core.AbstractClass`.
|
|
53
|
+
- `Harness` has support for the new async functionality.
|
|
54
|
+
- The `run()` method is now explicitly `async`, as are its sub-methods.
|
|
55
|
+
- Moved Harness Plugins into a `harness/plugin` sub-folder.
|
|
56
|
+
- `Stats` can find the harness via the `registerGlobal` feature.
|
|
57
|
+
It still supports the CommonJS `require.main` method, but no
|
|
58
|
+
longer depends on it.
|
|
59
|
+
- The `functional` module now supports using a different name
|
|
60
|
+
for the exported function than the underlying method. Useful
|
|
61
|
+
as the `async()` method is a reserved word and cannot be used
|
|
62
|
+
as a variable/function name (so it's renamed to `callAsync()`).
|
|
63
|
+
- A bunch of extra modules are exported directly now.
|
|
64
|
+
- Renamed some constructors for easier debugging.
|
|
65
|
+
### Removed
|
|
66
|
+
- The `harness/browser` plugin that I never implemented.
|
|
67
|
+
That's better done in a separate package.
|
|
68
|
+
|
|
14
69
|
## [1.9.0] - 2024-07-27
|
|
15
70
|
### Changed
|
|
16
71
|
- Renamed `Harness` constructor name to `LumTestsHarness` to be more unique.
|
|
@@ -149,7 +204,8 @@ and a reference to a property of a module will be in `@tests.propName` format.
|
|
|
149
204
|
- Ported from Lum.js v4 library set.
|
|
150
205
|
- Added a few more features from the PHP version.
|
|
151
206
|
|
|
152
|
-
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/
|
|
207
|
+
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v2.0.0...HEAD
|
|
208
|
+
[2.0.0]: https://github.com/supernovus/lum.tests.js/compare/v1.9.0...v2.0.0
|
|
153
209
|
[1.9.0]: https://github.com/supernovus/lum.tests.js/compare/v1.8.0...v1.9.0
|
|
154
210
|
[1.8.0]: https://github.com/supernovus/lum.tests.js/compare/v1.7.1...v1.8.0
|
|
155
211
|
[1.7.1]: https://github.com/supernovus/lum.tests.js/compare/v1.7.0...v1.7.1
|
package/README.md
CHANGED
|
@@ -6,15 +6,6 @@ This is nowhere near as advanced as many of the other options out there,
|
|
|
6
6
|
but does what I need it to do, and works with the TAP testing protocol that
|
|
7
7
|
the Perl and Raku programming languages popularized.
|
|
8
8
|
|
|
9
|
-
## Exports
|
|
10
|
-
|
|
11
|
-
| Name | Description |
|
|
12
|
-
| -------------------- | ---------------------------------------------------- |
|
|
13
|
-
| `Test` | The core class for testing. |
|
|
14
|
-
| `Harness` | A class for running a bunch of tests together. |
|
|
15
|
-
| `functional()` | A function for using functional-style testing. |
|
|
16
|
-
| `new()` | A function for getting a new `Test` instance. |
|
|
17
|
-
|
|
18
9
|
## Official URLs
|
|
19
10
|
|
|
20
11
|
This library can be found in two places:
|
package/TODO.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# TODO
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- Add explicit support for running ES Module based tests.
|
|
4
|
+
- An alternative to passing `module` object to test instances.
|
|
4
5
|
- Write tests for:
|
|
5
6
|
- `call()`
|
|
6
7
|
- `diesWith()`
|
|
7
8
|
- `callIs()`
|
|
8
9
|
- `matches()`
|
|
9
10
|
- `run()`
|
|
11
|
+
- `async()` (currently used in `@lumjs/encode:v2.0.0` package).
|
|
10
12
|
- Test the `harness/parser` and associated `grammar/tap` libraries.
|
|
11
13
|
- Test the *external* test mode in `Harness`.
|
|
12
14
|
|
package/bin/lumtest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const process = require('node:process');
|
|
3
3
|
const Harness = require('../lib/harness');
|
|
4
|
+
const NodePlugin = require('../lib/harness/plugin/node');
|
|
4
5
|
const core = require('@lumjs/core');
|
|
5
6
|
const {S} = core.types;
|
|
6
7
|
|
|
@@ -59,7 +60,8 @@ for (const arg of args)
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
harness.
|
|
63
|
+
// Create our Harness instance.
|
|
64
|
+
const harness = new Harness(NodePlugin, hOpts);
|
|
65
|
+
module.exports = harness; // Export it as the 'main' module.
|
|
66
|
+
harness.addDir(aDir, aOpts); // Add all the files in the test dir.
|
|
67
|
+
harness.run(rPlan); // Run all the tests.
|
package/lib/harness/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const core = require('@lumjs/core');
|
|
2
|
-
const {def,lazy
|
|
2
|
+
const {def,lazy} = core;
|
|
3
3
|
const {B,F} = core.types;
|
|
4
4
|
const QueuedTest = require('./queuedtest');
|
|
5
5
|
const Plugin = require('./plugin');
|
|
@@ -12,16 +12,13 @@ const Plugin = require('./plugin');
|
|
|
12
12
|
*
|
|
13
13
|
* @prop {object} opts - Options passed to constructor
|
|
14
14
|
*
|
|
15
|
-
* @prop {
|
|
15
|
+
* @prop {module:@lumjs/tests/test/stats} testSuite
|
|
16
16
|
*
|
|
17
17
|
* This meta-test will keep track of the success or failure of
|
|
18
18
|
* all the other tests. So its `plan` will be equal to the number of
|
|
19
19
|
* tests we're running, and so forth.
|
|
20
20
|
*
|
|
21
|
-
* @prop {
|
|
22
|
-
*
|
|
23
|
-
* This may be provided as a parameter to the constructor,
|
|
24
|
-
* or auto-selected based on our Javascript environment.
|
|
21
|
+
* @prop {module:@lumjs/tests/harness/plugin} plugin - JS platform plugin
|
|
25
22
|
*
|
|
26
23
|
* @prop {Array} queue - An array of `QueuedTest` instances
|
|
27
24
|
* representing tests that we want to run.
|
|
@@ -38,15 +35,12 @@ class LumTestsHarness
|
|
|
38
35
|
/**
|
|
39
36
|
* Build a new Harness
|
|
40
37
|
*
|
|
41
|
-
* @param {
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* This is probably not needed. We'll generate a new `Stats` instance.
|
|
38
|
+
* @param {module:@lumjs/tests/harness/plugin} plugin
|
|
39
|
+
* The plugin for the current JS environment.
|
|
45
40
|
*
|
|
46
|
-
*
|
|
41
|
+
* You can pass an instance `object`, or a class constructor `function`.
|
|
47
42
|
*
|
|
48
|
-
*
|
|
49
|
-
* plugin to use based on the JS environment.
|
|
43
|
+
* @param {object} [opts] Advanced Options
|
|
50
44
|
*
|
|
51
45
|
* @param {boolean} [opts.plannedFailure=true] Is a broken plan a failure?
|
|
52
46
|
*
|
|
@@ -63,9 +57,35 @@ class LumTestsHarness
|
|
|
63
57
|
*
|
|
64
58
|
* If this is `false` then no warning will be shown.
|
|
65
59
|
*
|
|
60
|
+
* @param {boolean} [opts.registerGlobal=false] Add a global variable?
|
|
61
|
+
*
|
|
62
|
+
* If this is `true`, a global variable called `__lum_tests_harness__`
|
|
63
|
+
* will be registered as a pointer to this instance.
|
|
64
|
+
*
|
|
65
|
+
* @param {module:@lumjs/tests/test/stats} [opts.testSuite] Meta-test
|
|
66
|
+
*
|
|
67
|
+
* This option is generally for internal (mostly debugging) use only.
|
|
68
|
+
* Don't use it, just let the constructor build a new `Stats` instance.
|
|
69
|
+
*
|
|
66
70
|
*/
|
|
67
|
-
constructor(opts={})
|
|
71
|
+
constructor(plugin, opts={})
|
|
68
72
|
{
|
|
73
|
+
if (typeof plugin === F && Plugin.isPrototypeOf(plugin))
|
|
74
|
+
{ // Class constructor was passed, build an instance.
|
|
75
|
+
plugin = new plugin();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (plugin instanceof Plugin)
|
|
79
|
+
{ // An explicit plugin was specified.
|
|
80
|
+
this.plugin = plugin;
|
|
81
|
+
plugin.harness = this;
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
{ // That's not valid...
|
|
85
|
+
console.error({plugin, opts, harness: this});
|
|
86
|
+
throw new TypeError('Invalid Plugin specified');
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
// Save a reference to the options.
|
|
70
90
|
this.opts = opts;
|
|
71
91
|
|
|
@@ -78,23 +98,6 @@ class LumTestsHarness
|
|
|
78
98
|
this.testSuite = new Stats(opts.testSuite);
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
if (opts.plugin instanceof Plugin)
|
|
82
|
-
{
|
|
83
|
-
this.plugin = opts.plugin;
|
|
84
|
-
}
|
|
85
|
-
else if (ctx.isBrowser)
|
|
86
|
-
{
|
|
87
|
-
this.plugin = require('./browser').new(this);
|
|
88
|
-
}
|
|
89
|
-
else if (ctx.isNode)
|
|
90
|
-
{
|
|
91
|
-
this.plugin = require('./node').new(this);
|
|
92
|
-
}
|
|
93
|
-
else
|
|
94
|
-
{
|
|
95
|
-
throw new Error('Could not determine plugin');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
101
|
//console.debug("# ~ plugin", this.plugin, this);
|
|
99
102
|
|
|
100
103
|
this.queue = []; // Tests to be ran.
|
|
@@ -105,6 +108,11 @@ class LumTestsHarness
|
|
|
105
108
|
this.plannedFailure = opts.plannedFailure ?? true;
|
|
106
109
|
this.plannedWarning = opts.plannedWarning ?? true;
|
|
107
110
|
|
|
111
|
+
if (opts.registerGlobal)
|
|
112
|
+
{ // Add a global variable.
|
|
113
|
+
globalThis[Stats.HARNESS_GLOBAL] = this;
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
} // constructor()
|
|
109
117
|
|
|
110
118
|
/**
|
|
@@ -177,9 +185,9 @@ class LumTestsHarness
|
|
|
177
185
|
* Run all the queued tests
|
|
178
186
|
*
|
|
179
187
|
* @param {boolean} [plan=true] Set a test plan for the entire suite
|
|
180
|
-
* @returns {object} `this`
|
|
188
|
+
* @returns {Promise<object>} Resolves to `this`
|
|
181
189
|
*/
|
|
182
|
-
run(plan=true)
|
|
190
|
+
async run(plan=true)
|
|
183
191
|
{
|
|
184
192
|
if (plan)
|
|
185
193
|
{
|
|
@@ -188,15 +196,15 @@ class LumTestsHarness
|
|
|
188
196
|
|
|
189
197
|
for (const queued of this.queue)
|
|
190
198
|
{
|
|
191
|
-
this.runTest(queued);
|
|
199
|
+
await this.runTest(queued);
|
|
192
200
|
}
|
|
193
201
|
|
|
194
202
|
this.testSuite.done();
|
|
195
203
|
|
|
196
|
-
return this
|
|
204
|
+
return this
|
|
197
205
|
}
|
|
198
206
|
|
|
199
|
-
runTest(queued)
|
|
207
|
+
async runTest(queued)
|
|
200
208
|
{
|
|
201
209
|
const name = queued.filename;
|
|
202
210
|
let test;
|
|
@@ -218,22 +226,42 @@ class LumTestsHarness
|
|
|
218
226
|
}
|
|
219
227
|
else
|
|
220
228
|
{ // Evaluate if the test is successful or not.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
if (test.waiting)
|
|
230
|
+
{ // async tests running
|
|
231
|
+
try
|
|
232
|
+
{
|
|
233
|
+
await test.wait();
|
|
234
|
+
this.testTest(test, name);
|
|
235
|
+
}
|
|
236
|
+
catch (err)
|
|
237
|
+
{
|
|
238
|
+
this.testSuite.fail(name, err);
|
|
239
|
+
}
|
|
229
240
|
}
|
|
230
|
-
else
|
|
231
|
-
{
|
|
232
|
-
|
|
241
|
+
else
|
|
242
|
+
{
|
|
243
|
+
this.testTest(test, name);
|
|
233
244
|
}
|
|
234
245
|
}
|
|
235
246
|
|
|
236
|
-
return
|
|
247
|
+
return test;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
testTest(test, name)
|
|
251
|
+
{
|
|
252
|
+
const ok = this.plugin.ok(test);
|
|
253
|
+
if (typeof ok === B)
|
|
254
|
+
{ // A simple boolean response
|
|
255
|
+
this.testSuite.ok(ok, name);
|
|
256
|
+
}
|
|
257
|
+
else if (ok instanceof Error)
|
|
258
|
+
{ // An error was returned
|
|
259
|
+
this.testSuite.fail(name, ok);
|
|
260
|
+
}
|
|
261
|
+
else
|
|
262
|
+
{ // This should never happen...
|
|
263
|
+
throw new Error("Invalid result from plugin.ok() !!");
|
|
264
|
+
}
|
|
237
265
|
}
|
|
238
266
|
|
|
239
267
|
tap()
|
package/lib/harness/parser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {S,N,needType,isArray
|
|
1
|
+
const {S,N,needType,isArray} = require('@lumjs/core/types');
|
|
2
2
|
const Stats = require('../test/stats');
|
|
3
3
|
const Tap = require('../grammar/tap');
|
|
4
4
|
|
|
@@ -21,7 +21,7 @@ const T = 'test',
|
|
|
21
21
|
*
|
|
22
22
|
* @exports module:@lumjs/tests/harness/parser
|
|
23
23
|
*/
|
|
24
|
-
class
|
|
24
|
+
class LumTestParser
|
|
25
25
|
{
|
|
26
26
|
/**
|
|
27
27
|
* Build a TAP Parser.
|
|
@@ -145,9 +145,9 @@ class Parser
|
|
|
145
145
|
|
|
146
146
|
static new(opts)
|
|
147
147
|
{
|
|
148
|
-
return new
|
|
148
|
+
return new LumTestParser(opts);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// Export the class as the parser.
|
|
153
|
-
module.exports =
|
|
153
|
+
module.exports = LumTestParser;
|
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
const core = require('@lumjs/core');
|
|
2
|
-
const {
|
|
3
|
-
const {TestFailure,PlanFailure} = require('
|
|
2
|
+
const {AbstractError} = core;
|
|
3
|
+
const {TestFailure,PlanFailure} = require('../errors');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* An abstract base class for Harness plugins
|
|
7
7
|
*
|
|
8
|
-
* @
|
|
8
|
+
* @prop {module:@lumjs/tests/harness} harness
|
|
9
|
+
* The harness instance will assign itself to this property.
|
|
10
|
+
*
|
|
11
|
+
* @exports module:@lumjs/tests/harness/plugin
|
|
9
12
|
*/
|
|
10
|
-
class Plugin
|
|
13
|
+
class Plugin
|
|
11
14
|
{
|
|
12
|
-
/**
|
|
13
|
-
* (Internal class constructor)
|
|
14
|
-
* @param {module:@lumjs/tests/harness} harness - Parent `Harness` instance
|
|
15
|
-
*/
|
|
16
|
-
constructor(harness)
|
|
17
|
-
{
|
|
18
|
-
super();
|
|
19
|
-
this.harness = harness;
|
|
20
|
-
this.$needs('run');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
/**
|
|
24
16
|
* (ABSTRACT METHOD) Run a test
|
|
25
17
|
*
|
|
26
18
|
* This method must be implemented by every plugin.
|
|
27
19
|
*
|
|
28
|
-
* @name module:@lumjs/tests/harness~Plugin#run
|
|
29
|
-
* @function
|
|
30
20
|
* @param {object} queued - A queued test object
|
|
31
21
|
*/
|
|
22
|
+
run(queued)
|
|
23
|
+
{
|
|
24
|
+
throw new AbstractError("run() method not implemented");
|
|
25
|
+
}
|
|
32
26
|
|
|
33
27
|
/**
|
|
34
28
|
* See if a test had failures
|
|
@@ -67,11 +61,6 @@ class Plugin extends AbstractClass
|
|
|
67
61
|
return true;
|
|
68
62
|
}
|
|
69
63
|
|
|
70
|
-
static new(harness)
|
|
71
|
-
{
|
|
72
|
-
return new this(harness);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
64
|
}
|
|
76
65
|
|
|
77
66
|
module.exports = Plugin;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
// Node.js harness plugin
|
|
2
2
|
const core = require('@lumjs/core');
|
|
3
|
-
const {N} = core.types;
|
|
4
|
-
const Plugin = require('./
|
|
3
|
+
const {N,F,S} = core.types;
|
|
4
|
+
const Plugin = require('./index');
|
|
5
5
|
const cp = require('node:child_process');
|
|
6
6
|
const getCwd = require('node:process').cwd;
|
|
7
7
|
const fs = require('node:fs');
|
|
8
8
|
const path = require('node:path');
|
|
9
|
+
const RE = /[\/\(\[]+/;
|
|
10
|
+
const WS = /\s+/g;
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Node.js Harness plugin
|
|
12
14
|
*
|
|
13
|
-
* @exports module:@lumjs/tests/harness
|
|
14
|
-
* @extends module:@lumjs/tetss/harness
|
|
15
|
+
* @exports module:@lumjs/tests/harness/plugin/node
|
|
16
|
+
* @extends module:@lumjs/tetss/harness/plugin
|
|
15
17
|
*/
|
|
16
18
|
class NodePlugin extends Plugin
|
|
17
19
|
{
|
|
@@ -41,17 +43,43 @@ class NodePlugin extends Plugin
|
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
addDir(dir, opts
|
|
46
|
+
addDir(dir, opts, recurse)
|
|
45
47
|
{
|
|
48
|
+
opts = Object.assign({}, this.harness.options, opts);
|
|
49
|
+
|
|
46
50
|
if (typeof recurse !== N && typeof opts.recurse === N)
|
|
47
51
|
{
|
|
48
52
|
recurse = opts.recurse;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
const ext = opts.ext ?? '.js';
|
|
52
55
|
const testOpts = opts.test ?? opts;
|
|
53
56
|
const files = fs.readdirSync(dir, {encoding: 'utf8', withFileTypes: true});
|
|
54
57
|
|
|
58
|
+
let isTest = opts.matchFiles;
|
|
59
|
+
if (typeof isTest !== F)
|
|
60
|
+
{ // No custom test, let's make one.
|
|
61
|
+
let ext = opts.ext ?? '.js';
|
|
62
|
+
|
|
63
|
+
if (typeof ext === S)
|
|
64
|
+
{
|
|
65
|
+
ext = ext.trim();
|
|
66
|
+
if (ext.match(RE))
|
|
67
|
+
{ // A RegExp pattern string of some sort.
|
|
68
|
+
ext = ext.split('/').map(v => v.replaceAll(WS, '')).filter(v => v !== '');
|
|
69
|
+
ext = new RegExp(...ext);
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{ // A simple file extension.
|
|
73
|
+
isTest = (file) => file.name.endsWith(ext);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ext instanceof RegExp)
|
|
78
|
+
{ // RegExp filename test.
|
|
79
|
+
isTest = (file) => file.name.match(ext);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
55
83
|
for (const file of files)
|
|
56
84
|
{
|
|
57
85
|
if (file.name === '.' || file.name === '..') continue;
|
|
@@ -59,7 +87,7 @@ class NodePlugin extends Plugin
|
|
|
59
87
|
{ // Recurse to a nested directory.
|
|
60
88
|
this.addDir(path.join(dir, file.name), opts, recurse-1);
|
|
61
89
|
}
|
|
62
|
-
else if (file.isFile() && file
|
|
90
|
+
else if (file.isFile() && isTest(file))
|
|
63
91
|
{ // It would seem to be a valid test.
|
|
64
92
|
this.harness.addTest(path.join(dir, file.name), testOpts);
|
|
65
93
|
}
|
package/lib/test/functional.js
CHANGED
|
@@ -56,11 +56,22 @@ function functional(opts={}, testClass=Test)
|
|
|
56
56
|
const proxyMethods = testClass.$METHODS.all;
|
|
57
57
|
const test = new testClass(opts);
|
|
58
58
|
const functions = { test };
|
|
59
|
-
for (const
|
|
59
|
+
for (const mdef of proxyMethods)
|
|
60
60
|
{
|
|
61
|
-
|
|
61
|
+
let sname, tname;
|
|
62
|
+
if (Array.isArray(mdef))
|
|
63
|
+
{ // separate source and target names
|
|
64
|
+
sname = mdef[0];
|
|
65
|
+
tname = mdef[1];
|
|
66
|
+
}
|
|
67
|
+
else
|
|
68
|
+
{ // same names for both
|
|
69
|
+
sname = tname = mdef;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
functions[tname] = function ()
|
|
62
73
|
{
|
|
63
|
-
return test[
|
|
74
|
+
return test[sname](...arguments);
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
return functions;
|
package/lib/test/index.js
CHANGED
|
@@ -9,12 +9,13 @@ const TEST_METHODS =
|
|
|
9
9
|
[
|
|
10
10
|
'ok', 'call', 'callIs', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
|
|
11
11
|
'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'matches', 'skip',
|
|
12
|
+
['async', 'callAsync'],
|
|
12
13
|
];
|
|
13
14
|
|
|
14
15
|
// A list of other methods to export that are not standard tests.
|
|
15
16
|
const META_METHODS =
|
|
16
17
|
[
|
|
17
|
-
'plan', 'diag', 'run', 'tap', 'output', 'done',
|
|
18
|
+
'plan', 'diag', 'run', 'tap', 'output', 'done', 'wait',
|
|
18
19
|
];
|
|
19
20
|
|
|
20
21
|
// The function that powers `Test.call()` and friends.
|
|
@@ -40,10 +41,10 @@ function $call (testfunc, args)
|
|
|
40
41
|
* Raku's Test libraries.
|
|
41
42
|
*
|
|
42
43
|
* @exports module:@lumjs/tests/test
|
|
43
|
-
* @extends module:@lumjs/tests/test
|
|
44
|
+
* @extends module:@lumjs/tests/test/stats
|
|
44
45
|
*
|
|
45
46
|
*/
|
|
46
|
-
class
|
|
47
|
+
class LumTest extends Stats
|
|
47
48
|
{
|
|
48
49
|
/**
|
|
49
50
|
* Build a new Test instance.
|
|
@@ -621,16 +622,16 @@ class Test extends Stats
|
|
|
621
622
|
} // class Test
|
|
622
623
|
|
|
623
624
|
// May want this for sub-classes.
|
|
624
|
-
def(
|
|
625
|
+
def(LumTest, 'Stats', Stats);
|
|
625
626
|
|
|
626
627
|
// Should never need this, but...
|
|
627
|
-
def(
|
|
628
|
+
def(LumTest, 'Log', Log);
|
|
628
629
|
|
|
629
630
|
// Probably don't need this either, but...
|
|
630
|
-
def(
|
|
631
|
+
def(LumTest, '$call', $call);
|
|
631
632
|
|
|
632
633
|
// Methods we're exporting for the 'functional' API.
|
|
633
|
-
def(
|
|
634
|
+
def(LumTest, '$METHODS',
|
|
634
635
|
{
|
|
635
636
|
test: TEST_METHODS,
|
|
636
637
|
meta: META_METHODS,
|
|
@@ -685,4 +686,4 @@ def(Test, '$METHODS',
|
|
|
685
686
|
}); // Test.$METHODS
|
|
686
687
|
|
|
687
688
|
// Export the class
|
|
688
|
-
module.exports =
|
|
689
|
+
module.exports = LumTest;
|
package/lib/test/log.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
const types = require('@lumjs/core').types;
|
|
3
|
-
const {S,O,isArray,
|
|
3
|
+
const {S,O,isArray,def} = types;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A log representing the results of a test.
|
|
@@ -22,7 +22,7 @@ const {S,O,isArray,stringify,def} = types;
|
|
|
22
22
|
* @property {object} [details.info] An optional array of extra information.
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
|
-
class
|
|
25
|
+
class LumTestLog
|
|
26
26
|
{
|
|
27
27
|
/**
|
|
28
28
|
* (Internal Constructor)
|
|
@@ -37,7 +37,6 @@ class Log
|
|
|
37
37
|
this.desc = null;
|
|
38
38
|
this.directive = null;
|
|
39
39
|
this.details = {};
|
|
40
|
-
this.stringifyDepth = test.stringifyDepth;
|
|
41
40
|
def(this, '$test$', test);
|
|
42
41
|
}
|
|
43
42
|
|
|
@@ -52,7 +51,7 @@ class Log
|
|
|
52
51
|
*/
|
|
53
52
|
tap (num)
|
|
54
53
|
{
|
|
55
|
-
const
|
|
54
|
+
const test = this.$test$;
|
|
56
55
|
|
|
57
56
|
var out;
|
|
58
57
|
if (this.ok)
|
|
@@ -81,13 +80,13 @@ class Log
|
|
|
81
80
|
const stringy = this.details.stringify;
|
|
82
81
|
|
|
83
82
|
let got = this.details.got;
|
|
84
|
-
if (stringy) got = stringify(got
|
|
83
|
+
if (stringy) got = test.stringify(got);
|
|
85
84
|
out += `# got: ${got}\n`;
|
|
86
85
|
|
|
87
86
|
if ('wanted' in this.details)
|
|
88
87
|
{
|
|
89
88
|
let want = this.details.wanted;
|
|
90
|
-
if (stringy) want = stringify(want
|
|
89
|
+
if (stringy) want = test.stringify(want);
|
|
91
90
|
out += `# expected: ${want}\n`;
|
|
92
91
|
}
|
|
93
92
|
|
|
@@ -106,7 +105,9 @@ class Log
|
|
|
106
105
|
|
|
107
106
|
for (const i in info)
|
|
108
107
|
{
|
|
109
|
-
const line = (typeof info[i] === S)
|
|
108
|
+
const line = (typeof info[i] === S)
|
|
109
|
+
? info[i]
|
|
110
|
+
: test.stringify(info[i]);
|
|
110
111
|
out += `# - ${line}\n`;
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -117,4 +118,4 @@ class Log
|
|
|
117
118
|
} // class Log
|
|
118
119
|
|
|
119
120
|
// Export the class itself.
|
|
120
|
-
module.exports =
|
|
121
|
+
module.exports = LumTestLog;
|
package/lib/test/stats.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
const core = require('@lumjs/core');
|
|
2
2
|
const mods = require('@lumjs/core/modules');
|
|
3
3
|
const {types} = core;
|
|
4
|
-
const {S,N,isObj,def} = types;
|
|
4
|
+
const {S,N,F,isObj,def} = types;
|
|
5
5
|
const Log = require('./log');
|
|
6
|
+
const HARNESS_GLOBAL = '__lum_tests_harness__';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_ASYNC_OPTS =
|
|
9
|
+
{
|
|
10
|
+
timeout: 30000,
|
|
11
|
+
interval: 5,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_STRINGIFY_OPTS =
|
|
15
|
+
{
|
|
16
|
+
maxDepth: 1,
|
|
17
|
+
jsonObjects: true,
|
|
18
|
+
}
|
|
6
19
|
|
|
7
20
|
/**
|
|
8
21
|
* Minimalistic base class for the `Test` library.
|
|
@@ -11,7 +24,7 @@ const Log = require('./log');
|
|
|
11
24
|
* It's extremely minimal and does not provide the core testing methods.
|
|
12
25
|
* See the [Test]{@link module:@lumjs/tests/test} class for that.
|
|
13
26
|
*
|
|
14
|
-
* @alias module:@lumjs/tests/test
|
|
27
|
+
* @alias module:@lumjs/tests/test/stats
|
|
15
28
|
*
|
|
16
29
|
* @property {number} planned - Number of tests planned, `0` if unplanned.
|
|
17
30
|
* @property {number} failed - Number of tests that failed.
|
|
@@ -21,7 +34,7 @@ const Log = require('./log');
|
|
|
21
34
|
* @property {boolean} isTop - Test module was loaded from the command line.
|
|
22
35
|
* @property {?object} harness - The top-level `Harness` if one was found.
|
|
23
36
|
*/
|
|
24
|
-
class
|
|
37
|
+
class LumTestStats
|
|
25
38
|
{
|
|
26
39
|
/**
|
|
27
40
|
* Build a new instance.
|
|
@@ -42,6 +55,10 @@ class Stats
|
|
|
42
55
|
* @param {number} [opts.stringify=1] The depth `stringify()` should recurse
|
|
43
56
|
* objects and Arrays before switching to plain JSON stringification.
|
|
44
57
|
*
|
|
58
|
+
* @param {object} [opts.async] Options for async tests
|
|
59
|
+
* @param {number} [opts.async.timeout=30000] Max time to wait (in ms)
|
|
60
|
+
* @param {number} [opts.async.interval=5] Status check every (in ms)
|
|
61
|
+
*
|
|
45
62
|
*/
|
|
46
63
|
constructor (opts={})
|
|
47
64
|
{
|
|
@@ -69,11 +86,16 @@ class Stats
|
|
|
69
86
|
this.id = null;
|
|
70
87
|
}
|
|
71
88
|
|
|
72
|
-
this.
|
|
73
|
-
|
|
89
|
+
this.stringifyOpts = Object.assign({},
|
|
90
|
+
DEFAULT_STRINGIFY_OPTS,
|
|
91
|
+
opts.stringify);
|
|
92
|
+
|
|
74
93
|
this.failed = 0;
|
|
75
94
|
this.skipped = 0;
|
|
76
95
|
this.planned = 0;
|
|
96
|
+
this.waiting = 0;
|
|
97
|
+
|
|
98
|
+
this.asyncOpts = Object.assign({}, DEFAULT_ASYNC_OPTS, opts.async);
|
|
77
99
|
|
|
78
100
|
// The test logs for each unit.
|
|
79
101
|
this.log = [];
|
|
@@ -106,10 +128,12 @@ class Stats
|
|
|
106
128
|
{ // We found the Harness instance.
|
|
107
129
|
this.harness = require.main.exports;
|
|
108
130
|
}
|
|
131
|
+
else if (isObj(globalThis[HARNESS_GLOBAL]))
|
|
132
|
+
{
|
|
133
|
+
this.harness = globalThis[HARNESS_GLOBAL];
|
|
134
|
+
}
|
|
109
135
|
}
|
|
110
|
-
|
|
111
|
-
// Finally, mark the test stats as not generated yet.
|
|
112
|
-
def(this, '$done', false);
|
|
136
|
+
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
// Internal method.
|
|
@@ -118,10 +142,9 @@ class Stats
|
|
|
118
142
|
return new Log(this);
|
|
119
143
|
}
|
|
120
144
|
|
|
121
|
-
// A wrapper around types.stringify()
|
|
122
145
|
stringify(what)
|
|
123
146
|
{
|
|
124
|
-
return types.stringify(what, this.
|
|
147
|
+
return types.stringify(what, this.stringifyOpts);
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
/**
|
|
@@ -277,6 +300,83 @@ class Stats
|
|
|
277
300
|
this.log.push(msg);
|
|
278
301
|
}
|
|
279
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Run async test code
|
|
305
|
+
*
|
|
306
|
+
* If a test needs to wait for the results of an async operation,
|
|
307
|
+
* then this is the way to do it. This method does not actually
|
|
308
|
+
* call any testing methods, that needs to be done inside the `call`.
|
|
309
|
+
*
|
|
310
|
+
* This increments `this.waiting`, which will be decremented when
|
|
311
|
+
* the `Promise` returned by the `call` is resolved or rejected.
|
|
312
|
+
*
|
|
313
|
+
* @param {(function|Promise)} call - Async code to wait for
|
|
314
|
+
*
|
|
315
|
+
* The `function` must be `async` or return a `Promise`.
|
|
316
|
+
* The function will have this Stats instance applied as `this`.
|
|
317
|
+
*
|
|
318
|
+
* @param {...any} args - Arguments for `call` function.
|
|
319
|
+
*
|
|
320
|
+
* @returns {Promise}
|
|
321
|
+
*/
|
|
322
|
+
async async(call, ...args)
|
|
323
|
+
{
|
|
324
|
+
let testPromise;
|
|
325
|
+
this.waiting++;
|
|
326
|
+
|
|
327
|
+
if (typeof call === F)
|
|
328
|
+
{ // It MUST be an async function, OR return a Promise.
|
|
329
|
+
testPromise = call.apply(this, args);
|
|
330
|
+
}
|
|
331
|
+
else
|
|
332
|
+
{ // Assume the promise was passed directly.
|
|
333
|
+
testPromise = call;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (testPromise instanceof Promise)
|
|
337
|
+
{
|
|
338
|
+
testPromise.finally(() => this.waiting--);
|
|
339
|
+
}
|
|
340
|
+
else
|
|
341
|
+
{
|
|
342
|
+
throw new TypeError("async() call must return a Promise");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return testPromise;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Called if `this.waiting` is greater than `0`
|
|
350
|
+
*
|
|
351
|
+
* @returns {Promise<object>}
|
|
352
|
+
*
|
|
353
|
+
* Will resolve to `this` when `this.waiting` becomes `0`.
|
|
354
|
+
* Will be rejected if the maximum wait timeout is reached.
|
|
355
|
+
*/
|
|
356
|
+
wait()
|
|
357
|
+
{
|
|
358
|
+
const ao = this.asyncOpts;
|
|
359
|
+
return new Promise((resolve, reject) =>
|
|
360
|
+
{
|
|
361
|
+
const timeout = setTimeout(() =>
|
|
362
|
+
{
|
|
363
|
+
clearInterval(test);
|
|
364
|
+
clearTimeout(timeout);
|
|
365
|
+
reject(new Error("Timed out waiting for async encoding"));
|
|
366
|
+
}, ao.timeout);
|
|
367
|
+
|
|
368
|
+
const test = setInterval(() =>
|
|
369
|
+
{
|
|
370
|
+
if (this.waiting === 0)
|
|
371
|
+
{
|
|
372
|
+
clearInterval(test);
|
|
373
|
+
clearTimeout(timeout);
|
|
374
|
+
resolve(this);
|
|
375
|
+
}
|
|
376
|
+
}, ao.interval);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
280
380
|
/**
|
|
281
381
|
* Return TAP formatted output for all the tests.
|
|
282
382
|
*
|
|
@@ -360,24 +460,33 @@ class Stats
|
|
|
360
460
|
}
|
|
361
461
|
|
|
362
462
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
* This will mark the test-set as finished, so attempting to run further
|
|
366
|
-
* tests after will result in a `RangeError` being thrown.
|
|
463
|
+
* Run this when you're done testing.
|
|
367
464
|
*
|
|
368
|
-
*
|
|
465
|
+
* It doesn't do much at all if a `Harness` is in use.
|
|
466
|
+
*
|
|
467
|
+
* If no `Harness` is in use, this will call `this.output()`.
|
|
468
|
+
* If there are any async calls we're waiting on this will wait
|
|
469
|
+
* until they are complete before calling `this.output()`.
|
|
369
470
|
*/
|
|
370
471
|
done ()
|
|
371
472
|
{
|
|
372
|
-
if (this
|
|
473
|
+
if (this.harness)
|
|
474
|
+
{ // The harness will handle the rest.
|
|
475
|
+
return this;
|
|
476
|
+
}
|
|
477
|
+
else if (this.waiting)
|
|
478
|
+
{
|
|
479
|
+
this.wait().finally(() => this.done());
|
|
480
|
+
}
|
|
481
|
+
else
|
|
373
482
|
{
|
|
374
|
-
|
|
483
|
+
this.output();
|
|
375
484
|
}
|
|
376
|
-
def(this, '$done', true);
|
|
377
|
-
return (this.harness ? this : this.output());
|
|
378
485
|
}
|
|
379
486
|
|
|
380
|
-
} // class
|
|
487
|
+
} // class Stats
|
|
488
|
+
|
|
489
|
+
def(LumTestStats, 'HARNESS_GLOBAL', HARNESS_GLOBAL);
|
|
381
490
|
|
|
382
491
|
// Export the class
|
|
383
|
-
module.exports =
|
|
492
|
+
module.exports = LumTestStats;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumjs/tests",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"bin":
|
|
6
6
|
{
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
".": "./lib/index.js",
|
|
12
12
|
"./test": "./lib/test/index.js",
|
|
13
13
|
"./test/functional": "./lib/test/functional.js",
|
|
14
|
+
"./test/stats": "./lib/test/stats.js",
|
|
14
15
|
"./harness": "./lib/harness/index.js",
|
|
15
16
|
"./harness/parser": "./lib/harness/parser.js",
|
|
17
|
+
"./harness/plugin": "./lib/harness/plugin/index.js",
|
|
18
|
+
"./harness/plugin/node": "./lib/harness/plugin/node.js",
|
|
16
19
|
"./package.json": "./package.json",
|
|
17
20
|
"./data/*": "./test/data/*.js"
|
|
18
21
|
},
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
},
|
|
25
28
|
"dependencies":
|
|
26
29
|
{
|
|
27
|
-
"@lumjs/core": "^1.
|
|
30
|
+
"@lumjs/core": "^1.38.4"
|
|
28
31
|
},
|
|
29
32
|
"scripts": {
|
|
30
33
|
"test": "./bin/lumtest.js",
|
package/lib/harness/browser.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Browser harness plugin
|
|
2
|
-
const Plugin = require('./plugin');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Browser Harness plugin
|
|
6
|
-
*
|
|
7
|
-
* @exports module:@lumjs/tests/harness~BrowserPlugin
|
|
8
|
-
* @extends module:@lumjs/tetss/harness~Plugin
|
|
9
|
-
*/
|
|
10
|
-
class BrowserPlugin extends Plugin
|
|
11
|
-
{
|
|
12
|
-
run(queued)
|
|
13
|
-
{
|
|
14
|
-
throw new Error("Browser tests not supported yet");
|
|
15
|
-
}
|
|
16
|
-
} // Node plugin class
|
|
17
|
-
|
|
18
|
-
module.exports = BrowserPlugin;
|