Skip to content

Commit

Permalink
feat: show progress while running tests
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Nov 23, 2017
1 parent 6cbddcd commit 3169db2
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 41 deletions.
59 changes: 51 additions & 8 deletions src/LineWriter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable complexity, no-use-extend-native/no-use-extend-native */
const path = require('path');
const chalk = require('chalk');
const bar = require('utf8-bar');
const formatComment = require('./format/formatComment');
const formatCodeFrame = require('./format/formatCodeFrame');
const formatStatsBar = require('./format/formatStatsBar');
const formatFailureMessageTraceLine = require('./format/formatFailureMessageTraceLine');
Expand Down Expand Up @@ -28,8 +30,6 @@ const PASS = chalk.supportsColor ?
chalk`{reset.inverse.bold.green ${PASS_TEXT} }` :
` ${PASS_TEXT} `;

const formatComment = (line) => chalk`{hidden #} ${line}`;

class LineWriter {
constructor (logger, root) {
this.counter = 0;
Expand All @@ -44,12 +44,16 @@ class LineWriter {
return this.counter;
}

info (line) {
this.logger.info(line);
}

blank () {
this.logger.info('');
this.info('');
}

comment (line) {
this.logger.info(formatComment(line));
this.info(formatComment(line));
}

commentBlock (str) {
Expand Down Expand Up @@ -95,9 +99,9 @@ class LineWriter {
const list = [];

if (total) {
const bar = formatStatsBar(passed / total, passed + skipped < total);
const formattedBar = formatStatsBar(passed / total, passed + skipped < total);

list.push(bar);
list.push(formattedBar);

if (failed) {
list.push(chalk`{red.bold ${failed} failed}`);
Expand Down Expand Up @@ -125,9 +129,9 @@ class LineWriter {
const list = [];

const percent = passed / total;
const bar = formatStatsBar(percent, percent < 1 && !updated && !added);
const formattedStatsBar = formatStatsBar(percent, percent < 1 && !updated && !added);

list.push(bar);
list.push(formattedStatsBar);

if (failed) {
list.push(chalk`{red.bold ${failed} failed}`);
Expand Down Expand Up @@ -304,6 +308,45 @@ class LineWriter {
this.logger.log(chalk`{reset.inverse 1..${count}}`);
this.planWritten = true;
}

aggregatedResults (aggregatedResults, estimatedTime) {
const snapshotResults = aggregatedResults.snapshot;
const snapshotsAdded = snapshotResults.added;
const snapshotsFailed = snapshotResults.unmatched;
const snapshotsPassed = snapshotResults.matched;
const snapshotsTotal = snapshotResults.total;
const snapshotsUpdated = snapshotResults.updated;
const suitesFailed = aggregatedResults.numFailedTestSuites;
const suitesPassed = aggregatedResults.numPassedTestSuites;
const suitesPending = aggregatedResults.numPendingTestSuites;
const suitesTotal = aggregatedResults.numTotalTestSuites;
const testsFailed = aggregatedResults.numFailedTests;
const testsPassed = aggregatedResults.numPassedTests;
const testsPending = aggregatedResults.numPendingTests;
const testsTotal = aggregatedResults.numTotalTests;
const startTime = aggregatedResults.startTime;

this.stats('Test Suites', suitesFailed, suitesPending, suitesPassed, suitesTotal);
this.stats('Tests', testsFailed, testsPending, testsPassed, testsTotal);
if (snapshotsTotal) {
this.snapshots(snapshotsFailed, snapshotsUpdated, snapshotsAdded, snapshotsPassed, snapshotsTotal);
}

const timeValue = `${((Date.now() - startTime) / 1e3).toFixed(3)}s` + (estimatedTime ? `, estimated ${estimatedTime}s` : '');

this.keyValue('Time', timeValue);
}

timeProgressBar (percentage) {
if (percentage > 1) {
return;
}

const line = bar(this.logger.stream.columns, percentage);
const lineFormatted = chalk`{grey.dim ${line}}`;

this.logger.write(lineFormatted);
}
}

module.exports = LineWriter;
19 changes: 15 additions & 4 deletions src/Logger.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const formatLog = require('./format/formatLog');

/* eslint-disable sort-keys */
const LEVELS = {
ERROR: 1,
Expand All @@ -6,13 +8,14 @@ const LEVELS = {
};
/* eslint-enable sort-keys */

// eslint-disable-next-line no-console
const DEFAULT_LOG = console.log;
const sLevel = Symbol('level');

class Logger {
constructor ({log = DEFAULT_LOG, logLevel = 'INFO'} = {}) {
this.log = log;
constructor ({
logLevel = 'INFO',
stream = process.stdout
} = {}) {
this.stream = stream;
this.setLevel(logLevel);
}

Expand All @@ -32,6 +35,14 @@ class Logger {
return Object.keys(LEVELS).filter((key) => LEVELS[key] === this[sLevel])[0];
}

write (data) {
this.stream.write(data);
}

log (...args) {
this.write(formatLog(...args) + '\n');
}

info (...args) {
if (this[sLevel] >= LEVELS.INFO) {
this.log(...args);
Expand Down
39 changes: 39 additions & 0 deletions src/LoggerBufferable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const Logger = require('./Logger');

/**
* This logger can buffer multiple calls to .log() and then
* flush them all at once to STDOUT. Useful for printing test
* suite output all at once for some terminals.
*/
class LoggerBufferable extends Logger {
constructor (...args) {
super(...args);

this.isBuffering = false;
this.queue = '';
}

buffer () {
this.isBuffering = true;
}

flush () {
this.isBuffering = false;
super.write(this.queue);
this.queue = '';
}

push (data) {
this.queue += data;
}

write (data) {
if (this.isBuffering) {
this.push(data);
} else {
super.write(data);
}
}
}

module.exports = LoggerBufferable;
70 changes: 70 additions & 0 deletions src/LoggerTemporal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const LoggerBufferable = require('./LoggerBufferable');

const getLineCount = (str) => {
let count = 1;
let pos = -1;

while ((pos = str.indexOf('\n', pos + 1)) > -1) {
count++;
}

return count;
};

/**
* This logger allows to print temporary output at the very bottom
* that will be erased and overwritten on the next call to .log().
* Used when showing temporary progress when streaming test suite results.
*/
class LoggerTemporal extends LoggerBufferable {
constructor (...args) {
super(...args);

this.linesToErase = 0;
this.isTemporary = false;
this.queueTemporary = '';
}

temporary () {
if (!this.isBuffering) {
throw new Error('You need to call .buffer() before .temporary().');
}
this.isTemporary = true;
}

push (data) {
if (this.isTemporary) {
this.queueTemporary += data;
} else {
super.push(data);
}
}

flush () {
super.flush();

this.write(this.queueTemporary);
this.linesToErase = getLineCount(this.queueTemporary) - 1;

this.queueTemporary = '';
this.isTemporary = false;
}

write (data) {
const {stream} = this;

if (this.linesToErase && stream.moveCursor) {
for (let index = 0; index < this.linesToErase; index++) {
stream.cursorTo(0);
stream.clearLine();
stream.moveCursor(0, -1);
}

this.linesToErase = 0;
}

super.write(data);
}
}

module.exports = LoggerTemporal;
62 changes: 33 additions & 29 deletions src/TapReporter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable id-match, class-methods-use-this, no-console */
const path = require('path');
const chalk = require('chalk');
const Logger = require('./Logger');
const LoggerTemporal = require('./LoggerTemporal');
const LineWriter = require('./LineWriter');

const STATUS_PASSED = 'passed';
Expand All @@ -17,9 +17,10 @@ class TapReporter {
this.globalConfig = globalConfig;
this.options = options;
this[sShouldFail] = false;
this.writer = new LineWriter(new Logger({logLevel}), globalConfig.rootDir);
this.writer = new LineWriter(new LoggerTemporal({logLevel}), globalConfig.rootDir);
this.onAssertionResult = this.onAssertionResult.bind(this);

this.lastAggregatedResults = {};
this.onRunStartResults = {};
this.onRunStartOptions = {};
}
Expand Down Expand Up @@ -59,7 +60,18 @@ class TapReporter {
}
}

onTestResult (test, testResult) {
onRunStart (results, options) {
this.onRunStartOptions = options;

this.writer.start(results.numTotalTestSuites);
this.writer.blank();
}

onTestResult (test, testResult, aggregatedResults) {
this.lastAggregatedResults = aggregatedResults;

this.writer.logger.buffer();

const {testExecError, testResults, testFilePath, numFailingTests} = testResult;
const {dir, base} = path.parse(testFilePath);
const suiteFailed = Boolean(testExecError);
Expand All @@ -76,47 +88,39 @@ class TapReporter {
} else {
testResults.forEach(this.onAssertionResult);
}
}

onRunStart (results, options) {
this.onRunStartOptions = options;
this.writer.logger.temporary();

this.writer.start(results.numTotalTestSuites);
this.writer.blank();
this.writer.aggregatedResults(aggregatedResults);

const {estimatedTime} = this.onRunStartOptions;

if (estimatedTime) {
const startTime = aggregatedResults.startTime;
const percentage = (Date.now() - startTime) / 1e3 / estimatedTime / 3;

if (percentage <= 1) {
this.writer.blank();
this.writer.timeProgressBar(percentage);
}
}

this.writer.logger.flush();
}

onRunComplete (contexts, aggregatedResults) {
const {estimatedTime} = this.onRunStartOptions;

const snapshotResults = aggregatedResults.snapshot;
const snapshotsAdded = snapshotResults.added;
const snapshotsFailed = snapshotResults.unmatched;
const snapshotsPassed = snapshotResults.matched;
const snapshotsTotal = snapshotResults.total;
const snapshotsUpdated = snapshotResults.updated;
const suitesFailed = aggregatedResults.numFailedTestSuites;
const suitesPassed = aggregatedResults.numPassedTestSuites;
const suitesPending = aggregatedResults.numPendingTestSuites;
const suitesTotal = aggregatedResults.numTotalTestSuites;
const testsFailed = aggregatedResults.numFailedTests;
const testsPassed = aggregatedResults.numPassedTests;
const testsPending = aggregatedResults.numPendingTests;
const testsTotal = aggregatedResults.numTotalTests;
const startTime = aggregatedResults.startTime;

this[sShouldFail] = testsFailed > 0 || suitesFailed > 0;

this.writer.blank();
this.writer.plan();
this.writer.blank();
this.writer.stats('Test Suites', suitesFailed, suitesPending, suitesPassed, suitesTotal);
this.writer.stats('Tests', testsFailed, testsPending, testsPassed, testsTotal);
if (snapshotsTotal) {
this.writer.snapshots(snapshotsFailed, snapshotsUpdated, snapshotsAdded, snapshotsPassed, snapshotsTotal);
}

const timeValue = `${((Date.now() - startTime) / 1e3).toFixed(3)}s` + (estimatedTime ? `, estimated ${estimatedTime}s` : '');

this.writer.keyValue('Time', timeValue);
this.writer.aggregatedResults(aggregatedResults, estimatedTime);
this.writer.blank();
this.writer.commentLight('Ran all test suites.');
this.writer.blank();
Expand Down
3 changes: 3 additions & 0 deletions src/format/formatComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const chalk = require('chalk');

module.exports = (line) => chalk`{hidden #} ${line}`;
3 changes: 3 additions & 0 deletions src/format/formatLog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const {format} = require('util');

module.exports = (...args) => format(...args);

0 comments on commit 3169db2

Please sign in to comment.