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/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD index 166bfa5..bbca953 100644 --- a/src/test/java/com/google/devtools/build/lib/BUILD +++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -1215,6 +1215,7 @@ "//src/main/java/com/google/devtools/build/lib:bazel-rules", "//src/main/java/com/google/devtools/build/lib:build-base", "//src/main/java/com/google/devtools/build/lib:events", + "//src/main/java/com/google/devtools/build/lib:io", "//src/main/java/com/google/devtools/build/lib:util", "//src/main/java/com/google/devtools/build/lib/actions", "//src/main/java/com/google/devtools/build/lib/cmdline",
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(); } }