aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java')
-rw-r--r--src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java440
1 files changed, 440 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
new file mode 100644
index 0000000000..2f601d4fde
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
@@ -0,0 +1,440 @@
+// Copyright 2015 Google Inc. 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.actions.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.AbstractActionOwner;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionGraph;
+import com.google.devtools.build.lib.actions.ActionInputHelper;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.MutableActionGraph;
+import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.actions.cache.MetadataHandler;
+import com.google.devtools.build.lib.exec.SingleBuildFileCache;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A bunch of utilities that are useful for test concerning actions, artifacts,
+ * etc.
+ */
+public final class ActionsTestUtil {
+
+ private final ActionGraph actionGraph;
+
+ public ActionsTestUtil(ActionGraph actionGraph) {
+ this.actionGraph = actionGraph;
+ }
+
+ private static final Label NULL_LABEL = Label.parseAbsoluteUnchecked("//null/action:owner");
+
+ public static ActionExecutionContext createContext(Executor executor, FileOutErr fileOutErr,
+ Path execRoot, MetadataHandler metadataHandler, @Nullable ActionGraph actionGraph) {
+ return new ActionExecutionContext(
+ executor,
+ new SingleBuildFileCache(execRoot.getPathString(), execRoot.getFileSystem()),
+ metadataHandler, fileOutErr,
+ actionGraph == null
+ ? null
+ : ActionInputHelper.actionGraphMiddlemanExpander(actionGraph));
+ }
+
+ /**
+ * A dummy ActionOwner implementation for use in tests.
+ */
+ public static class NullActionOwner extends AbstractActionOwner {
+ @Override
+ public Label getLabel() {
+ return NULL_LABEL;
+ }
+
+ @Override
+ public final String getConfigurationName() {
+ return "dummy-configuration";
+ }
+
+ @Override
+ public String getConfigurationMnemonic() {
+ return "dummy-configuration-mnemonic";
+ }
+
+ @Override
+ public final String getConfigurationShortCacheKey() {
+ return "dummy-configuration";
+ }
+ }
+
+ public static final Artifact DUMMY_ARTIFACT = new Artifact(
+ new PathFragment("dummy"),
+ Root.asSourceRoot(new InMemoryFileSystem().getRootDirectory()));
+
+ public static final ActionOwner NULL_ACTION_OWNER = new NullActionOwner();
+
+ public static final ArtifactOwner NULL_ARTIFACT_OWNER =
+ new ArtifactOwner() {
+ @Override
+ public Label getLabel() {
+ return NULL_LABEL;
+ }
+ };
+
+ public static class UncheckedActionConflictException extends RuntimeException {
+ public UncheckedActionConflictException(ActionConflictException e) {
+ super(e);
+ }
+ }
+
+ /**
+ * A dummy Action class for use in tests.
+ */
+ public static class NullAction extends AbstractAction {
+
+ public NullAction() {
+ super(NULL_ACTION_OWNER, Artifact.NO_ARTIFACTS, ImmutableList.of(DUMMY_ARTIFACT));
+ }
+
+ public NullAction(ActionOwner owner, Artifact... outputs) {
+ super(owner, Artifact.NO_ARTIFACTS, ImmutableList.copyOf(outputs));
+ }
+
+ public NullAction(Artifact... outputs) {
+ super(NULL_ACTION_OWNER, Artifact.NO_ARTIFACTS, ImmutableList.copyOf(outputs));
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return "";
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext) {
+ }
+
+ @Override protected String computeKey() { return "action"; }
+ @Override public ResourceSet estimateResourceConsumption(Executor executor) {
+ return ResourceSet.ZERO;
+ }
+ @Override
+ public String getMnemonic() {
+ return "Null";
+ }
+ }
+
+ /**
+ * For a bunch of actions, gets the basenames of the paths and accumulates
+ * them in a space separated string, like <code>foo.o bar.o baz.a</code>.
+ */
+ public static String baseNamesOf(Iterable<Artifact> artifacts) {
+ List<String> baseNames = baseArtifactNames(artifacts);
+ return Joiner.on(' ').join(baseNames);
+ }
+
+ /**
+ * For a bunch of actions, gets the basenames of the paths, sorts them in alphabetical
+ * order and accumulates them in a space separated string, for example
+ * <code>bar.o baz.a foo.o</code>.
+ */
+ public static String sortedBaseNamesOf(Iterable<Artifact> artifacts) {
+ List<String> baseNames = baseArtifactNames(artifacts);
+ Collections.sort(baseNames);
+ return Joiner.on(' ').join(baseNames);
+ }
+
+ /**
+ * For a bunch of artifacts, gets the basenames and accumulates them in a
+ * List.
+ */
+ public static List<String> baseArtifactNames(Iterable<Artifact> artifacts) {
+ List<String> baseNames = new ArrayList<>();
+ for (Artifact artifact : artifacts) {
+ baseNames.add(artifact.getExecPath().getBaseName());
+ }
+ return baseNames;
+ }
+
+ /**
+ * For a bunch of artifacts, gets the exec paths and accumulates them in a
+ * List.
+ */
+ public static List<String> execPaths(Iterable<Artifact> artifacts) {
+ List<String> names = new ArrayList<>();
+ for (Artifact artifact : artifacts) {
+ names.add(artifact.getExecPathString());
+ }
+ return names;
+ }
+
+ /**
+ * For a bunch of artifacts, gets the pretty printed names and accumulates them in a List. Note
+ * that this returns the root-relative paths, not the exec paths.
+ */
+ public static List<String> prettyArtifactNames(Iterable<Artifact> artifacts) {
+ List<String> result = new ArrayList<>();
+ for (Artifact artifact : artifacts) {
+ result.add(artifact.prettyPrint());
+ }
+ return result;
+ }
+
+ public static List<String> prettyJarNames(Iterable<Artifact> jars) {
+ List<String> result = new ArrayList<>();
+ for (Artifact jar : jars) {
+ result.add(jar.prettyPrint());
+ }
+ return result;
+ }
+
+ /**
+ * Returns the closure of the predecessors of any of the given types, joining the basenames of the
+ * artifacts into a space-separated string like "libfoo.a libbar.a libbaz.a".
+ */
+ public String predecessorClosureOf(Artifact artifact, FileType... types) {
+ return predecessorClosureOf(Collections.singleton(artifact), types);
+ }
+
+ /**
+ * Returns the closure of the predecessors of any of the given types.
+ */
+ public Collection<String> predecessorClosureAsCollection(Artifact artifact, FileType... types) {
+ return predecessorClosureAsCollection(Collections.singleton(artifact), types);
+ }
+
+ /**
+ * Returns the closure of the predecessors of any of the given types, joining the basenames of the
+ * artifacts into a space-separated string like "libfoo.a libbar.a libbaz.a".
+ */
+ public String predecessorClosureOf(Iterable<Artifact> artifacts, FileType... types) {
+ Set<Artifact> visited = artifactClosureOf(artifacts);
+ return baseNamesOf(FileType.filter(visited, types));
+ }
+
+ /**
+ * Returns the closure of the predecessors of any of the given types.
+ */
+ public Collection<String> predecessorClosureAsCollection(Iterable<Artifact> artifacts,
+ FileType... types) {
+ return baseArtifactNames(FileType.filter(artifactClosureOf(artifacts), types));
+ }
+
+ public String predecessorClosureOfJars(Iterable<Artifact> artifacts, FileType... types) {
+ return baseNamesOf(FileType.filter(artifactClosureOf(artifacts), types));
+ }
+
+ public Collection<String> predecessorClosureJarsAsCollection(Iterable<Artifact> artifacts,
+ FileType... types) {
+ Set<Artifact> visited = artifactClosureOf(artifacts);
+ return baseArtifactNames(FileType.filter(visited, types));
+ }
+
+ /**
+ * Returns the closure over the input files of an action.
+ */
+ public Set<Artifact> inputClosureOf(Action action) {
+ return artifactClosureOf(action.getInputs());
+ }
+
+ /**
+ * Returns the closure over the input files of an artifact.
+ */
+ public Set<Artifact> artifactClosureOf(Artifact artifact) {
+ return artifactClosureOf(Collections.singleton(artifact));
+ }
+
+ /**
+ * Returns the closure over the input files of an artifact, filtered by the given matcher.
+ */
+ public Set<Artifact> filteredArtifactClosureOf(Artifact artifact, Predicate<Artifact> matcher) {
+ return ImmutableSet.copyOf(Iterables.filter(artifactClosureOf(artifact), matcher));
+ }
+
+ /**
+ * Returns the closure over the input files of a set of artifacts.
+ */
+ public Set<Artifact> artifactClosureOf(Iterable<Artifact> artifacts) {
+ Set<Artifact> visited = new LinkedHashSet<>();
+ List<Artifact> toVisit = Lists.newArrayList(artifacts);
+ while (!toVisit.isEmpty()) {
+ Artifact current = toVisit.remove(0);
+ if (!visited.add(current)) {
+ continue;
+ }
+ Action generatingAction = actionGraph.getGeneratingAction(current);
+ if (generatingAction != null) {
+ Iterables.addAll(toVisit, generatingAction.getInputs());
+ }
+ }
+ return visited;
+ }
+
+ /**
+ * Returns the closure over the input files of a set of artifacts, filtered by the given matcher.
+ */
+ public Set<Artifact> filteredArtifactClosureOf(Iterable<Artifact> artifacts,
+ Predicate<Artifact> matcher) {
+ return ImmutableSet.copyOf(Iterables.filter(artifactClosureOf(artifacts), matcher));
+ }
+
+ /**
+ * Returns a predicate to match {@link Artifact}s with the given root-relative path suffix.
+ */
+ public static Predicate<Artifact> getArtifactSuffixMatcher(final String suffix) {
+ return new Predicate<Artifact>() {
+ @Override
+ public boolean apply(Artifact input) {
+ return input.getRootRelativePath().getPathString().endsWith(suffix);
+ }
+ };
+ }
+
+ /**
+ * Finds all the actions that are instances of <code>actionClass</code>
+ * in the transitive closure of prerequisites.
+ */
+ public <A extends Action> List<A> findTransitivePrerequisitesOf(Artifact artifact,
+ Class<A> actionClass, Predicate<Artifact> allowedArtifacts) {
+ List<A> actions = new ArrayList<>();
+ Set<Artifact> visited = new LinkedHashSet<>();
+ List<Artifact> toVisit = new LinkedList<>();
+ toVisit.add(artifact);
+ while (!toVisit.isEmpty()) {
+ Artifact current = toVisit.remove(0);
+ if (!visited.add(current)) {
+ continue;
+ }
+ Action generatingAction = actionGraph.getGeneratingAction(current);
+ if (generatingAction != null) {
+ Iterables.addAll(toVisit, Iterables.filter(generatingAction.getInputs(), allowedArtifacts));
+ if (actionClass.isInstance(generatingAction)) {
+ actions.add(actionClass.cast(generatingAction));
+ }
+ }
+ }
+ return actions;
+ }
+
+ public <A extends Action> List<A> findTransitivePrerequisitesOf(
+ Artifact artifact, Class<A> actionClass) {
+ return findTransitivePrerequisitesOf(artifact, actionClass, Predicates.<Artifact>alwaysTrue());
+ }
+
+ /**
+ * Looks in the given artifacts Iterable for the first Artifact whose path ends with the given
+ * suffix and returns its generating Action.
+ */
+ public Action getActionForArtifactEndingWith(Iterable<Artifact> artifacts, String suffix) {
+ Artifact a = getFirstArtifactEndingWith(artifacts, suffix);
+ return a != null ? actionGraph.getGeneratingAction(a) : null;
+ }
+
+ /**
+ * Looks in the given artifacts Iterable for the first Artifact whose path ends with the given
+ * suffix and returns the Artifact.
+ */
+ public static Artifact getFirstArtifactEndingWith(
+ Iterable<Artifact> artifacts, String suffix) {
+ for (Artifact a : artifacts) {
+ if (a.getExecPath().getPathString().endsWith(suffix)) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first artifact which is an input to "action" and has the
+ * specified basename. An assertion error is raised if none is found.
+ */
+ public static Artifact getInput(Action action, String basename) {
+ for (Artifact artifact : action.getInputs()) {
+ if (artifact.getExecPath().getBaseName().equals(basename)) {
+ return artifact;
+ }
+ }
+ throw new AssertionError("No input with basename '" + basename + "' in action " + action);
+ }
+
+ /**
+ * Returns true if an artifact that is an input to "action" with the specific
+ * basename exists.
+ */
+ public static boolean hasInput(Action action, String basename) {
+ try {
+ getInput(action, basename);
+ return true;
+ } catch (AssertionError e) {
+ return false;
+ }
+ }
+
+ /**
+ * Assert that an artifact is the primary output of its generating action.
+ */
+ public void assertPrimaryInputAndOutputArtifacts(Artifact input, Artifact output) {
+ Action generatingAction = actionGraph.getGeneratingAction(output);
+ assertThat(generatingAction).isNotNull();
+ assertThat(generatingAction.getPrimaryOutput()).isEqualTo(output);
+ assertThat(generatingAction.getPrimaryInput()).isEqualTo(input);
+ }
+
+ /**
+ * Returns the first artifact which is an output of "action" and has the
+ * specified basename. An assertion error is raised if none is found.
+ */
+ public static Artifact getOutput(Action action, String basename) {
+ for (Artifact artifact : action.getOutputs()) {
+ if (artifact.getExecPath().getBaseName().equals(basename)) {
+ return artifact;
+ }
+ }
+ throw new AssertionError("No output with basename '" + basename + "' in action " + action);
+ }
+
+ public static void registerActionWith(Action action, MutableActionGraph actionGraph) {
+ try {
+ actionGraph.registerAction(action);
+ } catch (ActionConflictException e) {
+ throw new UncheckedActionConflictException(e);
+ }
+ }
+}