blob: 1a5b3dcf1188b542351a2a02a0098eef270c684e [file] [log] [blame]
// 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.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.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.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.LabelAndConfiguration;
import com.google.devtools.build.lib.analysis.OutputGroupProvider;
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.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.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.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.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.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.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.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.FileType;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.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<String> 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<PrecomputedValue.Injected> extraPrecomputedValues = ImmutableList.of(
PrecomputedValue.injected(
RepositoryDelegatorFunction.REPOSITORY_OVERRIDES,
ImmutableMap.<RepositoryName, PathFragment>of()));
pkgFactory =
analysisMock
.getPackageFactoryBuilderForTesting(directories)
.setExtraPrecomputeValues(extraPrecomputedValues)
.setEnvironmentExtensions(getEnvironmentExtensions())
.setPlatformSetRegexps(getPlatformSetRegexps())
.build(ruleClassProvider, scratch.getFileSystem());
tsgm = new TimestampGranularityMonitor(BlazeClock.instance());
skyframeExecutor =
SequencedSkyframeExecutor.create(
pkgFactory,
fileSystem,
directories,
workspaceStatusActionFactory,
ruleClassProvider.getBuildInfoFactories(),
ImmutableList.<DiffAwareness.Factory>of(),
Predicates.<PathFragment>alwaysFalse(),
analysisMock.getSkyFunctions(directories),
ImmutableList.<SkyValueDirtinessChecker>of(),
PathFragment.EMPTY_FRAGMENT,
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.<String, String>of(),
ImmutableMap.<String, String>of(),
tsgm);
useConfiguration();
setUpSkyframe();
// Also initializes ResourceManager.
ResourceManager.instance().setAvailableResources(getStartingResources());
}
protected Map<String, String> 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<EnvironmentExtension> getEnvironmentExtensions() {
return ImmutableList.<EnvironmentExtension>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<String> 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.<String>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.
*
* <p>Fails with an assertion error if this doesn't happen.
*
* <p>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.<String, String>of(),
ImmutableMap.<String, String>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.
*
* <p>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.handleConfiguredTargetChange();
view = new BuildView(directories, ruleClassProvider, skyframeExecutor, null);
view.setConfigurationsForTesting(masterConfig);
view.setArtifactRoots(new PackageRootsNoSymlinkCreation(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<ConfiguredTarget> 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.
*
* <p>Historically this meant they contained the same object reference. But with upcoming dynamic
* configurations that may no longer be true (for example, they may have the same values but not
* the same {@link BuildConfiguration.Fragment}s. So this method abstracts the
* "configuration equivalency" checking into one place, where the implementation logic can evolve
* as needed.
*/
protected void assertConfigurationsEqual(BuildConfiguration config1, BuildConfiguration config2) {
// BuildOptions and crosstool files determine a configuration's content. Within the context
// of these tests only the former actually change.
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<? extends TransitiveInfoCollection> 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 <C extends TransitiveInfoProvider> Iterable<C> getPrerequisites(ConfiguredTarget target,
String attributeName, Class<C> 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<Artifact> getPrerequisiteArtifacts(
ConfiguredTarget target, String attributeName) throws Exception {
Set<Artifact> result = new LinkedHashSet<>();
for (FileProvider provider : getPrerequisites(target, attributeName, FileProvider.class)) {
Iterables.addAll(result, provider.getFilesToBuild());
}
return ImmutableList.copyOf(result);
}
protected ActionGraph getActionGraph() {
return skyframeExecutor.getActionGraph(reporter);
}
protected final Action getGeneratingAction(Artifact artifact) {
Preconditions.checkNotNull(artifact);
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<Artifact> filesToBuild = getFilesToBuild(target);
return getGeneratingAction(outputName, filesToBuild, "filesToBuild");
}
private Action getGeneratingAction(
String outputName, NestedSet<Artifact> 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<Artifact> 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<String> 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.
*
* <p>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 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.
*
* <p>Triggers Skyframe to reinitialize everything.
*/
public void rewriteWorkspace(String... lines) throws Exception {
scratch.overwriteFile(
"WORKSPACE",
new ImmutableList.Builder<String>()
.addAll(analysisMock.getWorkspaceContents(mockToolsConfig))
.addAll(ImmutableList.copyOf(lines))
.build());
invalidatePackages();
// Need to re-initialize the workspace status.
getSkyframeExecutor().maybeInvalidateWorkspaceStatusValue("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.
*
* <p>The returned set preserves the order of the input.
*/
protected Set<String> artifactsToStrings(Iterable<Artifact> artifacts) {
return AnalysisTestUtil.artifactsToStrings(masterConfig, artifacts);
}
/**
* Asserts that targetName's outputs are exactly expectedOuts.
*
* @param targetName The label of a rule.
* @param expectedOuts The labels of the expected outputs of the rule.
*/
protected void assertOuts(String targetName, String... expectedOuts) throws Exception {
Rule ruleTarget = (Rule) getTarget(targetName);
for (String expectedOut : expectedOuts) {
Target outTarget = getTarget(expectedOut);
if (!(outTarget instanceof OutputFile)) {
fail("Target " + outTarget + " is not an output");
assertThat(((OutputFile) outTarget).getGeneratingRule()).isSameAs(ruleTarget);
// This ensures that the output artifact is wired up in the action graph
getConfiguredTarget(expectedOut);
}
}
Collection<OutputFile> 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/".
*
* <p>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.
*
* <p>At the call site it expects the {@code packageName} to contain:
* <ol>
* <li>{@code :gvalid} - genrule that outputs a valid file</li>
* <li>{@code :ginvalid} - genrule that outputs an invalid file</li>
* <li>{@code :gmix} - genrule that outputs a mix of valid and invalid
* files</li>
* <li>{@code :valid} - rule of type {@code ruleType} that has a valid
* file, {@code :gvalid} and {@code :gmix} in the srcs</li>
* <li>{@code :invalid} - rule of type {@code ruleType} that has an invalid
* file, {@code :ginvalid} in the srcs</li>
* <li>{@code :mix} - rule of type {@code ruleType} that has a valid and an
* invalid file in the srcs</li>
* </ol>
*
* @param packageName the package where the rules under test are located
* @param ruleType rules under test types
* @param expectedTypes expected file types
*/
protected void assertSrcsValidityForRuleType(String packageName, String ruleType,
String expectedTypes) throws Exception {
reporter.removeHandler(failFastHandler);
String descriptionSingle = ruleType + " srcs file (expected " + expectedTypes + ")";
String descriptionPlural = ruleType + " srcs files (expected " + expectedTypes + ")";
String descriptionPluralFile = "(expected " + expectedTypes + ")";
assertSrcsValidity(ruleType, packageName + ":valid", false,
"need at least one " + descriptionSingle,
"'" + packageName + ":gvalid' does not produce any " + descriptionPlural,
"'" + packageName + ":gmix' does not produce any " + descriptionPlural);
assertSrcsValidity(ruleType, packageName + ":invalid", true,
"file '" + packageName + ":a.foo' is misplaced here " + descriptionPluralFile,
"'" + packageName + ":ginvalid' does not produce any " + descriptionPlural);
assertSrcsValidity(ruleType, packageName + ":mix", true,
"'" + packageName + ":a.foo' does not produce any " + descriptionPlural);
}
protected void assertSrcsValidity(String ruleType, String targetName, boolean expectedError,
String... expectedMessages) throws Exception{
ConfiguredTarget target = getConfiguredTarget(targetName);
if (expectedError) {
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<String> actionInputsToPaths(Iterable<? extends ActionInput> actionInputs) {
return ImmutableList.copyOf(
Iterables.transform(actionInputs, new Function<ActionInput, String>() {
@Override
public String apply(ActionInput actionInput) {
return actionInput.getExecPathString();
}
}));
}
protected String readContentAsLatin1String(Artifact artifact) throws IOException {
return new String(FileSystemUtils.readContentAsLatin1(artifact.getPath()));
}
/**
* Asserts that the predecessor closure of the given Artifact contains the same elements as those
* in expectedPredecessors, plus the given common predecessors. Only looks at predecessors of
* the given file type.
*/
public void assertPredecessorClosureSameContents(
Artifact artifact, FileType fType, Iterable<String> common, String... expectedPredecessors) {
assertSameContentsWithCommonElements(
actionsTestUtil().predecessorClosureAsCollection(artifact, fType),
expectedPredecessors, common);
}
/**
* Utility method for asserting that the contents of one collection are the
* same as those in a second plus some set of common elements.
*/
protected void assertSameContentsWithCommonElements(Iterable<Artifact> artifacts,
Iterable<String> common, String... expectedInputs) {
assertThat(Iterables.concat(Lists.newArrayList(expectedInputs), common))
.containsExactlyElementsIn(ActionsTestUtil.prettyArtifactNames(artifacts));
}
/**
* Utility method for asserting that the contents of one collection are the
* same as those in a second plus some set of common elements.
*/
protected void assertSameContentsWithCommonElements(Iterable<String> artifacts,
String[] expectedInputs, Iterable<String> common) {
assertThat(Iterables.concat(Lists.newArrayList(expectedInputs), common))
.containsExactlyElementsIn(artifacts);
}
/**
* Utility method for asserting that a list contains the elements of a
* sublist. This is useful for checking that a list of arguments contains a
* particular set of arguments.
*/
protected void assertContainsSublist(List<String> list, List<String> sublist) {
assertContainsSublist(null, list, sublist);
}
/**
* Utility method for asserting that a list contains the elements of a
* sublist. This is useful for checking that a list of arguments contains a
* particular set of arguments.
*/
protected void assertContainsSublist(String message, List<String> list, List<String> sublist) {
if (Collections.indexOfSubList(list, sublist) == -1) {
fail((message == null ? "" : (message + ' '))
+ "expected: <" + list + "> to contain sublist: <" + sublist + ">");
}
}
protected void assertContainsSelfEdgeEvent(String label) {
assertContainsEvent(label + " [self-edge]");
}
protected Iterable<Artifact> collectRunfiles(ConfiguredTarget target) {
RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class);
if (runfilesProvider != null) {
return runfilesProvider.getDefaultRunfiles().getAllArtifacts();
} else {
return Runfiles.EMPTY.getAllArtifacts();
}
}
protected NestedSet<Artifact> getFilesToBuild(TransitiveInfoCollection target) {
return target.getProvider(FileProvider.class).getFilesToBuild();
}
/**
* Returns all extra actions for that target (no transitive actions), no duplicate actions.
*/
protected ImmutableList<Action> getExtraActionActions(ConfiguredTarget target) {
LinkedHashSet<Action> result = new LinkedHashSet<>();
for (Artifact artifact : getExtraActionArtifacts(target)) {
result.add(getGeneratingAction(artifact));
}
return ImmutableList.copyOf(result);
}
/**
* Returns all extra actions for that target (including transitive actions).
*/
protected ImmutableList<ExtraAction> getTransitiveExtraActionActions(ConfiguredTarget target) {
ImmutableList.Builder<ExtraAction> result = new ImmutableList.Builder<>();
for (Artifact artifact :
target
.getProvider(ExtraActionArtifactsProvider.class)
.getTransitiveExtraActionArtifacts()) {
Action action = getGeneratingAction(artifact);
if (action instanceof ExtraAction) {
result.add((ExtraAction) action);
}
}
return result.build();
}
protected ImmutableList<Action> getFilesToBuildActions(ConfiguredTarget target) {
List<Action> result = new ArrayList<>();
for (Artifact artifact : getFilesToBuild(target)) {
Action action = getGeneratingAction(artifact);
if (action != null) {
result.add(action);
}
}
return ImmutableList.copyOf(result);
}
protected NestedSet<Artifact> getOutputGroup(
TransitiveInfoCollection target, String outputGroup) {
OutputGroupProvider provider = OutputGroupProvider.get(target);
return provider == null
? NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)
: provider.getOutputGroup(outputGroup);
}
protected NestedSet<Artifact> getExtraActionArtifacts(ConfiguredTarget target) {
return target.getProvider(ExtraActionArtifactsProvider.class).getExtraActionArtifacts();
}
protected Artifact getExecutable(String label) throws Exception {
return getConfiguredTarget(label).getProvider(FilesToRunProvider.class).getExecutable();
}
protected Artifact getExecutable(TransitiveInfoCollection target) {
return target.getProvider(FilesToRunProvider.class).getExecutable();
}
protected NestedSet<Artifact> getFilesToRun(TransitiveInfoCollection target) {
return target.getProvider(FilesToRunProvider.class).getFilesToRun();
}
protected NestedSet<Artifact> getFilesToRun(Label label) throws Exception {
return getConfiguredTarget(label, targetConfig)
.getProvider(FilesToRunProvider.class).getFilesToRun();
}
protected NestedSet<Artifact> getFilesToRun(String label) throws Exception {
return getConfiguredTarget(label).getProvider(FilesToRunProvider.class).getFilesToRun();
}
protected RunfilesSupport getRunfilesSupport(String label) throws Exception {
return getConfiguredTarget(label).getProvider(FilesToRunProvider.class).getRunfilesSupport();
}
protected RunfilesSupport getRunfilesSupport(TransitiveInfoCollection target) {
return target.getProvider(FilesToRunProvider.class).getRunfilesSupport();
}
protected static Runfiles getDefaultRunfiles(ConfiguredTarget target) {
return target.getProvider(RunfilesProvider.class).getDefaultRunfiles();
}
protected static Runfiles getDataRunfiles(ConfiguredTarget target) {
return target.getProvider(RunfilesProvider.class).getDataRunfiles();
}
protected BuildConfiguration getTargetConfiguration() {
return Iterables.getOnlyElement(masterConfig.getTargetConfigurations());
}
protected BuildConfiguration getDataConfiguration() 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 ct.getAttributeMapper();
}
protected AttributeMap attributes(ConfiguredTarget rule) {
return attributes((RuleConfiguredTarget) rule);
}
protected AnalysisResult update(List<String> targets,
boolean keepGoing,
int loadingPhaseThreads,
boolean doAnalysis,
EventBus eventBus) throws Exception {
return update(
targets, ImmutableList.<String>of(), keepGoing, loadingPhaseThreads, doAnalysis, eventBus);
}
protected AnalysisResult update(
List<String> targets,
List<String> aspects,
boolean keepGoing,
int loadingPhaseThreads,
boolean doAnalysis,
EventBus eventBus)
throws Exception {
LoadingOptions loadingOptions = Options.getDefaults(LoadingOptions.class);
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<Artifact> artifactNamed(final String name) {
return new Predicate<Artifact>() {
@Override
public boolean apply(Artifact input) {
return name.equals(input.prettyPrint());
}
};
}
/**
* Utility method for tests. Converts an array of strings into a set of labels.
*
* @param strings the set of strings to be converted to labels.
* @throws LabelSyntaxException if there are any syntax errors in the strings.
*/
public static Set<Label> asLabelSet(String... strings) throws LabelSyntaxException {
return asLabelSet(ImmutableList.copyOf(strings));
}
/**
* Utility method for tests. Converts an array of strings into a set of labels.
*
* @param strings the set of strings to be converted to labels.
* @throws LabelSyntaxException if there are any syntax errors in the strings.
*/
public static Set<Label> asLabelSet(Iterable<String> strings) throws LabelSyntaxException {
Set<Label> result = Sets.newTreeSet();
for (String s : strings) {
result.add(Label.parseAbsolute(s));
}
return result;
}
protected String getErrorMsgSingleFile(String attrName, String ruleType, String ruleName,
String depRuleName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": '"
+ depRuleName + "' must produce a single file";
}
protected String getErrorMsgNoGoodFiles(String attrName, String ruleType, String ruleName,
String depRuleName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": '"
+ depRuleName + "' does not produce any " + ruleType + " " + attrName + " files";
}
protected String getErrorMsgMisplacedFiles(String attrName, String ruleType, String ruleName,
String fileName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": file '"
+ fileName + "' is misplaced here";
}
protected String getErrorNonExistingTarget(String attrName, String ruleType, String ruleName,
String targetName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": target '"
+ targetName + "' does not exist";
}
protected String getErrorNonExistingRule(String attrName, String ruleType, String ruleName,
String targetName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": rule '"
+ targetName + "' does not exist";
}
protected String getErrorMsgMisplacedRules(String attrName, String ruleType, String ruleName,
String depRuleType, String depRuleName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": "
+ depRuleType + " rule '" + depRuleName + "' is misplaced here";
}
protected String getErrorMsgNonEmptyList(String attrName, String ruleType, String ruleName) {
return "in " + attrName + " attribute of " + ruleType + " rule " + ruleName + ": attribute "
+ "must be non empty";
}
protected String getErrorMsgMandatoryMissing(String attrName, String ruleType) {
return "missing value for mandatory attribute '" + attrName + "' in '" + ruleType + "' rule";
}
protected String getErrorMsgWrongAttributeValue(String value, String... expected) {
return String.format("has to be one of %s instead of '%s'",
StringUtil.joinEnglishList(ImmutableSet.copyOf(expected), "or", "'"), value);
}
protected String getErrorMsgMandatoryProviderMissing(String offendingRule, String providerName) {
return String.format("'%s' does not have mandatory providers: '%s'",
offendingRule, providerName);
}
/**
* Utility method for tests that result in errors early during
* package loading. Given the name of the package for the test,
* and the rules for the build file, create a scratch file, load
* the build file, and produce the package.
* @param packageName the name of the package for the build file
* @param lines the rules for the build file as an array of strings
* @return the loaded package from the populated package cache
* @throws Exception if there is an error creating the temporary files
* for the test.
*/
protected com.google.devtools.build.lib.packages.Package createScratchPackageForImplicitCycle(
String packageName, String... lines) throws Exception {
eventCollector.clear();
reporter.removeHandler(failFastHandler);
scratch.file("" + packageName + "/BUILD", lines);
return getPackageManager()
.getPackage(reporter, PackageIdentifier.createInMainRepo(packageName));
}
/**
* A stub analysis environment.
*/
protected class StubAnalysisEnvironment implements AnalysisEnvironment {
@Override
public void registerAction(ActionAnalysisMetadata... action) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasErrors() {
return false;
}
@Override
public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) {
throw new UnsupportedOperationException();
}
@Override
public Artifact getTreeArtifact(PathFragment rootRelativePath, Root root) {
throw new UnsupportedOperationException();
}
@Override
public ExtendedEventHandler getEventHandler() {
return reporter;
}
@Override
public MiddlemanFactory getMiddlemanFactory() {
throw new UnsupportedOperationException();
}
@Override
public Action getLocalGeneratingAction(Artifact artifact) {
throw new UnsupportedOperationException();
}
@Override
public List<ActionAnalysisMetadata> getRegisteredActions() {
throw new UnsupportedOperationException();
}
@Override
public SkyFunction.Environment getSkyframeEnv() {
throw new UnsupportedOperationException();
}
@Override
public SkylarkSemantics getSkylarkSemantics() {
throw new UnsupportedOperationException();
}
@Override
public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) {
throw new UnsupportedOperationException();
}
@Override
public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) {
throw new UnsupportedOperationException();
}
@Override
public Artifact getStableWorkspaceStatusArtifact() {
throw new UnsupportedOperationException();
}
@Override
public Artifact getVolatileWorkspaceStatusArtifact() {
throw new UnsupportedOperationException();
}
@Override
public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key,
BuildConfiguration config) {
throw new UnsupportedOperationException();
}
@Override
public ArtifactOwner getOwner() {
throw new UnsupportedOperationException();
}
@Override
public ImmutableSet<Artifact> getOrphanArtifacts() {
throw new UnsupportedOperationException();
}
}
protected Iterable<String> baselineCoverageArtifactBasenames(ConfiguredTarget target)
throws Exception {
ImmutableList.Builder<String> basenames = ImmutableList.builder();
for (Artifact baselineCoverage : target
.getProvider(InstrumentedFilesProvider.class)
.getBaselineCoverageArtifacts()) {
BaselineCoverageAction baselineAction =
(BaselineCoverageAction) getGeneratingAction(baselineCoverage);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
baselineAction.newDeterministicWriter(ActionsTestUtil.createContext(reporter))
.writeOutputFile(bytes);
for (String line : new String(bytes.toByteArray(), StandardCharsets.UTF_8).split("\n")) {
if (line.startsWith("SF:")) {
String basename = line.substring(line.lastIndexOf('/') + 1);
basenames.add(basename);
}
}
}
return basenames.build();
}
/**
* Finds an artifact in the transitive closure of a set of other artifacts by following a path
* based on artifact name suffixes.
*
* <p>This selects the first artifact in the input set that matches the first suffix, then selects
* the first artifact in the inputs of its generating action that matches the second suffix etc.,
* and repeats this until the supplied suffixes run out.
*/
protected Artifact artifactByPath(Iterable<Artifact> artifacts, String... suffixes) {
Artifact artifact = getFirstArtifactEndingWith(artifacts, suffixes[0]);
Action action = null;
for (int i = 1; i < suffixes.length; i++) {
if (artifact == null) {
if (action == null) {
throw new IllegalStateException("No suffix " + suffixes[0] + " among artifacts: "
+ ActionsTestUtil.baseArtifactNames(artifacts));
} else {
throw new IllegalStateException("No suffix " + suffixes[i]
+ " among inputs of action " + action.describe() + ": "
+ ActionsTestUtil.baseArtifactNames(artifacts));
}
}
action = getGeneratingAction(artifact);
artifacts = action.getInputs();
artifact = getFirstArtifactEndingWith(artifacts, suffixes[i]);
}
return artifact;
}
/**
* Retrieves an instance of {@code PseudoAction} that is shadowed by an extra action
* @param targetLabel Label of the target with an extra action
* @param actionListenerLabel Label of the action listener
*/
protected PseudoAction<?> getPseudoActionViaExtraAction(
String targetLabel, String actionListenerLabel) throws Exception {
useConfiguration(String.format("--experimental_action_listener=%s", actionListenerLabel));
ConfiguredTarget target = getConfiguredTarget(targetLabel);
List<Action> actions = getExtraActionActions(target);
assertThat(actions).isNotNull();
assertThat(actions).hasSize(2);
ExtraAction extraAction = null;
for (Action action : actions) {
if (action instanceof ExtraAction) {
extraAction = (ExtraAction) action;
break;
}
}
assertWithMessage(actions.toString()).that(extraAction).isNotNull();
Action pseudoAction = extraAction.getShadowedAction();
assertThat(pseudoAction).isInstanceOf(PseudoAction.class);
assertThat(pseudoAction.getPrimaryOutput().getExecPathString())
.isEqualTo(
String.format(
"%s%s.extra_action_dummy",
targetConfig.getGenfilesFragment(), convertLabelToPath(targetLabel)));
return (PseudoAction<?>) pseudoAction;
}
/**
* Converts the given label to an output path where double slashes and colons are
* replaced with single slashes
* @param label
*/
private String convertLabelToPath(String label) {
return label.replace(':', '/').substring(1);
}
protected Map<String, String> getSymlinkTreeManifest(Artifact outputManifest) throws Exception {
SymlinkTreeAction symlinkTreeAction = (SymlinkTreeAction) getGeneratingAction(outputManifest);
Artifact inputManifest = Iterables.getOnlyElement(symlinkTreeAction.getInputs());
SourceManifestAction inputManifestAction =
(SourceManifestAction) getGeneratingAction(inputManifest);
// Ask the manifest to write itself to a byte array so that we can
// read its contents.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
inputManifestAction.writeOutputFile(stream, reporter);
String contents = stream.toString();
// Get the file names from the manifest output.
ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
for (String line : Splitter.on('\n').split(contents)) {
int space = line.indexOf(' ');
if (space < 0) {
continue;
}
result.put(line.substring(0, space), line.substring(space + 1));
}
return result.build();
}
protected Artifact getImplicitOutputArtifact(
ConfiguredTarget target, SafeImplicitOutputsFunction outputFunction) {
return getImplicitOutputArtifact(target, target.getConfiguration(), outputFunction);
}
protected Artifact getImplicitOutputArtifact(
ConfiguredTarget target,
BuildConfiguration configuration,
SafeImplicitOutputsFunction outputFunction) {
Rule associatedRule = target.getTarget().getAssociatedRule();
RepositoryName repository = associatedRule.getRepository();
Root root;
if (associatedRule.hasBinaryOutput()) {
root = configuration.getBinDirectory(repository);
} else {
root = configuration.getGenfilesDirectory(repository);
}
ArtifactOwner owner =
new ConfiguredTargetKey(target.getTarget().getLabel(), target.getConfiguration());
RawAttributeMapper attr = RawAttributeMapper.of(associatedRule);
String path = Iterables.getOnlyElement(outputFunction.getImplicitOutputs(attr));
return view.getArtifactFactory()
.getDerivedArtifact(
target.getTarget().getLabel().getPackageFragment().getRelative(path), root, owner);
}
}