Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give a deprecation warning if a suite callback returns a value. #3243

Closed
wants to merge 12 commits into from
Closed

Give a deprecation warning if a suite callback returns a value. #3243

wants to merge 12 commits into from

Conversation

jleedev
Copy link

@jleedev jleedev commented Feb 14, 2018

Description of the Change

As long as this is not supported, the behavior is confusing. 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.

In short, the test suite below currently does nothing; this change will make it into an error.

describe("my suite", async function() {
  await Promise.resolve();
  it("does a thing", function() {
    assert(false);
  });
});

Other usage could possibly produce a partially-initialized test suite that fails for unknown reasons; this will make it fail obviously.

Alternate Designs

Original proposal was to throw, but settled for a deprecation warning in the accepted version.
This would need be revisited in future to throw an Error instead.

Why should this be in core?

N/A

Benefits

Reduction in user confusion/expectations attempting to use describe function as anything but a namespace.

Possible Drawbacks

Users will get deprecation warning for unsupported behavior.
No real downside to knowledge.

Applicable issues

Fixes #2975
semver-patch

@jsf-clabot
Copy link

jsf-clabot commented Feb 14, 2018

CLA assistant check
All committers have signed the CLA.

@coveralls
Copy link

coveralls commented Feb 14, 2018

Coverage Status

Coverage decreased (-0.03%) to 89.973% when pulling 0570f42 on jleedev:async-describe into ffd760e on mochajs:master.

@Bamieh
Copy link
Contributor

Bamieh commented Feb 18, 2018

@jleedev Awesome!

Indeed the describe function should not be async, the approach is to use before for such cases.

@boneskull
Copy link
Member

boneskull commented Feb 28, 2018

Thanks. I'm not sure adding more checks around ways in which Mocha can be misused is something we need to be in the business of. Nowhere in the documentation does it state that the return value of a suite's callback is consumed.

Similarly, you could pass a done callback to the suite, which is also unsupported--should we add a special exception for that? It's really a slippery slope...

@boneskull
Copy link
Member

@Bamieh Given my comments above, do you still think this should be merged?

@boneskull boneskull added area: usability concerning user experience or interface semver-patch implementation requires increase of "patch" version number; "bug fixes" status: waiting for author waiting on response from OP - more information needed labels Feb 28, 2018
@jleedev
Copy link
Author

jleedev commented Feb 28, 2018

My argument here is that "just slap async on the function" is so tempting to integrate with whatever resources are needed, and it's easy to break your entire test suite in a subtle way. I don't think it's at all the same as attemping to pass done to the suite (and not just because calling done() would raise a runtime error). It's easy to forget that function and async function have a big difference, exactly because the point is to make async code look similar to synchronous code.

That being said, it could be beneficial to call out a suggested fix — "You probably want to use before() instead."

@ORESoftware
Copy link

if the describe block returns anything truthy, throw an error

@boneskull
Copy link
Member

I get what you're saying about before(), but it happens at a totally different phase of execution. You don't want to mix that stuff up.

I like @ORESoftware's suggestion, actually, but that is a breaking change, because it doesn't necessarily cause anybody's tests to fail. For example:

describe('something', function () {
  it('is my test', function () {
    // ...
  });

  return true;
});

It's pointless, but it currently works, unlike using async function.

What would we think of adding a soft deprecation warning if a value is returned from a describe block, instead of an error? Next major would then toss an exception.

@ORESoftware
Copy link

ORESoftware commented Mar 1, 2018

@boneskull add a warning if anything truthy is returned for the next few patches, then maybe next minor release throw an error?

@boneskull
Copy link
Member

@ORESoftware well, next major since it's breaking, but yeah, that's the idea.

@ORESoftware
Copy link

yeah I guess that makes sense

@boneskull
Copy link
Member

@jleedev are you on-board changing this to a deprecation message? basically:

  • If anything is returned from a suite, let's warn. Use something like:
    // lib/utils.js (untested)
    exports.deprecate = process.emitWarning 
      // Node.js v6+
      ? function (msg) { process.emitWarning(msg, 'DeprecationWarning'); }
      // Everything else
      : function (msg) { console.warn(msg); };
    
    // elsewhere
    if (suiteReturnsSomething) {
      utils.deprecate('Returning a value from a Suite is deprecated; this will throw an exception in the next major version of Mocha');
    }
  • Ensure this is done in a way which will work in a browser

@jleedev
Copy link
Author

jleedev commented Mar 6, 2018

Yes, I like that. Branch updated with this strawman. Robots are running the browser tests.

@jleedev jleedev changed the title Throw an error if a suite callback returns a promise. Give a deprecation warning if a suite callback returns a value. Mar 9, 2018
Copy link
Contributor

@Bamieh Bamieh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@boneskull IMHO we should not be warning users for mocha misuse, so in most cases we should not be doing this. However i feel that this warning is reasonable to prevent very subtle errors, but again it is restricting our users for no real reason on the library level, a small warning text in the documentation might suffice here.

lib/interfaces/common.js Outdated Show resolved Hide resolved
lib/interfaces/common.js Outdated Show resolved Hide resolved
Copy link
Member

@boneskull boneskull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good. I have a couple changes needed to avoid user complaints 😉

lib/utils.js Outdated
*
* @param {string} msg
*/
exports.deprecate = process.emitWarning
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to complicate this, but it's a bit more complicated. 😝

  1. The user should only see this notice once. For each unique msg, we need some to keep some flag (probably in an object, as its key, since we have no access to Set) and do nothing if the flag is already set. For safety, coerce msg into a string via msg = String(msg)
  2. Internally, process.emitWarning emits on the next tick. So, console.warn(msg) must happen within process.nextTick as well. This is to avoid bloating or interfering w/ the call stack

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please rebase onto master to make the Snyk warning go away

Copy link
Author

@jleedev jleedev Mar 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If process.nextTick is unavailable (outside of Node), is it preferred to use Promise.resolve().then(…) or setTimeout(…, 0)?

(Aha, Snyk wouldn't actually show me anything.)

Did this change and rebased.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also need to hook into the --no-deprecation flags?

Copy link
Member

@boneskull boneskull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looking good.

There's a unit test suite for the utils module; could you add a quick unit test for exports.deprecate()? It's throwing off the coverage numbers.

After we have a test (and the other change I mentioned) it'll be good to merge. Great work!

lib/utils.js Outdated Show resolved Hide resolved
@boneskull boneskull removed the status: waiting for author waiting on response from OP - more information needed label Apr 12, 2018
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.
@jleedev
Copy link
Author

jleedev commented Apr 14, 2018

unit test suite for the utils module

I actually added to the integration tests. Is it possible to verify a call to console.warn otherwise?

@plroebuck
Copy link
Contributor

utils.deprecate('Returning a value from a Suite is deprecated; this will throw an exception in the next major version of Mocha');

The message itself implies that this was previously supported.

'Returning a value from a Suite was previously undocumented; it is now explicitly verboten and will throw an exception in the next major version of Mocha'

@boneskull
Copy link
Member

@plroebuck

The message itself implies that this was previously supported

I disagree; it was not disallowed, not supported. If you pass an extra parameter to any given function in Mocha's API, if it doesn't break, that doesn't imply it's supported.

@Bamieh

@boneskull IMHO we should not be warning users for mocha misuse, so in most cases we should not be doing this. However i feel that this warning is reasonable to prevent very subtle errors, but again it is restricting our users for no real reason on the library level, a small warning text in the documentation might suffice here.

I agree that we shouldn't generally be in the business of warning users about misusing the API. The only reason I'm entertaining it is because of the potential for causing difficult-to-solve problems.

IMO the real problem here is that suites don't support async behavior. Maybe that should change. If we do want that to change (at some point), we shouldn't merge this, and instead add a note to the docs.

@mochajs/core would love further thoughts

@Bamieh
Copy link
Contributor

Bamieh commented Apr 22, 2018

@boneskull I agree with you about not merging this if there is a possibility of having async suites.

I do not see the value of supporting async suites, as I usually encourage people to write all the fetching logic in the before of the suite, the describe body can only hold empty var/let variables to be assigned in the test cases of the suite to solve the function scoping (some people use this directly to avoid this as well, but we do not encourage it). Following that logic, having async describe might be contradictory and unnecessary.

@jleedev
Copy link
Author

jleedev commented Apr 24, 2018

I don't understand why the possibility of async suites in the future should prevent merging this right now. Either way, adding async suites will break compatibility to the same degree, and the warning can be removed. No one should be returning a Promise from a suite before they're actually supported.

@plroebuck
Copy link
Contributor

@jleedev #2975 would be "fixed" (for certain definitions of fixed) by this PR, right?

@plroebuck
Copy link
Contributor

@boneskull were your concerns addressed?
if so, could you resolve any remaining conversations and explicitly approve?

@plroebuck plroebuck added the status: needs review a maintainer should (re-)review this pull request label Nov 2, 2018
@boneskull
Copy link
Member

I don't understand why the possibility of async suites in the future should prevent merging this right now. Either way, adding async suites will break compatibility to the same degree, and the warning can be removed. No one should be returning a Promise from a suite before they're actually supported.

churn, mainly. but async suites is probably a ways off.

test/integration/deprecate.spec.js Show resolved Hide resolved
test/integration/deprecate.spec.js Show resolved Hide resolved
@boneskull
Copy link
Member

@plroebuck I've noted two problems. fine to merge and then fix them after; I don't want to ask @jleedev to do more work.

@boneskull
Copy link
Member

I've "approved", but the stuff I noted must be addressed one way or another

@plroebuck
Copy link
Contributor

plroebuck commented Nov 2, 2018

I'll ask. Since this was intended for patch release, plan going forward is to?

  1. correct to throw error instead of deprecation warn (for 6.0) Gomer's reaction
  2. merge as is and throw error (for 7.0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: usability concerning user experience or interface semver-patch implementation requires increase of "patch" version number; "bug fixes"
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Describe block with async function behaving weirdly
9 participants