blob: 9cf26998088ea394c20abe42725728cb28e5098d [file] [log] [blame]
/*
* Copyright 2014-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.test.result.groups;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.TestRule;
import com.facebook.buck.test.TestResults;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
/**
* Group TestResults together into cliques based on the graph built by following 'deps' and
* 'source_under_test' edges.
*/
public class TestResultsGrouper {
// Maps a library to a set of tests that test it.
private final SetMultimap<BuildRule, TestRule> testRulesForBuildRules = HashMultimap.create();
// Maps a test to a set of libraries it *indirectly* depends on.
private final SetMultimap<TestRule, BuildRule> allAncestorDependenciesForTestRule =
HashMultimap.create();
// Maps a test onto its results, as given to us by post().
private final Map<TestRule, TestResults> allTestResults = Maps.newHashMap();
// The set of tests we've already grouped-and-flushed.
private final Set<TestRule> flushedTestRules = Sets.newHashSet();
// The set of tests we've seen, but not yet been able to group-and-flush.
private final Set<TestRule> pendingTestRules = Sets.newHashSet();
public TestResultsGrouper(Iterable<TestRule> testRules) {
for (TestRule testRule : testRules) {
allTestResults.put(testRule, null);
ImmutableSet<BuildRule> sourcesUnderTest = testRule.getSourceUnderTest();
for (BuildRule buildRule : sourcesUnderTest) {
// We have found the (test => library) edge, so store the inverse (library => test) edge.
testRulesForBuildRules.put(buildRule, testRule);
// Store all the ancestor dependencies (but not the direct dependency).
for (BuildRule dep : getAllDependencies(buildRule)) {
if (dep != buildRule) {
allAncestorDependenciesForTestRule.put(testRule, dep);
}
}
}
}
}
/**
* Accept and enqueue a set of TestResults for the given TestRule. When enough results have been
* received that complete cliques of test-rule / test-results can be formed, they are returned.
*
* @param finishedTest The test that just finished.
* @param testResults The test's results.
* @return The TestResults (keyed by respective TestRules) which, as a result of this most recent
* test result, now form complete cliques.
*/
public synchronized Map<TestRule, TestResults> post(
TestRule finishedTest,
TestResults testResults) {
String targetName = finishedTest.getBuildTarget().getFullyQualifiedName();
Preconditions.checkState(allTestResults.containsKey(finishedTest),
"Unexpected test results found for test '%s'", targetName);
Preconditions.checkState(allTestResults.get(finishedTest) == null,
"Duplicate test results found for test '%s'", targetName);
allTestResults.put(finishedTest, testResults);
pendingTestRules.add(finishedTest);
return ImmutableMap.copyOf(flush());
}
ImmutableSet<BuildRule> getAllDependencies(BuildRule rootBuildRule) {
Set<BuildRule> allDeps = Sets.newHashSet();
Set<BuildRule> seen = Sets.newHashSet();
LinkedList<BuildRule> queue = new LinkedList<>();
queue.add(rootBuildRule);
while (!queue.isEmpty()) {
BuildRule node = queue.remove();
allDeps.add(node);
for (BuildRule child : node.getDeps()) {
if (seen.add(child)) {
queue.add(child);
}
}
}
return ImmutableSet.copyOf(allDeps);
}
private Set<TestRule> getDependentTestRules(TestRule testRule) {
ImmutableSet.Builder<TestRule> dependentTestRulesBuilder = new ImmutableSet.Builder<>();
Set<BuildRule> deps = allAncestorDependenciesForTestRule.get(testRule);
for (BuildRule dep : deps) {
// Dependencies may not necessarily have a TestRule that tests them.
//
// BuildRules: :parent ----> :child ----> :grandchild
// TestRules: ^ParentTest ^ChildTest
//
// It might be that no one wrote a test (:grandchild) or this test run wasn't asked to include
// the relevant test ("buck test :parent").
//
if (testRulesForBuildRules.containsKey(dep)) {
Set<TestRule> dependentTestRules = testRulesForBuildRules.get(dep);
dependentTestRulesBuilder.addAll(dependentTestRules);
}
}
return dependentTestRulesBuilder.build();
}
private Map<TestRule, TestResults> flush() {
Map<TestRule, TestResults> result = Maps.newHashMap();
for (TestRule pendingTestRule : pendingTestRules) {
Map<TestRule, TestResults> toAdd = flushOne(pendingTestRule);
for (TestRule flushableTestRule : toAdd.keySet()) {
if (!flushedTestRules.contains(flushableTestRule)) {
TestResults flushableTestResults = toAdd.get(flushableTestRule);
result.put(flushableTestRule, flushableTestResults);
flushedTestRules.add(flushableTestRule);
}
}
}
pendingTestRules.removeAll(flushedTestRules);
return result;
}
/**
* The returned Map may contain test results that we've already flushed.
*
* For example, if we have Parent depending on Child, with a ParentTest and ChildTest, the
* ChildTest may flush (child-results) first (it has no dependencies), and the ParentTest will
* build the set (parent-results, child-results), but we should not re-flush child-results.
*/
private Map<TestRule, TestResults> flushOne(TestRule ourTestRule) {
Map<TestRule, TestResults> result = Maps.newHashMap();
TestResults ourTestResults = Preconditions.checkNotNull(allTestResults.get(ourTestRule));
result.put(ourTestRule, ourTestResults);
int failedDepsCount = 0;
Set<TestRule> dependentTestRules = getDependentTestRules(ourTestRule);
for (TestRule dependentTestRule : dependentTestRules) {
TestResults dependentTestResults = allTestResults.get(dependentTestRule);
if (dependentTestResults == null) {
return ImmutableMap.of();
} else {
if (!dependentTestResults.isSuccess()) {
failedDepsCount++;
}
result.put(dependentTestRule, dependentTestResults);
}
}
ourTestResults.setDependenciesPassTheirTests(failedDepsCount == 0);
return result;
}
}