blob: 1b507d1be67e438849ebecf69364e472e071c61d [file] [log] [blame]
// Copyright 2019 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 com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import static org.junit.Assert.fail;
import com.google.common.base.Ascii;
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.ActionExecutionContext.LostInputsCheck;
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.ActionLookupValue;
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.DerivedArtifact;
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.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.BuildConfigurationCollection;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.CoreOptions.ConfigsMode;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
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.skylark.StarlarkTransition;
import com.google.devtools.build.lib.analysis.test.BaselineCoverageAction;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
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.LabelConstants;
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.NestedSetExpander;
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.PackageValidator;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
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.ManagedDirectoriesKnowledge;
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.PackageRootsNoSymlinkCreation;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.build.lib.testutil.BlazeTestUtils;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.testutil.SkyframeExecutorTestHelper;
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.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
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.HashSet;
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<String> configurationArgs;
private ConfigsMode configsMode = ConfigsMode.NOTRIM;
protected OptionsParser optionsParser;
private PackageCacheOptions packageCacheOptions;
private StarlarkSemanticsOptions starlarkSemanticsOptions;
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);
mockToolsConfig.create("bazel_tools_workspace/WORKSPACE", "workspace(name = 'bazel_tools')");
mockToolsConfig.create("bazel_tools_workspace/tools/build_defs/repo/BUILD");
mockToolsConfig.create(
"bazel_tools_workspace/tools/build_defs/repo/utils.bzl",
"def maybe(repo_rule, name, **kwargs):",
" if name not in native.existing_rules():",
" repo_rule(name = name, **kwargs)");
mockToolsConfig.create(
"bazel_tools_workspace/tools/build_defs/repo/http.bzl",
"def http_archive(**kwargs):",
" pass",
"",
"def http_file(**kwargs):",
" pass");
initializeMockClient();
packageCacheOptions = parsePackageCacheOptions();
starlarkSemanticsOptions = parseSkylarkSemanticsOptions();
workspaceStatusActionFactory = new AnalysisTestUtil.DummyWorkspaceStatusActionFactory();
mutableActionGraph = new MapBasedActionGraph(actionKeyContext);
ruleClassProvider = getRuleClassProvider();
getOutputPath().createDirectoryAndParents();
ImmutableList<PrecomputedValue.Injected> extraPrecomputedValues =
ImmutableList.of(
PrecomputedValue.injected(
PrecomputedValue.STARLARK_SEMANTICS, StarlarkSemantics.DEFAULT_SEMANTICS),
PrecomputedValue.injected(PrecomputedValue.REPO_ENV, ImmutableMap.<String, String>of()),
PrecomputedValue.injected(
RepositoryDelegatorFunction.REPOSITORY_OVERRIDES,
ImmutableMap.<RepositoryName, PathFragment>of()),
PrecomputedValue.injected(
RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE,
Optional.<RootedPath>absent()),
PrecomputedValue.injected(
RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING,
RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY),
PrecomputedValue.injected(
PrecomputedValue.BUILD_INFO_FACTORIES,
ruleClassProvider.getBuildInfoFactoriesAsMap()));
PackageFactory.BuilderForTesting pkgFactoryBuilder =
analysisMock
.getPackageFactoryBuilderForTesting(directories)
.setExtraPrecomputeValues(extraPrecomputedValues)
.setEnvironmentExtensions(getEnvironmentExtensions())
.setPackageValidator(getPackageValidator());
if (!doPackageLoadingChecks) {
pkgFactoryBuilder.disableChecks();
}
pkgFactory = pkgFactoryBuilder.build(ruleClassProvider, fileSystem);
tsgm = new TimestampGranularityMonitor(BlazeClock.instance());
skyframeExecutor =
BazelSkyframeExecutorConstants.newBazelSkyframeExecutorBuilder()
.setPkgFactory(pkgFactory)
.setFileSystem(fileSystem)
.setDirectories(directories)
.setActionKeyContext(actionKeyContext)
.setDefaultBuildOptions(
DefaultBuildOptionsForTesting.getDefaultBuildOptionsForTest(ruleClassProvider))
.setWorkspaceStatusActionFactory(workspaceStatusActionFactory)
.setExtraSkyFunctions(analysisMock.getSkyFunctions(directories))
.setManagedDirectoriesKnowledge(getManagedDirectoriesKnowledge())
.build();
SkyframeExecutorTestHelper.process(skyframeExecutor);
skyframeExecutor.injectExtraPrecomputedValues(extraPrecomputedValues);
packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PUBLIC;
packageCacheOptions.showLoadingProgress = true;
packageCacheOptions.globbingThreads = 7;
skyframeExecutor.preparePackageLoading(
new PathPackageLocator(
outputBase,
ImmutableList.of(root),
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY),
packageCacheOptions,
starlarkSemanticsOptions,
UUID.randomUUID(),
ImmutableMap.<String, String>of(),
tsgm);
skyframeExecutor.setActionEnv(ImmutableMap.<String, String>of());
useConfiguration();
setUpSkyframe();
this.actionLogBufferPathGenerator =
new ActionLogBufferPathGenerator(
directories.getActionTempsDirectory(getExecRoot()),
directories.getPersistentActionOutsDirectory(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 ManagedDirectoriesKnowledge getManagedDirectoriesKnowledge() {
return null;
}
protected PackageFactory getPackageFactory() {
return pkgFactory;
}
protected Iterable<EnvironmentExtension> getEnvironmentExtensions() {
return ImmutableList.<EnvironmentExtension>of();
}
protected StarlarkSemantics getSkylarkSemantics() {
return starlarkSemanticsOptions.toSkylarkSemantics();
}
protected PackageValidator getPackageValidator() {
return PackageValidator.NOOP_VALIDATOR;
}
protected final BuildConfigurationCollection createConfigurations(
ImmutableMap<String, Object> skylarkOptions, String... args) throws Exception {
optionsParser =
OptionsParser.builder()
.optionsClasses(
Iterables.concat(
Arrays.asList(ExecutionOptions.class, BuildRequestOptions.class),
ruleClassProvider.getConfigurationOptions()))
.build();
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");
// Always default to k8, even on mac and windows. Tests that need different cpu should set it
// using {@link useConfiguration()} explicitly.
allArgs.add("--cpu=k8");
allArgs.add("--host_cpu=k8");
optionsParser.parse(allArgs);
optionsParser.parse(args);
// TODO(juliexxia): when the starlark options parsing work goes in, add type verification here.
optionsParser.setStarlarkOptions(skylarkOptions);
BuildOptions buildOptions = ruleClassProvider.createBuildOptions(optionsParser);
return skyframeExecutor.createConfigurations(
reporter, buildOptions, ImmutableSet.<String>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.
*
* <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,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY);
packageCacheOptions.showLoadingProgress = true;
packageCacheOptions.globbingThreads = 7;
skyframeExecutor.preparePackageLoading(
pkgLocator,
packageCacheOptions,
starlarkSemanticsOptions,
UUID.randomUUID(),
ImmutableMap.<String, String>of(),
tsgm);
skyframeExecutor.setActionEnv(ImmutableMap.<String, String>of());
skyframeExecutor.setDeletedPackages(ImmutableSet.copyOf(packageCacheOptions.getDeletedPackages()));
skyframeExecutor.injectExtraPrecomputedValues(
ImmutableList.of(
PrecomputedValue.injected(
RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE,
Optional.<RootedPath>absent()),
PrecomputedValue.injected(
RepositoryDelegatorFunction.OUTPUT_VERIFICATION_REPOSITORY_RULES,
ImmutableSet.<String>of()),
PrecomputedValue.injected(
RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION,
Optional.<RootedPath>absent())));
}
protected void setPackageCacheOptions(String... options) throws Exception {
packageCacheOptions = parsePackageCacheOptions(options);
setUpSkyframe();
}
protected void setSkylarkSemanticsOptions(String... options) throws Exception {
starlarkSemanticsOptions = parseSkylarkSemanticsOptions(options);
setUpSkyframe();
}
private static PackageCacheOptions parsePackageCacheOptions(String... options) throws Exception {
OptionsParser parser =
OptionsParser.builder().optionsClasses(PackageCacheOptions.class).build();
parser.parse("--default_visibility=public");
parser.parse(options);
return parser.getOptions(PackageCacheOptions.class);
}
private static StarlarkSemanticsOptions parseSkylarkSemanticsOptions(String... options)
throws Exception {
OptionsParser parser =
OptionsParser.builder().optionsClasses(StarlarkSemanticsOptions.class).build();
parser.parse(options);
return parser.getOptions(StarlarkSemanticsOptions.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, 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.
*
* TODO(juliexxia): when skylark option parsing exists, find a way to combine these parameters
* into a single parameter so skylark/native options don't have to be specified separately.
*
* @param skylarkOptions map of skylark-defined options where the keys are option names (in the
* form of label-like strings) and the values are option values
* @param args native option name/pair descriptions in command line form (e.g. "--cpu=k8")
*
* @throws IllegalArgumentException
*/
protected void useConfiguration(ImmutableMap<String, Object> skylarkOptions, String... args)
throws Exception {
ImmutableList<String> actualArgs =
ImmutableList.<String>builder()
.addAll(TestConstants.PRODUCT_SPECIFIC_FLAGS)
.add(args)
.add("--experimental_dynamic_configs=" + Ascii.toLowerCase(configsMode.toString()))
.build();
masterConfig = createConfigurations(skylarkOptions, actualArgs.toArray(new String[0]));
targetConfig = getTargetConfiguration();
targetConfigKey = BuildConfigurationValue.key(targetConfig);
configurationArgs = actualArgs;
createBuildView();
}
protected void useConfiguration(String... args) throws Exception {
useConfiguration(ImmutableMap.of(), args);
}
/**
* 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());
skyframeExecutor.handleAnalysisInvalidatingChange();
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,
new ActionLookupValue.ActionLookupKey() {
@Override
public SkyFunctionName functionName() {
return null;
}
},
/*isSystemEnv=*/ true,
/*extendedSanityChecks=*/ false,
/*allowAnalysisFailures=*/ false,
reporter,
skyframeExecutor.getSkyFunctionEnvironmentForTesting(reporter));
}
/**
* 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<ConfiguredTarget> getDirectPrerequisites(ConfiguredTarget target)
throws Exception {
return view.getDirectPrerequisitesForTesting(reporter, target, masterConfig);
}
protected final Collection<ConfiguredTargetAndData> 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.
*
* <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)) {
result.addAll(provider.getFilesToBuild().toList());
}
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<String> 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.
*
* <p>If no param file is used, return the action's arguments.
*/
@Nullable
protected final Iterable<String> 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
protected ParameterFileWriteAction paramFileWriteActionForAction(Action action) {
for (Artifact input : action.getInputs().toList()) {
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) {
if (artifact.isSourceArtifact() || !((DerivedArtifact) artifact).hasGeneratingActionKey()) {
return null;
}
actionAnalysisMetadata = getActionGraph().getGeneratingAction(artifact);
}
return actionAnalysisMetadata;
}
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.toList(), 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<Artifact> 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).toList(), artifactNamed(outputName)));
}
protected final List<String> getGeneratingSpawnActionArgs(Artifact artifact)
throws CommandLineExpansionException {
SpawnAction a = getGeneratingSpawnAction(artifact);
return 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.
*
* <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.
*
* @throws AssertionError if the target cannot be transitioned into with the given configuration
*/
protected ConfiguredTarget getConfiguredTarget(Label label, BuildConfiguration config) {
try {
return view.getConfiguredTargetForTesting(
reporter, BlazeTestUtils.convertLabel(label), config);
} catch (InvalidConfigurationException
| StarlarkTransition.TransitionException
| InterruptedException e) {
throw new AssertionError(e);
}
}
/**
* Returns a ConfiguredTargetAndData for the specified label, using the given build configuration.
*/
protected ConfiguredTargetAndData getConfiguredTargetAndData(
Label label, BuildConfiguration config)
throws StarlarkTransition.TransitionException, InvalidConfigurationException,
InterruptedException {
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, StarlarkTransition.TransitionException,
InvalidConfigurationException, InterruptedException {
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.
*
* <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();
}
/**
* 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 {
// Allow to create the BUILD file also in the top package.
String buildFilePathString = packageName.isEmpty() ? "BUILD" : packageName + "/BUILD";
if (packageName.equals(LabelConstants.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 containing
* '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);
}
/**
* Check that configuration of the target named 'label' fails with an error message containing
* 'expectedErrorMessage'.
*
* @param label the target name to test
* @param expectedErrorMessage the expected error message.
* @return the found error.
*/
protected Event checkError(String label, String expectedErrorMessage) throws Exception {
eventCollector.clear();
reporter.removeHandler(failFastHandler); // expect errors
ConfiguredTarget target = getConfiguredTarget(label);
if (target != null) {
assertWithMessage("Rule '" + label + "' 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);
// The error happens during the loading of the Skylark file so checkError doesn't work here
assertThrows(Exception.class, () -> getTarget(target));
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(NestedSet<? extends Artifact> artifacts) {
return artifactsToStrings(artifacts.toList());
}
/**
* 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<? extends 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()).isSameInstanceAs(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))
.isSameInstanceAs(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 final Artifact.DerivedArtifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
if ((owner instanceof ActionLookupValue.ActionLookupKey)) {
ActionLookupValue actionLookupValue;
try {
actionLookupValue =
(ActionLookupValue)
skyframeExecutor.getEvaluatorForTesting().getExistingValue((SkyKey) owner);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
if (actionLookupValue != null) {
for (ActionAnalysisMetadata action : actionLookupValue.getActions()) {
for (Artifact output : action.getOutputs()) {
if (output.getRootRelativePath().equals(rootRelativePath)
&& output.getRoot().equals(root)) {
return (Artifact.DerivedArtifact) output;
}
}
}
}
}
// Fall back: some tests don't actually need an artifact with an owner.
return view.getArtifactFactory().getDerivedArtifact(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) {
ActionLookupValue.ActionLookupKey actionLookupKey =
ConfiguredTargetKey.of(owner, owner.getConfigurationKey(), /*isHostConfiguration=*/ false);
return getDerivedArtifact(
owner.getLabel().getPackageFragment().getRelative(packageRelativePath),
getConfiguration(owner).getBinDirectory(RepositoryName.MAIN),
actionLookupKey);
}
/**
* 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);
}
/** Returns the input {@link Artifact}s to the given {@link Action} with the given exec paths. */
protected List<Artifact> getInputs(Action owner, Collection<String> execPaths) {
Set<String> expectedPaths = new HashSet<>(execPaths);
List<Artifact> result = new ArrayList<>();
for (Artifact output : owner.getInputs().toList()) {
if (expectedPaths.remove(output.getExecPathString())) {
result.add(output);
}
}
assertWithMessage("expected paths not found in: %s", Artifact.asExecPaths(owner.getInputs()))
.that(expectedPaths)
.isEmpty();
return result;
}
/**
* 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.DerivedArtifact 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));
}
/**
* 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);
}
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#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/".
*
* <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;
}
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(ruleClassProvider.getRunfilesPrefix());
}
/**
* 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,
"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<String> actionInputsToPaths(NestedSet<? extends ActionInput> actionInputs) {
return ImmutableList.copyOf(
Iterables.transform(
actionInputs.toList(), (actionInput) -> 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<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 NestedSet<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).toList()) {
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()
.toList()) {
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).toList()) {
Action action = getGeneratingAction(artifact);
if (action != null) {
result.add(action);
}
}
return ImmutableList.copyOf(result);
}
protected NestedSet<Artifact> getOutputGroup(
TransitiveInfoCollection target, String outputGroup) {
OutputGroupInfo provider = OutputGroupInfo.get(target);
return provider == null
? NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)
: provider.getOutputGroup(outputGroup);
}
protected NestedSet<Artifact.DerivedArtifact> 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 getHostConfiguration() {
return masterConfig.getHostConfiguration();
}
/**
* Returns the configuration created by applying the given transition to the source configuration.
*
* @throws AssertionError if the transition couldn't be evaluated
*/
protected BuildConfiguration getConfiguration(
BuildConfiguration fromConfig, PatchTransition transition) throws InterruptedException {
if (transition == NoTransition.INSTANCE) {
return fromConfig;
} else if (transition == NullTransition.INSTANCE) {
return null;
} else {
try {
return skyframeExecutor.getConfigurationForTesting(
reporter, fromConfig.fragmentClasses(), transition.patch(fromConfig.getOptions()));
} catch (OptionsParsingException | InvalidConfigurationException e) {
throw new AssertionError(e);
}
}
}
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
| StarlarkTransition.TransitionException
| InvalidConfigurationException
| InterruptedException 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<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 =
customLoadingOptions == null
? Options.getDefaults(LoadingOptions.class)
: customLoadingOptions;
AnalysisOptions viewOptions = Options.getDefaults(AnalysisOptions.class);
TargetPatternPhaseValue loadingResult =
skyframeExecutor.loadTargetPatternsWithFilters(
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<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, ImmutableMap.of()));
}
return result;
}
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 + ": source 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, ArtifactRoot root) {
throw new UnsupportedOperationException();
}
@Override
public SpecialArtifact getTreeArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
throw new UnsupportedOperationException();
}
@Override
public SpecialArtifact getSymlinkArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
throw new UnsupportedOperationException();
}
@Override
public Artifact getSourceArtifactForNinjaBuild(PathFragment execPath, 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 ImmutableList<ActionAnalysisMetadata> getRegisteredActions() {
throw new UnsupportedOperationException();
}
@Override
public SkyFunction.Environment getSkyframeEnv() {
throw new UnsupportedOperationException();
}
@Override
public StarlarkSemantics getSkylarkSemantics() {
return starlarkSemanticsOptions.toSkylarkSemantics();
}
@Override
public Artifact getFilesetArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
throw new UnsupportedOperationException();
}
@Override
public Artifact.DerivedArtifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root) {
throw new UnsupportedOperationException();
}
@Override
public Artifact.DerivedArtifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) {
throw new UnsupportedOperationException();
}
@Override
public Artifact getStableWorkspaceStatusArtifact() {
throw new UnsupportedOperationException();
}
@Override
public Artifact getVolatileWorkspaceStatusArtifact() {
throw new UnsupportedOperationException();
}
@Override
public ImmutableList<Artifact> getBuildInfo(
boolean stamp, BuildInfoKey key, BuildConfiguration config) {
throw new UnsupportedOperationException();
}
@Override
public ActionLookupValue.ActionLookupKey getOwner() {
throw new UnsupportedOperationException();
}
@Override
public ImmutableSet<Artifact> getOrphanArtifacts() {
throw new UnsupportedOperationException();
}
@Override
public ImmutableSet<Artifact> getTreeArtifactsConflictingWithFiles() {
throw new UnsupportedOperationException();
}
@Override
public ActionKeyContext getActionKeyContext() {
return actionKeyContext;
}
}
protected Iterable<String> baselineCoverageArtifactBasenames(ConfiguredTarget target)
throws Exception {
ImmutableList.Builder<String> basenames = ImmutableList.builder();
for (Artifact baselineCoverage :
target
.get(InstrumentedFilesInfo.SKYLARK_CONSTRUCTOR)
.getBaselineCoverageArtifacts()
.toList()) {
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(NestedSet<Artifact> artifacts, String... suffixes) {
return artifactByPath(artifacts.toList(), suffixes);
}
/**
* 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().toList();
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 final String getImplicitOutputPath(
ConfiguredTarget target, SafeImplicitOutputsFunction outputFunction) {
Rule rule;
try {
rule = (Rule) skyframeExecutor.getPackageManager().getTarget(reporter, target.getLabel());
} catch (NoSuchPackageException | NoSuchTargetException | InterruptedException e) {
throw new IllegalStateException(e);
}
RawAttributeMapper attr = RawAttributeMapper.of(rule.getAssociatedRule());
return Iterables.getOnlyElement(outputFunction.getImplicitOutputs(eventCollector, attr));
}
/**
* Gets the artifact whose name is derived from {@code outputFunction}. Despite the name, this can
* be called for artifacts that are not declared as implicit outputs: it just finds the artifact
* inside the configured target by calling {@link #getBinArtifact(String, ConfiguredTarget)} on
* the result of the {@code outputFunction}.
*/
protected final Artifact getImplicitOutputArtifact(
ConfiguredTarget target, SafeImplicitOutputsFunction outputFunction) {
return getBinArtifact(getImplicitOutputPath(target, outputFunction), target);
}
public Path getExecRoot() {
return directories.getExecRoot(ruleClassProvider.getRunfilesPrefix());
}
/** Returns true iff commandLine contains the option --flagName followed by arg. */
protected static boolean containsFlag(String flagName, String arg, Iterable<String> commandLine) {
Iterator<String> iterator = commandLine.iterator();
while (iterator.hasNext()) {
if (flagName.equals(iterator.next()) && iterator.hasNext() && arg.equals(iterator.next())) {
return true;
}
}
return false;
}
/** Returns the list of arguments in commandLine that follow after --flagName. */
protected static ImmutableList<String> flagValue(String flagName, Iterable<String> commandLine) {
ImmutableList.Builder<String> resultBuilder = ImmutableList.builder();
Iterator<String> iterator = commandLine.iterator();
boolean found = false;
while (iterator.hasNext()) {
String val = iterator.next();
if (found) {
if (val.startsWith("--")) {
break;
}
resultBuilder.add(val);
} else if (flagName.equals(val)) {
found = true;
}
}
Preconditions.checkArgument(found);
return resultBuilder.build();
}
/** Creates instances of {@link ActionExecutionContext} consistent with test case. */
public class ActionExecutionContextBuilder {
private MetadataProvider actionInputFileCache = null;
private TreeMap<String, String> clientEnv = new TreeMap<>();
private ArtifactExpander artifactExpander = null;
public ActionExecutionContextBuilder setMetadataProvider(
MetadataProvider actionInputFileCache) {
this.actionInputFileCache = actionInputFileCache;
return this;
}
public ActionExecutionContextBuilder setArtifactExpander(ArtifactExpander artifactExpander) {
this.artifactExpander = artifactExpander;
return this;
}
public ActionExecutionContext build() {
return new ActionExecutionContext(
new DummyExecutor(fileSystem, getExecRoot()),
actionInputFileCache,
/*actionInputPrefetcher=*/ null,
actionKeyContext,
/*metadataHandler=*/ null,
LostInputsCheck.NONE,
actionLogBufferPathGenerator.generate(ArtifactPathResolver.IDENTITY),
reporter,
clientEnv,
/*topLevelFilesets=*/ ImmutableMap.of(),
artifactExpander,
/*actionFileSystem=*/ null,
/*skyframeDepsResult*/ null,
NestedSetExpander.DEFAULT);
}
}
}