testem 3.19.0 → 3.20.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/README.md +32 -9
- package/lib/api.js +1 -1
- package/lib/app.js +204 -141
- package/lib/browser_launcher.js +5 -6
- package/lib/config.js +246 -133
- package/lib/file_watcher.js +111 -37
- package/lib/file_watcher_impl.js +295 -0
- package/lib/launcher.js +14 -23
- package/lib/log.js +92 -0
- package/lib/process-ctl.js +4 -4
- package/lib/reporters/dev/index.js +2 -1
- package/lib/reporters/dev/split_log_panel.js +2 -2
- package/lib/runners/browser_test_runner.js +15 -8
- package/lib/runners/hook_runner.js +13 -12
- package/lib/runners/process_test_runner.js +4 -5
- package/lib/runners/tap_process_test_runner.js +5 -6
- package/lib/runners/to-result.js +1 -1
- package/lib/server/index.js +62 -38
- package/lib/tap_consumer.js +74 -12
- package/lib/utils/browser-args.js +1 -1
- package/lib/utils/esbuild-browser-polyfills.js +46 -0
- package/lib/utils/esbuild-buffer-inject.js +6 -0
- package/lib/utils/expand_glob_pattern.js +19 -0
- package/lib/utils/file_watch_glob_policy.js +119 -0
- package/lib/utils/fileutils.js +4 -4
- package/lib/utils/is_emfile_error.js +16 -0
- package/lib/utils/known-browsers.js +161 -123
- package/lib/utils/path_pattern_match.js +39 -0
- package/lib/utils/process.js +45 -4
- package/lib/utils/promises.js +149 -0
- package/lib/utils/report-file.js +1 -3
- package/lib/utils/reporter.js +5 -5
- package/lib/utils/run-timeout.js +4 -4
- package/lib/utils/signal-listeners.js +9 -7
- package/lib/utils/tmp-cleanup.js +26 -0
- package/package.json +42 -32
- package/testem.js +170 -79
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
const log = require('
|
|
3
|
+
const log = require('../log');
|
|
4
4
|
const BrowserTapConsumer = require('../browser_tap_consumer');
|
|
5
|
-
const Bluebird = require('bluebird');
|
|
6
|
-
|
|
7
5
|
const LogEntry = require('../utils/log-entry');
|
|
6
|
+
const { asCallback } = require('../utils/promises');
|
|
8
7
|
const toResult = require('./to-result');
|
|
9
8
|
|
|
10
9
|
module.exports = class BrowserTestRunner {
|
|
@@ -33,7 +32,7 @@ module.exports = class BrowserTestRunner {
|
|
|
33
32
|
this.finished = false;
|
|
34
33
|
this.pending = true;
|
|
35
34
|
|
|
36
|
-
return new
|
|
35
|
+
return new Promise(resolve => {
|
|
37
36
|
this.onFinish = resolve;
|
|
38
37
|
|
|
39
38
|
if (this.socket) {
|
|
@@ -41,26 +40,28 @@ module.exports = class BrowserTestRunner {
|
|
|
41
40
|
} else {
|
|
42
41
|
this.launcher.start().then(browserProcess => {
|
|
43
42
|
this.process = browserProcess;
|
|
44
|
-
this.
|
|
43
|
+
if (!this.launcher.settings.ignoreProcessExit) {
|
|
44
|
+
this.process.on('processExit', this.onProcessExit.bind(this));
|
|
45
|
+
}
|
|
45
46
|
this.process.on('processError', this.onProcessError.bind(this));
|
|
46
47
|
this.setupStartTimer();
|
|
47
48
|
}).catch(err => {
|
|
48
49
|
this.onProcessError(err);
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
|
-
}).asCallback(onFinish);
|
|
52
|
+
}).then(...asCallback(onFinish));
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
stop(cb) {
|
|
55
56
|
if (this.socket) {
|
|
56
57
|
this.socket.emit('stop-run');
|
|
57
58
|
}
|
|
58
|
-
return
|
|
59
|
+
return Promise.resolve().then(...asCallback(cb));
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
exit() {
|
|
62
63
|
if (!this.process) {
|
|
63
|
-
return
|
|
64
|
+
return Promise.resolve();
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// mark that somebody (generally app.js) has requested that the
|
|
@@ -119,6 +120,12 @@ module.exports = class BrowserTestRunner {
|
|
|
119
120
|
socket.on('test-metadata', this.onTestMetadata.bind(this));
|
|
120
121
|
socket.on('top-level-error', this.onGlobalError.bind(this));
|
|
121
122
|
|
|
123
|
+
socket.on('tap-debug', function(data) {
|
|
124
|
+
if (process.env.TESTEM_VERBOSE) {
|
|
125
|
+
log.verbose('tap-debug', data && data.msg);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
122
129
|
socket.on('browser-console', function(/* ...args */) {
|
|
123
130
|
let args = Array.prototype.slice.call(arguments);
|
|
124
131
|
let type = args.shift();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const log = require('
|
|
3
|
+
const { fromCallback, disposer } = require('../utils/promises');
|
|
4
|
+
const log = require('../log');
|
|
5
5
|
|
|
6
6
|
const template = require('../utils/strutils').template;
|
|
7
7
|
const ProcessCtl = require('../process-ctl');
|
|
@@ -11,13 +11,13 @@ module.exports = class HookRunner {
|
|
|
11
11
|
this.config = config;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
run(hook, data
|
|
14
|
+
run(hook, data) {
|
|
15
15
|
let hookCfg = this.config.get(hook);
|
|
16
16
|
if (!hookCfg) {
|
|
17
|
-
return
|
|
17
|
+
return Promise.resolve();
|
|
18
18
|
}
|
|
19
19
|
if (typeof hookCfg === 'function') {
|
|
20
|
-
return
|
|
20
|
+
return fromCallback(callback => {
|
|
21
21
|
hookCfg(this.config, data, callback);
|
|
22
22
|
});
|
|
23
23
|
}
|
|
@@ -55,9 +55,8 @@ module.exports = class HookRunner {
|
|
|
55
55
|
} else if (exe) {
|
|
56
56
|
args = this.varsub(args || []);
|
|
57
57
|
hookProcessPromise = this.processCtl.spawn(exe, args, options);
|
|
58
|
-
command = exe + ' ' + args.join(' ');
|
|
59
58
|
} else {
|
|
60
|
-
return
|
|
59
|
+
return Promise.reject(new Error('No command or exe/args specified for hook ' + hook));
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
waitForTextTimeout = waitForTextTimeout || 10000;
|
|
@@ -65,7 +64,7 @@ module.exports = class HookRunner {
|
|
|
65
64
|
|
|
66
65
|
let exited = false;
|
|
67
66
|
let finished;
|
|
68
|
-
let p = new
|
|
67
|
+
let p = new Promise((resolve, reject) => {
|
|
69
68
|
finished = (err, stdout, stderr) => {
|
|
70
69
|
if (exited) {
|
|
71
70
|
return;
|
|
@@ -109,7 +108,7 @@ module.exports = class HookRunner {
|
|
|
109
108
|
}
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
return p
|
|
111
|
+
return p;
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
varsubParams() {
|
|
@@ -135,17 +134,19 @@ module.exports = class HookRunner {
|
|
|
135
134
|
return this.hookProcess.kill();
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
return
|
|
137
|
+
return Promise.resolve();
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
static with(config, hook, data) {
|
|
142
141
|
let hookRunner = new HookRunner(config);
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
let runPromise = hookRunner.run(hook, data).catch(err => {
|
|
145
144
|
log.error(hook + ' error', err);
|
|
146
145
|
|
|
147
146
|
throw err;
|
|
148
|
-
})
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return disposer(runPromise, () => hookRunner.stop());
|
|
149
150
|
}
|
|
150
151
|
};
|
|
151
152
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
var Bluebird = require('bluebird');
|
|
3
|
-
|
|
4
2
|
var toResult = require('./to-result');
|
|
3
|
+
const { asCallback } = require('../utils/promises');
|
|
5
4
|
|
|
6
5
|
class ProcessTestRunner {
|
|
7
6
|
constructor(launcher, reporter) {
|
|
@@ -15,7 +14,7 @@ class ProcessTestRunner {
|
|
|
15
14
|
this.onStart();
|
|
16
15
|
this.finished = false;
|
|
17
16
|
|
|
18
|
-
return new
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
19
18
|
this.onFinish = resolve;
|
|
20
19
|
this.launcher.start().then(testProcess => {
|
|
21
20
|
this.process = testProcess;
|
|
@@ -23,12 +22,12 @@ class ProcessTestRunner {
|
|
|
23
22
|
this.process.once('processError', this.onProcessError.bind(this));
|
|
24
23
|
return Promise.resolve(testProcess);
|
|
25
24
|
}).catch(reject);
|
|
26
|
-
}).asCallback(onFinish);
|
|
25
|
+
}).then(...asCallback(onFinish));
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
exit() {
|
|
30
29
|
if (!this.process) {
|
|
31
|
-
return
|
|
30
|
+
return Promise.resolve();
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
return this.process.kill();
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
var TapConsumer = require('../tap_consumer');
|
|
4
|
-
var log = require('
|
|
5
|
-
var Bluebird = require('bluebird');
|
|
6
|
-
|
|
4
|
+
var log = require('../log');
|
|
7
5
|
var toResult = require('./to-result');
|
|
6
|
+
const { asCallback } = require('../utils/promises');
|
|
8
7
|
|
|
9
8
|
class TapProcessTestRunner {
|
|
10
9
|
constructor(launcher, reporter) {
|
|
@@ -23,19 +22,19 @@ class TapProcessTestRunner {
|
|
|
23
22
|
this.tapConsumer.on('test-result', this.onTestResult.bind(this));
|
|
24
23
|
this.tapConsumer.on('all-test-results', this.onAllTestResults.bind(this));
|
|
25
24
|
|
|
26
|
-
return new
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
27
26
|
this.onFinish = resolve;
|
|
28
27
|
this.launcher.start().then(tapProcess => {
|
|
29
28
|
this.process = tapProcess;
|
|
30
29
|
this.process.once('processError', this.onProcessError.bind(this));
|
|
31
30
|
this.process.process.stdout.pipe(this.tapConsumer.stream);
|
|
32
31
|
}).catch(reject);
|
|
33
|
-
}).asCallback(onFinish);
|
|
32
|
+
}).then(...asCallback(onFinish));
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
exit() {
|
|
37
36
|
if (!this.process) {
|
|
38
|
-
return
|
|
37
|
+
return Promise.resolve();
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
return this.process.kill();
|
package/lib/runners/to-result.js
CHANGED
package/lib/server/index.js
CHANGED
|
@@ -14,15 +14,13 @@ var socketIO = require('socket.io');
|
|
|
14
14
|
var compression = require('compression');
|
|
15
15
|
var fs = require('fs');
|
|
16
16
|
var path = require('path');
|
|
17
|
-
var log = require('
|
|
17
|
+
var log = require('../log');
|
|
18
18
|
var EventEmitter = require('events').EventEmitter;
|
|
19
19
|
var Mustache = require('consolidate').mustache;
|
|
20
20
|
var http = require('http');
|
|
21
21
|
var https = require('https');
|
|
22
22
|
var httpProxy = require('http-proxy');
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
var readFileAsync = Bluebird.promisify(fs.readFile);
|
|
23
|
+
const { fromCallback, each } = require('../utils/promises');
|
|
26
24
|
|
|
27
25
|
class Server extends EventEmitter {
|
|
28
26
|
constructor(config) {
|
|
@@ -37,7 +35,7 @@ class Server extends EventEmitter {
|
|
|
37
35
|
this.nextSocketId = 0;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
start(
|
|
38
|
+
start() {
|
|
41
39
|
this.createExpress();
|
|
42
40
|
|
|
43
41
|
// Start the server!
|
|
@@ -50,7 +48,7 @@ class Server extends EventEmitter {
|
|
|
50
48
|
});
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
return new
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
54
52
|
this.server.on('listening', () => {
|
|
55
53
|
this.config.set('port', this.server.address().port);
|
|
56
54
|
resolve();
|
|
@@ -63,23 +61,23 @@ class Server extends EventEmitter {
|
|
|
63
61
|
});
|
|
64
62
|
|
|
65
63
|
this.server.listen(this.config.get('port'));
|
|
66
|
-
})
|
|
64
|
+
});
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
stop(
|
|
67
|
+
stop() {
|
|
70
68
|
if (this.server && !this.stopped) {
|
|
71
69
|
this.stopped = true;
|
|
72
70
|
|
|
73
|
-
return
|
|
71
|
+
return fromCallback(closeCallback => {
|
|
74
72
|
this.server.close(closeCallback);
|
|
75
73
|
|
|
76
74
|
// Destroy all open sockets
|
|
77
75
|
for (var socketId in this.sockets) {
|
|
78
76
|
this.sockets[socketId].destroy();
|
|
79
77
|
}
|
|
80
|
-
})
|
|
78
|
+
});
|
|
81
79
|
} else {
|
|
82
|
-
return
|
|
80
|
+
return Promise.resolve();
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
|
|
@@ -105,8 +103,10 @@ class Server extends EventEmitter {
|
|
|
105
103
|
})
|
|
106
104
|
);
|
|
107
105
|
|
|
106
|
+
// In Express 5 (path-to-regexp v8), wildcard params like /*file are captured
|
|
107
|
+
// as an array of path segments, so we rejoin them with '/' to reconstruct the path.
|
|
108
108
|
var serveStaticFile = (req, res) => {
|
|
109
|
-
this.serveStaticFile(req.params
|
|
109
|
+
this.serveStaticFile('/' + req.params.file.join('/'), req, res);
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
if (this.config.get('key') || this.config.get('pfx')) {
|
|
@@ -144,16 +144,31 @@ class Server extends EventEmitter {
|
|
|
144
144
|
res.redirect(`/${String(Math.floor(Math.random() * 10000))}`);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
app.get(/\/(-?[0-9]+)$/, (req, res) => {
|
|
148
|
-
this.serveHomePage(req, res);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
147
|
app.get('/testem.js', (req, res) => {
|
|
152
148
|
this.serveTestemClientJs(req, res);
|
|
153
149
|
});
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
// Express 5 dropped regex-in-param syntax (e.g. /:id(-?\d+)), so we match
|
|
152
|
+
// any single-segment :id and validate numerically in the handler.
|
|
153
|
+
app.get('/:id', (req, res, next) => {
|
|
154
|
+
if (/^-?\d+$/.test(req.params.id)) {
|
|
155
|
+
this.serveHomePage(req, res);
|
|
156
|
+
} else {
|
|
157
|
+
next();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Serve static files prefixed with a numeric browser id (e.g. /1234/web/test.js).
|
|
162
|
+
// The numeric guard prevents /:id from greedily swallowing non-numeric paths.
|
|
163
|
+
app.all('/:id/*file', (req, res, next) => {
|
|
164
|
+
if (/^-?\d+$/.test(req.params.id)) {
|
|
165
|
+
serveStaticFile(req, res);
|
|
166
|
+
} else {
|
|
167
|
+
next();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// Catch-all: serve any other path as a static file.
|
|
171
|
+
app.all('/*file', serveStaticFile);
|
|
157
172
|
|
|
158
173
|
app.use((err, req, res, next) => {
|
|
159
174
|
if (err) {
|
|
@@ -231,7 +246,13 @@ class Server extends EventEmitter {
|
|
|
231
246
|
});
|
|
232
247
|
return;
|
|
233
248
|
}
|
|
234
|
-
|
|
249
|
+
// Express 5 no longer accepts bare wildcard segments (e.g. /api*). We
|
|
250
|
+
// replace each * in the user-supplied proxy URL with a named param, then
|
|
251
|
+
// append an optional catch-all for trailing path segments.
|
|
252
|
+
let wIdx = 0;
|
|
253
|
+
const proxyPath =
|
|
254
|
+
url.replace(/\*/g, () => `:_proxyW${wIdx++}`).replace(/\/$/, '') + '{/*_proxyRest}';
|
|
255
|
+
app.all(proxyPath, (req, res, next) => {
|
|
235
256
|
var opts = proxies[url];
|
|
236
257
|
if (this.shouldProxy(req, opts)) {
|
|
237
258
|
if (opts.host) {
|
|
@@ -281,11 +302,13 @@ class Server extends EventEmitter {
|
|
|
281
302
|
var url = base + test_page;
|
|
282
303
|
res.redirect(url);
|
|
283
304
|
} else {
|
|
284
|
-
config.getServeFiles(
|
|
285
|
-
|
|
286
|
-
this.renderRunnerPage(
|
|
305
|
+
Promise.all([config.getServeFiles(), config.getFooterScripts()])
|
|
306
|
+
.then(([files, footerScripts]) => {
|
|
307
|
+
this.renderRunnerPage(null, files, footerScripts, res);
|
|
308
|
+
})
|
|
309
|
+
.catch(err => {
|
|
310
|
+
this.renderRunnerPage(err, [], [], res);
|
|
287
311
|
});
|
|
288
|
-
});
|
|
289
312
|
}
|
|
290
313
|
}
|
|
291
314
|
|
|
@@ -314,11 +337,11 @@ class Server extends EventEmitter {
|
|
|
314
337
|
'mocha_adapter.js',
|
|
315
338
|
'testem_client.js'
|
|
316
339
|
];
|
|
317
|
-
|
|
340
|
+
each(files, file => {
|
|
318
341
|
if (file.indexOf(path.sep) === -1) {
|
|
319
342
|
file = `${__dirname}/../../public/testem/${file}`;
|
|
320
343
|
}
|
|
321
|
-
return
|
|
344
|
+
return fs.promises.readFile(file).then(data => {
|
|
322
345
|
res.write(`\n//============== ${path.basename(file)} ==================\n\n`);
|
|
323
346
|
res.write(data);
|
|
324
347
|
}).catch(err => {
|
|
@@ -376,20 +399,21 @@ class Server extends EventEmitter {
|
|
|
376
399
|
this.emit('file-requested', filePath);
|
|
377
400
|
});
|
|
378
401
|
} else {
|
|
379
|
-
fs.stat(filePath
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
});
|
|
389
|
-
} else {
|
|
402
|
+
fs.promises.stat(filePath)
|
|
403
|
+
.then(stat => {
|
|
404
|
+
this.emit('file-requested', filePath);
|
|
405
|
+
if (stat.isDirectory()) {
|
|
406
|
+
return fs.promises.readdir(filePath).then(files => {
|
|
407
|
+
var dirListingPage = `${__dirname}/../../views/directorylisting.mustache`;
|
|
408
|
+
res.render(dirListingPage, { files: files });
|
|
409
|
+
});
|
|
410
|
+
}
|
|
390
411
|
res.sendFile(filePath);
|
|
391
|
-
}
|
|
392
|
-
|
|
412
|
+
})
|
|
413
|
+
.catch(() => {
|
|
414
|
+
this.emit('file-requested', filePath);
|
|
415
|
+
res.sendFile(filePath);
|
|
416
|
+
});
|
|
393
417
|
}
|
|
394
418
|
}
|
|
395
419
|
|
package/lib/tap_consumer.js
CHANGED
|
@@ -3,40 +3,92 @@
|
|
|
3
3
|
const yaml = require('js-yaml');
|
|
4
4
|
const extend = Object.assign;
|
|
5
5
|
const EventEmitter = require('events').EventEmitter;
|
|
6
|
-
const TapParser = require('tap-parser');
|
|
7
|
-
const log = require('
|
|
6
|
+
const { Parser: TapParser } = require('tap-parser');
|
|
7
|
+
const log = require('./log');
|
|
8
8
|
|
|
9
9
|
module.exports = class TapConsumer extends EventEmitter {
|
|
10
10
|
constructor() {
|
|
11
11
|
super();
|
|
12
12
|
|
|
13
|
+
this.namePrefix = '';
|
|
14
|
+
this.prefixStack = [];
|
|
15
|
+
this.childParsers = [];
|
|
16
|
+
|
|
13
17
|
this.stream = new TapParser();
|
|
14
|
-
this.stream
|
|
15
|
-
|
|
18
|
+
this.attachParser(this.stream, true);
|
|
19
|
+
}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
attachParser(parser, isRoot) {
|
|
22
|
+
parser.on('assert', this.onTapAssert.bind(this));
|
|
23
|
+
parser.on('extra', this.onTapExtra.bind(this));
|
|
24
|
+
parser.on('child', this.onChildParser.bind(this));
|
|
25
|
+
parser.on('complete', this.onParserComplete.bind(this, parser));
|
|
26
|
+
if (isRoot) {
|
|
27
|
+
parser.on('bailout', this.onTapError.bind(this));
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
onChildParser(childParser) {
|
|
32
|
+
this.prefixStack.push(this.namePrefix);
|
|
33
|
+
const parentPrefix = this.namePrefix;
|
|
34
|
+
const segment = (childParser.name && childParser.name.trim()) || '';
|
|
35
|
+
if (parentPrefix && segment) {
|
|
36
|
+
this.namePrefix = `${parentPrefix} > ${segment}`;
|
|
37
|
+
} else if (segment) {
|
|
38
|
+
this.namePrefix = segment;
|
|
39
|
+
} else if (parentPrefix) {
|
|
40
|
+
this.namePrefix = parentPrefix;
|
|
41
|
+
} else {
|
|
42
|
+
this.namePrefix = '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.childParsers.push(childParser);
|
|
46
|
+
this.attachParser(childParser, false);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onParserComplete(parser) {
|
|
50
|
+
if (parser === this.stream) {
|
|
51
|
+
this.onTapEnd();
|
|
24
52
|
return;
|
|
25
53
|
}
|
|
54
|
+
this.namePrefix = this.prefixStack.pop();
|
|
55
|
+
const idx = this.childParsers.indexOf(parser);
|
|
56
|
+
if (idx !== -1) {
|
|
57
|
+
this.childParsers.splice(idx, 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onTapAssert(data) {
|
|
62
|
+
log.verbose(JSON.stringify(data, null, 2));
|
|
26
63
|
|
|
27
64
|
if (data.id === undefined) {
|
|
28
65
|
return;
|
|
29
66
|
}
|
|
30
67
|
|
|
68
|
+
const baseName = data.name ? data.name.trim() : '';
|
|
69
|
+
const qualifiedName = this.namePrefix
|
|
70
|
+
? `${this.namePrefix} > ${baseName}`
|
|
71
|
+
: baseName;
|
|
72
|
+
|
|
31
73
|
let test = {
|
|
32
74
|
passed: 0,
|
|
33
75
|
failed: 0,
|
|
34
76
|
total: 1,
|
|
35
77
|
id: data.id,
|
|
36
|
-
name:
|
|
78
|
+
name: qualifiedName,
|
|
37
79
|
items: []
|
|
38
80
|
};
|
|
39
81
|
|
|
82
|
+
if (data.skip) {
|
|
83
|
+
test.skipped = true;
|
|
84
|
+
this.emit('test-result', test);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (data.todo) {
|
|
89
|
+
test.todo = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
40
92
|
if (!data.ok) {
|
|
41
93
|
let stack;
|
|
42
94
|
if (data.diag) {
|
|
@@ -71,6 +123,16 @@ module.exports = class TapConsumer extends EventEmitter {
|
|
|
71
123
|
}
|
|
72
124
|
}
|
|
73
125
|
|
|
126
|
+
detachAllListeners() {
|
|
127
|
+
this.stream.removeAllListeners();
|
|
128
|
+
for (const p of this.childParsers) {
|
|
129
|
+
p.removeAllListeners();
|
|
130
|
+
}
|
|
131
|
+
this.childParsers = [];
|
|
132
|
+
this.prefixStack = [];
|
|
133
|
+
this.namePrefix = '';
|
|
134
|
+
}
|
|
135
|
+
|
|
74
136
|
onTapError(reason) {
|
|
75
137
|
let test = {
|
|
76
138
|
failed: 1,
|
|
@@ -81,13 +143,13 @@ module.exports = class TapConsumer extends EventEmitter {
|
|
|
81
143
|
}
|
|
82
144
|
};
|
|
83
145
|
|
|
84
|
-
this.
|
|
146
|
+
this.detachAllListeners();
|
|
85
147
|
this.emit('test-result', test);
|
|
86
148
|
this.emit('all-test-results');
|
|
87
149
|
}
|
|
88
150
|
|
|
89
151
|
onTapEnd() {
|
|
90
|
-
this.
|
|
152
|
+
this.detachAllListeners();
|
|
91
153
|
this.emit('all-test-results');
|
|
92
154
|
}
|
|
93
155
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared esbuild config for bundling Node.js-dependent test code to run in a
|
|
3
|
+
* browser via testem.
|
|
4
|
+
*
|
|
5
|
+
* `esbuild-plugins-node-modules-polyfill` stores the `process`
|
|
6
|
+
* polyfill as a scoped variable inside the IIFE it emits, but CJS module
|
|
7
|
+
* factories (wrapped in `__commonJS`) reference `process`, `__dirname`, and
|
|
8
|
+
* `setImmediate` as free globals — causing ReferenceErrors at runtime.
|
|
9
|
+
*
|
|
10
|
+
* These two options fix that:
|
|
11
|
+
* - `banner.js`: sets `globalThis.process`, `globalThis.setImmediate`, and
|
|
12
|
+
* `globalThis.Buffer` before the bundle executes, so CJS factories find
|
|
13
|
+
* them as globals.
|
|
14
|
+
* - `define`: replaces `__dirname` / `__filename` literals, which esbuild
|
|
15
|
+
* does not inject when targeting the browser platform.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
var banner = '(function(){' +
|
|
19
|
+
'if(typeof process==="undefined"){' +
|
|
20
|
+
'var p={env:{},browser:true,platform:"browser",' +
|
|
21
|
+
'nextTick:function(fn){Promise.resolve().then(fn);},' +
|
|
22
|
+
'on:function(){},once:function(){},off:function(){},' +
|
|
23
|
+
'emit:function(){},removeListener:function(){},' +
|
|
24
|
+
'removeAllListeners:function(){},addListener:function(){},' +
|
|
25
|
+
'binding:function(){throw new Error("process.binding not supported");},' +
|
|
26
|
+
'cwd:function(){return "/";},' +
|
|
27
|
+
'exit:function(){}' +
|
|
28
|
+
'};' +
|
|
29
|
+
'if(typeof globalThis!=="undefined")globalThis.process=p;' +
|
|
30
|
+
'else if(typeof window!=="undefined")window.process=p;' +
|
|
31
|
+
'}' +
|
|
32
|
+
'if(typeof setImmediate==="undefined"){' +
|
|
33
|
+
'var si=function(fn){Promise.resolve().then(fn);};' +
|
|
34
|
+
'if(typeof globalThis!=="undefined")globalThis.setImmediate=si;' +
|
|
35
|
+
'else if(typeof window!=="undefined")window.setImmediate=si;' +
|
|
36
|
+
'}' +
|
|
37
|
+
'})();';
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
banner: { js: banner },
|
|
41
|
+
bufferInject: require.resolve('./esbuild-buffer-inject.js'),
|
|
42
|
+
define: {
|
|
43
|
+
__dirname: '""',
|
|
44
|
+
__filename: '""'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// esbuild inject shim: exposes Buffer as a global for browser bundles.
|
|
2
|
+
// The esbuild-plugins-node-modules-polyfill plugin resolves `require('buffer')`
|
|
3
|
+
// to a browser polyfill, but does not assign it to globalThis automatically.
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
5
|
+
if (typeof globalThis !== 'undefined') { globalThis.Buffer = Buffer; }
|
|
6
|
+
else if (typeof window !== 'undefined') { window.Buffer = Buffer; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { glob } = require('glob');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Expand one resolved pattern to matching file paths using the project glob backend.
|
|
5
|
+
*
|
|
6
|
+
* Callers pass POSIX-style paths (see `convertToPosix`); this keeps options aligned
|
|
7
|
+
* with `glob` today and allows swapping the implementation later without touching Config.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} resolvedPatternPosix - Single pattern (already resolved to absolute or cwd-relative form, forward slashes).
|
|
10
|
+
* @param {string[]} [ignorePatternsPosix] - Ignore globs in POSIX form; empty entries are dropped.
|
|
11
|
+
* @returns {Promise<string[]>} Matching paths, sorted lexicographically (same ordering as before extraction).
|
|
12
|
+
*/
|
|
13
|
+
async function expandGlobPattern(resolvedPatternPosix, ignorePatternsPosix) {
|
|
14
|
+
const ignore = (ignorePatternsPosix || []).filter(Boolean);
|
|
15
|
+
const files = await glob(resolvedPatternPosix, { ignore });
|
|
16
|
+
return files.slice().sort();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = expandGlobPattern;
|