Fixing #3834, making sure test.log always exists.

Even if the test action produced no output, which it really shouldn't, Bazel should create an empty test.log file.

TESTED=unit tests
RELNOTES: Fixes #3834
PiperOrigin-RevId: 172412615
diff --git a/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java b/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
index e4a945c..cfa5d7d 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
@@ -15,6 +15,8 @@
 package com.google.devtools.build.lib.exec;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.when;
 
@@ -25,6 +27,7 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.SpawnActionContext;
 import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.actions.SpawnResult.Status;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.config.BinTools;
 import com.google.devtools.build.lib.analysis.test.TestProvider;
@@ -32,8 +35,13 @@
 import com.google.devtools.build.lib.analysis.test.TestRunnerAction;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.clock.BlazeClock;
+import com.google.devtools.build.lib.exec.TestStrategy.TestOutputFormat;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.common.options.Options;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.List;
 import java.util.Set;
 import org.junit.Before;
@@ -42,6 +50,8 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 /** Unit tests for {@link StandaloneTestStrategy}. */
 @RunWith(JUnit4.class)
@@ -81,8 +91,7 @@
 
     // setup a test action
 
-    scratch.file(
-        "standalone/simple_test.sh", "echo \"All tests passed, you are awesome!\"", "exit 0");
+    scratch.file("standalone/simple_test.sh", "this does not get executed, it is mocked out");
 
     scratch.file(
         "standalone/BUILD",
@@ -97,6 +106,8 @@
         configuredTarget.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
     Artifact testStatusArtifact = Iterables.getOnlyElement(testStatusArtifacts);
     TestRunnerAction testRunnerAction = (TestRunnerAction) getGeneratingAction(testStatusArtifact);
+    FileSystemUtils.createDirectoryAndParents(
+        testRunnerAction.getTestLog().getPath().getParentDirectory());
 
     // setup a mock ActionExecutionContext
 
@@ -107,26 +118,188 @@
     when(actionExecutionContext.getEventHandler()).thenReturn(reporter);
     when(actionExecutionContext.getEventBus()).thenReturn(eventBus);
 
-    long expectedWallTimeMillis = 10;
     SpawnResult expectedSpawnResult =
-        new SpawnResult.Builder()
-            .setStatus(SpawnResult.Status.SUCCESS)
-            .setWallTimeMillis(expectedWallTimeMillis)
-            .build();
+        new SpawnResult.Builder().setStatus(Status.SUCCESS).setWallTimeMillis(10).build();
     when(spawnActionContext.exec(any(), any())).thenReturn(ImmutableSet.of(expectedSpawnResult));
 
     when(actionExecutionContext.getSpawnActionContext(any())).thenReturn(spawnActionContext);
 
     // actual StandaloneTestStrategy execution
-
     Set<SpawnResult> spawnResults =
         standaloneTestStrategy.exec(testRunnerAction, actionExecutionContext);
 
     // check that the rigged SpawnResult was returned
-
     assertThat(spawnResults).containsExactly(expectedSpawnResult);
-    SpawnResult spawnResult = Iterables.getOnlyElement(spawnResults);
-    assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.SUCCESS);
-    assertThat(spawnResult.getWallTimeMillis()).isEqualTo(expectedWallTimeMillis);
+  }
+
+  @Test
+  public void testThatTestLogAndOutputAreReturned() throws Exception {
+
+    // setup a StandaloneTestStrategy
+
+    ExecutionOptions executionOptions = Options.getDefaults(ExecutionOptions.class);
+    executionOptions.testOutput = TestOutputFormat.ERRORS;
+    Path tmpDirRoot = TestStrategy.getTmpRoot(rootDirectory, outputBase, executionOptions);
+    TestedStandaloneTestStrategy standaloneTestStrategy =
+        new TestedStandaloneTestStrategy(executionOptions, binTools, tmpDirRoot);
+
+    // setup a test action
+
+    scratch.file("standalone/failing_test.sh", "this does not get executed, it is mocked out");
+
+    scratch.file(
+        "standalone/BUILD",
+        "sh_test(",
+        "    name = \"failing_test\",",
+        "    size = \"small\",",
+        "    srcs = [\"failing_test.sh\"],",
+        ")");
+
+    ConfiguredTarget configuredTarget = getConfiguredTarget("//standalone:failing_test");
+    List<Artifact> testStatusArtifacts =
+        configuredTarget.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
+    Artifact testStatusArtifact = Iterables.getOnlyElement(testStatusArtifacts);
+    TestRunnerAction testRunnerAction = (TestRunnerAction) getGeneratingAction(testStatusArtifact);
+    FileSystemUtils.createDirectoryAndParents(
+        testRunnerAction.getTestLog().getPath().getParentDirectory());
+    // setup a mock ActionExecutionContext
+
+    when(actionExecutionContext.getClock()).thenReturn(BlazeClock.instance());
+    when(actionExecutionContext.withFileOutErr(any()))
+        .thenAnswer(
+            new Answer<ActionExecutionContext>() {
+              @SuppressWarnings("unchecked")
+              @Override
+              public ActionExecutionContext answer(InvocationOnMock invocation) throws Throwable {
+                FileOutErr outErr = (FileOutErr) invocation.getArguments()[0];
+                try (OutputStream stream = outErr.getOutputStream()) {
+                  stream.write("This will not appear in the test output: bla\n".getBytes(UTF_8));
+                  stream.write((TestLogHelper.HEADER_DELIMITER + "\n").getBytes(UTF_8));
+                  stream.write("This will appear in the test output: foo\n".getBytes(UTF_8));
+                }
+                return actionExecutionContext;
+              }
+            });
+    reporter.removeHandler(failFastHandler);
+    when(actionExecutionContext.getExecRoot()).thenReturn(outputBase.getRelative("execroot"));
+    when(actionExecutionContext.getClientEnv()).thenReturn(ImmutableMap.of());
+    when(actionExecutionContext.getEventHandler()).thenReturn(reporter);
+    when(actionExecutionContext.getEventBus()).thenReturn(eventBus);
+    Path outPath = tmpDirRoot.getRelative("test-out.txt");
+    Path errPath = tmpDirRoot.getRelative("test-err.txt");
+    FileOutErr outErr = new FileOutErr(outPath, errPath);
+    when(actionExecutionContext.getFileOutErr()).thenReturn(outErr);
+
+    SpawnResult expectedSpawnResult =
+        new SpawnResult.Builder().setStatus(Status.EXECUTION_FAILED).setExitCode(1).build();
+    when(spawnActionContext.exec(any(), any()))
+        .thenThrow(
+            new SpawnExecException(
+                "Failure!!",
+                expectedSpawnResult,
+                /*forciblyRunRemotely=*/ false,
+                /*catastrophe=*/ false));
+    when(actionExecutionContext.getSpawnActionContext(any())).thenReturn(spawnActionContext);
+
+    // actual StandaloneTestStrategy execution
+    Set<SpawnResult> spawnResults =
+        standaloneTestStrategy.exec(testRunnerAction, actionExecutionContext);
+
+    // check that the rigged SpawnResult was returned
+    assertThat(spawnResults).containsExactly(expectedSpawnResult);
+    // check that the test log contains all the output
+    try {
+      String logData = FileSystemUtils.readContent(testRunnerAction.getTestLog().getPath(), UTF_8);
+      assertThat(logData).contains("bla");
+      assertThat(logData).contains(TestLogHelper.HEADER_DELIMITER);
+      assertThat(logData).contains("foo");
+    } catch (IOException e) {
+      fail("Test log missing: " + testRunnerAction.getTestLog().getPath());
+    }
+    // check that the test stdout contains all the expected output
+    outErr.close(); // Create the output files.
+    try {
+      String outData = FileSystemUtils.readContent(outPath, UTF_8);
+      assertThat(outData)
+          .contains("==================== Test output for //standalone:failing_test:");
+      assertThat(outData).doesNotContain("bla");
+      assertThat(outData).doesNotContain(TestLogHelper.HEADER_DELIMITER);
+      assertThat(outData).contains("foo");
+      assertThat(outData)
+          .contains(
+              "================================================================================");
+    } catch (IOException e) {
+      fail("Test stdout file missing: " + outPath);
+    }
+    assertThat(errPath.exists()).isFalse();
+  }
+
+  @Test
+  public void testEmptyOutputCreatesEmptyLogFile() throws Exception {
+    // setup a StandaloneTestStrategy
+    ExecutionOptions executionOptions = Options.getDefaults(ExecutionOptions.class);
+    executionOptions.testOutput = TestOutputFormat.ALL;
+    Path tmpDirRoot = TestStrategy.getTmpRoot(rootDirectory, outputBase, executionOptions);
+    TestedStandaloneTestStrategy standaloneTestStrategy =
+        new TestedStandaloneTestStrategy(executionOptions, binTools, tmpDirRoot);
+
+    // setup a test action
+    scratch.file("standalone/empty_test.sh", "this does not get executed, it is mocked out");
+    scratch.file(
+        "standalone/BUILD",
+        "sh_test(",
+        "    name = \"empty_test\",",
+        "    size = \"small\",",
+        "    srcs = [\"empty_test.sh\"],",
+        ")");
+    ConfiguredTarget configuredTarget = getConfiguredTarget("//standalone:empty_test");
+    List<Artifact> testStatusArtifacts =
+        configuredTarget.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
+    Artifact testStatusArtifact = Iterables.getOnlyElement(testStatusArtifacts);
+    TestRunnerAction testRunnerAction = (TestRunnerAction) getGeneratingAction(testStatusArtifact);
+    FileSystemUtils.createDirectoryAndParents(
+        testRunnerAction.getTestLog().getPath().getParentDirectory());
+
+    // setup a mock ActionExecutionContext
+    when(actionExecutionContext.getClock()).thenReturn(BlazeClock.instance());
+    when(actionExecutionContext.withFileOutErr(any())).thenReturn(actionExecutionContext);
+    when(actionExecutionContext.getExecRoot()).thenReturn(outputBase.getRelative("execroot"));
+    when(actionExecutionContext.getClientEnv()).thenReturn(ImmutableMap.of());
+    when(actionExecutionContext.getEventHandler()).thenReturn(reporter);
+    when(actionExecutionContext.getEventBus()).thenReturn(eventBus);
+    Path outPath = tmpDirRoot.getRelative("test-out.txt");
+    Path errPath = tmpDirRoot.getRelative("test-err.txt");
+    FileOutErr outErr = new FileOutErr(outPath, errPath);
+    when(actionExecutionContext.getFileOutErr()).thenReturn(outErr);
+
+    SpawnResult expectedSpawnResult = new SpawnResult.Builder().setStatus(Status.SUCCESS).build();
+    when(spawnActionContext.exec(any(), any())).thenReturn(ImmutableSet.of(expectedSpawnResult));
+    when(actionExecutionContext.getSpawnActionContext(any())).thenReturn(spawnActionContext);
+
+    // actual StandaloneTestStrategy execution
+    Set<SpawnResult> spawnResults =
+        standaloneTestStrategy.exec(testRunnerAction, actionExecutionContext);
+
+    // check that the rigged SpawnResult was returned
+    assertThat(spawnResults).containsExactly(expectedSpawnResult);
+    // check that the test log contains all the output
+    try {
+      String logData = FileSystemUtils.readContent(testRunnerAction.getTestLog().getPath(), UTF_8);
+      assertThat(logData).isEmpty();
+    } catch (IOException e) {
+      fail("Test log missing: " + testRunnerAction.getTestLog().getPath());
+    }
+    // check that the test stdout contains all the expected output
+    outErr.close(); // Create the output files.
+    try {
+      String outData = FileSystemUtils.readContent(outPath, UTF_8);
+      String emptyOutput =
+          "==================== Test output for //standalone:empty_test:(\\s)*"
+              + "================================================================================(\\s)*";
+      assertThat(outData).matches(emptyOutput);
+    } catch (IOException e) {
+      fail("Test stdout file missing: " + outPath);
+    }
+    assertThat(errPath.exists()).isFalse();
   }
 }