Distinguish between cached and non-cached test runs.
Summary:
Display in the results if a test was read from the cached results or not.
Test Plan:
CachedTestRunReportingIntegrationTest is green.
diff --git a/src/com/facebook/buck/cli/TestCommand.java b/src/com/facebook/buck/cli/TestCommand.java
index 0a72362..2f4a07e 100644
--- a/src/com/facebook/buck/cli/TestCommand.java
+++ b/src/com/facebook/buck/cli/TestCommand.java
@@ -55,6 +55,7 @@
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
@@ -75,6 +76,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import javax.xml.parsers.DocumentBuilder;
@@ -456,7 +458,9 @@
// because the rule is cached, but its results must still be processed.
ListenableFuture<TestResults> testResults =
stepRunner.runStepsAndYieldResult(steps,
- test.interpretTestResults(executionContext),
+ getCachingStatusTransformingCallable(
+ isTestRunRequired,
+ test.interpretTestResults(executionContext)),
test.getBuildTarget());
Futures.addCallback(testResults, onTestFinishedCallback);
results.add(testResults);
@@ -510,6 +514,27 @@
return failures ? 1 : 0;
}
+ private Callable<TestResults> getCachingStatusTransformingCallable(
+ boolean isTestRunRequired,
+ final Callable<TestResults> originalCallable) {
+ if (isTestRunRequired) {
+ return originalCallable;
+ }
+ return new Callable<TestResults>() {
+ public TestResults call() throws Exception {
+ TestResults originalTestResults = originalCallable.call();
+ ImmutableList<TestCaseSummary> cachedTestResults = FluentIterable
+ .from(originalTestResults.getTestCases())
+ .transform(TestCaseSummary.TO_CACHED_TRANSFORMATION)
+ .toList();
+ return new TestResults(
+ originalTestResults.getBuildTarget(),
+ cachedTestResults,
+ originalTestResults.getContacts());
+ }
+ };
+ }
+
@VisibleForTesting
static boolean isTestRunRequiredForTest(
TestRule test,
diff --git a/src/com/facebook/buck/test/TestCaseSummary.java b/src/com/facebook/buck/test/TestCaseSummary.java
index 37b96b1..83a185a 100644
--- a/src/com/facebook/buck/test/TestCaseSummary.java
+++ b/src/com/facebook/buck/test/TestCaseSummary.java
@@ -18,6 +18,7 @@
import com.facebook.buck.util.Ansi;
import com.facebook.buck.util.TimeFormat;
+import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
@@ -28,12 +29,28 @@
@Immutable
public class TestCaseSummary {
+ /**
+ * Transformation to annotate TestCaseSummary marking them as being read from cached results
+ */
+ public final static Function<TestCaseSummary, TestCaseSummary> TO_CACHED_TRANSFORMATION =
+ new Function<TestCaseSummary, TestCaseSummary>() {
+
+ @Override
+ public TestCaseSummary apply(TestCaseSummary summary) {
+ return new TestCaseSummary(summary, /* isCached */ true);
+ }
+ };
+
private final String testCaseName;
private final ImmutableList<TestResultSummary> testResults;
private final boolean isSuccess;
private final int failureCount;
private final long totalTime;
+ private final boolean isCached;
+ /**
+ * Creates a TestCaseSummary which is assumed to be not read from cached results
+ */
public TestCaseSummary(String testCaseName, List<TestResultSummary> testResults) {
this.testCaseName = Preconditions.checkNotNull(testCaseName);
this.testResults = ImmutableList.copyOf(testResults);
@@ -51,6 +68,18 @@
this.isSuccess = isSuccess;
this.failureCount = failureCount;
this.totalTime = totalTime;
+ this.isCached = false;
+ }
+
+ /** Creates a copy of {@code summary} with the specified value of {@code isCached}. */
+ private TestCaseSummary(TestCaseSummary summary, boolean isCached) {
+ Preconditions.checkNotNull(summary);
+ this.testCaseName = summary.testCaseName;
+ this.testResults = summary.testResults;
+ this.isSuccess = summary.isSuccess;
+ this.failureCount = summary.failureCount;
+ this.totalTime = summary.totalTime;
+ this.isCached = isCached;
}
public boolean isSuccess() {
@@ -71,7 +100,8 @@
String status = ansi.asHighlightedStatusText(isSuccess(), isSuccess() ? "PASS" : "FAIL");
return String.format("%s %s %2d Passed %2d Failed %s",
status,
- TimeFormat.formatForConsole(totalTime, ansi),
+ !isCached ? TimeFormat.formatForConsole(totalTime, ansi)
+ : ansi.asHighlightedStatusText(isSuccess(), "CACHED"),
testResults.size() - failureCount,
failureCount,
testCaseName);
diff --git a/test/com/facebook/buck/java/CachedTestRunReportingIntegrationTest.java b/test/com/facebook/buck/java/CachedTestRunReportingIntegrationTest.java
new file mode 100644
index 0000000..5623e5e
--- /dev/null
+++ b/test/com/facebook/buck/java/CachedTestRunReportingIntegrationTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.java;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.facebook.buck.testutil.integration.DebuggableTemporaryFolder;
+import com.facebook.buck.testutil.integration.ProjectWorkspace;
+import com.facebook.buck.testutil.integration.TestDataHelper;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+public class CachedTestRunReportingIntegrationTest {
+
+ private final static Charset CHARSET_FOR_TEST = Charsets.UTF_8;
+
+ @Rule
+ public DebuggableTemporaryFolder tmp = new DebuggableTemporaryFolder();
+
+ /**
+ * Test that we correctly report which test runs are cached.
+ */
+ @Test
+ public void testCachedTestRun() throws IOException {
+ tmp.delete();
+ ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(
+ this, "cached_test", tmp);
+ workspace.setUp();
+
+ // No caching for this run.
+ assertFalse(
+ "There should not be any caching for the initial test run.",
+ isTestRunCached(workspace, true));
+
+ // Cached Results.
+ assertTrue(
+ "A second test run without any modifications should be cached.",
+ isTestRunCached(workspace, true));
+
+ // Make the test fail.
+ File testFile = workspace.getFile("LameTest.java");
+ String originalJavaCode = Files.toString(testFile, CHARSET_FOR_TEST);
+ String failingJavaCode = originalJavaCode.replace("String str = \"I am not null.\";",
+ "String str = null;");
+ Files.write(failingJavaCode, testFile, CHARSET_FOR_TEST);
+
+ // No caching for this run.
+ assertFalse(
+ "There should not be any caching for this test run.",
+ isTestRunCached(workspace, false));
+
+ // Cached Results.
+ assertTrue(
+ "A second test run without any modifications should be cached.",
+ isTestRunCached(workspace, false));
+ }
+
+ private boolean isTestRunCached(ProjectWorkspace workspace, boolean expectSuccess)
+ throws IOException {
+ ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("test", "//:test");
+ workspace.verify();
+ result.assertExitCode(expectSuccess ? 0 : 1);
+ // Test that Test status is reported
+ assertTrue(result.getStderr().contains("com.example.LameTest"));
+ String status = expectSuccess ? "PASS CACHED" : "FAIL CACHED";
+ return result.getStderr().contains(status);
+ }
+}