17850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/*
27850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project
37850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
47850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License");
57850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * you may not use this file except in compliance with the License.
67850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * You may obtain a copy of the License at
77850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
87850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *      http://www.apache.org/licenses/LICENSE-2.0
97850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Unless required by applicable law or agreed to in writing, software
117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS,
127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * See the License for the specific language governing permissions and
147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * limitations under the License.
157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */
167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compackage vogar;
187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
19d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.comimport com.google.common.collect.Lists;
208918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.comimport java.util.Collection;
215b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.comimport java.util.Collections;
22d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.comimport java.util.Date;
235884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.comimport java.util.HashMap;
247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.List;
255884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.comimport java.util.Map;
265b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.comimport vogar.util.MarkResetConsole;
277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/**
295884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com * Controls, formats and emits output to the command line. This class emits
305884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com * output in two modes:
315884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com * <ul>
325884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com *   <li><strong>Streaming</strong> output prints as it is received, but cannot
335884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com *       support multiple concurrent output streams.
345884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com *   <li><strong>Multiplexing</strong> buffers output until it is complete and
355884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com *       then prints it completely.
365884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com * </ul>
377850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */
38b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.compublic abstract class Console implements Log {
39dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    static final long DAY_MILLIS = 1000 * 60 * 60 * 24;
40dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    static final long HOUR_MILLIS = 1000 * 60 * 60;
41dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    static final long WARNING_HOURS = 12;
42dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    static final long FAILURE_HOURS = 48;
43dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com
44d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com    private boolean useColor;
450942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com    private boolean ansi;
4688b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com    private boolean verbose;
475884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    protected String indent;
485884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    protected CurrentLine currentLine = CurrentLine.NEW;
495b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com    protected final MarkResetConsole out = new MarkResetConsole(System.out);
505b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com    protected MarkResetConsole.Mark currentVerboseMark;
515b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com    protected MarkResetConsole.Mark currentStreamMark;
527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
53400bee347dd7464ecc17dc24c82f59c59645ff44jessewilson@google.com    private Console() {}
54027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com
55027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com    public void setIndent(String indent) {
567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        this.indent = indent;
57027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com    }
58027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com
595ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray    public void setUseColor(
605ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray      boolean useColor, int passColor, int skipColor, int failColor, int warnColor) {
61d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        this.useColor = useColor;
6250bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com        Color.PASS.setCode(passColor);
635ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray        Color.SKIP.setCode(skipColor);
6450bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com        Color.FAIL.setCode(failColor);
655ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray        Color.WARN.setCode(warnColor);
663617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com        Color.COMMENT.setCode(34);
677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
690942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com    public void setAnsi(boolean ansi) {
700942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com        this.ansi = ansi;
710942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com    }
720942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com
7388b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com    public void setVerbose(boolean verbose) {
7488b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com        this.verbose = verbose;
7588b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com    }
7688b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com
77f83be5e4273263df2bb9ef609946b911695b3996jessewilson@google.com    public boolean isVerbose() {
78f83be5e4273263df2bb9ef609946b911695b3996jessewilson@google.com        return verbose;
79f83be5e4273263df2bb9ef609946b911695b3996jessewilson@google.com    }
80f83be5e4273263df2bb9ef609946b911695b3996jessewilson@google.com
81ba1c2cee7506480a0a239e2d99a944d08fc47be5jessewilson@google.com    public synchronized void verbose(String s) {
825b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        /*
830942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com         * terminal does't support overwriting output, so don't print
840942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com         * verbose message unless requested.
850942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com         */
860942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com        if (!verbose && !ansi) {
870942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com            return;
880942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com        }
890942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com        /*
9072bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com         * When writing verbose output in the middle of streamed output, keep
9172bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com         * the streamed mark location. That way we can remove the verbose output
9272bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com         * later without losing our position mid-line in the streamed output.
935b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com         */
9472bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com        MarkResetConsole.Mark savedStreamMark = currentLine == CurrentLine.STREAMED_OUTPUT
9572bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com                ? out.mark()
9672bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com                : currentStreamMark;
9772bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com        newLine();
9872bce7bb658ad68388f7b3adfadaa71979cb88c9jessewilson@google.com        currentStreamMark = savedStreamMark;
995b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com
1005b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        currentVerboseMark = out.mark();
1015b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        out.print(s);
10288b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com        currentLine = CurrentLine.VERBOSE;
103027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com    }
104027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com
1053dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com    public synchronized void warn(String message) {
1065b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        warn(message, Collections.<String>emptyList());
1073dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com    }
1083dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com
1093dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com    /**
1103dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com     * Warns, and also puts a list of strings afterwards.
1113dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com     */
1123dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com    public synchronized void warn(String message, List<String> list) {
1133dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com        newLine();
1145b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        out.println(colorString("Warning: " + message, Color.WARN));
1153dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com        for (String item : list) {
1165b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println(colorString(indent + item, Color.WARN));
1173dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com        }
1183dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com    }
1193dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com
120ba1c2cee7506480a0a239e2d99a944d08fc47be5jessewilson@google.com    public synchronized void info(String s) {
121400bee347dd7464ecc17dc24c82f59c59645ff44jessewilson@google.com        newLine();
1225b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        out.println(s);
123027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com    }
124027ca289d7c7b21501a9d14e69f10735033f57a0elliott.n.hughes@gmail.com
125ba1c2cee7506480a0a239e2d99a944d08fc47be5jessewilson@google.com    public synchronized void info(String message, Throwable throwable) {
1267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        newLine();
1275b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        out.println(message);
128400bee347dd7464ecc17dc24c82f59c59645ff44jessewilson@google.com        throwable.printStackTrace(System.out);
1297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
1307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
1325884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     * Begins streaming output for the named action.
1337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
1345884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    public void action(String name) {}
1357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1365884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    /**
1375884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     * Begins streaming output for the named outcome.
1385884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     */
1395884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    public void outcome(String name) {}
1407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
1427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Appends the action output immediately to the stream when streaming is on,
1437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * or to a buffer when streaming is off. Buffered output will be held and
1447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * printed only if the outcome is unsuccessful.
1457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
1465884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    public abstract void streamOutput(String outcomeName, String output);
1477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
1493617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com     * Hook to flush anything streamed via {@link #streamOutput}.
1503617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com     */
1513617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com    protected void flushBufferedOutput(String outcomeName) {}
1523617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com
1533617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com    /**
1547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Writes the action's outcome.
1557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
1563617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com    public synchronized void printResult(
1573617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com            String outcomeName, Result result, ResultValue resultValue, Expectation expectation) {
1583617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com        // when the result is interesting, include the description and bug number
1593617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com        if (result != Result.SUCCESS || resultValue != ResultValue.OK) {
1603617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com            if (!expectation.getDescription().isEmpty()) {
1613617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com                streamOutput(outcomeName, "\n" + colorString(expectation.getDescription(), Color.COMMENT));
1623617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com            }
1633617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com            if (expectation.getBug() != -1) {
1643617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com                streamOutput(outcomeName, "\n" + colorString("http://b/" + expectation.getBug(), Color.COMMENT));
1653617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com            }
1663617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com        }
1673617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com
1683617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com        flushBufferedOutput(outcomeName);
1693617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com
1705884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        if (currentLine == CurrentLine.NAME) {
1715b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print(" ");
1725884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        } else {
1732b3ec15cc85c52d60bebc7587cdb5604cf79cec6jessewilson@google.com            newLine(); // TODO: backup the cursor up to the name if there's no streaming output
1742b3ec15cc85c52d60bebc7587cdb5604cf79cec6jessewilson@google.com            out.print(indent + outcomeName + " ");
1755884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        }
1767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1775884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        if (resultValue == ResultValue.OK) {
1785b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println(colorString("OK (" + result + ")", Color.PASS));
1791d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com        } else if (resultValue == ResultValue.FAIL) {
1805b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println(colorString("FAIL (" + result + ")", Color.FAIL));
1811d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com        } else if (resultValue == ResultValue.IGNORE) {
1825b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println(colorString("SKIP (" + result + ")", Color.WARN));
1837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
1847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        currentLine = CurrentLine.NEW;
1867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
1877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1888918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com    public synchronized void summarizeOutcomes(Collection<AnnotatedOutcome> annotatedOutcomes) {
1898918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        List<AnnotatedOutcome> annotatedOutcomesSorted =
1908918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                AnnotatedOutcome.ORDER_BY_NAME.sortedCopy(annotatedOutcomes);
191d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
192d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        List<String> failures = Lists.newArrayList();
193d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        List<String> skips = Lists.newArrayList();
194d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        List<String> successes = Lists.newArrayList();
1955ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray        List<String> warnings = Lists.newArrayList();
196d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
197d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        // figure out whether each outcome is noteworthy, and add a message to the appropriate list
1988918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        for (AnnotatedOutcome annotatedOutcome : annotatedOutcomesSorted) {
199d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            if (!annotatedOutcome.isNoteworthy()) {
200d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                continue;
201d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
202d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
203d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            Color color;
204d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            List<String> list;
2058918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            ResultValue resultValue = annotatedOutcome.getResultValue();
2068918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            if (resultValue == ResultValue.OK) {
20750bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                color = Color.PASS;
208d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                list = successes;
2098918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            } else if (resultValue == ResultValue.FAIL) {
21050bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                color = Color.FAIL;
211d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                list = failures;
2125ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray            } else if (resultValue == ResultValue.WARNING) {
21350bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                color = Color.WARN;
2145ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray                list = warnings;
2155ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray            } else {
2165ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray                color = Color.SKIP;
217d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                list = skips;
218d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
219d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
220dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            Long lastRun = annotatedOutcome.lastRun(null);
221d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            String timestamp;
222dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            if (lastRun == null) {
223dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com                timestamp = colorString("unknown", Color.WARN);
224d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            } else {
225dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com                timestamp = formatElapsedTime(new Date().getTime() - lastRun);
226d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
227d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
228d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            String brokeThisMessage = "";
2293a4c5dbace966faa26dcbc04e40470efad37ef01jsharpe@google.com            ResultValue mostRecentResultValue = annotatedOutcome.getMostRecentResultValue(null);
2303a4c5dbace966faa26dcbc04e40470efad37ef01jsharpe@google.com            if (mostRecentResultValue != null && resultValue != mostRecentResultValue) {
231d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                if (resultValue == ResultValue.OK) {
232dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com                    brokeThisMessage = colorString(" (you might have fixed this)", Color.WARN);
233d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                } else {
234dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com                    brokeThisMessage = colorString(" (you might have broken this)", Color.WARN);
235d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com                }
2363a4c5dbace966faa26dcbc04e40470efad37ef01jsharpe@google.com            } else if (mostRecentResultValue == null) {
23750bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                brokeThisMessage = colorString(" (no test history available)", Color.WARN);
238d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
239d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
240b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com            List<ResultValue> previousResultValues = annotatedOutcome.getPreviousResultValues();
2418918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            int numPreviousResultValues = previousResultValues.size();
2428918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            int numResultValuesToShow = Math.min(10, numPreviousResultValues);
243b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com            List<ResultValue> previousResultValuesToShow = previousResultValues.subList(
244b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com                    numPreviousResultValues - numResultValuesToShow, numPreviousResultValues);
245d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
2468918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            StringBuilder sb = new StringBuilder();
2478918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            sb.append(indent);
2488918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            sb.append(colorString(annotatedOutcome.getOutcome().getName(), color));
249d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            if (!previousResultValuesToShow.isEmpty()) {
250dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com                sb.append(String.format(" [last %d: %s] [last run: %s]",
2518918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                        previousResultValuesToShow.size(),
2528918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                        generateSparkLine(previousResultValuesToShow),
2538918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                        timestamp));
254d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
2558918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            sb.append(brokeThisMessage);
2568918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            list.add(sb.toString());
257d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        }
258d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
2593dafbce3b8d025f465ebf0a4c6d018c7dc31654ejsharpe@google.com        newLine();
260d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        if (!successes.isEmpty()) {
2615b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println("Success summary:");
262d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            for (String success : successes) {
2635b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com                out.println(success);
264d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
265d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        }
266d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        if (!failures.isEmpty()) {
2675b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println("Failure summary:");
268d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            for (String failure : failures) {
2695b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com                out.println(failure);
270d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
271d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        }
272d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        if (!skips.isEmpty()) {
2735b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.println("Skips summary:");
274d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            for (String skip : skips) {
2755b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com                out.println(skip);
276d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
2777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
2785ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray        if (!warnings.isEmpty()) {
2795ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray            out.println("Warnings summary:");
2805ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray            for (String warning : warnings) {
2815ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray                out.println(warning);
2825ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray            }
2835ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray        }
2847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
2857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
286dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    private String formatElapsedTime(long elapsedTime) {
287dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        if (elapsedTime < 0) {
288dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            throw new IllegalArgumentException("non-negative elapsed times only");
289dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        }
290dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com
291dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        String formatted;
292dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        if (elapsedTime >= DAY_MILLIS) {
293dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            long days = elapsedTime / DAY_MILLIS;
294dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            formatted = String.format("%d days ago", days);
295dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        } else if (elapsedTime >= HOUR_MILLIS) {
296dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            long hours = elapsedTime / HOUR_MILLIS;
297dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            formatted = String.format("%d hours ago", hours);
298dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        } else {
299dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            formatted = "less than an hour ago";
300dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        }
301dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com
302dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        Color color = elapsedTimeWarningColor(elapsedTime);
303dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        return colorString(formatted, color);
304dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    }
305dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com
306dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    private Color elapsedTimeWarningColor(long elapsedTime) {
307dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        if (elapsedTime < WARNING_HOURS * HOUR_MILLIS) {
308dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            return Color.PASS;
309dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        } else if (elapsedTime < FAILURE_HOURS * HOUR_MILLIS) {
310dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            return Color.WARN;
311dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        } else {
312dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com            return Color.FAIL;
313dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com        }
314dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com    }
315dfebab2a6f40fde48ee696eb0be85ef399891924jsharpe@google.com
316d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com    private String generateSparkLine(List<ResultValue> resultValues) {
317d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        StringBuilder sb = new StringBuilder();
318d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        for (ResultValue resultValue : resultValues) {
319d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            if (resultValue == ResultValue.OK) {
32050bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                sb.append(colorString("\u2713", Color.PASS));
321d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            } else if (resultValue == ResultValue.FAIL) {
32250bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                sb.append(colorString("X", Color.FAIL));
323d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            } else {
32450bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com                sb.append(colorString("-", Color.WARN));
325d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com            }
3261d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com        }
327d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        return sb.toString();
3281d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com    }
3291d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com
3307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
3317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Prints the action output with appropriate indentation.
3327850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
3334878be6187c09b4b65fed8c99f3aedb9b613d59cjessewilson@google.com    public synchronized void streamOutput(CharSequence streamedOutput) {
3343ad417cb75663abaa5d34573d520a57a42ee68d6jessewilson@google.com        if (streamedOutput.length() == 0) {
3353ad417cb75663abaa5d34573d520a57a42ee68d6jessewilson@google.com            return;
3363ad417cb75663abaa5d34573d520a57a42ee68d6jessewilson@google.com        }
3373ad417cb75663abaa5d34573d520a57a42ee68d6jessewilson@google.com
3385884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        String[] lines = messageToLines(streamedOutput.toString());
3397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3400942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com        if (currentLine == CurrentLine.VERBOSE && currentStreamMark != null && ansi) {
3415b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            currentStreamMark.reset();
3425b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            currentStreamMark = null;
3435b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        } else if (currentLine != CurrentLine.STREAMED_OUTPUT) {
3447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            newLine();
3455b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print(indent);
3465b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print(indent);
3477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3485b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        out.print(lines[0]);
3497850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        currentLine = CurrentLine.STREAMED_OUTPUT;
3507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        for (int i = 1; i < lines.length; i++) {
3527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            newLine();
3537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (lines[i].length() > 0) {
3555b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com                out.print(indent);
3565b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com                out.print(indent);
3575b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com                out.print(lines[i]);
3587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                currentLine = CurrentLine.STREAMED_OUTPUT;
3597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
3607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
3627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
3647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Inserts a linebreak if necessary.
3657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
3665884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    protected void newLine() {
3675b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        currentStreamMark = null;
3685b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com
3690942355a74d759fb2e50a002b6b0b93430f07d72bdc@google.com        if (currentLine == CurrentLine.VERBOSE && !verbose && ansi) {
3705b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            /*
3715b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com             * Verbose means we leave all verbose output on the screen.
3725b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com             * Otherwise we overwrite verbose output when new output arrives.
3735b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com             */
3745b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            currentVerboseMark.reset();
3755b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com        } else if (currentLine != CurrentLine.NEW) {
3765b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print("\n");
3777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        currentLine = CurrentLine.NEW;
3807850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
3817850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
3837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Status of a currently-in-progress line of output.
3847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
3857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    enum CurrentLine {
3867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        /**
3887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * The line is blank.
3897850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         */
3907850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        NEW,
3917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        /**
3937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * The line contains streamed application output. Additional streamed
3947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * output may be appended without additional line separators or
3957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * indentation.
3967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         */
3977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        STREAMED_OUTPUT,
3987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        /**
4007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * The line contains the name of an action or outcome. The outcome's
4017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * result (such as "OK") can be appended without additional line
4027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * separators or indentation.
4037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         */
4047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        NAME,
40588b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com
40688b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com        /**
40788b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com         * The line contains verbose output, and may be overwritten.
40888b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com         */
40988b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com        VERBOSE,
4107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
4137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Returns an array containing the lines of the given text.
4147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
4157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private String[] messageToLines(String message) {
4167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        // pass Integer.MAX_VALUE so split doesn't trim trailing empty strings.
4177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        return message.split("\r\n|\r|\n", Integer.MAX_VALUE);
4187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4197850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
420d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com    private enum Color {
4215ad56966d0c66ab4b733fe97c4b862f5f85711e8Nicolas Geoffray        PASS, FAIL, SKIP, WARN, COMMENT;
4228918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com
42350bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com        int code = 0;
424d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com
425d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        public int getCode() {
4268918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            return code;
427d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        }
42850bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com
42950bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com        public void setCode(int code) {
43050bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com            this.code = code;
43150bd7c43bee5854a7a824265ec224c58c67c698bjsharpe@google.com        }
4327850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
43388b369c017a9eeee0f074ff4be8506b8952c76e0elliott.n.hughes@gmail.com
434d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com    protected String colorString(String message, Color color) {
435d806c4c900e08bf04e07b5c564f2f61d8c490731jsharpe@google.com        return useColor ? ("\u001b[" + color.getCode() + ";1m" + message + "\u001b[0m") : message;
4361d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com    }
4371d4fef707a383dc57285e6608ca290be48811a85jsharpe@google.com
4385884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    /**
4395884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     * This console prints output as it's emitted. It supports at most one
4405884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     * action at a time.
4415884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     */
442b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com    static class StreamingConsole extends Console {
4435884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        private String currentName;
4445884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4455884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        @Override public synchronized void action(String name) {
4465884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            newLine();
4475b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print("Action " + name);
4485884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            currentName = name;
4495884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            currentLine = CurrentLine.NAME;
4505884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        }
4515884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4525884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        /**
4535884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com         * Prints the beginning of the named outcome.
4545884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com         */
4555884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        @Override public synchronized void outcome(String name) {
4565884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            // if the outcome and action names are the same, omit the outcome name
4575884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            if (name.equals(currentName)) {
4585884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com                return;
4595884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            }
4605884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4615884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            currentName = name;
462d59b17f5dc87d856a01714853d5296d387ff8dedjessewilson@google.com            newLine();
4635b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print(indent + name);
4645884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            currentLine = CurrentLine.NAME;
4655884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        }
4665884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4675884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        @Override public synchronized void streamOutput(String outcomeName, String output) {
4684878be6187c09b4b65fed8c99f3aedb9b613d59cjessewilson@google.com            streamOutput(output);
4695884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        }
4705884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    }
4715884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4725884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    /**
4735884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     * This console buffers output, only printing when a result is found. It
4745884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     * supports multiple concurrent actions.
4755884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com     */
476b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com    static class MultiplexingConsole extends Console {
4775884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        private final Map<String, StringBuilder> bufferedOutputByOutcome = new HashMap<String, StringBuilder>();
4785884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4795884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        @Override public synchronized void streamOutput(String outcomeName, String output) {
4805884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            StringBuilder buffer = bufferedOutputByOutcome.get(outcomeName);
4815884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            if (buffer == null) {
4825884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com                buffer = new StringBuilder();
4835884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com                bufferedOutputByOutcome.put(outcomeName, buffer);
4845884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            }
4855884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4865884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            buffer.append(output);
4875884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        }
4885884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4893617522d6678cd021a98b635b46e536ae14b45f3jessewilson@google.com        @Override protected synchronized void flushBufferedOutput(String outcomeName) {
4905884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            newLine();
4915b2080e28c579ae6e563143c6a1788846d8da2f1jessewilson@google.com            out.print(indent + outcomeName);
4925884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            currentLine = CurrentLine.NAME;
4935884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com
4945884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            StringBuilder buffer = bufferedOutputByOutcome.remove(outcomeName);
4955884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            if (buffer != null) {
4964878be6187c09b4b65fed8c99f3aedb9b613d59cjessewilson@google.com                streamOutput(buffer);
4975884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com            }
4985884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com        }
4995884b0e6bc3eec46f4a374254626a14d128179fajessewilson@google.com    }
5007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com}
501