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

Remove separate "internal error" from exec #802

Merged
merged 7 commits into from Jan 29, 2018
Merged

Conversation

freitagbr
Copy link
Contributor

Fixes #734

Removes common.error('internal error') from exec, allowing any internal errors in exec to be wrapped by ShellJSInternalError instead.

@freitagbr freitagbr added breaking Breaking change exec Issues specific to the shell.exec() API labels Nov 10, 2017
@freitagbr freitagbr added this to the v0.8.0 milestone Nov 10, 2017
test/exec.js Outdated
@@ -35,7 +35,7 @@ test('config.fatal and unknown command', t => {
shell.config.fatal = true;
t.throws(() => {
shell.exec('asdfasdf'); // could not find command
}, /exec: internal error/);
}, /((asdfasdf: not found)|(asdfasdf: command not found)|('asdfasdf' is not recognized))/);
Copy link
Member

Choose a reason for hiding this comment

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

Why 3 regexes? If these are different shells, let's put a comment.

Should we check it's a shelljsinternalerror?

test/exec.js Outdated
t.truthy(shell.error());
const err = t.throws(() => {
shell.exec(`${JSON.stringify(shell.config.execPath)} test/resources/exec/slow.js 100`, { timeout: 10 }); // times out
}, /ENOENT: no such file or directory/);
Copy link
Member

Choose a reason for hiding this comment

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

Oh, let's actually debug this. This looks like a real bug

@nfischer
Copy link
Member

Wow, this is a huge improvement. For curiosity, what do the stack traces look like for these errors?

The stdout and stderr files may never be opened or written to in certain
circumstances. In particular, if the timeout is short enough, the child
node process does not have enough time to start, and the child script
does not execute, so the files are not written to. So, catch errors form
trying to read the files, and ignore them.
src/exec.js Outdated
if (opts.encoding === 'buffer') {
stdout = fs.readFileSync(stdoutFile);
stderr = fs.readFileSync(stderrFile);
try { stdout = fs.readFileSync(stdoutFile); } catch (e) {}
Copy link
Member

Choose a reason for hiding this comment

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

Is this really the right fix? Why aren't the files created by the child process? I think that's where the bug really is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the unit test specifically, the timeout is set to 10 ms, and node doesn't startup in that time. The code in exec-child.js is what is creating the stdout and stderr files, and so if node hasn't started, then the files will not exist after the execSync call exits.

Perhaps instead the files should be created before the execSync call, and the child process just writes to them?

Copy link
Member

Choose a reason for hiding this comment

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

This sort of bug usually means that the exec() call inside exec-child.js throws an exception:

var c = childProcess.exec(cmd, execOptions, function (err) {

Copy link
Member

Choose a reason for hiding this comment

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

Oh, didn't see your last message. Huh, if node doesn't startup, then I think we have a few options:

  1. Don't try to handle this, let it be an internal error
  2. Modify the test's timeout so that we're more confident node can start up
  3. Create files beforehand (but this isn't really necessary in the common case, seems like a waste)

Copy link
Member

Choose a reason for hiding this comment

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

This test case is all about checking the timeout option works. So we can also try something like:

echo start
sleep 5
echo stop # Check that this doesn't print, because we timeout before this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for the delay, I'll work on this soon.

Copy link
Member

Choose a reason for hiding this comment

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

No rush. I think it's fine to wrap this into v0.9 anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test passes when the timeout is set to 500 ms (minus the try/catch statements).If you think it's alright, I will just increase the timeout on the test.

Copy link
Member

Choose a reason for hiding this comment

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

You mean to revert the try-catch statements here and to change the test timeout instead? I think that's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'll go ahead with this.

@nfischer nfischer modified the milestones: v0.8.0, v0.9.0 Jan 11, 2018
test/exec.js Outdated
}, /exec: internal error/);
// the expected message depends on where tests are run:
// /bin/sh says "foo: not found"
// /bin/bash says "foo: command not found"
Copy link
Member

Choose a reason for hiding this comment

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

We only need to support /bin/sh and cmd.exe, since these are the default shells. I think error messages are implementation specific, so let's just limit it to those 2 if we can get away with it (so long as mac, linux, and windows bots are happy).

Copy link
Contributor Author

@freitagbr freitagbr Jan 12, 2018

Choose a reason for hiding this comment

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

On my machine, at least, /bin/sh is the default shell, but the message is different than /bin/sh on linux.

Copy link
Member

Choose a reason for hiding this comment

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

How about we regex for asdfasdf instead? I think that's good enough.

src/exec.js Outdated
if (opts.encoding === 'buffer') {
stdout = fs.readFileSync(stdoutFile);
stderr = fs.readFileSync(stderrFile);
try { stdout = fs.readFileSync(stdoutFile); } catch (e) {}
Copy link
Member

Choose a reason for hiding this comment

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

You mean to revert the try-catch statements here and to change the test timeout instead? I think that's fine.

@nfischer
Copy link
Member

@freitagbr any idea why we have travis failures?

@nfischer
Copy link
Member

Ah, I tried it locally. It looks like e.status is 0. Must be a behavior change between node 5/6. We probably shouldn't let code be 0 if an exception was thrown. Maybe we can try code = e.status || DEFAULT_ERROR_CODE.

@codecov-io
Copy link

codecov-io commented Jan 29, 2018

Codecov Report

Merging #802 into master will increase coverage by 0.33%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #802      +/-   ##
==========================================
+ Coverage   95.47%   95.81%   +0.33%     
==========================================
  Files          34       34              
  Lines        1260     1361     +101     
==========================================
+ Hits         1203     1304     +101     
  Misses         57       57
Impacted Files Coverage Δ
src/exec.js 97.1% <100%> (+0.04%) ⬆️
src/common.js 98.93% <0%> (+0.58%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6189d7f...5e79a99. Read the comment docs.

@nfischer
Copy link
Member

LGTM. Could you add a comment explaining that .status is sometimes 0, even when this is in fact an error, and that DEFAULT_ERROR_CODE was chosen arbitrarily?

@freitagbr
Copy link
Contributor Author

Sure, I will do that. Also, this is a breaking change, so do we want to land this on a development branch? i.e. landing in v0.9 vs. v0.8.

@nfischer nfischer removed the breaking Breaking change label Jan 29, 2018
@nfischer
Copy link
Member

I don't think this is really breaking anything. The behavior changes I see are:

  • exec() used to abort in an unhelpful way when it had bugs. The bugs are still there, but now it aborts in a helpful way
    • If someone depended on aborting-without-exception and are now broken, it means they were doing something which caused an exec() bug to begin with. We don't support bugs
  • exec() used to return a shellstring with an empty .stderr attribute. Now its .stderr is useful

If you agree, feel free to merge

@freitagbr freitagbr merged commit 9077f41 into master Jan 29, 2018
@freitagbr freitagbr deleted the exec-internal-error branch January 29, 2018 20:28
@joelchoo
Copy link

joelchoo commented May 8, 2018

@nfischer I'm really interested in getting this fix. Any indication of when 0.8.2 will be coming out?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
exec Issues specific to the shell.exec() API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants