From 451719b33398232688899f793b80a25ced19a8ae Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Wed, 14 Feb 2018 15:44:38 -0500 Subject: [PATCH 01/14] Throw an error if a suite callback returns a promise. As long as this is not supported, the behavior is confusing (see issue 2975 and others). It can produce a suite that is not properly initialized. Turning this into a hard error will make it clear that this is not supported. --- lib/interfaces/common.js | 5 ++++- .../suite/suite-async-callback.fixture.js | 3 +++ test/integration/suite.spec.js | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 test/integration/fixtures/suite/suite-async-callback.fixture.js diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index f0cdd4eb1a..49483e6df7 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -109,7 +109,10 @@ module.exports = function (suites, context, mocha) { suite.parent._onlySuites = suite.parent._onlySuites.concat(suite); } if (typeof opts.fn === 'function') { - opts.fn.call(suite); + var result = opts.fn.call(suite); + if (result && typeof result.then === 'function') { + throw new Error('Suite "' + suite.fullTitle() + '" was given an async callback. This will not behave as you expect.'); + } suites.shift(); } else if (typeof opts.fn === 'undefined' && !suite.pending) { throw new Error('Suite "' + suite.fullTitle() + '" was defined but no callback was supplied. Supply a callback or explicitly skip the suite.'); diff --git a/test/integration/fixtures/suite/suite-async-callback.fixture.js b/test/integration/fixtures/suite/suite-async-callback.fixture.js new file mode 100644 index 0000000000..b642b22994 --- /dev/null +++ b/test/integration/fixtures/suite/suite-async-callback.fixture.js @@ -0,0 +1,3 @@ +'use strict'; + +describe('a suite with an async callback', async function () {}); diff --git a/test/integration/suite.spec.js b/test/integration/suite.spec.js index 4af47697f7..7821ce774b 100644 --- a/test/integration/suite.spec.js +++ b/test/integration/suite.spec.js @@ -50,3 +50,19 @@ describe('skipped suite w/ callback', function () { }); }); }); + +describe('suite with erroneous async callback', function () { + this.timeout(2000); + it('should throw an error for suite callback returning promise', function (done) { + run('suite/suite-async-callback.fixture.js', args, function (err, res) { + if (err) { + done(err); + return; + } + var pattern = new RegExp('Error', 'g'); + var result = res.output.match(pattern) || []; + assert.equal(result.length, 2); + done(); + }); + }); +}); From bd110051f04136f46f7bf01e8f2d1c39b7209374 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Wed, 14 Feb 2018 16:28:09 -0500 Subject: [PATCH 02/14] Node 4/6 compatibility --- .../fixtures/suite/suite-async-callback.fixture.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/fixtures/suite/suite-async-callback.fixture.js b/test/integration/fixtures/suite/suite-async-callback.fixture.js index b642b22994..bd63838ef2 100644 --- a/test/integration/fixtures/suite/suite-async-callback.fixture.js +++ b/test/integration/fixtures/suite/suite-async-callback.fixture.js @@ -1,3 +1,5 @@ 'use strict'; -describe('a suite with an async callback', async function () {}); +describe('a suite with an async callback', function () { + return Promise.resolve(); +}); From 5c8d729202022220471a518fae85c13c16235b00 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Tue, 6 Mar 2018 07:35:46 -0500 Subject: [PATCH 03/14] Give a DeprecationWarning on suite callback returning any value. --- lib/interfaces/common.js | 5 +++-- lib/utils.js | 11 +++++++++++ .../fixtures/suite/suite-async-callback.fixture.js | 5 ----- .../fixtures/suite/suite-returning-value.fixture.js | 5 +++++ test/integration/suite.spec.js | 10 +++++----- 5 files changed, 24 insertions(+), 12 deletions(-) delete mode 100644 test/integration/fixtures/suite/suite-async-callback.fixture.js create mode 100644 test/integration/fixtures/suite/suite-returning-value.fixture.js diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index 49483e6df7..2d60b9cf94 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -1,6 +1,7 @@ 'use strict'; var Suite = require('../suite'); +var utils = require('../utils'); /** * Functions common to more than one interface. @@ -110,8 +111,8 @@ module.exports = function (suites, context, mocha) { } if (typeof opts.fn === 'function') { var result = opts.fn.call(suite); - if (result && typeof result.then === 'function') { - throw new Error('Suite "' + suite.fullTitle() + '" was given an async callback. This will not behave as you expect.'); + if (typeof result !== 'undefined') { + utils.deprecate('Returning a value from a Suite is deprecated; this will throw an exception in the next major version of Mocha'); } suites.shift(); } else if (typeof opts.fn === 'undefined' && !suite.pending) { diff --git a/lib/utils.js b/lib/utils.js index d6bb2bd8e9..4e33a522e6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -543,6 +543,17 @@ exports.getError = function (err) { return err || exports.undefinedError(); }; +/** + * Show a deprecation warning + * + * @param {string} msg + */ +exports.deprecate = process.emitWarning + // Node.js v6+ + ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); } + // Everything else + : function (msg) { console.warn(msg); }; + /** * @summary * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) diff --git a/test/integration/fixtures/suite/suite-async-callback.fixture.js b/test/integration/fixtures/suite/suite-async-callback.fixture.js deleted file mode 100644 index bd63838ef2..0000000000 --- a/test/integration/fixtures/suite/suite-async-callback.fixture.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -describe('a suite with an async callback', function () { - return Promise.resolve(); -}); diff --git a/test/integration/fixtures/suite/suite-returning-value.fixture.js b/test/integration/fixtures/suite/suite-returning-value.fixture.js new file mode 100644 index 0000000000..8859cfb5d9 --- /dev/null +++ b/test/integration/fixtures/suite/suite-returning-value.fixture.js @@ -0,0 +1,5 @@ +'use strict'; + +describe('a suite returning a value', function () { + return Promise.resolve(); +}); diff --git a/test/integration/suite.spec.js b/test/integration/suite.spec.js index 7821ce774b..ab96111c55 100644 --- a/test/integration/suite.spec.js +++ b/test/integration/suite.spec.js @@ -51,17 +51,17 @@ describe('skipped suite w/ callback', function () { }); }); -describe('suite with erroneous async callback', function () { +describe('suite returning a value', function () { this.timeout(2000); - it('should throw an error for suite callback returning promise', function (done) { - run('suite/suite-async-callback.fixture.js', args, function (err, res) { + it('should give a deprecation warning for suite callback returning a value', function (done) { + run('suite/suite-returning-value.fixture.js', args, function (err, res) { if (err) { done(err); return; } - var pattern = new RegExp('Error', 'g'); + var pattern = new RegExp('DeprecationWarning', 'g'); var result = res.output.match(pattern) || []; - assert.equal(result.length, 2); + assert.equal(result.length, 1); done(); }); }); From 45b820e09ced7ac7ff1bd96b5b3247004d1ae297 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Thu, 8 Mar 2018 15:39:00 +0000 Subject: [PATCH 04/14] Node 4 compatibility --- test/integration/suite.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/suite.spec.js b/test/integration/suite.spec.js index ab96111c55..7bacc57e39 100644 --- a/test/integration/suite.spec.js +++ b/test/integration/suite.spec.js @@ -59,7 +59,7 @@ describe('suite returning a value', function () { done(err); return; } - var pattern = new RegExp('DeprecationWarning', 'g'); + var pattern = new RegExp('deprecated', 'g'); var result = res.output.match(pattern) || []; assert.equal(result.length, 1); done(); From 33a301c1964a03b2695f6ec13b8f133f257d75ed Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Mon, 12 Mar 2018 17:46:29 -0400 Subject: [PATCH 05/14] Simplify check --- lib/interfaces/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index 2d60b9cf94..f4bed20fee 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -111,7 +111,7 @@ module.exports = function (suites, context, mocha) { } if (typeof opts.fn === 'function') { var result = opts.fn.call(suite); - if (typeof result !== 'undefined') { + if (result) { utils.deprecate('Returning a value from a Suite is deprecated; this will throw an exception in the next major version of Mocha'); } suites.shift(); From 83f39a2159cdc5de7707f65630f934852dfaea3f Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Mon, 19 Mar 2018 07:55:22 -0400 Subject: [PATCH 06/14] Put back typeof undefined check after review --- lib/interfaces/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index f4bed20fee..2d60b9cf94 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -111,7 +111,7 @@ module.exports = function (suites, context, mocha) { } if (typeof opts.fn === 'function') { var result = opts.fn.call(suite); - if (result) { + if (typeof result !== 'undefined') { utils.deprecate('Returning a value from a Suite is deprecated; this will throw an exception in the next major version of Mocha'); } suites.shift(); From f799f766c2f14027a68d94460401dd9e6ed9e156 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Mon, 19 Mar 2018 10:26:14 -0400 Subject: [PATCH 07/14] Give a more detailed message when complaining about suite callback return value. --- lib/interfaces/common.js | 2 +- test/integration/suite.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index 2d60b9cf94..b4e89379ee 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -112,7 +112,7 @@ module.exports = function (suites, context, mocha) { if (typeof opts.fn === 'function') { var result = opts.fn.call(suite); if (typeof result !== 'undefined') { - utils.deprecate('Returning a value from a Suite is deprecated; this will throw an exception in the next major version of Mocha'); + utils.deprecate('Deprecation Warning: Suites do not support a return value;' + opts.title + ' returned :' + result); } suites.shift(); } else if (typeof opts.fn === 'undefined' && !suite.pending) { diff --git a/test/integration/suite.spec.js b/test/integration/suite.spec.js index 7bacc57e39..9af9b4ffa6 100644 --- a/test/integration/suite.spec.js +++ b/test/integration/suite.spec.js @@ -59,7 +59,7 @@ describe('suite returning a value', function () { done(err); return; } - var pattern = new RegExp('deprecated', 'g'); + var pattern = new RegExp('Deprecation Warning', 'g'); var result = res.output.match(pattern) || []; assert.equal(result.length, 1); done(); From ada3448a92881b042c6cad7c0c36ba4d8d59cc80 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Sat, 24 Mar 2018 12:13:22 -0400 Subject: [PATCH 08/14] Deprecation warning: Show a message only once; use process.nextTick when falling back to console.warn --- lib/utils.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 4e33a522e6..768130b17f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -544,15 +544,30 @@ exports.getError = function (err) { }; /** - * Show a deprecation warning + * Show a deprecation warning. Each distinct message is only displayed once. * * @param {string} msg */ -exports.deprecate = process.emitWarning +exports.deprecate = function (msg) { + msg = String(msg); + if (seenDeprecationMsg.hasOwnProperty(msg)) { + return; + } + seenDeprecationMsg[msg] = true; + doDeprecationWarning(msg); +}; + +var seenDeprecationMsg = {}; + +var doDeprecationWarning = process.emitWarning // Node.js v6+ ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); } + // Node.js v4+ + // process.emitWarning emits on the next tick; emulate that. + : process.nextTick ? + function (msg) { process.nextTick(() => console.warn(msg)); } // Everything else - : function (msg) { console.warn(msg); }; + : function (msg) { Promise.resolve().then(() => console.warn(msg)); }; /** * @summary From 51b746281e07319c47f4dd9b1812177602987d27 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Sat, 24 Mar 2018 12:37:15 -0400 Subject: [PATCH 09/14] Lint fix --- lib/utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 768130b17f..b4beffcf0c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -564,10 +564,10 @@ var doDeprecationWarning = process.emitWarning ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); } // Node.js v4+ // process.emitWarning emits on the next tick; emulate that. - : process.nextTick ? - function (msg) { process.nextTick(() => console.warn(msg)); } - // Everything else - : function (msg) { Promise.resolve().then(() => console.warn(msg)); }; + : process.nextTick + ? function (msg) { process.nextTick(() => console.warn(msg)); } + // Everything else + : function (msg) { Promise.resolve().then(() => console.warn(msg)); }; /** * @summary From d065c00b438e0a59317b95839af5aa2d03e34775 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Sun, 25 Mar 2018 08:50:39 -0400 Subject: [PATCH 10/14] Add a test for utils.deprecate --- test/integration/deprecate.spec.js | 20 +++++++++++++++++++ .../integration/fixtures/deprecate.fixture.js | 9 +++++++++ 2 files changed, 29 insertions(+) create mode 100644 test/integration/deprecate.spec.js create mode 100644 test/integration/fixtures/deprecate.fixture.js diff --git a/test/integration/deprecate.spec.js b/test/integration/deprecate.spec.js new file mode 100644 index 0000000000..77edf3dda9 --- /dev/null +++ b/test/integration/deprecate.spec.js @@ -0,0 +1,20 @@ +'use strict'; + +var assert = require('assert'); +var run = require('./helpers').runMocha; +var args = []; + +describe.only('utils.deprecate test', function () { + it('should print unique deprecation only once', function (done) { + run('deprecate.fixture.js', args, function (err, res) { + if (err) { + done(err); + return; + } + console.trace(res); + var result = res.output.match(/deprecated thing/g) || []; + assert.equal(result.length, 2); + done(); + }); + }); +}); diff --git a/test/integration/fixtures/deprecate.fixture.js b/test/integration/fixtures/deprecate.fixture.js new file mode 100644 index 0000000000..cf98884600 --- /dev/null +++ b/test/integration/fixtures/deprecate.fixture.js @@ -0,0 +1,9 @@ +'use strict'; + +var utils = require("../../../lib/utils"); + +it('consolidates identical calls to deprecate', function() { + utils.deprecate("suite foo did a deprecated thing"); + utils.deprecate("suite foo did a deprecated thing"); + utils.deprecate("suite bar did a deprecated thing"); +}); From d318c2393474ff89cee83797df2ffc415c0c5d90 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Sat, 14 Apr 2018 11:00:44 -0400 Subject: [PATCH 11/14] Unconditionally use process.nextTick --- lib/utils.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index b4beffcf0c..5a9dfe480b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -562,12 +562,8 @@ var seenDeprecationMsg = {}; var doDeprecationWarning = process.emitWarning // Node.js v6+ ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); } - // Node.js v4+ - // process.emitWarning emits on the next tick; emulate that. - : process.nextTick - ? function (msg) { process.nextTick(() => console.warn(msg)); } - // Everything else - : function (msg) { Promise.resolve().then(() => console.warn(msg)); }; + // Everything else + : function (msg) { process.nextTick(() => console.warn(msg)); } /** * @summary From 5cbaee1ca427705bbff879459ee03adc568708b5 Mon Sep 17 00:00:00 2001 From: Josh Lee Date: Sat, 14 Apr 2018 11:54:45 -0400 Subject: [PATCH 12/14] Lint fix --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 5a9dfe480b..5c01b7ad86 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -563,7 +563,7 @@ var doDeprecationWarning = process.emitWarning // Node.js v6+ ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); } // Everything else - : function (msg) { process.nextTick(() => console.warn(msg)); } + : function (msg) { process.nextTick(() => console.warn(msg)); }; /** * @summary From 0fd5585cc4bcabb1faa2ced1ece89941b40c2355 Mon Sep 17 00:00:00 2001 From: Paul Roebuck Date: Sat, 3 Nov 2018 20:06:57 -0500 Subject: [PATCH 13/14] style: Make prettier happy Formatting changes required to consider commit. In test specs, combined `return done(err)`. --- lib/interfaces/common.js | 11 +++++++++-- lib/utils.js | 16 +++++++++++----- test/integration/deprecate.spec.js | 9 ++++----- test/integration/suite.spec.js | 18 +++++++----------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/interfaces/common.js b/lib/interfaces/common.js index 07d2da81d8..7997312786 100644 --- a/lib/interfaces/common.js +++ b/lib/interfaces/common.js @@ -93,6 +93,7 @@ module.exports = function(suites, context, mocha) { /** * Creates a suite. + * * @param {Object} opts Options * @param {string} opts.title Title of Suite * @param {Function} [opts.fn] Suite Function (not always applicable) @@ -112,14 +113,20 @@ module.exports = function(suites, context, mocha) { if (typeof opts.fn === 'function') { var result = opts.fn.call(suite); if (typeof result !== 'undefined') { - utils.deprecate('Deprecation Warning: Suites do not support a return value;' + opts.title + ' returned :' + result); + utils.deprecate( + 'Deprecation Warning: Suites do not support a return value;' + + opts.title + + ' returned :' + + result + ); } suites.shift(); } else if (typeof opts.fn === 'undefined' && !suite.pending) { throw new Error( 'Suite "' + suite.fullTitle() + - '" was defined but no callback was supplied. Supply a callback or explicitly skip the suite.' + '" was defined but no callback was supplied. ' + + 'Supply a callback or explicitly skip the suite.' ); } else if (!opts.fn && suite.pending) { suites.shift(); diff --git a/lib/utils.js b/lib/utils.js index b0eb56aa14..71ea6ce4fb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -589,7 +589,7 @@ exports.getError = function(err) { * * @param {string} msg */ -exports.deprecate = function (msg) { +exports.deprecate = function(msg) { msg = String(msg); if (seenDeprecationMsg.hasOwnProperty(msg)) { return; @@ -601,10 +601,16 @@ exports.deprecate = function (msg) { var seenDeprecationMsg = {}; var doDeprecationWarning = process.emitWarning - // Node.js v6+ - ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); } - // Everything else - : function (msg) { process.nextTick(() => console.warn(msg)); }; + ? function(msg) { + // Node.js v6+ + process.emitWarning(msg, 'DeprecationWarning'); + } + : function(msg) { + // Everything else + process.nextTick(function() { + console.warn(msg); + }); + }; /** * @summary diff --git a/test/integration/deprecate.spec.js b/test/integration/deprecate.spec.js index 77edf3dda9..049a77f959 100644 --- a/test/integration/deprecate.spec.js +++ b/test/integration/deprecate.spec.js @@ -4,12 +4,11 @@ var assert = require('assert'); var run = require('./helpers').runMocha; var args = []; -describe.only('utils.deprecate test', function () { - it('should print unique deprecation only once', function (done) { - run('deprecate.fixture.js', args, function (err, res) { +describe.only('utils.deprecate test', function() { + it('should print unique deprecation only once', function(done) { + run('deprecate.fixture.js', args, function(err, res) { if (err) { - done(err); - return; + return done(err); } console.trace(res); var result = res.output.match(/deprecated thing/g) || []; diff --git a/test/integration/suite.spec.js b/test/integration/suite.spec.js index beec2710a5..338a198f63 100644 --- a/test/integration/suite.spec.js +++ b/test/integration/suite.spec.js @@ -9,8 +9,7 @@ describe('suite w/no callback', function() { it('should throw a helpful error message when a callback for suite is not supplied', function(done) { run('suite/suite-no-callback.fixture.js', args, function(err, res) { if (err) { - done(err); - return; + return done(err); } var result = res.output.match(/no callback was supplied/) || []; assert.equal(result.length, 1); @@ -24,8 +23,7 @@ describe('skipped suite w/no callback', function() { it('should not throw an error when a callback for skipped suite is not supplied', function(done) { run('suite/suite-skipped-no-callback.fixture.js', args, function(err, res) { if (err) { - done(err); - return; + return done(err); } var pattern = new RegExp('Error', 'g'); var result = res.output.match(pattern) || []; @@ -40,8 +38,7 @@ describe('skipped suite w/ callback', function() { it('should not throw an error when a callback for skipped suite is supplied', function(done) { run('suite/suite-skipped-callback.fixture.js', args, function(err, res) { if (err) { - done(err); - return; + return done(err); } var pattern = new RegExp('Error', 'g'); var result = res.output.match(pattern) || []; @@ -51,13 +48,12 @@ describe('skipped suite w/ callback', function() { }); }); -describe('suite returning a value', function () { +describe('suite returning a value', function() { this.timeout(2000); - it('should give a deprecation warning for suite callback returning a value', function (done) { - run('suite/suite-returning-value.fixture.js', args, function (err, res) { + it('should give a deprecation warning for suite callback returning a value', function(done) { + run('suite/suite-returning-value.fixture.js', args, function(err, res) { if (err) { - done(err); - return; + return done(err); } var pattern = new RegExp('Deprecation Warning', 'g'); var result = res.output.match(pattern) || []; From 46971692f7a728ddeba87def4bbe4e4f86199776 Mon Sep 17 00:00:00 2001 From: Paul Roebuck Date: Sat, 3 Nov 2018 20:11:08 -0500 Subject: [PATCH 14/14] test(deprecate.spec.js): Make PR requested changes for approval Removed `.only` from `describe`. Removed `console.trace` debug output. --- test/integration/deprecate.spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/deprecate.spec.js b/test/integration/deprecate.spec.js index 049a77f959..db806fcde5 100644 --- a/test/integration/deprecate.spec.js +++ b/test/integration/deprecate.spec.js @@ -4,13 +4,12 @@ var assert = require('assert'); var run = require('./helpers').runMocha; var args = []; -describe.only('utils.deprecate test', function() { +describe('utils.deprecate test', function() { it('should print unique deprecation only once', function(done) { run('deprecate.fixture.js', args, function(err, res) { if (err) { return done(err); } - console.trace(res); var result = res.output.match(/deprecated thing/g) || []; assert.equal(result.length, 2); done();