diff options
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/analysis/util')
3 files changed, 565 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/ActionTester.java b/src/test/java/com/google/devtools/build/lib/analysis/util/ActionTester.java new file mode 100644 index 0000000000..2dfa0eae3d --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/ActionTester.java @@ -0,0 +1,76 @@ +// 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.analysis.util; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Actions; + +/** + * Test helper for testing {@link Action} implementations. + */ +public class ActionTester { + + /** + * A generator for action instances. + */ + public interface ActionCombinationFactory { + + /** + * Returns a new action instance. The parameter {@code i} is used to vary the parameters used to + * create the action. Implementations should do something like this: + * <code><pre> + * return new MyAction(owner, inputs, outputs, configuration, + * (i & 1) == 0 ? a1 : a2, + * (i & 2) == 0 ? b1 : b2, + * (i & 4) == 0 ? c1 : c2); + * (i & 16) == 0 ? d1 : d2); + * </pre></code> + * + * <p>The wrap-around (in this case at 32) is intentional and is checked for by the testing + * method. + * + * <p>To reduce the combinatorial complexity of testing an action class, all elements that are + * only used to change the executed command line should go into a single parameter, and the key + * computation should take the generated command line into account. + * + * <p>Furthermore, when called with identical parameters, this method should return different + * instances (i.e. according to {@code ==}), but they should have the same key. + */ + Action generate(int i); + } + + /** + * Tests that different actions have different keys. The count should specify how many different + * permutations the {@link ActionCombinationFactory} can generate. + */ + public static void runTest(int count, ActionCombinationFactory factory) throws Exception { + Action[] actions = new Action[count]; + for (int i = 0; i < actions.length; i++) { + actions[i] = factory.generate(i); + } + // Sanity check that the count is correct. + assertThat(Actions.canBeShared(actions[0], factory.generate(count))).isTrue(); + + for (int i = 0; i < actions.length; i++) { + assertThat(Actions.canBeShared(actions[i], factory.generate(i))).isTrue(); + for (int j = i + 1; j < actions.length; j++) { + assertWithMessage(i + " and " + j).that(Actions.canBeShared(actions[i], actions[j])) + .isFalse(); + } + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java new file mode 100644 index 0000000000..c411695605 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java @@ -0,0 +1,59 @@ +// 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.analysis.util; + +import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; +import com.google.devtools.build.lib.packages.util.MockToolsConfig; + +import java.io.IOException; +import java.util.Collection; + +/** + * Create a mock client for the analysis phase, as well as a configuration factory. + */ +public abstract class AnalysisMock { + + /** + * This is called from test setup to create the mock directory layout needed to create the + * configuration. + */ + public abstract void setupMockClient(MockToolsConfig mockToolsConfig) throws IOException; + + public abstract ConfigurationFactory createConfigurationFactory(); + + public abstract Collection<String> getOptionOverrides(); + + public static class Delegate extends AnalysisMock { + private final AnalysisMock delegate; + + public Delegate(AnalysisMock delegate) { + this.delegate = delegate; + } + + @Override + public void setupMockClient(MockToolsConfig mockToolsConfig) throws IOException { + delegate.setupMockClient(mockToolsConfig); + } + + @Override + public ConfigurationFactory createConfigurationFactory() { + return delegate.createConfigurationFactory(); + } + + @Override + public Collection<String> getOptionOverrides() { + return delegate.getOptionOverrides(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java new file mode 100644 index 0000000000..3d75939dd9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java @@ -0,0 +1,430 @@ +// 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.analysis.util; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionContextProvider; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionExecutionException; +import com.google.devtools.build.lib.actions.ActionGraph; +import com.google.devtools.build.lib.actions.ActionInputFileCache; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactFactory; +import com.google.devtools.build.lib.actions.ArtifactOwner; +import com.google.devtools.build.lib.actions.ExecutionStrategy; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.actions.Executor.ActionContext; +import com.google.devtools.build.lib.actions.ExecutorInitException; +import com.google.devtools.build.lib.actions.MiddlemanFactory; +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.util.ActionsTestUtil; +import com.google.devtools.build.lib.analysis.AnalysisEnvironment; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.BuildInfoHelper; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.WorkspaceStatusAction; +import com.google.devtools.build.lib.analysis.WorkspaceStatusAction.Key; +import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.SkyFunction; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public final class AnalysisTestUtil { + + /** + * An {@link AnalysisEnvironment} implementation that collects the actions registered. + */ + public static class CollectingAnalysisEnvironment implements AnalysisEnvironment { + private final List<Action> actions = new ArrayList<>(); + private final AnalysisEnvironment original; + + public CollectingAnalysisEnvironment(AnalysisEnvironment original) { + this.original = original; + } + + public void clear() { + actions.clear(); + } + + @Override + public void registerAction(Action... actions) { + for (Action action : actions) { + this.actions.add(action); + } + original.registerAction(actions); + } + + /** Calls {@link MutableActionGraph#registerAction} for all collected actions. */ + public void registerWith(MutableActionGraph actionGraph) { + for (Action action : actions) { + try { + actionGraph.registerAction(action); + } catch (ActionConflictException e) { + throw new ActionsTestUtil.UncheckedActionConflictException(e); + } + } + } + + @Override + public EventHandler getEventHandler() { + return original.getEventHandler(); + } + + @Override + public boolean hasErrors() { + return original.hasErrors(); + } + + @Override + public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) { + return original.getDerivedArtifact(rootRelativePath, root); + } + + @Override + public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) { + return original.getConstantMetadataArtifact(rootRelativePath, root); + } + + @Override + public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) { + return original.getFilesetArtifact(rootRelativePath, root); + } + + @Override + public Artifact getEmbeddedToolArtifact(String embeddedPath) { + return original.getEmbeddedToolArtifact(embeddedPath); + } + + @Override + public MiddlemanFactory getMiddlemanFactory() { + return original.getMiddlemanFactory(); + } + + @Override + public Action getLocalGeneratingAction(Artifact artifact) { + return original.getLocalGeneratingAction(artifact); + } + + @Override + public Iterable<Action> getRegisteredActions() { + return original.getRegisteredActions(); + } + + @Override + public SkyFunction.Environment getSkyframeEnv() { + return null; + } + + @Override + public Artifact getStableWorkspaceStatusArtifact() { + return original.getStableWorkspaceStatusArtifact(); + } + + @Override + public Artifact getVolatileWorkspaceStatusArtifact() { + return original.getVolatileWorkspaceStatusArtifact(); + } + + @Override + public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key) { + return original.getBuildInfo(ruleContext, key); + } + + @Override + public ArtifactOwner getOwner() { + return original.getOwner(); + } + + @Override + public ImmutableSet<Artifact> getOrphanArtifacts() { + return original.getOrphanArtifacts(); + } + } + + public static class DummyWorkspaceStatusAction extends WorkspaceStatusAction { + private final String key; + private final Artifact stableStatus; + private final Artifact volatileStatus; + + public DummyWorkspaceStatusAction(String key, + Artifact stableStatus, Artifact volatileStatus) { + super( + BuildInfoHelper.BUILD_INFO_ACTION_OWNER, + ImmutableList.<Artifact>of(), + ImmutableList.of(stableStatus, volatileStatus)); + this.key = key; + this.stableStatus = stableStatus; + this.volatileStatus = volatileStatus; + } + + @Override + public String describeStrategy(Executor executor) { + return ""; + } + + @Override + public void execute(ActionExecutionContext actionExecutionContext) + throws ActionExecutionException { + try { + FileSystemUtils.writeContent(stableStatus.getPath(), new byte[] {}); + FileSystemUtils.writeContent(volatileStatus.getPath(), new byte[] {}); + } catch (IOException e) { + throw new ActionExecutionException(e, this, true); + } + } + + @Override + public String getMnemonic() { + return "DummyBuildInfoAction" + key; + } + + @Override + public ResourceSet estimateResourceConsumption(Executor executor) { + return ResourceSet.ZERO; + } + + @Override + public String computeKey() { + return ""; + } + + @Override + public Artifact getVolatileStatus() { + return volatileStatus; + } + + @Override + public Artifact getStableStatus() { + return stableStatus; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DummyWorkspaceStatusAction)) { + return false; + } + + DummyWorkspaceStatusAction that = (DummyWorkspaceStatusAction) o; + return that.key.equals(this.key); + } + } + + @ExecutionStrategy(contextType = WorkspaceStatusAction.Context.class) + public static class DummyWorkspaceStatusActionContext implements WorkspaceStatusAction.Context { + @Override + public ImmutableMap<String, Key> getStableKeys() { + return ImmutableMap.of(); + } + + @Override + public ImmutableMap<String, Key> getVolatileKeys() { + return ImmutableMap.of(); + } + } + + public static class DummyWorkspaceActionContextProvider implements ActionContextProvider { + @Override + public Iterable<ActionContext> getActionContexts() { + return ImmutableList.<ActionContext>of(new DummyWorkspaceStatusActionContext()); + } + + @Override + public void executorCreated(Iterable<ActionContext> usedContexts) throws ExecutorInitException { + } + + @Override + public void executionPhaseStarting(ActionInputFileCache actionInputFileCache, + ActionGraph actionGraph, + Iterable<Artifact> topLevelArtifacts) throws ExecutorInitException, InterruptedException { + } + + @Override + public void executionPhaseEnding() { + } + } + + /** + * A workspace status action factory that does not do any interaction with the environment. + */ + public static class DummyWorkspaceStatusActionFactory implements WorkspaceStatusAction.Factory { + private final BlazeDirectories directories; + private String key; + + public DummyWorkspaceStatusActionFactory(BlazeDirectories directories) { + this.directories = directories; + this.key = ""; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public WorkspaceStatusAction createWorkspaceStatusAction( + ArtifactFactory artifactFactory, ArtifactOwner artifactOwner, Supplier<UUID> buildId) { + Artifact stableStatus = artifactFactory.getDerivedArtifact( + new PathFragment("build-info.txt"), + directories.getBuildDataDirectory(), artifactOwner); + Artifact volatileStatus = artifactFactory.getConstantMetadataArtifact( + new PathFragment("build-changelist.txt"), + directories.getBuildDataDirectory(), artifactOwner); + return new DummyWorkspaceStatusAction(key, stableStatus, volatileStatus); + } + + @Override + public Map<String, String> createDummyWorkspaceStatus() { + return ImmutableMap.of(); + } + } + + public static final AnalysisEnvironment STUB_ANALYSIS_ENVIRONMENT = new AnalysisEnvironment() { + @Override + public void registerAction(Action... action) { + } + + @Override + public boolean hasErrors() { + return false; + } + + @Override + public Artifact getEmbeddedToolArtifact(String embeddedPath) { + return null; + } + + @Override + public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) { + return null; + } + + @Override + public EventHandler getEventHandler() { + return null; + } + + @Override + public MiddlemanFactory getMiddlemanFactory() { + return null; + } + + @Override + public Action getLocalGeneratingAction(Artifact artifact) { + return null; + } + + @Override + public Iterable<Action> getRegisteredActions() { + return ImmutableList.of(); + } + + @Override + public SkyFunction.Environment getSkyframeEnv() { + return null; + } + + @Override + public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) { + return null; + } + + @Override + public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) { + return null; + } + + @Override + public Artifact getStableWorkspaceStatusArtifact() { + return null; + } + + @Override + public Artifact getVolatileWorkspaceStatusArtifact() { + return null; + } + + @Override + public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key) { + return ImmutableList.of(); + } + + @Override + public ArtifactOwner getOwner() { + return ArtifactOwner.NULL_OWNER; + } + + @Override + public ImmutableSet<Artifact> getOrphanArtifacts() { + return ImmutableSet.<Artifact>of(); + } + }; + + /** + * Given a collection of Artifacts, returns a corresponding set of strings of + * the form "{root} {relpath}", such as "bin x/libx.a". Such strings make + * assertions easier to write. + * + * <p>The returned set preserves the order of the input. + */ + public static Set<String> artifactsToStrings(BuildConfigurationCollection configurations, + Iterable<Artifact> artifacts) { + Map<Root, String> rootMap = new HashMap<>(); + BuildConfiguration targetConfiguration = + Iterables.getOnlyElement(configurations.getTargetConfigurations()); + BuildConfiguration hostConfiguration = + targetConfiguration.getConfiguration(ConfigurationTransition.HOST); + rootMap.put(targetConfiguration.getBinDirectory(), "bin"); + rootMap.put(targetConfiguration.getGenfilesDirectory(), "genfiles"); + rootMap.put(targetConfiguration.getMiddlemanDirectory(), "internal"); + rootMap.put(hostConfiguration.getBinDirectory(), "bin(host)"); + rootMap.put(hostConfiguration.getGenfilesDirectory(), "genfiles(host)"); + rootMap.put(hostConfiguration.getMiddlemanDirectory(), "internal(host)"); + + Set<String> files = new LinkedHashSet<>(); + for (Artifact artifact : artifacts) { + Root root = artifact.getRoot(); + if (root.isSourceRoot()) { + files.add("src " + artifact.getRootRelativePath()); + } else { + String name = rootMap.get(root); + if (name == null) { + name = "/"; + } + files.add(name + " " + artifact.getRootRelativePath()); + } + } + return files; + } + +} |