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

feat: add option to use posix exit code upon fatal signal #4989

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

73rhodes
Copy link

@73rhodes 73rhodes commented May 31, 2023

Requirements

  • Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
  • All new code requires tests to ensure against regressions.

Description of the Change

Mocha uses the number of failed tests as an exit code, which is unconventional and has led to a number of issues when trying to integrate mocha into ci/cd pipelines.

To resolve these issues, this PR introduces a --posix-exit-codes boolean command-line option. If this option is specified, a fatal signal (eg. SIGABRT, et al) will cause the process to consistently exit with a standard posix exit code (128 + the numeric ID of the signal) when mocha is run as a child process (which is the case when passing node options). This helps to solve issues for toolchains that expect standard posix exit codes, for example by preventing out-of-memory crashes from being silently ignored.

The GNU libc manual page provides additional context on why using the number of errors as an exit status is problematic:

Warning: Don’t try to use the number of errors as the exit status. This is actually not very useful; a parent process would generally not care how many errors occurred. Worse than that, it does not work, because the status value is truncated to eight bits. Thus, if the program tried to report 256 errors, the parent would receive a report of 0 errors—that is, success. For the same reason, it does not work to use the value of errno as the exit status—these can exceed 255.

Providing the option to exit with standard posix shell exit codes avoids these problems and solves a number of downstream issues listed below.

Alternate Designs

The alternatives considered were not in the scope of the mocha project.

Why should this be in core?

This option is implemented in the core repository where signal handling occurs.

Benefits

This PR provides a solution for #3559 and various downstream issues reported by mocha consumers; it preserves non-zero exit codes when mocha is spawned as a child process via various CI/CD or reporting tools and prevents silently swallowing out-of-memory errors.

Possible Drawbacks

Introduces another option. Requires docs. Leaves non-standard exit code behavior to remain as the default.

Applicable issues

fixes #3559
#3893
#2445
#2438
istanbuljs/nyc#798
cypress-io/cypress#24695

  • No breaking changes; this is an enhancement (minor release)

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented May 31, 2023

CLA Signed

The committers listed above are authorized under a signed CLA.

@73rhodes 73rhodes marked this pull request as ready for review May 31, 2023 16:34
Copy link
Contributor

This PR hasn't had any recent activity, and I'm labeling it stale. Remove the label or comment or this PR will be closed in 14 days. Thanks for contributing to Mocha!

@github-actions github-actions bot added the stale this has been inactive for a while... label Nov 17, 2023
@JoshuaKGoldberg JoshuaKGoldberg removed the stale this has been inactive for a while... label Nov 18, 2023
@JoshuaKGoldberg
Copy link
Member

We should probably disable the stale action, pending picking up reviewing PRs.

Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

Nice and clean implementation, thanks! ✨

Requesting changes on a few things. But let me know, please, if I'm off base here 🙂.

bin/mocha.js Outdated Show resolved Hide resolved
test/integration/options/posixExitCodes.spec.js Outdated Show resolved Hide resolved
test/integration/options/posixExitCodes.spec.js Outdated Show resolved Hide resolved
test/integration/options/posixExitCodes.spec.js Outdated Show resolved Hide resolved
lib/cli/run.js Show resolved Hide resolved
@JoshuaKGoldberg JoshuaKGoldberg added status: waiting for author waiting on response from OP - more information needed semver-minor implementation requires increase of "minor" version number; "features" labels Mar 4, 2024
@JoshuaKGoldberg
Copy link
Member

Note that after this lands, we'll want to file a followup issue about making this the default behavior in some future version of Mocha.

@JoshuaKGoldberg JoshuaKGoldberg changed the title Option to use posix exit code upon fatal signal feat: add option to use posix exit code upon fatal signal Mar 4, 2024
Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>
@73rhodes
Copy link
Author

@JoshuaKGoldberg Thanks for the review! Hopefully that latest update addresses your questions, but let me know if there's anything else you'd like to see. Looking froward to not using our own fork of mocha! 😆

Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

Progress! Thanks for adding in the tests. I think we'll need to capture some more cases?

docs/index.md Outdated Show resolved Hide resolved
bin/mocha.js Show resolved Hide resolved
73rhodes and others added 2 commits March 25, 2024 22:58
Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>
@JoshuaKGoldberg
Copy link
Member

Will take a look, thanks!

Remove test that asserts the problematic behavior
Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

This looks wonderful to me, thanks so much for all the discussion and iteration @73rhodes! 🙌

Marking this as having one team member approval. We'll want another team member to take a look too. Ping @voxpelli, IIRC you were particularly interested in making Mocha more standards-compliant?

@JoshuaKGoldberg JoshuaKGoldberg requested review from a team and removed request for plroebuck April 24, 2024 20:29
@coveralls
Copy link

coveralls commented Apr 25, 2024

Coverage Status

coverage: 94.371% (+0.01%) from 94.358%
when pulling 59e471d on zendesk:master
into 99601da on mochajs:master.

docs/index.md Outdated Show resolved Hide resolved
@73rhodes
Copy link
Author

@JoshuaKGoldberg just circling back to see if there's anything you need from me to unblock this enhancement? lmk 🙏

@JoshuaKGoldberg
Copy link
Member

Thanks for the ping! Nothing yet. The other maintainers have been swamped (so much happening in the ecosystem!) but I'm still holding onto hope someone will be able to review. I've personally time boxed this one to within June.

Copy link
Member

@voxpelli voxpelli left a comment

Choose a reason for hiding this comment

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

As far as I can see this only changes the flow in this flow:

if (mochaArgs['node-option'] || Object.keys(nodeArgs).length || hasInspect) {

Not in the other flow:

mocha/bin/mocha.js

Lines 139 to 141 in 2f3fedc

} else {
debug('running Mocha in-process');
require('../lib/cli/cli').main([], mochaArgs);

Is this really the right place for this fix?

Eg: The non-standard error codes are set here:

const exitMochaLater = code => {
process.on('exit', () => {
process.exitCode = Math.min(code, 255);
});
};
/**
* Exits Mocha when Mocha itself has finished execution, regardless of
* what the tests or code under test is doing.
* @param {number} code - Exit code; typically # of failures
* @ignore
* @private
*/
const exitMocha = code => {
const clampedCode = Math.min(code, 255);
let draining = 0;
// Eagerly set the process's exit code in case stream.write doesn't
// execute its callback before the process terminates.
process.exitCode = clampedCode;

And in the old-school case where one forces the process to be killed once all tests are done it happens within that:

process.exit(clampedCode);


I overall find this PR to be a mix of two changes:

  1. Stop mocha from abusing the exit code as an error count reporter
  2. Add signal specific exit codes

The latter of the two I would want to read up on more, but the first of them should be an easier fix.

@voxpelli
Copy link
Member

Node.js should already be setting the signal exit codes, I wonder if we can piggy back on that instead? https://nodejs.org/api/process.html#exit-codes

@JoshuaKGoldberg JoshuaKGoldberg added status: waiting for author waiting on response from OP - more information needed semver-minor implementation requires increase of "minor" version number; "features" and removed semver-minor implementation requires increase of "minor" version number; "features" status: in triage a maintainer should (re-)triage (review) this issue labels Jul 2, 2024
@73rhodes
Copy link
Author

73rhodes commented Jul 4, 2024

@voxpelli Thanks for the review. That's right, the option only applies "when mocha is run as a child process". The reasoning was to try and be as unobtrusive as possible and address specifically the problem of fatal errors (OOM et al) getting swallowed silently in a CI/CD pipeline where mocha is spawned by something like nyc. It sounds like there's general agreement that mocha shouldn't use the exit code to report the number of errors though, so I can update this PR to support the option in both cases. As for exit codes other than 0, 1 and 128+signal - I guess we'd need to know what the exit code scheme should be and could be something for another PR....

@voxpelli
Copy link
Member

voxpelli commented Jul 4, 2024

@73rhodes Is there a need to handle 128+signal codes manually or is that already handled by node.js itself? Reason why I ask is since its typically not something I have seen handled manually by other CLI tools built with node.js

73rhodes and others added 2 commits July 11, 2024 11:48
@73rhodes
Copy link
Author

73rhodes commented Aug 2, 2024

@voxpelli ... I just updated the PR with a commit (this PR) that adds the signal-handling behavior for both the child-process and in-process modes of running mocha, as requested. As far as piggybacking on node's signal handling, I'm just working with these inputs:

proc.on('exit', (code, signal) => {

and

const exitMochaLater = code => {

where, based on what I've seen and implemented in the tests, we need to check the code and signal and decide on the desired exit code ourselves. Maybe it's possible that at some deeper layer of mocha internals there's a different way to implement this, but this has been working solidly in production.

@JoshuaKGoldberg JoshuaKGoldberg added status: needs review a maintainer should (re-)review this pull request and removed status: waiting for author waiting on response from OP - more information needed labels Aug 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver-minor implementation requires increase of "minor" version number; "features" status: needs review a maintainer should (re-)review this pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🚀 Feature: Add option to exit with standard exit codes
5 participants