testem 3.19.1 → 3.20.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 +175 -134
- package/lib/api.js +1 -1
- package/lib/app.js +186 -116
- package/lib/cli-local-delegation.js +93 -0
- package/lib/config.js +260 -119
- package/lib/file_watcher.js +111 -37
- package/lib/file_watcher_impl.js +295 -0
- package/lib/launcher.js +7 -12
- package/lib/reporters/dev/runner_tabs.js +52 -47
- package/lib/reporters/dev/split_log_panel.js +1 -1
- package/lib/reporters/dev/toast_notify.js +5 -0
- package/lib/runners/browser_test_runner.js +3 -1
- package/lib/runners/hook_runner.js +0 -1
- package/lib/server/index.js +39 -10
- package/lib/tap_consumer.js +73 -11
- package/lib/utils/esbuild-browser-polyfills.js +1 -1
- package/lib/utils/esbuild-buffer-inject.js +1 -1
- package/lib/utils/expand_glob_pattern.js +19 -0
- package/lib/utils/file_watch_glob_policy.js +119 -0
- package/lib/utils/is_emfile_error.js +16 -0
- package/lib/utils/known-browsers.js +155 -118
- package/lib/utils/path_pattern_match.js +39 -0
- package/lib/utils/process.js +42 -0
- package/lib/utils/promises.js +20 -1
- package/lib/utils/reporter.js +2 -2
- package/lib/utils/tmp-cleanup.js +26 -0
- package/package.json +40 -31
- package/testem.js +174 -78
package/lib/file_watcher.js
CHANGED
|
@@ -1,51 +1,125 @@
|
|
|
1
|
+
const EventEmitter = require('events').EventEmitter;
|
|
1
2
|
|
|
3
|
+
const Impl = require('./file_watcher_impl');
|
|
2
4
|
|
|
3
|
-
const
|
|
4
|
-
const EventEmitter = require('events').EventEmitter;
|
|
5
|
+
const kCompose = Symbol('FileWatcher.compose');
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Coordinates file watching for Testem: it observes the current working directory (and any
|
|
9
|
+
* non-glob paths from config that live outside it), applies the same include / ignore rules as
|
|
10
|
+
* the rest of the app (`src_files`, `src_files_ignore`, `watch_files`, etc.), and reports when
|
|
11
|
+
* paths that matter for the test run change.
|
|
12
|
+
*
|
|
13
|
+
* Listen for {@link FileWatcher#event:fileChanged} to react to changes, and optionally for
|
|
14
|
+
* {@link FileWatcher#event:EMFILE} when the process hits open-file limits.
|
|
15
|
+
*
|
|
16
|
+
* ### Construction
|
|
17
|
+
*
|
|
18
|
+
* **Do not call `new FileWatcher(...)`.** The constructor is not part of the public API; it
|
|
19
|
+
* exists only for internal wiring and will always throw if you invoke it from outside this
|
|
20
|
+
* module. Obtain instances exclusively via {@link FileWatcher.create} with a config object that
|
|
21
|
+
* exposes `get(key)` and `isCwdMode()` (the same shape as Testem’s `Config`).
|
|
22
|
+
*
|
|
23
|
+
* ### Async vs synchronous
|
|
24
|
+
*
|
|
25
|
+
* **Return a `Promise` — use `await`, `.then()`, or `.catch()`:**
|
|
26
|
+
* {@link FileWatcher.create}, {@link FileWatcher#add}, {@link FileWatcher#close}.
|
|
27
|
+
*
|
|
28
|
+
* **Synchronous:** {@link FileWatcher#onFileChanged}, {@link FileWatcher#onEMFILE}, and
|
|
29
|
+
* inherited `EventEmitter` methods (`on`, `once`, `emit`, `removeListener`, …).
|
|
30
|
+
*
|
|
31
|
+
* @hideconstructor
|
|
32
|
+
* @fires FileWatcher#event:fileChanged
|
|
33
|
+
* @fires FileWatcher#event:EMFILE
|
|
34
|
+
*/
|
|
35
|
+
class FileWatcher extends EventEmitter {
|
|
36
|
+
/**
|
|
37
|
+
* Not for public use. Application code must use {@link FileWatcher.create} instead; calling
|
|
38
|
+
* `new FileWatcher` directly always throws.
|
|
39
|
+
*
|
|
40
|
+
* @private
|
|
41
|
+
* @param {*} impl Internal implementation instance.
|
|
42
|
+
* @param {symbol} token Internal composition token (not exposed).
|
|
43
|
+
*/
|
|
44
|
+
constructor(impl, token) {
|
|
8
45
|
super();
|
|
46
|
+
if (token !== kCompose) {
|
|
47
|
+
throw new TypeError(
|
|
48
|
+
'FileWatcher is not constructable; use FileWatcher.create(config)',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
this._impl = impl;
|
|
52
|
+
impl.on('fileChanged', (filePath) => this.emit('fileChanged', filePath));
|
|
53
|
+
impl.on('EMFILE', () => this.emit('EMFILE'));
|
|
54
|
+
}
|
|
9
55
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Create a watcher, wait until the underlying engine has finished its initial setup, and
|
|
58
|
+
* return a ready-to-use `FileWatcher`. This is the **only** supported way to obtain an
|
|
59
|
+
* instance.
|
|
60
|
+
*
|
|
61
|
+
* @param {{ get: function(string): *, isCwdMode: function(): boolean }} config Testem-style
|
|
62
|
+
* config (typically a `Config` instance): `get` reads keys such as `src_files`,
|
|
63
|
+
* `src_files_ignore`, `watch_files`, `file`, `disable_watching`; `isCwdMode` reflects cwd
|
|
64
|
+
* mode.
|
|
65
|
+
* @returns {Promise<FileWatcher>} Resolves with a connected watcher once startup is complete.
|
|
66
|
+
* **Await** this promise; if the engine fails during startup, the promise rejects.
|
|
67
|
+
*/
|
|
68
|
+
static async create(config) {
|
|
69
|
+
const impl = await Impl.create(config);
|
|
70
|
+
return new FileWatcher(impl, kCompose);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Request that a **concrete** filesystem path be watched in addition to the configured
|
|
75
|
+
* patterns (for example, a file the dev server started serving after startup). Glob or
|
|
76
|
+
* minimatch strings are not accepted here — only real paths; pattern-based includes stay in
|
|
77
|
+
* config.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} file Path relative to the current working directory or absolute, as
|
|
80
|
+
* accepted by the engine.
|
|
81
|
+
* @returns {Promise<void>} Settles when the engine has finished the add request. **Await**
|
|
82
|
+
* this promise (or attach `.catch()`) so validation errors, engine errors, and internal
|
|
83
|
+
* teardown on failure are observed.
|
|
84
|
+
*/
|
|
85
|
+
add(file) {
|
|
86
|
+
return this._impl.add(file);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Tear down the watcher: close the engine, release OS watch handles, and remove listeners on
|
|
91
|
+
* this `FileWatcher`. Safe to call more than once; subsequent calls resolve promptly.
|
|
92
|
+
*
|
|
93
|
+
* @returns {Promise<void>} Resolves when shutdown is complete. **Await** if later code must
|
|
94
|
+
* run only after handles are released.
|
|
95
|
+
*/
|
|
96
|
+
close() {
|
|
97
|
+
return Promise.resolve(this._impl.close()).then(() => {
|
|
98
|
+
this.removeAllListeners();
|
|
13
99
|
});
|
|
14
|
-
let onFileChanged = this.onFileChanged.bind(this);
|
|
15
|
-
this.fileWatcher.on('change', onFileChanged);
|
|
16
|
-
this.fileWatcher.on('add', onFileChanged);
|
|
17
|
-
this.fileWatcher.on('remove', onFileChanged);
|
|
18
|
-
this.fileWatcher.on('emfile', this.onEMFILE.bind(this));
|
|
19
|
-
|
|
20
|
-
let watchFiles = config.get('watch_files');
|
|
21
|
-
this.fileWatcher.clear();
|
|
22
|
-
let confFile = config.get('file');
|
|
23
|
-
if (confFile) {
|
|
24
|
-
this.fileWatcher.add(confFile);
|
|
25
|
-
}
|
|
26
|
-
if (config.isCwdMode()) {
|
|
27
|
-
this.fileWatcher.add('*.js');
|
|
28
|
-
}
|
|
29
|
-
if (watchFiles) {
|
|
30
|
-
this.fileWatcher.add(watchFiles);
|
|
31
|
-
}
|
|
32
|
-
let srcFiles = config.get('src_files') || '*.js';
|
|
33
|
-
this.fileWatcher.add(srcFiles);
|
|
34
|
-
let ignoreFiles = config.get('src_files_ignore');
|
|
35
|
-
if (ignoreFiles) {
|
|
36
|
-
this.fileWatcher.ignore(ignoreFiles);
|
|
37
|
-
}
|
|
38
100
|
}
|
|
39
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Manually signal that a path changed, for the same listeners as a real `fileChanged` event.
|
|
104
|
+
* Used in tests and narrow control flows; normal file edits are reported automatically by the
|
|
105
|
+
* engine.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} filePath Path that should be treated as changed.
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
40
110
|
onFileChanged(filePath) {
|
|
41
|
-
this.
|
|
111
|
+
return this._impl.onFileChanged(filePath);
|
|
42
112
|
}
|
|
43
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Manually emit the `EMFILE` condition (too many open files) for listeners that show user
|
|
116
|
+
* guidance. The real engine also surfaces this when appropriate.
|
|
117
|
+
*
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
44
120
|
onEMFILE() {
|
|
45
|
-
this.
|
|
121
|
+
return this._impl.onEMFILE();
|
|
46
122
|
}
|
|
123
|
+
}
|
|
47
124
|
|
|
48
|
-
|
|
49
|
-
this.fileWatcher.add(file);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
125
|
+
module.exports = FileWatcher;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal `FileWatcher` implementation (see {@link module:lib/file_watcher} for the public API).
|
|
3
|
+
*
|
|
4
|
+
* **Third-party pieces in use today:** `chokidar` (filesystem watch engine), `glob` (`hasMagic`
|
|
5
|
+
* for validating concrete `add` paths), and `minimatch` (include-pattern magic checks). Policy
|
|
6
|
+
* lists and matching live largely in {@link module:lib/utils/file_watch_glob_policy}.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here assumes a single vendor forever: the engine is created only through
|
|
9
|
+
* {@link FileWatcherImpl.createWatcher} (with `chokidar` loaded lazily there), and glob/minimatch
|
|
10
|
+
* are confined to small helpers—so swapping the watcher, glob detection, or minimatch usage is
|
|
11
|
+
* meant to be localized work rather than a rewrite.
|
|
12
|
+
*
|
|
13
|
+
* @module lib/file_watcher_impl
|
|
14
|
+
*/
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const EventEmitter = require('events').EventEmitter;
|
|
17
|
+
const { hasMagic } = require('glob');
|
|
18
|
+
const { Minimatch } = require('minimatch');
|
|
19
|
+
|
|
20
|
+
const isEmfileError = require('./utils/is_emfile_error');
|
|
21
|
+
const { convertToPosix } = require('./utils/posix');
|
|
22
|
+
const {
|
|
23
|
+
buildWatchGlobPolicy,
|
|
24
|
+
pathMatchesWatchTarget,
|
|
25
|
+
} = require('./utils/file_watch_glob_policy');
|
|
26
|
+
|
|
27
|
+
/** @private Only {@link FileWatcherImpl.create} may construct instances. */
|
|
28
|
+
const kInternal = Symbol('FileWatcherImpl.internal');
|
|
29
|
+
|
|
30
|
+
function includePatternHasGlobMagic(pattern) {
|
|
31
|
+
return new Minimatch(pattern, { dot: true }).hasMagic();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolvedPathIsUnderCwd(resolved) {
|
|
35
|
+
const rel = path.relative(process.cwd(), resolved);
|
|
36
|
+
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether the watch engine's `all` event (add/addDir) refers to the same path as `file` /
|
|
41
|
+
* `absTarget`.
|
|
42
|
+
*/
|
|
43
|
+
function dynamicAddEventMatchesPath(watchedPath, file, absTarget) {
|
|
44
|
+
const absWatched = path.resolve(watchedPath);
|
|
45
|
+
const relWatched = convertToPosix(
|
|
46
|
+
path.relative(process.cwd(), absWatched),
|
|
47
|
+
);
|
|
48
|
+
const relTarget = convertToPosix(
|
|
49
|
+
path.relative(process.cwd(), absTarget),
|
|
50
|
+
);
|
|
51
|
+
return (
|
|
52
|
+
absWatched === absTarget ||
|
|
53
|
+
relWatched === relTarget ||
|
|
54
|
+
watchedPath === file
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class FileWatcherImpl extends EventEmitter {
|
|
59
|
+
/**
|
|
60
|
+
* @param {{ get: function(string): *, isCwdMode: function(): boolean }} config
|
|
61
|
+
* @param {symbol} internalToken
|
|
62
|
+
*/
|
|
63
|
+
constructor(config, internalToken) {
|
|
64
|
+
super();
|
|
65
|
+
|
|
66
|
+
if (internalToken !== kInternal) {
|
|
67
|
+
throw new TypeError(
|
|
68
|
+
'FileWatcherImpl is not constructable; use FileWatcherImpl.create(config)',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const policy = buildWatchGlobPolicy(config);
|
|
73
|
+
this._watchPolicy = policy;
|
|
74
|
+
this._explicitWatchRelPaths = new Set();
|
|
75
|
+
const opts = { ignoreInitial: true };
|
|
76
|
+
if (policy.ignorePatterns.length) {
|
|
77
|
+
opts.ignored = policy.ignorePatterns;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.fileWatcher = FileWatcherImpl.createWatcher('.', opts);
|
|
81
|
+
|
|
82
|
+
let readySettled = false;
|
|
83
|
+
this._readyPromise = new Promise((resolve, reject) => {
|
|
84
|
+
this.fileWatcher.once('ready', () => {
|
|
85
|
+
readySettled = true;
|
|
86
|
+
resolve();
|
|
87
|
+
});
|
|
88
|
+
this.fileWatcher.once('error', (err) => {
|
|
89
|
+
if (readySettled || isEmfileError(err)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
readySettled = true;
|
|
93
|
+
reject(err);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < policy.includePatterns.length; i++) {
|
|
98
|
+
const pattern = policy.includePatterns[i];
|
|
99
|
+
if (includePatternHasGlobMagic(pattern)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const resolved = path.resolve(process.cwd(), pattern);
|
|
103
|
+
if (!resolvedPathIsUnderCwd(resolved)) {
|
|
104
|
+
this.fileWatcher.add(path.normalize(pattern));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const onEngineFsEvent = this.onEngineFsEvent.bind(this);
|
|
109
|
+
this.fileWatcher.on('change', onEngineFsEvent);
|
|
110
|
+
this.fileWatcher.on('add', onEngineFsEvent);
|
|
111
|
+
this.fileWatcher.on('unlink', onEngineFsEvent);
|
|
112
|
+
this.fileWatcher.on('unlinkDir', onEngineFsEvent);
|
|
113
|
+
this.fileWatcher.on('error', (err) => {
|
|
114
|
+
if (isEmfileError(err)) {
|
|
115
|
+
this.onEMFILE();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {{ get: function(string): *, isCwdMode: function(): boolean }} config
|
|
122
|
+
* @returns {Promise<FileWatcherImpl>}
|
|
123
|
+
*/
|
|
124
|
+
static async create(config) {
|
|
125
|
+
const fw = new FileWatcherImpl(config, kInternal);
|
|
126
|
+
await fw._readyPromise;
|
|
127
|
+
return fw;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onEngineFsEvent(filePath) {
|
|
131
|
+
const resolved = path.resolve(filePath);
|
|
132
|
+
const posixRel = convertToPosix(
|
|
133
|
+
path.relative(process.cwd(), resolved),
|
|
134
|
+
);
|
|
135
|
+
if (
|
|
136
|
+
pathMatchesWatchTarget(posixRel, this._watchPolicy) ||
|
|
137
|
+
this._explicitWatchRelPaths.has(posixRel)
|
|
138
|
+
) {
|
|
139
|
+
this.onFileChanged(filePath);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
onFileChanged(filePath) {
|
|
144
|
+
this.emit('fileChanged', filePath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
onEMFILE() {
|
|
148
|
+
this.emit('EMFILE');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {Error} err
|
|
153
|
+
* @returns {Promise<never>}
|
|
154
|
+
*/
|
|
155
|
+
async _closeAfterFailedAdd(err) {
|
|
156
|
+
try {
|
|
157
|
+
await this.close();
|
|
158
|
+
} catch {
|
|
159
|
+
// ignore close errors; primary error is `err`
|
|
160
|
+
}
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {string} file
|
|
166
|
+
* @returns {Promise<void>}
|
|
167
|
+
*/
|
|
168
|
+
async _validateConcreteAddPath(file) {
|
|
169
|
+
if (typeof file !== 'string') {
|
|
170
|
+
await this._closeAfterFailedAdd(
|
|
171
|
+
new TypeError('FileWatcher.add expects a string path'),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (file.length === 0) {
|
|
175
|
+
await this._closeAfterFailedAdd(
|
|
176
|
+
new TypeError('FileWatcher.add expects a non-empty path'),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (hasMagic(file, { magicalBraces: true })) {
|
|
180
|
+
await this._closeAfterFailedAdd(
|
|
181
|
+
new TypeError(
|
|
182
|
+
'FileWatcher.add does not accept glob patterns; pass a concrete file path: ' +
|
|
183
|
+
file,
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Wait for dynamic `add` on the engine to settle (`all` add/addDir, `error`, or timeout).
|
|
191
|
+
*
|
|
192
|
+
* @param {string} file
|
|
193
|
+
* @returns {Promise<void>}
|
|
194
|
+
*/
|
|
195
|
+
async _awaitEngineDynamicAdd(file) {
|
|
196
|
+
const engine = this.fileWatcher;
|
|
197
|
+
const absTarget = path.resolve(process.cwd(), file);
|
|
198
|
+
let onError;
|
|
199
|
+
let onAll;
|
|
200
|
+
let timeoutId;
|
|
201
|
+
|
|
202
|
+
const cleanup = () => {
|
|
203
|
+
if (onError) {
|
|
204
|
+
engine.removeListener('error', onError);
|
|
205
|
+
}
|
|
206
|
+
if (onAll) {
|
|
207
|
+
engine.removeListener('all', onAll);
|
|
208
|
+
}
|
|
209
|
+
if (timeoutId !== undefined) {
|
|
210
|
+
clearTimeout(timeoutId);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
await new Promise((resolve, reject) => {
|
|
215
|
+
let settled = false;
|
|
216
|
+
|
|
217
|
+
const finish = (err) => {
|
|
218
|
+
if (settled) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
settled = true;
|
|
222
|
+
cleanup();
|
|
223
|
+
if (err) {
|
|
224
|
+
reject(err);
|
|
225
|
+
} else {
|
|
226
|
+
resolve();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
onError = (err) => {
|
|
231
|
+
finish(err);
|
|
232
|
+
};
|
|
233
|
+
engine.prependOnceListener('error', onError);
|
|
234
|
+
|
|
235
|
+
onAll = (event, watchedPath) => {
|
|
236
|
+
if (event === 'add' || event === 'addDir') {
|
|
237
|
+
if (dynamicAddEventMatchesPath(watchedPath, file, absTarget)) {
|
|
238
|
+
finish();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
engine.on('all', onAll);
|
|
243
|
+
|
|
244
|
+
timeoutId = setTimeout(() => finish(), 1000);
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
engine.add(file);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
finish(err);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @param {string} file
|
|
256
|
+
* @returns {Promise<void>}
|
|
257
|
+
*/
|
|
258
|
+
async add(file) {
|
|
259
|
+
await this._validateConcreteAddPath(file);
|
|
260
|
+
const rel = convertToPosix(
|
|
261
|
+
path.relative(
|
|
262
|
+
process.cwd(),
|
|
263
|
+
path.resolve(process.cwd(), file),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
this._explicitWatchRelPaths.add(rel);
|
|
267
|
+
try {
|
|
268
|
+
await this._awaitEngineDynamicAdd(file);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
await this._closeAfterFailedAdd(err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @returns {Promise<void>}
|
|
276
|
+
*/
|
|
277
|
+
close() {
|
|
278
|
+
if (this.fileWatcher) {
|
|
279
|
+
const engine = this.fileWatcher;
|
|
280
|
+
this.fileWatcher = null;
|
|
281
|
+
return Promise.resolve(engine.close()).then(() => {
|
|
282
|
+
this.removeAllListeners();
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
this.removeAllListeners();
|
|
286
|
+
return Promise.resolve();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Lazy `require` keeps chokidar off the load path until a real watcher is built (e.g. tests stub this). */
|
|
291
|
+
FileWatcherImpl.createWatcher = function createWatcher(rootPath, opts) {
|
|
292
|
+
return require('chokidar').watch(rootPath, opts);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
module.exports = FileWatcherImpl;
|
package/lib/launcher.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
const log = require('./log');
|
|
4
|
-
const
|
|
2
|
+
const fs = require('fs');
|
|
5
3
|
const path = require('path');
|
|
6
4
|
const { fromCallback } = require('./utils/promises');
|
|
5
|
+
const { registerCleanup } = require('./utils/tmp-cleanup');
|
|
7
6
|
|
|
8
7
|
const template = require('./utils/strutils').template;
|
|
9
8
|
const ProcessCtl = require('./process-ctl');
|
|
10
9
|
|
|
11
|
-
// setup graceful cleanup: removes the created directories when an uncaught exception occurs.
|
|
12
|
-
tmp.setGracefulCleanup();
|
|
13
|
-
|
|
14
10
|
module.exports = class Launcher {
|
|
15
11
|
constructor(name, settings, config) {
|
|
16
12
|
this.name = name;
|
|
@@ -126,12 +122,11 @@ module.exports = class Launcher {
|
|
|
126
122
|
|
|
127
123
|
setupBrowserTmpDir() {
|
|
128
124
|
const userDataDir = this.config.getUserDataDir();
|
|
129
|
-
const tmpPath = path.join(userDataDir, 'testem-' + this.id);
|
|
125
|
+
const tmpPath = path.join(userDataDir, 'testem-' + this.id + '-');
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
127
|
+
const dir = fs.mkdtempSync(tmpPath);
|
|
128
|
+
registerCleanup(dir);
|
|
129
|
+
this.browserTmpDirectory = dir;
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
browserTmpDir() {
|
|
@@ -139,6 +134,6 @@ module.exports = class Launcher {
|
|
|
139
134
|
this.setupBrowserTmpDir();
|
|
140
135
|
}
|
|
141
136
|
|
|
142
|
-
return this.browserTmpDirectory
|
|
137
|
+
return this.browserTmpDirectory;
|
|
143
138
|
}
|
|
144
139
|
};
|