// Copyright © 2011-2013, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package fi.jumi.launcher.ui; import fi.jumi.actors.eventizers.Event; import fi.jumi.actors.queue.MessageReceiver; import fi.jumi.api.drivers.TestId; import fi.jumi.core.api.*; import fi.jumi.core.results.*; import javax.annotation.CheckForNull; import javax.annotation.concurrent.NotThreadSafe; import java.io.*; import static fi.jumi.core.results.SuiteProgressMeter.Status.*; @NotThreadSafe public class TextUI { // TODO: if multiple readers are needed, create a Streamer class per the original designs private final MessageReceiver<Event<SuiteListener>> eventStream; private final Printer printer; private final SuiteEventDemuxer demuxer = new SuiteEventDemuxer(); private final SuitePrinter suitePrinter = new SuitePrinter(); private final SuiteProgressMeter progressMeter = new SuiteProgressMeter(); private final TextProgressBar progressBar = new TextProgressBar("[", "-----=====-----=====-----=====-----=====-----=====", "]"); private boolean passingTestsVisible = true; private boolean progressBarVisible = true; private boolean hasInternalErrors = false; private boolean hasFailures = false; public TextUI(MessageReceiver<Event<SuiteListener>> eventStream, Printer printer) { this.eventStream = eventStream; this.printer = printer; } public void setPassingTestsVisible(boolean passingTestsVisible) { this.passingTestsVisible = passingTestsVisible; } public void setProgressBarVisible(boolean progressBarVisible) { this.progressBarVisible = progressBarVisible; } public boolean hasFailures() { return hasFailures || hasInternalErrors; } public void update() { while (!demuxer.isSuiteFinished()) { Event<SuiteListener> message = eventStream.poll(); if (message == null) { break; } updateWithMessage(message); } } public void updateUntilFinished() throws InterruptedException { while (!demuxer.isSuiteFinished()) { Event<SuiteListener> message = eventStream.take(); updateWithMessage(message); } } private void updateWithMessage(Event<SuiteListener> message) { if (progressBarVisible) { message.fireOn(progressMeter); progressBar .setProgress(progressMeter.getProgress()) .setIndeterminate(progressMeter.getStatus() == INDETERMINATE) .setComplete(progressMeter.getStatus() == COMPLETE); printer.printMetaIncrement(progressBar.toStringIncremental()); } demuxer.send(message); message.fireOn(suitePrinter); } // printing visitors @NotThreadSafe private class SuitePrinter extends NullSuiteListener { @Override public void onInternalError(String message, StackTrace cause) { printer.printMetaLine(" > Internal Error"); printer.printMetaLine(" > " + message); printer.printErr(getStackTraceAsString(cause)); printer.printErr("\n"); hasInternalErrors = true; } @Override public void onFailure(RunId runId, StackTrace cause) { hasFailures = true; } @Override public void onRunFinished(RunId runId) { if (passingTestsVisible || hasFailures(runId)) { demuxer.visitRun(runId, new RunPrinter()); progressBar.resetIncrementalPrinting(); } } private boolean hasFailures(RunId runId) { SuiteResultsSummary tmp = new SuiteResultsSummary(); demuxer.visitRun(runId, tmp); return tmp.getFailingTests() > 0; } @Override public void onSuiteFinished() { SuiteResultsSummary summary = new SuiteResultsSummary(); demuxer.visitAllRuns(summary); printSuiteFooter(summary); } // visual style private void printSuiteFooter(SuiteResultsSummary summary) { int pass = summary.getPassingTests(); int fail = summary.getFailingTests(); printer.printMetaLine(String.format("Pass: %d, Fail: %d", pass, fail)); if (hasFailures) { printer.printMetaLine("There were test failures"); } if (hasInternalErrors) { printer.printMetaLine("There were internal errors"); } } } @NotThreadSafe private class RunPrinter implements RunVisitor { private int testNestingLevel = 0; @Override public void onRunStarted(RunId runId, TestFile testFile) { printRunHeader(testFile, runId); } @Override public void onTestStarted(RunId runId, TestFile testFile, TestId testId) { testNestingLevel++; printTestName("+", testFile, testId); } @Override public void onPrintedOut(RunId runId, TestFile testFile, @CheckForNull TestId testId, String text) { printer.printOut(text); } @Override public void onPrintedErr(RunId runId, TestFile testFile, @CheckForNull TestId testId, String text) { printer.printErr(text); } @Override public void onFailure(RunId runId, TestFile testFile, TestId testId, StackTrace cause) { printer.printErr(getStackTraceAsString(cause)); } @Override public void onTestFinished(RunId runId, TestFile testFile, TestId testId) { printTestName("-", testFile, testId); testNestingLevel--; } @Override public void onRunFinished(RunId runId, TestFile testFile) { printRunFooter(); } // visual style private void printRunHeader(TestFile testFile, RunId runId) { printer.printMetaLine(" > Run #" + runId.toInt() + " in " + testFile); } private void printTestName(String bullet, TestFile testFile, TestId testId) { printer.printMetaLine(" > " + testNameIndent() + bullet + " " + demuxer.getTestName(testFile, testId)); } private void printRunFooter() { printer.printMetaLine(""); } private String testNameIndent() { StringBuilder indent = new StringBuilder(); for (int i = 1; i < testNestingLevel; i++) { indent.append(" "); } return indent.toString(); } } private static String getStackTraceAsString(Throwable cause) { StringWriter buffer = new StringWriter(); cause.printStackTrace(new PrintWriter(buffer)); return buffer.toString(); } }