Skip to content

Commit 1b313de

Browse files
committed
server: Fix stuck test process when using qtap.run() with file not found
This didn't affect the CLI because: * that calls the runWaitFor() wrapper, which happened to work because it rejects after an 'error' event. And, while the main place to emit the 'error' event is qtap.run() and we don't reach that here, there was code in server.js emitting this directly. * it then calls process.exit(1), thus causing the HTTP Server instance to be force-closed. In testing, however, while qtap.run() and qtap.runWaitFor() work fine, the server stays running in the background, and thus the qunit process hangs forever because server.js emitted the 'error' event, while qtap.run() is still awaiting browserPromise, and thus we never got to the "finally", and thus the server still running. Avoid this kind of out-of-order processing by making sure the only place to emit the 'error' event is the qtap.run() code, and only after the 'finally' branch. So instead, pass the error to browser.stop(). That way, browserPromise will settle by throwing, and from there it will bubble up to qtap.run().
1 parent 514229c commit 1b313de

File tree

3 files changed

+29
-16
lines changed

3 files changed

+29
-16
lines changed

src/qtap.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,6 @@ function run (files, browserNames = 'detect', runOptions = {}) {
222222
})();
223223
runPromise
224224
.finally(() => {
225-
// Make sure we close our server even if the above throws, so that Node.js
226-
// may naturally exit (no open ports remaining)
227225
for (const server of servers) {
228226
server.close();
229227
}

src/server.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class ControlServer {
9090
this.browsers = new Map();
9191
// Optimization: Prefetch test file in parallel with server creation and browser launching.
9292
//
93-
// To prevent a global error (unhandledRejection), we add a no-op catch() handler here.
93+
// To prevent a Node.js error (unhandledRejection), we add a no-op catch() handler here.
9494
// Once launchBrowser is called, we will await this in handleRequest/getTestFile,
9595
// which is then propertly caught by server.on('request') below, which emits it
9696
// as 'error' event.
@@ -130,9 +130,20 @@ class ControlServer {
130130
await this.handleRequest(req, url, resp);
131131
}
132132
} catch (e) {
133-
this.logger.warning('respond_uncaught', req.url, String(e));
134-
eventbus.emit('error', e);
133+
this.logger.warning('server_respond_uncaught', e);
135134
this.serveError(resp, 500, /** @type {Error} */ (e));
135+
136+
// When we catch this, qtap.run() is awaiting ControlServer#launchBrowser
137+
// (as browerPromise). Make sure we don't get stuck, by stopping the browsers.
138+
// That way:
139+
// - launchBrowser() will throw/return,
140+
// - qtap.run() emit error/finish,
141+
// - qtap.runWaitFor() will throw/return.
142+
for (const browser of this.browsers.values()) {
143+
browser.stop(new util.BrowserStopSignal(String(e), {
144+
cause: e
145+
}));
146+
}
136147
}
137148
});
138149

@@ -144,8 +155,7 @@ class ControlServer {
144155
throw new Error('ControlServer.close must only be called once');
145156
}
146157
this.closeCalled = true;
147-
148-
this.logger.debug('http_close');
158+
this.logger.debug('server_close');
149159
server.close();
150160
server.closeAllConnections();
151161
};

test/qtap.test.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,6 @@ QUnit.module('qtap', function (hooks) {
7777
options,
7878
error: /Unknown browser unknown/,
7979
},
80-
'file not found': {
81-
files: 'notfound.html',
82-
browsers: 'fake',
83-
options: {
84-
...options,
85-
config: 'test/fixtures/qtap.config.js'
86-
},
87-
error: new Error('Could not open notfound.html'),
88-
},
8980
'config not found': {
9081
files: 'notfound.html',
9182
browsers: 'maybe',
@@ -333,6 +324,20 @@ QUnit.module('qtap', function (hooks) {
333324
],
334325
exitCode: 0
335326
},
327+
notfound: {
328+
files: 'notfound.html',
329+
browsers: 'fake',
330+
options: {
331+
...options,
332+
config: 'test/fixtures/qtap.config.js'
333+
},
334+
expected: [
335+
'client: running notfound.html',
336+
'online',
337+
'bail: Error: Could not open notfound.html',
338+
],
339+
exitCode: 1
340+
},
336341
qunitPass: {
337342
files: 'test/fixtures/qunit-pass.html',
338343
options,

0 commit comments

Comments
 (0)