Skip to content

Commit

Permalink
test: add coverage for exit function
Browse files Browse the repository at this point in the history
This adds test coverage for the shell.exit() function. This also
refactors how we mock stdout/stderr and adds support for mocking
process.exit() (which was needed for this change).

While I was writing these tests, I realized there was an edge case I
missed in PR #1122. This change fixes that edge case.

Issue #1013
  • Loading branch information
nfischer committed Nov 12, 2023
1 parent b8b1c42 commit 1d8a108
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 64 deletions.
2 changes: 2 additions & 0 deletions shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ require('./commands').forEach(function (command) {
//@
//@ Exits the current process with the given exit `code`.
exports.exit = function exit(code) {
common.state.error = null;
common.state.errorCode = 0;
if (code) {
common.error('exit', {
continue: true,
Expand Down
50 changes: 26 additions & 24 deletions test/echo.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ shell.config.silent = true;

test.beforeEach(t => {
t.context.tmp = utils.getTempDir();
mocks.init();
mocks.stdout.init();
mocks.stderr.init();
});

test.afterEach.always(t => {
shell.rm('-rf', t.context.tmp);
mocks.restore();
mocks.stdout.restore();
mocks.stderr.restore();
});

//
Expand All @@ -22,8 +24,8 @@ test.afterEach.always(t => {

test('simple test with defaults', t => {
const result = shell.echo('hello', 'world');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, 'hello world\n');
Expand All @@ -33,8 +35,8 @@ test('simple test with defaults', t => {
test('allow arguments to begin with a hyphen', t => {
// Github issue #20
const result = shell.echo('-asdf', '111');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 1);
t.is(stdout, '-asdf 111\n');
Expand All @@ -43,8 +45,8 @@ test('allow arguments to begin with a hyphen', t => {

test("using null as an explicit argument doesn't crash the function", t => {
const result = shell.echo(null);
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, 'null\n');
Expand All @@ -53,8 +55,8 @@ test("using null as an explicit argument doesn't crash the function", t => {

test('-e option', t => {
const result = shell.echo('-e', '\tmessage');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, '\tmessage\n');
Expand All @@ -72,8 +74,8 @@ test('piping to a file', t => {
t.falsy(shell.error());
t.is(resultB.code, 0);
const result = shell.cat(tmp);
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(stdout, 'A\nB\n');
t.is(stderr, '');
Expand All @@ -82,8 +84,8 @@ test('piping to a file', t => {

test('-n option', t => {
const result = shell.echo('-n', 'message');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, 'message');
Expand All @@ -92,8 +94,8 @@ test('-n option', t => {

test('-ne option', t => {
const result = shell.echo('-ne', 'message');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, 'message');
Expand All @@ -102,8 +104,8 @@ test('-ne option', t => {

test('-en option', t => {
const result = shell.echo('-en', 'message');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, 'message');
Expand All @@ -112,8 +114,8 @@ test('-en option', t => {

test('-en option with escaped characters', t => {
const result = shell.echo('-en', '\tmessage\n');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 0);
t.is(stdout, '\tmessage\n');
Expand All @@ -131,8 +133,8 @@ test('piping to a file with -n', t => {
t.falsy(shell.error());
t.is(resultB.code, 0);
const result = shell.cat(tmp);
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(stdout, 'AB');
t.is(stderr, '');
Expand All @@ -141,8 +143,8 @@ test('piping to a file with -n', t => {

test('stderr with unrecognized options is empty', t => {
const result = shell.echo('-asdf');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(result.code, 1);
t.falsy(result.stderr);
Expand Down
10 changes: 6 additions & 4 deletions test/exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ const ORIG_EXEC_PATH = shell.config.execPath;
shell.config.silent = true;

test.beforeEach(() => {
mocks.init();
mocks.stdout.init();
mocks.stderr.init();
});

test.afterEach.always(() => {
process.chdir(CWD);
shell.config.execPath = ORIG_EXEC_PATH;
mocks.restore();
mocks.stdout.restore();
mocks.stderr.restore();
});

//
Expand Down Expand Up @@ -102,8 +104,8 @@ test('check if stdout + stderr go to output', t => {

test('check if stdout + stderr should not be printed to console if silent', t => {
shell.exec(`${JSON.stringify(shell.config.execPath)} -e "console.error(1234); console.log(666); process.exit(12);"`, { silent: true });
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.is(stdout, '');
t.is(stderr, '');
});
Expand Down
57 changes: 57 additions & 0 deletions test/exit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import test from 'ava';

import shell from '..';

import mocks from './utils/mocks';

//
// Valids
//

function runExitInSubprocess(code) {
const script = code !== undefined
? `var shell = require("."); shell.exit(${code});`
: 'var shell = require("."); shell.exit();';
const result = shell.exec(
`${JSON.stringify(shell.config.execPath)} -e ${JSON.stringify(script)}`
);
const actualReturnCode = result.code;
return actualReturnCode;
}

test('exit with success status code', t => {
t.is(runExitInSubprocess(0), 0);
});

test('exit without explicit code should be success', t => {
t.is(runExitInSubprocess(), 0);
});

test('exit with failure status code', t => {
t.is(runExitInSubprocess(5), 5);
t.is(runExitInSubprocess(2), 2);
t.is(runExitInSubprocess(25), 25);
});

test('exit correctly sets the shell.errorCode()', t => {
try {
mocks.exit.init();
shell.exit(5); // Safe to call shell.exit() because it's mocked.
t.is(shell.errorCode(), 5);
t.is(mocks.exit.getValue(), 5);
t.truthy(shell.error());

shell.exit(0); // Safe to call shell.exit() because it's mocked.
t.is(shell.errorCode(), 0);
t.falsy(mocks.exit.getValue());
t.falsy(shell.error());

// Also try it without an explicit argument.
shell.exit(); // Safe to call shell.exit() because it's mocked.
t.is(shell.errorCode(), 0);
t.falsy(mocks.exit.getValue());
t.falsy(shell.error());
} finally {
mocks.exit.restore();
}
});
20 changes: 12 additions & 8 deletions test/popd.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,36 +120,40 @@ test('quiet mode off', t => {
try {
shell.pushd('test/resources/pushd');
shell.config.silent = false;
mocks.init();
mocks.stdout.init();
mocks.stderr.init();
const trail = shell.popd();
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(stdout, '');
t.is(stderr, `${rootDir}\n`);
t.is(process.cwd(), trail[0]);
t.deepEqual(trail, [rootDir]);
} finally {
shell.config.silent = true;
mocks.restore();
mocks.stdout.restore();
mocks.stderr.restore();
}
});

test('quiet mode on', t => {
try {
shell.pushd('test/resources/pushd');
shell.config.silent = false;
mocks.init();
mocks.stdout.init();
mocks.stderr.init();
const trail = shell.popd('-q');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(stdout, '');
t.is(stderr, '');
t.is(process.cwd(), trail[0]);
t.deepEqual(trail, [rootDir]);
} finally {
shell.config.silent = true;
mocks.restore();
mocks.stdout.restore();
mocks.stderr.restore();
}
});
20 changes: 12 additions & 8 deletions test/pushd.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,11 @@ test('Push without arguments invalid when stack is empty', t => {
test('quiet mode off', t => {
try {
shell.config.silent = false;
mocks.init();
mocks.stdout.init();
mocks.stderr.init();
const trail = shell.pushd('test/resources/pushd');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(stdout, '');
t.is(stderr, `${path.resolve(rootDir, 'test/resources/pushd')} ${rootDir}\n`);
Expand All @@ -347,17 +348,19 @@ test('quiet mode off', t => {
]);
} finally {
shell.config.silent = true;
mocks.restore();
mocks.stdout.restore();
mocks.stderr.restore();
}
});

test('quiet mode on', t => {
try {
shell.config.silent = false;
mocks.init();
mocks.stdout.init();
mocks.stderr.init();
const trail = shell.pushd('-q', 'test/resources/pushd');
const stdout = mocks.stdout();
const stderr = mocks.stderr();
const stdout = mocks.stdout.getValue();
const stderr = mocks.stderr.getValue();
t.falsy(shell.error());
t.is(stdout, '');
t.is(stderr, '');
Expand All @@ -368,6 +371,7 @@ test('quiet mode on', t => {
]);
} finally {
shell.config.silent = true;
mocks.restore();
mocks.stdout.restore();
mocks.stderr.restore();
}
});

0 comments on commit 1d8a108

Please sign in to comment.