Add util/taskReporter
It's a lightweight utility for reporting task progress in a CLI. It's inspired by execDependencyGraph. Test plan: `yarn test`; unit tests included.
This commit is contained in:
parent
0889a0a5d1
commit
daa7409abb
|
@ -0,0 +1,87 @@
|
|||
// @flow
|
||||
|
||||
const chalk = require("chalk");
|
||||
|
||||
type TaskId = string;
|
||||
|
||||
type MsSinceEpoch = number;
|
||||
type ConsoleLog = (string) => void;
|
||||
type GetTime = () => MsSinceEpoch;
|
||||
|
||||
/**
|
||||
* This class is a lightweight utility for reporting task progress to the
|
||||
* command line.
|
||||
*
|
||||
* - When a task is started, it's printed to the CLI with a " GO " label.
|
||||
* - When it's finished, it's printed with a "DONE" label, along with the time
|
||||
* elapsed.
|
||||
* - Tasks are tracked and represented by string id.
|
||||
* - The same task id may be re-used after the first task with that id is
|
||||
* finished.
|
||||
*/
|
||||
export class TaskReporter {
|
||||
// Maps the task to the time
|
||||
activeTasks: Map<TaskId, MsSinceEpoch>;
|
||||
_consoleLog: ConsoleLog;
|
||||
_getTime: GetTime;
|
||||
|
||||
constructor(consoleLog?: ConsoleLog, getTime?: GetTime) {
|
||||
this._consoleLog = consoleLog || console.log;
|
||||
this._getTime =
|
||||
getTime ||
|
||||
function() {
|
||||
return +new Date();
|
||||
};
|
||||
this.activeTasks = new Map();
|
||||
}
|
||||
|
||||
start(taskId: TaskId) {
|
||||
if (this.activeTasks.has(taskId)) {
|
||||
throw new Error(`task ${taskId} already registered`);
|
||||
}
|
||||
this.activeTasks.set(taskId, this._getTime());
|
||||
this._consoleLog(startMessage(taskId));
|
||||
return this;
|
||||
}
|
||||
|
||||
finish(taskId: TaskId) {
|
||||
const startTime = this.activeTasks.get(taskId);
|
||||
if (startTime == null) {
|
||||
throw new Error(`task ${taskId} not registered`);
|
||||
}
|
||||
const elapsedTime = this._getTime() - startTime;
|
||||
this._consoleLog(finishMessage(taskId, elapsedTime));
|
||||
this.activeTasks.delete(taskId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatTimeElapsed(elapsed: MsSinceEpoch): string {
|
||||
if (elapsed < 0) {
|
||||
throw new Error("nonegative time expected");
|
||||
}
|
||||
if (elapsed < 1000) {
|
||||
return `${elapsed}ms`;
|
||||
}
|
||||
const seconds = Math.round(elapsed / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes == 0) return `${seconds}s`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours == 0) return `${minutes}m ${seconds % 60}s`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days == 0) return `${hours}h ${minutes % 60}m`;
|
||||
return `${days}d ${hours % 24}h`;
|
||||
}
|
||||
|
||||
export function startMessage(taskId: string): string {
|
||||
const label = chalk.bgBlue.bold.white(" GO ");
|
||||
const message = `${label} ${taskId}`;
|
||||
return message;
|
||||
}
|
||||
|
||||
export function finishMessage(taskId: string, elapsedTimeMs: number): string {
|
||||
const elapsed = formatTimeElapsed(elapsedTimeMs);
|
||||
const label = chalk.bgGreen.bold.white(" DONE ");
|
||||
const message = `${label} ${taskId}: ${elapsed}`;
|
||||
return message;
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
TaskReporter,
|
||||
formatTimeElapsed,
|
||||
startMessage,
|
||||
finishMessage,
|
||||
} from "./taskReporter";
|
||||
|
||||
describe("util/taskReporter", () => {
|
||||
describe("formatTimeElapsed", () => {
|
||||
function tc(expected, ms) {
|
||||
it(`works for ${expected}`, () => {
|
||||
expect(formatTimeElapsed(ms)).toEqual(expected);
|
||||
});
|
||||
}
|
||||
tc("0ms", 0);
|
||||
tc("50ms", 50);
|
||||
tc("999ms", 999);
|
||||
const secs = 1000;
|
||||
tc("1s", 1 * secs + 400);
|
||||
tc("2s", 1 * secs + 600);
|
||||
tc("59s", 59 * secs);
|
||||
const mins = secs * 60;
|
||||
tc("1m 3s", mins + 3 * secs);
|
||||
tc("59m 59s", 59 * mins + 59 * secs);
|
||||
const hours = mins * 60;
|
||||
tc("1h 0m", hours);
|
||||
tc("1h 3m", hours + mins * 3);
|
||||
tc("23h 59m", 23 * hours + 59 * mins);
|
||||
const days = 24 * hours;
|
||||
tc("1d 0h", days);
|
||||
tc("555d 23h", 555 * days + 23 * hours);
|
||||
});
|
||||
|
||||
describe("TaskReporter", () => {
|
||||
class TestCase {
|
||||
_time: number;
|
||||
messages: string[];
|
||||
taskReporter: TaskReporter;
|
||||
|
||||
constructor() {
|
||||
this._time = 0;
|
||||
this.messages = [];
|
||||
const logMock = (x) => {
|
||||
this.messages.push(x);
|
||||
};
|
||||
const timeMock = () => this._time;
|
||||
this.taskReporter = new TaskReporter(logMock, timeMock);
|
||||
}
|
||||
start(task: string) {
|
||||
this.taskReporter.start(task);
|
||||
return this;
|
||||
}
|
||||
finish(task: string) {
|
||||
this.taskReporter.finish(task);
|
||||
return this;
|
||||
}
|
||||
time(t: number) {
|
||||
this._time = t;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
it("errors when finishing an unregistered task", () => {
|
||||
const fail = () => new TestCase().finish("foo");
|
||||
expect(fail).toThrowError("task foo not registered");
|
||||
});
|
||||
it("errors when starting a task twice", () => {
|
||||
const fail = () => new TestCase().start("foo").start("foo");
|
||||
expect(fail).toThrowError("task foo already registered");
|
||||
});
|
||||
it("errors when finishing a task twice", () => {
|
||||
const fail = () =>
|
||||
new TestCase()
|
||||
.start("foo")
|
||||
.finish("foo")
|
||||
.finish("foo");
|
||||
expect(fail).toThrowError("task foo not registered");
|
||||
});
|
||||
|
||||
it("works for a task that immediately finishes", () => {
|
||||
const {messages} = new TestCase().start("foo").finish("foo");
|
||||
expect(messages).toEqual([startMessage("foo"), finishMessage("foo", 0)]);
|
||||
});
|
||||
|
||||
it("works when two tasks are started, then one finishes", () => {
|
||||
const {messages} = new TestCase()
|
||||
.start("foo")
|
||||
.start("bar")
|
||||
.time(200)
|
||||
.finish("foo");
|
||||
expect(messages).toEqual([
|
||||
startMessage("foo"),
|
||||
startMessage("bar"),
|
||||
finishMessage("foo", 200),
|
||||
]);
|
||||
});
|
||||
it("works when a task is started, finished, and re-started", () => {
|
||||
const {messages} = new TestCase()
|
||||
.start("foo")
|
||||
.finish("foo")
|
||||
.start("foo")
|
||||
.time(200)
|
||||
.finish("foo");
|
||||
expect(messages).toEqual([
|
||||
startMessage("foo"),
|
||||
finishMessage("foo", 0),
|
||||
startMessage("foo"),
|
||||
finishMessage("foo", 200),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue