aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/buildtool/InstrumentationFilterSupport.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/buildtool/InstrumentationFilterSupport.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/buildtool/InstrumentationFilterSupport.java150
1 files changed, 150 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/InstrumentationFilterSupport.java b/src/main/java/com/google/devtools/build/lib/buildtool/InstrumentationFilterSupport.java
new file mode 100644
index 0000000000..5781e0272b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/InstrumentationFilterSupport.java
@@ -0,0 +1,150 @@
+// Copyright 2017 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.buildtool;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
+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.vfs.PathFragment;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.SortedSet;
+
+/**
+ * Helper class to heuristically compute an instrumentation filter from a list of tests to run.
+ */
+public final class InstrumentationFilterSupport {
+ public static final String INSTRUMENTATION_FILTER_FLAG = "instrumentation_filter";
+
+ /**
+ * Method implements a heuristic used to set default value of the
+ * --instrumentation_filter option. Following algorithm is used:
+ * 1) Identify all test targets on the command line.
+ * 2) Expand all test suites into the individual test targets
+ * 3) Calculate list of package names containing all test targets above.
+ * 4) Replace all "javatests/" substrings in package names with "java/".
+ * 5) If two packages reside in the same directory, use filter based on
+ * the parent directory name instead. Doing so significantly simplifies
+ * instrumentation filter in majority of real-life scenarios (in
+ * particular when dealing with my/package/... wildcards).
+ * 6) Set --instrumentation_filter default value to instrument everything
+ * in those packages.
+ */
+ @VisibleForTesting
+ public static String computeInstrumentationFilter(
+ EventHandler eventHandler, Collection<Target> testTargets) {
+ SortedSet<String> packageFilters = Sets.newTreeSet();
+ collectInstrumentedPackages(testTargets, packageFilters);
+ optimizeFilterSet(packageFilters);
+
+ String instrumentationFilter = "//" + Joiner.on(",//").join(packageFilters);
+ if (!packageFilters.isEmpty()) {
+ eventHandler.handle(
+ Event.info("Using default value for --instrumentation_filter: \""
+ + instrumentationFilter + "\"."));
+ eventHandler.handle(Event.info("Override the above default with --"
+ + INSTRUMENTATION_FILTER_FLAG));
+ }
+ return instrumentationFilter;
+ }
+
+ private static void collectInstrumentedPackages(
+ Collection<Target> targets, Set<String> packageFilters) {
+ for (Target target : targets) {
+ // Add package-based filters for every test target.
+ String prefix = getInstrumentedPrefix(target.getLabel().getPackageName());
+ if (!prefix.isEmpty()) {
+ packageFilters.add(prefix);
+ }
+ if (TargetUtils.isTestSuiteRule(target)) {
+ AttributeMap attributes = NonconfigurableAttributeMapper.of((Rule) target);
+ // We don't need to handle $implicit_tests attribute since we already added
+ // test_suite package to the set.
+ for (Label label : attributes.get("tests", BuildType.LABEL_LIST)) {
+ // Add package-based filters for all tests in the test suite.
+ packageFilters.add(getInstrumentedPrefix(label.getPackageName()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns prefix string that should be instrumented for a given package. Input string should
+ * be formatted like the output of Label.getPackageName().
+ * Generally, package name will be used as such string with two modifications.
+ * - "javatests/ directories will be substituted with "java/", since we do
+ * not want to instrument java test code. "java/" directories in "test/" will
+ * be replaced by the same in "main/".
+ * - "/internal", "/public", and "tests/" package suffix will be dropped, since usually we would
+ * want to instrument code in the parent package as well
+ */
+ @VisibleForTesting
+ public static String getInstrumentedPrefix(String packageName) {
+ if (packageName.endsWith("/internal")) {
+ packageName = packageName.substring(0, packageName.length() - "/internal".length());
+ } else if (packageName.endsWith("/public")) {
+ packageName = packageName.substring(0, packageName.length() - "/public".length());
+ } else if (packageName.endsWith("/tests")) {
+ packageName = packageName.substring(0, packageName.length() - "/tests".length());
+ }
+ return packageName
+ .replaceFirst("(?<=^|/)javatests/", "java/")
+ .replaceFirst("(?<=^|/)test/java/", "main/java/");
+ }
+
+ private static void optimizeFilterSet(SortedSet<String> packageFilters) {
+ Iterator<String> iterator = packageFilters.iterator();
+ if (iterator.hasNext()) {
+ // Find common parent filters to reduce number of filter expressions. In practice this
+ // still produces nicely constrained instrumentation filter while making final
+ // filter value much more user-friendly - especially in case of /my/package/... wildcards.
+ Set<String> parentFilters = Sets.newTreeSet();
+ String filterString = iterator.next();
+ PathFragment parent = PathFragment.create(filterString).getParentDirectory();
+ while (iterator.hasNext()) {
+ String current = iterator.next();
+ if (parent != null && parent.getPathString().length() > 0
+ && !current.startsWith(filterString) && current.startsWith(parent.getPathString())) {
+ parentFilters.add(parent.getPathString());
+ } else {
+ filterString = current;
+ parent = PathFragment.create(filterString).getParentDirectory();
+ }
+ }
+ packageFilters.addAll(parentFilters);
+
+ // Optimize away nested filters.
+ iterator = packageFilters.iterator();
+ String prev = iterator.next();
+ while (iterator.hasNext()) {
+ String current = iterator.next();
+ if (current.startsWith(prev)) {
+ iterator.remove();
+ } else {
+ prev = current;
+ }
+ }
+ }
+ }
+}