diff --git a/src/core/utilities.js b/src/core/utilities.js index 9cd8e78b1..a1bbe16b6 100644 --- a/src/core/utilities.js +++ b/src/core/utilities.js @@ -1,45 +1,12 @@ import { window } from '../globals'; -import Logger from '../logger'; export const toString = Object.prototype.toString; export const hasOwn = Object.prototype.hasOwnProperty; export const slice = Array.prototype.slice; -const nativePerf = getNativePerf(); - -// TODO: Consider using globalThis instead so that perf marks work -// in Node.js as well. As they can have overhead, we should also -// have a way to disable these, and/or make them an opt-in reporter -// in QUnit 3 and then support globalThis. -// For example: `QUnit.addReporter(QUnit.reporters.perf)`. -function getNativePerf () { - if (window && - typeof window.performance !== 'undefined' && - typeof window.performance.mark === 'function' && - typeof window.performance.measure === 'function' - ) { - return window.performance; - } else { - return undefined; - } -} - export const performance = { - now: nativePerf - ? nativePerf.now.bind(nativePerf) - : Date.now, - measure: nativePerf - ? function (comment, startMark, endMark) { - // `performance.measure` may fail if the mark could not be found. - // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()` - try { - nativePerf.measure(comment, startMark, endMark); - } catch (ex) { - Logger.warn('performance.measure could not be executed because of ', ex.message); - } - } - : function () {}, - mark: nativePerf ? nativePerf.mark.bind(nativePerf) : function () {} + // eslint-disable-next-line compat/compat -- Checked + now: window && window.performance && window.performance.now ? window.performance.now.bind(window.performance) : Date.now }; // Returns a new Array with the elements that are in a but not in b diff --git a/src/html-reporter/html.js b/src/html-reporter/html.js index dd097a34f..bad16362f 100644 --- a/src/html-reporter/html.js +++ b/src/html-reporter/html.js @@ -39,6 +39,8 @@ export function escapeText (str) { return; } + QUnit.reporters.perf.init(QUnit); + const config = QUnit.config; const hiddenTests = []; let collapseNext = false; diff --git a/src/reporters.js b/src/reporters.js index 207c43473..7a2eae4d9 100644 --- a/src/reporters.js +++ b/src/reporters.js @@ -1,7 +1,9 @@ import ConsoleReporter from './reporters/ConsoleReporter.js'; import TapReporter from './reporters/TapReporter.js'; +import PerfReporter from './reporters/PerfReporter.js'; export default { - console: ConsoleReporter, - tap: TapReporter + tap: TapReporter, + perf: PerfReporter, + console: ConsoleReporter }; diff --git a/src/reporters/PerfReporter.js b/src/reporters/PerfReporter.js new file mode 100644 index 000000000..9a49c7dc0 --- /dev/null +++ b/src/reporters/PerfReporter.js @@ -0,0 +1,92 @@ +import { window } from '../globals'; +import Logger from '../logger'; + +// TODO: Consider using globalThis instead of window, so that the reporter +// works for Node.js as well. As this can add overhead, we should make +// this opt-in before we enable it for CLI. +// +// QUnit 3 will switch from `window` to `globalThis` and then make it +// no longer an implicit feature of the HTML Reporter, but rather let +// it be opt-in via `QUnit.config.reporters = ['perf']` or something +// like that. +const nativePerf = ( + window && + typeof window.performance !== 'undefined' && + // eslint-disable-next-line compat/compat -- Checked + typeof window.performance.mark === 'function' && + // eslint-disable-next-line compat/compat -- Checked + typeof window.performance.measure === 'function' +) + ? window.performance + : undefined; + +const perf = { + measure: nativePerf + ? function (comment, startMark, endMark) { + // `performance.measure` may fail if the mark could not be found. + // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()` + try { + nativePerf.measure(comment, startMark, endMark); + } catch (ex) { + Logger.warn('performance.measure could not be executed because of ', ex.message); + } + } + : function () {}, + mark: nativePerf ? nativePerf.mark.bind(nativePerf) : function () {} +}; + +export default class PerfReporter { + constructor (runner, options = {}) { + this.perf = options.perf || perf; + + runner.on('runStart', this.onRunStart.bind(this)); + runner.on('runEnd', this.onRunEnd.bind(this)); + runner.on('suiteStart', this.onSuiteStart.bind(this)); + runner.on('suiteEnd', this.onSuiteEnd.bind(this)); + runner.on('testStart', this.onTestStart.bind(this)); + runner.on('testEnd', this.onTestEnd.bind(this)); + } + + static init (runner, options) { + return new PerfReporter(runner, options); + } + + onRunStart () { + this.perf.mark('qunit_suite_0_start'); + } + + onSuiteStart (suiteStart) { + const suiteLevel = suiteStart.fullName.length; + this.perf.mark(`qunit_suite_${suiteLevel}_start`); + } + + onSuiteEnd (suiteEnd) { + const suiteLevel = suiteEnd.fullName.length; + const suiteName = suiteEnd.fullName.join(' – '); + + this.perf.mark(`qunit_suite_${suiteLevel}_end`); + this.perf.measure(`QUnit Test Suite: ${suiteName}`, + `qunit_suite_${suiteLevel}_start`, + `qunit_suite_${suiteLevel}_end` + ); + } + + onTestStart () { + this.perf.mark('qunit_test_start'); + } + + onTestEnd (testEnd) { + this.perf.mark('qunit_test_end'); + const testName = testEnd.fullName.join(' – '); + + this.perf.measure(`QUnit Test: ${testName}`, + 'qunit_test_start', + 'qunit_test_end' + ); + } + + onRunEnd () { + this.perf.mark('qunit_suite_0_end'); + this.perf.measure('QUnit Test Run', 'qunit_suite_0_start', 'qunit_suite_0_end'); + } +} diff --git a/src/reports/suite.js b/src/reports/suite.js index a2b72289f..dae864303 100644 --- a/src/reports/suite.js +++ b/src/reports/suite.js @@ -21,9 +21,6 @@ export default class SuiteReport { start (recordTime) { if (recordTime) { this._startTime = performance.now(); - - const suiteLevel = this.fullName.length; - performance.mark(`qunit_suite_${suiteLevel}_start`); } return { @@ -40,16 +37,6 @@ export default class SuiteReport { end (recordTime) { if (recordTime) { this._endTime = performance.now(); - - const suiteLevel = this.fullName.length; - const suiteName = this.fullName.join(' – '); - - performance.mark(`qunit_suite_${suiteLevel}_end`); - performance.measure( - suiteLevel === 0 ? 'QUnit Test Run' : `QUnit Test Suite: ${suiteName}`, - `qunit_suite_${suiteLevel}_start`, - `qunit_suite_${suiteLevel}_end` - ); } return { diff --git a/src/reports/test.js b/src/reports/test.js index 980207dd6..b58daf0be 100644 --- a/src/reports/test.js +++ b/src/reports/test.js @@ -22,7 +22,6 @@ export default class TestReport { start (recordTime) { if (recordTime) { this._startTime = performance.now(); - performance.mark('qunit_test_start'); } return { @@ -35,17 +34,6 @@ export default class TestReport { end (recordTime) { if (recordTime) { this._endTime = performance.now(); - if (performance) { - performance.mark('qunit_test_end'); - - const testName = this.fullName.join(' – '); - - performance.measure( - `QUnit Test: ${testName}`, - 'qunit_test_start', - 'qunit_test_end' - ); - } } return extend(this.start(), { diff --git a/test/cli/fixtures/expected/tap-outputs.js b/test/cli/fixtures/expected/tap-outputs.js index 1abcd4b72..39c21d78b 100644 --- a/test/cli/fixtures/expected/tap-outputs.js +++ b/test/cli/fixtures/expected/tap-outputs.js @@ -172,14 +172,14 @@ ok 5 A-Test > derp 'qunit --reporter does-not-exist': `# stderr No reporter found matching "does-not-exist". -Built-in reporters: console, tap +Built-in reporters: console, perf, tap Extra reporters found among package dependencies: npm-reporter # exit code: 1`, 'qunit --reporter': `# stderr -Built-in reporters: console, tap +Built-in reporters: console, perf, tap Extra reporters found among package dependencies: npm-reporter # exit code: 1`,