Skip to content

Commit

Permalink
throw error when exitOnError set to false and errors occur - fixes #48
Browse files Browse the repository at this point in the history
  • Loading branch information
SamVerschueren committed Feb 19, 2017
1 parent c29cf62 commit 7a76a71
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 22 deletions.
17 changes: 13 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
const Task = require('./lib/task');
const TaskWrapper = require('./lib/task-wrapper');
const renderer = require('./lib/renderer');
const ListrError = require('./lib/listr-error');

const runTask = (task, context) => {
const runTask = (task, context, errors) => {
if (!task.isEnabled()) {
return Promise.resolve();
}

return new TaskWrapper(task).run(context);
return new TaskWrapper(task, errors).run(context);
};

class Listr {
Expand Down Expand Up @@ -74,21 +75,29 @@ class Listr {

context = context || Object.create(null);

const errors = [];

this._checkAll(context);

let tasks;
if (this._options.concurrent === true) {
tasks = Promise.all(this._tasks.map(task => runTask(task, context)));
tasks = Promise.all(this._tasks.map(task => runTask(task, context, errors)));
} else {
tasks = this._tasks.reduce((promise, task) => promise.then(() => {
this._checkAll(context);

return runTask(task, context);
return runTask(task, context, errors);
}), Promise.resolve());
}

return tasks
.then(() => {
if (errors.length > 0) {
const err = new ListrError('Something went wrong');
err.errors = errors;
throw err;
}

this._renderer.end();

return context;
Expand Down
9 changes: 9 additions & 0 deletions lib/listr-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';
class ListrError extends Error {
constructor(message) {
super(message);
this.name = 'ListrError';
}
}

module.exports = ListrError;
14 changes: 13 additions & 1 deletion lib/task-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use strict';
const state = require('./state');
const ListrError = require('./listr-error');

class TaskWrapper {

constructor(task) {
constructor(task, errors) {
this._task = task;
this._errors = errors;
}

set title(title) {
Expand All @@ -20,6 +22,16 @@ class TaskWrapper {
return this._task.title;
}

report(error) {
if (error instanceof ListrError) {
for (const err of error.errors) {
this._errors.push(err);
}
} else {
this._errors.push(error);
}
}

skip(message) {
if (message && typeof message !== 'string') {
throw new TypeError(`Expected \`message\` to be of type \`string\`, got \`${typeof message}\``);
Expand Down
8 changes: 8 additions & 0 deletions lib/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const Subject = require('rxjs/Subject').Subject;
const renderer = require('./renderer');
const state = require('./state');
const utils = require('./utils');
const ListrError = require('./listr-error');

const defaultSkipFn = () => false;

Expand Down Expand Up @@ -179,13 +180,20 @@ class Task extends Subject {
.catch(err => {
this.state = state.FAILED;

if (err instanceof ListrError) {
wrapper.report(err);
return;
}

this._output = err.message;

this.next({
type: 'DATA',
data: err.message
});

wrapper.report(err);

if (this._listr.exitOnError !== false) {
// Do not exit when explicitely set to `false`
throw err;
Expand Down
105 changes: 88 additions & 17 deletions test/exit-on-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ test('exit on error', async t => {
});

test('set `exitOnError` to false', async t => {
t.plan(8);

const list = new Listr(tasks, {
exitOnError: false,
renderer: SimpleRenderer
Expand All @@ -50,10 +52,17 @@ test('set `exitOnError` to false', async t => {
'done'
]);

await list.run();
try {
await list.run();
} catch (err) {
t.is(err.message, 'Something went wrong');
t.is(err.errors.length, 1);
}
});

test('set `exitOnError` to false in nested list', async t => {
t.plan(15);

const list = new Listr([
{
title: 'foo',
Expand All @@ -65,7 +74,7 @@ test('set `exitOnError` to false in nested list', async t => {
return new Listr([
{
title: 'unicorn',
task: () => Promise.reject(new Error('Something went wrong'))
task: () => Promise.reject(new Error('Unicorn failed'))
},
{
title: 'rainbow',
Expand All @@ -90,31 +99,39 @@ test('set `exitOnError` to false in nested list', async t => {
'bar [started]',
'unicorn [started]',
'unicorn [failed]',
'> Something went wrong',
'> Unicorn failed',
'rainbow [started]',
'rainbow [completed]',
'bar [completed]',
'bar [failed]',
'baz [started]',
'baz [completed]',
'done'
]);

await list.run();
try {
await list.run();
} catch (err) {
t.is(err.message, 'Something went wrong');
t.is(err.errors.length, 1);
t.is(err.errors[0].message, 'Unicorn failed');
}
});

test('set `exitOnError` to false in root', async t => {
t.plan(17);

const list = new Listr([
{
title: 'foo',
task: () => Promise.reject(new Error('Something went wrong in foo'))
task: () => Promise.reject(new Error('Foo failed'))
},
{
title: 'bar',
task: () => {
return new Listr([
{
title: 'unicorn',
task: () => Promise.reject(new Error('Something went wrong in unicorn'))
task: () => Promise.reject(new Error('Unicorn failed'))
},
{
title: 'rainbow',
Expand All @@ -135,35 +152,44 @@ test('set `exitOnError` to false in root', async t => {
testOutput(t, [
'foo [started]',
'foo [failed]',
'> Something went wrong in foo',
'> Foo failed',
'bar [started]',
'unicorn [started]',
'unicorn [failed]',
'> Something went wrong in unicorn',
'> Unicorn failed',
'rainbow [started]',
'rainbow [completed]',
'bar [completed]',
'bar [failed]',
'baz [started]',
'baz [completed]',
'done'
]);

await list.run();
try {
await list.run();
} catch (err) {
t.is(err.name, 'ListrError');
t.is(err.errors.length, 2);
t.is(err.errors[0].message, 'Foo failed');
t.is(err.errors[1].message, 'Unicorn failed');
}
});

test('set `exitOnError` to false in root and true in child', async t => {
t.plan(16);

const list = new Listr([
{
title: 'foo',
task: () => Promise.reject(new Error('Something went wrong in foo'))
task: () => Promise.reject(new Error('Foo failed'))
},
{
title: 'bar',
task: () => {
return new Listr([
{
title: 'unicorn',
task: () => Promise.reject(new Error('Something went wrong in unicorn'))
task: () => Promise.reject(new Error('Unicorn failed'))
},
{
title: 'rainbow',
Expand All @@ -186,17 +212,62 @@ test('set `exitOnError` to false in root and true in child', async t => {
testOutput(t, [
'foo [started]',
'foo [failed]',
'> Something went wrong in foo',
'> Foo failed',
'bar [started]',
'unicorn [started]',
'unicorn [failed]',
'> Something went wrong in unicorn',
'> Unicorn failed',
'bar [failed]',
'> Something went wrong in unicorn',
'> Unicorn failed',
'baz [started]',
'baz [completed]',
'done'
]);

await list.run();
try {
await list.run();
} catch (err) {
t.is(err.name, 'ListrError');
t.is(err.errors.length, 2);
t.is(err.errors[0].message, 'Foo failed');
t.is(err.errors[1].message, 'Unicorn failed');
}
});

test('exit on error throws error object with context', async t => {
t.plan(10);

const list = new Listr([
{
title: 'foo',
task: () => Promise.reject(new Error('Foo failed'))
},
{
title: 'bar',
task: ctx => {
ctx.foo = 'bar';
}
}
], {
exitOnError: false,
renderer: SimpleRenderer
});

testOutput(t, [
'foo [started]',
'foo [failed]',
'> Foo failed',
'bar [started]',
'bar [completed]',
'done'
]);

try {
await list.run();
} catch (err) {
t.is(err.name, 'ListrError');
t.is(err.errors.length, 1);
t.is(err.errors[0].message, 'Foo failed');
t.deepEqual(err.context, {foo: 'bar'});
}
});

0 comments on commit 7a76a71

Please sign in to comment.