blob: 245177a07e4356eac1efe35008eed0075f17e3c8 [file] [log] [blame]
// Copyright 2014 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.packages;
import com.google.devtools.build.lib.util.Pair;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Utility functions over test Targets that don't really belong in the base {@link Target}
* interface.
*/
public final class TestTargetUtils {
/**
* Returns whether a test with the specified tags matches a filter (as specified by the set
* of its positive and its negative filters).
*/
public static boolean testMatchesFilters(
Collection<String> testTags,
Collection<String> requiredTags,
Collection<String> excludedTags,
boolean mustMatchAllPositive) {
for (String tag : excludedTags) {
if (testTags.contains(tag)) {
return false;
}
}
// Check required tags, if there are any.
if (requiredTags.isEmpty()) {
return true;
} else if (mustMatchAllPositive) {
// Require all tags to be present.
for (String tag : requiredTags) {
if (!testTags.contains(tag)) {
return false;
}
}
return true;
} else {
// Require at least one positive tag. If the two collections are not disjoint, then they have
// at least one element in common.
return !Collections.disjoint(requiredTags, testTags);
}
}
/**
* Decides whether to include a test in a test_suite or not.
* @param testTags Collection of all tags exhibited by a given test.
* @param requiredTags Tags declared by the suite. A Test must match ALL of these.
* @param excludedTags Tags declared by the suite. A Test must match NONE of these.
* @return false is the test is to be removed.
*/
public static boolean testMatchesFilters(
Collection<String> testTags,
Collection<String> requiredTags,
Collection<String> excludedTags) {
return testMatchesFilters(
testTags, requiredTags, excludedTags, /* mustMatchAllPositive= */ true);
}
/**
* Decides whether to include a test in a test_suite or not.
* @param testTarget A given test target.
* @param requiredTags Tags declared by the suite. A Test must match ALL of these.
* @param excludedTags Tags declared by the suite. A Test must match NONE of these.
* @return false is the test is to be removed.
*/
private static boolean testMatchesFilters(
Rule testTarget,
Collection<String> requiredTags,
Collection<String> excludedTags) {
AttributeMap nonConfigurableAttrs = NonconfigurableAttributeMapper.of(testTarget);
Set<String> testTags = new HashSet<>(nonConfigurableAttrs.get("tags", Type.STRING_LIST));
testTags.add(nonConfigurableAttrs.get("size", Type.STRING));
return testMatchesFilters(testTags, requiredTags, excludedTags);
}
/**
* Filters 'tests' (by mutation) according to the 'tags' attribute, specifically those that
* match ALL of the tags in tagsAttribute.
*
* @precondition {@code env.getAccessor().isTestSuite(testSuite)}
* @precondition {@code env.getAccessor().isTestRule(test)} for all test in tests
*/
public static void filterTests(Rule testSuite, Set<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 = sortTagsBySense(tagsAttribute);
Collection<String> positiveTags = tagLists.first;
Collection<String> negativeTags = tagLists.second;
tests.removeIf((Target t) -> !testMatchesFilters((Rule) t, positiveTags, negativeTags));
}
/**
* Separates a list of text "tags" into a Pair of Collections, where
* the first element are the required or positive tags and the second element
* are the excluded or negative tags.
* This should work on tag list provided from the command line
* --test_tags_filters flag or on tag filters explicitly declared in the
* suite.
*
* @param tagList A collection of text targets to separate.
*/
public static Pair<Collection<String>, Collection<String>> sortTagsBySense(
Iterable<String> tagList) {
Collection<String> requiredTags = new HashSet<>();
Collection<String> excludedTags = new HashSet<>();
for (String tag : tagList) {
if (tag.startsWith("-")) {
excludedTags.add(tag.substring(1));
} else if (tag.startsWith("+")) {
requiredTags.add(tag.substring(1));
} else if (tag.equals("manual")) {
// Ignore manual attribute because it is an exception: it is not a filter
// but a property of test_suite
continue;
} else {
requiredTags.add(tag);
}
}
return Pair.of(requiredTags, excludedTags);
}
}