// 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.common.truth.Truth.assertWithMessage; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; import static org.junit.Assert.fail; import com.google.common.base.Function; 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.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.ActionGraph; import com.google.devtools.build.lib.actions.ActionInput; import com.google.devtools.build.lib.actions.ActionLookupValue; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.ArtifactOwner; import com.google.devtools.build.lib.actions.CommandLineExpansionException; 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.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.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.ServerDirectories; import com.google.devtools.build.lib.analysis.SourceManifestAction; 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.ParameterFileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.SymlinkTreeAction; 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.BuildConfiguration.Options.ConfigsMode; 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.PatchTransition; import com.google.devtools.build.lib.analysis.extra.ExtraAction; import com.google.devtools.build.lib.analysis.test.BaselineCoverageAction; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider; import com.google.devtools.build.lib.buildtool.BuildRequest; import com.google.devtools.build.lib.clock.BlazeClock; 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.cmdline.RepositoryName; 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.ExtendedEventHandler; import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.exec.ExecutionOptions; import com.google.devtools.build.lib.packages.AspectClass; import com.google.devtools.build.lib.packages.AspectDescriptor; import com.google.devtools.build.lib.packages.AspectParameters; import com.google.devtools.build.lib.packages.Attribute; 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.ImplicitOutputsFunction.SafeImplicitOutputsFunction; 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.RawAttributeMapper; 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.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.rules.repository.RepositoryDelegatorFunction; import com.google.devtools.build.lib.skyframe.AspectValue; import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants; import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; import com.google.devtools.build.lib.skyframe.DiffAwareness; import com.google.devtools.build.lib.skyframe.LegacyLoadingPhaseRunner; 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.syntax.SkylarkSemanticsOptions; import com.google.devtools.build.lib.testutil.BlazeTestUtils; import com.google.devtools.build.lib.testutil.FoundationTestCase; import com.google.devtools.build.lib.testutil.TestConstants; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.Preconditions; 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.ErrorInfo; import com.google.devtools.build.skyframe.MemoizingEvaluator; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.common.options.InvocationPolicyEnforcer; import com.google.devtools.common.options.Options; import com.google.devtools.common.options.OptionsParser; import java.io.ByteArrayOutputStream; import java.io.IOException; 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; import javax.annotation.Nullable; import org.junit.Before; /** * Common test code that creates a BuildView instance. */ public abstract class BuildViewTestCase extends FoundationTestCase { protected static final int LOADING_PHASE_THREADS = 20; protected AnalysisMock analysisMock; protected ConfiguredRuleClassProvider ruleClassProvider; protected BuildView view; protected SequencedSkyframeExecutor skyframeExecutor; protected TimestampGranularityMonitor tsgm; 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 private List configurationArgs; private ConfigsMode configsMode = ConfigsMode.NOTRIM; protected OptionsParser optionsParser; private PackageCacheOptions packageCacheOptions; private SkylarkSemanticsOptions skylarkSemanticsOptions; protected PackageFactory pkgFactory; protected MockToolsConfig mockToolsConfig; protected WorkspaceStatusAction.Factory workspaceStatusActionFactory; private MutableActionGraph mutableActionGraph; @Before public final void initializeSkyframeExecutor() throws Exception { analysisMock = getAnalysisMock(); directories = new BlazeDirectories( new ServerDirectories(outputBase, outputBase), rootDirectory, analysisMock.getProductName()); binTools = BinTools.forUnitTesting(directories, analysisMock.getEmbeddedTools()); mockToolsConfig = new MockToolsConfig(rootDirectory, false); analysisMock.setupMockClient(mockToolsConfig); analysisMock.setupMockWorkspaceFiles(directories.getEmbeddedBinariesRoot()); packageCacheOptions = parsePackageCacheOptions(); skylarkSemanticsOptions = parseSkylarkSemanticsOptions(); workspaceStatusActionFactory = new AnalysisTestUtil.DummyWorkspaceStatusActionFactory(directories); mutableActionGraph = new MapBasedActionGraph(); ruleClassProvider = getRuleClassProvider(); ImmutableList extraPrecomputedValues = ImmutableList.of( PrecomputedValue.injected( RepositoryDelegatorFunction.REPOSITORY_OVERRIDES, ImmutableMap.of())); pkgFactory = analysisMock .getPackageFactoryBuilderForTesting() .setExtraPrecomputeValues(extraPrecomputedValues) .setEnvironmentExtensions(getEnvironmentExtensions()) .setPlatformSetRegexps(getPlatformSetRegexps()) .build(ruleClassProvider, scratch.getFileSystem()); tsgm = new TimestampGranularityMonitor(BlazeClock.instance()); skyframeExecutor = SequencedSkyframeExecutor.create( pkgFactory, directories, binTools, workspaceStatusActionFactory, ruleClassProvider.getBuildInfoFactories(), ImmutableList.of(), Predicates.alwaysFalse(), analysisMock.getSkyFunctions(), ImmutableList.of(), ImmutableList.of(), PathFragment.EMPTY_FRAGMENT, analysisMock.getProductName(), BazelSkyframeExecutorConstants.CROSS_REPOSITORY_LABEL_VIOLATION_STRATEGY, BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY, BazelSkyframeExecutorConstants.ACTION_ON_IO_EXCEPTION_READING_BUILD_FILE); TestConstants.processSkyframeExecutorForTesting(skyframeExecutor); skyframeExecutor.injectExtraPrecomputedValues(extraPrecomputedValues); packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PUBLIC; packageCacheOptions.showLoadingProgress = true; packageCacheOptions.globbingThreads = 7; skyframeExecutor.preparePackageLoading( new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)), packageCacheOptions, skylarkSemanticsOptions, "", UUID.randomUUID(), ImmutableMap.of(), ImmutableMap.of(), tsgm); useConfiguration(); setUpSkyframe(); // Also initializes ResourceManager. ResourceManager.instance().setAvailableResources(getStartingResources()); } protected Map getPlatformSetRegexps() { return null; } protected AnalysisMock getAnalysisMock() { return AnalysisMock.get(); } /** Creates or retrieves the rule class provider used in this test. */ protected ConfiguredRuleClassProvider getRuleClassProvider() { return getAnalysisMock().createRuleClassProvider(); } protected PackageFactory getPackageFactory() { return pkgFactory; } protected Iterable getEnvironmentExtensions() { return ImmutableList.of(); } protected SkylarkSemanticsOptions getSkylarkSemantics() { return skylarkSemanticsOptions; } 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())); List allArgs = new ArrayList<>(); // TODO(dmarting): Add --stamp option only to test that requires it. allArgs.add("--stamp"); // Stamp is now defaulted to false. allArgs.add("--experimental_extended_sanity_checks"); allArgs.add("--features=cc_include_scanning"); allArgs.addAll(getAnalysisMock().getOptionOverrides()); optionsParser.parse(allArgs); optionsParser.parse(args); InvocationPolicyEnforcer optionsPolicyEnforcer = getAnalysisMock().getInvocationPolicyEnforcer(); optionsPolicyEnforcer.enforce(optionsParser); BuildOptions buildOptions = ruleClassProvider.createBuildOptions(optionsParser); skyframeExecutor.invalidateConfigurationCollection(); return skyframeExecutor.createConfigurations( reporter, ruleClassProvider.getConfigurationFragments(), buildOptions, ImmutableSet.of(), false); } 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); } /** * Checks that loading the given target fails with the expected error message. * *

Fails with an assertion error if this doesn't happen. * *

This method is useful for checking loading phase errors. Analysis phase errors can be * checked with {@link #getConfiguredTarget} and related methods. */ protected void assertTargetError(String label, String expectedError) throws InterruptedException { try { getTarget(label); fail("Expected loading phase failure for target " + label); } catch (NoSuchPackageException | NoSuchTargetException | LabelSyntaxException e) { // Target loading failed as expected. } assertContainsEvent(expectedError); } private void setUpSkyframe() { PathPackageLocator pkgLocator = PathPackageLocator.create( outputBase, packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory); packageCacheOptions.showLoadingProgress = true; packageCacheOptions.globbingThreads = 7; skyframeExecutor.preparePackageLoading( pkgLocator, packageCacheOptions, skylarkSemanticsOptions, ruleClassProvider.getDefaultsPackageContent(optionsParser), UUID.randomUUID(), ImmutableMap.of(), ImmutableMap.of(), tsgm); skyframeExecutor.setDeletedPackages(ImmutableSet.copyOf(packageCacheOptions.getDeletedPackages())); } protected void setPackageCacheOptions(String... options) throws Exception { packageCacheOptions = parsePackageCacheOptions(options); setUpSkyframe(); } protected void setSkylarkSemanticsOptions(String... options) throws Exception { skylarkSemanticsOptions = parseSkylarkSemanticsOptions(options); setUpSkyframe(); } private static 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); } private static SkylarkSemanticsOptions parseSkylarkSemanticsOptions(String... options) throws Exception { OptionsParser parser = OptionsParser.newOptionsParser(SkylarkSemanticsOptions.class); parser.parse(options); return parser.getOptions(SkylarkSemanticsOptions.class); } /** Used by skyframe-only tests. */ protected SequencedSkyframeExecutor getSkyframeExecutor() { return Preconditions.checkNotNull(skyframeExecutor); } protected PackageManager getPackageManager() { return skyframeExecutor.getPackageManager(); } protected void invalidatePackages() throws InterruptedException { invalidatePackages(true); } /** * Invalidates all existing packages. Optionally invalidates configurations too. * *

Tests should invalidate both unless they have specific reason not to. * * @throws InterruptedException */ protected void invalidatePackages(boolean alsoConfigs) throws InterruptedException { skyframeExecutor.invalidateFilesUnderPathForTesting(reporter, ModifiedFileSet.EVERYTHING_MODIFIED, rootDirectory); if (alsoConfigs) { try { // Also invalidate all configurations. This is important: by invalidating all files we // invalidate CROSSTOOL, which invalidates CppConfiguration (and a few other fragments). So // we need to invalidate the {@link SkyframeBuildView#hostConfigurationCache} as well. // Otherwise we end up with old CppConfiguration instances. Even though they're logically // equal to the new ones, CppConfiguration has no .equals() method and some production code // expects equality. useConfiguration(configurationArgs.toArray(new String[0])); } catch (Exception e) { // There are enough dependers on this method that don't handle Exception that just passing // through the Exception would result in a huge refactoring. As it stands this shouldn't // fail anyway because this method only gets called after a successful useConfiguration() // call anyway. throw new RuntimeException(e); } } } /** * 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 void useConfiguration(String... args) throws Exception { String[] actualArgs; actualArgs = Arrays.copyOf(args, args.length + 1); actualArgs[args.length] = "--experimental_dynamic_configs=" + configsMode.toString().toLowerCase(); masterConfig = createConfigurations(actualArgs); targetConfig = getTargetConfiguration(); configurationArgs = Arrays.asList(actualArgs); createBuildView(); } /** * Makes subsequent {@link #useConfiguration} calls automatically use the specified style for * configurations. */ protected final void useConfigurationMode(ConfigsMode mode) { configsMode = mode; } /** * 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().equals(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.createInMainRepo(""), rootDirectory)); } protected CachingAnalysisEnvironment getTestAnalysisEnvironment() { return new CachingAnalysisEnvironment( view.getArtifactFactory(), ArtifactOwner.NULL_OWNER, /*isSystemEnv=*/ true, /*extendedSanityChecks*/ false, reporter, /* env= */ null, /* allowRegisteringActions= */ true); } /** * 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 getDirectPrerequisites(ConfiguredTarget target) throws Exception { return view.getDirectPrerequisitesForTesting(reporter, target, masterConfig); } protected ConfiguredTarget getDirectPrerequisite(ConfiguredTarget target, String label) throws Exception { Label candidateLabel = Label.parseAbsolute(label); for (ConfiguredTarget candidate : getDirectPrerequisites(target)) { if (candidate.getLabel().equals(candidateLabel)) { return candidate; } } return null; } /** * Asserts that two configurations are the same. * *

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. assertThat(config2.cloneOptions()).isEqualTo(config1.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 Exception { return view.getRuleContextForTesting( reporter, target, new StubAnalysisEnvironment(), masterConfig); } protected RuleContext getRuleContext(ConfiguredTarget target, AnalysisEnvironment analysisEnvironment) throws Exception { return view.getRuleContextForTesting( reporter, target, analysisEnvironment, 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 Exception { // 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); } /** * 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 getPrerequisites(ConfiguredTarget target, String attributeName) throws Exception { 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 Iterable getPrerequisites(ConfiguredTarget target, String attributeName, Class classType) throws Exception { 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 getPrerequisiteArtifacts( ConfiguredTarget target, String attributeName) throws Exception { Set 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); ActionAnalysisMetadata action = mutableActionGraph.getGeneratingAction(artifact); if (action == null) { action = getActionGraph().getGeneratingAction(artifact); } if (action != null) { Preconditions.checkState( action instanceof Action, "%s is not a proper Action object", action.prettyPrint()); return (Action) action; } else { return null; } } @Nullable protected final ParameterFileWriteAction findParamsFileAction(SpawnAction spawnAction) { for (Artifact input : spawnAction.getInputs()) { Action generatingAction = getGeneratingAction(input); if (generatingAction instanceof ParameterFileWriteAction) { return (ParameterFileWriteAction) generatingAction; } } return null; } protected Action getGeneratingAction(ConfiguredTarget target, String outputName) { NestedSet filesToBuild = getFilesToBuild(target); return getGeneratingAction(outputName, filesToBuild, "filesToBuild"); } private Action getGeneratingAction( String outputName, NestedSet filesToBuild, String providerName) { Artifact artifact = Iterables.find(filesToBuild, artifactNamed(outputName), null); if (artifact == null) { fail( String.format( "Artifact named '%s' not found in %s (%s)", outputName, providerName, filesToBuild)); } return getGeneratingAction(artifact); } protected Action getGeneratingActionInOutputGroup( ConfiguredTarget target, String outputName, String outputGroupName) { NestedSet outputGroup = OutputGroupProvider.get(target).getOutputGroup(outputGroupName); return getGeneratingAction(outputName, outputGroup, "outputGroup/" + outputGroupName); } /** * 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 final List getGeneratingSpawnActionArgs(Artifact artifact) throws CommandLineExpansionException { SpawnAction a = getGeneratingSpawnAction(artifact); ParameterFileWriteAction p = findParamsFileAction(a); return p == null ? a.getArguments() : ImmutableList.copyOf(Iterables.concat(a.getArguments(), p.getContents())); } protected SpawnAction getGeneratingSpawnAction(ConfiguredTarget target, String outputName) { return getGeneratingSpawnAction( Iterables.find(getFilesToBuild(target), artifactNamed(outputName))); } protected ActionsTestUtil actionsTestUtil() { return new ActionsTestUtil(getActionGraph()); } // Get a MutableActionGraph for testing purposes. protected MutableActionGraph getMutableActionGraph() { return mutableActionGraph; } /** * Returns the ConfiguredTarget for the specified label, configured for the "build" (aka "target") * configuration. If the label corresponds to a target with a top-level configuration transition, * that transition is applied to the given config in the returned ConfiguredTarget. */ public ConfiguredTarget getConfiguredTarget(String label) throws LabelSyntaxException { return getConfiguredTarget(label, targetConfig); } /** * Returns the ConfiguredTarget for the specified label, using the given build configuration. If * the label corresponds to a target with a top-level configuration transition, that transition is * applied to the given config in the returned ConfiguredTarget. */ protected ConfiguredTarget getConfiguredTarget(String label, BuildConfiguration config) throws LabelSyntaxException { return getConfiguredTarget(Label.parseAbsolute(label), config); } /** * Returns the ConfiguredTarget for the specified label, using the * given build configuration. If the label corresponds to a target with a top-level configuration * transition, that transition is applied to the given config in the returned ConfiguredTarget. * *

If the evaluation of the SkyKey corresponding to the configured target fails, this * method may return null. In that case, use a debugger to inspect the {@link ErrorInfo} * for the evaluation, which is produced by the * {@link MemoizingEvaluator#getExistingValueForTesting} call in * {@link SkyframeExecutor#getConfiguredTargetForTesting}. See also b/26382502. */ protected ConfiguredTarget getConfiguredTarget(Label label, BuildConfiguration config) { return view.getConfiguredTargetForTesting(reporter, BlazeTestUtils.convertLabel(label), config); } /** * Returns the ConfiguredTarget for the specified file label, configured for * the "build" (aka "target") configuration. */ protected FileConfiguredTarget getFileConfiguredTarget(String label) throws LabelSyntaxException { return (FileConfiguredTarget) getConfiguredTarget(label, targetConfig); } /** * Returns the ConfiguredTarget for the specified label, configured for * the "host" configuration. */ protected ConfiguredTarget getHostConfiguredTarget(String label) throws LabelSyntaxException { return getConfiguredTarget(label, getHostConfiguration()); } /** * Returns the ConfiguredTarget for the specified file label, configured for * the "host" configuration. */ protected FileConfiguredTarget getHostFileConfiguredTarget(String label) throws LabelSyntaxException { return (FileConfiguredTarget) getHostConfiguredTarget(label); } /** * Rewrites the WORKSPACE to have the required boilerplate and the given lines of content. * *

Triggers Skyframe to reinitialize everything. */ public void rewriteWorkspace(String... lines) throws Exception { scratch.overwriteFile( "WORKSPACE", new ImmutableList.Builder() .addAll(analysisMock.getWorkspaceContents(mockToolsConfig)) .addAll(ImmutableList.copyOf(lines)) .build()); invalidatePackages(); // Need to re-initialize the workspace status. getSkyframeExecutor().injectWorkspaceStatusData("test"); } /** * Create and return a configured 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 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"; if (packageName.equals(Label.EXTERNAL_PACKAGE_NAME.getPathString())) { buildFilePathString = "WORKSPACE"; scratch.overwriteFile(buildFilePathString, lines); } else { scratch.file(buildFilePathString, lines); } skyframeExecutor.invalidateFilesUnderPathForTesting( reporter, new ModifiedFileSet.Builder().modify(PathFragment.create(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); return view.getConfiguredTargetForTesting(reporter, rule.getLabel(), config); } /** * 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) { assertWithMessage( "Rule '" + "//" + packageName + ":" + ruleName + "' did not contain an error") .that(view.hasErrors(target)) .isTrue(); } return assertContainsEvent(expectedErrorMessage); } /** * Checks whether loading the given target results in the specified error message. * * @param target the name of the target. * @param expectedErrorMessage the expected error message. */ protected void checkLoadingPhaseError(String target, String expectedErrorMessage) { reporter.removeHandler(failFastHandler); try { // The error happens during the loading of the Skylark file so checkError doesn't work here getTarget(target); fail( String.format( "checkLoadingPhaseError(): expected an exception with '%s' when loading target '%s'.", expectedErrorMessage, target)); } catch (Exception expected) { } 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); assertWithMessage("Rule '" + "//" + packageName + ":" + ruleName + "' did contain an error") .that(view.hasErrors(target)) .isFalse(); 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. * *

The returned set preserves the order of the input. */ protected Set artifactsToStrings(Iterable 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"); assertThat(((OutputFile) outTarget).getGeneratingRule()).isSameAs(ruleTarget); // This ensures that the output artifact is wired up in the action graph getConfiguredTarget(expectedOut); } } Collection outs = ruleTarget.getOutputFiles(); assertWithMessage("Mismatched outputs: " + outs) .that(outs.size()) .isEqualTo(expectedOuts.length); } /** * Asserts that there exists a configured target file for the given label. */ protected void assertConfiguredTargetExists(String label) throws Exception { assertThat(getFileConfiguredTarget(label)).isNotNull(); } /** * 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 { assertWithMessage("Action for " + labelA + " did not match " + labelB) .that(getGeneratingActionForLabel(labelB)) .isSameAs(getGeneratingActionForLabel(labelA)); } protected Artifact getSourceArtifact(PathFragment rootRelativePath, Root root) { return view.getArtifactFactory().getSourceArtifact(rootRelativePath, root); } protected Artifact getSourceArtifact(String name) { return getSourceArtifact(PathFragment.create(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(PathFragment.create(rootRelativePath), targetConfig.getBinDirectory(RepositoryName.MAIN), 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) { ConfiguredTargetKey config = makeLabelAndConfiguration(owner); return getPackageRelativeDerivedArtifact( packageRelativePath, config.getConfiguration().getBinDirectory(RepositoryName.MAIN), config); } /** * 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(RepositoryName.MAIN), 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, AspectClass creatingAspectFactory) { return getBinArtifact( packageRelativePath, owner, creatingAspectFactory, AspectParameters.EMPTY); } /** * 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, AspectClass creatingAspectFactory, AspectParameters parameters) { return getPackageRelativeDerivedArtifact( packageRelativePath, owner.getConfiguration().getBinDirectory(RepositoryName.MAIN), (AspectValue.AspectKey) ActionLookupValue.key(AspectValue.createAspectKey( owner.getLabel(), owner.getConfiguration(), new AspectDescriptor(creatingAspectFactory, parameters), owner.getConfiguration() )) .argument()); } /** * 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(PathFragment.create(rootRelativePath), targetConfig.getGenfilesDirectory(RepositoryName.MAIN), 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) { ConfiguredTargetKey configKey = makeLabelAndConfiguration(owner); return getGenfilesArtifact(packageRelativePath, configKey, configKey.getConfiguration()); } /** * 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) { ConfiguredTargetKey configKey = new ConfiguredTargetKey(owner); return getGenfilesArtifact(packageRelativePath, configKey, configKey.getConfiguration()); } /** * 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, NativeAspectClass creatingAspectFactory) { return getGenfilesArtifact( packageRelativePath, owner, creatingAspectFactory, AspectParameters.EMPTY); } protected Artifact getGenfilesArtifact( String packageRelativePath, ConfiguredTarget owner, NativeAspectClass creatingAspectFactory, AspectParameters params) { return getPackageRelativeDerivedArtifact( packageRelativePath, owner.getConfiguration().getGenfilesDirectory( owner.getTarget().getLabel().getPackageIdentifier().getRepository()), (AspectValue.AspectKey) ActionLookupValue.key(AspectValue.createAspectKey( owner.getLabel(), owner.getConfiguration(), new AspectDescriptor(creatingAspectFactory, params), owner.getConfiguration() )) .argument()); } /** * Strips the C++-contributed prefix out of an output path when tests are run with trimmed * configurations. e.g. turns "bazel-out/gcc-X-glibc-Y-k8-fastbuild/ to "bazel-out/fastbuild/". * *

This should be used for targets use configurations with C++ fragments. */ protected String stripCppPrefixForTrimmedConfigs(String outputPath) { return targetConfig.trimConfigurations() ? AnalysisTestUtil.OUTPUT_PATH_CPP_PREFIX_PATTERN.matcher(outputPath).replaceFirst("") : outputPath; } /** * 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, BuildConfiguration config) { return getPackageRelativeDerivedArtifact( packageRelativePath, config.getGenfilesDirectory(RepositoryName.MAIN), 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.getLabel().getPackageIdentifier().getRepository()), 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(PathFragment.create(rootRelativePath), targetConfig.getBinDirectory(RepositoryName.MAIN), 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. * *

At the call site it expects the {@code packageName} to contain: *

    *
  1. {@code :gvalid} - genrule that outputs a valid file
  2. *
  3. {@code :ginvalid} - genrule that outputs an invalid file
  4. *
  5. {@code :gmix} - genrule that outputs a mix of valid and invalid * files
  6. *
  7. {@code :valid} - rule of type {@code ruleType} that has a valid * file, {@code :gvalid} and {@code :gmix} in the srcs
  8. *
  9. {@code :invalid} - rule of type {@code ruleType} that has an invalid * file, {@code :ginvalid} in the srcs
  10. *
  11. {@code :mix} - rule of type {@code ruleType} that has a valid and an * invalid file in the srcs
  12. *
* * @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) { assertThat(view.hasErrors(target)).isTrue(); for (String expectedMessage : expectedMessages) { String message = "in srcs attribute of " + ruleType + " rule " + targetName + ": " + expectedMessage; assertContainsEvent(message); } } else { assertThat(view.hasErrors(target)).isFalse(); for (String expectedMessage : expectedMessages) { String message = "in srcs attribute of " + ruleType + " rule " + target.getLabel() + ": " + expectedMessage; assertDoesNotContainEvent(message); } } } public static Label makeLabel(String label) { try { return Label.parseAbsolute(label); } catch (LabelSyntaxException e) { throw new IllegalStateException(e); } } private ConfiguredTargetKey makeLabelAndConfiguration(String label) { BuildConfiguration config; try { config = getConfiguredTarget(label).getConfiguration(); config = view.getConfigurationForTesting(getTarget(label), config, reporter); } catch (LabelSyntaxException e) { throw new IllegalArgumentException(e); } catch (Exception e) { //TODO(b/36585204): Clean this up throw new RuntimeException(e); } return new ConfiguredTargetKey(makeLabel(label), config); } protected static List actionInputsToPaths(Iterable actionInputs) { return ImmutableList.copyOf( Iterables.transform(actionInputs, new Function() { @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 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 artifacts, Iterable 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 artifacts, String[] expectedInputs, Iterable 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 list, List 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 list, List 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 collectRunfiles(ConfiguredTarget target) { RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class); if (runfilesProvider != null) { return runfilesProvider.getDefaultRunfiles().getAllArtifacts(); } else { return Runfiles.EMPTY.getAllArtifacts(); } } protected NestedSet getFilesToBuild(TransitiveInfoCollection target) { return target.getProvider(FileProvider.class).getFilesToBuild(); } /** * Returns all extra actions for that target (no transitive actions), no duplicate actions. */ protected ImmutableList getExtraActionActions(ConfiguredTarget target) { LinkedHashSet 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 getTransitiveExtraActionActions(ConfiguredTarget target) { ImmutableList.Builder result = new ImmutableList.Builder<>(); for (Artifact artifact : target .getProvider(ExtraActionArtifactsProvider.class) .getTransitiveExtraActionArtifacts()) { Action action = getGeneratingAction(artifact); if (action instanceof ExtraAction) { result.add((ExtraAction) action); } } return result.build(); } protected ImmutableList getFilesToBuildActions(ConfiguredTarget target) { List result = new ArrayList<>(); for (Artifact artifact : getFilesToBuild(target)) { Action action = getGeneratingAction(artifact); if (action != null) { result.add(action); } } return ImmutableList.copyOf(result); } protected NestedSet getOutputGroup( TransitiveInfoCollection target, String outputGroup) { OutputGroupProvider provider = OutputGroupProvider.get(target); return provider == null ? NestedSetBuilder.emptySet(Order.STABLE_ORDER) : provider.getOutputGroup(outputGroup); } protected NestedSet 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 NestedSet getFilesToRun(TransitiveInfoCollection target) { return target.getProvider(FilesToRunProvider.class).getFilesToRun(); } protected NestedSet getFilesToRun(Label label) throws Exception { return getConfiguredTarget(label, targetConfig) .getProvider(FilesToRunProvider.class).getFilesToRun(); } protected NestedSet 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() throws InterruptedException { return getConfiguration(getTargetConfiguration(), ConfigurationTransition.DATA); } protected BuildConfiguration getHostConfiguration() { return masterConfig.getHostConfiguration(); } /** * Returns the configuration created by applying the given transition to the source configuration. */ protected BuildConfiguration getConfiguration(BuildConfiguration fromConfig, Attribute.Transition transition) throws InterruptedException { if (transition == ConfigurationTransition.NONE) { return fromConfig; } else if (transition == ConfigurationTransition.NULL) { return null; } else { PatchTransition patchTransition = (PatchTransition) ruleClassProvider.getDynamicTransitionMapper().map(transition); return skyframeExecutor.getConfigurationForTesting(reporter, fromConfig.fragmentClasses(), patchTransition.apply(fromConfig.getOptions())); } } /** * 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 targets, boolean keepGoing, int loadingPhaseThreads, boolean doAnalysis, EventBus eventBus) throws Exception { return update( targets, ImmutableList.of(), keepGoing, loadingPhaseThreads, doAnalysis, eventBus); } protected AnalysisResult update( List targets, List aspects, boolean keepGoing, int loadingPhaseThreads, boolean doAnalysis, EventBus eventBus) throws Exception { LoadingOptions loadingOptions = Options.getDefaults(LoadingOptions.class); BuildView.Options viewOptions = Options.getDefaults(BuildView.Options.class); viewOptions.keepGoing = keepGoing; viewOptions.loadingPhaseThreads = loadingPhaseThreads; LoadingPhaseRunner runner = new LegacyLoadingPhaseRunner(getPackageManager(), Collections.unmodifiableSet(ruleClassProvider.getRuleClassMap().keySet())); LoadingResult loadingResult = runner.execute( reporter, targets, PathFragment.EMPTY_FRAGMENT, loadingOptions, 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, aspects, viewOptions, AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT, reporter, eventBus); } protected static Predicate artifactNamed(final String name) { return new Predicate() { @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