Add startHTTPServer() support function

Centralize httpd creation and add automatic retry to try to deal with
NS_ERROR_SOCKET_ADDRESS_IN_USE errors in CI.
This commit is contained in:
Dan Stillman 2023-08-16 01:10:56 -04:00
parent 17daf9fe8d
commit fb96cd595d
5 changed files with 75 additions and 83 deletions

View file

@ -1094,3 +1094,26 @@ function setHTTPResponse(server, baseURL, response, responses, username, passwor
server.respondWith(response.method, baseURL + response.url, responseArray); server.respondWith(response.method, baseURL + response.url, responseArray);
} }
} }
let httpdServerPort = 16213;
/**
* @param {Number} [port] - Port number to use. If not provided, one is picked automatically.
* @return {Promise<{ httpd: Object, port: Number }>}
*/
async function startHTTPServer(port = null) {
if (!port) {
port = httpdServerPort;
}
Components.utils.import("resource://zotero-unit/httpd.js");
var httpd = new HttpServer();
while (true) {
try {
httpd.start(port);
break;
}
catch (e) {
await Zotero.Promise.delay(10);
}
}
return { httpd, port };
}

View file

@ -326,31 +326,20 @@ describe("Zotero.Attachments", function() {
describe("#importFromDocument()", function () { describe("#importFromDocument()", function () {
Components.utils.import("resource://gre/modules/FileUtils.jsm"); Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://zotero-unit/httpd.js");
var testServerPath, httpd, prefix; var testServerPath, httpd, prefix;
var testServerPortMin = 16213; var testServerPort;
var testServerPortMax = testServerPortMin + 20;
var testServerPort = testServerPortMin;
before(async function () { before(async function () {
this.timeout(20000); this.timeout(20000);
Zotero.Prefs.set("httpServer.enabled", true); Zotero.Prefs.set("httpServer.enabled", true);
}); });
beforeEach(function () { beforeEach(async function () {
// Cycle through ports to prevent NS_ERROR_SOCKET_ADDRESS_IN_USE errors from server
// not always fully stopping in time
if (testServerPort < testServerPortMax) {
testServerPort++;
}
else {
testServerPort = testServerPortMin;
}
// Use random prefix because httpd does not actually stop between tests // Use random prefix because httpd does not actually stop between tests
prefix = Zotero.Utilities.randomString(); prefix = Zotero.Utilities.randomString();
({ httpd, port: testServerPort } = await startHTTPServer());
testServerPath = 'http://127.0.0.1:' + testServerPort + '/' + prefix; testServerPath = 'http://127.0.0.1:' + testServerPort + '/' + prefix;
httpd = new HttpServer();
httpd.start(testServerPort);
}); });
afterEach(async function () { afterEach(async function () {
@ -586,7 +575,6 @@ describe("Zotero.Attachments", function() {
var pageURL9 = 'http://website/article9'; var pageURL9 = 'http://website/article9';
var pageURL10 = 'http://website/refresh'; var pageURL10 = 'http://website/refresh';
Components.utils.import("resource://zotero-unit/httpd.js");
var httpd; var httpd;
var port = 16213; var port = 16213;
var baseURL = `http://localhost:${port}/`; var baseURL = `http://localhost:${port}/`;
@ -821,8 +809,7 @@ describe("Zotero.Attachments", function() {
}); });
beforeEach(async function () { beforeEach(async function () {
httpd = new HttpServer(); ({ httpd } = await startHTTPServer(port));
httpd.start(port);
httpd.registerFile( httpd.registerFile(
pdfURL.substr(baseURL.length - 1), pdfURL.substr(baseURL.length - 1),
Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'test.pdf')) Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'test.pdf'))

View file

@ -4,17 +4,12 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
// //
// Setup // Setup
// //
Components.utils.import("resource://zotero-unit/httpd.js"); const davScheme = "http";
const davBasePath = "/webdav/";
const davUsername = "user";
const davPassword = "password";
var davScheme = "http"; var win, controller, server, requestCount, httpd, davHostPath, davURL;
var davPort = 16214;
var davBasePath = "/webdav/";
var davHostPath = `localhost:${davPort}${davBasePath}`;
var davUsername = "user";
var davPassword = "password";
var davURL = `${davScheme}://${davHostPath}`;
var win, controller, server, requestCount;
var responses = {}; var responses = {};
function setResponse(response) { function setResponse(response) {
@ -46,8 +41,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
return params; return params;
} }
beforeEach(function* () { beforeEach(async function () {
yield resetDB({ await resetDB({
thisArg: this, thisArg: this,
skipBundledFiles: true skipBundledFiles: true
}); });
@ -56,11 +51,13 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
server = sinon.fakeServer.create(); server = sinon.fakeServer.create();
server.autoRespond = true; server.autoRespond = true;
this.httpd = new HttpServer(); var port;
this.httpd.start(davPort); ({ httpd, port } = await startHTTPServer());
davHostPath = `localhost:${port}${davBasePath}`;
davURL = `${davScheme}://${davHostPath}`;
yield Zotero.Users.setCurrentUserID(1); await Zotero.Users.setCurrentUserID(1);
yield Zotero.Users.setCurrentUsername("testuser"); await Zotero.Users.setCurrentUsername("testuser");
Zotero.Sync.Storage.Local.setModeForLibrary(Zotero.Libraries.userLibraryID, 'webdav'); Zotero.Sync.Storage.Local.setModeForLibrary(Zotero.Libraries.userLibraryID, 'webdav');
controller = new Zotero.Sync.Storage.Mode.WebDAV; controller = new Zotero.Sync.Storage.Mode.WebDAV;
@ -124,7 +121,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
}) })
afterEach(async function () { afterEach(async function () {
await new Promise(request => this.httpd.stop(request)); await new Promise(request => httpd.stop(request));
}) })
after(function* () { after(function* () {
@ -248,7 +245,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
+ '<hash>8286300a280f64a4b5cfaac547c21d32</hash>' + '<hash>8286300a280f64a4b5cfaac547c21d32</hash>'
+ '</properties>' + '</properties>'
}); });
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/${item.key}.zip`, `${davBasePath}zotero/${item.key}.zip`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -312,7 +309,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
+ `<hash>${md5}</hash>` + `<hash>${md5}</hash>`
+ '</properties>' + '</properties>'
}); });
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/${item.key}.zip`, `${davBasePath}zotero/${item.key}.zip`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -621,7 +618,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
yield OS.File.remove(zipPath); yield OS.File.remove(zipPath);
// OPTIONS request to cache credentials // OPTIONS request to cache credentials
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/`, `${davBasePath}zotero/`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -644,7 +641,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
} }
} }
); );
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/${item.key}.prop`, `${davBasePath}zotero/${item.key}.prop`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -672,7 +669,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
} }
} }
); );
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/${item.key}.zip`, `${davBasePath}zotero/${item.key}.zip`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -777,7 +774,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
it("should show an error for a 403", function* () { it("should show an error for a 403", function* () {
Zotero.HTTP.mock = null; Zotero.HTTP.mock = null;
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/`, `${davBasePath}zotero/`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -816,7 +813,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
Zotero.HTTP.mock = null; Zotero.HTTP.mock = null;
Zotero.Prefs.set("sync.storage.url", davHostPath); Zotero.Prefs.set("sync.storage.url", davHostPath);
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/`, `${davBasePath}zotero/`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -831,7 +828,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
} }
} }
); );
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}`, `${davBasePath}`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -870,7 +867,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
it("should show an error for a 200 for a nonexistent file", async function () { it("should show an error for a 200 for a nonexistent file", async function () {
Zotero.HTTP.mock = null; Zotero.HTTP.mock = null;
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/`, `${davBasePath}zotero/`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -891,7 +888,7 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
} }
} }
); );
this.httpd.registerPathHandler( httpd.registerPathHandler(
`${davBasePath}zotero/nonexistent.prop`, `${davBasePath}zotero/nonexistent.prop`,
{ {
handle: function (request, response) { handle: function (request, response) {

View file

@ -4,13 +4,9 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
// //
// Setup // Setup
// //
Components.utils.import("resource://zotero-unit/httpd.js");
var apiKey = Zotero.Utilities.randomString(24); var apiKey = Zotero.Utilities.randomString(24);
var port = 16213;
var baseURL = `http://localhost:${port}/`;
var win, server, requestCount; var win, server, requestCount, httpd, baseURL;
var responses = {}; var responses = {};
function setResponse(response) { function setResponse(response) {
@ -45,22 +41,23 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
// //
// Tests // Tests
// //
beforeEach(function* () { beforeEach(async function () {
yield resetDB({ await resetDB({
thisArg: this, thisArg: this,
skipBundledFiles: true skipBundledFiles: true
}); });
win = yield loadZoteroPane(); win = await loadZoteroPane();
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest; Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
server = sinon.fakeServer.create(); server = sinon.fakeServer.create();
server.autoRespond = true; server.autoRespond = true;
this.httpd = new HttpServer(); var port;
this.httpd.start(port); ({ httpd, port } = await startHTTPServer());
baseURL = `http://localhost:${port}/`;
yield Zotero.Users.setCurrentUserID(1); await Zotero.Users.setCurrentUserID(1);
yield Zotero.Users.setCurrentUsername("testuser"); await Zotero.Users.setCurrentUsername("testuser");
Zotero.Sync.Storage.Local.setModeForLibrary(Zotero.Libraries.userLibraryID, 'zfs'); Zotero.Sync.Storage.Local.setModeForLibrary(Zotero.Libraries.userLibraryID, 'zfs');
@ -103,7 +100,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
afterEach(function* () { afterEach(function* () {
var defer = new Zotero.Promise.defer(); var defer = new Zotero.Promise.defer();
this.httpd.stop(() => defer.resolve()); httpd.stop(() => defer.resolve());
yield defer.promise; yield defer.promise;
win.close(); win.close();
}) })
@ -148,7 +145,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
item.attachmentSyncState = "to_download"; item.attachmentSyncState = "to_download";
yield item.saveTx(); yield item.saveTx();
this.httpd.registerPathHandler( httpd.registerPathHandler(
`/users/1/items/${item.key}/file`, `/users/1/items/${item.key}/file`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -214,7 +211,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
item.attachmentSyncState = "to_download"; item.attachmentSyncState = "to_download";
yield item.saveTx(); yield item.saveTx();
this.httpd.registerPathHandler( httpd.registerPathHandler(
`/users/1/items/${item.key}/file`, `/users/1/items/${item.key}/file`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -251,7 +248,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var md5 = Zotero.Utilities.Internal.md5(text) var md5 = Zotero.Utilities.Internal.md5(text)
var s3Path = `pretend-s3/${item.key}`; var s3Path = `pretend-s3/${item.key}`;
this.httpd.registerPathHandler( httpd.registerPathHandler(
`/users/1/items/${item.key}/file`, `/users/1/items/${item.key}/file`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -272,7 +269,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
} }
} }
); );
this.httpd.registerPathHandler( httpd.registerPathHandler(
"/" + s3Path, "/" + s3Path,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -313,7 +310,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var md5 = Zotero.Utilities.Internal.md5(text); var md5 = Zotero.Utilities.Internal.md5(text);
var s3Path = `pretend-s3/${item.key}`; var s3Path = `pretend-s3/${item.key}`;
this.httpd.registerPathHandler( httpd.registerPathHandler(
`/users/1/items/${item.key}/file`, `/users/1/items/${item.key}/file`,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -325,7 +322,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
} }
} }
); );
this.httpd.registerPathHandler( httpd.registerPathHandler(
"/" + s3Path, "/" + s3Path,
{ {
handle: function (request, response) { handle: function (request, response) {
@ -687,7 +684,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var md5 = Zotero.Utilities.Internal.md5(file) var md5 = Zotero.Utilities.Internal.md5(file)
var s3Path = `pretend-s3/${item.key}`; var s3Path = `pretend-s3/${item.key}`;
this.httpd.registerPathHandler( httpd.registerPathHandler(
`/users/1/items/${item.key}/file`, `/users/1/items/${item.key}/file`,
{ {
handle: function (request, response) { handle: function (request, response) {

View file

@ -141,11 +141,7 @@ describe("ZoteroPane", function() {
}) })
describe("#viewAttachment", function () { describe("#viewAttachment", function () {
Components.utils.import("resource://zotero-unit/httpd.js");
var apiKey = Zotero.Utilities.randomString(24); var apiKey = Zotero.Utilities.randomString(24);
var testServerPortMin = 16213;
var testServerPortMax = testServerPortMin + 20;
var testServerPort = testServerPortMin;
var baseURL; var baseURL;
var httpd; var httpd;
@ -202,23 +198,15 @@ describe("ZoteroPane", function() {
before(function () { before(function () {
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest; Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
}) })
beforeEach(function* () { beforeEach(async function () {
// Cycle through ports to prevent NS_ERROR_SOCKET_ADDRESS_IN_USE errors from server var port;
// not always fully stopping in time ({ httpd, port } = await startHTTPServer());
if (testServerPort < testServerPortMax) { baseURL = `http://localhost:${port}/`;
testServerPort++;
}
else {
testServerPort = testServerPortMin;
}
baseURL = `http://localhost:${testServerPort}/`;
Zotero.Prefs.set("api.url", baseURL); Zotero.Prefs.set("api.url", baseURL);
httpd = new HttpServer();
httpd.start(testServerPort);
Zotero.Sync.Runner.apiKey = apiKey; Zotero.Sync.Runner.apiKey = apiKey;
yield Zotero.Users.setCurrentUserID(1); await Zotero.Users.setCurrentUserID(1);
yield Zotero.Users.setCurrentUsername("testuser"); await Zotero.Users.setCurrentUsername("testuser");
}) })
afterEach(function* () { afterEach(function* () {
var defer = new Zotero.Promise.defer(); var defer = new Zotero.Promise.defer();