diff --git a/.eslintrc.json b/.eslintrc.json index a98dc4c77a..83668f2826 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,6 +25,7 @@ }, "rules": { + "comma-dangle": ["error", "only-multiline"], "eol-last": "error", "no-empty": "off", "no-console": "off", diff --git a/bin/build-utils.js b/bin/build-utils.js index 29aa599562..41ccbc0071 100644 --- a/bin/build-utils.js +++ b/bin/build-utils.js @@ -48,9 +48,7 @@ function doBrowserify(pkgName, filepath, opts, exclude) { path.dirname(filepath) + '/' + path.basename(filepath)); }); } else { - bundler = browserify(addPath(pkgName, filepath), opts) - .transform('es3ify') - .plugin('bundle-collapser/plugin'); + bundler = browserify(addPath(pkgName, filepath), opts); } if (exclude) { diff --git a/bin/test-browser.js b/bin/test-browser.js index f0f91e2826..5d32585abc 100755 --- a/bin/test-browser.js +++ b/bin/test-browser.js @@ -6,12 +6,16 @@ const playwright = require('playwright'); const { identity, pickBy } = require('lodash'); var MochaSpecReporter = require('mocha').reporters.Spec; +const createMochaStatsCollector = require('mocha/lib/stats-collector'); var devserver = require('./dev-server.js'); // BAIL=0 to disable bailing var bail = process.env.BAIL !== '0'; +// Track if the browser has closed at the request of this script, or due to an external event. +let closeRequested; + // Playwright BrowserType whitelist. // See: https://playwright.dev/docs/api/class-playwright const SUPPORTED_BROWSERS = [ 'chromium', 'firefox', 'webkit' ]; @@ -56,33 +60,51 @@ const qs = { testUrl += '?'; testUrl += new URLSearchParams(pickBy(qs, identity)); +class ArrayMap extends Map { + get(key) { + if (!this.has(key)) { + this.set(key, []); + } + return super.get(key); + } +} + class RemoteRunner { constructor(browser) { this.browser = browser; - this.handlers = {}; + this.handlers = new ArrayMap(); + this.onceHandlers = new ArrayMap(); this.handleEvent = this.handleEvent.bind(this); + createMochaStatsCollector(this); + } + + once(name, handler) { + this.onceHandlers.get(name).push(handler); } on(name, handler) { - var handlers = this.handlers; + this.handlers.get(name).push(handler); + } - if (!handlers[name]) { - handlers[name] = []; - } - handlers[name].push(handler); + triggerHandlers(eventName, handlerArgs) { + const triggerHandler = handler => handler.apply(null, handlerArgs); + + this.onceHandlers.get(eventName).forEach(triggerHandler); + this.onceHandlers.delete(eventName); + + this.handlers.get(eventName).forEach(triggerHandler); } async handleEvent(event) { try { var additionalProps = ['pass', 'fail', 'pending'].indexOf(event.name) === -1 ? {} : { slow: event.obj.slow ? function () { return event.obj.slow; } : function () { return 60; }, - fullTitle: event.obj.fullTitle ? function () { return event.obj.fullTitle; } : undefined + fullTitle: event.obj.fullTitle ? function () { return event.obj.fullTitle; } : undefined, + titlePath: event.obj.titlePath ? function () { return event.obj.titlePath; } : undefined, }; var obj = Object.assign({}, event.obj, additionalProps); - this.handlers[event.name].forEach(function (handler) { - handler(obj, event.err); - }); + this.triggerHandlers(event.name, [ obj, event.err ]); switch (event.name) { case 'fail': this.handleFailed(); break; @@ -91,12 +113,14 @@ class RemoteRunner { } catch (e) { console.error('Tests failed:', e); + closeRequested = true; await this.browser.close(); process.exit(3); } } async handleEnd(failed) { + closeRequested = true; await this.browser.close(); process.exit(!process.env.PERF && failed ? 1 : 0); } @@ -104,9 +128,7 @@ class RemoteRunner { handleFailed() { if (bail) { try { - this.handlers['end'].forEach(function (handler) { - handler(); - }); + this.triggerHandlers('end'); } catch (e) { console.log('An error occurred while bailing:', e); } finally { @@ -147,8 +169,20 @@ async function startTest() { headless: true, }; const browser = await playwright[browserName].launch(options); + const page = await browser.newPage(); + // Playwright's Browser.on('close') event handler would be the more obvious + // choice here, but it does not seem to be triggered if the browser is closed + // by an external event (e.g. process is killed, user closes non-headless + // browser window). + page.on('close', () => { + if (!closeRequested) { + console.log('!!! Browser closed by external event.'); + process.exit(1); + } + }); + const runner = new RemoteRunner(browser); new MochaSpecReporter(runner); new BenchmarkConsoleReporter(runner); diff --git a/bin/test-node.sh b/bin/test-node.sh index c2a7f03159..1ab2b0a9af 100755 --- a/bin/test-node.sh +++ b/bin/test-node.sh @@ -37,7 +37,9 @@ fi if [ "$PERF" ]; then node tests/performance/index.js elif [ ! "$COVERAGE" ]; then + # --exit required to workaround #8839 ./node_modules/.bin/mocha \ + --exit \ "$BAIL_OPT" \ --timeout "$TIMEOUT" \ --require=./tests/integration/node.setup.js \ @@ -45,9 +47,11 @@ elif [ ! "$COVERAGE" ]; then --grep="$GREP" \ "$TESTS_PATH" else + # --exit required to workaround #8839 ./node_modules/.bin/istanbul cover \ --no-default-excludes -x 'tests/**' -x 'node_modules/**' \ ./node_modules/mocha/bin/_mocha -- \ + --exit \ "$BAIL_OPT" \ --timeout "$TIMEOUT" \ --require=./tests/integration/node.setup.js \ diff --git a/package.json b/package.json index a283289395..c09ce4f90f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build-modules": "node bin/build-modules.js", "test-unit": "mocha tests/unit", "test-node": "./bin/test-node.sh", - "test-component": "mocha tests/component", + "test-component": "mocha --exit tests/component # --exit for #8839", "test-fuzzy": "TYPE=fuzzy npm run test", "test-browser": "npm run build-test && node ./bin/test-browser.js", "test-memleak": "mocha -gc tests/memleak", @@ -41,7 +41,7 @@ "abort-controller": "3.0.0", "clone-buffer": "1.0.0", "double-ended-queue": "2.1.0-0", - "fetch-cookie": "0.11.0", + "fetch-cookie": "2.1.0", "fruitdown": "1.0.2", "immediate": "3.3.0", "level": "6.0.1", @@ -66,7 +66,6 @@ "browserify": "16.4.0", "browserify-incremental": "3.1.1", "builtin-modules": "3.1.0", - "bundle-collapser": "1.3.0", "chai": "3.5.0", "chai-as-promised": "5.3.0", "change-case": "4.0.1", @@ -74,7 +73,6 @@ "cssmin": "0.4.3", "denodeify": "1.2.1", "derequire": "2.1.1", - "es3ify": "0.2.2", "eslint": "8.7.0", "express": "4.17.0", "express-pouchdb": "4.2.0", @@ -90,7 +88,7 @@ "marky": "1.2.0", "median": "0.0.2", "mkdirp": "0.5.1", - "mocha": "3.5.0", + "mocha": "10.2.0", "mockery": "2.1.0", "ncp": "2.0.0", "playwright": "1.36.1", diff --git a/tests/component/test.deletion_error.js b/tests/component/test.deletion_error.js index 62902b1c7c..2f07520da1 100644 --- a/tests/component/test.deletion_error.js +++ b/tests/component/test.deletion_error.js @@ -29,8 +29,8 @@ describe('test.deletion_error.js', function () { server = app.listen(0); }); - after(function () { - return server.close(); + after(function (done) { + server.close(done); }); it('Test error during deletion', function () { diff --git a/tests/component/test.read_only_replication.js b/tests/component/test.read_only_replication.js index d070878edd..55040cf059 100644 --- a/tests/component/test.read_only_replication.js +++ b/tests/component/test.read_only_replication.js @@ -38,8 +38,8 @@ describe('test.read_only_replication.js', function () { server = app.listen(0); }); - after(function () { - return server.close(); + after(function (done) { + server.close(done); }); it('Test checkpointer handles error codes', function () { diff --git a/tests/component/test.replication_perf_regression.js b/tests/component/test.replication_perf_regression.js index 953d1119ef..84595cdd3b 100644 --- a/tests/component/test.replication_perf_regression.js +++ b/tests/component/test.replication_perf_regression.js @@ -34,8 +34,8 @@ describe('test.replication_perf_regression.js', function () { }; }); - after(function () { - return server.close(); + after(function (done) { + server.close(done); }); it('#5199 fix excessively long replication loop', function () { diff --git a/tests/find/test-suite-1/test.default-index.js b/tests/find/test-suite-1/test.default-index.js index afcaaa954b..61700da0de 100644 --- a/tests/find/test-suite-1/test.default-index.js +++ b/tests/find/test-suite-1/test.default-index.js @@ -126,7 +126,7 @@ describe('test.default-index.js', function () { ]).then(function () { return db.find({ selector: {foo: {$ne: "eba"}}, - fields: ["_id",], + fields: ["_id"], sort: ["_id"] }); }).then(function (resp) { diff --git a/tests/integration/browser.migration.js b/tests/integration/browser.migration.js index 2936a2ba60..e721649d7b 100644 --- a/tests/integration/browser.migration.js +++ b/tests/integration/browser.migration.js @@ -80,6 +80,12 @@ describe('migration', function () { var dbs = {}; + before(function () { + if (usingIndexeddb() && !versionGte(scenario, '7.2.1')) { + return this.skip(); + } + }); + beforeEach(function (done) { if (skip) { return this.skip(); @@ -122,12 +128,6 @@ describe('migration', function () { testUtils.cleanup([dbs.first.local, dbs.second.local], done); }); - before(function () { - if (usingIndexeddb() && !versionGte(scenario, '7.2.1')) { - return this.skip(); - } - }); - var origDocs = [ {_id: '0', a: 1, b: 1}, {_id: '3', a: 4, b: 16}, diff --git a/tests/integration/webrunner.js b/tests/integration/webrunner.js index 4720b5e4b2..1577c9663a 100644 --- a/tests/integration/webrunner.js +++ b/tests/integration/webrunner.js @@ -21,7 +21,8 @@ title: obj.title, duration: obj.duration, slow: typeof obj.slow === 'function' ? obj.slow() : undefined, - fullTitle: typeof obj.fullTitle === 'function' ? obj.fullTitle() : undefined + fullTitle: typeof obj.fullTitle === 'function' ? obj.fullTitle() : undefined, + titlePath: typeof obj.titlePath === 'function' ? obj.titlePath() : undefined, }, err: err && { actual: err.actual,