diff options
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/analysis/util')
2 files changed, 2041 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCaseForJunit4.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCaseForJunit4.java new file mode 100644 index 0000000000..e8e2b48004 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCaseForJunit4.java @@ -0,0 +1,373 @@ +// Copyright 2015 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.analysis.util; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Iterables; +import com.google.common.eventbus.EventBus; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.ActionGraph; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.BuildView; +import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult; +import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.InputFileConfiguredTarget; +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.BuildOptions; +import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; +import com.google.devtools.build.lib.buildtool.BuildRequest.BuildRequestOptions; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.build.lib.exec.ExecutionOptions; +import com.google.devtools.build.lib.packages.PackageFactory; +import com.google.devtools.build.lib.packages.Preprocessor; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.util.MockToolsConfig; +import com.google.devtools.build.lib.pkgcache.LegacyLoadingPhaseRunner; +import com.google.devtools.build.lib.pkgcache.LoadingOptions; +import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner; +import com.google.devtools.build.lib.pkgcache.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.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.skyframe.SkyValueDirtinessChecker; +import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; +import com.google.devtools.build.lib.testutil.FoundationTestCaseForJunit4; +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.io.TimestampGranularityMonitor; +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.SkyKey; +import com.google.devtools.common.options.Options; +import com.google.devtools.common.options.OptionsParser; + +import org.junit.Before; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Testing framework for tests of the analysis phase that uses the BuildView and LoadingPhaseRunner + * APIs correctly (compared to {@link BuildViewTestCaseForJunit4}). + * + * <p>The intended usage pattern is to first call {@link #update} with the set of targets, and then + * assert properties of the configured targets obtained from {@link #getConfiguredTarget}. + * + * <p>This class intentionally does not inherit from {@link BuildViewTestCase}; BuildViewTestCase + * abuses the BuildView API in ways that are incompatible with the goals of this test, i.e. the + * convenience methods provided there wouldn't work here. + */ +public abstract class AnalysisTestCaseForJunit4 extends FoundationTestCaseForJunit4 { + private static final int LOADING_PHASE_THREADS = 20; + + /** All the flags that can be passed to {@link BuildView#update}. */ + public enum Flag { + KEEP_GOING + } + + /** Helper class to make it easy to enable and disable flags. */ + public static final class FlagBuilder { + private final Set<Flag> flags = new HashSet<>(); + + public FlagBuilder with(Flag flag) { + flags.add(flag); + return this; + } + + public FlagBuilder without(Flag flag) { + flags.remove(flag); + return this; + } + } + + protected BlazeDirectories directories; + protected MockToolsConfig mockToolsConfig; + + private OptionsParser optionsParser; + protected PackageManager packageManager; + private LoadingPhaseRunner loadingPhaseRunner; + private ConfigurationFactory configurationFactory; + private BuildView buildView; + + // Note that these configurations are virtual (they use only VFS) + private BuildConfigurationCollection masterConfig; + + private AnalysisResult analysisResult; + protected SkyframeExecutor skyframeExecutor = null; + protected ConfiguredRuleClassProvider ruleClassProvider; + + protected AnalysisTestUtil.DummyWorkspaceStatusActionFactory workspaceStatusActionFactory; + private PathPackageLocator pkgLocator; + private AnalysisMock analysisMock; + + @Before + public final void createMocks() throws Exception { + analysisMock = AnalysisMock.get(); + directories = new BlazeDirectories(outputBase, outputBase, rootDirectory); + workspaceStatusActionFactory = + new AnalysisTestUtil.DummyWorkspaceStatusActionFactory(directories); + + mockToolsConfig = new MockToolsConfig(rootDirectory); + analysisMock.setupMockClient(mockToolsConfig); + analysisMock.setupMockWorkspaceFiles(directories.getEmbeddedBinariesRoot()); + configurationFactory = analysisMock.createConfigurationFactory(); + } + + @Before + public final void createPkgLocator() throws Exception { + pkgLocator = new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)); + } + + @Before + public final void useCorrectRuleClassProvider() throws Exception { + useRuleClassProvider(TestRuleClassProvider.getRuleClassProvider()); + } + + /** + * Changes the rule class provider to be used for the loading and the analysis phase. + */ + protected void useRuleClassProvider(ConfiguredRuleClassProvider ruleClassProvider) + throws Exception { + this.ruleClassProvider = ruleClassProvider; + PackageFactory pkgFactory = new PackageFactory(ruleClassProvider); + BinTools binTools = BinTools.forUnitTesting(directories, TestConstants.EMBEDDED_TOOLS); + skyframeExecutor = + SequencedSkyframeExecutor.create( + pkgFactory, + new TimestampGranularityMonitor(BlazeClock.instance()), + directories, + binTools, + workspaceStatusActionFactory, + ruleClassProvider.getBuildInfoFactories(), + ImmutableSet.<Path>of(), + ImmutableList.<DiffAwareness.Factory>of(), + Predicates.<PathFragment>alwaysFalse(), + Preprocessor.Factory.Supplier.NullSupplier.INSTANCE, + analysisMock.getSkyFunctions(directories), + getPrecomputedValues(), + ImmutableList.<SkyValueDirtinessChecker>of()); + skyframeExecutor.preparePackageLoading(pkgLocator, + Options.getDefaults(PackageCacheOptions.class).defaultVisibility, true, + 3, ruleClassProvider.getDefaultsPackageContent(), UUID.randomUUID()); + packageManager = skyframeExecutor.getPackageManager(); + loadingPhaseRunner = + new LegacyLoadingPhaseRunner(packageManager, pkgFactory.getRuleClassNames()); + buildView = new BuildView(directories, ruleClassProvider, skyframeExecutor, null); + useConfiguration(); + } + + protected ImmutableList<PrecomputedValue.Injected> getPrecomputedValues() { + return ImmutableList.of(); + } + + protected final void useConfigurationFactory(ConfigurationFactory configurationFactory) { + this.configurationFactory = configurationFactory; + } + + /** + * Sets host and target configuration using the specified options, falling back to the default + * options for unspecified ones, and recreates the build view. + */ + protected final void useConfiguration(String... args) throws Exception { + optionsParser = OptionsParser.newOptionsParser(Iterables.concat(Arrays.asList( + ExecutionOptions.class, + PackageCacheOptions.class, + BuildRequestOptions.class, + BuildView.Options.class), + ruleClassProvider.getConfigurationOptions())); + optionsParser.parse(new String[] {"--default_visibility=public" }); + optionsParser.parse(args); + } + + protected FlagBuilder defaultFlags() { + return new FlagBuilder(); + } + + protected Action getGeneratingAction(Artifact artifact) { + ensureUpdateWasCalled(); + return analysisResult.getActionGraph().getGeneratingAction(artifact); + } + + protected BuildConfigurationCollection getBuildConfigurationCollection() { + return masterConfig; + } + + protected BuildConfiguration getTargetConfiguration() { + return Iterables.getOnlyElement(masterConfig.getTargetConfigurations()); + } + + protected BuildConfiguration getHostConfiguration() { + return masterConfig.getHostConfiguration(); + } + + protected final void ensureUpdateWasCalled() { + Preconditions.checkState(analysisResult != null, "You must run update() first!"); + } + + /** + * Update the BuildView: syncs the package cache; loads and analyzes the given labels. + */ + protected void update(EventBus eventBus, FlagBuilder config, String... labels) throws Exception { + Set<Flag> flags = config.flags; + + LoadingOptions loadingOptions = Options.getDefaults(LoadingOptions.class); + loadingOptions.loadingPhaseThreads = LOADING_PHASE_THREADS; + + BuildView.Options viewOptions = optionsParser.getOptions(BuildView.Options.class); + viewOptions.keepGoing = flags.contains(Flag.KEEP_GOING); + + BuildOptions buildOptions = ruleClassProvider.createBuildOptions(optionsParser); + PackageCacheOptions packageCacheOptions = optionsParser.getOptions(PackageCacheOptions.class); + + PathPackageLocator pathPackageLocator = PathPackageLocator.create( + null, packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory); + skyframeExecutor.preparePackageLoading(pathPackageLocator, + packageCacheOptions.defaultVisibility, true, + 7, ruleClassProvider.getDefaultsPackageContent(), UUID.randomUUID()); + skyframeExecutor.invalidateFilesUnderPathForTesting(reporter, + ModifiedFileSet.EVERYTHING_MODIFIED, rootDirectory); + + LoadingResult loadingResult = loadingPhaseRunner + .execute(reporter, eventBus, ImmutableList.copyOf(labels), loadingOptions, + buildOptions.getAllLabels(), viewOptions.keepGoing, isLoadingEnabled(), + /*determineTests=*/false, /*callback=*/null); + + BuildRequestOptions requestOptions = optionsParser.getOptions(BuildRequestOptions.class); + ImmutableSortedSet<String> multiCpu = ImmutableSortedSet.copyOf(requestOptions.multiCpus); + masterConfig = skyframeExecutor.createConfigurations( + reporter, configurationFactory, buildOptions, directories, multiCpu, false); + analysisResult = + buildView.update( + loadingResult, + masterConfig, + ImmutableList.<String>of(), + viewOptions, + AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT, + reporter, + eventBus, + isLoadingEnabled()); + } + + protected void update(FlagBuilder config, String... labels) throws Exception { + update(new EventBus(), config, labels); + } + + /** + * Update the BuildView: syncs the package cache; loads and analyzes the given labels. + */ + protected void update(String... labels) throws Exception { + update(new EventBus(), defaultFlags(), labels); + } + + protected Target getTarget(String label) { + try { + return SkyframeExecutorTestUtils.getExistingTarget(skyframeExecutor, + Label.parseAbsolute(label)); + } catch (LabelSyntaxException e) { + throw new AssertionError(e); + } + } + + protected ConfiguredTarget getConfiguredTarget(String label, BuildConfiguration configuration) { + ensureUpdateWasCalled(); + return getConfiguredTargetForSkyframe(label, configuration); + } + + private ConfiguredTarget getConfiguredTargetForSkyframe(String label, + BuildConfiguration configuration) { + Label parsedLabel; + try { + parsedLabel = Label.parseAbsolute(label); + } catch (LabelSyntaxException e) { + throw new AssertionError(e); + } + return skyframeExecutor.getConfiguredTargetForTesting(reporter, parsedLabel, configuration); + } + + /** + * Returns the corresponding configured target, if it exists. Note that this will only return + * anything useful after a call to update() with the same label. + */ + protected ConfiguredTarget getConfiguredTarget(String label) { + return getConfiguredTarget(label, getTargetConfiguration()); + } + + /** + * Returns the corresponding configured target, if it exists. Note that this will only return + * anything useful after a call to update() with the same label. The label passed in must + * represent an input file. + */ + protected InputFileConfiguredTarget getInputFileConfiguredTarget(String label) { + return (InputFileConfiguredTarget) getConfiguredTarget(label, null); + } + + protected boolean hasErrors(ConfiguredTarget configuredTarget) { + return buildView.hasErrors(configuredTarget); + } + + protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner) { + Label label = owner.getLabel(); + return buildView.getArtifactFactory().getDerivedArtifact( + label.getPackageFragment().getRelative(packageRelativePath), + getTargetConfiguration().getBinDirectory(), + new ConfiguredTargetKey(owner)); + } + + protected Set<SkyKey> getSkyframeEvaluatedTargetKeys() { + return buildView.getSkyframeEvaluatedTargetKeysForTesting(); + } + + protected int getTargetsVisited() { + return buildView.getTargetsVisited(); + } + + protected String getAnalysisError() { + ensureUpdateWasCalled(); + return analysisResult.getError(); + } + + protected BuildView getView() { + return buildView; + } + + protected ActionGraph getActionGraph() { + return skyframeExecutor.getActionGraph(reporter); + } + + protected AnalysisResult getAnalysisResult() { + return analysisResult; + } + + protected void clearAnalysisResult() { + analysisResult = null; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCaseForJunit4.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCaseForJunit4.java new file mode 100644 index 0000000000..16061cba99 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCaseForJunit4.java @@ -0,0 +1,1668 @@ +// Copyright 2015 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.analysis.util; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +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.base.Splitter; +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.ActionGraph; +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.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.AnalysisEnvironment; +import com.google.devtools.build.lib.analysis.AnalysisUtils; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.analysis.BuildView; +import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult; +import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment; +import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory; +import com.google.devtools.build.lib.analysis.ConfiguredAttributeMapper; +import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider; +import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider.ExtraArtifactSet; +import com.google.devtools.build.lib.analysis.FileConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.LabelAndConfiguration; +import com.google.devtools.build.lib.analysis.OutputGroupProvider; +import com.google.devtools.build.lib.analysis.PseudoAction; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.analysis.SourceManifestAction; +import com.google.devtools.build.lib.analysis.SymlinkTreeAction; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; +import com.google.devtools.build.lib.analysis.WorkspaceStatusAction; +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.BuildOptions; +import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; +import com.google.devtools.build.lib.buildtool.BuildRequest; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +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.AspectParameters; +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.NativeAspectClass; +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.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.LegacyLoadingPhaseRunner; +import com.google.devtools.build.lib.pkgcache.LoadingOptions; +import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner; +import com.google.devtools.build.lib.pkgcache.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.extra.ExtraAction; +import com.google.devtools.build.lib.rules.test.BaselineCoverageAction; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.skyframe.AspectValue; +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.skyframe.SkyValueDirtinessChecker; +import com.google.devtools.build.lib.testutil.FoundationTestCaseForJunit4; +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.common.options.Options; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; + +import org.junit.Before; + +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.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Common test code that creates a BuildView instance. + */ +public abstract class BuildViewTestCaseForJunit4 extends FoundationTestCaseForJunit4 { + 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; + private PackageFactory pkgFactory; + + protected MockToolsConfig mockToolsConfig; + + protected WorkspaceStatusAction.Factory workspaceStatusActionFactory; + + private MutableActionGraph mutableActionGraph; + + @Before + public final void initializeSkyframeExecutor() throws Exception { + AnalysisMock mock = getAnalysisMock(); + directories = new BlazeDirectories(outputBase, outputBase, rootDirectory); + binTools = BinTools.forUnitTesting(directories, TestConstants.EMBEDDED_TOOLS); + mockToolsConfig = new MockToolsConfig(rootDirectory, false); + mock.setupMockClient(mockToolsConfig); + mock.setupMockWorkspaceFiles(directories.getEmbeddedBinariesRoot()); + + configurationFactory = mock.createConfigurationFactory(); + packageCacheOptions = parsePackageCacheOptions(); + workspaceStatusActionFactory = + new AnalysisTestUtil.DummyWorkspaceStatusActionFactory(directories); + mutableActionGraph = new MapBasedActionGraph(); + ruleClassProvider = getRuleClassProvider(); + pkgFactory = new PackageFactory(ruleClassProvider, getEnvironmentExtensions()); + skyframeExecutor = + SequencedSkyframeExecutor.create( + pkgFactory, + new TimestampGranularityMonitor(BlazeClock.instance()), + directories, + binTools, + workspaceStatusActionFactory, + ruleClassProvider.getBuildInfoFactories(), + ImmutableSet.<Path>of(), + ImmutableList.<DiffAwareness.Factory>of(), + Predicates.<PathFragment>alwaysFalse(), + getPreprocessorFactorySupplier(), + mock.getSkyFunctions(directories), + getPrecomputedValues(), + ImmutableList.<SkyValueDirtinessChecker>of()); + skyframeExecutor.preparePackageLoading( + new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)), + ConstantRuleVisibility.PUBLIC, true, 7, "", + 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 PackageFactory getPackageFactory() { + return pkgFactory; + } + + 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()); + skyframeExecutor.invalidateConfigurationCollection(); + return skyframeExecutor.createConfigurations(reporter, configurationFactory, buildOptions, + directories, ImmutableSet.<String>of(), false); + } catch (InvalidConfigurationException | OptionsParsingException e) { + throw new IllegalArgumentException(e); + } + } + + protected Target getTarget(String label) + throws NoSuchPackageException, NoSuchTargetException, + LabelSyntaxException, 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( + null, packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory); + skyframeExecutor.preparePackageLoading(pkgLocator, + packageCacheOptions.defaultVisibility, true, + 7, 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(); + } + + /** + * Invalidates all existing packages. + * @throws InterruptedException + */ + protected void invalidatePackages() throws InterruptedException { + skyframeExecutor.invalidateFilesUnderPathForTesting(reporter, + 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().isHostConfiguration(), + "Host configuration %s is not a host configuration' " + + "and does not match target configuration %s", + getHostConfiguration(), getTargetConfiguration()); + + String defaultsPackageContent = ruleClassProvider.getDefaultsPackageContent(optionsParser); + skyframeExecutor.setupDefaultPackage(defaultsPackageContent); + skyframeExecutor.dropConfiguredTargets(); + + view = new BuildView(directories, ruleClassProvider, skyframeExecutor, null); + view.setConfigurationsForTesting(masterConfig); + + view.setArtifactRoots( + ImmutableMap.of(PackageIdentifier.createInDefaultRepo(""), rootDirectory), masterConfig); + 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) + throws InterruptedException { + return view.getDirectPrerequisitesForTesting(reporter, target, masterConfig); + } + + /** + * Asserts that a target's prerequisites contain the given dependency. + */ + // TODO(bazel-team): replace this method with assertThat(iterable).contains(target). + // That doesn't work now because dynamic configurations aren't yet applied to top-level targets. + // This means that getConfiguredTarget("//go:two") returns a different configuration than + // requesting "//go:two" as a dependency. So the configured targets aren't considered "equal". + // Once we apply dynamic configs to top-level targets this discrepancy will go away. + protected void assertDirectPrerequisitesContain(ConfiguredTarget target, ConfiguredTarget dep) + throws InterruptedException { + Iterable<ConfiguredTarget> prereqs = getDirectPrerequisites(target); + BuildConfiguration depConfig = dep.getConfiguration(); + for (ConfiguredTarget contained : prereqs) { + if (contained.getLabel().equals(dep.getLabel())) { + BuildConfiguration containedConfig = contained.getConfiguration(); + if (containedConfig == null && depConfig == null) { + return; + } else if (containedConfig != null + && depConfig != null + && containedConfig.cloneOptions().equals(depConfig.cloneOptions())) { + return; + } + } + } + fail("Cannot find " + target.toString() + " in " + prereqs.toString()); + } + + /** + * Asserts that two configurations are the same. + * + * <p>Historically this meant they contained the same object reference. But with upcoming dynamic + * configurations that may no longer be true (for example, they may have the same values but not + * the same {@link BuildConfiguration.Fragment}s. So this method abstracts the + * "configuration equivalency" checking into one place, where the implementation logic can evolve + * as needed. + */ + protected void assertConfigurationsEqual(BuildConfiguration config1, BuildConfiguration config2) { + // BuildOptions and crosstool files determine a configuration's content. Within the context + // of these tests only the former actually change. + assertEquals(config1.cloneOptions(), config2.cloneOptions()); + } + + /** + * 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) throws InterruptedException { + return view.getRuleContextForTesting( + reporter, target, new StubAnalysisEnvironment(), masterConfig); + } + + /** + * 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) + throws InterruptedException { + // 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, masterConfig, 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 List<? extends TransitiveInfoCollection> getPrerequisites(ConfiguredTarget target, + String attributeName) throws InterruptedException { + 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) throws InterruptedException { + 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) throws InterruptedException { + Set<Artifact> result = new LinkedHashSet<>(); + for (FileProvider provider : getPrerequisites(target, attributeName, FileProvider.class)) { + Iterables.addAll(result, provider.getFilesToBuild()); + } + return ImmutableList.copyOf(result); + } + + protected ActionGraph getActionGraph() { + return skyframeExecutor.getActionGraph(reporter); + } + + protected final Action getGeneratingAction(Artifact artifact) { + Preconditions.checkNotNull(artifact); + Action action = mutableActionGraph.getGeneratingAction(artifact); + if (action != null) { + return action; + } + return getActionGraph().getGeneratingAction(artifact); + } + + /** + * Returns the SpawnAction that generates an artifact. + * Implicitly assumes the action is a SpawnAction. + */ + protected final SpawnAction getGeneratingSpawnAction(Artifact artifact) { + return (SpawnAction) getGeneratingAction(artifact); + } + + protected void simulateLoadingPhase() { + try { + ensureTargetsVisited(targetConfig.getAllLabels().values()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected ActionsTestUtil actionsTestUtil() { + return new ActionsTestUtil(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, + LabelSyntaxException { + 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, + LabelSyntaxException, 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, + LabelSyntaxException, InterruptedException { + return getConfiguredTarget(Label.parseAbsolute(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(reporter, 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, + LabelSyntaxException, 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, + LabelSyntaxException, 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, + LabelSyntaxException, 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 { + String buildFilePathString = packageName + "/BUILD"; + scratch.file(buildFilePathString, lines); + skyframeExecutor.invalidateFilesUnderPathForTesting( + reporter, + new ModifiedFileSet.Builder().modify(new PathFragment(buildFilePathString)).build(), + rootDirectory); + 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(reporter, rule.getLabel(), 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) { + return getSourceArtifact(new PathFragment(name), Root.asSourceRoot(rootDirectory)); + } + + /** + * 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}, + * where the given artifact belongs to the given ConfiguredTarget together with the given Aspect. + * So to specify a file foo/foo.o owned by target //foo:foo with an aspect from FooAspect, + * {@code packageRelativePath} should just be "foo.o", and aspectOfOwner should be + * FooAspect.class. This method is necessary when an Aspect of the target, not the target itself, + * is creating an Artifact. + */ + protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner, + Class<? extends ConfiguredAspectFactory> creatingAspectFactory) { + return getPackageRelativeDerivedArtifact( + packageRelativePath, + owner.getConfiguration().getBinDirectory(), + (AspectValue.AspectKey) + AspectValue.key( + owner.getLabel(), + owner.getConfiguration(), + new NativeAspectClass(creatingAspectFactory), + AspectParameters.EMPTY) + .argument()); + } + + /** + * 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}, + * where the given artifact belongs to the given ConfiguredTarget together with the given Aspect. + * So to specify a file foo/foo.o owned by target //foo:foo with an apsect from FooAspect, + * {@code packageRelativePath} should just be "foo.o", and aspectOfOwner should be + * FooAspect.class. This method is necessary when an Apsect of the target, not the target itself, + * is creating an Artifact. + */ + protected Artifact getGenfilesArtifact(String packageRelativePath, ConfiguredTarget owner, + Class<? extends ConfiguredAspectFactory> creatingAspectFactory) { + return getPackageRelativeDerivedArtifact( + packageRelativePath, + owner.getConfiguration().getGenfilesDirectory(), + (AspectValue.AspectKey) + AspectValue.key( + owner.getLabel(), + owner.getConfiguration(), + new NativeAspectClass(creatingAspectFactory), + AspectParameters.EMPTY) + .argument()); + } + + /** + * 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); + } + + /** + * Gets a derived Artifact for testing in the subdirectory of the {@link + * BuildConfiguration#getIncludeDirectory()} 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.h". + */ + protected Artifact getIncludeArtifact(String packageRelativePath, String owner) { + return getIncludeArtifact(packageRelativePath, makeLabelAndConfiguration(owner)); + } + + /** + * Gets a derived Artifact for testing in the subdirectory of the {@link + * BuildConfiguration#getIncludeDirectory()} 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.h". + */ + private Artifact getIncludeArtifact(String packageRelativePath, ArtifactOwner owner) { + return getPackageRelativeDerivedArtifact(packageRelativePath, + targetConfig.getIncludeDirectory(), + owner); + } + + /** + * @return a shared artifact at the binary-root relative path {@code rootRelativePath} owned by + * {@code owner}. + * + * @param rootRelativePath the binary-root relative path of the artifact. + * @param owner the artifact's owner. + */ + protected Artifact getSharedArtifact(String rootRelativePath, ConfiguredTarget owner) { + return getDerivedArtifact(new PathFragment(rootRelativePath), targetConfig.getBinDirectory(), + new ConfiguredTargetKey(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 (LabelSyntaxException 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) { + assertThat(Iterables.concat(Lists.newArrayList(expectedInputs), common)) + .containsExactlyElementsIn(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) { + assertThat(Iterables.concat(Lists.newArrayList(expectedInputs), common)) + .containsExactlyElementsIn(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); + } + + /** + * Returns all extra actions for that target (including transitive actions). + */ + protected ImmutableList<ExtraAction> getTransitiveExtraActionActions(ConfiguredTarget target) { + ImmutableList.Builder<ExtraAction> result = new ImmutableList.Builder<>(); + for (ExtraArtifactSet set : target.getProvider(ExtraActionArtifactsProvider.class) + .getTransitiveExtraActionArtifacts()) { + for (Artifact artifact : set.getArtifacts()) { + Action action = getGeneratingAction(artifact); + if (action instanceof ExtraAction) { + result.add((ExtraAction) action); + } + } + } + return result.build(); + } + + 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) { + OutputGroupProvider provider = target.getProvider(OutputGroupProvider.class); + return provider == null + ? NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER) + : provider.getOutputGroup(outputGroup); + } + + protected NestedSet<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() { + BuildConfiguration targetConfig = getTargetConfiguration(); + // TODO(bazel-team): do a proper data transition for dynamic configurations. + return targetConfig.useDynamicConfigurations() + ? targetConfig + : targetConfig.getConfiguration(ConfigurationTransition.DATA); + } + + protected BuildConfiguration getHostConfiguration() { + return masterConfig.getHostConfiguration(); + } + + /** + * 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 { + return update( + targets, ImmutableList.<String>of(), keepGoing, loadingPhaseThreads, doAnalysis, eventBus); + } + + protected AnalysisResult update( + List<String> targets, + List<String> aspects, + boolean keepGoing, + int loadingPhaseThreads, + boolean doAnalysis, + EventBus eventBus) + throws Exception { + + LoadingOptions loadingOptions = Options.getDefaults(LoadingOptions.class); + loadingOptions.loadingPhaseThreads = loadingPhaseThreads; + + BuildView.Options viewOptions = Options.getDefaults(BuildView.Options.class); + viewOptions.keepGoing = keepGoing; + + LoadingPhaseRunner runner = new LegacyLoadingPhaseRunner(getPackageManager(), + Collections.unmodifiableSet(ruleClassProvider.getRuleClassMap().keySet())); + LoadingResult loadingResult = runner.execute(reporter, eventBus, targets, loadingOptions, + getTargetConfiguration().getAllLabels(), viewOptions.keepGoing, + isLoadingEnabled(), /*determineTests=*/false, /*callback=*/null); + if (!doAnalysis) { + // TODO(bazel-team): What's supposed to happen in this case? + return null; + } + return view.update( + loadingResult, + masterConfig, + aspects, + viewOptions, + AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT, + reporter, + eventBus, + isLoadingEnabled()); + } + + 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 LabelSyntaxException if there are any syntax errors in the strings. + */ + public static Set<Label> asLabelSet(String... strings) throws LabelSyntaxException { + 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 LabelSyntaxException if there are any syntax errors in the strings. + */ + public static Set<Label> asLabelSet(Iterable<String> strings) throws LabelSyntaxException { + Set<Label> result = Sets.newTreeSet(); + for (String s : strings) { + result.add(Label.parseAbsolute(s)); + } + return result; + } + + protected SpawnAction getGeneratingAction(ConfiguredTarget target, String outputName) { + return getGeneratingSpawnAction( + Iterables.find(getFilesToBuild(target), artifactNamed(outputName))); + } + + 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"; + } + + protected 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); + } + + protected String getErrorMsgMandatoryProviderMissing(String offendingRule, String providerName) { + return String.format("'%s' does not have mandatory provider '%s'", offendingRule, providerName); + } + + /** + * Utility method for tests that result in errors early during + * package loading. Given the name of the package for the test, + * and the rules for the build file, create a scratch file, load + * the build file, and produce the package. + * @param packageName the name of the package for the build file + * @param lines the rules for the build file as an array of strings + * @return the loaded package from the populated package cache + * @throws Exception if there is an error creating the temporary files + * for the test. + */ + protected com.google.devtools.build.lib.packages.Package createScratchPackageForImplicitCycle( + String packageName, String... lines) throws Exception { + eventCollector.clear(); + reporter.removeHandler(failFastHandler); + scratch.file("" + packageName + "/BUILD", lines); + return getPackageManager() + .getPackage(reporter, PackageIdentifier.createInDefaultRepo(packageName)); + } + + /** + * A stub analysis environment. + */ + protected 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 { + ImmutableList.Builder<String> basenames = ImmutableList.builder(); + for (Artifact baselineCoverage : target + .getProvider(InstrumentedFilesProvider.class) + .getBaselineCoverageArtifacts()) { + BaselineCoverageAction baselineAction = + (BaselineCoverageAction) getGeneratingAction(baselineCoverage); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + baselineAction.newDeterministicWriter(null, null).writeOutputFile(bytes); + + 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(); + } + + /** + * Finds an artifact in the transitive closure of a set of other artifacts by following a path + * based on artifact name suffixes. + * + * <p>This selects the first artifact in the input set that matches the first suffix, then selects + * the first artifact in the inputs of its generating action that matches the second suffix etc., + * and repeats this until the supplied suffixes run out. + */ + protected Artifact artifactByPath(Iterable<Artifact> artifacts, String... suffixes) { + Artifact artifact = getFirstArtifactEndingWith(artifacts, suffixes[0]); + Action action = null; + for (int i = 1; i < suffixes.length; i++) { + if (artifact == null) { + if (action == null) { + throw new IllegalStateException("No suffix " + suffixes[0] + " among artifacts: " + + ActionsTestUtil.baseArtifactNames(artifacts)); + } else { + throw new IllegalStateException("No suffix " + suffixes[i] + + " among inputs of action " + action.describe() + ": " + + ActionsTestUtil.baseArtifactNames(artifacts)); + } + } + + action = getGeneratingAction(artifact); + artifacts = action.getInputs(); + artifact = getFirstArtifactEndingWith(artifacts, suffixes[i]); + } + + return artifact; + } + + /** + * Retrieves an instance of {@code PseudoAction} that is shadowed by an extra action + * @param targetLabel Label of the target with an extra action + * @param actionListenerLabel Label of the action listener + */ + protected PseudoAction<?> getPseudoActionViaExtraAction( + String targetLabel, String actionListenerLabel) throws Exception { + useConfiguration(String.format("--experimental_action_listener=%s", actionListenerLabel)); + + ConfiguredTarget target = getConfiguredTarget(targetLabel); + List<Action> actions = getExtraActionActions(target); + + assertNotNull(actions); + assertThat(actions).hasSize(2); + + ExtraAction extraAction = null; + + for (Action action : actions) { + if (action instanceof ExtraAction) { + extraAction = (ExtraAction) action; + break; + } + } + + assertNotNull(actions.toString(), extraAction); + + Action pseudoAction = extraAction.getShadowedAction(); + + assertThat(pseudoAction).isInstanceOf(PseudoAction.class); + assertEquals( + String.format("%s%s.extra_action_dummy", targetConfig.getGenfilesFragment(), + convertLabelToPath(targetLabel)), + pseudoAction.getPrimaryOutput().getExecPathString()); + + return (PseudoAction<?>) pseudoAction; + } + + /** + * Converts the given label to an output path where double slashes and colons are + * replaced with single slashes + * @param label + */ + private String convertLabelToPath(String label) { + return label.replace(':', '/').substring(1); + } + + protected Map<String, String> getSymlinkTreeManifest(Artifact outputManifest) throws Exception { + SymlinkTreeAction symlinkTreeAction = (SymlinkTreeAction) getGeneratingAction(outputManifest); + Artifact inputManifest = Iterables.getOnlyElement(symlinkTreeAction.getInputs()); + SourceManifestAction inputManifestAction = + (SourceManifestAction) getGeneratingAction(inputManifest); + // Ask the manifest to write itself to a byte array so that we can + // read its contents. + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + inputManifestAction.writeOutputFile(stream, reporter); + String contents = stream.toString(); + + // Get the file names from the manifest output. + ImmutableMap.Builder<String, String> result = ImmutableMap.builder(); + for (String line : Splitter.on('\n').split(contents)) { + int space = line.indexOf(' '); + if (space < 0) { + continue; + } + result.put(line.substring(0, space), line.substring(space + 1)); + } + + return result.build(); + } +} |