From 4924b4997492e15140870d03e01da55b142f8248 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Tue, 13 Feb 2018 12:32:23 -0800 Subject: [PATCH 1/8] Adding async timer detection support in tests --- addon-test-support/ember-qunit/index.js | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/addon-test-support/ember-qunit/index.js b/addon-test-support/ember-qunit/index.js index 1c463544..2d41ff9e 100644 --- a/addon-test-support/ember-qunit/index.js +++ b/addon-test-support/ember-qunit/index.js @@ -6,6 +6,7 @@ export { module, test, skip, only, todo } from 'qunit'; export { loadTests } from './test-loader'; import { deprecate } from '@ember/debug'; +import { run } from '@ember/runloop'; import { loadTests } from './test-loader'; import Ember from 'ember'; import QUnit from 'qunit'; @@ -24,8 +25,11 @@ import { setupApplicationContext, teardownApplicationContext, validateErrorHandler, + getSettledState, } from '@ember/test-helpers'; +const TESTS_WITH_LEAKY_ASYNC = []; + export function setResolver() { deprecate( '`setResolver` should be imported from `@ember/test-helpers`, but was imported from `ember-qunit`', @@ -228,6 +232,28 @@ export function setupEmberOnerrorValidation() { }); } +export function setupAsyncTimerLeakDetection() { + QUnit.testDone(({ module, name }) => { + let { hasPendingTimers } = getSettledState(); + + if (hasPendingTimers) { + TESTS_WITH_LEAKY_ASYNC.push(`${module}: ${name}`); + run.cancelTimers(); + } + }); + + QUnit.done(() => { + if (TESTS_WITH_LEAKY_ASYNC.length > 0) { + throw new Error( + `ASYNC LEAKAGE DETECTED IN TESTS + The following (${TESTS_WITH_LEAKY_ASYNC.length}) tests setup a timer that was never torn down before the test completed: \n + ${TESTS_WITH_LEAKY_ASYNC.join('\n')} + ` + ); + } + }); +} + /** @method start @param {Object} [options] Options to be used for enabling/disabling behaviors @@ -265,6 +291,10 @@ export function start(options = {}) { setupEmberOnerrorValidation(); } + if (options.setupAsyncTimerLeakDetection !== false) { + setupAsyncTimerLeakDetection(); + } + if (options.startTests !== false) { startTests(); } From a406a56530a7d316da57891b057d622a81bdd328 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Tue, 13 Feb 2018 16:12:00 -0800 Subject: [PATCH 2/8] Adding tests --- .../ember-qunit/async-timer-leak-detection.js | 23 +++++++ addon-test-support/ember-qunit/index.js | 15 +--- testem.js | 17 +++-- .../setup-async-timer-leak-detection-test.js | 69 +++++++++++++++++++ 4 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 addon-test-support/ember-qunit/async-timer-leak-detection.js create mode 100644 tests/unit/setup-async-timer-leak-detection-test.js diff --git a/addon-test-support/ember-qunit/async-timer-leak-detection.js b/addon-test-support/ember-qunit/async-timer-leak-detection.js new file mode 100644 index 00000000..43f3a449 --- /dev/null +++ b/addon-test-support/ember-qunit/async-timer-leak-detection.js @@ -0,0 +1,23 @@ +export function detectPendingTimers( + hasPendingTimers, + pendingTimers, + testModule, + testName, + cancelTimers +) { + if (hasPendingTimers) { + pendingTimers.push(`${testModule}: ${testName}`); + cancelTimers(); + } +} + +export function reportPendingTimers(pendingTimers) { + if (pendingTimers.length > 0) { + throw new Error( + `ASYNC LEAKAGE DETECTED IN TESTS + The following (${pendingTimers.length}) tests setup a timer that was never torn down before the test completed: \n + ${pendingTimers.join('\n')} + ` + ); + } +} diff --git a/addon-test-support/ember-qunit/index.js b/addon-test-support/ember-qunit/index.js index 2d41ff9e..ca734284 100644 --- a/addon-test-support/ember-qunit/index.js +++ b/addon-test-support/ember-qunit/index.js @@ -27,6 +27,7 @@ import { validateErrorHandler, getSettledState, } from '@ember/test-helpers'; +import { detectPendingTimers, reportPendingTimers } from './async-timer-leak-detection'; const TESTS_WITH_LEAKY_ASYNC = []; @@ -236,21 +237,11 @@ export function setupAsyncTimerLeakDetection() { QUnit.testDone(({ module, name }) => { let { hasPendingTimers } = getSettledState(); - if (hasPendingTimers) { - TESTS_WITH_LEAKY_ASYNC.push(`${module}: ${name}`); - run.cancelTimers(); - } + detectPendingTimers(hasPendingTimers, TESTS_WITH_LEAKY_ASYNC, module, name, run.cancelTimers); }); QUnit.done(() => { - if (TESTS_WITH_LEAKY_ASYNC.length > 0) { - throw new Error( - `ASYNC LEAKAGE DETECTED IN TESTS - The following (${TESTS_WITH_LEAKY_ASYNC.length}) tests setup a timer that was never torn down before the test completed: \n - ${TESTS_WITH_LEAKY_ASYNC.join('\n')} - ` - ); - } + reportPendingTimers(TESTS_WITH_LEAKY_ASYNC); }); } diff --git a/testem.js b/testem.js index 1993f327..03946afc 100644 --- a/testem.js +++ b/testem.js @@ -9,12 +9,15 @@ module.exports = { 'Chrome' ], browser_args: { - Chrome: [ - '--disable-gpu', - '--headless', - '--no-sandbox', - '--remote-debugging-port=9222', - '--window-size=1440,900' - ] + Chrome: { + mode: 'ci', + args: [ + '--disable-gpu', + '--headless', + '--no-sandbox', + '--remote-debugging-port=9222', + '--window-size=1440,900' + ] + } } }; diff --git a/tests/unit/setup-async-timer-leak-detection-test.js b/tests/unit/setup-async-timer-leak-detection-test.js new file mode 100644 index 00000000..42db997d --- /dev/null +++ b/tests/unit/setup-async-timer-leak-detection-test.js @@ -0,0 +1,69 @@ +import { module, test } from 'qunit'; +import { detectPendingTimers, reportPendingTimers } from 'ember-qunit/async-timer-leak-detection'; + +module('setupEmberOnerrorValidation', function() { + test('detectPendingTimers does not queue test info when leaky async timers not detected', function( + assert + ) { + assert.expect(2); + + let hasPendingTimers = false; + let pendingTimers = []; + let callCount = 0; + let cancelTimers = () => { + callCount++; + }; + + detectPendingTimers(hasPendingTimers, pendingTimers, '', '', cancelTimers); + + assert.equal(callCount, 0, 'cancel was not called'); + assert.equal(pendingTimers.length, 0, 'pending timers has no pending messages'); + }); + + test('detectPendingTimers correctly queues test info when leaky async timers detected', function( + assert + ) { + assert.expect(3); + + let hasPendingTimers = true; + let pendingTimers = []; + let callCount = 0; + let cancelTimers = () => { + callCount++; + }; + + detectPendingTimers(hasPendingTimers, pendingTimers, 'module', 'test name', cancelTimers); + + assert.equal(callCount, 1, 'cancel was called'); + assert.equal(pendingTimers.length, 1, 'pending timers has a pending message'); + assert.equal( + pendingTimers[0], + 'module: test name', + 'pending timers contains the correct message' + ); + }); + + test('reportPendingTimers does not throw when no pending timers exist', function(assert) { + assert.expect(1); + + reportPendingTimers([]); + + assert.ok(true); + }); + + test('reportPendingTimers throws when pending timers exist', function(assert) { + assert.expect(1); + + let pendingTimers = []; + pendingTimers.push('module: test name'); + + assert.throws( + function() { + reportPendingTimers(pendingTimers); + }, + `ASYNC LEAKAGE DETECTED IN TESTS + The following (1) tests setup a timer that was never torn down before the test completed: \n + module: test name` + ); + }); +}); From 193bd8b5af7b0d7566c94e66fb65049afa8ec4e1 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Tue, 13 Feb 2018 16:29:59 -0800 Subject: [PATCH 3/8] Fixing prettier errors --- tests/unit/setup-async-timer-leak-detection-test.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/setup-async-timer-leak-detection-test.js b/tests/unit/setup-async-timer-leak-detection-test.js index 42db997d..3ab6d2f6 100644 --- a/tests/unit/setup-async-timer-leak-detection-test.js +++ b/tests/unit/setup-async-timer-leak-detection-test.js @@ -2,9 +2,7 @@ import { module, test } from 'qunit'; import { detectPendingTimers, reportPendingTimers } from 'ember-qunit/async-timer-leak-detection'; module('setupEmberOnerrorValidation', function() { - test('detectPendingTimers does not queue test info when leaky async timers not detected', function( - assert - ) { + test('detectPendingTimers does not queue test info when leaky async timers not detected', function(assert) { assert.expect(2); let hasPendingTimers = false; @@ -20,9 +18,7 @@ module('setupEmberOnerrorValidation', function() { assert.equal(pendingTimers.length, 0, 'pending timers has no pending messages'); }); - test('detectPendingTimers correctly queues test info when leaky async timers detected', function( - assert - ) { + test('detectPendingTimers correctly queues test info when leaky async timers detected', function(assert) { assert.expect(3); let hasPendingTimers = true; From 2643f843814feff909a2df42375c002e095bbe6e Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Thu, 15 Feb 2018 08:19:46 -0800 Subject: [PATCH 4/8] Fixing tests to test actual behavior --- .../ember-qunit/async-timer-leak-detection.js | 30 +++++----- addon-test-support/ember-qunit/index.js | 17 ++---- .../setup-async-timer-leak-detection-test.js | 55 +++++-------------- 3 files changed, 36 insertions(+), 66 deletions(-) diff --git a/addon-test-support/ember-qunit/async-timer-leak-detection.js b/addon-test-support/ember-qunit/async-timer-leak-detection.js index 43f3a449..5015b034 100644 --- a/addon-test-support/ember-qunit/async-timer-leak-detection.js +++ b/addon-test-support/ember-qunit/async-timer-leak-detection.js @@ -1,22 +1,26 @@ -export function detectPendingTimers( - hasPendingTimers, - pendingTimers, - testModule, - testName, - cancelTimers -) { +import { run } from '@ember/runloop'; +import { getSettledState } from '@ember/test-helpers'; + +const TESTS_WITH_LEAKY_ASYNC = []; + +export function detectPendingTimers({ module, name }) { + let { hasPendingTimers } = getSettledState(); + if (hasPendingTimers) { - pendingTimers.push(`${testModule}: ${testName}`); - cancelTimers(); + TESTS_WITH_LEAKY_ASYNC.push(`${module}: ${name}`); + run.cancelTimers(); } } -export function reportPendingTimers(pendingTimers) { - if (pendingTimers.length > 0) { +export function reportPendingTimers() { + if (TESTS_WITH_LEAKY_ASYNC.length > 0) { + let leakyTests = TESTS_WITH_LEAKY_ASYNC.slice(); + TESTS_WITH_LEAKY_ASYNC.length = 0; + throw new Error( `ASYNC LEAKAGE DETECTED IN TESTS - The following (${pendingTimers.length}) tests setup a timer that was never torn down before the test completed: \n - ${pendingTimers.join('\n')} + The following (${leakyTests.length}) tests setup a timer that was never torn down before the test completed: \n + ${leakyTests.join('\n')} ` ); } diff --git a/addon-test-support/ember-qunit/index.js b/addon-test-support/ember-qunit/index.js index ca734284..e5b6c992 100644 --- a/addon-test-support/ember-qunit/index.js +++ b/addon-test-support/ember-qunit/index.js @@ -6,7 +6,6 @@ export { module, test, skip, only, todo } from 'qunit'; export { loadTests } from './test-loader'; import { deprecate } from '@ember/debug'; -import { run } from '@ember/runloop'; import { loadTests } from './test-loader'; import Ember from 'ember'; import QUnit from 'qunit'; @@ -25,12 +24,9 @@ import { setupApplicationContext, teardownApplicationContext, validateErrorHandler, - getSettledState, } from '@ember/test-helpers'; import { detectPendingTimers, reportPendingTimers } from './async-timer-leak-detection'; -const TESTS_WITH_LEAKY_ASYNC = []; - export function setResolver() { deprecate( '`setResolver` should be imported from `@ember/test-helpers`, but was imported from `ember-qunit`', @@ -234,15 +230,8 @@ export function setupEmberOnerrorValidation() { } export function setupAsyncTimerLeakDetection() { - QUnit.testDone(({ module, name }) => { - let { hasPendingTimers } = getSettledState(); - - detectPendingTimers(hasPendingTimers, TESTS_WITH_LEAKY_ASYNC, module, name, run.cancelTimers); - }); - - QUnit.done(() => { - reportPendingTimers(TESTS_WITH_LEAKY_ASYNC); - }); + QUnit.testDone(detectPendingTimers); + QUnit.done(reportPendingTimers); } /** @@ -260,6 +249,8 @@ export function setupAsyncTimerLeakDetection() { back to `false` after each test will. @param {Boolean} [options.setupEmberOnerrorValidation] If `false` validation of `Ember.onerror` will be disabled. + @param {Boolean} [options.setupAsyncTimerLeakDetection] If `false` async timer leak + detection will be disabled. */ export function start(options = {}) { if (options.loadTests !== false) { diff --git a/tests/unit/setup-async-timer-leak-detection-test.js b/tests/unit/setup-async-timer-leak-detection-test.js index 3ab6d2f6..cecac7bc 100644 --- a/tests/unit/setup-async-timer-leak-detection-test.js +++ b/tests/unit/setup-async-timer-leak-detection-test.js @@ -1,48 +1,21 @@ +import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { detectPendingTimers, reportPendingTimers } from 'ember-qunit/async-timer-leak-detection'; -module('setupEmberOnerrorValidation', function() { - test('detectPendingTimers does not queue test info when leaky async timers not detected', function(assert) { - assert.expect(2); - - let hasPendingTimers = false; - let pendingTimers = []; - let callCount = 0; - let cancelTimers = () => { - callCount++; - }; - - detectPendingTimers(hasPendingTimers, pendingTimers, '', '', cancelTimers); - - assert.equal(callCount, 0, 'cancel was not called'); - assert.equal(pendingTimers.length, 0, 'pending timers has no pending messages'); +module('setupAsyncTimerLeakDetection', function(hooks) { + hooks.beforeEach(function() { + this.cancelId = 0; }); - test('detectPendingTimers correctly queues test info when leaky async timers detected', function(assert) { - assert.expect(3); - - let hasPendingTimers = true; - let pendingTimers = []; - let callCount = 0; - let cancelTimers = () => { - callCount++; - }; - - detectPendingTimers(hasPendingTimers, pendingTimers, 'module', 'test name', cancelTimers); - - assert.equal(callCount, 1, 'cancel was called'); - assert.equal(pendingTimers.length, 1, 'pending timers has a pending message'); - assert.equal( - pendingTimers[0], - 'module: test name', - 'pending timers contains the correct message' - ); + hooks.afterEach(function() { + run.cancel(this.cancelId); }); test('reportPendingTimers does not throw when no pending timers exist', function(assert) { assert.expect(1); - reportPendingTimers([]); + detectPendingTimers({ module: 'foo', name: 'bar' }); + reportPendingTimers(); assert.ok(true); }); @@ -50,16 +23,18 @@ module('setupEmberOnerrorValidation', function() { test('reportPendingTimers throws when pending timers exist', function(assert) { assert.expect(1); - let pendingTimers = []; - pendingTimers.push('module: test name'); + this.cancelId = run.later(() => {}, 10); + + detectPendingTimers({ module: 'foo', name: 'bar' }); assert.throws( function() { - reportPendingTimers(pendingTimers); + reportPendingTimers(); }, - `ASYNC LEAKAGE DETECTED IN TESTS + Error, + new RegExp(`ASYNC LEAKAGE DETECTED IN TESTS The following (1) tests setup a timer that was never torn down before the test completed: \n - module: test name` + foo: bar`) ); }); }); From f5eb5a6507c416ce2efee8bf13fffdf4a0288919 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Thu, 15 Feb 2018 09:26:58 -0800 Subject: [PATCH 5/8] Renaming to reflect increased scope of testing for settledness --- .../ember-qunit/async-timer-leak-detection.js | 27 ------------ addon-test-support/ember-qunit/index.js | 16 +++---- .../tests-not-settled-detection.js | 44 +++++++++++++++++++ ...setup-tests-not-settled-detection-test.js} | 14 +++--- 4 files changed, 59 insertions(+), 42 deletions(-) delete mode 100644 addon-test-support/ember-qunit/async-timer-leak-detection.js create mode 100644 addon-test-support/ember-qunit/tests-not-settled-detection.js rename tests/unit/{setup-async-timer-leak-detection-test.js => setup-tests-not-settled-detection-test.js} (60%) diff --git a/addon-test-support/ember-qunit/async-timer-leak-detection.js b/addon-test-support/ember-qunit/async-timer-leak-detection.js deleted file mode 100644 index 5015b034..00000000 --- a/addon-test-support/ember-qunit/async-timer-leak-detection.js +++ /dev/null @@ -1,27 +0,0 @@ -import { run } from '@ember/runloop'; -import { getSettledState } from '@ember/test-helpers'; - -const TESTS_WITH_LEAKY_ASYNC = []; - -export function detectPendingTimers({ module, name }) { - let { hasPendingTimers } = getSettledState(); - - if (hasPendingTimers) { - TESTS_WITH_LEAKY_ASYNC.push(`${module}: ${name}`); - run.cancelTimers(); - } -} - -export function reportPendingTimers() { - if (TESTS_WITH_LEAKY_ASYNC.length > 0) { - let leakyTests = TESTS_WITH_LEAKY_ASYNC.slice(); - TESTS_WITH_LEAKY_ASYNC.length = 0; - - throw new Error( - `ASYNC LEAKAGE DETECTED IN TESTS - The following (${leakyTests.length}) tests setup a timer that was never torn down before the test completed: \n - ${leakyTests.join('\n')} - ` - ); - } -} diff --git a/addon-test-support/ember-qunit/index.js b/addon-test-support/ember-qunit/index.js index e5b6c992..432e161d 100644 --- a/addon-test-support/ember-qunit/index.js +++ b/addon-test-support/ember-qunit/index.js @@ -25,7 +25,7 @@ import { teardownApplicationContext, validateErrorHandler, } from '@ember/test-helpers'; -import { detectPendingTimers, reportPendingTimers } from './async-timer-leak-detection'; +import { detectIfNotSettled, reportIfNotSettled } from './tests-not-settled-detection'; export function setResolver() { deprecate( @@ -229,9 +229,9 @@ export function setupEmberOnerrorValidation() { }); } -export function setupAsyncTimerLeakDetection() { - QUnit.testDone(detectPendingTimers); - QUnit.done(reportPendingTimers); +export function setupTestsNotSettledDetection() { + QUnit.testDone(detectIfNotSettled); + QUnit.done(reportIfNotSettled); } /** @@ -249,8 +249,8 @@ export function setupAsyncTimerLeakDetection() { back to `false` after each test will. @param {Boolean} [options.setupEmberOnerrorValidation] If `false` validation of `Ember.onerror` will be disabled. - @param {Boolean} [options.setupAsyncTimerLeakDetection] If `false` async timer leak - detection will be disabled. + @param {Boolean} [options.setupTestsNotSettledDetection] If `false` tests not settled detection + will be disabled. */ export function start(options = {}) { if (options.loadTests !== false) { @@ -273,8 +273,8 @@ export function start(options = {}) { setupEmberOnerrorValidation(); } - if (options.setupAsyncTimerLeakDetection !== false) { - setupAsyncTimerLeakDetection(); + if (options.setupTestsNotSettledDetection !== false) { + setupTestsNotSettledDetection(); } if (options.startTests !== false) { diff --git a/addon-test-support/ember-qunit/tests-not-settled-detection.js b/addon-test-support/ember-qunit/tests-not-settled-detection.js new file mode 100644 index 00000000..19b83e6e --- /dev/null +++ b/addon-test-support/ember-qunit/tests-not-settled-detection.js @@ -0,0 +1,44 @@ +import { run } from '@ember/runloop'; +import { isSettled } from '@ember/test-helpers'; + +const TESTS_NOT_SETTLED = []; + +/** + * Detects if a specific test hasn't reached the settled state. A test is considered + * settled if it: + * + * - has no pending timers + * - is not in a runloop + * - has no pending AJAX requests + * - has no pending test waiters + * + * @param {Object} testInfo + * @param {string} testInfo.module The name of the test module + * @param {string} testInfo.name The test name + */ +export function detectIfNotSettled({ module, name }) { + if (!isSettled()) { + TESTS_NOT_SETTLED.push(`${module}: ${name}`); + run.cancelTimers(); + } +} + +/** + * Reports if a test is not considered settled. Please see above for what + * constitutes settledness. + * + * @throws Error if tests have not settled + */ +export function reportIfNotSettled() { + if (TESTS_NOT_SETTLED.length > 0) { + let leakyTests = TESTS_NOT_SETTLED.slice(); + TESTS_NOT_SETTLED.length = 0; + + throw new Error( + `ASYNC LEAKAGE DETECTED IN TESTS + The following (${leakyTests.length}) tests setup a timer that was never torn down before the test completed: \n + ${leakyTests.join('\n')} + ` + ); + } +} diff --git a/tests/unit/setup-async-timer-leak-detection-test.js b/tests/unit/setup-tests-not-settled-detection-test.js similarity index 60% rename from tests/unit/setup-async-timer-leak-detection-test.js rename to tests/unit/setup-tests-not-settled-detection-test.js index cecac7bc..ef99f699 100644 --- a/tests/unit/setup-async-timer-leak-detection-test.js +++ b/tests/unit/setup-tests-not-settled-detection-test.js @@ -1,8 +1,8 @@ import { run } from '@ember/runloop'; import { module, test } from 'qunit'; -import { detectPendingTimers, reportPendingTimers } from 'ember-qunit/async-timer-leak-detection'; +import { detectIfNotSettled, reportIfNotSettled } from 'ember-qunit/tests-not-settled-detection'; -module('setupAsyncTimerLeakDetection', function(hooks) { +module('setupTestsNotSettledDetection', function(hooks) { hooks.beforeEach(function() { this.cancelId = 0; }); @@ -11,11 +11,11 @@ module('setupAsyncTimerLeakDetection', function(hooks) { run.cancel(this.cancelId); }); - test('reportPendingTimers does not throw when no pending timers exist', function(assert) { + test('reportIfNotSettled does not throw when no pending timers exist', function(assert) { assert.expect(1); - detectPendingTimers({ module: 'foo', name: 'bar' }); - reportPendingTimers(); + detectIfNotSettled({ module: 'foo', name: 'bar' }); + reportIfNotSettled(); assert.ok(true); }); @@ -25,11 +25,11 @@ module('setupAsyncTimerLeakDetection', function(hooks) { this.cancelId = run.later(() => {}, 10); - detectPendingTimers({ module: 'foo', name: 'bar' }); + detectIfNotSettled({ module: 'foo', name: 'bar' }); assert.throws( function() { - reportPendingTimers(); + reportIfNotSettled(); }, Error, new RegExp(`ASYNC LEAKAGE DETECTED IN TESTS From 3febb6b0b798229fa690127a4c74219edcec28cc Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Thu, 15 Feb 2018 09:37:33 -0800 Subject: [PATCH 6/8] Adding tests for test waiters --- .../tests-not-settled-detection.js | 14 +++--- .../setup-tests-not-settled-detection-test.js | 46 ++++++++++++++++--- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/addon-test-support/ember-qunit/tests-not-settled-detection.js b/addon-test-support/ember-qunit/tests-not-settled-detection.js index 19b83e6e..80f8b98f 100644 --- a/addon-test-support/ember-qunit/tests-not-settled-detection.js +++ b/addon-test-support/ember-qunit/tests-not-settled-detection.js @@ -34,11 +34,13 @@ export function reportIfNotSettled() { let leakyTests = TESTS_NOT_SETTLED.slice(); TESTS_NOT_SETTLED.length = 0; - throw new Error( - `ASYNC LEAKAGE DETECTED IN TESTS - The following (${leakyTests.length}) tests setup a timer that was never torn down before the test completed: \n - ${leakyTests.join('\n')} - ` - ); + throw new Error(getMessage(leakyTests.length, leakyTests.join('\n'))); } } + +export function getMessage(testCount, testsToReport) { + return `TESTS HAVE UNSETTLED ASYNC BEHAVIOR + The following (${testCount}) tests have one or more of pending timers, pending AJAX requests, pending test waiters, or are still in a runloop: \n + ${testsToReport} + `; +} diff --git a/tests/unit/setup-tests-not-settled-detection-test.js b/tests/unit/setup-tests-not-settled-detection-test.js index ef99f699..ef524dd5 100644 --- a/tests/unit/setup-tests-not-settled-detection-test.js +++ b/tests/unit/setup-tests-not-settled-detection-test.js @@ -1,17 +1,37 @@ +import Ember from 'ember'; import { run } from '@ember/runloop'; import { module, test } from 'qunit'; -import { detectIfNotSettled, reportIfNotSettled } from 'ember-qunit/tests-not-settled-detection'; +import { + detectIfNotSettled, + reportIfNotSettled, + getMessage, +} from 'ember-qunit/tests-not-settled-detection'; module('setupTestsNotSettledDetection', function(hooks) { hooks.beforeEach(function() { this.cancelId = 0; + + this._waiter = () => { + return !this.isWaiterPending; + }; + + // In Ember < 2.8 `registerWaiter` expected to be bound to + // `Ember.Test` 😭 + // + // Once we have dropped support for < 2.8 we should swap this to + // use: + // + // import { registerWaiter } from '@ember/test'; + Ember.Test.registerWaiter(this._waiter); }); hooks.afterEach(function() { + Ember.Test.unregisterWaiter(this._waiter); + run.cancel(this.cancelId); }); - test('reportIfNotSettled does not throw when no pending timers exist', function(assert) { + test('reportIfNotSettled does not throw when test has settled', function(assert) { assert.expect(1); detectIfNotSettled({ module: 'foo', name: 'bar' }); @@ -20,7 +40,7 @@ module('setupTestsNotSettledDetection', function(hooks) { assert.ok(true); }); - test('reportPendingTimers throws when pending timers exist', function(assert) { + test('reportIfNotSettled throws when test has not settled pending timers', function(assert) { assert.expect(1); this.cancelId = run.later(() => {}, 10); @@ -32,9 +52,23 @@ module('setupTestsNotSettledDetection', function(hooks) { reportIfNotSettled(); }, Error, - new RegExp(`ASYNC LEAKAGE DETECTED IN TESTS - The following (1) tests setup a timer that was never torn down before the test completed: \n - foo: bar`) + getMessage(1, 'foo: bar') + ); + }); + + test('reportIfNotSettled throws when test has not settled test waiters', function(assert) { + assert.expect(1); + + this.isWaiterPending = true; + + detectIfNotSettled({ module: 'foo', name: 'bar' }); + + assert.throws( + function() { + reportIfNotSettled(); + }, + Error, + getMessage(1, 'foo: bar') ); }); }); From 04888048c64a86b2965a75538b1b2449474ae84a Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 16 Feb 2018 08:31:23 -0800 Subject: [PATCH 7/8] Renaming to testIsolationValidation --- addon-test-support/ember-qunit/index.js | 14 +++++----- ...ection.js => test-isolation-validation.js} | 28 ++++++++++--------- ....js => setup-test-isolation-validation.js} | 26 ++++++++--------- 3 files changed, 35 insertions(+), 33 deletions(-) rename addon-test-support/ember-qunit/{tests-not-settled-detection.js => test-isolation-validation.js} (53%) rename tests/unit/{setup-tests-not-settled-detection-test.js => setup-test-isolation-validation.js} (59%) diff --git a/addon-test-support/ember-qunit/index.js b/addon-test-support/ember-qunit/index.js index 432e161d..52ee93db 100644 --- a/addon-test-support/ember-qunit/index.js +++ b/addon-test-support/ember-qunit/index.js @@ -25,7 +25,7 @@ import { teardownApplicationContext, validateErrorHandler, } from '@ember/test-helpers'; -import { detectIfNotSettled, reportIfNotSettled } from './tests-not-settled-detection'; +import { detectIfTestNotIsolated, reportIfTestNotIsolated } from './test-isolation-validation'; export function setResolver() { deprecate( @@ -229,9 +229,9 @@ export function setupEmberOnerrorValidation() { }); } -export function setupTestsNotSettledDetection() { - QUnit.testDone(detectIfNotSettled); - QUnit.done(reportIfNotSettled); +export function setupTestIsolationValidation() { + QUnit.testDone(detectIfTestNotIsolated); + QUnit.done(reportIfTestNotIsolated); } /** @@ -249,7 +249,7 @@ export function setupTestsNotSettledDetection() { back to `false` after each test will. @param {Boolean} [options.setupEmberOnerrorValidation] If `false` validation of `Ember.onerror` will be disabled. - @param {Boolean} [options.setupTestsNotSettledDetection] If `false` tests not settled detection + @param {Boolean} [options.setupTestIsolationValidation] If `false` test isolation validation will be disabled. */ export function start(options = {}) { @@ -273,8 +273,8 @@ export function start(options = {}) { setupEmberOnerrorValidation(); } - if (options.setupTestsNotSettledDetection !== false) { - setupTestsNotSettledDetection(); + if (options.setupTestIsolationValidation !== false) { + setupTestIsolationValidation(); } if (options.startTests !== false) { diff --git a/addon-test-support/ember-qunit/tests-not-settled-detection.js b/addon-test-support/ember-qunit/test-isolation-validation.js similarity index 53% rename from addon-test-support/ember-qunit/tests-not-settled-detection.js rename to addon-test-support/ember-qunit/test-isolation-validation.js index 80f8b98f..de8e52bd 100644 --- a/addon-test-support/ember-qunit/tests-not-settled-detection.js +++ b/addon-test-support/ember-qunit/test-isolation-validation.js @@ -1,45 +1,47 @@ import { run } from '@ember/runloop'; import { isSettled } from '@ember/test-helpers'; -const TESTS_NOT_SETTLED = []; +const TESTS_NOT_ISOLATED = []; /** - * Detects if a specific test hasn't reached the settled state. A test is considered - * settled if it: + * Detects if a specific test isn't isolated. A test is considered + * not isolated if it: * * - has no pending timers * - is not in a runloop * - has no pending AJAX requests * - has no pending test waiters * + * @function detectIfTestNotIsolated * @param {Object} testInfo * @param {string} testInfo.module The name of the test module * @param {string} testInfo.name The test name */ -export function detectIfNotSettled({ module, name }) { +export function detectIfTestNotIsolated({ module, name }) { if (!isSettled()) { - TESTS_NOT_SETTLED.push(`${module}: ${name}`); + TESTS_NOT_ISOLATED.push(`${module}: ${name}`); run.cancelTimers(); } } /** - * Reports if a test is not considered settled. Please see above for what - * constitutes settledness. + * Reports if a test isn't isolated. Please see above for what + * constitutes a test being isolated. * - * @throws Error if tests have not settled + * @function reportIfTestNotIsolated + * @throws Error if tests are not isolated */ -export function reportIfNotSettled() { - if (TESTS_NOT_SETTLED.length > 0) { - let leakyTests = TESTS_NOT_SETTLED.slice(); - TESTS_NOT_SETTLED.length = 0; +export function reportIfTestNotIsolated() { + if (TESTS_NOT_ISOLATED.length > 0) { + let leakyTests = TESTS_NOT_ISOLATED.slice(); + TESTS_NOT_ISOLATED.length = 0; throw new Error(getMessage(leakyTests.length, leakyTests.join('\n'))); } } export function getMessage(testCount, testsToReport) { - return `TESTS HAVE UNSETTLED ASYNC BEHAVIOR + return `TESTS ARE NOT ISOLATED The following (${testCount}) tests have one or more of pending timers, pending AJAX requests, pending test waiters, or are still in a runloop: \n ${testsToReport} `; diff --git a/tests/unit/setup-tests-not-settled-detection-test.js b/tests/unit/setup-test-isolation-validation.js similarity index 59% rename from tests/unit/setup-tests-not-settled-detection-test.js rename to tests/unit/setup-test-isolation-validation.js index ef524dd5..bfe4f4bf 100644 --- a/tests/unit/setup-tests-not-settled-detection-test.js +++ b/tests/unit/setup-test-isolation-validation.js @@ -2,12 +2,12 @@ import Ember from 'ember'; import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { - detectIfNotSettled, - reportIfNotSettled, + detectIfTestNotIsolated, + reportIfTestNotIsolated, getMessage, -} from 'ember-qunit/tests-not-settled-detection'; +} from 'ember-qunit/test-isolation-validation'; -module('setupTestsNotSettledDetection', function(hooks) { +module('setupTestIsolationValidation', function(hooks) { hooks.beforeEach(function() { this.cancelId = 0; @@ -31,41 +31,41 @@ module('setupTestsNotSettledDetection', function(hooks) { run.cancel(this.cancelId); }); - test('reportIfNotSettled does not throw when test has settled', function(assert) { + test('reportIfTestNotIsolated does not throw when test is isolated', function(assert) { assert.expect(1); - detectIfNotSettled({ module: 'foo', name: 'bar' }); - reportIfNotSettled(); + detectIfTestNotIsolated({ module: 'foo', name: 'bar' }); + reportIfTestNotIsolated(); assert.ok(true); }); - test('reportIfNotSettled throws when test has not settled pending timers', function(assert) { + test('reportIfTestNotIsolated throws when test has pending timers', function(assert) { assert.expect(1); this.cancelId = run.later(() => {}, 10); - detectIfNotSettled({ module: 'foo', name: 'bar' }); + detectIfTestNotIsolated({ module: 'foo', name: 'bar' }); assert.throws( function() { - reportIfNotSettled(); + reportIfTestNotIsolated(); }, Error, getMessage(1, 'foo: bar') ); }); - test('reportIfNotSettled throws when test has not settled test waiters', function(assert) { + test('reportIfTestNotIsolated throws when test has test waiters', function(assert) { assert.expect(1); this.isWaiterPending = true; - detectIfNotSettled({ module: 'foo', name: 'bar' }); + detectIfTestNotIsolated({ module: 'foo', name: 'bar' }); assert.throws( function() { - reportIfNotSettled(); + reportIfTestNotIsolated(); }, Error, getMessage(1, 'foo: bar') From a7e93c4b4b535dae62fed992b46c00b62bfc83f4 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 23 Feb 2018 09:38:50 -0800 Subject: [PATCH 8/8] Adding extra check to option --- addon-test-support/ember-qunit/index.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/addon-test-support/ember-qunit/index.js b/addon-test-support/ember-qunit/index.js index 52ee93db..120a7b02 100644 --- a/addon-test-support/ember-qunit/index.js +++ b/addon-test-support/ember-qunit/index.js @@ -1,5 +1,7 @@ export { default as moduleFor } from './legacy-2-x/module-for'; -export { default as moduleForComponent } from './legacy-2-x/module-for-component'; +export { + default as moduleForComponent, +} from './legacy-2-x/module-for-component'; export { default as moduleForModel } from './legacy-2-x/module-for-model'; export { default as QUnitAdapter } from './adapter'; export { module, test, skip, only, todo } from 'qunit'; @@ -25,7 +27,10 @@ import { teardownApplicationContext, validateErrorHandler, } from '@ember/test-helpers'; -import { detectIfTestNotIsolated, reportIfTestNotIsolated } from './test-isolation-validation'; +import { + detectIfTestNotIsolated, + reportIfTestNotIsolated, +} from './test-isolation-validation'; export function setResolver() { deprecate( @@ -165,7 +170,8 @@ export function setupTestContainer() { let params = QUnit.urlParams; let containerVisibility = params.nocontainer ? 'hidden' : 'visible'; - let containerPosition = params.dockcontainer || params.devmode ? 'fixed' : 'relative'; + let containerPosition = + params.dockcontainer || params.devmode ? 'fixed' : 'relative'; if (params.devmode) { testContainer.className = ' full-screen'; @@ -176,7 +182,9 @@ export function setupTestContainer() { let qunitContainer = document.getElementById('qunit'); if (params.dockcontainer) { - qunitContainer.style.marginBottom = window.getComputedStyle(testContainer).height; + qunitContainer.style.marginBottom = window.getComputedStyle( + testContainer + ).height; } } @@ -273,7 +281,10 @@ export function start(options = {}) { setupEmberOnerrorValidation(); } - if (options.setupTestIsolationValidation !== false) { + if ( + typeof options.setupTestIsolationValidation !== 'undefined' && + options.setupTestIsolationValidation !== false + ) { setupTestIsolationValidation(); }