aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java
diff options
context:
space:
mode:
authorGravatar Ulf Adams <ulfjack@google.com>2015-10-14 10:08:25 +0000
committerGravatar David Chen <dzc@google.com>2015-10-14 18:29:19 +0000
commitd6fce4428db80f8e5d369581baea415e202cfe62 (patch)
tree2307e2312857d082593a658b7c7bd3988145719c /src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java
parent17f11ebecacad00868d5e311254edb147daf156f (diff)
Reimplement target pattern parsing in Skyframe.
This is currently not hooked up, and we're passing (potentially) massive numbers of targets around. -- MOS_MIGRATED_REVID=105395404
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java229
1 files changed, 229 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java
new file mode 100644
index 0000000000..3c559de383
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestsInSuiteFunction.java
@@ -0,0 +1,229 @@
+// 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.skyframe;
+
+import com.google.common.base.Predicate;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.cmdline.ResolvedTargets;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.packages.TestTargetUtils;
+import com.google.devtools.build.lib.skyframe.TestsInSuiteValue.TestsInSuite;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.ValueOrException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * TestsInSuiteFunction takes a single test_suite target and expands all of the tests it contains,
+ * possibly recursively.
+ */
+// TODO(ulfjack): What about test_suite rules that include each other.
+final class TestsInSuiteFunction implements SkyFunction {
+ @Override
+ public SkyValue compute(SkyKey key, Environment env) {
+ TestsInSuite expansion = (TestsInSuite) key.argument();
+ ResolvedTargets<Target> result =
+ computeTestsInSuite(env, expansion.getTestSuite(), expansion.isStrict());
+ if (env.valuesMissing()) {
+ return null;
+ }
+ return new TestsInSuiteValue(result);
+ }
+
+ /**
+ * Populates 'result' with all the tests associated with the specified
+ * 'testSuite'. Throws an exception if any target is missing.
+ *
+ * <p>CAUTION! Keep this logic consistent with {@code TestSuite}!
+ */
+ private ResolvedTargets<Target> computeTestsInSuite(
+ Environment env, Rule testSuite, boolean strict) {
+ ResolvedTargets.Builder<Target> builder = ResolvedTargets.builder();
+ List<Target> testsAndSuites = new ArrayList<>();
+ // Note that testsAndSuites can contain input file targets; the test_suite rule does not
+ // restrict the set of targets that can appear in tests or suites.
+ builder.mergeError(getPrerequisites(env, testSuite, "tests", testsAndSuites));
+ if (testSuite.getRuleClassObject().hasAttr("suites", BuildType.LABEL_LIST)) {
+ builder.mergeError(getPrerequisites(env, testSuite, "suites", testsAndSuites));
+ }
+
+ // 1. Add all tests
+ for (Target test : testsAndSuites) {
+ if (TargetUtils.isTestRule(test)) {
+ builder.add(test);
+ } else if (strict && !TargetUtils.isTestSuiteRule(test)) {
+ // If strict mode is enabled, then give an error for any non-test, non-test-suite targets.
+ // TODO(ulfjack): We need to throw to end the process if we happen to be in --nokeep_going,
+ // but we can't know whether or not we are at this point.
+ env.getListener().handle(Event.error(testSuite.getLocation(),
+ "in test_suite rule '" + testSuite.getLabel()
+ + "': expecting a test or a test_suite rule but '" + test.getLabel()
+ + "' is not one."));
+ builder.setError();
+ }
+ }
+
+ // 2. Add implicit dependencies on tests in same package, if any.
+ List<Target> implicitTests = new ArrayList<>();
+ builder.mergeError(getPrerequisites(env, testSuite, "$implicit_tests", implicitTests));
+ for (Target target : implicitTests) {
+ // The Package construction of $implicit_tests ensures that this check never fails, but we
+ // add it here anyway for compatibility with future code.
+ if (TargetUtils.isTestRule(target)) {
+ builder.add(target);
+ }
+ }
+
+ // 3. Filter based on tags, size, env.
+ filterTests(testSuite, builder);
+
+ // 4. Expand all suites recursively.
+ for (Target suite : testsAndSuites) {
+ if (TargetUtils.isTestSuiteRule(suite)) {
+ TestsInSuiteValue value =
+ (TestsInSuiteValue) env.getValue(TestsInSuiteValue.key(suite, strict));
+ builder.merge(value.getTargets());
+ }
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Adds the set of targets found in the attribute named {@code attrName}, which must be of label
+ * list type, of the {@code test_suite} rule named {@code testSuite}. Returns true if the method
+ * found a problem during the lookup process; the actual error message is reported to the
+ * environment.
+ */
+ private boolean getPrerequisites(Environment env, Rule testSuite, String attrName,
+ List<Target> targets) {
+ List<Label> labels =
+ NonconfigurableAttributeMapper.of(testSuite).get(attrName, BuildType.LABEL_LIST);
+ Set<PackageIdentifier> pkgIdentifiers = new LinkedHashSet<>();
+ for (Label label : labels) {
+ pkgIdentifiers.add(label.getPackageIdentifier());
+ }
+ Map<SkyKey, ValueOrException<BuildFileNotFoundException>> packages = env.getValuesOrThrow(
+ PackageValue.keys(pkgIdentifiers), BuildFileNotFoundException.class);
+ if (env.valuesMissing()) {
+ return false;
+ }
+ boolean hasError = false;
+ Map<PackageIdentifier, Package> packageMap = new HashMap<>();
+ for (Entry<SkyKey, ValueOrException<BuildFileNotFoundException>> entry : packages.entrySet()) {
+ try {
+ packageMap.put(
+ (PackageIdentifier) entry.getKey().argument(),
+ ((PackageValue) entry.getValue().get()).getPackage());
+ } catch (BuildFileNotFoundException e) {
+ env.getListener().handle(Event.error(e.getMessage()));
+ hasError = true;
+ }
+ }
+
+ for (Label label : labels) {
+ Package pkg = packageMap.get(label.getPackageIdentifier());
+ if (pkg == null) {
+ continue;
+ }
+ try {
+ targets.add(pkg.getTarget(label.getName()));
+ } catch (NoSuchTargetException e) {
+ env.getListener().handle(Event.error(e.getMessage()));
+ hasError = true;
+ }
+ }
+ return hasError;
+ }
+
+ /**
+ * Filters 'tests' (by mutation) according to the 'tags' attribute, specifically those that
+ * match ALL of the tags in tagsAttribute.
+ */
+ private static void filterTests(Rule testSuite, ResolvedTargets.Builder<Target> tests) {
+ List<String> tagsAttribute =
+ NonconfigurableAttributeMapper.of(testSuite).get("tags", Type.STRING_LIST);
+ // Split the tags list into positive and negative tags.
+ Pair<Collection<String>, Collection<String>> tagLists =
+ TestTargetUtils.sortTagsBySense(tagsAttribute);
+ final Collection<String> positiveTags = tagLists.first;
+ final Collection<String> negativeTags = tagLists.second;
+
+ tests.filter(new Predicate<Target>() {
+ @Override
+ public boolean apply(@Nullable Target input) {
+ Rule test = (Rule) input;
+ AttributeMap nonConfigurableAttributes = NonconfigurableAttributeMapper.of(test);
+ List<String> testTags =
+ new ArrayList<>(nonConfigurableAttributes.get("tags", Type.STRING_LIST));
+ testTags.add(nonConfigurableAttributes.get("size", Type.STRING));
+ return includeTest(testTags, positiveTags, negativeTags);
+ }
+ });
+ }
+
+ /**
+ * Decides whether to include a test in a test_suite or not.
+ *
+ * @param testTags Collection of all tags exhibited by a given test.
+ * @param positiveTags Tags declared by the suite. A Test must match ALL of these.
+ * @param negativeTags Tags declared by the suite. A Test must match NONE of these.
+ * @return false if the test is to be removed.
+ */
+ private static boolean includeTest(Collection<String> testTags,
+ Collection<String> positiveTags, Collection<String> negativeTags) {
+ // Add this test if it matches ALL of the positive tags and NONE of the
+ // negative tags in the tags attribute.
+ for (String tag : negativeTags) {
+ if (testTags.contains(tag)) {
+ return false;
+ }
+ }
+ for (String tag : positiveTags) {
+ if (!testTags.contains(tag)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public String extractTag(SkyKey skyKey) {
+ return null;
+ }
+}