blob: db183ce9ea5744f1fb9292e4e143eb66f8df1388 [file] [log] [blame]
// 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.ActionGraph;
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.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.exec.ExecutionOptions;
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.SkyValueDirtinessChecker;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
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}).
*
* <p>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}.
*
* <p>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<Flag> 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;
protected boolean enableLoading = true;
@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);
BinTools binTools = BinTools.forUnitTesting(directories, TestConstants.EMBEDDED_TOOLS);
skyframeExecutor =
SequencedSkyframeExecutor.create(
reporter,
pkgFactory,
new TimestampGranularityMonitor(BlazeClock.instance()),
directories,
binTools,
workspaceStatusActionFactory,
ruleClassProvider.getBuildInfoFactories(),
ImmutableSet.<Path>of(),
ImmutableList.<DiffAwareness.Factory>of(),
Predicates.<PathFragment>alwaysFalse(),
Preprocessor.Factory.Supplier.NullSupplier.INSTANCE,
ImmutableMap.<SkyFunctionName, SkyFunction>of(),
getPrecomputedValues(),
ImmutableList.<SkyValueDirtinessChecker>of());
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, ruleClassProvider, skyframeExecutor, null);
useConfiguration();
}
protected ImmutableList<PrecomputedValue.Injected> 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 BuildConfigurationCollection getBuildConfigurationCollection() {
return masterConfig;
}
protected BuildConfiguration getTargetConfiguration() {
return Iterables.getOnlyElement(masterConfig.getTargetConfigurations());
}
protected BuildConfiguration getHostConfiguration() {
return masterConfig.getHostConfiguration();
}
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<Flag> 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(
null, packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory);
skyframeExecutor.preparePackageLoading(pathPackageLocator,
packageCacheOptions.defaultVisibility, true,
7, ruleClassProvider.getDefaultsPackageContent(), UUID.randomUUID());
skyframeExecutor.invalidateFilesUnderPathForTesting(reporter,
ModifiedFileSet.EVERYTHING_MODIFIED, rootDirectory);
LoadingResult loadingResult = loadingPhaseRunner
.execute(reporter, eventBus, ImmutableList.copyOf(labels), loadingOptions,
buildOptions.getAllLabels(), viewOptions.keepGoing, enableLoading,
/*determineTests=*/false, /*callback=*/null);
BuildRequestOptions requestOptions = optionsParser.getOptions(BuildRequestOptions.class);
ImmutableSortedSet<String> multiCpu = ImmutableSortedSet.copyOf(requestOptions.multiCpus);
masterConfig = skyframeExecutor.createConfigurations(
reporter, configurationFactory, buildOptions, directories, multiCpu, false);
analysisResult =
buildView.update(
loadingResult,
masterConfig,
ImmutableList.<String>of(),
viewOptions,
AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT,
reporter,
eventBus,
enableLoading);
}
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 (LabelSyntaxException 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 (LabelSyntaxException 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<SkyKey> getSkyframeEvaluatedTargetKeys() {
return buildView.getSkyframeEvaluatedTargetKeysForTesting();
}
protected int getTargetsVisited() {
return buildView.getTargetsVisited();
}
protected String getAnalysisError() {
ensureUpdateWasCalled();
return analysisResult.getError();
}
protected BuildView getView() {
return buildView;
}
protected ActionGraph getActionGraph() {
return skyframeExecutor.getActionGraph(reporter);
}
protected AnalysisResult getAnalysisResult() {
return analysisResult;
}
protected void clearAnalysisResult() {
analysisResult = null;
}
protected void disableLoading() {
enableLoading = false;
}
}