aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib
diff options
context:
space:
mode:
authorGravatar Ulf Adams <ulfjack@google.com>2015-02-25 14:18:14 +0000
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 14:18:14 +0000
commitd5baac0122a72209553a338ea28648d9184ca37e (patch)
tree8d73a4dbac72f1e2f99ec0910e6ba3c848b81fcb /src/test/java/com/google/devtools/build/lib
parent7d6a4f618488a778f4e5780a1ad6f8a3e27a6ec9 (diff)
Open-source BuildViewTestCase and the infrastructure required by it, as well as
the tests under analysis/actions. They don't run yet, because the mock client setup is still missing. -- MOS_MIGRATED_REVID=87149625
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib')
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/BuildViewTestCase.java1341
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteActionTest.java50
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLineTest.java78
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTest.java46
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java93
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java377
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java92
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java169
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/util/ActionTester.java76
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java59
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java430
-rw-r--r--src/test/java/com/google/devtools/build/lib/exec/util/TestExecutorBuilder.java111
-rw-r--r--src/test/java/com/google/devtools/build/lib/packages/util/MockToolsConfig.java137
-rw-r--r--src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java5
14 files changed, 3064 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTestCase.java
new file mode 100644
index 0000000000..7566f2f80f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTestCase.java
@@ -0,0 +1,1341 @@
+// 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;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+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.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionInput;
+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.MapBasedActionGraph;
+import com.google.devtools.build.lib.actions.MiddlemanFactory;
+import com.google.devtools.build.lib.actions.MutableActionGraph;
+import com.google.devtools.build.lib.actions.ResourceManager;
+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.BuildView.AnalysisResult;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationKey;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.util.AnalysisMock;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil;
+import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.exec.ExecutionOptions;
+import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import com.google.devtools.build.lib.packages.Preprocessor;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.util.MockToolsConfig;
+import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner;
+import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner.LoadingResult;
+import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
+import com.google.devtools.build.lib.pkgcache.PackageManager;
+import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
+import com.google.devtools.build.lib.pkgcache.TransitivePackageLoader;
+import com.google.devtools.build.lib.rules.test.BaselineCoverageAction;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.DiffAwareness;
+import com.google.devtools.build.lib.skyframe.PrecomputedValue;
+import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.testutil.FoundationTestCase;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.StringUtil;
+import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.ModifiedFileSet;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.common.options.Options;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import org.mockito.Mockito;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Common test code that creates a BuildView instance.
+ */
+public abstract class BuildViewTestCase extends FoundationTestCase {
+ protected static final int LOADING_PHASE_THREADS = 20;
+
+ protected ConfiguredRuleClassProvider ruleClassProvider;
+ protected ConfigurationFactory configurationFactory;
+ protected BuildView view;
+
+ private SequencedSkyframeExecutor skyframeExecutor;
+
+ protected BlazeDirectories directories;
+ protected BinTools binTools;
+
+ // Note that these configurations are virtual (they use only VFS)
+ protected BuildConfigurationCollection masterConfig;
+ protected BuildConfiguration targetConfig; // "target" or "build" config
+
+ protected OptionsParser optionsParser;
+ private PackageCacheOptions packageCacheOptions;
+
+ protected MockToolsConfig mockToolsConfig;
+
+ protected WorkspaceStatusAction.Factory workspaceStatusActionFactory;
+
+ private MutableActionGraph mutableActionGraph;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ AnalysisMock mock = getAnalysisMock();
+ directories = new BlazeDirectories(outputBase, outputBase, rootDirectory);
+ binTools = BinTools.forUnitTesting(directories, TestConstants.EMBEDDED_TOOLS);
+ mockToolsConfig = new MockToolsConfig(rootDirectory, false);
+ mock.setupMockClient(mockToolsConfig);
+ configurationFactory = mock.createConfigurationFactory();
+ packageCacheOptions = parsePackageCacheOptions();
+ workspaceStatusActionFactory =
+ new AnalysisTestUtil.DummyWorkspaceStatusActionFactory(directories);
+ mutableActionGraph = new MapBasedActionGraph();
+ ruleClassProvider = getRuleClassProvider();
+ skyframeExecutor = SequencedSkyframeExecutor.create(reporter,
+ new PackageFactory(ruleClassProvider, getEnvironmentExtensions()),
+ new TimestampGranularityMonitor(BlazeClock.instance()), directories,
+ workspaceStatusActionFactory,
+ ruleClassProvider.getBuildInfoFactories(),
+ ImmutableSet.<Path>of(),
+ ImmutableList.<DiffAwareness.Factory>of(),
+ Predicates.<PathFragment>alwaysFalse(),
+ getPreprocessorFactorySupplier(),
+ ImmutableMap.<SkyFunctionName, SkyFunction>of(),
+ getPrecomputedValues()
+ );
+ skyframeExecutor.preparePackageLoading(
+ new PathPackageLocator(rootDirectory), ConstantRuleVisibility.PUBLIC, true, "",
+ UUID.randomUUID());
+ useConfiguration();
+ setUpSkyframe();
+ // Also initializes ResourceManager.
+ ResourceManager.instance().setAvailableResources(getStartingResources());
+ }
+
+ protected AnalysisMock getAnalysisMock() {
+ try {
+ Class<?> providerClass = Class.forName(TestConstants.TEST_ANALYSIS_MOCK);
+ Field instanceField = providerClass.getField("INSTANCE");
+ return (AnalysisMock) instanceField.get(null);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /** Creates or retrieves the rule class provider used in this test. */
+ protected ConfiguredRuleClassProvider getRuleClassProvider() {
+ return TestRuleClassProvider.getRuleClassProvider();
+ }
+
+ protected Iterable<EnvironmentExtension> getEnvironmentExtensions() {
+ return ImmutableList.<EnvironmentExtension>of();
+ }
+
+ protected ImmutableList<PrecomputedValue.Injected> getPrecomputedValues() {
+ return ImmutableList.of();
+ }
+
+ protected Preprocessor.Factory.Supplier getPreprocessorFactorySupplier() {
+ return Preprocessor.Factory.Supplier.NullSupplier.INSTANCE;
+ }
+
+ protected ResourceSet getStartingResources() {
+ // Effectively disable ResourceManager by default.
+ return ResourceSet.createWithRamCpuIo(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
+ }
+
+ protected final BuildConfigurationCollection createConfigurations(String... args)
+ throws Exception {
+ optionsParser = OptionsParser.newOptionsParser(Iterables.concat(Arrays.asList(
+ ExecutionOptions.class,
+ BuildRequest.BuildRequestOptions.class),
+ ruleClassProvider.getConfigurationOptions()));
+ try {
+ List<String> configurationArgs = new ArrayList<>();
+ configurationArgs.add("--experimental_extended_sanity_checks");
+ configurationArgs.addAll(getAnalysisMock().getOptionOverrides());
+
+ optionsParser.parse(configurationArgs);
+ optionsParser.parse(args);
+
+ configurationFactory.forbidSanityCheck();
+ BuildOptions buildOptions = ruleClassProvider.createBuildOptions(optionsParser);
+ ensureTargetsVisited(buildOptions.getAllLabels().values());
+ BuildConfigurationKey key = new BuildConfigurationKey(
+ buildOptions, directories,
+ ImmutableMap.<String, String>of());
+ skyframeExecutor.invalidateConfigurationCollection();
+ return skyframeExecutor.createConfigurations(configurationFactory, key);
+ } catch (InvalidConfigurationException | OptionsParsingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ protected Target getTarget(String label)
+ throws NoSuchPackageException, NoSuchTargetException,
+ Label.SyntaxException, InterruptedException {
+ return getTarget(Label.parseAbsolute(label));
+ }
+
+ protected Target getTarget(Label label)
+ throws NoSuchPackageException, NoSuchTargetException, InterruptedException {
+ return getPackageManager().getTarget(reporter, label);
+ }
+
+ private void setUpSkyframe() {
+ PathPackageLocator pkgLocator = PathPackageLocator.create(
+ packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory);
+ skyframeExecutor.preparePackageLoading(pkgLocator,
+ packageCacheOptions.defaultVisibility, true,
+ ruleClassProvider.getDefaultsPackageContent(optionsParser),
+ UUID.randomUUID());
+ skyframeExecutor.setDeletedPackages(ImmutableSet.copyOf(packageCacheOptions.deletedPackages));
+ }
+
+ protected void setPackageCacheOptions(String... options) throws Exception {
+ packageCacheOptions = parsePackageCacheOptions(options);
+ setUpSkyframe();
+ }
+
+ private PackageCacheOptions parsePackageCacheOptions(String... options) throws Exception {
+ OptionsParser parser = OptionsParser.newOptionsParser(PackageCacheOptions.class);
+ parser.parse("--default_visibility=public");
+ parser.parse(options);
+ return parser.getOptions(PackageCacheOptions.class);
+ }
+
+ /** Used by skyframe-only tests. */
+ protected SequencedSkyframeExecutor getSkyframeExecutor() {
+ return Preconditions.checkNotNull(skyframeExecutor);
+ }
+
+ protected PackageManager getPackageManager() {
+ return skyframeExecutor.getPackageManager();
+ }
+
+ protected AnalysisHooks getAnalysisHooks() {
+ return new AnalysisHooks() {
+ @Override
+ public PackageManager getPackageManager() {
+ return BuildViewTestCase.this.getPackageManager();
+ }
+
+ @Override
+ public ConfiguredTarget getExistingConfiguredTarget(Target target,
+ BuildConfiguration configuration) {
+ return view.getExistingConfiguredTarget(target, configuration);
+ }
+
+ };
+ }
+
+ /**
+ * Invalidates all existing packages.
+ * @throws InterruptedException
+ */
+ protected void invalidatePackages() throws InterruptedException {
+ skyframeExecutor.invalidateFilesUnderPathForTesting(ModifiedFileSet.EVERYTHING_MODIFIED,
+ rootDirectory);
+ }
+
+ /**
+ * Sets host and target configuration using the specified options, falling back to the default
+ * options for unspecified ones, and recreates the build view.
+ *
+ * @throws IllegalArgumentException
+ */
+ protected final void useConfiguration(String... args) throws Exception {
+ masterConfig = createConfigurations(args);
+ targetConfig = getTargetConfiguration();
+ createBuildView();
+ }
+
+ /**
+ * Creates BuildView using current hostConfig/targetConfig values.
+ * Ensures that hostConfig is either identical to the targetConfig or has
+ * 'host' short name.
+ */
+ protected final void createBuildView() throws Exception {
+ Preconditions.checkNotNull(masterConfig);
+ Preconditions.checkState(getHostConfiguration() == getTargetConfiguration()
+ || getHostConfiguration().getShortName().equals("host"),
+ "Host configuration %s does not have name 'host' "
+ + "and does not match target configuration %s",
+ getHostConfiguration(), getTargetConfiguration());
+
+ String defaultsPackageContent = ruleClassProvider.getDefaultsPackageContent(optionsParser);
+ skyframeExecutor.setupDefaultPackage(defaultsPackageContent);
+ skyframeExecutor.dropConfiguredTargets();
+
+ view = new BuildView(directories, getPackageManager(), ruleClassProvider, skyframeExecutor,
+ binTools, null);
+ view.setConfigurationsForTesting(masterConfig);
+
+ view.setArtifactRoots(
+ ImmutableMap.of(PackageIdentifier.createInDefaultRepo(""), rootDirectory));
+ simulateLoadingPhase();
+ }
+
+ protected CachingAnalysisEnvironment getTestAnalysisEnvironment() {
+ return new CachingAnalysisEnvironment(view.getArtifactFactory(),
+ ArtifactOwner.NULL_OWNER, /*isSystemEnv=*/true, /*extendedSanityChecks*/false, reporter,
+ /*skyframeEnv=*/ null, /*actionsEnabled=*/true, binTools);
+ }
+
+ /**
+ * Allows access to the prerequisites of a configured target. This is currently used in some tests
+ * to reach into the internals of RuleCT for white box testing. In principle, this should not be
+ * used; instead tests should only assert on properties of the exposed provider instances and / or
+ * the action graph.
+ */
+ protected Iterable<ConfiguredTarget> getDirectPrerequisites(ConfiguredTarget target) {
+ return view.getDirectPrerequisites(target);
+ }
+
+ /**
+ * Creates and returns a rule context that is equivalent to the one that was used to create the
+ * given configured target.
+ */
+ protected RuleContext getRuleContext(ConfiguredTarget target) {
+ return new RuleContext.Builder(
+ new StubAnalysisEnvironment(), (Rule) target.getTarget(),
+ target.getConfiguration(), ruleClassProvider.getPrerequisiteValidator())
+ .setVisibility(NestedSetBuilder.<PackageSpecification>create(
+ Order.STABLE_ORDER, PackageSpecification.EVERYTHING))
+ .setPrerequisites(view.getPrerequisiteMapForTesting(target))
+ .setConfigConditions(ImmutableSet.<ConfigMatchingProvider>of())
+ .build();
+ }
+
+ /**
+ * Creates and returns a rule context to use for Skylark tests that is equivalent to the one
+ * that was used to create the given configured target.
+ */
+ protected RuleContext getRuleContextForSkylark(ConfiguredTarget target) {
+ // TODO(bazel-team): we need this horrible workaround because CachingAnalysisEnvironment
+ // only works with StoredErrorEventListener despite the fact it accepts the interface
+ // ErrorEventListener, so it's not possible to create it with reporter.
+ // See BuildView.getRuleContextForTesting().
+ StoredEventHandler eventHandler = new StoredEventHandler() {
+ @Override
+ public synchronized void handle(Event e) {
+ super.handle(e);
+ reporter.handle(e);
+ }
+ };
+ return view.getRuleContextForTesting(target, eventHandler);
+ }
+
+ /**
+ * Allows access to the prerequisites of a configured target. This is currently used in some tests
+ * to reach into the internals of RuleCT for white box testing. In principle, this should not be
+ * used; instead tests should only assert on properties of the exposed provider instances and / or
+ * the action graph.
+ */
+ protected List<? extends TransitiveInfoCollection> getPrerequisites(ConfiguredTarget target,
+ String attributeName) {
+ return getRuleContext(target).getConfiguredTargetMap().get(attributeName);
+ }
+
+ /**
+ * Allows access to the prerequisites of a configured target. This is currently used in some tests
+ * to reach into the internals of RuleCT for white box testing. In principle, this should not be
+ * used; instead tests should only assert on properties of the exposed provider instances and / or
+ * the action graph.
+ */
+ protected <C extends TransitiveInfoProvider> Iterable<C> getPrerequisites(
+ ConfiguredTarget target, String attributeName, Class<C> classType) {
+ return AnalysisUtils.getProviders(getPrerequisites(target, attributeName), classType);
+ }
+
+ /**
+ * Allows access to the prerequisites of a configured target. This is currently used in some tests
+ * to reach into the internals of RuleCT for white box testing. In principle, this should not be
+ * used; instead tests should only assert on properties of the exposed provider instances and / or
+ * the action graph.
+ */
+ protected ImmutableList<Artifact> getPrerequisiteArtifacts(
+ ConfiguredTarget target, String attributeName) {
+ Set<Artifact> result = new LinkedHashSet<>();
+ for (FileProvider provider : getPrerequisites(target, attributeName, FileProvider.class)) {
+ Iterables.addAll(result, provider.getFilesToBuild());
+ }
+ return ImmutableList.copyOf(result);
+ }
+
+ protected final Action getGeneratingAction(Artifact artifact) {
+ Preconditions.checkNotNull(artifact);
+ Action action = mutableActionGraph.getGeneratingAction(artifact);
+ if (action != null) {
+ return action;
+ }
+ return view.getActionGraph().getGeneratingAction(artifact);
+ }
+
+ protected void simulateLoadingPhase() {
+ try {
+ ensureTargetsVisited(targetConfig.getAllLabels().values());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected ActionsTestUtil actionsTestUtil() {
+ return new ActionsTestUtil(view.getActionGraph());
+ }
+
+ private Set<Target> getTargets(Iterable<Label> labels) throws InterruptedException,
+ NoSuchTargetException, NoSuchPackageException{
+ Set<Target> targets = Sets.newHashSet();
+ for (Label label : labels) {
+ targets.add(skyframeExecutor.getPackageManager().getTarget(reporter, label));
+ }
+ return targets;
+ }
+
+ // Get a MutableActionGraph for testing purposes.
+ protected MutableActionGraph getMutableActionGraph() {
+ return mutableActionGraph;
+ }
+
+ protected TransitivePackageLoader makeVisitor() {
+ setUpSkyframe();
+ return skyframeExecutor.pkgLoader();
+ }
+
+ /**
+ * Construct the containing package of the specified labels, and all of its transitive
+ * dependencies. This must be done prior to configuration, as the latter is intolerant of
+ * NoSuchTargetExceptions.
+ */
+ protected boolean ensureTargetsVisited(TransitivePackageLoader visitor,
+ Collection<Label> targets, Collection<Label> labels, boolean keepGoing)
+ throws InterruptedException, NoSuchTargetException, NoSuchPackageException {
+ boolean success = visitor.sync(reporter,
+ ImmutableSet.copyOf(getTargets(targets)),
+ ImmutableSet.copyOf(labels),
+ keepGoing,
+ /*parallelThreads=*/4,
+ /*maxDepth=*/Integer.MAX_VALUE);
+ return success;
+ }
+
+ protected boolean ensureTargetsVisited(Collection<Label> labels)
+ throws InterruptedException, NoSuchTargetException, NoSuchPackageException {
+ return ensureTargetsVisited(makeVisitor(), ImmutableSet.<Label>of(), labels,
+ /*keepGoing=*/false);
+ }
+
+ protected boolean ensureTargetsVisited(Label label)
+ throws InterruptedException, NoSuchTargetException, NoSuchPackageException {
+ return ensureTargetsVisited(ImmutableList.of(label));
+ }
+
+ protected boolean ensureTargetsVisited(String... labels)
+ throws InterruptedException, NoSuchTargetException, NoSuchPackageException, SyntaxException {
+ List<Label> actualLabels = new ArrayList<>();
+ for (String label : labels) {
+ actualLabels.add(Label.parseAbsolute(label));
+ }
+ return ensureTargetsVisited(actualLabels);
+ }
+
+ /**
+ * Returns the ConfiguredTarget for the specified label, configured for the
+ * "build" (aka "target") configuration.
+ */
+ protected ConfiguredTarget getConfiguredTarget(String label)
+ throws NoSuchPackageException, NoSuchTargetException,
+ Label.SyntaxException, InterruptedException {
+ return getConfiguredTarget(label, targetConfig);
+ }
+
+ /**
+ * Returns the ConfiguredTarget for the specified label, using the
+ * given build configuration.
+ */
+ protected ConfiguredTarget getConfiguredTarget(String label, BuildConfiguration config)
+ throws NoSuchPackageException, NoSuchTargetException,
+ Label.SyntaxException, InterruptedException {
+ ensureTargetsVisited(label);
+ return view.getConfiguredTargetForTesting(getTarget(label), config);
+ }
+
+ /**
+ * Returns the ConfiguredTarget for the specified label, using the
+ * given build configuration.
+ */
+ protected ConfiguredTarget getConfiguredTarget(Label label, BuildConfiguration config)
+ throws NoSuchPackageException, NoSuchTargetException, InterruptedException {
+ ensureTargetsVisited(label);
+ return view.getConfiguredTargetForTesting(getTarget(label), config);
+ }
+
+ /**
+ * Returns the ConfiguredTarget for the specified file label, configured for
+ * the "build" (aka "target") configuration.
+ */
+ protected FileConfiguredTarget getFileConfiguredTarget(String label)
+ throws NoSuchPackageException, NoSuchTargetException,
+ Label.SyntaxException, InterruptedException {
+ return (FileConfiguredTarget) getConfiguredTarget(label, targetConfig);
+ }
+
+ /**
+ * Returns the ConfiguredTarget for the specified label, configured for
+ * the "host" configuration.
+ */
+ protected ConfiguredTarget getHostConfiguredTarget(String label)
+ throws NoSuchPackageException, NoSuchTargetException,
+ Label.SyntaxException, InterruptedException {
+ return getConfiguredTarget(label, getHostConfiguration());
+ }
+
+ /**
+ * Returns the ConfiguredTarget for the specified file label, configured for
+ * the "host" configuration.
+ */
+ protected FileConfiguredTarget getHostFileConfiguredTarget(String label)
+ throws NoSuchPackageException, NoSuchTargetException,
+ Label.SyntaxException, InterruptedException {
+ return (FileConfiguredTarget) getHostConfiguredTarget(label);
+ }
+
+ /**
+ * Create and return a configured scratch rule.
+ *
+ * @param packageName the package name ofthe rule.
+ * @param ruleName the name of the rule.
+ * @param lines the text of the rule.
+ * @return the configured target instance for the created rule.
+ * @throws IOException
+ * @throws Exception
+ */
+ protected ConfiguredTarget scratchConfiguredTarget(String packageName,
+ String ruleName,
+ String... lines)
+ throws IOException, Exception {
+ return scratchConfiguredTarget(packageName, ruleName, targetConfig, lines);
+ }
+
+ /**
+ * Create and return a scratch rule.
+ *
+ * @param packageName the package name of the rule.
+ * @param ruleName the name of the rule.
+ * @param lines the text of the rule.
+ * @return the rule instance for the created rule.
+ * @throws IOException
+ * @throws Exception
+ */
+ protected Rule scratchRule(String packageName, String ruleName, String... lines)
+ throws Exception {
+ scratchFile("/" + TestConstants.TEST_WORKSPACE_DIRECTORY + "/" + packageName + "/BUILD", lines);
+ return (Rule) getTarget("//" + packageName + ":" + ruleName);
+ }
+
+ /**
+ * Create and return a configured scratch rule.
+ *
+ * @param packageName the package name of the rule.
+ * @param ruleName the name of the rule.
+ * @param config the configuration to use to construct the configured rule.
+ * @param lines the text of the rule.
+ * @return the configured target instance for the created rule.
+ * @throws IOException
+ * @throws Exception
+ */
+ protected ConfiguredTarget scratchConfiguredTarget(String packageName,
+ String ruleName,
+ BuildConfiguration config,
+ String... lines)
+ throws IOException, Exception {
+ Target rule = scratchRule(packageName, ruleName, lines);
+ if (ensureTargetsVisited(rule.getLabel())) {
+ return view.getConfiguredTargetForTesting(rule, config);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Check that configuration of the target named 'ruleName' in the
+ * specified BUILD file fails with an error message ending in
+ * 'expectedErrorMessage'.
+ *
+ * @param packageName the package name of the generated BUILD file
+ * @param ruleName the rule name for the rule in the generated BUILD file
+ * @param expectedErrorMessage the expected error message.
+ * @param lines the text of the rule.
+ * @return the found error.
+ */
+ protected Event checkError(String packageName,
+ String ruleName,
+ String expectedErrorMessage,
+ String... lines) throws Exception {
+ eventCollector.clear();
+ reporter.removeHandler(failFastHandler); // expect errors
+ ConfiguredTarget target = scratchConfiguredTarget(packageName, ruleName, lines);
+ if (target != null) {
+ assertTrue("Rule '" + "//" + packageName + ":" + ruleName + "' did not contain an error",
+ view.hasErrors(target));
+ }
+ return assertContainsEvent(expectedErrorMessage);
+ }
+
+ /**
+ * Check that configuration of the target named 'ruleName' in the
+ * specified BUILD file reports a warning message ending in
+ * 'expectedWarningMessage', and that no errors were reported.
+ *
+ * @param packageName the package name of the generated BUILD file
+ * @param ruleName the rule name for the rule in the generated BUILD file
+ * @param expectedWarningMessage the expected warning message.
+ * @param lines the text of the rule.
+ * @return the found error.
+ */
+ protected Event checkWarning(String packageName,
+ String ruleName,
+ String expectedWarningMessage,
+ String... lines) throws Exception {
+ eventCollector.clear();
+ ConfiguredTarget target = scratchConfiguredTarget(packageName, ruleName,
+ lines);
+ assertFalse("Rule '" + "//" + packageName + ":" + ruleName
+ + "' did contain an error",
+ view.hasErrors(target));
+ return assertContainsEvent(expectedWarningMessage);
+ }
+
+ /**
+ * 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.
+ */
+ protected Set<String> artifactsToStrings(Iterable<Artifact> artifacts) {
+ return AnalysisTestUtil.artifactsToStrings(masterConfig, artifacts);
+ }
+
+ /**
+ * Asserts that targetName's outputs are exactly expectedOuts.
+ *
+ * @param targetName The label of a rule.
+ * @param expectedOuts The labels of the expected outputs of the rule.
+ */
+ protected void assertOuts(String targetName, String... expectedOuts) throws Exception {
+ Rule ruleTarget = (Rule) getTarget(targetName);
+ for (String expectedOut : expectedOuts) {
+ Target outTarget = getTarget(expectedOut);
+ if (!(outTarget instanceof OutputFile)) {
+ fail("Target " + outTarget + " is not an output");
+ assertSame(ruleTarget, ((OutputFile) outTarget).getGeneratingRule());
+ // This ensures that the output artifact is wired up in the action graph
+ getConfiguredTarget(expectedOut);
+ }
+ }
+
+ Collection<OutputFile> outs = ruleTarget.getOutputFiles();
+ assertEquals("Mismatched outputs: " + outs, expectedOuts.length, outs.size());
+ }
+
+ /**
+ * Asserts that there exists a configured target file for the given label.
+ */
+ protected void assertConfiguredTargetExists(String label) throws Exception {
+ assertNotNull(getFileConfiguredTarget(label));
+ }
+
+ /**
+ * Assert that the first label and the second label are both generated
+ * by the same command.
+ */
+ protected void assertSameGeneratingAction(String labelA, String labelB)
+ throws Exception {
+ assertSame(
+ "Action for " + labelA + " did not match " + labelB,
+ getGeneratingActionForLabel(labelA),
+ getGeneratingActionForLabel(labelB));
+ }
+
+ protected Artifact getSourceArtifact(PathFragment rootRelativePath, Root root) {
+ return view.getArtifactFactory().getSourceArtifact(rootRelativePath, root);
+ }
+
+ protected Artifact getSourceArtifact(String name) throws IOException {
+ return getSourceArtifact(new PathFragment(name),
+ Root.asSourceRoot(scratchDir("/" + TestConstants.TEST_WORKSPACE_DIRECTORY)));
+ }
+
+ /**
+ * Gets a derived artifact, creating it if necessary. {@code ArtifactOwner} should be a genuine
+ * {@link LabelAndConfiguration} corresponding to a {@link ConfiguredTarget}. If called from a
+ * test that does not exercise the analysis phase, the convenience methods {@link
+ * #getBinArtifactWithNoOwner} or {@link #getGenfilesArtifactWithNoOwner} should be used instead.
+ */
+ protected Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root,
+ ArtifactOwner owner) {
+ return view.getArtifactFactory().getDerivedArtifact(rootRelativePath, root, owner);
+ }
+
+ /**
+ * Gets a derived Artifact for testing with path of the form
+ * root/owner.getPackageFragment()/packageRelativePath.
+ *
+ * @see #getDerivedArtifact(PathFragment, Root, ArtifactOwner)
+ */
+ private Artifact getPackageRelativeDerivedArtifact(String packageRelativePath, Root root,
+ ArtifactOwner owner) {
+ return getDerivedArtifact(
+ owner.getLabel().getPackageFragment().getRelative(packageRelativePath),
+ root, owner);
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the {@link BuildConfiguration#getBinDirectory()}. This
+ * method should only be used for tests that do no analysis, and so there is no ConfiguredTarget
+ * to own this artifact. If the test runs the analysis phase, {@link
+ * #getBinArtifact(String, ArtifactOwner)} or its convenience methods should be
+ * used instead.
+ */
+ protected Artifact getBinArtifactWithNoOwner(String rootRelativePath) {
+ return getDerivedArtifact(new PathFragment(rootRelativePath), targetConfig.getBinDirectory(),
+ ActionsTestUtil.NULL_ARTIFACT_OWNER);
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the subdirectory of the {@link
+ * BuildConfiguration#getBinDirectory()} corresponding to the package of {@code owner}. So
+ * to specify a file foo/foo.o owned by target //foo:foo, {@code packageRelativePath} should just
+ * be "foo.o".
+ */
+ protected Artifact getBinArtifact(String packageRelativePath, String owner) {
+ return getBinArtifact(packageRelativePath, makeLabelAndConfiguration(owner));
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the subdirectory of the {@link
+ * BuildConfiguration#getBinDirectory()} corresponding to the package of {@code owner}. So
+ * to specify a file foo/foo.o owned by target //foo:foo, {@code packageRelativePath} should just
+ * be "foo.o".
+ */
+ protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner) {
+ return getPackageRelativeDerivedArtifact(packageRelativePath,
+ owner.getConfiguration().getBinDirectory(), new ConfiguredTargetKey(owner));
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the subdirectory of the {@link
+ * BuildConfiguration#getBinDirectory()} corresponding to the package of {@code owner}. So
+ * to specify a file foo/foo.o owned by target //foo:foo, {@code packageRelativePath} should just
+ * be "foo.o".
+ */
+ private Artifact getBinArtifact(String packageRelativePath, ArtifactOwner owner) {
+ return getPackageRelativeDerivedArtifact(packageRelativePath, targetConfig.getBinDirectory(),
+ owner);
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the {@link BuildConfiguration#getGenfilesDirectory()}.
+ * This method should only be used for tests that do no analysis, and so there is no
+ * ConfiguredTarget to own this artifact. If the test runs the analysis phase, {@link
+ * #getGenfilesArtifact(String, ArtifactOwner)} or its convenience methods should be used instead.
+ */
+ protected Artifact getGenfilesArtifactWithNoOwner(String rootRelativePath) {
+ return getDerivedArtifact(new PathFragment(rootRelativePath),
+ targetConfig.getGenfilesDirectory(), ActionsTestUtil.NULL_ARTIFACT_OWNER);
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the subdirectory of the {@link
+ * BuildConfiguration#getGenfilesDirectory()} corresponding to the package of {@code owner}.
+ * So to specify a file foo/foo.o owned by target //foo:foo, {@code packageRelativePath} should
+ * just be "foo.o".
+ */
+ protected Artifact getGenfilesArtifact(String packageRelativePath, String owner) {
+ return getGenfilesArtifact(packageRelativePath, makeLabelAndConfiguration(owner));
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the subdirectory of the {@link
+ * BuildConfiguration#getGenfilesDirectory()} corresponding to the package of {@code owner}.
+ * So to specify a file foo/foo.o owned by target //foo:foo, {@code packageRelativePath} should
+ * just be "foo.o".
+ */
+ protected Artifact getGenfilesArtifact(String packageRelativePath, ConfiguredTarget owner) {
+ return getGenfilesArtifact(packageRelativePath, new ConfiguredTargetKey(owner));
+ }
+
+ /**
+ * Gets a derived Artifact for testing in the subdirectory of the {@link
+ * BuildConfiguration#getGenfilesDirectory()} corresponding to the package of {@code owner}.
+ * So to specify a file foo/foo.o owned by target //foo:foo, {@code packageRelativePath} should
+ * just be "foo.o".
+ */
+ private Artifact getGenfilesArtifact(String packageRelativePath, ArtifactOwner owner) {
+ return getPackageRelativeDerivedArtifact(packageRelativePath,
+ targetConfig.getGenfilesDirectory(),
+ owner);
+ }
+
+ protected Action getGeneratingActionForLabel(String label) throws Exception {
+ return getGeneratingAction(getFileConfiguredTarget(label).getArtifact());
+ }
+
+ protected String fileName(Artifact artifact) {
+ return artifact.getExecPathString();
+ }
+
+ protected String fileName(FileConfiguredTarget target) {
+ return fileName(target.getArtifact());
+ }
+
+ protected String fileName(String name) throws Exception {
+ return fileName(getFileConfiguredTarget(name));
+ }
+
+ protected Path getOutputPath() {
+ return directories.getOutputPath();
+ }
+
+ /**
+ * Verifies whether the rule checks the 'srcs' attribute validity.
+ *
+ * <p>At the call site it expects the {@code packageName} to contain:
+ * <ol>
+ * <li>{@code :gvalid} - genrule that outputs a valid file</li>
+ * <li>{@code :ginvalid} - genrule that outputs an invalid file</li>
+ * <li>{@code :gmix} - genrule that outputs a mix of valid and invalid
+ * files</li>
+ * <li>{@code :valid} - rule of type {@code ruleType} that has a valid
+ * file, {@code :gvalid} and {@code :gmix} in the srcs</li>
+ * <li>{@code :invalid} - rule of type {@code ruleType} that has an invalid
+ * file, {@code :ginvalid} in the srcs</li>
+ * <li>{@code :mix} - rule of type {@code ruleType} that has a valid and an
+ * invalid file in the srcs</li>
+ * </ol>
+ *
+ * @param packageName the package where the rules under test are located
+ * @param ruleType rules under test types
+ * @param expectedTypes expected file types
+ */
+ protected void assertSrcsValidityForRuleType(String packageName, String ruleType,
+ String expectedTypes) throws Exception {
+ reporter.removeHandler(failFastHandler);
+ String descriptionSingle = ruleType + " srcs file (expected " + expectedTypes + ")";
+ String descriptionPlural = ruleType + " srcs files (expected " + expectedTypes + ")";
+ String descriptionPluralFile = "(expected " + expectedTypes + ")";
+ assertSrcsValidity(ruleType, packageName + ":valid", false,
+ "need at least one " + descriptionSingle,
+ "'" + packageName + ":gvalid' does not produce any " + descriptionPlural,
+ "'" + packageName + ":gmix' does not produce any " + descriptionPlural);
+ assertSrcsValidity(ruleType, packageName + ":invalid", true,
+ "file '" + packageName + ":a.foo' is misplaced here " + descriptionPluralFile,
+ "'" + packageName + ":ginvalid' does not produce any " + descriptionPlural);
+ assertSrcsValidity(ruleType, packageName + ":mix", true,
+ "'" + packageName + ":a.foo' does not produce any " + descriptionPlural);
+ }
+
+ protected void assertSrcsValidity(String ruleType, String targetName, boolean expectedError,
+ String... expectedMessages) throws Exception{
+ ConfiguredTarget target = getConfiguredTarget(targetName);
+ if (expectedError) {
+ assertTrue(view.hasErrors(target));
+ for (String expectedMessage : expectedMessages) {
+ String message = "in srcs attribute of " + ruleType + " rule " + targetName + ": "
+ + expectedMessage;
+ assertContainsEvent(message);
+ }
+ } else {
+ assertFalse(view.hasErrors(target));
+ for (String expectedMessage : expectedMessages) {
+ String message = "in srcs attribute of " + ruleType + " rule " + target.getLabel() + ": "
+ + expectedMessage;
+ assertDoesNotContainEvent(message);
+ }
+ }
+ }
+
+ private static Label makeLabel(String label) {
+ try {
+ return Label.parseAbsolute(label);
+ } catch (SyntaxException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private ConfiguredTargetKey makeLabelAndConfiguration(String label) {
+ return new ConfiguredTargetKey(makeLabel(label), targetConfig);
+ }
+
+ protected static List<String> actionInputsToPaths(Iterable<? extends ActionInput> actionInputs) {
+ return ImmutableList.copyOf(
+ Iterables.transform(actionInputs, new Function<ActionInput, String>() {
+ @Override
+ public String apply(ActionInput actionInput) {
+ return actionInput.getExecPathString();
+ }
+ }));
+ }
+
+ protected String readContentAsLatin1String(Artifact artifact) throws IOException {
+ return new String(FileSystemUtils.readContentAsLatin1(artifact.getPath()));
+ }
+
+ /**
+ * Asserts that the predecessor closure of the given Artifact contains the same elements as those
+ * in expectedPredecessors, plus the given common predecessors. Only looks at predecessors of
+ * the given file type.
+ */
+ public void assertPredecessorClosureSameContents(
+ Artifact artifact, FileType fType, Iterable<String> common, String... expectedPredecessors) {
+ assertSameContentsWithCommonElements(
+ actionsTestUtil().predecessorClosureAsCollection(artifact, fType),
+ expectedPredecessors, common);
+ }
+
+ /**
+ * Utility method for asserting that the contents of one collection are the
+ * same as those in a second plus some set of common elements.
+ */
+ protected void assertSameContentsWithCommonElements(Iterable<Artifact> artifacts,
+ Iterable<String> common, String... expectedInputs) {
+ assertSameContents(Iterables.concat(Lists.newArrayList(expectedInputs), common),
+ ActionsTestUtil.prettyArtifactNames(artifacts));
+ }
+
+ /**
+ * Utility method for asserting that the contents of one collection are the
+ * same as those in a second plus some set of common elements.
+ */
+ protected void assertSameContentsWithCommonElements(Iterable<String> artifacts,
+ String[] expectedInputs, Iterable<String> common) {
+ assertSameContents(Iterables.concat(Lists.newArrayList(expectedInputs), common), artifacts);
+ }
+
+ /**
+ * Utility method for asserting that a list contains the elements of a
+ * sublist This is useful for checking that a list of arguments contains a
+ * particular set of arguments.
+ */
+ protected void assertContainsSublist(List<String> list, List<String> sublist) {
+ assertContainsSublist(null, list, sublist);
+ }
+
+ /**
+ * Utility method for asserting that a list contains the elements of a
+ * sublist This is useful for checking that a list of arguments contains a
+ * particular set of arguments.
+ */
+ protected void assertContainsSublist(String message, List<String> list, List<String> sublist) {
+ if (Collections.indexOfSubList(list, sublist) == -1) {
+ fail((message == null ? "" : (message + ' '))
+ + "expected: <" + list + "> to contain sublist: <" + sublist + ">");
+ }
+ }
+
+ protected void assertContainsSelfEdgeEvent(String label) {
+ assertContainsEvent(label + " [self-edge]");
+ }
+
+ protected Iterable<Artifact> collectRunfiles(ConfiguredTarget target) {
+ RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class);
+ if (runfilesProvider != null) {
+ return runfilesProvider.getDefaultRunfiles().getAllArtifacts();
+ } else {
+ return Runfiles.EMPTY.getAllArtifacts();
+ }
+ }
+
+ protected NestedSet<Artifact> getFilesToBuild(TransitiveInfoCollection target) {
+ return target.getProvider(FileProvider.class).getFilesToBuild();
+ }
+
+ /**
+ * Returns all extra actions for that target (no transitive actions), no duplicate actions.
+ */
+ protected ImmutableList<Action> getExtraActionActions(ConfiguredTarget target) {
+ LinkedHashSet<Action> result = new LinkedHashSet<>();
+ for (Artifact artifact : getExtraActionArtifacts(target)) {
+ result.add(getGeneratingAction(artifact));
+ }
+ return ImmutableList.copyOf(result);
+ }
+
+ protected ImmutableList<Action> getFilesToBuildActions(ConfiguredTarget target) {
+ List<Action> result = new ArrayList<>();
+ for (Artifact artifact : getFilesToBuild(target)) {
+ Action action = getGeneratingAction(artifact);
+ if (action != null) {
+ result.add(action);
+ }
+ }
+ return ImmutableList.copyOf(result);
+ }
+
+ protected NestedSet<Artifact> getOutputGroup(
+ TransitiveInfoCollection target, String outputGroup) {
+ TopLevelArtifactProvider provider = target.getProvider(TopLevelArtifactProvider.class);
+ return provider == null
+ ? NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)
+ : provider.getOutputGroup(outputGroup);
+ }
+
+ protected ImmutableList<Artifact> getExtraActionArtifacts(ConfiguredTarget target) {
+ return target.getProvider(ExtraActionArtifactsProvider.class).getExtraActionArtifacts();
+ }
+
+ protected Artifact getExecutable(String label) throws Exception {
+ return getConfiguredTarget(label).getProvider(FilesToRunProvider.class).getExecutable();
+ }
+
+ protected Artifact getExecutable(TransitiveInfoCollection target) {
+ return target.getProvider(FilesToRunProvider.class).getExecutable();
+ }
+
+ protected ImmutableList<Artifact> getFilesToRun(TransitiveInfoCollection target) {
+ return target.getProvider(FilesToRunProvider.class).getFilesToRun();
+ }
+
+ protected ImmutableList<Artifact> getFilesToRun(Label label) throws Exception {
+ return getConfiguredTarget(label, targetConfig)
+ .getProvider(FilesToRunProvider.class).getFilesToRun();
+ }
+
+ protected ImmutableList<Artifact> getFilesToRun(String label) throws Exception {
+ return getConfiguredTarget(label).getProvider(FilesToRunProvider.class).getFilesToRun();
+ }
+
+ protected RunfilesSupport getRunfilesSupport(String label) throws Exception {
+ return getConfiguredTarget(label).getProvider(FilesToRunProvider.class).getRunfilesSupport();
+ }
+
+ protected RunfilesSupport getRunfilesSupport(TransitiveInfoCollection target) {
+ return target.getProvider(FilesToRunProvider.class).getRunfilesSupport();
+ }
+
+ protected static Runfiles getDefaultRunfiles(ConfiguredTarget target) {
+ return target.getProvider(RunfilesProvider.class).getDefaultRunfiles();
+ }
+
+ protected static Runfiles getDataRunfiles(ConfiguredTarget target) {
+ return target.getProvider(RunfilesProvider.class).getDataRunfiles();
+ }
+
+ protected BuildConfiguration getTargetConfiguration() {
+ return Iterables.getOnlyElement(masterConfig.getTargetConfigurations());
+ }
+
+ protected BuildConfiguration getDataConfiguration() {
+ return getTargetConfiguration().getConfiguration(ConfigurationTransition.DATA);
+ }
+
+ protected BuildConfiguration getHostConfiguration() {
+ return getTargetConfiguration().getConfiguration(ConfigurationTransition.HOST);
+ }
+
+ /**
+ * Returns an attribute value retriever for the given rule for the target configuration.
+
+ */
+ protected AttributeMap attributes(RuleConfiguredTarget ct) {
+ return ConfiguredAttributeMapper.of(ct);
+ }
+
+ protected AttributeMap attributes(ConfiguredTarget rule) {
+ return attributes((RuleConfiguredTarget) rule);
+ }
+
+ protected AnalysisResult update(List<String> targets,
+ boolean keepGoing,
+ int loadingPhaseThreads,
+ boolean doAnalysis,
+ EventBus eventBus) throws Exception {
+
+ LoadingPhaseRunner.Options loadingOptions =
+ Options.getDefaults(LoadingPhaseRunner.Options.class);
+ loadingOptions.loadingPhaseThreads = loadingPhaseThreads;
+
+ BuildView.Options viewOptions = Options.getDefaults(BuildView.Options.class);
+ viewOptions.keepGoing = keepGoing;
+
+ LoadingPhaseRunner runner = new LoadingPhaseRunner(getPackageManager(),
+ Collections.unmodifiableSet(ruleClassProvider.getRuleClassMap().keySet()));
+ LoadingResult loadingResult = runner.execute(reporter, eventBus, targets, loadingOptions,
+ getTargetConfiguration().getAllLabels(),
+ viewOptions.keepGoing, /*determineTests=*/false, /*callback=*/null);
+ if (!doAnalysis) {
+ // TODO(bazel-team): What's supposed to happen in this case?
+ return null;
+ }
+ return view.update(loadingResult, masterConfig, viewOptions,
+ TopLevelArtifactContext.DEFAULT, reporter, eventBus);
+ }
+
+ protected static Predicate<Artifact> artifactNamed(final String name) {
+ return new Predicate<Artifact>() {
+ @Override
+ public boolean apply(Artifact input) {
+ return name.equals(input.prettyPrint());
+ }
+ };
+ }
+
+ /**
+ * Utility method for tests. Converts an array of strings into a set of labels.
+ *
+ * @param strings the set of strings to be converted to labels.
+ * @throws SyntaxException if there are any syntax errors in the strings.
+ */
+ public static Set<Label> asLabelSet(String... strings) throws SyntaxException {
+ return asLabelSet(ImmutableList.copyOf(strings));
+ }
+
+ /**
+ * Utility method for tests. Converts an array of strings into a set of labels.
+ *
+ * @param strings the set of strings to be converted to labels.
+ * @throws SyntaxException if there are any syntax errors in the strings.
+ */
+ public static Set<Label> asLabelSet(Iterable<String> strings) throws SyntaxException {
+ Set<Label> result = Sets.newTreeSet();
+ for (String s : strings) {
+ result.add(Label.parseAbsolute(s));
+ }
+ return result;
+ }
+
+ protected SpawnAction getGeneratingAction(ConfiguredTarget target,
+ String outputName) {
+ Artifact found = Iterables.find(getFilesToBuild(target),
+ artifactNamed(outputName));
+ return (SpawnAction) getGeneratingAction(found);
+ }
+
+ protected String getErrorMsgSingleFile(String attrName, String ruleType, String ruleName,
+ String depRuleName) {
+ return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": '"
+ + depRuleName + "' must produce a single file";
+ }
+
+ protected String getErrorMsgNoGoodFiles(String attrName, String ruleType, String ruleName,
+ String depRuleName) {
+ return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": '"
+ + depRuleName + "' does not produce any " + ruleType + " " + attrName + " files";
+ }
+
+ protected String getErrorMsgMisplacedFiles(String attrName, String ruleType, String ruleName,
+ String fileName) {
+ return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": file '"
+ + fileName + "' is misplaced here";
+ }
+
+ protected String getErrorNonExistingTarget(String attrName, String ruleType, String ruleName,
+ String targetName) {
+ return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": target '"
+ + targetName + "' does not exist";
+ }
+
+ protected String getErrorNonExistingRule(String attrName, String ruleType, String ruleName,
+ String targetName) {
+ return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": rule '"
+ + targetName + "' does not exist";
+ }
+
+ protected String getErrorMsgMisplacedRules(String attrName, String ruleType, String ruleName,
+ String depRuleType, String depRuleName) {
+ return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": "
+ + depRuleType + " rule '" + depRuleName + "' is misplaced here";
+ }
+
+ public static String getErrorMsgNonEmptyList(String attrName, String ruleType, String ruleName) {
+ return "non empty attribute '" + attrName + "' in '" + ruleType
+ + "' rule '" + ruleName + "' has to have at least one value";
+ }
+
+ protected String getErrorMsgMandatoryMissing(String attrName, String ruleType) {
+ return "missing value for mandatory attribute '" + attrName + "' in '" + ruleType + "' rule";
+ }
+
+ protected String getErrorMsgWrongAttributeValue(String value, String... expected) {
+ return String.format("has to be one of %s instead of '%s'",
+ StringUtil.joinEnglishList(ImmutableSet.copyOf(expected), "or", "'"), value);
+ }
+
+ private class StubAnalysisEnvironment implements AnalysisEnvironment {
+
+ @Override
+ public void registerAction(Action... action) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasErrors() {
+ return false;
+ }
+
+ @Override
+ public Artifact getEmbeddedToolArtifact(String embeddedPath) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EventHandler getEventHandler() {
+ return reporter;
+ }
+
+ @Override
+ public MiddlemanFactory getMiddlemanFactory() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Action getLocalGeneratingAction(Artifact artifact) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterable<Action> getRegisteredActions() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SkyFunction.Environment getSkyframeEnv() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Artifact getStableWorkspaceStatusArtifact() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Artifact getVolatileWorkspaceStatusArtifact() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArtifactOwner getOwner() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ImmutableSet<Artifact> getOrphanArtifacts() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected Iterable<String> baselineCoverageArtifactBasenames(ConfiguredTarget target)
+ throws Exception {
+ Artifact baselineCoverage = Iterables.getOnlyElement(target
+ .getProvider(TopLevelArtifactProvider.class)
+ .getOutputGroup(TopLevelArtifactProvider.BASELINE_COVERAGE));
+ BaselineCoverageAction baselineAction =
+ (BaselineCoverageAction) getGeneratingAction(baselineCoverage);
+
+ EventBus eventBus = new EventBus();
+ Executor mockExecutor = Mockito.mock(Executor.class);
+ Mockito.when(mockExecutor.getEventBus()).thenReturn(eventBus);
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ baselineAction.newDeterministicWriter(reporter, mockExecutor).writeOutputFile(bytes);
+
+ ImmutableList.Builder<String> basenames = ImmutableList.builder();
+ for (String line : new String(bytes.toByteArray(), StandardCharsets.UTF_8).split("\n")) {
+ if (line.startsWith("SF:")) {
+ String basename = line.substring(line.lastIndexOf("/") + 1);
+ basenames.add(basename);
+ }
+ }
+
+ return basenames.build();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteActionTest.java
new file mode 100644
index 0000000000..22e6bcf30d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteActionTest.java
@@ -0,0 +1,50 @@
+// 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.actions;
+
+import com.google.common.io.ByteSource;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+
+import java.nio.charset.StandardCharsets;
+
+public class BinaryFileWriteActionTest extends FileWriteActionTestCase {
+ @Override
+ protected Action createAction(
+ ActionOwner actionOwner, Artifact outputArtifact, String data, boolean makeExecutable) {
+ return new BinaryFileWriteAction(actionOwner, outputArtifact,
+ ByteSource.wrap(data.getBytes(StandardCharsets.UTF_8)), makeExecutable);
+ }
+
+ public void testNoInputs() {
+ checkNoInputsByDefault();
+ }
+
+ public void testDestinationArtifactIsOutput() {
+ checkDestinationArtifactIsOutput();
+ }
+
+ public void testCanWriteNonExecutableFile() throws Exception {
+ checkCanWriteNonExecutableFile();
+ }
+
+ public void testCanWriteExecutableFile() throws Exception {
+ checkCanWriteExecutableFile();
+ }
+
+ public void testComputesConsistentKeys() throws Exception {
+ checkComputesConsistentKeys();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLineTest.java
new file mode 100644
index 0000000000..d3b8723fa5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLineTest.java
@@ -0,0 +1,78 @@
+// 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.actions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.BuildViewTestCase;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+public class CustomCommandLineTest extends BuildViewTestCase {
+ public void testAddBeforeEachPath() {
+ CustomCommandLine commandLine = new CustomCommandLine.Builder()
+ .add("foo")
+ .addBeforeEachPath(
+ "-I", ImmutableList.of(new PathFragment("/path1"), new PathFragment("/path2")))
+ .add("bar")
+ .addBeforeEachPath("-I", ImmutableList.<PathFragment>of())
+ .add("baz")
+ .build();
+ assertThat(commandLine.arguments())
+ .containsExactly("foo", "-I", "/path1", "-I", "/path2", "bar", "baz")
+ .inOrder();
+ }
+
+ public void testAddBeforeEach() {
+ CustomCommandLine commandLine = new CustomCommandLine.Builder()
+ .add("foo")
+ .addBeforeEach("-D", ImmutableList.<String>of())
+ .add("bar")
+ .addBeforeEach("-D", ImmutableList.of("DEBUG=42", "ENABLE_QUANTUM", "__OBJC__"))
+ .add("baz")
+ .build();
+ assertThat(commandLine.arguments())
+ .containsExactly(
+ "foo", "bar", "-D", "DEBUG=42", "-D", "ENABLE_QUANTUM", "-D", "__OBJC__", "baz")
+ .inOrder();
+ }
+
+ public void testAddBeforeEachExecPath() throws Exception {
+ CustomCommandLine commandLine = new CustomCommandLine.Builder()
+ .add("foo")
+ .addBeforeEachExecPath("-l",
+ ImmutableList.of(getSourceArtifact("pkg/util.a"), getSourceArtifact("pkg2/extra.a")))
+ .add("bar")
+ .addBeforeEachExecPath("-l", ImmutableList.<Artifact>of())
+ .add("baz")
+ .build();
+ assertThat(commandLine.arguments())
+ .containsExactly("foo", "-l", "pkg/util.a", "-l", "pkg2/extra.a", "bar", "baz")
+ .inOrder();
+ }
+
+ public void testAddFormatEach() {
+ CustomCommandLine commandLine = new CustomCommandLine.Builder()
+ .add("foo")
+ .addFormatEach("-X'%s'", ImmutableList.<String>of())
+ .add("bar")
+ .addFormatEach("-X'%s'", ImmutableList.of("42", "1011"))
+ .add("baz")
+ .build();
+ assertThat(commandLine.arguments())
+ .containsExactly("foo", "bar", "-X'42'", "-X'1011'", "baz")
+ .inOrder();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTest.java
new file mode 100644
index 0000000000..1bc136fcd5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTest.java
@@ -0,0 +1,46 @@
+// 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.actions;
+
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+
+public class FileWriteActionTest extends FileWriteActionTestCase {
+
+ @Override
+ protected FileWriteAction createAction(
+ ActionOwner actionOwner, Artifact outputArtifact, String data, boolean makeExecutable) {
+ return new FileWriteAction(actionOwner, outputArtifact, data, makeExecutable);
+ }
+
+ public void testNoInputs() {
+ checkNoInputsByDefault();
+ }
+
+ public void testDestinationArtifactIsOutput() {
+ checkDestinationArtifactIsOutput();
+ }
+
+ public void testCanWriteNonExecutableFile() throws Exception {
+ checkCanWriteNonExecutableFile();
+ }
+
+ public void testCanWriteExecutableFile() throws Exception {
+ checkCanWriteExecutableFile();
+ }
+
+ public void testComputesConsistentKeys() throws Exception {
+ checkComputesConsistentKeys();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java
new file mode 100644
index 0000000000..ff0ed2f2d1
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java
@@ -0,0 +1,93 @@
+// 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.actions;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER;
+
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.analysis.BuildViewTestCase;
+import com.google.devtools.build.lib.analysis.util.ActionTester;
+import com.google.devtools.build.lib.exec.util.TestExecutorBuilder;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.util.Collection;
+
+public abstract class FileWriteActionTestCase extends BuildViewTestCase {
+
+ private Action action;
+ private Artifact outputArtifact;
+ private Path output;
+ private Executor executor;
+ private ActionExecutionContext context;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ outputArtifact = getBinArtifactWithNoOwner("destination.txt");
+ output = outputArtifact.getPath();
+ FileSystemUtils.createDirectoryAndParents(output.getParentDirectory());
+ action = createAction(NULL_ACTION_OWNER, outputArtifact, "Hello World", false);
+ executor = new TestExecutorBuilder(directories, binTools).build();
+ context = new ActionExecutionContext(executor, null, null, new FileOutErr(), null);
+ }
+
+ protected abstract Action createAction(
+ ActionOwner actionOwner, Artifact outputArtifact, String data, boolean makeExecutable);
+
+ protected void checkNoInputsByDefault() {
+ assertThat(action.getInputs()).isEmpty();
+ assertNull(action.getPrimaryInput());
+ }
+
+ protected void checkDestinationArtifactIsOutput() {
+ Collection<Artifact> outputs = action.getOutputs();
+ assertEquals(asSet(outputArtifact), asSet(outputs));
+ assertEquals(outputArtifact, action.getPrimaryOutput());
+ }
+
+ protected void checkCanWriteNonExecutableFile() throws Exception {
+ action.execute(context);
+ String content = new String(FileSystemUtils.readContentAsLatin1(output));
+ assertEquals("Hello World", content);
+ assertFalse(output.isExecutable());
+ }
+
+ protected void checkCanWriteExecutableFile() throws Exception {
+ Artifact outputArtifact = getBinArtifactWithNoOwner("hello");
+ Path output = outputArtifact.getPath();
+ Action action = createAction(NULL_ACTION_OWNER, outputArtifact, "echo 'Hello World'", true);
+ action.execute(context);
+ String content = new String(FileSystemUtils.readContentAsLatin1(output));
+ assertEquals("echo 'Hello World'", content);
+ assertTrue(output.isExecutable());
+ }
+
+ protected void checkComputesConsistentKeys() throws Exception {
+ ActionTester.runTest(4, new ActionTester.ActionCombinationFactory() {
+ @Override
+ public Action generate(int i) {
+ return createAction(NULL_ACTION_OWNER, outputArtifact,
+ (i & 1) == 0 ? "0" : "1",
+ (i & 2) == 0);
+ }
+ });
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
new file mode 100644
index 0000000000..6ff4a8db4f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
@@ -0,0 +1,377 @@
+// 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.actions;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.util.Arrays.asList;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.actions.extra.EnvironmentVariable;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+import com.google.devtools.build.lib.actions.extra.SpawnInfo;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.BuildViewTestCase;
+import com.google.devtools.build.lib.analysis.util.ActionTester;
+import com.google.devtools.build.lib.analysis.util.ActionTester.ActionCombinationFactory;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests {@link SpawnAction}.
+ */
+public class SpawnActionTest extends BuildViewTestCase {
+ private Artifact welcomeArtifact;
+ private Artifact destinationArtifact;
+ private Artifact jarArtifact;
+ private AnalysisTestUtil.CollectingAnalysisEnvironment collectingAnalysisEnvironment;
+
+ private SpawnAction.Builder builder() {
+ return new SpawnAction.Builder();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ collectingAnalysisEnvironment = new AnalysisTestUtil.CollectingAnalysisEnvironment(
+ getTestAnalysisEnvironment());
+ welcomeArtifact = getSourceArtifact("pkg/welcome.txt");
+ jarArtifact = getSourceArtifact("pkg/exe.jar");
+ destinationArtifact = getBinArtifactWithNoOwner("dir/destination.txt");
+ }
+
+ private SpawnAction createCopyFromWelcomeToDestination() {
+ PathFragment cp = new PathFragment("/bin/cp");
+ List<String> arguments = asList(welcomeArtifact.getExecPath().getPathString(),
+ destinationArtifact.getExecPath().getPathString());
+
+ Action[] actions = builder()
+ .addInput(welcomeArtifact)
+ .addOutput(destinationArtifact)
+ .setExecutionInfo(ImmutableMap.<String, String>of("local", ""))
+ .setExecutable(cp)
+ .addArguments(arguments)
+ .setProgressMessage("hi, mom!")
+ .setMnemonic("Dummy")
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ return (SpawnAction) actions[0];
+ }
+
+ public void testWelcomeArtifactIsInput() {
+ SpawnAction copyFromWelcomeToDestination = createCopyFromWelcomeToDestination();
+ Iterable<Artifact> inputs = copyFromWelcomeToDestination.getInputs();
+ assertEquals(asSet(welcomeArtifact), asSet(inputs));
+ }
+
+ public void testDestinationArtifactIsOutput() {
+ SpawnAction copyFromWelcomeToDestination = createCopyFromWelcomeToDestination();
+ Collection<Artifact> outputs = copyFromWelcomeToDestination.getOutputs();
+ assertEquals(asSet(destinationArtifact), asSet(outputs));
+ }
+
+ public void testBuilder() throws Exception {
+ Artifact input = getSourceArtifact("input");
+ Artifact output = getBinArtifactWithNoOwner("output");
+ Action[] actions = builder()
+ .addInput(input)
+ .addOutput(output)
+ .setExecutable(scratchFile("/bin/xxx").asFragment())
+ .setProgressMessage("Test")
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ assertEquals(ActionsTestUtil.NULL_ACTION_OWNER.getLabel(),
+ action.getOwner().getLabel());
+ assertSameContents(asList(input), action.getInputs());
+ assertSameContents(asList(output), action.getOutputs());
+ assertEquals(AbstractAction.DEFAULT_RESOURCE_SET, action.getSpawn().getLocalResources());
+ assertSameContents(asList("/bin/xxx"), action.getArguments());
+ assertEquals("Test", action.getProgressMessage());
+ }
+
+ public void testBuilderWithExecutable() throws Exception {
+ Action[] actions = builder()
+ .setExecutable(welcomeArtifact)
+ .addOutput(destinationArtifact)
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ assertSameContents(asList(welcomeArtifact.getExecPath().getPathString()),
+ action.getArguments());
+ }
+
+ public void testBuilderWithJavaExecutable() throws Exception {
+ Action[] actions = builder()
+ .addOutput(destinationArtifact)
+ .setJavaExecutable(scratchFile("/bin/java").asFragment(),
+ jarArtifact, "MyMainClass", asList("-jvmarg"))
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ assertEquals(asList("/bin/java", "-Xverify:none", "-jvmarg", "-cp",
+ "pkg/exe.jar", "MyMainClass"), action.getArguments());
+ }
+
+ public void testBuilderWithJavaExecutableAndParameterFile() throws Exception {
+ useConfiguration("--min_param_file_size=0");
+ collectingAnalysisEnvironment = new AnalysisTestUtil.CollectingAnalysisEnvironment(
+ getTestAnalysisEnvironment());
+ Artifact output = getBinArtifactWithNoOwner("output");
+ Artifact paramFile = getBinArtifactWithNoOwner("output-2.params");
+ Action[] actions = builder()
+ .addOutput(output)
+ .setJavaExecutable(
+ scratchFile("/bin/java").asFragment(), jarArtifact, "MyMainClass", asList("-jvmarg"))
+ .addArgument("-X")
+ .useParameterFile(ParameterFileType.UNQUOTED)
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ if (getMutableActionGraph() != null) {
+ // Otherwise, CachingAnalysisEnvironment.registerAction() registers the action. We cannot
+ // use STUB_ANALYSIS_ENVIRONMENT here because we also need a BuildConfiguration.
+ collectingAnalysisEnvironment.registerWith(getMutableActionGraph());
+ }
+ assertEquals(asList("/bin/java", "-Xverify:none", "-jvmarg", "-cp",
+ "pkg/exe.jar", "MyMainClass", "@" + paramFile.getExecPathString()),
+ action.getArguments());
+ assertEquals(ImmutableList.of("-X"),
+ ImmutableList.copyOf(
+ ((ParameterFileWriteAction) getGeneratingAction(paramFile)).getContents()));
+ assertContainsSublist(actionInputsToPaths(action.getSpawn().getInputFiles()),
+ "pkg/exe.jar");
+ }
+
+ public void testBuilderWithJavaExecutableAndParameterFileAndParameterFileFlag() throws Exception {
+ useConfiguration("--min_param_file_size=0");
+ collectingAnalysisEnvironment = new AnalysisTestUtil.CollectingAnalysisEnvironment(
+ getTestAnalysisEnvironment());
+
+ Artifact output = getBinArtifactWithNoOwner("output");
+ Artifact paramFile = getBinArtifactWithNoOwner("output-2.params");
+ Action[] actions = builder()
+ .addOutput(output)
+ .setJavaExecutable(
+ scratchFile("/bin/java").asFragment(), jarArtifact, "MyMainClass", asList("-jvmarg"))
+ .addArgument("-X")
+ .useParameterFile(ParameterFileType.UNQUOTED, ISO_8859_1, "--flagfile=")
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ if (getMutableActionGraph() != null) {
+ // Otherwise, CachingAnalysisEnvironment.registerAction() registers the action. We cannot
+ // use STUB_ANALYSIS_ENVIRONMENT here because we also need a BuildConfiguration.
+ collectingAnalysisEnvironment.registerWith(getMutableActionGraph());
+ }
+ assertEquals(asList("/bin/java", "-Xverify:none", "-jvmarg", "-cp",
+ "pkg/exe.jar", "MyMainClass", "--flagfile=" + paramFile.getExecPathString()),
+ ImmutableList.copyOf(action.getArguments()));
+ assertEquals(Arrays.asList("-X"),
+ ImmutableList.copyOf(
+ ((ParameterFileWriteAction) getGeneratingAction(paramFile)).getContents()));
+ assertContainsSublist(actionInputsToPaths(action.getSpawn().getInputFiles()),
+ "pkg/exe.jar");
+ }
+
+ public void testBuilderWithExtraExecutableArguments() throws Exception {
+ Action[] actions = builder()
+ .addOutput(destinationArtifact)
+ .setJavaExecutable(
+ scratchFile("/bin/java").asFragment(), jarArtifact, "MyMainClass", asList("-jvmarg"))
+ .addExecutableArguments("execArg1", "execArg2")
+ .addArguments("arg1")
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ assertEquals(asList("/bin/java", "-Xverify:none", "-jvmarg", "-cp",
+ "pkg/exe.jar", "MyMainClass", "execArg1", "execArg2", "arg1"),
+ action.getArguments());
+ }
+
+ public void testBuilderWithExtraExecutableArgumentsAndParameterFile() throws Exception {
+ useConfiguration("--min_param_file_size=0");
+ collectingAnalysisEnvironment = new AnalysisTestUtil.CollectingAnalysisEnvironment(
+ getTestAnalysisEnvironment());
+ Artifact output = getBinArtifactWithNoOwner("output");
+ Artifact paramFile = getBinArtifactWithNoOwner("output-2.params");
+ Action[] actions = builder()
+ .addOutput(output)
+ .setJavaExecutable(
+ scratchFile("/bin/java").asFragment(), jarArtifact, "MyMainClass", asList("-jvmarg"))
+ .addExecutableArguments("execArg1", "execArg2")
+ .addArguments("arg1", "arg2", "arg3")
+ .useParameterFile(ParameterFileType.UNQUOTED)
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ if (getMutableActionGraph() != null) {
+ // Otherwise, CachingAnalysisEnvironment.registerAction() registers the action. We cannot
+ // use STUB_ANALYSIS_ENVIRONMENT here because we also need a BuildConfiguration.
+ collectingAnalysisEnvironment.registerWith(getMutableActionGraph());
+ }
+ assertEquals(asList("/bin/java", "-Xverify:none", "-jvmarg", "-cp",
+ "pkg/exe.jar", "MyMainClass", "execArg1", "execArg2",
+ "@" + paramFile.getExecPathString()), action.getSpawn().getArguments());
+ assertEquals(asList("/bin/java", "-Xverify:none", "-jvmarg", "-cp",
+ "pkg/exe.jar", "MyMainClass", "execArg1", "execArg2",
+ "@" + paramFile.getExecPathString()), ImmutableList.copyOf(action.getArguments()));
+ assertEquals(Arrays.asList("arg1", "arg2", "arg3"),
+ ImmutableList.copyOf(
+ ((ParameterFileWriteAction) getGeneratingAction(paramFile)).getContents()));
+ }
+
+ public void testParameterFiles() throws Exception {
+ Artifact output1 = getBinArtifactWithNoOwner("output1");
+ Artifact output2 = getBinArtifactWithNoOwner("output2");
+ Artifact paramFile = getBinArtifactWithNoOwner("output1-2.params");
+ PathFragment executable = new PathFragment("/bin/executable");
+
+ useConfiguration("--min_param_file_size=500");
+
+ String longOption = Strings.repeat("x", 1000);
+ SpawnAction spawnAction = ((SpawnAction) builder()
+ .addOutput(output1)
+ .setExecutable(executable)
+ .useParameterFile(ParameterFileType.UNQUOTED)
+ .addArgument(longOption)
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig)[0]);
+ assertThat(spawnAction.getRemainingArguments()).containsExactly(
+ "@" + paramFile.getExecPathString()).inOrder();
+
+ useConfiguration("--min_param_file_size=1500");
+ spawnAction = ((SpawnAction) builder()
+ .addOutput(output2)
+ .setExecutable(executable)
+ .useParameterFile(ParameterFileType.UNQUOTED)
+ .addArgument(longOption)
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig)[0]);
+ assertThat(spawnAction.getRemainingArguments()).containsExactly(longOption).inOrder();
+ }
+
+ public void testExtraActionInfo() throws Exception {
+ SpawnAction copyFromWelcomeToDestination = createCopyFromWelcomeToDestination();
+ ExtraActionInfo.Builder builder = copyFromWelcomeToDestination.getExtraActionInfo();
+ ExtraActionInfo info = builder.build();
+ assertEquals("Dummy", info.getMnemonic());
+
+ SpawnInfo spawnInfo = info.getExtension(SpawnInfo.spawnInfo);
+ assertNotNull(spawnInfo);
+
+ assertSameContents(copyFromWelcomeToDestination.getArguments(), spawnInfo.getArgumentList());
+
+ Iterable<String> inputPaths = Artifact.toExecPaths(
+ copyFromWelcomeToDestination.getInputs());
+ Iterable<String> outputPaths = Artifact.toExecPaths(
+ copyFromWelcomeToDestination.getOutputs());
+
+ assertSameContents(inputPaths, spawnInfo.getInputFileList());
+ assertSameContents(outputPaths, spawnInfo.getOutputFileList());
+ Map<String, String> environment = copyFromWelcomeToDestination.getEnvironment();
+ assertEquals(environment.size(), spawnInfo.getVariableCount());
+
+ for (EnvironmentVariable variable : spawnInfo.getVariableList()) {
+ assertEquals(variable.getValue(), environment.get(variable.getName()));
+ }
+ }
+
+ public void testInputManifest() throws Exception {
+ Artifact manifest = getSourceArtifact("MANIFEST");
+ Action[] actions = builder()
+ .addInput(manifest)
+ .addInputManifest(manifest, new PathFragment("/destination/"))
+ .addOutput(getBinArtifactWithNoOwner("output"))
+ .setExecutable(scratchFile("/bin/xxx").asFragment())
+ .setProgressMessage("Test")
+ .build(ActionsTestUtil.NULL_ACTION_OWNER, collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ SpawnAction action = (SpawnAction) actions[0];
+ List<String> inputFiles = actionInputsToPaths(action.getSpawn().getInputFiles());
+ assertTrue(inputFiles.isEmpty());
+ }
+
+ public void testComputeKey() throws Exception {
+ final Artifact artifactA = getSourceArtifact("a");
+ final Artifact artifactB = getSourceArtifact("b");
+
+ ActionTester.runTest(64, new ActionCombinationFactory() {
+ @Override
+ public Action generate(int i) {
+ SpawnAction.Builder builder = builder();
+ builder.addOutput(destinationArtifact);
+
+ PathFragment executable = (i & 1) == 0 ? artifactA.getExecPath() : artifactB.getExecPath();
+ if ((i & 2) == 0) {
+ builder.setExecutable(executable);
+ } else {
+ builder.setJavaExecutable(executable, jarArtifact, "Main", ImmutableList.<String>of());
+ }
+
+ builder.setMnemonic((i & 4) == 0 ? "a" : "b");
+
+ if ((i & 8) == 0) {
+ builder.addInputManifest(artifactA, new PathFragment("a"));
+ } else {
+ builder.addInputManifest(artifactB, new PathFragment("a"));
+ }
+
+ if ((i & 16) == 0) {
+ builder.addInputManifest(artifactA, new PathFragment("aa"));
+ } else {
+ builder.addInputManifest(artifactA, new PathFragment("ab"));
+ }
+
+ Map<String, String> env = new HashMap<>();
+ if ((i & 32) == 0) {
+ env.put("foo", "bar");
+ }
+ builder.setEnvironment(env);
+
+ Action[] actions = builder.build(ActionsTestUtil.NULL_ACTION_OWNER,
+ collectingAnalysisEnvironment, targetConfig);
+ collectingAnalysisEnvironment.registerAction(actions);
+ return actions[0];
+ }
+ });
+ }
+
+ public void testMnemonicMustNotContainSpaces() {
+ SpawnAction.Builder builder = builder();
+ try {
+ builder.setMnemonic("contains space");
+ fail("Expected exception");
+ } catch (IllegalArgumentException expected) {}
+ try {
+ builder.setMnemonic("contains\nnewline");
+ fail("Expected exception");
+ } catch (IllegalArgumentException expected) {}
+ try {
+ builder.setMnemonic("contains/slash");
+ fail("Expected exception");
+ } catch (IllegalArgumentException expected) {}
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
new file mode 100644
index 0000000000..4ab2c12fae
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
@@ -0,0 +1,92 @@
+// 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.actions;
+
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER;
+
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.analysis.BuildViewTestCase;
+import com.google.devtools.build.lib.exec.util.TestExecutorBuilder;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+
+/**
+ * Tests {@link SymlinkAction}.
+ */
+public class SymlinkActionTest extends BuildViewTestCase {
+
+ private Path input;
+ private Artifact inputArtifact;
+ private Path output;
+ private Artifact outputArtifact;
+ private SymlinkAction action;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ input = scratchFile("/workspace/input.txt", "Hello, world.");
+ inputArtifact = getSourceArtifact("input.txt");
+ Path linkedInput = directories.getExecRoot().getRelative("input.txt");
+ FileSystemUtils.createDirectoryAndParents(linkedInput.getParentDirectory());
+ linkedInput.createSymbolicLink(input);
+ outputArtifact = getBinArtifactWithNoOwner("destination.txt");
+ output = outputArtifact.getPath();
+ FileSystemUtils.createDirectoryAndParents(output.getParentDirectory());
+ action = new SymlinkAction(NULL_ACTION_OWNER,
+ inputArtifact, outputArtifact, "Symlinking test");
+ }
+
+ public void testInputArtifactIsInput() {
+ Iterable<Artifact> inputs = action.getInputs();
+ assertEquals(asSet(inputArtifact), asSet(inputs));
+ }
+
+ public void testDestinationArtifactIsOutput() {
+ Iterable<Artifact> outputs = action.getOutputs();
+ assertEquals(asSet(outputArtifact), asSet(outputs));
+ }
+
+ public void testSymlink() throws Exception {
+ Executor executor = new TestExecutorBuilder(directories, null).build();
+ action.execute(new ActionExecutionContext(executor, null, null, null, null));
+ assertTrue(output.isSymbolicLink());
+ assertEquals(input, output.resolveSymbolicLinks());
+ assertEquals(inputArtifact, action.getPrimaryInput());
+ assertEquals(outputArtifact, action.getPrimaryOutput());
+ }
+
+ public void testExecutableSymlink() throws Exception {
+ Executor executor = new TestExecutorBuilder(directories, null).build();
+ outputArtifact = getBinArtifactWithNoOwner("destination2.txt");
+ output = outputArtifact.getPath();
+ action = new ExecutableSymlinkAction(NULL_ACTION_OWNER, inputArtifact, outputArtifact);
+ assertFalse(input.isExecutable());
+ ActionExecutionContext actionExecutionContext =
+ new ActionExecutionContext(executor, null, null, null, null);
+ try {
+ action.execute(actionExecutionContext);
+ fail("Expected ActionExecutionException");
+ } catch (ActionExecutionException e) {
+ MoreAsserts.assertContainsRegex("'input.txt' is not executable", e.getMessage());
+ }
+ input.setExecutable(true);
+ action.execute(actionExecutionContext);
+ assertTrue(output.isSymbolicLink());
+ assertEquals(input, output.resolveSymbolicLinks());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java
new file mode 100644
index 0000000000..ec8935eabe
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java
@@ -0,0 +1,169 @@
+// 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.actions;
+
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER;
+
+import com.google.common.base.Joiner;
+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.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.exec.util.TestExecutorBuilder;
+import com.google.devtools.build.lib.testutil.FoundationTestCase;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.util.List;
+
+/**
+ * Tests {@link TemplateExpansionAction}.
+ */
+public class TemplateExpansionActionTest extends FoundationTestCase {
+
+ private static final String TEMPLATE = Joiner.on('\n').join("key=%key%", "value=%value%");
+
+ private Root outputRoot;
+ private Artifact inputArtifact;
+ private Artifact outputArtifact;
+ private Path output;
+ private List<Substitution> substitutions;
+ private BlazeDirectories directories;
+ private BinTools binTools;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Root workspace = Root.asSourceRoot(scratchDir("/workspace"));
+ outputRoot = Root.asDerivedRoot(scratchDir("/workspace"), scratchDir("/workspace/out"));
+ Path input = scratchFile("/workspace/input.txt", TEMPLATE);
+ inputArtifact = new Artifact(input, workspace);
+ output = scratchFS().getPath("/workspace/out/destination.txt");
+ outputArtifact = new Artifact(output, outputRoot);
+ substitutions = Lists.newArrayList();
+ substitutions.add(Substitution.of("%key%", "foo"));
+ substitutions.add(Substitution.of("%value%", "bar"));
+ directories = new BlazeDirectories(
+ scratchFS().getPath("/install"),
+ scratchFS().getPath("/base"),
+ scratchFS().getPath("/workspace"));
+ binTools = BinTools.empty(directories);
+ }
+
+ private TemplateExpansionAction create() {
+ TemplateExpansionAction result = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact, Template.forString(TEMPLATE), substitutions, false);
+ return result;
+ }
+
+ public void testInputsIsEmpty() {
+ assertTrue(Iterables.isEmpty(create().getInputs()));
+ }
+
+ public void testDestinationArtifactIsOutput() {
+ assertEquals(ImmutableSet.of(outputArtifact), create().getOutputs());
+ }
+
+ public void testExpansion() throws Exception {
+ Executor executor = new TestExecutorBuilder(directories, binTools).build();
+ create().execute(createContext(executor));
+ String content = new String(FileSystemUtils.readContentAsLatin1(output));
+ String expected = Joiner.on('\n').join("key=foo", "value=bar");
+ assertEquals(expected, content);
+ }
+
+ public void testKeySameIfSame() throws Exception {
+ Artifact outputArtifact2 = new Artifact(scratchFS().getPath("/workspace/out/destination.txt"),
+ outputRoot);
+ TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo")), false);
+ TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact2, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo")), false);
+ assertEquals(a.computeKey(), b.computeKey());
+ }
+
+ public void testKeyDiffersForSubstitution() throws Exception {
+ Artifact outputArtifact2 = new Artifact(scratchFS().getPath("/workspace/out/destination.txt"),
+ outputRoot);
+ TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo")), false);
+ TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact2, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo2")), false);
+ assertFalse(a.computeKey().equals(b.computeKey()));
+ }
+
+ public void testKeyDiffersForExecutable() throws Exception {
+ Artifact outputArtifact2 = new Artifact(scratchFS().getPath("/workspace/out/destination.txt"),
+ outputRoot);
+ TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo")), false);
+ TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact2, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo")), true);
+ assertFalse(a.computeKey().equals(b.computeKey()));
+ }
+
+ public void testKeyDiffersForTemplates() throws Exception {
+ Artifact outputArtifact2 = new Artifact(scratchFS().getPath("/workspace/out/destination.txt"),
+ outputRoot);
+ TemplateExpansionAction a = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact, Template.forString(TEMPLATE),
+ ImmutableList.of(Substitution.of("%key%", "foo")), false);
+ TemplateExpansionAction b = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ outputArtifact2, Template.forString(TEMPLATE + " "),
+ ImmutableList.of(Substitution.of("%key%", "foo")), false);
+ assertFalse(a.computeKey().equals(b.computeKey()));
+ }
+
+ private TemplateExpansionAction createWithArtifact() {
+ TemplateExpansionAction result = new TemplateExpansionAction(NULL_ACTION_OWNER,
+ inputArtifact, outputArtifact, substitutions, false);
+ return result;
+ }
+
+ public void testArtifactTemplateHasInput() {
+ assertEquals(ImmutableList.of(inputArtifact), createWithArtifact().getInputs());
+ }
+
+ public void testArtifactTemplateHasOutput() {
+ assertEquals(ImmutableSet.of(outputArtifact), createWithArtifact().getOutputs());
+ }
+
+ public void testArtifactTemplateExpansion() throws Exception {
+ Executor executor = new TestExecutorBuilder(directories, binTools).build();
+ createWithArtifact().execute(createContext(executor));
+ String content = new String(FileSystemUtils.readContentAsLatin1(output));
+ // The trailing "" is needed because scratchFile implicitly appends "\n".
+ String expected = Joiner.on('\n').join("key=foo", "value=bar", "");
+ assertEquals(expected, content);
+ }
+
+ private ActionExecutionContext createContext(Executor executor) {
+ return new ActionExecutionContext(executor, null, null, new FileOutErr(), null);
+ }
+}
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;
+ }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/exec/util/TestExecutorBuilder.java b/src/test/java/com/google/devtools/build/lib/exec/util/TestExecutorBuilder.java
new file mode 100644
index 0000000000..1743349d9a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/exec/util/TestExecutorBuilder.java
@@ -0,0 +1,111 @@
+// Copyright 2009 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.exec.util;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.ActionContextProvider;
+import com.google.devtools.build.lib.actions.BlazeExecutor;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.actions.ExecutorInitException;
+import com.google.devtools.build.lib.actions.SpawnActionContext;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.exec.ExecutionOptions;
+import com.google.devtools.build.lib.exec.FileWriteStrategy;
+import com.google.devtools.build.lib.exec.SourceManifestActionContextImpl;
+import com.google.devtools.build.lib.exec.SymlinkTreeStrategy;
+import com.google.devtools.build.lib.runtime.CommonCommandOptions;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Builder for the test instance of the {@link BlazeExecutor} class.
+ */
+public class TestExecutorBuilder {
+ public static final List<Class<? extends OptionsBase>> DEFAULT_OPTIONS = ImmutableList.of(
+ ExecutionOptions.class, CommonCommandOptions.class);
+ private final BlazeDirectories directories;
+ private Reporter reporter = new Reporter();
+ private EventBus bus = new EventBus();
+ private OptionsParser optionsParser = OptionsParser.newOptionsParser(DEFAULT_OPTIONS);
+ private List<ActionContext> strategies = new ArrayList<>();
+ private Map<String, SpawnActionContext> spawnStrategyMap = new HashMap<>();
+
+ public TestExecutorBuilder(BlazeDirectories directories, BinTools binTools) {
+ this.directories = directories;
+
+ strategies.add(new SourceManifestActionContextImpl(
+ new PathFragment(TestConstants.RUNFILES_PREFIX)));
+ strategies.add(new FileWriteStrategy());
+ strategies.add(new SymlinkTreeStrategy(null, binTools));
+ }
+
+ public TestExecutorBuilder setReporter(Reporter reporter) {
+ this.reporter = reporter;
+ return this;
+ }
+
+ public TestExecutorBuilder setBus(EventBus bus) {
+ this.bus = bus;
+ return this;
+ }
+
+ public TestExecutorBuilder setOptionsParser(OptionsParser optionsParser) {
+ this.optionsParser = optionsParser;
+ return this;
+ }
+
+ public TestExecutorBuilder parseOptions(String... options) throws OptionsParsingException {
+ this.optionsParser.parse(options);
+ return this;
+ }
+
+ public TestExecutorBuilder addStrategy(ActionContext strategy) {
+ strategies.add(strategy);
+ return this;
+ }
+
+ public TestExecutorBuilder addStrategyFactory(ActionContextProvider factory) {
+ Iterables.addAll(strategies, factory.getActionContexts());
+ return this;
+ }
+
+ public TestExecutorBuilder setExecution(String mnemonic, SpawnActionContext strategy) {
+ spawnStrategyMap.put(mnemonic, strategy);
+ strategies.add(strategy);
+ return this;
+ }
+
+ public BlazeExecutor build() throws ExecutorInitException {
+ return new BlazeExecutor(directories.getExecRoot(), directories.getOutputPath(), reporter, bus,
+ BlazeClock.instance(), optionsParser,
+ optionsParser.getOptions(ExecutionOptions.class).verboseFailures,
+ optionsParser.getOptions(ExecutionOptions.class).showSubcommands,
+ strategies,
+ ImmutableMap.copyOf(spawnStrategyMap), ImmutableList.<ActionContextProvider>of());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/MockToolsConfig.java b/src/test/java/com/google/devtools/build/lib/packages/util/MockToolsConfig.java
new file mode 100644
index 0000000000..fcf9b6f562
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/MockToolsConfig.java
@@ -0,0 +1,137 @@
+// 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.packages.util;
+
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.testutil.BlazeTestUtils;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.IOException;
+
+import javax.annotation.Nullable;
+
+/**
+ * Configuration for the mock client setup that we use for testing.
+ */
+public final class MockToolsConfig {
+
+ private final Path rootDirectory;
+ private final boolean realFileSystem;
+
+ // Allow the injection of the runfiles directory where actual tools are found.
+ // TestUtil.getRunfilesDir() caches the value of the "TEST_SRCDIR" system property, which makes
+ // it impossible to change if it doesn't get set early in test configuration setup.
+ private final Path runfilesDirectory;
+
+ public MockToolsConfig(Path rootDirectory) {
+ this(rootDirectory, false, null);
+ }
+
+ public MockToolsConfig(Path rootDirectory, boolean realFileSystem) {
+ this(rootDirectory, realFileSystem, null);
+ }
+
+ public MockToolsConfig(Path rootDirectory, boolean realFileSystem,
+ @Nullable Path runfilesDirectoryOpt) {
+ this.rootDirectory = rootDirectory;
+ this.realFileSystem = realFileSystem;
+ if (runfilesDirectoryOpt == null) {
+ this.runfilesDirectory = rootDirectory.getRelative(BlazeTestUtils.runfilesDir());
+ } else {
+ this.runfilesDirectory = runfilesDirectoryOpt;
+ }
+ }
+
+ public boolean isRealFileSystem() {
+ return realFileSystem;
+ }
+
+ public Path getPath(String relativePath) {
+ return rootDirectory.getRelative(relativePath);
+ }
+
+ public Path create(String relativePath, String... lines) throws IOException {
+ Path path = rootDirectory.getRelative(relativePath);
+ if (!path.exists()) {
+ FileSystemUtils.writeIsoLatin1(path, lines);
+ } else if (lines.length > 0) {
+ String existingContent = new String(FileSystemUtils.readContentAsLatin1(path));
+
+ StringBuilder newContent = new StringBuilder();
+ for (String line : lines) {
+ newContent.append(line);
+ newContent.append("\n");
+ }
+
+ if (!newContent.toString().equals(existingContent)) {
+ throw new IOException("Conflict: '" + relativePath + "':\n'" + newContent + "'\n vs \n'"
+ + existingContent + "'");
+ }
+ }
+ return path;
+ }
+
+ /**
+ * Links a tool into the workspace by creating a symbolic link to a real file. The target location
+ * in the workspace uses the same relative path as the given path to the tool in the runfiles
+ * tree. Use this if you do not need to rename or relocate the file, i.e., if the location in the
+ * workspace and the runfiles tree matches. Otherwise use {@link #linkTool(String, String)}.
+ *
+ * @param relativePath the relative path within the runfiles tree of the current test
+ * @throws IOException
+ */
+ public void linkTool(String relativePath) throws IOException {
+ Preconditions.checkState(realFileSystem);
+ linkTool(relativePath, relativePath);
+ }
+
+ /**
+ * Links a tool into the workspace by creating a symbolic link to a real file.
+ *
+ * @param relativePath the relative path within the runfiles tree of the current test
+ * @param dest the relative path in the mock client
+ * @throws IOException
+ */
+ public void linkTool(String relativePath, String dest) throws IOException {
+ Preconditions.checkState(realFileSystem);
+ Path target = runfilesDirectory.getRelative(TestConstants.RUNFILES_PREFIX + "/" + relativePath);
+ if (!target.exists()) {
+ // In some cases we run tests in a special client with a ../READONLY/ path where we may also
+ // find the runfiles. Try that, too.
+ Path readOnlyClientPath = rootDirectory.getRelative(
+ "../READONLY/" + TestConstants.RUNFILES_PREFIX + "/" + relativePath);
+ if (!readOnlyClientPath.exists()) {
+ throw new IOException("target does not exist " + target);
+ } else {
+ target = readOnlyClientPath;
+ }
+ }
+ Path path = rootDirectory.getRelative(dest);
+ FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
+ path.delete();
+ path.createSymbolicLink(target);
+ }
+
+ /**
+ * Convenience method to link multiple tools. Same as calling {@link #linkTool(String)} for each
+ * parameter.
+ */
+ public void linkTools(String... tools) throws IOException {
+ for (String tool : tools) {
+ linkTool(tool);
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
index 48e948c6d5..28ca6bf7f8 100644
--- a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
+++ b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
@@ -42,6 +42,11 @@ public class TestConstants {
public static final String RUNFILES_PREFIX = "DOES-NOT-WORK-YET";
/**
+ * Name of a class with an INSTANCE field of type AnalysisMock to be used for analysis tests.
+ */
+ public static final String TEST_ANALYSIS_MOCK = "DOES-NOT-WORK-YET";
+
+ /**
* Directory where we can find bazel's Java tests, relative to a test's runfiles directory.
*/
public static final String JAVATESTS_ROOT = "src/test/java/";