[plugin-helpers] report task failures to CLI with exitCode 1 (#17647) (#17649)

* [plugin-helpers] return promises/fail cli when async tasks fail

* [plugin-helpers] rename taskRunner to commanderAction

* [plugin-helpers] await async assertion
This commit is contained in:
Spencer 2018-04-10 17:45:09 -07:00 committed by GitHub
parent 079f303357
commit 967a3a69be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 180 additions and 49 deletions

View file

@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`commander action exits with status 1 when task throws error asynchronously 1`] = `
Array [
Array [
"Task \\"mockTask\\" failed:
Error: async error thrown
...stack trace...
",
],
]
`;
exports[`commander action exits with status 1 when task throws synchronously 1`] = `
Array [
Array [
"Task \\"mockTask\\" failed:
Error: sync error thrown
...stack trace...
",
],
]
`;
exports[`commander action passes args to getOptions, calls run() with taskName and options 1`] = `
Array [
Array [
"taskName",
Object {
"args": Array [
"b",
"c",
"d",
"e",
"f",
],
},
],
]
`;

View file

@ -0,0 +1,12 @@
const run = require('./run');
module.exports = function createCommanderAction(taskName, getOptions = () => {}) {
return async (command, ...args) => {
try {
await run(taskName, getOptions(...args));
} catch (error) {
process.stderr.write(`Task "${taskName}" failed:\n\n${error.stack || error.message}\n`);
process.exit(1);
}
};
};

View file

@ -0,0 +1,70 @@
/*eslint-env jest*/
const createCommanderAction = require('./commander_action');
const run = require('./run');
jest.mock('./run', () => jest.fn());
const STACK_TRACE_RE = /\n(?:\s+at .+(?:\n|$))+/g;
expect.addSnapshotSerializer({
print(val, serialize) {
return serialize(val.replace(STACK_TRACE_RE, '\n ...stack trace...\n'));
},
test(val) {
return typeof val === 'string' && STACK_TRACE_RE.test(val);
},
});
beforeAll(() => {
jest.spyOn(process.stderr, 'write').mockImplementation(() => {});
jest.spyOn(process, 'exit').mockImplementation(() => {});
});
beforeEach(() => {
run.mockReset();
jest.clearAllMocks();
});
afterAll(() => {
jest.restoreAllMocks();
});
describe('commander action', () => {
it('creates a function', async () => {
expect(typeof createCommanderAction()).toBe('function');
});
it('passes args to getOptions, calls run() with taskName and options', async () => {
const action = createCommanderAction('taskName', (...args) => ({ args }));
await action('a', 'b', 'c', 'd', 'e', 'f');
expect(run).toHaveBeenCalledTimes(1);
expect(run.mock.calls).toMatchSnapshot();
});
it('exits with status 1 when task throws synchronously', async () => {
run.mockImplementation(() => {
throw new Error('sync error thrown');
});
await createCommanderAction('mockTask')();
expect(process.stderr.write).toHaveBeenCalledTimes(1);
expect(process.stderr.write.mock.calls).toMatchSnapshot();
expect(process.exit).toHaveBeenCalledTimes(1);
expect(process.exit).toHaveBeenCalledWith(1);
});
it('exits with status 1 when task throws error asynchronously', async () => {
run.mockImplementation(async () => {
throw new Error('async error thrown');
});
await createCommanderAction('mockTask')();
expect(process.stderr.write).toHaveBeenCalledTimes(1);
expect(process.stderr.write.mock.calls).toMatchSnapshot();
expect(process.exit).toHaveBeenCalledTimes(1);
expect(process.exit).toHaveBeenCalledWith(1);
});
});

View file

@ -3,7 +3,9 @@ const tasks = require('./tasks');
module.exports = function run(name, options) {
const action = tasks[name];
if (!action) throw new Error('Invalid task: "' + name + '"');
if (!action) {
throw new Error('Invalid task: "' + name + '"');
}
const plugin = pluginConfig();
return action(plugin, run, options);

View file

@ -1,15 +1,16 @@
/*eslint-env jest*/
const testTask = jest.fn();
const plugin = { id: 'testPlugin' };
jest.mock('./plugin_config', () => () => (
{ id: 'testPlugin' }
));
jest.mock('./plugin_config', () => () => plugin);
jest.mock('./tasks', () => {
return { testTask };
return { testTask: jest.fn() };
});
const run = require('./run');
describe('task runner', () => {
describe('lib/run', () => {
beforeEach(() => jest.resetAllMocks());
it('throw given an invalid task', function () {
@ -22,9 +23,19 @@ describe('task runner', () => {
it('runs specified task with plugin and runner', function () {
run('testTask');
const { testTask } = require('./tasks');
const plugin = require('./plugin_config')();
const args = testTask.mock.calls[0];
expect(testTask.mock.calls).toHaveLength(1);
expect(args[0]).toBe(plugin);
expect(args[0]).toEqual(plugin);
expect(args[1]).toBe(run);
});
});
it('returns value returned by task', function () {
const { testTask } = require('./tasks');
const symbol = Symbol('foo');
testTask.mockReturnValue(symbol);
expect(run('testTask')).toBe(symbol);
});
});