

package com.facebook.buck.event.listener;

import static com.facebook.buck.event.TestEventConfigurator.configureTestEventAtTime;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import com.facebook.buck.artifact_cache.ArtifactCacheEvent;
import com.facebook.buck.artifact_cache.CacheResult;
import com.facebook.buck.artifact_cache.DirArtifactCacheEvent;
import com.facebook.buck.artifact_cache.HttpArtifactCacheEvent;
import com.facebook.buck.artifact_cache.HttpArtifactCacheEventFetchData;
import com.facebook.buck.artifact_cache.config.ArtifactCacheMode;
import com.facebook.buck.core.build.engine.BuildRuleStatus;
import com.facebook.buck.core.build.engine.BuildRuleSuccessType;
import com.facebook.buck.core.build.engine.type.UploadToCacheResultType;
import com.facebook.buck.core.build.event.BuildEvent;
import com.facebook.buck.core.build.event.BuildRuleEvent;
import com.facebook.buck.core.build.stats.BuildRuleDurationTracker;
import com.facebook.buck.core.cell.CellPathResolver;
import com.facebook.buck.core.cell.TestCellPathResolver;
import com.facebook.buck.core.config.FakeBuckConfig;
import com.facebook.buck.core.model.BuildId;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.BuildTargetFactory;
import com.facebook.buck.core.model.TargetConfigurationSerializerForTests;
import com.facebook.buck.core.parser.buildtargetparser.ParsingUnconfiguredBuildTargetViewFactory;
import com.facebook.buck.core.parser.buildtargetparser.UnconfiguredBuildTargetViewFactory;
import com.facebook.buck.core.rulekey.BuildRuleKeys;
import com.facebook.buck.core.rulekey.RuleKey;
import com.facebook.buck.core.rules.impl.FakeBuildRule;
import com.facebook.buck.core.test.event.TestRunEvent;
import com.facebook.buck.core.test.event.TestSummaryEvent;
import com.facebook.buck.event.ActionGraphEvent;
import com.facebook.buck.event.ArtifactCompressionEvent;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.BuckEventBusForTests;
import com.facebook.buck.event.CommandEvent;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.event.InstallEvent;
import com.facebook.buck.event.ProgressEvent;
import com.facebook.buck.event.ProjectGenerationEvent;
import com.facebook.buck.event.WatchmanStatusEvent;
import com.facebook.buck.event.listener.util.ProgressEstimator;
import com.facebook.buck.io.filesystem.impl.FakeProjectFilesystem;
import com.facebook.buck.json.ProjectBuildFileParseEvents;
import com.facebook.buck.parser.ParseEvent;
import com.facebook.buck.parser.api.ProjectBuildFileParser;
import com.facebook.buck.parser.events.ParseBuckFileEvent;
import com.facebook.buck.rules.keys.FakeRuleKeyFactory;
import com.facebook.buck.step.StepEvent;
import com.facebook.buck.test.TestCaseSummary;
import com.facebook.buck.test.TestResultSummary;
import com.facebook.buck.test.TestResults;
import com.facebook.buck.test.TestRuleEvent;
import com.facebook.buck.test.config.TestResultSummaryVerbosity;
import com.facebook.buck.test.result.type.ResultType;
import com.facebook.buck.test.selectors.TestSelectorList;
import com.facebook.buck.testutil.MoreAsserts;
import com.facebook.buck.testutil.TemporaryPaths;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.util.ExitCode;
import com.facebook.buck.util.environment.DefaultExecutionEnvironment;
import com.facebook.buck.util.environment.EnvVariablesProvider;
import com.facebook.buck.util.json.ObjectMappers;
import com.facebook.buck.util.timing.Clock;
import com.facebook.buck.util.timing.IncrementingFakeClock;
import com.facebook.buck.util.unit.SizeUnit;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.Subscribe;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.UUID;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class SuperConsoleEventBusListenerTest {
  private static final String TARGET_ONE = "//target:one";
  private static final String TARGET_TWO = "//target:two";
  private static final String TARGET_THREE = "//target:three";
  private static final String SEVERE_MESSAGE = "This is a sample severe message.";
  private static final TestResultSummaryVerbosity noisySummaryVerbosity =
      TestResultSummaryVerbosity.of(true, true);

  private static final TestResultSummaryVerbosity silentSummaryVerbosity =
      TestResultSummaryVerbosity.of(false, false);

  @Rule public final TemporaryPaths tmp = new TemporaryPaths();
  @Rule public final Timeout timeout = Timeout.seconds(10);

  private FileSystem vfs;
  private Path logPath;
  private BuildRuleDurationTracker durationTracker;
  private SuperConsoleConfig emptySuperConsoleConfig =
      new SuperConsoleConfig(FakeBuckConfig.builder().build());

  private String formatCacheStatsLine(boolean running, int artifacts, float size, float ratio) {
    String operationString = running ? "Downloading..." : "Downloaded";
    String sizeString = size == 0 ? "0.00 bytes" : String.format("%.2f Mbytes", size);
    return String.format(
        "%s %d artifacts, %s, %.1f%% cache miss", operationString, artifacts, sizeString, ratio);
  }

  @Before
  public void setUp() {
    vfs = Jimfs.newFileSystem(Configuration.unix());
    logPath = vfs.getPath("log.txt");
    durationTracker = new BuildRuleDurationTracker();
  }

  @Parameters(name = "{0}")
  public static Collection<Object[]> data() {
    return Arrays.asList(
        new Object[][] {
          {
            "no_build_id_and_no_build_url",
            false,
            Optional.empty(),
            ImmutableSet.of("build", "test", "install")
          },
          {
            "build_id_and_no_build_url",
            true,
            Optional.empty(),
            ImmutableSet.of("build", "test", "install")
          },
          {
            "build_id_and_build_url",
            true,
            Optional.of("View details at https://example.com/build/{build_id}"),
            ImmutableSet.of("build", "test", "install")
          },
          {
            "no_build_id_and_build_url",
            false,
            Optional.of("View details at https://example.com/build/{build_id}"),
            ImmutableSet.of("build", "test", "install")
          },
          {
            "no_build_id_and_build_url_but_no_build_command",
            false,
            Optional.of("View details at https://example.com/build/{build_id}"),
            ImmutableSet.of()
          }
        });
  }

  private final BuildId buildId = new BuildId("1234-5678");

  @Parameterized.Parameter(0)
  public String _ignoredName;

  @Parameterized.Parameter(1)
  public boolean printBuildId;

  @Parameterized.Parameter(2)
  public Optional<String> buildDetailsTemplate;

  @Parameterized.Parameter(3)
  public ImmutableSet<String> buildDetailsCommands;

  private static class TestRenderingConsole extends RenderingConsole {
    private final TestConsole testConsole;

    public TestRenderingConsole(Clock clock, TestConsole console) {
      super(clock, console);
      this.testConsole = console;
    }

    @Override
    protected void startRenderScheduler() {
      setIsRendering(true);
      // Don't start the actual renderer, we'll inspect things directly.
    }
  }

  @Test
  public void testSimpleBuild() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(
            fakeClock,
            eventBus,
            printBuildId,
            buildDetailsTemplate,
            buildDetailsCommands,
            renderingConsole);

    BuildTarget fakeTarget = BuildTargetFactory.newInstance("//banana:stand");
    BuildTarget dirCachedTarget = BuildTargetFactory.newInstance("//chicken:dance");
    BuildTarget remoteCachedTarget = BuildTargetFactory.newInstance("//chicken:noodles");
    RuleKey remoteCachedRuleKey = new RuleKey("deadbeef");
    ImmutableSet<BuildTarget> buildTargets =
        ImmutableSet.of(fakeTarget, dirCachedTarget, remoteCachedTarget);
    Iterable<String> buildArgs = Iterables.transform(buildTargets, Object::toString);
    FakeBuildRule fakeRule = new FakeBuildRule(fakeTarget, ImmutableSortedSet.of());
    FakeBuildRule cachedDirRule = new FakeBuildRule(dirCachedTarget, ImmutableSortedSet.of());

    ProjectBuildFileParseEvents.Started parseEventStarted =
        new ProjectBuildFileParseEvents.Started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseEventStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    validateBuildIdConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Parsing buck files... 0.0 sec"));

    validateBuildIdConsole(
        listener, renderingConsole, 100L, ImmutableList.of("Parsing buck files... 0.1 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectBuildFileParseEvents.Finished(parseEventStarted),
            200L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    validateBuildIdConsole(
        listener,
        renderingConsole,
        200L,
        ImmutableList.of("Parsing buck files: finished in 0.2 sec"));

    BuildEvent.Started buildEventStarted = BuildEvent.started(buildArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    ParseEvent.Started parseStarted = ParseEvent.started(buildTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener, renderingConsole, 300L, ImmutableList.of("Parsing buck files... 0.3 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            300L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            400L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String parsingLine = "Parsing buck files: finished in 0.3 sec";
    String actionGraphLine = "Creating action graph: finished in 0.1 sec";

    validateBuildIdConsole(
        listener,
        renderingConsole,
        540L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.1 sec"));

    BuildRuleEvent.Started started = BuildRuleEvent.started(fakeRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(started, 600L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        700L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (preparing)"));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        702L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (preparing)"));

    ArtifactCompressionEvent.Started compressStarted =
        ArtifactCompressionEvent.started(
            ArtifactCompressionEvent.Operation.COMPRESS, ImmutableSet.of());
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(compressStarted, 703L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        703L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (running artifact_compress[0.0 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ArtifactCompressionEvent.finished(compressStarted, 0, 0),
            704L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        705L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (preparing)"));

    CellPathResolver cellPathResolver = TestCellPathResolver.get(new FakeProjectFilesystem());
    UnconfiguredBuildTargetViewFactory unconfiguredBuildTargetFactory =
        new ParsingUnconfiguredBuildTargetViewFactory();
    DirArtifactCacheEvent.DirArtifactCacheEventFactory dirArtifactCacheEventFactory =
        new DirArtifactCacheEvent.DirArtifactCacheEventFactory(
            target -> unconfiguredBuildTargetFactory.create(cellPathResolver, target),
            TargetConfigurationSerializerForTests.create(cellPathResolver));

    ArtifactCacheEvent.Started dirFetchStarted =
        dirArtifactCacheEventFactory.newFetchStartedEvent(ImmutableSet.of());

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(dirFetchStarted, 740L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        741L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (running dir_artifact_fetch[0.0 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            dirArtifactCacheEventFactory.newFetchFinishedEvent(
                dirFetchStarted, CacheResult.hit("dir", ArtifactCacheMode.dir)),
            742L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    // Test a remote cache request.
    HttpArtifactCacheEvent.Started remoteFetchStarted =
        HttpArtifactCacheEvent.newFetchStartedEvent(null, remoteCachedRuleKey);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            remoteFetchStarted, 745L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    ArtifactCacheMode remoteCacheMode = ArtifactCacheMode.thrift_over_http;
    long remoteArtifactSizeBytes = 23 * 1024 * 1024;
    CacheResult remoteCacheResult =
        CacheResult.hit(
            remoteCacheMode.name(), remoteCacheMode, ImmutableMap.of(), remoteArtifactSizeBytes);
    HttpArtifactCacheEventFetchData.Builder fetchDataBuilder =
        HttpArtifactCacheEventFetchData.builder()
            .setFetchResult(remoteCacheResult)
            .setArtifactSizeBytes(remoteArtifactSizeBytes);

    HttpArtifactCacheEvent.Finished remoteFetchFinished =
        HttpArtifactCacheEvent.newFinishedEventBuilder(remoteFetchStarted)
            .setFetchDataBuilder(fetchDataBuilder)
            .build();

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            remoteFetchFinished, 780L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        790L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (preparing)"));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23, 0f),
            "Building... 0.4 sec",
            " - //banana:stand... 0.2 sec (preparing)"));

    String stepShortName = "doing_something";
    String stepDescription = "working hard";
    UUID stepUuid = UUID.randomUUID();
    StepEvent.Started stepEventStarted =
        StepEvent.started(stepShortName, stepDescription, stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(stepEventStarted, 800L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        900L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23f, 0f),
            "Building... 0.5 sec",
            " - //banana:stand... 0.3 sec (running doing_something[0.1 sec])"));

    String innerStepShortName = "doing_something_inner";
    String innerStepDescription = "working hard (for real)";
    UUID innerStepUuid = UUID.randomUUID();
    StepEvent.Started innerStepEventStarted =
        StepEvent.started(innerStepShortName, innerStepDescription, innerStepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            innerStepEventStarted, 800L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    // Should now show the inner step.
    validateBuildIdConsole(
        listener,
        renderingConsole,
        900L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23f, 0f),
            "Building... 0.5 sec",
            " - //banana:stand... 0.3 sec (running doing_something_inner[0.1 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(innerStepEventStarted, 0),
            900L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    // Should now return to showing the outer step.
    validateBuildIdConsole(
        listener,
        renderingConsole,
        900L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23f, 0f),
            "Building... 0.5 sec",
            " - //banana:stand... 0.3 sec (running doing_something[0.1 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(stepEventStarted, 0),
            900L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                started,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.ignored(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1000L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        1000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23f, 0f),
            "Building... 0.6 sec",
            " - IDLE"));

    BuildRuleEvent.Started startedCached = BuildRuleEvent.started(cachedDirRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(startedCached, 1010L, TimeUnit.MILLISECONDS, /* threadId */ 2L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        1100L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 1, 23f, 0f),
            "Building... 0.7 sec",
            " - IDLE",
            " - //chicken:dance... 0.0 sec (preparing)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                startedCached,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.hit(ArtifactCacheMode.dir.name(), ArtifactCacheMode.dir),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.FETCHED_FROM_CACHE),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1120L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 2L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildEvent.finished(buildEventStarted, ExitCode.SUCCESS),
            1234L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String buildingLine = "Building: finished in 0.8 sec";
    String totalLine = "  Total time: 1.0 sec";

    validateBuildIdConsole(
        listener,
        renderingConsole,
        1300L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ConsoleEvent.severe(SEVERE_MESSAGE), 1500L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsoleWithLogLines(
        listener,
        renderingConsole,
        1600L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine),
        ImmutableList.of(SEVERE_MESSAGE));

    InstallEvent.Started installEventStarted = InstallEvent.started(fakeTarget);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            installEventStarted, 2500L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateBuildIdConsole(
        listener,
        renderingConsole,
        3000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            "Installing... 0.5 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            InstallEvent.finished(installEventStarted, true, Optional.empty(), Optional.empty()),
            4000L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String installingFinished = "Installing: finished in 1.5 sec";

    validateBuildIdConsole(
        listener,
        renderingConsole,
        5000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            installingFinished));

    HttpArtifactCacheEvent.Scheduled storeScheduledOne =
        ArtifactCacheTestUtils.postStoreScheduled(
            eventBus, 0L, BuildTargetFactory.newInstance(TARGET_ONE), 6000L);

    HttpArtifactCacheEvent.Scheduled storeScheduledTwo =
        ArtifactCacheTestUtils.postStoreScheduled(
            eventBus, 0L, BuildTargetFactory.newInstance(TARGET_TWO), 6010L);

    HttpArtifactCacheEvent.Scheduled storeScheduledThree =
        ArtifactCacheTestUtils.postStoreScheduled(
            eventBus, 0L, BuildTargetFactory.newInstance(TARGET_THREE), 6020L);

    validateBuildIdConsole(
        listener,
        renderingConsole,
        6021L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            installingFinished,
            "HTTP CACHE UPLOAD... 0.00 bytes (0 COMPLETE/0 FAILED/0 UPLOADING/3 PENDING)"));

    HttpArtifactCacheEvent.Started storeStartedOne =
        ArtifactCacheTestUtils.postStoreStarted(eventBus, 0, 6025L, storeScheduledOne);

    validateBuildIdConsole(
        listener,
        renderingConsole,
        7000,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            installingFinished,
            "HTTP CACHE UPLOAD... 0.00 bytes (0 COMPLETE/0 FAILED/1 UPLOADING/2 PENDING)"));

    long artifactSizeOne = SizeUnit.KILOBYTES.toBytes(1.5);
    ArtifactCacheTestUtils.postStoreFinished(
        eventBus, 0, artifactSizeOne, 7020L, true, storeStartedOne);

    validateBuildIdConsole(
        listener,
        renderingConsole,
        7020,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            installingFinished,
            "HTTP CACHE UPLOAD... 1.50 Kbytes (1 COMPLETE/0 FAILED/0 UPLOADING/2 PENDING)"));

    HttpArtifactCacheEvent.Started storeStartedTwo =
        ArtifactCacheTestUtils.postStoreStarted(eventBus, 0, 7030L, storeScheduledTwo);
    long artifactSizeTwo = SizeUnit.KILOBYTES.toBytes(1.6);
    ArtifactCacheTestUtils.postStoreFinished(
        eventBus, 0, artifactSizeTwo, 7030L, false, storeStartedTwo);

    validateBuildIdConsole(
        listener,
        renderingConsole,
        7040,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            installingFinished,
            "HTTP CACHE UPLOAD... 1.50 Kbytes (1 COMPLETE/1 FAILED/0 UPLOADING/1 PENDING)"));

    HttpArtifactCacheEvent.Started storeStartedThree =
        ArtifactCacheTestUtils.postStoreStarted(eventBus, 0, 7040L, storeScheduledThree);
    long artifactSizeThree = SizeUnit.KILOBYTES.toBytes(0.6);
    ArtifactCacheTestUtils.postStoreFinished(
        eventBus, 0, artifactSizeThree, 7040L, true, storeStartedThree);

    validateBuildIdConsole(
        listener,
        renderingConsole,
        7040,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 1, 23f, 0f),
            buildingLine,
            totalLine,
            installingFinished,
            "HTTP CACHE UPLOAD... 2.10 Kbytes (2 COMPLETE/1 FAILED/0 UPLOADING/0 PENDING)"));

    CommandEvent.Started commandStarted =
        CommandEvent.started(
            "build", ImmutableList.of(), Paths.get(""), OptionalLong.of(100), 1234);
    eventBus.post(CommandEvent.finished(commandStarted, ExitCode.SUCCESS));
    if (buildDetailsCommands.contains("build") && buildDetailsTemplate.isPresent()) {
      validateBuildIdConsole(
          listener,
          renderingConsole,
          7040,
          ImmutableList.of(
              parsingLine,
              actionGraphLine,
              formatCacheStatsLine(false, 1, 23f, 0f),
              buildingLine,
              totalLine,
              installingFinished,
              "HTTP CACHE UPLOAD... 2.10 Kbytes (2 COMPLETE/1 FAILED/0 UPLOADING/0 PENDING)",
              "View details at https://example.com/build/1234-5678"));
    }
  }

  @Test
  public void testSimpleBuildWithProgress() throws IOException {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    BuildTarget fakeTarget = BuildTargetFactory.newInstance("//banana:stand");
    BuildTarget cachedTarget = BuildTargetFactory.newInstance("//chicken:dance");
    ImmutableSet<BuildTarget> buildTargets = ImmutableSet.of(fakeTarget, cachedTarget);
    Iterable<String> buildArgs = Iterables.transform(buildTargets, Object::toString);
    FakeBuildRule fakeRule = new FakeBuildRule(fakeTarget, ImmutableSortedSet.of());
    FakeBuildRule cachedRule = new FakeBuildRule(cachedTarget, ImmutableSortedSet.of());

    ProgressEstimator e = new ProgressEstimator(Optional.of(getStorageForTest()), eventBus);
    listener.setProgressEstimator(e);

    BuildEvent.RuleCountCalculated ruleCountCalculated =
        BuildEvent.ruleCountCalculated(ImmutableSet.of(), 10);
    eventBus.post(ruleCountCalculated);

    BuildEvent.Started buildEventStarted = BuildEvent.started(buildArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    ParseEvent.Started parseStarted = ParseEvent.started(buildTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        300L,
        ImmutableList.of("Parsing buck files... 0.1 sec (0/unknown)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseBuckFileEvent.finished(
                ParseBuckFileEvent.started(
                    Paths.get(""),
                    ParseBuckFileEvent.ParserKind.PYTHON_DSL,
                    ProjectBuildFileParser.class),
                7,
                99,
                Optional.empty()),
            300L,
            TimeUnit.MILLISECONDS,
            1));

    validateConsole(
        listener,
        renderingConsole,
        300L,
        ImmutableList.of("Parsing buck files... 0.1 sec (1/unknown)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            300L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        300L,
        ImmutableList.of(
            "Parsing buck files: finished in 0.1 sec", "Creating action graph... 0.0 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            400L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    String parsingLine = "Parsing buck files: finished in 0.1 sec";
    String actionGraphLine = "Creating action graph: finished in 0.1 sec";
    validateConsole(
        listener,
        renderingConsole,
        540L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.1 sec" + " (0%) 0/10 jobs, 0 updated"));

    BuildRuleEvent.Started started = BuildRuleEvent.started(fakeRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(started, 600L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.4 sec" + " (0%) 0/10 jobs, 0 updated",
            " - //banana:stand... 0.2 sec (preparing)"));

    String stepShortName = "doing_something";
    String stepDescription = "working hard";
    UUID stepUuid = UUID.randomUUID();
    StepEvent.Started stepEventStarted =
        StepEvent.started(stepShortName, stepDescription, stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(stepEventStarted, 800L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        900L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.5 sec" + " (0%) 0/10 jobs, 0 updated",
            " - //banana:stand... 0.3 sec (running doing_something[0.1 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(stepEventStarted, 0),
            900L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                started,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.miss(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1000L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        1000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 100f),
            "Building... 0.6 sec (10%) 1/10 jobs, 1 updated",
            " - IDLE"));

    BuildRuleEvent.Started startedCached = BuildRuleEvent.started(cachedRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(startedCached, 1010L, TimeUnit.MILLISECONDS, /* threadId */ 2L));

    validateConsole(
        listener,
        renderingConsole,
        1100L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 100f),
            "Building... 0.7 sec (10%) 1/10 jobs, 1 updated",
            " - IDLE",
            " - //chicken:dance... 0.0 sec (preparing)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                startedCached,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.miss(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1120L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 2L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildEvent.finished(buildEventStarted, ExitCode.SUCCESS),
            1234L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String buildingLine = "Building: finished in 0.8 sec" + " (100%) 2/10 jobs, 2 updated";
    String totalTime = "  Total time: 1.0 sec";

    validateConsole(
        listener,
        renderingConsole,
        1300L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalTime));
  }

  @Test
  public void testWatchman() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(WatchmanStatusEvent.started(), 0L, TimeUnit.MILLISECONDS, 0L));
    validateConsole(listener, renderingConsole, 0L, ImmutableList.of());

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(WatchmanStatusEvent.finished(), 1000L, TimeUnit.MILLISECONDS, 0L));
    validateConsole(
        listener,
        renderingConsole,
        1000L,
        ImmutableList.of("Processing filesystem changes: finished in 1.0 sec"));
  }

  @Test
  public void testQuickWatchman() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(WatchmanStatusEvent.started(), 0L, TimeUnit.MILLISECONDS, 0L));
    validateConsole(listener, renderingConsole, 0L, ImmutableList.of());

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(WatchmanStatusEvent.finished(), 500L, TimeUnit.MILLISECONDS, 0L));
    validateConsole(listener, renderingConsole, 500L, ImmutableList.of());
  }

  @Test
  public void testSimpleTest() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    BuildTarget testTarget = BuildTargetFactory.newInstance("//:test");
    ImmutableSet<BuildTarget> testTargets = ImmutableSet.of(testTarget);
    Iterable<String> testArgs = Iterables.transform(testTargets, Object::toString);
    FakeBuildRule testBuildRule = new FakeBuildRule(testTarget, ImmutableSortedSet.of());

    ProjectBuildFileParseEvents.Started parseEventStarted =
        new ProjectBuildFileParseEvents.Started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseEventStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Parsing buck files... 0.0 sec"));

    validateConsole(
        listener, renderingConsole, 100L, ImmutableList.of("Parsing buck files... 0.1 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectBuildFileParseEvents.Finished(parseEventStarted),
            200L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    validateConsole(
        listener,
        renderingConsole,
        200L,
        ImmutableList.of("Parsing buck files: finished in 0.2 sec"));

    BuildEvent.Started buildEventStarted = BuildEvent.started(testArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    ParseEvent.Started parseStarted = ParseEvent.started(testTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 300L, ImmutableList.of("Parsing buck files... 0.3 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            300L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            400L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String parsingLine = "Parsing buck files: finished in 0.3 sec";
    String actionGraphLine = "Creating action graph: finished in 0.1 sec";

    validateConsole(
        listener,
        renderingConsole,
        540L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.1 sec"));

    BuildRuleEvent.Started started = BuildRuleEvent.started(testBuildRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(started, 600L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.4 sec",
            " - //:test... 0.2 sec (preparing)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                started,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.miss(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1000L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildEvent.finished(buildEventStarted, ExitCode.SUCCESS),
            1234L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String buildingLine = "Building: finished in 0.8 sec";
    String totalLine = "  Total time: 1.0 sec";

    validateConsole(
        listener,
        renderingConsole,
        1300L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRunEvent.started(
                true, // isRunAllTests
                TestSelectorList.empty(),
                false, // shouldExplainTestSelectorList
                ImmutableSet.copyOf(testArgs)),
            2500L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.5 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRuleEvent.started(testTarget), 3100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.7 sec",
            " - //:test... 0.1 sec"));

    UUID stepUuid = new UUID(0, 1);
    StepEvent.Started stepEventStarted = StepEvent.started("step_name", "step_desc", stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            stepEventStarted, 3300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3400L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.9 sec",
            " - //:test... 0.3 sec (running step_name[0.1 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(stepEventStarted, 0),
            3500L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3600L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.1 sec",
            " - //:test... 0.5 sec"));

    UUID testUUID = new UUID(2, 3);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestSummaryEvent.started(testUUID, "TestClass", "Foo"),
            3700L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.3 sec",
            " - //:test... 0.7 sec (running Foo[0.1 sec])"));

    TestResultSummary testResultSummary =
        new TestResultSummary(
            "TestClass",
            "Foo",
            ResultType.SUCCESS,
            0L, // time
            null, // message
            null, // stacktrace
            null, // stdOut
            null); // stdErr
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestSummaryEvent.finished(testUUID, testResultSummary),
            3900L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        4000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.5 sec (1 PASS/0 FAIL)",
            " - //:test... 0.9 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRunEvent.finished(
                ImmutableSet.copyOf(testArgs),
                ImmutableList.of(
                    TestResults.of(
                        testTarget,
                        ImmutableList.of(
                            new TestCaseSummary("TestClass", ImmutableList.of(testResultSummary))),
                        ImmutableSet.of(), // contacts
                        ImmutableSet.of()))), // labels
            4100L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String testingLine = "Testing: finished in 1.6 sec (1 PASS/0 FAIL)";

    validateConsoleWithStdOutAndErr(
        listener,
        renderingConsole,
        4200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            testingLine),
        ImmutableList.of(),
        Optional.of(
            Joiner.on(System.lineSeparator())
                .join(
                    "RESULTS FOR ALL TESTS",
                    "PASS    <100ms  1 Passed   0 Skipped   0 Failed   TestClass",
                    "TESTS PASSED",
                    "")),
        // We don't care about stderr, since the last frame will be flushed there.
        Optional.empty());
  }

  @Test
  public void testSkippedTest() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    BuildTarget testTarget = BuildTargetFactory.newInstance("//:test");
    ImmutableSet<BuildTarget> testTargets = ImmutableSet.of(testTarget);
    Iterable<String> testArgs = Iterables.transform(testTargets, Object::toString);
    FakeBuildRule testBuildRule = new FakeBuildRule(testTarget, ImmutableSortedSet.of());

    ProjectBuildFileParseEvents.Started parseEventStarted =
        new ProjectBuildFileParseEvents.Started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseEventStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Parsing buck files... 0.0 sec"));

    validateConsole(
        listener, renderingConsole, 100L, ImmutableList.of("Parsing buck files... 0.1 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectBuildFileParseEvents.Finished(parseEventStarted),
            200L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    validateConsole(
        listener,
        renderingConsole,
        200L,
        ImmutableList.of("Parsing buck files: finished in 0.2 sec"));

    BuildEvent.Started buildEventStarted = BuildEvent.started(testArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    ParseEvent.Started parseStarted = ParseEvent.started(testTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 300L, ImmutableList.of("Parsing buck files... 0.3 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            300L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            400L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String parsingLine = "Parsing buck files: finished in 0.3 sec";
    String actionGraphLine = "Creating action graph: finished in 0.1 sec";

    validateConsole(
        listener,
        renderingConsole,
        540L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.1 sec"));

    BuildRuleEvent.Started started = BuildRuleEvent.started(testBuildRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(started, 600L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.4 sec",
            " - //:test... 0.2 sec (preparing)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                started,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.miss(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1000L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildEvent.finished(buildEventStarted, ExitCode.SUCCESS),
            1234L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String buildingLine = "Building: finished in 0.8 sec";
    String totalLine = "  Total time: 1.0 sec";

    validateConsole(
        listener,
        renderingConsole,
        1300L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRunEvent.started(
                true, // isRunAllTests
                TestSelectorList.empty(),
                false, // shouldExplainTestSelectorList
                ImmutableSet.copyOf(testArgs)),
            2500L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.5 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRuleEvent.started(testTarget), 3100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.7 sec",
            " - //:test... 0.1 sec"));

    UUID stepUuid = new UUID(0, 1);
    StepEvent.Started stepEventStarted = StepEvent.started("step_name", "step_desc", stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            stepEventStarted, 3300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3400L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.9 sec",
            " - //:test... 0.3 sec (running step_name[0.1 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(stepEventStarted, 0),
            3500L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3600L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.1 sec",
            " - //:test... 0.5 sec"));

    UUID testUUID = new UUID(2, 3);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestSummaryEvent.started(testUUID, "TestClass", "Foo"),
            3700L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.3 sec",
            " - //:test... 0.7 sec (running Foo[0.1 sec])"));

    TestResultSummary testResultSummary =
        new TestResultSummary(
            "TestClass",
            "Foo",
            ResultType.ASSUMPTION_VIOLATION,
            0L, // time
            null, // message
            null, // stacktrace
            null, // stdOut
            null); // stdErr

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestSummaryEvent.finished(testUUID, testResultSummary),
            3900L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        4000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.5 sec (0 PASS/1 SKIP/0 FAIL)",
            " - //:test... 0.9 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRunEvent.finished(
                ImmutableSet.copyOf(testArgs),
                ImmutableList.of(
                    TestResults.of(
                        testTarget,
                        ImmutableList.of(
                            new TestCaseSummary("TestClass", ImmutableList.of(testResultSummary))),
                        ImmutableSet.of(), // contacts
                        ImmutableSet.of()))), // labels
            4100L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String testingLine = "Testing: finished in 1.6 sec (0 PASS/1 SKIP/0 FAIL)";

    validateConsoleWithStdOutAndErr(
        listener,
        renderingConsole,
        4200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            testingLine),
        ImmutableList.of(),
        Optional.of(
            Joiner.on(System.lineSeparator())
                .join(
                    "RESULTS FOR ALL TESTS",
                    "ASSUME  <100ms  0 Passed   1 Skipped   0 Failed   TestClass",
                    "NO TESTS RAN (assumption violations)",
                    "")),
        // We don't care about stderr, since the last frame will be flushed there.
        Optional.empty());
  }

  @Test
  public void testFailingTest() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestConsole console = new TestConsole();

    BuildTarget testTarget = BuildTargetFactory.newInstance("//:test");
    ImmutableSet<BuildTarget> testTargets = ImmutableSet.of(testTarget);
    Iterable<String> testArgs = Iterables.transform(testTargets, Object::toString);
    FakeBuildRule testBuildRule = new FakeBuildRule(testTarget, ImmutableSortedSet.of());
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, console);
    SuperConsoleEventBusListener listener =
        new SuperConsoleEventBusListener(
            emptySuperConsoleConfig,
            renderingConsole,
            fakeClock,
            noisySummaryVerbosity,
            new DefaultExecutionEnvironment(
                EnvVariablesProvider.getSystemEnv(), System.getProperties()),
            Locale.US,
            logPath,
            0L,
            0L,
            1000L,
            false,
            buildId,
            false,
            Optional.empty(),
            ImmutableSet.of(),
            ImmutableList.of(),
            /* maxConcurrentReExecutions */ 0);
    listener.register(eventBus);

    ProjectBuildFileParseEvents.Started parseEventStarted =
        new ProjectBuildFileParseEvents.Started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseEventStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Parsing buck files... 0.0 sec"));

    validateConsole(
        listener, renderingConsole, 100L, ImmutableList.of("Parsing buck files... 0.1 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectBuildFileParseEvents.Finished(parseEventStarted),
            200L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    validateConsole(
        listener,
        renderingConsole,
        200L,
        ImmutableList.of("Parsing buck files: finished in 0.2 sec"));

    BuildEvent.Started buildEventStarted = BuildEvent.started(testArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    ParseEvent.Started parseStarted = ParseEvent.started(testTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 300L, ImmutableList.of("Parsing buck files... 0.3 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            300L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            400L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String parsingLine = "Parsing buck files: finished in 0.3 sec";
    String actionGraphLine = "Creating action graph: finished in 0.1 sec";

    validateConsole(
        listener,
        renderingConsole,
        540L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.1 sec"));

    BuildRuleEvent.Started started = BuildRuleEvent.started(testBuildRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(started, 600L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.4 sec",
            " - //:test... 0.2 sec (preparing)"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                started,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.miss(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            1000L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildEvent.finished(buildEventStarted, ExitCode.SUCCESS),
            1234L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String buildingLine = "Building: finished in 0.8 sec";
    String totalLine = "  Total time: 1.0 sec";

    validateConsole(
        listener,
        renderingConsole,
        1300L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRunEvent.started(
                true, // isRunAllTests
                TestSelectorList.empty(),
                false, // shouldExplainTestSelectorList
                ImmutableSet.copyOf(testArgs)),
            2500L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.5 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRuleEvent.started(testTarget), 3100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.7 sec",
            " - //:test... 0.1 sec"));

    UUID stepUuid = new UUID(0, 1);
    StepEvent.Started stepEventStarted = StepEvent.started("step_name", "step_desc", stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            stepEventStarted, 3300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3400L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 0.9 sec",
            " - //:test... 0.3 sec (running step_name[0.1 sec])"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(stepEventStarted, 0),
            3500L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3600L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.1 sec",
            " - //:test... 0.5 sec"));

    UUID testUUID = new UUID(2, 3);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestSummaryEvent.started(testUUID, "TestClass", "Foo"),
            3700L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        3800L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.3 sec",
            " - //:test... 0.7 sec (running Foo[0.1 sec])"));

    TestResultSummary testResultSummary =
        new TestResultSummary(
            "TestClass",
            "Foo",
            ResultType.FAILURE,
            0L, // time
            "Foo.java:47: Assertion failure: 'foo' != 'bar'", // message
            null, // stacktrace
            "Message on stdout", // stdOut
            "Message on stderr"); // stdErr
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestSummaryEvent.finished(testUUID, testResultSummary),
            3900L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    validateConsoleWithLogLines(
        listener,
        renderingConsole,
        4000L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            "Testing... 1.5 sec (0 PASS/1 FAIL)",
            " - //:test... 0.9 sec"),
        ImmutableList.of("FAILURE TestClass Foo: Foo.java:47: Assertion failure: 'foo' != 'bar'"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            TestRunEvent.finished(
                ImmutableSet.copyOf(testArgs),
                ImmutableList.of(
                    TestResults.of(
                        testTarget,
                        ImmutableList.of(
                            new TestCaseSummary("TestClass", ImmutableList.of(testResultSummary))),
                        ImmutableSet.of(), // contacts
                        ImmutableSet.of()))), // labels
            4100L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    String testingLine = "Testing: finished in 1.6 sec (0 PASS/1 FAIL)";

    validateConsoleWithStdOutAndErr(
        listener,
        renderingConsole,
        4200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(false, 0, 0f, 100f),
            buildingLine,
            totalLine,
            testingLine),
        ImmutableList.of(),
        Optional.of(
            Joiner.on(System.lineSeparator())
                .join(
                    "RESULTS FOR ALL TESTS",
                    "FAIL    <100ms  0 Passed   0 Skipped   1 Failed   TestClass",
                    "FAILURE TestClass Foo: Foo.java:47: Assertion failure: 'foo' != 'bar'",
                    "====STANDARD OUT====",
                    "Message on stdout",
                    "====STANDARD ERR====",
                    "Message on stderr",
                    "TESTS FAILED: 1 FAILURE",
                    "Failed target: //:test",
                    "FAIL TestClass",
                    "")),
        // We don't care about stderr, since the last frame will be flushed there.
        Optional.empty());
  }

  @Test
  public void testBuildRuleSuspendResumeEvents() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    BuildTarget fakeTarget = BuildTargetFactory.newInstance("//banana:stand");
    ImmutableSet<BuildTarget> buildTargets = ImmutableSet.of(fakeTarget);
    Iterable<String> buildArgs = Iterables.transform(buildTargets, Object::toString);
    FakeBuildRule fakeRule = new FakeBuildRule(fakeTarget, ImmutableSortedSet.of());
    String stepShortName = "doing_something";
    String stepDescription = "working hard";
    UUID stepUuid = UUID.randomUUID();

    FakeRuleKeyFactory ruleKeyFactory =
        new FakeRuleKeyFactory(ImmutableMap.of(fakeTarget, new RuleKey("aaaa")));

    // Start the build.
    BuildEvent.Started buildEventStarted = BuildEvent.started(buildArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(buildEventStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    // Start and stop parsing.
    String parsingLine = "Parsing buck files: finished in 0.0 sec";
    String actionGraphLine = "Creating action graph: finished in 0.0 sec";
    ParseEvent.Started parseStarted = ParseEvent.started(buildTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            0L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(actionGraphStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            0L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    // Start the rule.
    BuildRuleEvent.Started started = BuildRuleEvent.started(fakeRule, durationTracker);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(started, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    // Post events that run a step for 100ms.
    StepEvent.Started stepEventStarted =
        StepEvent.started(stepShortName, stepDescription, stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(stepEventStarted, 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(stepEventStarted, /* exitCode */ 0),
            100L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    // Suspend the rule.
    BuildRuleEvent.Suspended suspended = BuildRuleEvent.suspended(started, ruleKeyFactory);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(suspended, 100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    // Verify that the rule isn't printed now that it's suspended.
    validateConsole(
        listener,
        renderingConsole,
        200L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.2 sec",
            " - IDLE"));

    // Resume the rule.
    BuildRuleEvent.Resumed resumed =
        BuildRuleEvent.resumed(fakeRule, durationTracker, ruleKeyFactory);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(resumed, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    // Verify that we print "checking local..." now that we've resumed, and that we're accounting
    // for previous running time.
    validateConsole(
        listener,
        renderingConsole,
        300L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.3 sec",
            " - //banana:stand... 0.1 sec (preparing)"));

    // Post events that run another step.
    StepEvent.Started step2EventStarted =
        StepEvent.started(stepShortName, stepDescription, stepUuid);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            step2EventStarted, 400L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    // Verify the current console now accounts for the step.
    validateConsole(
        listener,
        renderingConsole,
        500L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.5 sec",
            " - //banana:stand... 0.3 sec (running doing_something[0.1 sec])"));

    // Finish the step and rule.
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            StepEvent.finished(step2EventStarted, /* exitCode */ 0),
            600L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildRuleEvent.finished(
                resumed,
                BuildRuleKeys.of(new RuleKey("aaaa")),
                BuildRuleStatus.SUCCESS,
                CacheResult.miss(),
                Optional.empty(),
                Optional.of(BuildRuleSuccessType.BUILT_LOCALLY),
                UploadToCacheResultType.UNCACHEABLE,
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Optional.empty()),
            600L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    // Verify that the rule isn't printed now that it's finally finished..
    validateConsole(
        listener,
        renderingConsole,
        700L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 100f),
            "Building... 0.7 sec",
            " - IDLE"));
  }

  @Test
  public void debugConsoleEventShouldNotPrintLogLineToConsole() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ConsoleEvent.fine("I'll get you Bluths - Hel-loh"),
            0L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));
    validateConsole(listener, renderingConsole, 0L, ImmutableList.of());
  }

  @Test
  public void testProjectGeneration() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.started(), 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Generating project... 0.0 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectGenerationEvent.Finished(), 0L, TimeUnit.MILLISECONDS, 0L));

    validateConsole(
        listener,
        renderingConsole,
        0L,
        ImmutableList.of("Generating project: finished in 0.0 sec"));
  }

  @Test
  public void testProjectGenerationWithProgress() throws IOException {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    ProgressEstimatorSynchronization progressEstimatorSynchronization =
        new ProgressEstimatorSynchronization(eventBus);

    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    Path storagePath = getStorageForTest();
    Map<String, Object> storageContents =
        ImmutableSortedMap.<String, Object>naturalOrder()
            .put(
                "project arg1 arg2",
                ImmutableSortedMap.<String, Number>naturalOrder()
                    .put(ProgressEstimator.EXPECTED_NUMBER_OF_GENERATED_PROJECT_FILES, 10)
                    .build())
            .build();
    String contents = ObjectMappers.WRITER.writeValueAsString(storageContents);
    Files.createDirectories(storagePath.getParent());
    Files.write(storagePath, contents.getBytes(StandardCharsets.UTF_8));

    ProgressEstimator e = new ProgressEstimator(Optional.of(storagePath), eventBus);
    listener.setProgressEstimator(e);

    eventBus.post(
        CommandEvent.started(
            "project", ImmutableList.of("arg1", "arg2"), Paths.get(""), OptionalLong.empty(), 23L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.started(), 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Generating project... 0.0 sec"));

    progressEstimatorSynchronization.expectCalculation();
    progressEstimatorSynchronization.expectCalculation();

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.processed(), 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.processed(), 100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    progressEstimatorSynchronization.awaitCalculation();

    validateConsole(
        listener, renderingConsole, 100L, ImmutableList.of("Generating project... 0.1 sec (20%)"));

    progressEstimatorSynchronization.expectCalculation();

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectGenerationEvent.Finished(), 200L, TimeUnit.MILLISECONDS, 0L));

    progressEstimatorSynchronization.awaitCalculation();

    validateConsole(
        listener,
        renderingConsole,
        0L,
        ImmutableList.of("Generating project: finished in 0.2 sec (100%)"));
  }

  @Test
  public void testProjectGenerationAndBuildWithProgress() throws IOException {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    ProgressEstimatorSynchronization progressEstimatorSynchronization =
        new ProgressEstimatorSynchronization(eventBus);

    Path storagePath = getStorageForTest();
    Map<String, Object> storageContents =
        ImmutableSortedMap.<String, Object>naturalOrder()
            .put(
                "project arg1 arg2",
                ImmutableSortedMap.<String, Number>naturalOrder()
                    .put(ProgressEstimator.EXPECTED_NUMBER_OF_GENERATED_PROJECT_FILES, 10)
                    .build())
            .build();
    String contents = ObjectMappers.WRITER.writeValueAsString(storageContents);
    Files.createDirectories(storagePath.getParent());
    Files.write(storagePath, contents.getBytes(StandardCharsets.UTF_8));

    ProgressEstimator e = new ProgressEstimator(Optional.of(storagePath), eventBus);
    listener.setProgressEstimator(e);

    eventBus.post(
        CommandEvent.started(
            "project", ImmutableList.of("arg1", "arg2"), Paths.get(""), OptionalLong.empty(), 23L));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.started(), 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Generating project... 0.0 sec"));

    progressEstimatorSynchronization.expectCalculation();
    progressEstimatorSynchronization.expectCalculation();

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.processed(), 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.processed(), 100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    progressEstimatorSynchronization.awaitCalculation();

    validateConsole(
        listener, renderingConsole, 100L, ImmutableList.of("Generating project... 0.1 sec (20%)"));

    BuildTarget fakeTarget = BuildTargetFactory.newInstance("//banana:stand");
    ImmutableSet<BuildTarget> buildTargets = ImmutableSet.of(fakeTarget);

    // no need to validate the output for parsing and action graph, since they're tested elsewhere
    ParseEvent.Started parseStarted = ParseEvent.started(buildTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 400L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            600L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 600L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            700L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    Iterable<String> buildArgs = Iterables.transform(buildTargets, Object::toString);
    BuildEvent.Started buildEventStarted = BuildEvent.started(buildArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 800L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        943L,
        ImmutableList.of(
            "Parsing buck files: finished in 0.2 sec",
            "Creating action graph: finished in 0.1 sec",
            "Generating project... 0.9 sec (20%)",
            formatCacheStatsLine(true, 0, 0, 0),
            "Building... 0.1 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            BuildEvent.finished(buildEventStarted, ExitCode.SUCCESS),
            1000L,
            TimeUnit.MILLISECONDS, /* threadId */
            0L));

    validateConsole(
        listener,
        renderingConsole,
        1000L,
        ImmutableList.of(
            "Parsing buck files: finished in 0.2 sec",
            "Creating action graph: finished in 0.1 sec",
            "Generating project... 1.0 sec (20%)",
            formatCacheStatsLine(false, 0, 0, 0),
            "Building: finished in 0.2 sec"));

    progressEstimatorSynchronization.expectCalculation();

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectGenerationEvent.Finished(), 1200L, TimeUnit.MILLISECONDS, 0L));

    progressEstimatorSynchronization.awaitCalculation();

    validateConsole(
        listener,
        renderingConsole,
        0L,
        ImmutableList.of(
            "Parsing buck files: finished in 0.2 sec",
            "Creating action graph: finished in 0.1 sec",
            "Generating project: finished in 1.2 sec (100%)",
            formatCacheStatsLine(false, 0, 0, 0),
            "Building: finished in 0.2 sec",
            "  Total time: 1.2 sec"));
  }

  @Test
  public void testPostingEventBeforeAnyLines() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    eventBus.post(ConsoleEvent.info("Hello world!"));

    validateConsoleWithLogLines(
        listener, renderingConsole, 0L, ImmutableList.of(), ImmutableList.of("Hello world!"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ProjectGenerationEvent.started(), 0L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener, renderingConsole, 0L, ImmutableList.of("Generating project... 0.0 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            new ProjectGenerationEvent.Finished(), 0L, TimeUnit.MILLISECONDS, 0L));

    validateConsole(
        listener,
        renderingConsole,
        0L,
        ImmutableList.of("Generating project: finished in 0.0 sec"));
  }

  @Test
  public void renderLinesWithLineLimit() throws IOException {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    try (SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole)) {

      FakeMultiStateRenderer fakeRenderer =
          new FakeMultiStateRenderer(ImmutableList.of(2L, 1L, 4L, 8L, 5L));
      ImmutableList.Builder<String> lines;

      ImmutableList<String> fullOutput =
          ImmutableList.of(
              " - Status of thread 2",
              " - Status of thread 1",
              " - Status of thread 4",
              " - Status of thread 8",
              " - Status of thread 5");

      compareOutput(listener, fakeRenderer, fullOutput, 10000);
      compareOutput(listener, fakeRenderer, fullOutput, 10000);
      compareOutput(listener, fakeRenderer, fullOutput, 6);
      compareOutput(listener, fakeRenderer, fullOutput, 5);

      lines = ImmutableList.builder();
      listener.renderLinesWithMaybeCompression(fakeRenderer, lines, 4, false);
      assertThat(
          lines.build(),
          equalTo(
              ImmutableList.of(
                  " - Status of thread 2",
                  " - Status of thread 1",
                  " - Status of thread 4",
                  " - 2 MORE THREADS: t8 t5")));
      assertThat(fakeRenderer.lastSortWasByTime(), is(true));

      lines = ImmutableList.builder();
      listener.renderLinesWithMaybeCompression(fakeRenderer, lines, 2, false);
      assertThat(
          lines.build(),
          equalTo(ImmutableList.of(" - Status of thread 2", " - 4 MORE THREADS: t1 t4 t8 t5")));
      assertThat(fakeRenderer.lastSortWasByTime(), is(true));

      lines = ImmutableList.builder();
      listener.renderLinesWithMaybeCompression(fakeRenderer, lines, 1, false);
      assertThat(lines.build(), equalTo(ImmutableList.of(" - 5 THREADS: t2 t1 t4 t8 t5")));
      assertThat(fakeRenderer.lastSortWasByTime(), is(true));
    }
  }

  private void validateBuildIdConsole(
      SuperConsoleEventBusListener listener,
      TestRenderingConsole renderingConsole,
      long timeMs,
      ImmutableList<String> lines) {
    validateBuildIdConsoleWithLogLines(
        listener, renderingConsole, timeMs, lines, ImmutableList.of());
  }

  private void validateBuildIdConsoleWithLogLines(
      SuperConsoleEventBusListener listener,
      TestRenderingConsole renderingConsole,
      long timeMs,
      ImmutableList<String> lines,
      ImmutableList<String> logLines) {

    Builder<String> builder =
        ImmutableList.builderWithExpectedSize(lines.size() + (printBuildId ? 1 : 0));
    if (printBuildId) {
      builder.add("Build UUID: 1234-5678");
    }
    builder.addAll(lines);

    validateConsoleWithStdOutAndErr(
        listener,
        renderingConsole,
        timeMs,
        builder.build(),
        logLines,
        Optional.of(""),
        Optional.of(""));
  }

  private void validateConsole(
      SuperConsoleEventBusListener listener,
      TestRenderingConsole renderingConsole,
      long timeMs,
      ImmutableList<String> lines) {
    validateConsoleWithLogLines(listener, renderingConsole, timeMs, lines, ImmutableList.of());
  }

  private void validateConsoleWithLogLines(
      SuperConsoleEventBusListener listener,
      TestRenderingConsole renderingConsole,
      long timeMs,
      ImmutableList<String> lines,
      ImmutableList<String> logLines) {
    validateConsoleWithStdOutAndErr(
        listener, renderingConsole, timeMs, lines, logLines, Optional.of(""), Optional.of(""));
  }

  private void validateConsoleWithStdOutAndErr(
      SuperConsoleEventBusListener listener,
      TestRenderingConsole console,
      long timeMs,
      ImmutableList<String> lines,
      ImmutableList<String> logLines,
      Optional<String> stdout,
      Optional<String> stderr) {

    if (stdout.isPresent()) {
      assertThat(console.testConsole.getTextWrittenToStdOut(), equalTo(stdout.get()));
    }
    if (stderr.isPresent()) {
      assertThat(console.testConsole.getTextWrittenToStdErr(), equalTo(stderr.get()));
    }
    MoreAsserts.assertIterablesEquals(lines, listener.createRenderLinesAtTime(timeMs));
    MoreAsserts.assertIterablesEquals(logLines, console.getPendingLogLines());
    console.clearPendingLogLines();
  }

  @Test
  public void testBuildTimeDoesNotDisplayNegativeOffset() {
    Clock fakeClock = new IncrementingFakeClock(TimeUnit.SECONDS.toNanos(1));
    BuckEventBus eventBus = BuckEventBusForTests.newInstance(fakeClock);
    TestRenderingConsole renderingConsole = new TestRenderingConsole(fakeClock, new TestConsole());
    SuperConsoleEventBusListener listener =
        createSuperConsole(fakeClock, eventBus, renderingConsole);

    BuildTarget fakeTarget = BuildTargetFactory.newInstance("//banana:stand");
    ImmutableSet<BuildTarget> buildTargets = ImmutableSet.of(fakeTarget);
    Iterable<String> buildArgs = Iterables.transform(buildTargets, Object::toString);

    // Do a full parse and action graph cycle before the build event starts
    // This sequencing occurs when running `buck project`
    ParseEvent.Started parseStarted = ParseEvent.started(buildTargets);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(parseStarted, 100L, TimeUnit.MILLISECONDS, /* threadId */ 0L));
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ParseEvent.finished(parseStarted, 10, Optional.empty()),
            200L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    ActionGraphEvent.Started actionGraphStarted = ActionGraphEvent.started();
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            actionGraphStarted, 200L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    validateConsole(
        listener,
        renderingConsole,
        200L,
        ImmutableList.of(
            "Parsing buck files: finished in 0.1 sec", "Creating action graph... 0.0 sec"));

    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            ActionGraphEvent.finished(actionGraphStarted),
            300L,
            TimeUnit.MILLISECONDS,
            /* threadId */ 0L));

    BuildEvent.Started buildEventStarted = BuildEvent.started(buildArgs);
    eventBus.postWithoutConfiguring(
        configureTestEventAtTime(
            buildEventStarted, 300L, TimeUnit.MILLISECONDS, /* threadId */ 0L));

    String parsingLine = "Parsing buck files: finished in 0.1 sec";
    String actionGraphLine = "Creating action graph: finished in 0.1 sec";

    validateConsole(
        listener,
        renderingConsole,
        433L,
        ImmutableList.of(
            parsingLine,
            actionGraphLine,
            formatCacheStatsLine(true, 0, 0f, 0f),
            "Building... 0.1 sec"));
  }

  private SuperConsoleEventBusListener createSuperConsole(
      Clock clock, BuckEventBus eventBus, TestRenderingConsole console) {
    return createSuperConsole(clock, eventBus, false, Optional.empty(), ImmutableSet.of(), console);
  }

  private SuperConsoleEventBusListener createSuperConsole(
      Clock clock,
      BuckEventBus eventBus,
      boolean printBuildId,
      Optional<String> buildDetailsTemplate,
      ImmutableSet<String> buildDetailsCommands,
      TestRenderingConsole console) {
    SuperConsoleEventBusListener listener =
        new SuperConsoleEventBusListener(
            emptySuperConsoleConfig,
            console,
            clock,
            silentSummaryVerbosity,
            new DefaultExecutionEnvironment(
                EnvVariablesProvider.getSystemEnv(), System.getProperties()),
            Locale.US,
            logPath,
            0L,
            0L,
            1000L,
            false,
            buildId,
            printBuildId,
            buildDetailsTemplate,
            buildDetailsCommands,
            ImmutableList.of(),
            /* maxConcurrentReExecutions */ 0);
    listener.register(eventBus);
    return listener;
  }

  private Path getStorageForTest() throws IOException {
    return tmp.newFile();
  }

  private void compareOutput(
      SuperConsoleEventBusListener listener,
      FakeMultiStateRenderer fakeRenderer,
      ImmutableList<String> fullOutput,
      int maxLines) {
    ImmutableList.Builder<String> lines;
    lines = ImmutableList.builder();
    listener.renderLinesWithMaybeCompression(fakeRenderer, lines, maxLines, false);
    MoreAsserts.assertIterablesEquals(fullOutput, lines.build());
    assertThat(fakeRenderer.lastSortWasByTime(), is(false));
  }

  private static class ProgressEstimatorSynchronization {
    /**
     * ProgressEstimator calculates progress asynchronously, indicating that it's down with
     * ProgressEvent.ProjectGenerationProgressUpdated. We create synchronization around the event so
     * that we can properly wait until progress calculation is completed to validate the
     * calculation.
     */
    private final Phaser phaser;

    ProgressEstimatorSynchronization(BuckEventBus eventBus) {
      phaser = new Phaser();
      phaser.register();

      eventBus.register(this);
    }

    /**
     * Every event should have been previous expected via {@link #expectCalculation()}
     *
     * @param e
     */
    @Subscribe
    public void progressCalculationDoneListener(ProgressEvent.ProjectGenerationProgressUpdated e) {
      phaser.arriveAndDeregister();
    }

    /** Indicates that we expect one progress calculation event to be received */
    public void expectCalculation() {
      phaser.register();
    }

    public void awaitCalculation() {
      phaser.arriveAndAwaitAdvance();
    }
  }
}
