Add semantic tests for ExperimentalStateTracker

Now that the experimental UI has the first properties we want to keep
in the long run, add a test asserting the following semantic
properties.

- Whenever only one action is running, it is shown somehow in the
  progress bar.

- Completed actions should not be shown in the progress bar.

- The earliest-started still running action should be visible in
  the progress bar.

While there, also drop the assumption in the ExperimentalStateTracker
that the ExecutionProgressReceiverAvailableEvent has to come before
any actions that has not been finished yet.

--
Change-Id: Ica52eb12546703e4f8f9d9c64928208621d19ced
Reviewed-on: https://bazel-review.googlesource.com/#/c/3048
MOS_MIGRATED_REVID=117121300
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/ExperimentalStateTrackerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/ExperimentalStateTrackerTest.java
new file mode 100644
index 0000000..973b13a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/runtime/ExperimentalStateTrackerTest.java
@@ -0,0 +1,158 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionCompletionEvent;
+import com.google.devtools.build.lib.actions.ActionStartedEvent;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.testutil.FoundationTestCase;
+import com.google.devtools.build.lib.util.io.AnsiTerminalWriter;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+
+/**
+ * Tests {@link ExperimentalStateTrackerTest}
+ */
+@RunWith(JUnit4.class)
+public class ExperimentalStateTrackerTest extends FoundationTestCase {
+
+  private Action mockAction(String progressMessage, String primaryOutput) {
+    Path path = outputBase.getRelative(new PathFragment(primaryOutput));
+    Artifact artifact = new Artifact(path, Root.asSourceRoot(path));
+
+    Action action = Mockito.mock(Action.class);
+    when(action.getProgressMessage()).thenReturn(progressMessage);
+    when(action.getPrimaryOutput()).thenReturn(artifact);
+    return action;
+  }
+
+  private class LoggingTerminalWriter implements AnsiTerminalWriter {
+    private String written;
+
+    LoggingTerminalWriter() {
+      this.written = "";
+    }
+
+    @Override
+    public LoggingTerminalWriter append(String text) throws IOException {
+      written += text;
+      return this;
+    }
+
+    @Override
+    public LoggingTerminalWriter newline() throws IOException {
+      return this;
+    }
+
+    @Override
+    public LoggingTerminalWriter okStatus() throws IOException {
+      return this;
+    }
+
+    @Override
+    public LoggingTerminalWriter failStatus() throws IOException {
+      return this;
+    }
+
+    @Override
+    public LoggingTerminalWriter normal() throws IOException {
+      return this;
+    }
+
+    String getWritten() {
+      return written;
+    }
+  }
+
+  @Test
+  public void testActionVisible() throws IOException {
+    // If there is only one action running, it should be visible
+    // somewhere in the progress bar.
+
+    String message = "Building foo";
+
+    ExperimentalStateTracker stateTracker = new ExperimentalStateTracker();
+    stateTracker.actionStarted(new ActionStartedEvent(mockAction(message, "bar/foo"), 123456789));
+    LoggingTerminalWriter terminalWriter = new LoggingTerminalWriter();
+    stateTracker.writeProgressBar(terminalWriter);
+
+    String output = terminalWriter.getWritten();
+    assertTrue(
+        "Action message '" + message + "' should be present in output: " + output,
+        output.contains(message));
+  }
+
+  @Test
+  public void testCompletedActionNotShown() throws IOException {
+    // Completed actions should not be reported in the progress bar.
+
+    String messageFast = "Running quick action";
+    String messageSlow = "Running slow action";
+
+    Action fastAction = mockAction(messageFast, "foo/fast");
+    Action slowAction = mockAction(messageSlow, "bar/slow");
+    ExperimentalStateTracker stateTracker = new ExperimentalStateTracker();
+    stateTracker.actionStarted(new ActionStartedEvent(fastAction, 123456789));
+    stateTracker.actionStarted(new ActionStartedEvent(slowAction, 123456999));
+    stateTracker.actionCompletion(new ActionCompletionEvent(20, fastAction));
+
+    LoggingTerminalWriter terminalWriter = new LoggingTerminalWriter();
+    stateTracker.writeProgressBar(terminalWriter);
+
+    String output = terminalWriter.getWritten();
+    assertFalse(
+        "Completed action '" + messageFast + "' should not be present in output: " + output,
+        output.contains(messageFast));
+    assertTrue(
+        "Only running action '" + messageSlow + "' should be present in output: " + output,
+        output.contains(messageSlow));
+  }
+
+  @Test
+  public void testOldestActionVisible() throws IOException {
+    // The earliest-started action is always visible somehow in the progress bar.
+
+    String messageOld = "Running the first-started action";
+
+    ExperimentalStateTracker stateTracker = new ExperimentalStateTracker();
+    stateTracker.actionStarted(
+        new ActionStartedEvent(mockAction(messageOld, "bar/foo"), 123456789));
+    for (int i = 0; i < 30; i++) {
+      stateTracker.actionStarted(
+          new ActionStartedEvent(
+              mockAction("Other action " + i, "some/other/actions/number" + i), 123456790 + i));
+    }
+
+    LoggingTerminalWriter terminalWriter = new LoggingTerminalWriter();
+    stateTracker.writeProgressBar(terminalWriter);
+
+    String output = terminalWriter.getWritten();
+    assertTrue(
+        "Longest running action '" + messageOld + "' should be visible in output: " + output,
+        output.contains(messageOld));
+  }
+}