GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — process: make `exitCode` configurable again by ljharb · Pull Request #49579 · nodejs/node Conversation
|
|
Review requested:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I like this change, solves my problem. (I worked around it by just letting the code in my tests update the "real" process.exitCode and then verifying it's been set, and setting it back to 0. But that's less good, because it means I can't also verify that it was set the number of times I expect, in the places I expect, etc.) Another idea to consider, which would preserve the air-tight protection against non-number exitCode:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Is it worthwhile to add a test for this? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Happy to if you can suggest where to put it :-) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Maybe see if it's possible/reasonable to include it in If that doesn't seem like the right place, perhaps create a separate test file as |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
What's interesting is that that test isn't failing now. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
+1 (agree with @Trott this should have a test)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
The process.exitCode = 1; delete process.exitCode; process.exitCode = 0; // With this patch, node will exit with 1, not 0. Previously, the JavaScript side hold the node/lib/internal/bootstrap/node.js Lines 123 to 124 in c55625f
Since v20, JavaScript side and the native side share the @isaacs I'm not entirely sure if I grasp your issue correctly, but it seems like you want to monitor changes in the exit code during testing. I'm not certain if this is a good idea, but would it resolve your issue if we would trigger an event along with a modified exit code whenever it's changed? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
If plugins export an `importLoader`, and Module.register exists, then
use that instead of their `loader` export.
process.exitCode became {configurable:false} in
nodejs/node#44711
Can bring back exitCode interception when/if it becomes configurable
again, re nodejs/node#49579
For now, just set it, and then verify it's the expected value, and put
it back to 0 if so.
If plugins export an `importLoader`, and Module.register exists, then
use that instead of their `loader` export.
process.exitCode became {configurable:false} in
nodejs/node#44711
Can bring back exitCode interception when/if it becomes configurable
again, re nodejs/node#49579
For now, just set it, and then verify it's the expected value, and put
it back to 0 if so.
There was an error while loading. Please reload this page.
Yes, it's for testing. It's somewhat less convenient to not be able to just do
Personally, I think that's fine. Put a note in the docs that |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I'm happy to add a test, but the existing |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
It seems that process.exitCode = 0; throws(() => { delete process.exitCode; // configurable: true }, /Cannot delete property 'exitCode' of #<process>/); In the test, we last set exitCode to 0 and then do node/lib/internal/process/execution.js Lines 156 to 167 in 48fcb20
In 165 line, the handler set exitCode to |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Is that a deeper bug that I should try to fix? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Yes, it's needed to handle. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
ok, i was able to verify that the test failed locally, and then i updated the test to pass with the change, so in theory this is good to go :-D |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| const origExitCode = Object.getOwnPropertyDescriptor(process, 'exitCode'); | ||
| try { | ||
| delete process.exitCode; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I think it needs to be handled so that even when users delete process.exitCode, the exit code set by the core code will still be applied.
process.exitCode = kGenericUserError;
process.exitCode is also used by the core. This is concerning because if users delete it, the intended behavior in the core, like the code mentioned above (#49579 (comment)), won't be applied.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how that would be possible. If it's deleted, there's nothing there.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Do note that process.exitCode = kGenericUserError; will still work after it's been deleted, it just won't have the magic getter/setter anymore.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
If process.exitCode = kGenericUserError; worked, the error code after terminating the node should be kGenericUserError, not 0. Last time I checked, it was 0... right?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I mean it should not be 0 when calling the exit status. echo $?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
it can be restricted to any domain you like :-) just integer numbers then?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I can give you the answer you want, as I can't guess how you'd implement it based on your current description. Currently, the types allowed are:
* {integer|string|null|undefined} The exit code. For string type, only
integer strings (e.g.,'1') are allowed. **Default:** `undefined`.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
ok, so I’ll move forward with that schema, thanks.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
hmm - seems like the exitCode is read in C++, so i'm not sure how it can read the JS-set value once it's overridden.
@daeyeon are you comfortable with this proceeding with the understanding that if a user mocks process.exitCode, it's up to them to restore it? (just like other globals)
If not, what about #49579 (comment) ?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
@daeyeon are you comfortable with this proceeding with the understanding that if a user mocks process.exitCode, it's up to them to restore it? (just like other globals)
The core's behavior related to exitCode will be breaking during the time gap between mocking and restoring. It's probably not enough to rely on the restoring after finishing the mocking.
If not, what about #49579 (comment) ?
Looks like a good idea. In addition to moving the getters/setters to the process prototype, I think that the core code should use the exitCode of the process prototype, rather than the process itself. This would address concerns about breaking and can provide guidance on how users can monitor it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
What if the getter/setter was on the prototype of the process, rather than the process object itself? Something like this: class Process { #exitCode = 0 get exitCode() { return this.#exitCode } set exitCode(ec) { if (ec === Math.floor(ec)) { this.#exitCode = ec } else { throw new Error('cannot set exit code to non-int') } } } // make the property on the prototype non-configurable Object.defineProperty(Process.prototype, 'exitCode', { ...Object.getOwnPropertyDescriptor(Process.prototype, 'exitCode'), configurable: false, }) const process = new Process() Object.defineProperty(process, 'exitCode', { value: 2, configurable: true, writable: true, }) console.log(process.exitCode) // 2, from defined property on object delete process.exitCode // deletes from object, not prototype console.log(process.exitCode) // 0 try { Object.defineProperty(process.__proto__, 'exitCode', { value: 99 }) } catch { console.log('cannot redefine') // this is printed } With this approach:
That is, you get this weirdness, which imo is way less weird than what we have now: process.exitCode = 1 delete process.exitCode console.log(process.exitCode) // 1 // process exits with code 1, not zero as would've happened prior to the non-configurable propdef |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
That's a clever alternative - but then the actual exit code still wouldn't read from the overridden value? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Correct, if you But isn't that sort of what happens in the code right now? Like, if you make the getter/setter configurable, then if someone redefines it, it no longer affects the actual process exit status code, until/unless they put it back. The difference is that "put it back" is |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| const origExitCode = Object.getOwnPropertyDescriptor(process, 'exitCode'); | ||
| try { | ||
| delete process.exitCode; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I think we should just test that redefining process.exitCode with accessors that call out to the original at the end still works as expected. That means users can still mock it if they call out to the original accessors. If they don't, or if they delete it, then it is just not going to get picked up as the actual exit code. And we can just put a note in the doc warning that delete process.exitCode is not going to work, like what @isaacs suggested in #49579 (comment)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Maybe we can have both? By default we define it both on the prototype and the process object itself. The own version is configurable, and mocking code can define different accessors on the process itself, calling out to the original own accessors for Node.js to pick up the exit code if necessary. If they delete the own version then there's still the prototype version for compat. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Sounds like a good direction. I'll update this soon. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This pull request has been marked as stale due to 90 days of inactivity. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Requested changes must be addressed to merge this pull request.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
unexpected breaking changes withprocess.exitCode/process.exit()
There was an error while loading. Please reload this page.