// 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.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; 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.ActionExecutionContext; import com.google.devtools.build.lib.actions.ActionGraph; import com.google.devtools.build.lib.actions.ActionInput; import com.google.devtools.build.lib.actions.ActionKeyContext; import com.google.devtools.build.lib.actions.ActionLogBufferPathGenerator; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; import com.google.devtools.build.lib.actions.ArtifactOwner; import com.google.devtools.build.lib.actions.ArtifactPathResolver; import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.actions.CommandAction; import com.google.devtools.build.lib.actions.CommandLine; import com.google.devtools.build.lib.actions.CommandLineExpansionException; import com.google.devtools.build.lib.actions.CommandLines; import com.google.devtools.build.lib.actions.CommandLines.CommandLineAndParamFileInfo; import com.google.devtools.build.lib.actions.MapBasedActionGraph; import com.google.devtools.build.lib.actions.MetadataProvider; import com.google.devtools.build.lib.actions.MiddlemanFactory; import com.google.devtools.build.lib.actions.MutableActionGraph; import com.google.devtools.build.lib.actions.ParameterFile; import com.google.devtools.build.lib.actions.ResourceManager; import com.google.devtools.build.lib.actions.ResourceSet; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.actions.util.DummyExecutor; import com.google.devtools.build.lib.analysis.AnalysisEnvironment; import com.google.devtools.build.lib.analysis.AnalysisOptions; import com.google.devtools.build.lib.analysis.AnalysisResult; import com.google.devtools.build.lib.analysis.AnalysisUtils; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment; 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.FileProvider; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.OutputGroupInfo; import com.google.devtools.build.lib.analysis.PseudoAction; 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.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.buildinfo.BuildInfoFactory.BuildInfoKey; 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.transitions.NoTransition; import com.google.devtools.build.lib.analysis.config.transitions.NullTransition; import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; 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.BuildRequestOptions; 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.AttributeMap; import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper; 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.SkylarkSemanticsOptions; 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.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.BuildConfigurationValue; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; import com.google.devtools.build.lib.skyframe.DiffAwareness; import com.google.devtools.build.lib.skyframe.PackageRootsNoSymlinkCreation; 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.TargetPatternPhaseValue; import com.google.devtools.build.lib.syntax.SkylarkSemantics; 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.StringUtil; 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.lib.vfs.Root; import com.google.devtools.build.lib.vfs.RootedPath; 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 com.google.devtools.common.options.OptionsParsingException; 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.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeMap; 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 BuildViewForTesting view; protected SequencedSkyframeExecutor skyframeExecutor; protected TimestampGranularityMonitor tsgm; protected BlazeDirectories directories; protected ActionKeyContext actionKeyContext; // 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; private LoadingOptions customLoadingOptions = null; protected BuildConfigurationValue.Key targetConfigKey; private ActionLogBufferPathGenerator actionLogBufferPathGenerator; @Before public final void initializeSkyframeExecutor() throws Exception { initializeSkyframeExecutor(/*doPackageLoadingChecks=*/ true); } public void initializeSkyframeExecutor(boolean doPackageLoadingChecks) throws Exception { analysisMock = getAnalysisMock(); directories = new BlazeDirectories( new ServerDirectories(outputBase, outputBase, outputBase), rootDirectory, /* defaultSystemJavabase= */ null, analysisMock.getProductName()); actionKeyContext = new ActionKeyContext(); mockToolsConfig = new MockToolsConfig(rootDirectory, false); initializeMockClient(); packageCacheOptions = parsePackageCacheOptions(); skylarkSemanticsOptions = parseSkylarkSemanticsOptions(); workspaceStatusActionFactory = new AnalysisTestUtil.DummyWorkspaceStatusActionFactory(directories); mutableActionGraph = new MapBasedActionGraph(actionKeyContext); ruleClassProvider = getRuleClassProvider(); ImmutableList extraPrecomputedValues = ImmutableList.of( PrecomputedValue.injected( RepositoryDelegatorFunction.REPOSITORY_OVERRIDES, ImmutableMap.of()), PrecomputedValue.injected( RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING, RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY)); PackageFactory.BuilderForTesting pkgFactoryBuilder = analysisMock .getPackageFactoryBuilderForTesting(directories) .setExtraPrecomputeValues(extraPrecomputedValues) .setEnvironmentExtensions(getEnvironmentExtensions()); if (!doPackageLoadingChecks) { pkgFactoryBuilder.disableChecks(); } pkgFactory = pkgFactoryBuilder.build(ruleClassProvider); tsgm = new TimestampGranularityMonitor(BlazeClock.instance()); skyframeExecutor = SequencedSkyframeExecutor.create( pkgFactory, fileSystem, directories, actionKeyContext, workspaceStatusActionFactory, ruleClassProvider.getBuildInfoFactories(), ImmutableList.of(), analysisMock.getSkyFunctions(directories), ImmutableList.of(), BazelSkyframeExecutorConstants.HARDCODED_BLACKLISTED_PACKAGE_PREFIXES, BazelSkyframeExecutorConstants.ADDITIONAL_BLACKLISTED_PACKAGE_PREFIXES_FILE, BazelSkyframeExecutorConstants.CROSS_REPOSITORY_LABEL_VIOLATION_STRATEGY, BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY, BazelSkyframeExecutorConstants.ACTION_ON_IO_EXCEPTION_READING_BUILD_FILE, DefaultBuildOptionsForTesting.getDefaultBuildOptionsForTest(ruleClassProvider)); TestConstants.processSkyframeExecutorForTesting(skyframeExecutor); skyframeExecutor.injectExtraPrecomputedValues(extraPrecomputedValues); packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PUBLIC; packageCacheOptions.showLoadingProgress = true; packageCacheOptions.globbingThreads = 7; skyframeExecutor.preparePackageLoading( new PathPackageLocator( outputBase, ImmutableList.of(Root.fromPath(rootDirectory)), BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY), packageCacheOptions, skylarkSemanticsOptions, "", UUID.randomUUID(), ImmutableMap.of(), tsgm); skyframeExecutor.setActionEnv(ImmutableMap.of()); useConfiguration(); setUpSkyframe(); // Also initializes ResourceManager. ResourceManager.instance().setAvailableResources(getStartingResources()); this.actionLogBufferPathGenerator = new ActionLogBufferPathGenerator( directories.getActionConsoleOutputDirectory(getExecRoot())); } public void initializeMockClient() throws IOException { analysisMock.setupMockClient(mockToolsConfig); analysisMock.setupMockWorkspaceFiles(directories.getEmbeddedBinariesRoot()); } 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 SkylarkSemantics getSkylarkSemantics() { return skylarkSemanticsOptions.toSkylarkSemantics(); } 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, 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.resetConfigurationCollectionForTesting(); skyframeExecutor.setConfigurationFragmentFactories( ruleClassProvider.getConfigurationFragments()); return skyframeExecutor.createConfigurations( reporter, buildOptions, ImmutableSet.of(), false); } protected Target getTarget(String label) throws NoSuchPackageException, NoSuchTargetException, LabelSyntaxException, InterruptedException { return getTarget(Label.parseAbsolute(label, ImmutableMap.of())); } 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, BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); packageCacheOptions.showLoadingProgress = true; packageCacheOptions.globbingThreads = 7; skyframeExecutor.preparePackageLoading( pkgLocator, packageCacheOptions, skylarkSemanticsOptions, ruleClassProvider.getDefaultsPackageContent(optionsParser), UUID.randomUUID(), ImmutableMap.of(), tsgm); skyframeExecutor.setActionEnv(ImmutableMap.of()); skyframeExecutor.setDeletedPackages(ImmutableSet.copyOf(packageCacheOptions.getDeletedPackages())); skyframeExecutor.injectExtraPrecomputedValues( ImmutableList.of( PrecomputedValue.injected( RepositoryDelegatorFunction.OUTPUT_VERIFICATION_REPOSITORY_RULES, ImmutableSet.of()), PrecomputedValue.injected( RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION, Optional.absent()))); } 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, Root.fromPath(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(); targetConfigKey = BuildConfigurationValue.key(targetConfig); 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.handleConfiguredTargetChange(); view = new BuildViewForTesting(directories, ruleClassProvider, skyframeExecutor, null); view.setConfigurationsForTesting(event -> {}, masterConfig); view.setArtifactRoots(new PackageRootsNoSymlinkCreation(Root.fromPath(rootDirectory))); } protected CachingAnalysisEnvironment getTestAnalysisEnvironment() { return new CachingAnalysisEnvironment( view.getArtifactFactory(), actionKeyContext, ArtifactOwner.NullArtifactOwner.INSTANCE, /*isSystemEnv=*/ true, /*extendedSanityChecks*/ false, reporter, /* env= */ null, /*sourceDependencyListener=*/ unused -> {}); } /** * 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 final Collection getDirectPrerequisites(ConfiguredTarget target) throws Exception { return view.getDirectPrerequisitesForTesting(reporter, target, masterConfig); } protected final Collection getDirectPrerequisites( ConfiguredTargetAndData ctad) throws Exception { return view.getConfiguredTargetAndDataDirectPrerequisitesForTesting( reporter, ctad, masterConfig); } protected final ConfiguredTarget getDirectPrerequisite(ConfiguredTarget target, String label) throws Exception { Label candidateLabel = Label.parseAbsolute(label, ImmutableMap.of()); for (ConfiguredTarget candidate : getDirectPrerequisites(target)) { if (candidate.getLabel().equals(candidateLabel)) { return candidate; } } return null; } protected final ConfiguredTargetAndData getConfiguredTargetAndDataDirectPrerequisite( ConfiguredTargetAndData ctad, String label) throws Exception { Label candidateLabel = Label.parseAbsolute(label, ImmutableMap.of()); for (ConfiguredTargetAndData candidate : getDirectPrerequisites(ctad)) { if (candidate.getConfiguredTarget().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); } /** Locates the first parameter file used by the action and returns its command line. */ @Nullable protected final CommandLine paramFileCommandLineForAction(Action action) { if (action instanceof SpawnAction) { CommandLines commandLines = ((SpawnAction) action).getCommandLines(); for (CommandLineAndParamFileInfo pair : commandLines.getCommandLines()) { if (pair.paramFileInfo != null) { return pair.commandLine; } } } ParameterFileWriteAction parameterFileWriteAction = paramFileWriteActionForAction(action); return parameterFileWriteAction != null ? parameterFileWriteAction.getCommandLine() : null; } /** Locates the first parameter file used by the action and returns its args. */ @Nullable protected final Iterable paramFileArgsForAction(Action action) throws CommandLineExpansionException { CommandLine commandLine = paramFileCommandLineForAction(action); return commandLine != null ? commandLine.arguments() : null; } /** * Locates the first parameter file used by the action and returns its args. * *

If no param file is used, return the action's arguments. */ @Nullable protected final Iterable paramFileArgsOrActionArgs(CommandAction action) throws CommandLineExpansionException { CommandLine commandLine = paramFileCommandLineForAction(action); return commandLine != null ? commandLine.arguments() : action.getArguments(); } /** Locates the first parameter file used by the action and returns its contents. */ @Nullable protected final String paramFileStringContentsForAction(Action action) throws CommandLineExpansionException, IOException { if (action instanceof SpawnAction) { CommandLines commandLines = ((SpawnAction) action).getCommandLines(); for (CommandLineAndParamFileInfo pair : commandLines.getCommandLines()) { if (pair.paramFileInfo != null) { ByteArrayOutputStream out = new ByteArrayOutputStream(); ParameterFile.writeParameterFile( out, pair.commandLine.arguments(), pair.paramFileInfo.getFileType(), pair.paramFileInfo.getCharset()); return new String(out.toByteArray(), pair.paramFileInfo.getCharset()); } } } ParameterFileWriteAction parameterFileWriteAction = paramFileWriteActionForAction(action); return parameterFileWriteAction != null ? parameterFileWriteAction.getStringContents() : null; } @Nullable private ParameterFileWriteAction paramFileWriteActionForAction(Action action) { for (Artifact input : action.getInputs()) { if (!(input instanceof SpecialArtifact)) { Action generatingAction = getGeneratingAction(input); if (generatingAction instanceof ParameterFileWriteAction) { return (ParameterFileWriteAction) generatingAction; } } } return null; } protected final ActionAnalysisMetadata getGeneratingActionAnalysisMetadata(Artifact artifact) { Preconditions.checkNotNull(artifact); ActionAnalysisMetadata actionAnalysisMetadata = mutableActionGraph.getGeneratingAction(artifact); if (actionAnalysisMetadata == null) { actionAnalysisMetadata = getActionGraph().getGeneratingAction(artifact); } return actionAnalysisMetadata; } 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 final Action getGeneratingAction(Artifact artifact) { ActionAnalysisMetadata action = getGeneratingActionAnalysisMetadata(artifact); if (action != null) { Preconditions.checkState( action instanceof Action, "%s is not a proper Action object", action.prettyPrint()); return (Action) action; } else { return null; } } protected Action getGeneratingActionInOutputGroup( ConfiguredTarget target, String outputName, String outputGroupName) { NestedSet outputGroup = OutputGroupInfo.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 SpawnAction getGeneratingSpawnAction(ConfiguredTarget target, String outputName) { return getGeneratingSpawnAction( Iterables.find(getFilesToBuild(target), artifactNamed(outputName))); } protected final List getGeneratingSpawnActionArgs(Artifact artifact) throws CommandLineExpansionException { SpawnAction a = getGeneratingSpawnAction(artifact); Iterable paramFileArgs = paramFileArgsForAction(a); return paramFileArgs != null ? ImmutableList.copyOf(Iterables.concat(a.getArguments(), paramFileArgs)) : a.getArguments(); } 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, ImmutableMap.of()), 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#getExistingValue} 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 a ConfiguredTargetAndData for the specified label, using the given build configuration. */ protected ConfiguredTargetAndData getConfiguredTargetAndData( Label label, BuildConfiguration config) { return view.getConfiguredTargetAndDataForTesting(reporter, label, config); } /** * Returns the ConfiguredTargetAndData for the specified label. If the label corresponds to a * target with a top-level configuration transition, that transition is applied to the given * config in the ConfiguredTargetAndData's ConfiguredTarget. */ public ConfiguredTargetAndData getConfiguredTargetAndData(String label) throws LabelSyntaxException { return getConfiguredTargetAndData(Label.parseAbsolute(label, ImmutableMap.of()), targetConfig); } /** * 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(); } /** * 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 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 { ConfiguredTargetAndData ctad = scratchConfiguredTargetAndData(packageName, ruleName, config, lines); return ctad == null ? null : ctad.getConfiguredTarget(); } /** * Creates and returns a configured scratch rule and its data. * * @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 tatarget and target instance for the created rule. * @throws Exception */ protected ConfiguredTargetAndData scratchConfiguredTargetAndData( String packageName, String rulename, String... lines) throws Exception { return scratchConfiguredTargetAndData(packageName, rulename, targetConfig, lines); } /** * Creates and returns a configured scratch rule and its data. * * @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 ConfiguredTargetAndData instance for the created rule. * @throws IOException * @throws Exception */ protected ConfiguredTargetAndData scratchConfiguredTargetAndData( String packageName, String ruleName, BuildConfiguration config, String... lines) throws Exception { Target rule = scratchRule(packageName, ruleName, lines); return view.getConfiguredTargetAndDataForTesting(reporter, rule.getLabel(), config); } /** * 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(), Root.fromPath(rootDirectory)); return (Rule) getTarget("//" + packageName + ":" + ruleName); } /** * 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.fromPath(rootDirectory)); } /** * Gets a derived artifact, creating it if necessary. {@code ArtifactOwner} should be a genuine * {@link ConfiguredTargetKey} 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, ArtifactRoot root, ArtifactOwner owner) { return view.getArtifactFactory().getDerivedArtifact(rootRelativePath, root, owner); } /** * Gets a tree artifact, creating it if necessary. {@code ArtifactOwner} should be a genuine * {@link ConfiguredTargetKey} 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 getTreeArtifact( PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) { return view.getArtifactFactory().getTreeArtifact(rootRelativePath, root, owner); } /** * Gets a Tree 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 final Artifact getTreeArtifact(String packageRelativePath, ConfiguredTarget owner) { return getPackageRelativeTreeArtifact( packageRelativePath, getConfiguration(owner).getBinDirectory(RepositoryName.MAIN), ConfiguredTargetKey.of( owner, skyframeExecutor.getConfiguration(reporter, owner.getConfigurationKey()))); } /** * Gets a derived Artifact for testing with path of the form * root/owner.getPackageFragment()/packageRelativePath. * * @see #getDerivedArtifact(PathFragment, ArtifactRoot, ArtifactOwner) */ private Artifact getPackageRelativeDerivedArtifact( String packageRelativePath, ArtifactRoot root, ArtifactOwner owner) { return getDerivedArtifact( owner.getLabel().getPackageFragment().getRelative(packageRelativePath), root, owner); } /** * Gets a tree Artifact for testing with path of the form * root/owner.getPackageFragment()/packageRelativePath. * * @see #getDerivedArtifact(PathFragment, ArtifactRoot, ArtifactOwner) */ private Artifact getPackageRelativeTreeArtifact( String packageRelativePath, ArtifactRoot root, ArtifactOwner owner) { return getTreeArtifact( 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, * ConfiguredTarget)} 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 final Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner) { return getPackageRelativeDerivedArtifact( packageRelativePath, getConfiguration(owner).getBinDirectory(RepositoryName.MAIN), ConfiguredTargetKey.of( owner, skyframeExecutor.getConfiguration(reporter, owner.getConfigurationKey()))); } /** * 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, getConfiguration(owner).getBinDirectory(RepositoryName.MAIN), (AspectValue.AspectKey) AspectValue.createAspectKey( owner.getLabel(), getConfiguration(owner), new AspectDescriptor(creatingAspectFactory, parameters), getConfiguration(owner)) .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, ConfiguredTarget)} 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) { BuildConfiguration config = getConfiguration(owner); return getGenfilesArtifact( packageRelativePath, ConfiguredTargetKey.of(makeLabel(owner), config), config); } /** * 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) { BuildConfiguration configuration = skyframeExecutor.getConfiguration(reporter, owner.getConfigurationKey()); ConfiguredTargetKey configKey = ConfiguredTargetKey.of(owner, configuration); return getGenfilesArtifact(packageRelativePath, configKey, configuration); } /** * 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, getConfiguration(owner) .getGenfilesDirectory(owner.getLabel().getPackageIdentifier().getRepository()), getOwnerForAspect(owner, creatingAspectFactory, params)); } protected AspectValue.AspectKey getOwnerForAspect( ConfiguredTarget owner, NativeAspectClass creatingAspectFactory, AspectParameters params) { return (AspectValue.AspectKey) AspectValue.createAspectKey( owner.getLabel(), getConfiguration(owner), new AspectDescriptor(creatingAspectFactory, params), getConfiguration(owner)) .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, 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, makeConfiguredTargetKey(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, 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), ConfiguredTargetKey.of( owner, skyframeExecutor.getConfiguration(reporter, owner.getConfigurationKey()))); } protected Action getGeneratingActionForLabel(String label) throws Exception { return getGeneratingAction(getFileConfiguredTarget(label).getArtifact()); } /** * 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; } 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, "source 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); } } } protected static ConfiguredAttributeMapper getMapperFromConfiguredTargetAndTarget( ConfiguredTargetAndData ctad) { return ConfiguredAttributeMapper.of( (Rule) ctad.getTarget(), ((RuleConfiguredTarget) ctad.getConfiguredTarget()).getConfigConditions()); } public static Label makeLabel(String label) { try { return Label.parseAbsolute(label, ImmutableMap.of()); } catch (LabelSyntaxException e) { throw new IllegalStateException(e); } } private ConfiguredTargetKey makeConfiguredTargetKey(String label) { return ConfiguredTargetKey.of(makeLabel(label), getConfiguration(label)); } protected static List actionInputsToPaths(Iterable actionInputs) { return ImmutableList.copyOf( Iterables.transform(actionInputs, new Function() { @Override public String apply(ActionInput actionInput) { return actionInput.getExecPathString(); } })); } /** * 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) { OutputGroupInfo provider = OutputGroupInfo.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 getHostConfiguration() { return masterConfig.getHostConfiguration(); } /** * Returns the configuration created by applying the given transition to the source configuration. */ protected BuildConfiguration getConfiguration(BuildConfiguration fromConfig, PatchTransition transition) throws InterruptedException { if (transition == NoTransition.INSTANCE) { return fromConfig; } else if (transition == NullTransition.INSTANCE) { return null; } else { return skyframeExecutor.getConfigurationForTesting( reporter, fromConfig.fragmentClasses(), transition.patch(fromConfig.getOptions())); } } private BuildConfiguration getConfiguration(String label) { BuildConfiguration config; try { config = getConfiguration(getConfiguredTarget(label)); 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 config; } protected final BuildConfiguration getConfiguration(ConfiguredTarget ct) { return skyframeExecutor.getConfiguration(reporter, ct.getConfigurationKey()); } /** * Returns an attribute value retriever for the given rule for the target configuration. */ protected AttributeMap attributes(RuleConfiguredTarget ct) { ConfiguredTargetAndData ctad; try { ctad = getConfiguredTargetAndData(ct.getLabel().toString()); } catch (LabelSyntaxException e) { throw new RuntimeException(e); } return getMapperFromConfiguredTargetAndTarget(ctad); } protected AttributeMap attributes(ConfiguredTarget rule) { return attributes((RuleConfiguredTarget) rule); } protected void useLoadingOptions(String... options) throws OptionsParsingException { customLoadingOptions = Options.parse(LoadingOptions.class, options).getOptions(); } 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 = customLoadingOptions == null ? Options.getDefaults(LoadingOptions.class) : customLoadingOptions; AnalysisOptions viewOptions = Options.getDefaults(AnalysisOptions.class); TargetPatternPhaseValue loadingResult = skyframeExecutor.loadTargetPatterns( reporter, targets, PathFragment.EMPTY_FRAGMENT, loadingOptions, loadingPhaseThreads, keepGoing, /*determineTests=*/ false); if (!doAnalysis) { // TODO(bazel-team): What's supposed to happen in this case? return null; } return view.update( loadingResult, targetConfig.getOptions(), /* multiCpu= */ ImmutableSet.of(), aspects, viewOptions, keepGoing, loadingPhaseThreads, 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