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

added suspending update renderer for task #136

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
10 changes: 10 additions & 0 deletions lib/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,21 @@ class Task extends Subject {
this.title = task.title;
this.skip = task.skip || defaultSkipFn;
this.task = task.task;
this.taskOptions = task.options;

Choose a reason for hiding this comment

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

This is moot. You can already access it as this.task.options.

Copy link
Author

@bunysae bunysae Sep 14, 2019

Choose a reason for hiding this comment

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

Sorry, but this.task can be just a function. task.options refers to input-variable task given in the constructor and not to this.task. We will need here a separate variable to store the options.

}

get subtasks() {
return this._subtasks;
}

shouldSuspendUpdateRenderer() {
if (this.isEnabled() && this.isPending() && !this.isCompleted() && this.taskOptions !== undefined) {
bunysae marked this conversation as resolved.
Show resolved Hide resolved
return this.taskOptions.suspendUpdateRenderer;
}

return false;
}

set state(state) {
this._state = state;

Expand Down Expand Up @@ -160,6 +169,7 @@ class Task extends Subject {
if (typeof skipped === 'string') {
this.output = skipped;
}

this.state = state.SKIPPED;
return;
}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
"hook-std": "^1.1.0",
"lint-staged": "^8.0.5",
"log-symbols": "^2.2.0",
"mockery": "^2.1.0",
"nyc": "^13.1.0",
"pre-commit": "^1.2.2",
"sinon": "^7.4.2",
"split": "^1.0.1",
"xo": "*",
"zen-observable": "^0.8.11"
Expand Down
22 changes: 22 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,28 @@ tasks.run();
```


## Rendering options

By default, [listr-update-renderer](https://github.com/SamVerschueren/listr-update-renderer) is active. This renderer refreshes the state of the tasks every 100 milliseconds. With the option ```suspendUpdateRenderer``` the refresh can be suspended for the time the task is running. After the task is completed, the listr-update-renderer starts again refreshing the state of the tasks.
bunysae marked this conversation as resolved.
Show resolved Hide resolved

```js
const tasks = new Listr([
{
title: 'Encrypt gpg-file',
task: () => execa('gpg', ['-e', 'file']),
options: {suspendUpdateRenderer: true}
}
bunysae marked this conversation as resolved.
Show resolved Hide resolved
{
title: 'Remove the unencrypted file',
task: () => execa('rm', ['file'])
}
]);

tasks.run();
```

This feature is usefull for tasks with user-input. The user-input is always gobbled up, when the listr-update-renderer is not in suspend-mode.
bunysae marked this conversation as resolved.
Show resolved Hide resolved

## Custom renderers

It's possible to write custom renderers for Listr. A renderer is an ES6 class that accepts the tasks that it should render, and the Listr options object. It has two methods, the `render` method which is called when it should start rendering, and the `end` method. The `end` method is called when all the tasks are completed or if a task failed. If a task failed, the error object is passed in via an argument.
Expand Down
2 changes: 1 addition & 1 deletion test/concurrent.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {serial as test} from 'ava';
import delay from 'delay';
import Listr from '..';

Choose a reason for hiding this comment

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

Why did you edit these files? They're out of the scope of this PR anyways, and the imports were fine the way they were.

Copy link
Author

Choose a reason for hiding this comment

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

The changes come from xo --fix command. I remove them in my next commit.

import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

const tasks = [
{
Expand Down
2 changes: 1 addition & 1 deletion test/enabled.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {serial as test} from 'ava';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('do not run disabled tasks', async t => {
const list = new Listr([
Expand Down
2 changes: 1 addition & 1 deletion test/exit-on-error.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {serial as test} from 'ava';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

const tasks = [
{
Expand Down
2 changes: 1 addition & 1 deletion test/output.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import test from 'ava';
import {Observable} from 'rxjs';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('output', async t => {
const list = new Listr([
Expand Down
2 changes: 1 addition & 1 deletion test/renderer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('renderer class', async t => {
const list = new Listr([
Expand Down
2 changes: 1 addition & 1 deletion test/skip.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('continue execution if skip() returns false or Promise for false', async t => {
t.plan(5); // Verify the correct number of tasks were executed
Expand Down
2 changes: 1 addition & 1 deletion test/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import * as fs from 'fs';
import * as path from 'path';
import test from 'ava';
import split from 'split';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('output', async t => {
const list = new Listr([
Expand Down
2 changes: 1 addition & 1 deletion test/subtask.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('renderer class', async t => {
const list = new Listr([
Expand Down
68 changes: 68 additions & 0 deletions test/suspend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {serial as test} from 'ava';
import sinon from 'sinon';

const mockery = require('mockery');

const logUpdateApi = {
main(text) {
console.log(text);
}
};
logUpdateApi.main.clear = function () {

Choose a reason for hiding this comment

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

You should create a noop function (e.g. const noop = () => {};) and use it here and below.

Copy link
Author

Choose a reason for hiding this comment

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

I imported the log-update-module directly. This section now should be refactored.

};

logUpdateApi.main.done = function () {
};

const mock = sinon.mock(logUpdateApi);

function getTasks() {
// Require is necessary at this position, otherwise mockery is not working correctly
const Listr = require('..');
const tasks = new Listr([
{
title: 'not suspending task',
task: () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('predefined output');
}, 150);
});
}
},
{
title: 'suspending task',
task: () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('predefined output');
}, 4000);
});
},
options: {suspendUpdateRenderer: true}
}
]);

return tasks;
}

test.before(() => {
/* One time for the first task,
one time when both tasks are finished
*/
mock.expects('main').twice();
mockery.registerAllowable('..');
mockery.registerMock('log-update', logUpdateApi.main);
mockery.enable({useCleanCache: true, warnOnUnregistered: false});
});

test('should suspend second task', async t => {

Choose a reason for hiding this comment

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

This doesn't test use-cases where suspendUpdateRenderer is actually needed. There should be a test(s) for that as well.

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

My intention was to create two task. The first task without the option suspendUpdateRenderer. So the update renderer should display status "task 1 pending" exactly one time. The second task with the option suspendUpdateRenderer. So the update renderer shouldn't display status "task 2 pending", because it is suspended. If the suspend-function doesn't work as expected, the update renderer displays "task 2 pending" more than once, because we have a timeout of 4s.
You could simply try it, when cloning my fork listr locally. With the current version of listr-update-renderer, the tests fail. With linking my fork of listr-update-renderer the tests pass.

What tests you thought of? Integration-tests, which ask for user input or tests, which mock listr-update-renderer?

Copy link

Choose a reason for hiding this comment

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

What I thought of is a test case in which the suspension is actually required, meaning that the test won't pass if the task isn't suspended (and only passes if it is).
(Even if the mock isn't called, that doesn't really mean much. We want the test to verify correct behavior, not logic.)

Copy link
Author

Choose a reason for hiding this comment

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

I added now a custom renderer mock, which output is excatly verified. It only passes, if suspension is activated.
When the mock isn't called with the correct args, the test fails.

Choose a reason for hiding this comment

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

I'm sorry to keep bothering you, but this is still not what I meant in my comments above.
As I recall, the reason we need this in np is since one of the commands we call waits for the user to enter a password, which we need to receive before moving on to the next task.
This test, again, only verifies we correctly pass on the suspendUpdateRenderer option from here to the Listr renderer (let me know if I'm mistaken, though).
I think that the simplest way to test this would be to actually run a command that waits for user input, then verify we do receive it when suspendUpdateRenderer is set to true (and don't when it's set to false, as the task isn't suspended).

Copy link
Author

@bunysae bunysae Mar 6, 2020

Choose a reason for hiding this comment

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

The problem isn't, that the task can't receive the user-input from stdin. The task actually receives the input from stdin independently of suspendUpdateRenderer. The problem is, that listr-update-renderer erases password-prompts on stdout. For the user it looks like the prompt never appears and the task is running infinite.
What we need to check is, that the task-output is not erased by the log-update-module, when a task with suspended renderer is running. I have done that in my actual version.

Copy link
Author

Choose a reason for hiding this comment

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

@itaisteinherz
Btw i added the testclass task-with-input, which proves, that the input from stdin is available for every task, no just for the tasks with suspended renderer.

await getTasks().run();

t.true(mock.verify());
});

test.after(() => {
mockery.disable();
mockery.deregisterAll();
});
2 changes: 1 addition & 1 deletion test/task-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {serial as test} from 'ava';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

test('changing the title during task execution', async t => {
const list = new Listr([
Expand Down
2 changes: 1 addition & 1 deletion test/tty.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import test from 'ava';
import {Observable} from 'rxjs';
import Listr from '..';
import SimpleRenderer from './fixtures/simple-renderer';
import TTYRenderer from './fixtures/tty-renderer';
import {testOutput} from './fixtures/utils';
import Listr from '..';

const ttyOutput = [
'foo [started]',
Expand Down
2 changes: 1 addition & 1 deletion test/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import test from 'ava';
import {Observable as RxObservable} from 'rxjs';
import ZenObservable from 'zen-observable';
import Listr from '..';
import {isListr, isObservable} from '../lib/utils';
import Listr from '..';

test('isListr', t => {
t.false(isListr(null));
Expand Down