tape-six 1.7.0 → 1.7.1
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/README.md +22 -0
- package/bin/tape6-bun.js +7 -8
- package/bin/tape6-deno.js +7 -8
- package/bin/tape6-node.js +12 -8
- package/bin/tape6-seq.js +7 -8
- package/index.d.ts +1 -1
- package/index.js +9 -0
- package/package.json +20 -6
- package/src/reporters/MinReporter.js +37 -0
- package/src/reporters/TTYReporter.js +4 -4
- package/src/runners/bun/worker.js +33 -5
- package/src/runners/deno/worker.js +33 -5
- package/src/runners/node/worker.js +34 -5
- package/src/runners/seq/TestWorker.js +68 -24
- package/src/test.js +6 -4
- package/src/utils/config.js +42 -4
- package/src/utils/listing.js +12 -20
- package/src/utils/fileSets.js +0 -114
package/README.md
CHANGED
|
@@ -99,11 +99,29 @@ And:
|
|
|
99
99
|
|
|
100
100
|
<img width="240" height="329" alt="image" src="https://github.com/user-attachments/assets/f3d8ac65-9e6a-499d-837f-0271146da1de" />
|
|
101
101
|
|
|
102
|
+
## Project structure
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
tape-six/
|
|
106
|
+
├── index.js # Main entry point, exports test, hooks, aliases
|
|
107
|
+
├── index.d.ts # TypeScript definitions for the public API
|
|
108
|
+
├── package.json # Package config; "tape6" section configures test discovery
|
|
109
|
+
├── bin/ # CLI utilities: tape6, tape6-server, tape6-node, tape6-bun, tape6-deno, tape6-seq
|
|
110
|
+
├── src/ # Source code (test engine, reporters, runners, utilities)
|
|
111
|
+
├── web-app/ # Browser test UI application
|
|
112
|
+
├── tests/ # Test files
|
|
113
|
+
├── ts-tests/ # TypeScript test files
|
|
114
|
+
├── wiki/ # GitHub wiki documentation (submodule)
|
|
115
|
+
└── vendors/ # Git submodules (deep6)
|
|
116
|
+
```
|
|
117
|
+
|
|
102
118
|
## Docs
|
|
103
119
|
|
|
104
120
|
The documentation can be found in the [wiki](https://github.com/uhop/tape-six/wiki).
|
|
105
121
|
See how it can be used in [tests/](https://github.com/uhop/tape-six/tree/master/tests).
|
|
106
122
|
|
|
123
|
+
For AI assistants: see [llms.txt](https://github.com/uhop/tape-six/blob/master/llms.txt) and [llms-full.txt](https://github.com/uhop/tape-six/blob/master/llms-full.txt) for LLM-optimized documentation.
|
|
124
|
+
|
|
107
125
|
The whole API is based on two objects: `test` and `Tester`.
|
|
108
126
|
|
|
109
127
|
### `test`
|
|
@@ -247,6 +265,8 @@ The following methods are available (all `msg` arguments are optional):
|
|
|
247
265
|
- `notStrictEquals()` — an alias of `notStrictEqual()`.
|
|
248
266
|
- `doesNotEqual()` — an alias of `notStrictEqual()`.
|
|
249
267
|
- `isUnequal()` — an alias of `notStrictEqual()`.
|
|
268
|
+
- `isNotEqual()` — an alias of `notStrictEqual()`.
|
|
269
|
+
- `isNot()` — an alias of `notStrictEqual()`.
|
|
250
270
|
- `looseEqual(a, b, msg)` — asserts that `a` and `b` are loosely equal.
|
|
251
271
|
- Loose equality is defined as `a == b`.
|
|
252
272
|
- `looseEquals()` — an alias of `looseEqual()`.
|
|
@@ -290,6 +310,7 @@ The following methods are available (all `msg` arguments are optional):
|
|
|
290
310
|
- Miscellaneous:
|
|
291
311
|
- `any` — returns the `any` object. It can be used in deep equivalency asserts to match any value.
|
|
292
312
|
See [deep6's any](https://github.com/uhop/deep6/wiki/any) for details.
|
|
313
|
+
- `_` — an alias of `any`.
|
|
293
314
|
- `plan(n)` — sets the number of tests in the test suite. Rarely used.
|
|
294
315
|
- `comment(msg)` — sends a comment to the test harness. Rarely used.
|
|
295
316
|
- `skipTest(...args, msg)` — skips the current test yet sends a message to the test harness.
|
|
@@ -390,6 +411,7 @@ Test output can be controlled by flags. See [Supported flags](https://github.com
|
|
|
390
411
|
|
|
391
412
|
The most recent releases:
|
|
392
413
|
|
|
414
|
+
- 1.7.1 _Added AI support, added timeout to start test runners, fixed some bugs in the sequential test runner._
|
|
393
415
|
- 1.7.0 _New features: after/before hooks for tests, aliases for `suite()`, `describe()`, `it()`, `tape6-seq` — an in-process sequential test runner. Improvements: stricter monochrome detection, refactoring, bugfixes, updated dev dependencies and the documentation._
|
|
394
416
|
- 1.6.0 _New features: support for `AssertionError` and 3rd-party assertion libraries based on it like `node:assert` and `chai`, support for `console.assert()`, support for `signal` to cancel asynchronous operations, tests wait for embedded tests, improved reporting of errors, updated dev dependencies._
|
|
395
417
|
- 1.5.1 _Better support for stopping parallel tests, better support for "failed to load" errors._
|
package/bin/tape6-bun.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import {fileURLToPath} from 'node:url';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
resolveTests,
|
|
7
|
+
resolvePatterns,
|
|
8
|
+
getReporterFileName,
|
|
9
|
+
getReporterType
|
|
10
|
+
} from '../src/utils/config.js';
|
|
6
11
|
|
|
7
12
|
import {getReporter, setReporter} from '../src/test.js';
|
|
8
13
|
import {selectTimer} from '../src/utils/timer.js';
|
|
@@ -85,17 +90,11 @@ const config = () => {
|
|
|
85
90
|
if (!parallel) parallel = globalThis.navigator?.hardwareConcurrency || 1;
|
|
86
91
|
};
|
|
87
92
|
|
|
88
|
-
const reporters = {
|
|
89
|
-
jsonl: 'JSONLReporter.js',
|
|
90
|
-
tap: 'TapReporter.js',
|
|
91
|
-
tty: 'TTYReporter.js'
|
|
92
|
-
};
|
|
93
|
-
|
|
94
93
|
const init = async () => {
|
|
95
94
|
const currentReporter = getReporter();
|
|
96
95
|
if (!currentReporter) {
|
|
97
96
|
const reporterType = getReporterType(),
|
|
98
|
-
reporterFile =
|
|
97
|
+
reporterFile = getReporterFileName(reporterType),
|
|
99
98
|
CustomReporter = (await import('../src/reporters/' + reporterFile)).default,
|
|
100
99
|
hasColors = !(
|
|
101
100
|
options.monochrome ||
|
package/bin/tape6-deno.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import {fileURLToPath} from 'node:url';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
resolveTests,
|
|
7
|
+
resolvePatterns,
|
|
8
|
+
getReporterFileName,
|
|
9
|
+
getReporterType
|
|
10
|
+
} from '../src/utils/config.js';
|
|
6
11
|
|
|
7
12
|
import {getReporter, setReporter} from '../src/test.js';
|
|
8
13
|
import {selectTimer} from '../src/utils/timer.js';
|
|
@@ -85,17 +90,11 @@ const config = () => {
|
|
|
85
90
|
if (!parallel) parallel = globalThis.navigator?.hardwareConcurrency || 1;
|
|
86
91
|
};
|
|
87
92
|
|
|
88
|
-
const reporters = {
|
|
89
|
-
jsonl: 'JSONLReporter.js',
|
|
90
|
-
tap: 'TapReporter.js',
|
|
91
|
-
tty: 'TTYReporter.js'
|
|
92
|
-
};
|
|
93
|
-
|
|
94
93
|
const init = async () => {
|
|
95
94
|
const currentReporter = getReporter();
|
|
96
95
|
if (!currentReporter) {
|
|
97
96
|
const reporterType = getReporterType(),
|
|
98
|
-
reporterFile =
|
|
97
|
+
reporterFile = getReporterFileName(reporterType),
|
|
99
98
|
CustomReporter = (await import('../src/reporters/' + reporterFile)).default,
|
|
100
99
|
hasColors = !(
|
|
101
100
|
options.monochrome ||
|
package/bin/tape6-node.js
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import {fileURLToPath} from 'node:url';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
resolveTests,
|
|
8
|
+
resolvePatterns,
|
|
9
|
+
getReporterFileName,
|
|
10
|
+
getReporterType
|
|
11
|
+
} from '../src/utils/config.js';
|
|
7
12
|
|
|
8
13
|
import {getReporter, setReporter} from '../src/test.js';
|
|
9
14
|
import {selectTimer} from '../src/utils/timer.js';
|
|
@@ -86,17 +91,11 @@ const config = () => {
|
|
|
86
91
|
if (!parallel) parallel = globalThis.navigator?.hardwareConcurrency || 1;
|
|
87
92
|
};
|
|
88
93
|
|
|
89
|
-
const reporters = {
|
|
90
|
-
jsonl: 'JSONLReporter.js',
|
|
91
|
-
tap: 'TapReporter.js',
|
|
92
|
-
tty: 'TTYReporter.js'
|
|
93
|
-
};
|
|
94
|
-
|
|
95
94
|
const init = async () => {
|
|
96
95
|
const currentReporter = getReporter();
|
|
97
96
|
if (!currentReporter) {
|
|
98
97
|
const reporterType = getReporterType(),
|
|
99
|
-
reporterFile =
|
|
98
|
+
reporterFile = getReporterFileName(reporterType),
|
|
100
99
|
CustomReporter = (await import('../src/reporters/' + reporterFile)).default,
|
|
101
100
|
hasColors = !(
|
|
102
101
|
options.monochrome ||
|
|
@@ -125,6 +124,11 @@ const main = async () => {
|
|
|
125
124
|
console.error('UNHANDLED ERROR:', origin, error)
|
|
126
125
|
);
|
|
127
126
|
|
|
127
|
+
if (!files.length) {
|
|
128
|
+
console.log('No files found.');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
128
132
|
const reporter = getReporter(),
|
|
129
133
|
worker = new TestWorker(reporter, parallel, options);
|
|
130
134
|
|
package/bin/tape6-seq.js
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import {fileURLToPath} from 'node:url';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
resolveTests,
|
|
8
|
+
resolvePatterns,
|
|
9
|
+
getReporterFileName,
|
|
10
|
+
getReporterType
|
|
11
|
+
} from '../src/utils/config.js';
|
|
7
12
|
|
|
8
13
|
import {getReporter, setReporter, setConfiguredFlag, testRunner} from '../src/test.js';
|
|
9
14
|
import {selectTimer} from '../src/utils/timer.js';
|
|
@@ -68,17 +73,11 @@ const config = () => {
|
|
|
68
73
|
options.flags = flags;
|
|
69
74
|
};
|
|
70
75
|
|
|
71
|
-
const reporters = {
|
|
72
|
-
jsonl: 'JSONLReporter.js',
|
|
73
|
-
tap: 'TapReporter.js',
|
|
74
|
-
tty: 'TTYReporter.js'
|
|
75
|
-
};
|
|
76
|
-
|
|
77
76
|
const init = async () => {
|
|
78
77
|
const currentReporter = getReporter();
|
|
79
78
|
if (!currentReporter) {
|
|
80
79
|
const reporterType = getReporterType(),
|
|
81
|
-
reporterFile =
|
|
80
|
+
reporterFile = getReporterFileName(reporterType),
|
|
82
81
|
CustomReporter = (await import('../src/reporters/' + reporterFile)).default,
|
|
83
82
|
hasColors = !(
|
|
84
83
|
options.monochrome ||
|
package/index.d.ts
CHANGED
|
@@ -629,7 +629,7 @@ export declare interface Tester {
|
|
|
629
629
|
is(a: unknown, b: unknown, message?: string): void;
|
|
630
630
|
|
|
631
631
|
/**
|
|
632
|
-
* Asserts that `actual` is
|
|
632
|
+
* Asserts that `actual` is strictly equal to `expected`. Alias of `strictEqual`.
|
|
633
633
|
* @param actual - The actual value
|
|
634
634
|
* @param expected - The expected value
|
|
635
635
|
* @param message - Optional message to display if the assertion fails
|
package/index.js
CHANGED
|
@@ -110,6 +110,9 @@ const init = async () => {
|
|
|
110
110
|
if (Deno.env.get('TAPE6_JSONL')) {
|
|
111
111
|
const {JSONLReporter} = await import('./src/reporters/JSONLReporter.js');
|
|
112
112
|
reporter = new JSONLReporter({...options, originalConsole, hasColors});
|
|
113
|
+
} else if (Deno.env.get('TAPE6_MIN')) {
|
|
114
|
+
const {MinReporter} = await import('./src/reporters/MinReporter.js');
|
|
115
|
+
reporter = new MinReporter({...options, originalConsole});
|
|
113
116
|
} else if (!Deno.env.get('TAPE6_TAP')) {
|
|
114
117
|
const {TTYReporter} = await import('./src/reporters/TTYReporter.js');
|
|
115
118
|
reporter = new TTYReporter({...options, originalConsole, hasColors});
|
|
@@ -124,6 +127,9 @@ const init = async () => {
|
|
|
124
127
|
if (Bun.env.TAPE6_JSONL) {
|
|
125
128
|
const {JSONLReporter} = await import('./src/reporters/JSONLReporter.js');
|
|
126
129
|
reporter = new JSONLReporter({...options, originalConsole, hasColors});
|
|
130
|
+
} else if (Bun.env.TAPE6_MIN) {
|
|
131
|
+
const {MinReporter} = await import('./src/reporters/MinReporter.js');
|
|
132
|
+
reporter = new MinReporter({...options, originalConsole});
|
|
127
133
|
} else if (!Bun.env.TAPE6_TAP) {
|
|
128
134
|
const {TTYReporter} = await import('./src/reporters/TTYReporter.js');
|
|
129
135
|
reporter = new TTYReporter({...options, originalConsole, hasColors});
|
|
@@ -138,6 +144,9 @@ const init = async () => {
|
|
|
138
144
|
if (process.env.TAPE6_JSONL) {
|
|
139
145
|
const {JSONLReporter} = await import('./src/reporters/JSONLReporter.js');
|
|
140
146
|
reporter = new JSONLReporter({...options, originalConsole, hasColors});
|
|
147
|
+
} else if (process.env.TAPE6_MIN) {
|
|
148
|
+
const {MinReporter} = await import('./src/reporters/MinReporter.js');
|
|
149
|
+
reporter = new MinReporter({...options, originalConsole});
|
|
141
150
|
} else if (!process.env.TAPE6_TAP) {
|
|
142
151
|
const {TTYReporter} = await import('./src/reporters/TTYReporter.js');
|
|
143
152
|
reporter = new TTYReporter({...options, originalConsole, hasColors});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tape-six",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "The test harness for the modern JavaScript and TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
15
|
"tape6": "bin/tape6.js",
|
|
16
|
+
"tape6-node": "bin/tape6-node.js",
|
|
16
17
|
"tape6-bun": "bin/tape6-bun.js",
|
|
17
18
|
"tape6-deno": "bin/tape6-deno.js",
|
|
18
19
|
"tape6-seq": "bin/tape6-seq.js",
|
|
@@ -30,8 +31,8 @@
|
|
|
30
31
|
"test:bun": "bun run ./bin/tape6-bun.js --flags FO",
|
|
31
32
|
"test:deno": "deno run -A ./bin/tape6-deno.js --flags FO",
|
|
32
33
|
"test:seq": "node ./bin/tape6-seq.js --flags FO",
|
|
33
|
-
"test:bun
|
|
34
|
-
"test:deno
|
|
34
|
+
"test:seq:bun": "bun run ./bin/tape6-seq.js --flags FO",
|
|
35
|
+
"test:seq:deno": "deno run -A ./bin/tape6-seq.js --flags FO",
|
|
35
36
|
"test:puppeteer": "node tests/puppeteer-chrome.js",
|
|
36
37
|
"test:playwright": "node tests/playwright-chrome.js",
|
|
37
38
|
"ts-check": "tsc --noEmit",
|
|
@@ -49,7 +50,18 @@
|
|
|
49
50
|
"test",
|
|
50
51
|
"harness",
|
|
51
52
|
"assert",
|
|
52
|
-
"browser"
|
|
53
|
+
"browser",
|
|
54
|
+
"unit-test",
|
|
55
|
+
"testing",
|
|
56
|
+
"test-runner",
|
|
57
|
+
"esm",
|
|
58
|
+
"es-modules",
|
|
59
|
+
"typescript",
|
|
60
|
+
"deno",
|
|
61
|
+
"bun",
|
|
62
|
+
"nodejs",
|
|
63
|
+
"tap-protocol",
|
|
64
|
+
"cross-runtime"
|
|
53
65
|
],
|
|
54
66
|
"author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com/)",
|
|
55
67
|
"funding": "https://github.com/sponsors/uhop",
|
|
@@ -58,6 +70,8 @@
|
|
|
58
70
|
"url": "https://github.com/uhop/tape-six/issues"
|
|
59
71
|
},
|
|
60
72
|
"homepage": "https://github.com/uhop/tape-six#readme",
|
|
73
|
+
"llms": "https://raw.githubusercontent.com/uhop/tape-six/master/llms.txt",
|
|
74
|
+
"llmsFull": "https://raw.githubusercontent.com/uhop/tape-six/master/llms-full.txt",
|
|
61
75
|
"files": [
|
|
62
76
|
"index.*",
|
|
63
77
|
"bin",
|
|
@@ -85,10 +99,10 @@
|
|
|
85
99
|
},
|
|
86
100
|
"devDependencies": {
|
|
87
101
|
"@types/chai": "^5.2.3",
|
|
88
|
-
"@types/node": "^25.
|
|
102
|
+
"@types/node": "^25.3.0",
|
|
89
103
|
"chai": "^6.2.2",
|
|
90
104
|
"playwright": "^1.58.2",
|
|
91
|
-
"puppeteer": "^24.37.
|
|
105
|
+
"puppeteer": "^24.37.5",
|
|
92
106
|
"typescript": "^5.9.3"
|
|
93
107
|
}
|
|
94
108
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Reporter from './Reporter.js';
|
|
2
|
+
|
|
3
|
+
const getEnvVar = name => {
|
|
4
|
+
if (typeof Deno == 'object' && Deno?.version) {
|
|
5
|
+
return Deno.env.get(name);
|
|
6
|
+
} else if (typeof Bun == 'object' && Bun?.version) {
|
|
7
|
+
return Bun.env[name];
|
|
8
|
+
} else if (typeof process == 'object' && process?.versions?.node) {
|
|
9
|
+
return process.env[name];
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class MinReporter extends Reporter {
|
|
15
|
+
constructor({failOnce = false, originalConsole} = {}) {
|
|
16
|
+
super({failOnce});
|
|
17
|
+
this.console = originalConsole || console;
|
|
18
|
+
}
|
|
19
|
+
report(event) {
|
|
20
|
+
event = this.state?.preprocess(event) || event;
|
|
21
|
+
const handler = Reporter.EVENT_MAP[event.type];
|
|
22
|
+
typeof handler == 'string' && this[handler]?.(event);
|
|
23
|
+
this.console.log(
|
|
24
|
+
'Test:',
|
|
25
|
+
event.test,
|
|
26
|
+
'Type:',
|
|
27
|
+
event.type,
|
|
28
|
+
'Name:',
|
|
29
|
+
event.name,
|
|
30
|
+
'Fail:',
|
|
31
|
+
event.fail
|
|
32
|
+
);
|
|
33
|
+
this.state?.postprocess(event);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default MinReporter;
|
|
@@ -58,7 +58,7 @@ export class TTYReporter extends Reporter {
|
|
|
58
58
|
this.hasColors =
|
|
59
59
|
hasColors &&
|
|
60
60
|
this.output.isTTY &&
|
|
61
|
-
(typeof this.output.hasColors == 'function' ? this.output.hasColors(
|
|
61
|
+
(typeof this.output.hasColors == 'function' ? this.output.hasColors(16) : true);
|
|
62
62
|
this.renumberAsserts = renumberAsserts;
|
|
63
63
|
this.failureOnly = failureOnly;
|
|
64
64
|
this.showBanner = showBanner;
|
|
@@ -221,7 +221,7 @@ export class TTYReporter extends Reporter {
|
|
|
221
221
|
paint = method === 'error' || method === 'assert' ? 'stderrPaint' : 'stdoutPaint',
|
|
222
222
|
prefix = this[paint](consoleDict[method] + ':') + ' ';
|
|
223
223
|
for (const line of lines) {
|
|
224
|
-
this.out(prefix + line);
|
|
224
|
+
this.out(prefix + line, this.failureOnly);
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
}
|
|
@@ -231,7 +231,7 @@ export class TTYReporter extends Reporter {
|
|
|
231
231
|
const lines = event.name.split(/\r?\n/),
|
|
232
232
|
prefix = this.stdoutPaint('stdout:') + ' ';
|
|
233
233
|
for (const line of lines) {
|
|
234
|
-
this.out(prefix + line);
|
|
234
|
+
this.out(prefix + line, this.failureOnly);
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
break;
|
|
@@ -240,7 +240,7 @@ export class TTYReporter extends Reporter {
|
|
|
240
240
|
const lines = event.name.split(/\r?\n/),
|
|
241
241
|
prefix = this.stderrPaint('stderr:') + ' ';
|
|
242
242
|
for (const line of lines) {
|
|
243
|
-
this.out(prefix + line);
|
|
243
|
+
this.out(prefix + line, this.failureOnly);
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
break;
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const DEFAULT_START_TIMEOUT = 5_000;
|
|
2
|
+
|
|
3
|
+
const getTimeout = () => {
|
|
4
|
+
const timeoutValue = Bun.env.TAPE6_WORKER_START_TIMEOUT;
|
|
5
|
+
if (!timeoutValue) return DEFAULT_START_TIMEOUT;
|
|
6
|
+
let timeout = Number(timeoutValue);
|
|
7
|
+
if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
|
|
8
|
+
return timeout;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const reportToParent = fileName => {
|
|
12
|
+
const timeout = getTimeout();
|
|
13
|
+
let timeoutBeforeStartId = setTimeout(() => {
|
|
14
|
+
postMessage({type: 'test', test: 0, name: 'FILE: /' + fileName});
|
|
15
|
+
postMessage({
|
|
16
|
+
name: `No tests found in ${timeout}ms`,
|
|
17
|
+
test: 0,
|
|
18
|
+
marker: new Error(),
|
|
19
|
+
operator: 'error',
|
|
20
|
+
fail: true
|
|
21
|
+
});
|
|
22
|
+
postMessage({type: 'end', test: 0, name: 'FILE: /' + fileName, fail: true});
|
|
23
|
+
}, timeout);
|
|
24
|
+
return msg => {
|
|
25
|
+
if (timeoutBeforeStartId) {
|
|
26
|
+
clearTimeout(timeoutBeforeStartId);
|
|
27
|
+
timeoutBeforeStartId = null;
|
|
28
|
+
}
|
|
29
|
+
if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
|
|
30
|
+
msg.name = 'FILE: /' + fileName;
|
|
31
|
+
}
|
|
32
|
+
postMessage(msg);
|
|
33
|
+
};
|
|
6
34
|
};
|
|
7
35
|
|
|
8
36
|
addEventListener('message', async event => {
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const DEFAULT_START_TIMEOUT = 5_000;
|
|
2
|
+
|
|
3
|
+
const getTimeout = () => {
|
|
4
|
+
const timeoutValue = Deno.env.get('TAPE6_WORKER_START TIMEOUT');
|
|
5
|
+
if (!timeoutValue) return DEFAULT_START_TIMEOUT;
|
|
6
|
+
let timeout = Number(timeoutValue);
|
|
7
|
+
if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
|
|
8
|
+
return timeout;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const reportToParent = fileName => {
|
|
12
|
+
const timeout = getTimeout();
|
|
13
|
+
let timeoutBeforeStartId = setTimeout(() => {
|
|
14
|
+
postMessage({type: 'test', test: 0, name: 'FILE: /' + fileName});
|
|
15
|
+
postMessage({
|
|
16
|
+
name: `No tests found in ${timeout}ms`,
|
|
17
|
+
test: 0,
|
|
18
|
+
marker: new Error(),
|
|
19
|
+
operator: 'error',
|
|
20
|
+
fail: true
|
|
21
|
+
});
|
|
22
|
+
postMessage({type: 'end', test: 0, name: 'FILE: /' + fileName, fail: true});
|
|
23
|
+
}, timeout);
|
|
24
|
+
return msg => {
|
|
25
|
+
if (timeoutBeforeStartId) {
|
|
26
|
+
clearTimeout(timeoutBeforeStartId);
|
|
27
|
+
timeoutBeforeStartId = null;
|
|
28
|
+
}
|
|
29
|
+
if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
|
|
30
|
+
msg.name = 'FILE: /' + fileName;
|
|
31
|
+
}
|
|
32
|
+
postMessage(msg);
|
|
33
|
+
};
|
|
6
34
|
};
|
|
7
35
|
|
|
8
36
|
addEventListener('message', async event => {
|
|
@@ -1,10 +1,39 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
1
2
|
import {parentPort} from 'node:worker_threads';
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const DEFAULT_START_TIMEOUT = 5_000;
|
|
5
|
+
|
|
6
|
+
const getTimeout = () => {
|
|
7
|
+
const timeoutValue = process.env.TAPE6_WORKER_START_TIMEOUT;
|
|
8
|
+
if (!timeoutValue) return DEFAULT_START_TIMEOUT;
|
|
9
|
+
let timeout = Number(timeoutValue);
|
|
10
|
+
if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
|
|
11
|
+
return timeout;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const reportToParent = fileName => {
|
|
15
|
+
const timeout = getTimeout();
|
|
16
|
+
let timeoutBeforeStartId = setTimeout(() => {
|
|
17
|
+
parentPort.postMessage({type: 'test', test: 0, name: 'FILE: /' + fileName});
|
|
18
|
+
parentPort.postMessage({
|
|
19
|
+
name: `No tests found in ${timeout}ms`,
|
|
20
|
+
test: 0,
|
|
21
|
+
marker: new Error(),
|
|
22
|
+
operator: 'error',
|
|
23
|
+
fail: true
|
|
24
|
+
});
|
|
25
|
+
parentPort.postMessage({type: 'end', test: 0, name: 'FILE: /' + fileName, fail: true});
|
|
26
|
+
}, timeout);
|
|
27
|
+
return msg => {
|
|
28
|
+
if (timeoutBeforeStartId) {
|
|
29
|
+
clearTimeout(timeoutBeforeStartId);
|
|
30
|
+
timeoutBeforeStartId = null;
|
|
31
|
+
}
|
|
32
|
+
if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
|
|
33
|
+
msg.name = 'FILE: /' + fileName;
|
|
34
|
+
}
|
|
35
|
+
parentPort.postMessage(msg);
|
|
36
|
+
};
|
|
8
37
|
};
|
|
9
38
|
|
|
10
39
|
parentPort.on('message', async msg => {
|
|
@@ -4,6 +4,7 @@ import {pathToFileURL} from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
import {isStopTest} from '../../State.js';
|
|
6
6
|
import EventServer from '../../utils/EventServer.js';
|
|
7
|
+
import {getTimeoutValue} from '../../utils/config.js';
|
|
7
8
|
import {getOriginalConsole, setCurrentReporter} from '../../utils/capture-console.js';
|
|
8
9
|
import {
|
|
9
10
|
clearBeforeAll,
|
|
@@ -11,7 +12,8 @@ import {
|
|
|
11
12
|
clearBeforeEach,
|
|
12
13
|
clearAfterEach,
|
|
13
14
|
setReporter,
|
|
14
|
-
registerNotifyCallback
|
|
15
|
+
registerNotifyCallback,
|
|
16
|
+
unregisterNotifyCallback
|
|
15
17
|
} from '../../test.js';
|
|
16
18
|
import BypassReporter from './BypassReporter.js';
|
|
17
19
|
|
|
@@ -23,10 +25,16 @@ export default class TestWorker extends EventServer {
|
|
|
23
25
|
this.testRunner = options.testRunner;
|
|
24
26
|
this.counter = 0;
|
|
25
27
|
this.originalConsole = getOriginalConsole() || globalThis.console;
|
|
28
|
+
this.timeout = getTimeoutValue();
|
|
29
|
+
this.timeoutId = null;
|
|
26
30
|
}
|
|
27
31
|
makeTask(fileName) {
|
|
28
32
|
const id = String(++this.counter),
|
|
29
33
|
reporter = new BypassReporter(this.reporter, event => {
|
|
34
|
+
if (this.timeoutId) {
|
|
35
|
+
clearTimeout(this.timeoutId);
|
|
36
|
+
this.timeoutId = null;
|
|
37
|
+
}
|
|
30
38
|
try {
|
|
31
39
|
this.report(id, event);
|
|
32
40
|
} catch (error) {
|
|
@@ -39,33 +47,22 @@ export default class TestWorker extends EventServer {
|
|
|
39
47
|
setReporter(reporter);
|
|
40
48
|
setCurrentReporter(this.reporter);
|
|
41
49
|
process.env.TAPE6_TEST_FILE_NAME = fileName;
|
|
42
|
-
|
|
50
|
+
const url = new URL(fileName, baseName);
|
|
51
|
+
import(url)
|
|
43
52
|
.then(async () => {
|
|
44
53
|
const testRunner = await this.testRunner;
|
|
45
54
|
registerNotifyCallback(testRunner);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
type: 'comment',
|
|
50
|
-
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
51
|
-
test: 0
|
|
52
|
-
});
|
|
53
|
-
try {
|
|
54
|
-
this.report(id, {
|
|
55
|
-
name: String(error),
|
|
56
|
-
test: 0,
|
|
57
|
-
marker: new Error(),
|
|
58
|
-
operator: 'error',
|
|
59
|
-
fail: true,
|
|
60
|
-
data: {
|
|
61
|
-
actual: error
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (!isStopTest(error)) throw error;
|
|
55
|
+
if (this.timeoutId) {
|
|
56
|
+
clearTimeout(this.timeoutId);
|
|
57
|
+
this.timeoutId = null;
|
|
66
58
|
}
|
|
67
|
-
this.
|
|
68
|
-
|
|
59
|
+
this.timeoutId = setTimeout(() => {
|
|
60
|
+
this.timeoutId = null;
|
|
61
|
+
unregisterNotifyCallback(testRunner);
|
|
62
|
+
this.#reportTimeout(id, fileName);
|
|
63
|
+
}, this.timeout);
|
|
64
|
+
})
|
|
65
|
+
.catch(error => this.#reportError(id, error));
|
|
69
66
|
return id;
|
|
70
67
|
}
|
|
71
68
|
destroyTask() {
|
|
@@ -76,4 +73,51 @@ export default class TestWorker extends EventServer {
|
|
|
76
73
|
clearBeforeEach();
|
|
77
74
|
clearAfterEach();
|
|
78
75
|
}
|
|
76
|
+
#reportError(id, error) {
|
|
77
|
+
this.report(id, {
|
|
78
|
+
type: 'comment',
|
|
79
|
+
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
80
|
+
test: 0
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
this.report(id, {
|
|
84
|
+
name: String(error),
|
|
85
|
+
test: 0,
|
|
86
|
+
marker: new Error(),
|
|
87
|
+
operator: 'error',
|
|
88
|
+
fail: true,
|
|
89
|
+
data: {
|
|
90
|
+
actual: error
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (!isStopTest(error)) throw error;
|
|
95
|
+
}
|
|
96
|
+
this.close(id);
|
|
97
|
+
}
|
|
98
|
+
#reportTimeout(id, fileName) {
|
|
99
|
+
this.report(id, {
|
|
100
|
+
type: 'test',
|
|
101
|
+
test: 0,
|
|
102
|
+
name: 'FILE: /' + fileName
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
this.report(id, {
|
|
106
|
+
name: `No tests found in ${this.timeout}ms`,
|
|
107
|
+
test: 0,
|
|
108
|
+
marker: new Error(),
|
|
109
|
+
operator: 'error',
|
|
110
|
+
fail: true
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (!isStopTest(error)) throw error;
|
|
114
|
+
}
|
|
115
|
+
this.report(id, {
|
|
116
|
+
type: 'end',
|
|
117
|
+
test: 0,
|
|
118
|
+
name: 'FILE: /' + fileName,
|
|
119
|
+
fail: true
|
|
120
|
+
});
|
|
121
|
+
this.close(id);
|
|
122
|
+
}
|
|
79
123
|
}
|
package/src/test.js
CHANGED
|
@@ -12,7 +12,7 @@ let tests = [],
|
|
|
12
12
|
reporter = null,
|
|
13
13
|
testCounter = 0,
|
|
14
14
|
isConfigured = false,
|
|
15
|
-
notifyCallback =
|
|
15
|
+
notifyCallback = new Set();
|
|
16
16
|
|
|
17
17
|
export const getConfiguredFlag = () => isConfigured;
|
|
18
18
|
export const setConfiguredFlag = value => (isConfigured = !!value);
|
|
@@ -26,10 +26,12 @@ export const registerNotifyCallback = callback => {
|
|
|
26
26
|
if (tests.length) {
|
|
27
27
|
defer(callback);
|
|
28
28
|
} else {
|
|
29
|
-
notifyCallback.
|
|
29
|
+
notifyCallback.add(callback);
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
export const unregisterNotifyCallback = callback => notifyCallback.delete(callback);
|
|
34
|
+
|
|
33
35
|
const testers = [];
|
|
34
36
|
|
|
35
37
|
export const getTesters = () => testers;
|
|
@@ -86,9 +88,9 @@ export const test = async (name, options, testFn) => {
|
|
|
86
88
|
isTimerSet = true;
|
|
87
89
|
}
|
|
88
90
|
const deferred = makeDeferred();
|
|
89
|
-
if (tests.push({options, deferred}) === 1 && notifyCallback.
|
|
91
|
+
if (tests.push({options, deferred}) === 1 && notifyCallback.size) {
|
|
90
92
|
for (const callback of notifyCallback) defer(callback);
|
|
91
|
-
notifyCallback =
|
|
93
|
+
notifyCallback = new Set();
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
return deferred.promise;
|
package/src/utils/config.js
CHANGED
|
@@ -2,18 +2,32 @@ import {promises as fsp} from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import {listing, wildToRe} from './listing.js';
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const exclude = (files, pattern) => {
|
|
7
|
+
const excluded = new Set();
|
|
8
|
+
for (const file of files) {
|
|
9
|
+
if (pattern.test(file)) excluded.add(file);
|
|
10
|
+
}
|
|
11
|
+
return files.difference(excluded);
|
|
12
|
+
};
|
|
6
13
|
|
|
7
14
|
export const resolvePatterns = async (rootFolder, patterns) => {
|
|
8
|
-
let result =
|
|
15
|
+
let result = new Set();
|
|
9
16
|
for (const item of patterns) {
|
|
10
17
|
if (item.length && item[0] == '!') {
|
|
11
18
|
result = exclude(result, wildToRe(rootFolder, item.substring(1)));
|
|
12
19
|
} else {
|
|
13
|
-
|
|
20
|
+
for await (const file of listing(rootFolder, item)) {
|
|
21
|
+
result.add(file);
|
|
22
|
+
}
|
|
14
23
|
}
|
|
15
24
|
}
|
|
16
|
-
|
|
25
|
+
|
|
26
|
+
const files = [];
|
|
27
|
+
for (const file of result) {
|
|
28
|
+
files.push(path.relative(rootFolder, file));
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
17
31
|
};
|
|
18
32
|
|
|
19
33
|
export const getConfig = async (rootFolder, traceFn) => {
|
|
@@ -88,9 +102,33 @@ if (typeof Deno == 'object' && Deno?.version) {
|
|
|
88
102
|
runtime.name = 'browser';
|
|
89
103
|
}
|
|
90
104
|
|
|
105
|
+
export const reporterFileNames = {
|
|
106
|
+
jsonl: 'JSONLReporter.js',
|
|
107
|
+
min: 'MinReporter.js',
|
|
108
|
+
tap: 'TapReporter.js',
|
|
109
|
+
tty: 'TTYReporter.js'
|
|
110
|
+
};
|
|
111
|
+
|
|
91
112
|
export const getReporterType = () => {
|
|
92
113
|
if (runtime.name === 'browser') return null;
|
|
93
114
|
if (runtime.getEnvVar('TAPE6_JSONL')) return 'jsonl';
|
|
115
|
+
if (runtime.getEnvVar('TAPE6_MIN')) return 'min';
|
|
94
116
|
if (runtime.getEnvVar('TAPE6_TAP')) return 'tap';
|
|
95
117
|
return 'tty';
|
|
96
118
|
};
|
|
119
|
+
|
|
120
|
+
export const getReporterFileName = type => {
|
|
121
|
+
const reporterType = type || getReporterType();
|
|
122
|
+
return reporterFileNames[reporterType] || reporterFileNames.tty;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const DEFAULT_START_TIMEOUT = 5_000;
|
|
126
|
+
|
|
127
|
+
export const getTimeoutValue = () => {
|
|
128
|
+
if (runtime.name === 'browser') return DEFAULT_START_TIMEOUT;
|
|
129
|
+
const timeoutValue = runtime.getEnvVar('TAPE6_WORKER_START_TIMEOUT');
|
|
130
|
+
if (!timeoutValue) return DEFAULT_START_TIMEOUT;
|
|
131
|
+
let timeout = Number(timeoutValue);
|
|
132
|
+
if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
|
|
133
|
+
return timeout;
|
|
134
|
+
};
|
package/src/utils/listing.js
CHANGED
|
@@ -17,44 +17,36 @@ const mergeWildcards = folders =>
|
|
|
17
17
|
[]
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
const listFiles = async (rootFolder, folders, baseRe, parents)
|
|
20
|
+
const listFiles = async function* (rootFolder, folders, baseRe, parents) {
|
|
21
21
|
const dir = path.join(rootFolder, parents.join(path.sep)),
|
|
22
22
|
files = await fsp.readdir(dir, {withFileTypes: true});
|
|
23
23
|
|
|
24
|
-
let result = [];
|
|
25
|
-
|
|
26
24
|
if (!folders.length) {
|
|
27
25
|
for (const file of files) {
|
|
28
|
-
if (file.isFile() && baseRe.test(file.name))
|
|
26
|
+
if (file.isFile() && baseRe.test(file.name)) yield path.join(dir, file.name);
|
|
29
27
|
}
|
|
30
|
-
return
|
|
28
|
+
return;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
const theRest = folders.slice(1);
|
|
34
32
|
|
|
35
33
|
if (folders[0]) {
|
|
36
34
|
for (const file of files) {
|
|
37
|
-
if (file.isDirectory() && folders[0].test(file.name))
|
|
38
|
-
|
|
39
|
-
await listFiles(rootFolder, theRest, baseRe, parents.concat(file.name))
|
|
40
|
-
);
|
|
41
|
-
}
|
|
35
|
+
if (file.isDirectory() && folders[0].test(file.name))
|
|
36
|
+
yield* listFiles(rootFolder, theRest, baseRe, parents.concat(file.name));
|
|
42
37
|
}
|
|
43
|
-
return
|
|
38
|
+
return;
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
yield* listFiles(rootFolder, theRest, baseRe, parents);
|
|
47
42
|
for (const file of files) {
|
|
48
|
-
if (file.isDirectory())
|
|
49
|
-
|
|
50
|
-
await listFiles(rootFolder, folders, baseRe, parents.concat(file.name))
|
|
51
|
-
);
|
|
52
|
-
}
|
|
43
|
+
if (file.isDirectory())
|
|
44
|
+
yield* listFiles(rootFolder, folders, baseRe, parents.concat(file.name));
|
|
53
45
|
}
|
|
54
|
-
return
|
|
46
|
+
return;
|
|
55
47
|
};
|
|
56
48
|
|
|
57
|
-
export const listing = async (rootFolder, wildcard)
|
|
49
|
+
export const listing = async function* (rootFolder, wildcard) {
|
|
58
50
|
const parsed = path.parse(path.normalize(wildcard)),
|
|
59
51
|
baseRe = new RegExp('^' + prepRe(parsed.name, '.*') + prepRe(parsed.ext, '.*', true) + '$'),
|
|
60
52
|
folders = mergeWildcards(
|
|
@@ -63,7 +55,7 @@ export const listing = async (rootFolder, wildcard) => {
|
|
|
63
55
|
.filter(part => part)
|
|
64
56
|
.map(part => (part === '**' ? null : new RegExp('^' + prepRe(part, notSep) + '$')))
|
|
65
57
|
);
|
|
66
|
-
|
|
58
|
+
yield* listFiles(rootFolder, folders, baseRe, []);
|
|
67
59
|
};
|
|
68
60
|
|
|
69
61
|
export const wildToRe = (rootFolder, wildcard) => {
|
package/src/utils/fileSets.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// file sets are represented as sorted name lists with no duplicated items
|
|
2
|
-
|
|
3
|
-
export const normalize = fileSet =>
|
|
4
|
-
fileSet.sort().filter((name, i, list) => !i || list[i - 1] !== name);
|
|
5
|
-
|
|
6
|
-
export const union = (a, b) => {
|
|
7
|
-
if (!a.length) return b.slice(0);
|
|
8
|
-
if (!b.length) return a.slice(0);
|
|
9
|
-
|
|
10
|
-
let i = 0,
|
|
11
|
-
j = 0,
|
|
12
|
-
x = a[0],
|
|
13
|
-
y = b[0];
|
|
14
|
-
|
|
15
|
-
const result = [];
|
|
16
|
-
for (;;) {
|
|
17
|
-
if (x < y) {
|
|
18
|
-
result.push(x);
|
|
19
|
-
++i;
|
|
20
|
-
if (i >= a.length) break;
|
|
21
|
-
x = a[i];
|
|
22
|
-
} else if (x > y) {
|
|
23
|
-
result.push(y);
|
|
24
|
-
++j;
|
|
25
|
-
if (j >= b.length) break;
|
|
26
|
-
y = b[j];
|
|
27
|
-
} else {
|
|
28
|
-
result.push(x);
|
|
29
|
-
++i;
|
|
30
|
-
++j;
|
|
31
|
-
if (i >= a.length || j >= b.length) break;
|
|
32
|
-
x = a[i];
|
|
33
|
-
y = b[j];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (i < a.length) {
|
|
38
|
-
result.push(...a.slice(i));
|
|
39
|
-
} else if (j < b.length) {
|
|
40
|
-
result.push(...b.slice(j));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return result;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const intersection = (a, b) => {
|
|
47
|
-
if (!a.length || !b.length) return [];
|
|
48
|
-
|
|
49
|
-
let i = 0,
|
|
50
|
-
j = 0,
|
|
51
|
-
x = a[0],
|
|
52
|
-
y = b[0];
|
|
53
|
-
|
|
54
|
-
const result = [];
|
|
55
|
-
for (;;) {
|
|
56
|
-
if (x < y) {
|
|
57
|
-
++i;
|
|
58
|
-
if (i >= a.length) break;
|
|
59
|
-
x = a[i];
|
|
60
|
-
} else if (x > y) {
|
|
61
|
-
++j;
|
|
62
|
-
if (j >= b.length) break;
|
|
63
|
-
y = b[j];
|
|
64
|
-
} else {
|
|
65
|
-
result.push(x);
|
|
66
|
-
++i;
|
|
67
|
-
++j;
|
|
68
|
-
if (i >= a.length || j >= b.length) break;
|
|
69
|
-
x = a[i];
|
|
70
|
-
y = b[j];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return result;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export const difference = (a, b) => {
|
|
78
|
-
if (!a.length) return [];
|
|
79
|
-
if (!b.length) return a.slice(0);
|
|
80
|
-
|
|
81
|
-
let i = 0,
|
|
82
|
-
j = 0,
|
|
83
|
-
x = a[0],
|
|
84
|
-
y = b[0];
|
|
85
|
-
|
|
86
|
-
const result = [];
|
|
87
|
-
for (;;) {
|
|
88
|
-
if (x < y) {
|
|
89
|
-
result.push(x);
|
|
90
|
-
++i;
|
|
91
|
-
if (i >= a.length) break;
|
|
92
|
-
x = a[i];
|
|
93
|
-
} else if (x > y) {
|
|
94
|
-
++j;
|
|
95
|
-
if (j >= b.length) break;
|
|
96
|
-
y = b[j];
|
|
97
|
-
} else {
|
|
98
|
-
++i;
|
|
99
|
-
++j;
|
|
100
|
-
if (i >= a.length || j >= b.length) break;
|
|
101
|
-
x = a[i];
|
|
102
|
-
y = b[j];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (i < a.length) {
|
|
107
|
-
result.push(...a.slice(i));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return result;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export const filter = (a, re) => a.filter(item => re.test(item));
|
|
114
|
-
export const exclude = (a, re) => a.filter(item => !re.test(item));
|