@lumjs/tests 1.9.0 → 2.0.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 +38 -1
- package/README.md +0 -9
- package/TODO.md +1 -2
- 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} +3 -3
- package/lib/test/functional.js +14 -3
- package/lib/test/index.js +9 -8
- package/lib/test/log.js +2 -2
- package/lib/test/stats.js +119 -17
- package/package.json +5 -2
- package/lib/harness/browser.js +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,42 @@ and a reference to a property of a module will be in `@tests.propName` format.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [2.0.0] - 2024-08-14
|
|
15
|
+
### Added
|
|
16
|
+
- The `Stats` class (and `Test` which extends it) now has async support.
|
|
17
|
+
- A new `.waiting` integer property indicates if async calls are in use.
|
|
18
|
+
- New `opts.async` nested options added to constructor options.
|
|
19
|
+
- The `async()` method takes an async function that returns a Promise,
|
|
20
|
+
increments the `.waiting` property, and then decrements it once the
|
|
21
|
+
returned Promise is either resolved or rejected.
|
|
22
|
+
- A `wait()` method is used by the `done()` method, and the Harness
|
|
23
|
+
to wait for all async calls to finish.
|
|
24
|
+
- A new `registerGlobal` option for the `Harness` constructor that if `true`
|
|
25
|
+
will make it assign the instance to a special global variable.
|
|
26
|
+
### Changed
|
|
27
|
+
- The `Harness` constructor now requires a `Plugin` as a mandatory
|
|
28
|
+
parameter. No more auto-detection or other crufty magic.
|
|
29
|
+
- The `bin/lumtest.js` script was updated with the API changes.
|
|
30
|
+
- The `Plugin` constructor no longer takes the Harness instance as
|
|
31
|
+
a paramter. The `harness` property will be assigned directly by
|
|
32
|
+
the harness instance instead.
|
|
33
|
+
- `Plugin` class no longer extends the deprecated `core.AbstractClass`.
|
|
34
|
+
- `Harness` has support for the new async functionality.
|
|
35
|
+
- The `run()` method is now explicitly `async`, as are its sub-methods.
|
|
36
|
+
- Moved Harness Plugins into a `harness/plugin` sub-folder.
|
|
37
|
+
- `Stats` can find the harness via the `registerGlobal` feature.
|
|
38
|
+
It still supports the CommonJS `require.main` method, but no
|
|
39
|
+
longer depends on it.
|
|
40
|
+
- The `functional` module now supports using a different name
|
|
41
|
+
for the exported function than the underlying method. Useful
|
|
42
|
+
as the `async()` method is a reserved word and cannot be used
|
|
43
|
+
as a variable/function name (so it's renamed to `callAsync()`).
|
|
44
|
+
- A bunch of extra modules are exported directly now.
|
|
45
|
+
- Renamed some constructors for easier debugging.
|
|
46
|
+
### Removed
|
|
47
|
+
- The `harness/browser` plugin that I never implemented.
|
|
48
|
+
That's better done in a separate package.
|
|
49
|
+
|
|
14
50
|
## [1.9.0] - 2024-07-27
|
|
15
51
|
### Changed
|
|
16
52
|
- Renamed `Harness` constructor name to `LumTestsHarness` to be more unique.
|
|
@@ -149,7 +185,8 @@ and a reference to a property of a module will be in `@tests.propName` format.
|
|
|
149
185
|
- Ported from Lum.js v4 library set.
|
|
150
186
|
- Added a few more features from the PHP version.
|
|
151
187
|
|
|
152
|
-
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/
|
|
188
|
+
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v2.0.0...HEAD
|
|
189
|
+
[2.0.0]: https://github.com/supernovus/lum.tests.js/compare/v1.9.0...v2.0.0
|
|
153
190
|
[1.9.0]: https://github.com/supernovus/lum.tests.js/compare/v1.8.0...v1.9.0
|
|
154
191
|
[1.8.0]: https://github.com/supernovus/lum.tests.js/compare/v1.7.1...v1.8.0
|
|
155
192
|
[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,11 @@
|
|
|
1
1
|
# TODO
|
|
2
2
|
|
|
3
|
-
- Update to properly support `async` tests; the `harness` will need some work.
|
|
4
3
|
- Write tests for:
|
|
5
4
|
- `call()`
|
|
6
5
|
- `diesWith()`
|
|
7
6
|
- `callIs()`
|
|
8
7
|
- `matches()`
|
|
9
8
|
- `run()`
|
|
9
|
+
- `async()` (currently used in `@lumjs/encode:v2.0.0` package).
|
|
10
10
|
- Test the `harness/parser` and associated `grammar/tap` libraries.
|
|
11
11
|
- Test the *external* test mode in `Harness`.
|
|
12
|
-
|
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,7 +1,7 @@
|
|
|
1
1
|
// Node.js harness plugin
|
|
2
2
|
const core = require('@lumjs/core');
|
|
3
3
|
const {N} = core.types;
|
|
4
|
-
const Plugin = require('./
|
|
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');
|
|
@@ -10,8 +10,8 @@ const path = require('node:path');
|
|
|
10
10
|
/**
|
|
11
11
|
* Node.js Harness plugin
|
|
12
12
|
*
|
|
13
|
-
* @exports module:@lumjs/tests/harness
|
|
14
|
-
* @extends module:@lumjs/tetss/harness
|
|
13
|
+
* @exports module:@lumjs/tests/harness/plugin/node
|
|
14
|
+
* @extends module:@lumjs/tetss/harness/plugin
|
|
15
15
|
*/
|
|
16
16
|
class NodePlugin extends Plugin
|
|
17
17
|
{
|
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
|
@@ -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)
|
|
@@ -117,4 +117,4 @@ class Log
|
|
|
117
117
|
} // class Log
|
|
118
118
|
|
|
119
119
|
// Export the class itself.
|
|
120
|
-
module.exports =
|
|
120
|
+
module.exports = LumTestLog;
|
package/lib/test/stats.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
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
|
+
}
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Minimalistic base class for the `Test` library.
|
|
@@ -11,7 +18,7 @@ const Log = require('./log');
|
|
|
11
18
|
* It's extremely minimal and does not provide the core testing methods.
|
|
12
19
|
* See the [Test]{@link module:@lumjs/tests/test} class for that.
|
|
13
20
|
*
|
|
14
|
-
* @alias module:@lumjs/tests/test
|
|
21
|
+
* @alias module:@lumjs/tests/test/stats
|
|
15
22
|
*
|
|
16
23
|
* @property {number} planned - Number of tests planned, `0` if unplanned.
|
|
17
24
|
* @property {number} failed - Number of tests that failed.
|
|
@@ -21,7 +28,7 @@ const Log = require('./log');
|
|
|
21
28
|
* @property {boolean} isTop - Test module was loaded from the command line.
|
|
22
29
|
* @property {?object} harness - The top-level `Harness` if one was found.
|
|
23
30
|
*/
|
|
24
|
-
class
|
|
31
|
+
class LumTestStats
|
|
25
32
|
{
|
|
26
33
|
/**
|
|
27
34
|
* Build a new instance.
|
|
@@ -42,6 +49,10 @@ class Stats
|
|
|
42
49
|
* @param {number} [opts.stringify=1] The depth `stringify()` should recurse
|
|
43
50
|
* objects and Arrays before switching to plain JSON stringification.
|
|
44
51
|
*
|
|
52
|
+
* @param {object} [opts.async] Options for async tests
|
|
53
|
+
* @param {number} [opts.async.timeout=30000] Max time to wait (in ms)
|
|
54
|
+
* @param {number} [opts.async.interval=5] Status check every (in ms)
|
|
55
|
+
*
|
|
45
56
|
*/
|
|
46
57
|
constructor (opts={})
|
|
47
58
|
{
|
|
@@ -74,6 +85,9 @@ class Stats
|
|
|
74
85
|
this.failed = 0;
|
|
75
86
|
this.skipped = 0;
|
|
76
87
|
this.planned = 0;
|
|
88
|
+
this.waiting = 0;
|
|
89
|
+
|
|
90
|
+
this.asyncOpts = Object.assign({}, DEFAULT_ASYNC_OPTS, opts.async);
|
|
77
91
|
|
|
78
92
|
// The test logs for each unit.
|
|
79
93
|
this.log = [];
|
|
@@ -106,10 +120,12 @@ class Stats
|
|
|
106
120
|
{ // We found the Harness instance.
|
|
107
121
|
this.harness = require.main.exports;
|
|
108
122
|
}
|
|
123
|
+
else if (isObj(globalThis[HARNESS_GLOBAL]))
|
|
124
|
+
{
|
|
125
|
+
this.harness = globalThis[HARNESS_GLOBAL];
|
|
126
|
+
}
|
|
109
127
|
}
|
|
110
|
-
|
|
111
|
-
// Finally, mark the test stats as not generated yet.
|
|
112
|
-
def(this, '$done', false);
|
|
128
|
+
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
// Internal method.
|
|
@@ -277,6 +293,83 @@ class Stats
|
|
|
277
293
|
this.log.push(msg);
|
|
278
294
|
}
|
|
279
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Run async test code
|
|
298
|
+
*
|
|
299
|
+
* If a test needs to wait for the results of an async operation,
|
|
300
|
+
* then this is the way to do it. This method does not actually
|
|
301
|
+
* call any testing methods, that needs to be done inside the `call`.
|
|
302
|
+
*
|
|
303
|
+
* This increments `this.waiting`, which will be decremented when
|
|
304
|
+
* the `Promise` returned by the `call` is resolved or rejected.
|
|
305
|
+
*
|
|
306
|
+
* @param {(function|Promise)} call - Async code to wait for
|
|
307
|
+
*
|
|
308
|
+
* The `function` must be `async` or return a `Promise`.
|
|
309
|
+
* The function will have this Stats instance applied as `this`.
|
|
310
|
+
*
|
|
311
|
+
* @param {...any} args - Arguments for `call` function.
|
|
312
|
+
*
|
|
313
|
+
* @returns {Promise}
|
|
314
|
+
*/
|
|
315
|
+
async async(call, ...args)
|
|
316
|
+
{
|
|
317
|
+
let testPromise;
|
|
318
|
+
this.waiting++;
|
|
319
|
+
|
|
320
|
+
if (typeof call === F)
|
|
321
|
+
{ // It MUST be an async function, OR return a Promise.
|
|
322
|
+
testPromise = call.apply(this, args);
|
|
323
|
+
}
|
|
324
|
+
else
|
|
325
|
+
{ // Assume the promise was passed directly.
|
|
326
|
+
testPromise = call;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (testPromise instanceof Promise)
|
|
330
|
+
{
|
|
331
|
+
testPromise.finally(() => this.waiting--);
|
|
332
|
+
}
|
|
333
|
+
else
|
|
334
|
+
{
|
|
335
|
+
throw new TypeError("async() call must return a Promise");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return testPromise;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Called if `this.waiting` is greater than `0`
|
|
343
|
+
*
|
|
344
|
+
* @returns {Promise<object>}
|
|
345
|
+
*
|
|
346
|
+
* Will resolve to `this` when `this.waiting` becomes `0`.
|
|
347
|
+
* Will be rejected if the maximum wait timeout is reached.
|
|
348
|
+
*/
|
|
349
|
+
wait()
|
|
350
|
+
{
|
|
351
|
+
const ao = this.asyncOpts;
|
|
352
|
+
return new Promise((resolve, reject) =>
|
|
353
|
+
{
|
|
354
|
+
const timeout = setTimeout(() =>
|
|
355
|
+
{
|
|
356
|
+
clearInterval(test);
|
|
357
|
+
clearTimeout(timeout);
|
|
358
|
+
reject(new Error("Timed out waiting for async encoding"));
|
|
359
|
+
}, ao.timeout);
|
|
360
|
+
|
|
361
|
+
const test = setInterval(() =>
|
|
362
|
+
{
|
|
363
|
+
if (this.waiting === 0)
|
|
364
|
+
{
|
|
365
|
+
clearInterval(test);
|
|
366
|
+
clearTimeout(timeout);
|
|
367
|
+
resolve(this);
|
|
368
|
+
}
|
|
369
|
+
}, ao.interval);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
280
373
|
/**
|
|
281
374
|
* Return TAP formatted output for all the tests.
|
|
282
375
|
*
|
|
@@ -360,24 +453,33 @@ class Stats
|
|
|
360
453
|
}
|
|
361
454
|
|
|
362
455
|
/**
|
|
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.
|
|
456
|
+
* Run this when you're done testing.
|
|
367
457
|
*
|
|
368
|
-
*
|
|
458
|
+
* It doesn't do much at all if a `Harness` is in use.
|
|
459
|
+
*
|
|
460
|
+
* If no `Harness` is in use, this will call `this.output()`.
|
|
461
|
+
* If there are any async calls we're waiting on this will wait
|
|
462
|
+
* until they are complete before calling `this.output()`.
|
|
369
463
|
*/
|
|
370
464
|
done ()
|
|
371
465
|
{
|
|
372
|
-
if (this
|
|
466
|
+
if (this.harness)
|
|
467
|
+
{ // The harness will handle the rest.
|
|
468
|
+
return this;
|
|
469
|
+
}
|
|
470
|
+
else if (this.waiting)
|
|
373
471
|
{
|
|
374
|
-
|
|
472
|
+
this.wait().finally(() => this.done());
|
|
473
|
+
}
|
|
474
|
+
else
|
|
475
|
+
{
|
|
476
|
+
this.output();
|
|
375
477
|
}
|
|
376
|
-
def(this, '$done', true);
|
|
377
|
-
return (this.harness ? this : this.output());
|
|
378
478
|
}
|
|
379
479
|
|
|
380
|
-
} // class
|
|
480
|
+
} // class Stats
|
|
481
|
+
|
|
482
|
+
def(LumTestStats, 'HARNESS_GLOBAL', HARNESS_GLOBAL);
|
|
381
483
|
|
|
382
484
|
// Export the class
|
|
383
|
-
module.exports =
|
|
485
|
+
module.exports = LumTestStats;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumjs/tests",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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.26.0"
|
|
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;
|