// Copyright 2011-2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.analysis.util;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.BuildView;
import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.InputFileConfiguredTarget;
import com.google.devtools.build.lib.analysis.config.BinTools;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
import com.google.devtools.build.lib.buildtool.BuildRequest.BuildRequestOptions;
import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.packages.Preprocessor;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.util.MockToolsConfig;
import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner;
import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner.LoadingResult;
import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
import com.google.devtools.build.lib.pkgcache.PackageManager;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.skyframe.DiffAwareness;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.testutil.TestConstants;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.build.lib.util.BlazeClock;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.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 java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* Testing framework for tests of the analysis phase that uses the BuildView and LoadingPhaseRunner
* APIs correctly (compared to {@link BuildViewTestCase}).
*
*
The intended usage pattern is to first call {@link #update} with the set of targets, and then
* assert properties of the configured targets obtained from {@link #getConfiguredTarget}.
*
*
This class intentionally does not inherit from {@link BuildViewTestCase}; BuildViewTestCase
* abuses the BuildView API in ways that are incompatible with the goals of this test, i.e. the
* convenience methods provided there wouldn't work here.
*/
public abstract class AnalysisTestCase extends FoundationTestCase {
private static final int LOADING_PHASE_THREADS = 20;
/** All the flags that can be passed to {@link BuildView#update}. */
public enum Flag {
KEEP_GOING
}
/** Helper class to make it easy to enable and disable flags. */
public static final class FlagBuilder {
private final Set flags = new HashSet<>();
public FlagBuilder with(Flag flag) {
flags.add(flag);
return this;
}
public FlagBuilder without(Flag flag) {
flags.remove(flag);
return this;
}
}
protected BlazeDirectories directories;
protected MockToolsConfig mockToolsConfig;
private OptionsParser optionsParser;
protected PackageManager packageManager;
private LoadingPhaseRunner loadingPhaseRunner;
private ConfigurationFactory configurationFactory;
private BuildView buildView;
// Note that these configurations are virtual (they use only VFS)
private BuildConfigurationCollection masterConfig;
private AnalysisResult analysisResult;
protected SkyframeExecutor skyframeExecutor = null;
protected ConfiguredRuleClassProvider ruleClassProvider;
protected AnalysisTestUtil.DummyWorkspaceStatusActionFactory workspaceStatusActionFactory;
private PathPackageLocator pkgLocator;
@Override
protected void setUp() throws Exception {
super.setUp();
AnalysisMock mock = getAnalysisMock();
pkgLocator = new PathPackageLocator(rootDirectory);
directories = new BlazeDirectories(outputBase, outputBase, rootDirectory);
workspaceStatusActionFactory =
new AnalysisTestUtil.DummyWorkspaceStatusActionFactory(directories);
mockToolsConfig = new MockToolsConfig(rootDirectory);
mock.setupMockClient(mockToolsConfig);
mock.setupMockWorkspaceFiles(directories.getEmbeddedBinariesRoot());
configurationFactory = mock.createConfigurationFactory();
useRuleClassProvider(TestRuleClassProvider.getRuleClassProvider());
}
protected AnalysisMock getAnalysisMock() {
try {
Class> providerClass = Class.forName(TestConstants.TEST_ANALYSIS_MOCK);
Field instanceField = providerClass.getField("INSTANCE");
return (AnalysisMock) instanceField.get(null);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/**
* Changes the rule class provider to be used for the loading and the analysis phase.
*/
protected void useRuleClassProvider(ConfiguredRuleClassProvider ruleClassProvider)
throws Exception {
this.ruleClassProvider = ruleClassProvider;
PackageFactory pkgFactory = new PackageFactory(ruleClassProvider);
skyframeExecutor = SequencedSkyframeExecutor.create(reporter, pkgFactory,
new TimestampGranularityMonitor(BlazeClock.instance()), directories,
workspaceStatusActionFactory,
ruleClassProvider.getBuildInfoFactories(), ImmutableSet.of(),
ImmutableList.of(),
Predicates.alwaysFalse(),
Preprocessor.Factory.Supplier.NullSupplier.INSTANCE,
ImmutableMap.of(),
getPrecomputedValues()
);
skyframeExecutor.preparePackageLoading(pkgLocator,
Options.getDefaults(PackageCacheOptions.class).defaultVisibility, true,
3, ruleClassProvider.getDefaultsPackageContent(), UUID.randomUUID());
packageManager = skyframeExecutor.getPackageManager();
loadingPhaseRunner = new LoadingPhaseRunner(packageManager, pkgFactory.getRuleClassNames());
buildView = new BuildView(directories, skyframeExecutor.getPackageManager(), ruleClassProvider,
skyframeExecutor, BinTools.forUnitTesting(directories, TestConstants.EMBEDDED_TOOLS), null);
useConfiguration();
}
protected ImmutableList getPrecomputedValues() {
return ImmutableList.of();
}
protected final void useConfigurationFactory(ConfigurationFactory configurationFactory) {
this.configurationFactory = configurationFactory;
}
/**
* Sets host and target configuration using the specified options, falling back to the default
* options for unspecified ones, and recreates the build view.
*/
protected final void useConfiguration(String... args) throws Exception {
optionsParser = OptionsParser.newOptionsParser(Iterables.concat(Arrays.asList(
ExecutionOptions.class,
PackageCacheOptions.class,
BuildRequestOptions.class,
BuildView.Options.class),
ruleClassProvider.getConfigurationOptions()));
optionsParser.parse(new String[] {"--default_visibility=public" });
optionsParser.parse(args);
}
protected FlagBuilder defaultFlags() {
return new FlagBuilder();
}
protected Action getGeneratingAction(Artifact artifact) {
ensureUpdateWasCalled();
return analysisResult.getActionGraph().getGeneratingAction(artifact);
}
protected BuildConfiguration getTargetConfiguration() {
return Iterables.getOnlyElement(masterConfig.getTargetConfigurations());
}
protected BuildConfiguration getHostConfiguration() {
return getTargetConfiguration().getConfiguration(ConfigurationTransition.HOST);
}
protected final void ensureUpdateWasCalled() {
Preconditions.checkState(analysisResult != null, "You must run update() first!");
}
/**
* Update the BuildView: syncs the package cache; loads and analyzes the given labels.
*/
protected void update(EventBus eventBus, FlagBuilder config, String... labels) throws Exception {
Set flags = config.flags;
LoadingPhaseRunner.Options loadingOptions =
Options.getDefaults(LoadingPhaseRunner.Options.class);
loadingOptions.loadingPhaseThreads = LOADING_PHASE_THREADS;
BuildView.Options viewOptions = optionsParser.getOptions(BuildView.Options.class);
viewOptions.keepGoing = flags.contains(Flag.KEEP_GOING);
BuildOptions buildOptions = ruleClassProvider.createBuildOptions(optionsParser);
PackageCacheOptions packageCacheOptions = optionsParser.getOptions(PackageCacheOptions.class);
PathPackageLocator pathPackageLocator = PathPackageLocator.create(
packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory);
skyframeExecutor.preparePackageLoading(pathPackageLocator,
packageCacheOptions.defaultVisibility, true,
7, ruleClassProvider.getDefaultsPackageContent(), UUID.randomUUID());
skyframeExecutor.invalidateFilesUnderPathForTesting(ModifiedFileSet.EVERYTHING_MODIFIED,
rootDirectory);
LoadingResult loadingResult = loadingPhaseRunner
.execute(reporter, eventBus, ImmutableList.copyOf(labels), loadingOptions,
buildOptions.getAllLabels(), viewOptions.keepGoing, /*determineTests=*/false,
/*callback=*/null);
BuildRequestOptions requestOptions = optionsParser.getOptions(BuildRequestOptions.class);
ImmutableSortedSet multiCpu = ImmutableSortedSet.copyOf(requestOptions.multiCpus);
masterConfig = skyframeExecutor.createConfigurations(
configurationFactory, buildOptions, directories, multiCpu, false);
analysisResult = buildView.update(loadingResult, masterConfig, viewOptions,
AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT, reporter, eventBus);
}
protected void update(FlagBuilder config, String... labels) throws Exception {
update(new EventBus(), config, labels);
}
/**
* Update the BuildView: syncs the package cache; loads and analyzes the given labels.
*/
protected void update(String... labels) throws Exception {
update(new EventBus(), defaultFlags(), labels);
}
protected Target getTarget(String label) {
try {
return SkyframeExecutorTestUtils.getExistingTarget(skyframeExecutor,
Label.parseAbsolute(label));
} catch (SyntaxException e) {
throw new AssertionError(e);
}
}
protected ConfiguredTarget getConfiguredTarget(String label, BuildConfiguration configuration) {
ensureUpdateWasCalled();
return getConfiguredTargetForSkyframe(label, configuration);
}
private ConfiguredTarget getConfiguredTargetForSkyframe(String label,
BuildConfiguration configuration) {
Label parsedLabel;
try {
parsedLabel = Label.parseAbsolute(label);
} catch (SyntaxException e) {
throw new AssertionError(e);
}
return skyframeExecutor.getConfiguredTargetForTesting(parsedLabel, configuration);
}
/**
* Returns the corresponding configured target, if it exists. Note that this will only return
* anything useful after a call to update() with the same label.
*/
protected ConfiguredTarget getConfiguredTarget(String label) {
return getConfiguredTarget(label, getTargetConfiguration());
}
/**
* Returns the corresponding configured target, if it exists. Note that this will only return
* anything useful after a call to update() with the same label. The label passed in must
* represent an input file.
*/
protected InputFileConfiguredTarget getInputFileConfiguredTarget(String label) {
return (InputFileConfiguredTarget) getConfiguredTarget(label, null);
}
protected boolean hasErrors(ConfiguredTarget configuredTarget) {
return buildView.hasErrors(configuredTarget);
}
protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner) {
Label label = owner.getLabel();
return buildView.getArtifactFactory().getDerivedArtifact(
label.getPackageFragment().getRelative(packageRelativePath),
getTargetConfiguration().getBinDirectory(),
new ConfiguredTargetKey(owner));
}
protected Set getSkyframeEvaluatedTargetKeys() {
return buildView.getSkyframeEvaluatedTargetKeysForTesting();
}
protected int getTargetsVisited() {
return buildView.getTargetsVisited();
}
protected String getAnalysisError() {
ensureUpdateWasCalled();
return analysisResult.getError();
}
protected BuildView getView() {
return buildView;
}
protected AnalysisResult getAnalysisResult() {
return analysisResult;
}
protected void clearAnalysisResult() {
analysisResult = null;
}
}