Add timing info for tests and correct caching vs. run ratio
Now prints:
//foo:bar (cached) PASSED in 0.1s
instead of:
//foo:bar (1/0 cached) PASSED
Fixes #218.
--
MOS_MIGRATED_REVID=105210302
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java b/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java
new file mode 100644
index 0000000..e08df35
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/runtime/TestSummaryTest.java
@@ -0,0 +1,492 @@
+// Copyright 2015 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.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.find;
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
+import com.google.devtools.build.lib.view.test.TestStatus.FailedTestCasesStatus;
+import com.google.devtools.build.lib.view.test.TestStatus.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class TestSummaryTest {
+
+ private static final String ANY_STRING = ".*?";
+ private static final String PATH = "package";
+ private static final String TARGET_NAME = "name";
+ private ConfiguredTarget stubTarget;
+ private static final List<Long> SMALL_TIMING = ImmutableList.of(1L, 2L, 3L, 4L);
+
+ private static final int CACHED = SMALL_TIMING.size();
+ private static final int NOT_CACHED = 0;
+
+ private FileSystem fs;
+ private TestSummary.Builder basicBuilder;
+
+ @Before
+ public void setUp() throws Exception {
+ fs = new InMemoryFileSystem(BlazeClock.instance());
+ stubTarget = stubTarget();
+ basicBuilder = getTemplateBuilder();
+ }
+
+ private TestSummary.Builder getTemplateBuilder() {
+ return TestSummary.newBuilder()
+ .setTarget(stubTarget)
+ .setStatus(BlazeTestStatus.PASSED)
+ .setNumCached(NOT_CACHED)
+ .setActionRan(true)
+ .setRanRemotely(false)
+ .setWasUnreportedWrongSize(false);
+ }
+
+ private List<Path> getPathList(String... names) {
+ List<Path> list = new ArrayList<>();
+ for (String name : names) {
+ list.add(fs.getPath(name));
+ }
+ return list;
+ }
+
+ @Test
+ public void testShouldProperlyTestLabels() throws Exception {
+ ConfiguredTarget target = target("somepath", "MyTarget");
+ String expectedString = ANY_STRING + "//somepath:MyTarget" + ANY_STRING;
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summaryStatus = createTestSummary(target, BlazeTestStatus.PASSED, CACHED);
+ TestSummaryPrinter.print(summaryStatus, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testShouldPrintPassedStatus() throws Exception {
+ String expectedString = ANY_STRING + "INFO" + ANY_STRING + BlazeTestStatus.PASSED + ANY_STRING;
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.PASSED, NOT_CACHED);
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testShouldPrintFailedStatus() throws Exception {
+ String expectedString = ANY_STRING + "ERROR" + ANY_STRING + BlazeTestStatus.FAILED + ANY_STRING;
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.FAILED, NOT_CACHED);
+
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testShouldPrintCachedStatus() throws Exception {
+ String expectedString = ANY_STRING + "\\(cached" + ANY_STRING;
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.PASSED, CACHED);
+
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testPartialCachedStatus() throws Exception {
+ String expectedString = ANY_STRING + "\\(3/4 cached" + ANY_STRING;
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.PASSED, CACHED - 1);
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testIncompleteCached() throws Exception {
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.INCOMPLETE, CACHED - 1);
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ verify(terminalPrinter).print(not(contains("cached")));
+ }
+
+ @Test
+ public void testShouldPrintUncachedStatus() throws Exception {
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.PASSED, NOT_CACHED);
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ verify(terminalPrinter).print(not(contains("cached")));
+ }
+
+ @Test
+ public void testNoTiming() throws Exception {
+ String expectedString = ANY_STRING + "INFO" + ANY_STRING + BlazeTestStatus.PASSED;
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = createTestSummary(stubTarget, BlazeTestStatus.PASSED, NOT_CACHED);
+
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testBuilder() throws Exception {
+ // No need to copy if built twice in a row; no direct setters on the object.
+ TestSummary summary = basicBuilder.build();
+ TestSummary sameSummary = basicBuilder.build();
+ assertSame(summary, sameSummary);
+
+ basicBuilder.addTestTimes(ImmutableList.of(40L));
+
+ TestSummary summaryCopy = basicBuilder.build();
+ assertEquals(summary.getTarget(), summaryCopy.getTarget());
+ assertEquals(summary.getStatus(), summaryCopy.getStatus());
+ assertEquals(summary.numCached(), summaryCopy.numCached());
+ assertNotSame(summary, summaryCopy);
+ assertEquals(0, summary.totalRuns());
+ assertEquals(1, summaryCopy.totalRuns());
+
+ // Check that the builder can add a new warning to the copy,
+ // despite the immutability of the original.
+ basicBuilder.addTestTimes(ImmutableList.of(60L));
+
+ TestSummary fiftyCached = basicBuilder.setNumCached(50).build();
+ assertEquals(summary.getStatus(), fiftyCached.getStatus());
+ assertEquals(50, fiftyCached.numCached());
+ assertEquals(2, fiftyCached.totalRuns());
+
+ TestSummary sixtyCached = basicBuilder.setNumCached(60).build();
+ assertEquals(60, sixtyCached.numCached());
+ assertEquals(50, fiftyCached.numCached());
+
+ TestSummary failedCacheTemplate = TestSummary.newBuilderFromExisting(fiftyCached)
+ .setStatus(BlazeTestStatus.FAILED)
+ .build();
+ assertEquals(50, failedCacheTemplate.numCached());
+ assertEquals(BlazeTestStatus.FAILED, failedCacheTemplate.getStatus());
+ }
+
+ @Test
+ public void testSingleTime() throws Exception {
+ String expectedString = ANY_STRING + "INFO" + ANY_STRING + BlazeTestStatus.PASSED + ANY_STRING +
+ "in 3.4s";
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = basicBuilder.addTestTimes(ImmutableList.of(3412L)).build();
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testNoTime() throws Exception {
+ // The last part matches anything not containing "in".
+ String expectedString = ANY_STRING + "INFO" + ANY_STRING + BlazeTestStatus.PASSED + "(?!in)*";
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = basicBuilder.addTestTimes(ImmutableList.of(3412L)).build();
+ TestSummaryPrinter.print(summary, terminalPrinter, false, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testMultipleTimes() throws Exception {
+ String expectedString = ANY_STRING + "INFO" + ANY_STRING + BlazeTestStatus.PASSED + ANY_STRING +
+ "\n Stats over 3 runs: max = 3.0s, min = 1.0s, " +
+ "avg = 2.0s, dev = 0.8s";
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummary summary = basicBuilder
+ .addTestTimes(ImmutableList.of(1000L, 2000L, 3000L))
+ .build();
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testCoverageDataReferences() throws Exception {
+ List<Path> paths = getPathList("/cov1.dat", "/cov2.dat", "/cov3.dat", "/cov4.dat");
+ FileSystemUtils.writeContentAsLatin1(paths.get(1), "something");
+ FileSystemUtils.writeContentAsLatin1(paths.get(3), "");
+ FileSystemUtils.writeContentAsLatin1(paths.get(3), "something else");
+ TestSummary summary = basicBuilder.addCoverageFiles(paths).build();
+
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ verify(terminalPrinter).print(find(ANY_STRING + "INFO" + ANY_STRING + BlazeTestStatus.PASSED));
+ verify(terminalPrinter).print(find(" /cov2.dat"));
+ verify(terminalPrinter).print(find(" /cov4.dat"));
+ }
+
+ @Test
+ public void testFlakyAttempts() throws Exception {
+ String expectedString = ANY_STRING + "WARNING" + ANY_STRING + BlazeTestStatus.FLAKY +
+ ANY_STRING + ", failed in 2 out of 3";
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = basicBuilder
+ .setStatus(BlazeTestStatus.FLAKY)
+ .addPassedLogs(getPathList("/a"))
+ .addFailedLogs(getPathList("/b", "/c"))
+ .build();
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testNumberOfFailedRuns() throws Exception {
+ String expectedString = ANY_STRING + "ERROR" + ANY_STRING + BlazeTestStatus.FAILED +
+ ANY_STRING + "in 2 out of 3";
+ AnsiTerminalPrinter terminalPrinter = Mockito.mock(AnsiTerminalPrinter.class);
+
+ TestSummary summary = basicBuilder
+ .setStatus(BlazeTestStatus.FAILED)
+ .addPassedLogs(getPathList("/a"))
+ .addFailedLogs(getPathList("/b", "/c"))
+ .build();
+ TestSummaryPrinter.print(summary, terminalPrinter, true, false);
+ terminalPrinter.print(find(expectedString));
+ }
+
+ @Test
+ public void testFileNamesNotShown() throws Exception {
+ List<TestCase> emptyDetails = ImmutableList.of();
+ TestSummary summary = basicBuilder
+ .setStatus(BlazeTestStatus.FAILED)
+ .addPassedLogs(getPathList("/apple"))
+ .addFailedLogs(getPathList("/pear"))
+ .addCoverageFiles(getPathList("/maracuja"))
+ .addFailedTestCases(emptyDetails, FailedTestCasesStatus.FULL)
+ .build();
+
+ // Check that only //package:name is printed.
+ AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summary, printer, true, true);
+ verify(printer).print(contains("//package:name"));
+ }
+
+ @Test
+ public void testMessageShownWhenTestCasesMissing() throws Exception {
+ ImmutableList<TestCase> emptyList = ImmutableList.of();
+ TestSummary summary = createTestSummaryWithDetails(
+ BlazeTestStatus.FAILED, emptyList, FailedTestCasesStatus.NOT_AVAILABLE);
+
+ AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summary, printer, true, true);
+ verify(printer).print(contains("//package:name"));
+ verify(printer).print(contains("not available"));
+ }
+
+ @Test
+ public void testMessageShownForPartialResults() throws Exception {
+ ImmutableList<TestCase> testCases =
+ ImmutableList.of(newDetail("orange", TestCase.Status.FAILED, 1500L));
+ TestSummary summary = createTestSummaryWithDetails(BlazeTestStatus.FAILED, testCases,
+ FailedTestCasesStatus.PARTIAL);
+
+ AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summary, printer, true, true);
+ verify(printer).print(contains("//package:name"));
+ verify(printer).print(find("FAILED.*orange"));
+ verify(printer).print(contains("incomplete"));
+ }
+
+ private TestCase newDetail(String name, TestCase.Status status, long duration) {
+ return TestCase.newBuilder()
+ .setName(name)
+ .setStatus(status)
+ .setRunDurationMillis(duration).build();
+ }
+
+ @Test
+ public void testTestCaseNamesShownWhenNeeded() throws Exception {
+ TestCase detailPassed =
+ newDetail("strawberry", TestCase.Status.PASSED, 1000L);
+ TestCase detailFailed =
+ newDetail("orange", TestCase.Status.FAILED, 1500L);
+
+ TestSummary summaryPassed = createTestSummaryWithDetails(
+ BlazeTestStatus.PASSED, Arrays.asList(detailPassed));
+
+ TestSummary summaryFailed = createTestSummaryWithDetails(
+ BlazeTestStatus.FAILED, Arrays.asList(detailPassed, detailFailed));
+ assertEquals(BlazeTestStatus.FAILED, summaryFailed.getStatus());
+
+ AnsiTerminalPrinter printerPassed = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summaryPassed, printerPassed, true, true);
+ verify(printerPassed).print(contains("//package:name"));
+
+ AnsiTerminalPrinter printerFailed = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summaryFailed, printerFailed, true, true);
+ verify(printerFailed).print(contains("//package:name"));
+ verify(printerFailed).print(find("FAILED.*orange *\\(1\\.5"));
+ }
+
+ @Test
+ public void testTestCaseNamesOrdered() throws Exception {
+ TestCase[] details = {
+ newDetail("apple", TestCase.Status.FAILED, 1000L),
+ newDetail("banana", TestCase.Status.FAILED, 1000L),
+ newDetail("cranberry", TestCase.Status.FAILED, 1000L)
+ };
+
+ // The exceedingly dumb approach: writing all the permutations down manually
+ // is simply easier than any way of generating them.
+ int[][] permutations = {
+ { 0, 1, 2 },
+ { 0, 2, 1 },
+ { 1, 0, 2 },
+ { 1, 2, 0 },
+ { 2, 0, 1 },
+ { 2, 1, 0 }
+ };
+
+ for (int[] permutation : permutations) {
+ List<TestCase> permutatedDetails = new ArrayList<>();
+
+ for (int element : permutation) {
+ permutatedDetails.add(details[element]);
+ }
+
+ TestSummary summary = createTestSummaryWithDetails(BlazeTestStatus.FAILED, permutatedDetails);
+
+ // A mock that checks the ordering of method calls
+ AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summary, printer, true, true);
+ InOrder order = Mockito.inOrder(printer);
+ order.verify(printer).print(contains("//package:name"));
+ order.verify(printer).print(find("FAILED.*apple"));
+ order.verify(printer).print(find("FAILED.*banana"));
+ order.verify(printer).print(find("FAILED.*cranberry"));
+ }
+ }
+
+ @Test
+ public void testCachedResultsFirstInSort() throws Exception {
+ TestSummary summaryFailedCached = createTestSummary(BlazeTestStatus.FAILED, CACHED);
+ TestSummary summaryFailedNotCached = createTestSummary(BlazeTestStatus.FAILED, NOT_CACHED);
+ TestSummary summaryPassedCached = createTestSummary(BlazeTestStatus.PASSED, CACHED);
+ TestSummary summaryPassedNotCached = createTestSummary(BlazeTestStatus.PASSED, NOT_CACHED);
+
+ // This way we can make the test independent from the sort order of FAILEd
+ // and PASSED.
+
+ assertTrue(summaryFailedCached.compareTo(summaryPassedNotCached) < 0);
+ assertTrue(summaryPassedCached.compareTo(summaryFailedNotCached) < 0);
+ }
+
+ @Test
+ public void testCollectingFailedDetails() throws Exception {
+ TestCase rootCase = TestCase.newBuilder()
+ .setName("tests")
+ .setRunDurationMillis(5000L)
+ .addChild(newDetail("apple", TestCase.Status.FAILED, 1000L))
+ .addChild(newDetail("banana", TestCase.Status.PASSED, 1000L))
+ .addChild(newDetail("cherry", TestCase.Status.ERROR, 1000L))
+ .build();
+
+ TestSummary summary = getTemplateBuilder()
+ .collectFailedTests(rootCase)
+ .setStatus(BlazeTestStatus.FAILED)
+ .build();
+
+ AnsiTerminalPrinter printer = Mockito.mock(AnsiTerminalPrinter.class);
+ TestSummaryPrinter.print(summary, printer, true, true);
+ verify(printer).print(contains("//package:name"));
+ verify(printer).print(find("FAILED.*apple"));
+ verify(printer).print(find("ERROR.*cherry"));
+ }
+
+ private ConfiguredTarget target(String path, String targetName) throws Exception {
+ ConfiguredTarget target = Mockito.mock(ConfiguredTarget.class);
+ when(target.getLabel()).thenReturn(Label.create(path, targetName));
+ return target;
+ }
+
+ private ConfiguredTarget stubTarget() throws Exception {
+ return target(PATH, TARGET_NAME);
+ }
+
+ private TestSummary createTestSummaryWithDetails(BlazeTestStatus status,
+ List<TestCase> details) {
+ TestSummary summary = getTemplateBuilder()
+ .setStatus(status)
+ .addFailedTestCases(details, FailedTestCasesStatus.FULL)
+ .build();
+ return summary;
+ }
+
+ private TestSummary createTestSummaryWithDetails(
+ BlazeTestStatus status, List<TestCase> testCaseList,
+ FailedTestCasesStatus detailsStatus) {
+ TestSummary summary = getTemplateBuilder()
+ .setStatus(status)
+ .addFailedTestCases(testCaseList, detailsStatus)
+ .build();
+ return summary;
+ }
+
+ private static TestSummary createTestSummary(ConfiguredTarget target, BlazeTestStatus status,
+ int numCached) {
+ ImmutableList<TestCase> emptyList = ImmutableList.of();
+ TestSummary summary = TestSummary.newBuilder()
+ .setTarget(target)
+ .setStatus(status)
+ .setNumCached(numCached)
+ .setActionRan(true)
+ .setRanRemotely(false)
+ .setWasUnreportedWrongSize(false)
+ .addFailedTestCases(emptyList, FailedTestCasesStatus.FULL)
+ .addTestTimes(SMALL_TIMING)
+ .build();
+ return summary;
+ }
+
+ private TestSummary createTestSummary(BlazeTestStatus status, int numCached) {
+ TestSummary summary = getTemplateBuilder()
+ .setStatus(status)
+ .setNumCached(numCached)
+ .addTestTimes(SMALL_TIMING)
+ .build();
+ return summary;
+ }
+}