Open-source BlazeRuntimeWrapper and BuildIntegrationTestCase.
These classes are used by some tests that should be open-sourced.
PiperOrigin-RevId: 251217041
diff --git a/src/BUILD b/src/BUILD
index e3f5d64..68961d0 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -435,6 +435,7 @@
"//src/main/cpp:srcs",
"//src/main/java/com/google/devtools/build/docgen:srcs",
"//src/main/java/com/google/devtools/build/lib:srcs",
+ "//src/main/java/com/google/devtools/build/lib/includescanning:srcs",
"//src/main/java/com/google/devtools/build/lib/network:srcs",
"//src/main/java/com/google/devtools/build/skydoc:srcs",
"//src/main/java/com/google/devtools/build/skyframe:srcs",
diff --git a/src/main/java/com/google/devtools/build/lib/includescanning/BUILD b/src/main/java/com/google/devtools/build/lib/includescanning/BUILD
new file mode 100644
index 0000000..0ee8f14
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/includescanning/BUILD
@@ -0,0 +1,37 @@
+package(
+ default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["*"]),
+)
+
+java_library(
+ name = "includescanning",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:events",
+ "//src/main/java/com/google/devtools/build/lib:io",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
+ "//src/main/java/com/google/devtools/build/lib:resource-converter",
+ "//src/main/java/com/google/devtools/build/lib:runtime",
+ "//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity",
+ "//src/main/java/com/google/devtools/build/lib/analysis/platform",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/concurrent",
+ "//src/main/java/com/google/devtools/build/lib/profiler",
+ "//src/main/java/com/google/devtools/build/lib/rules/cpp",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
+ "//src/main/java/com/google/devtools/build/lib/vfs:output_service",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+ "//src/main/java/com/google/devtools/build/skyframe",
+ "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+ "//src/main/java/com/google/devtools/common/options",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
index 15976e8..5d38918 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
@@ -15,9 +15,44 @@
srcs = glob(["util/*.java"]),
deps = [
"//src/main/java/com/google/devtools/build/lib:bazel-main",
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:build-request-options",
+ "//src/main/java/com/google/devtools/build/lib:command-utils",
+ "//src/main/java/com/google/devtools/build/lib:events",
+ "//src/main/java/com/google/devtools/build/lib:exitcode-external",
+ "//src/main/java/com/google/devtools/build/lib:io",
+ "//src/main/java/com/google/devtools/build/lib:keep-going-option",
+ "//src/main/java/com/google/devtools/build/lib:loading-phase-threads-option",
+ "//src/main/java/com/google/devtools/build/lib:logging-util",
+ "//src/main/java/com/google/devtools/build/lib:out-err",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
"//src/main/java/com/google/devtools/build/lib:runtime",
+ "//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
+ "//src/main/java/com/google/devtools/build/lib/exec/local:options",
+ "//src/main/java/com/google/devtools/build/lib/includescanning",
+ "//src/main/java/com/google/devtools/build/lib/network:connectivity",
+ "//src/main/java/com/google/devtools/build/lib/sandbox",
+ "//src/main/java/com/google/devtools/build/lib/shell",
+ "//src/main/java/com/google/devtools/build/lib/standalone",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
+ "//src/main/java/com/google/devtools/build/lib/vfs:output_service",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+ "//src/main/java/com/google/devtools/common/options:invocation_policy",
+ "//src/main/java/com/google/devtools/common/options:options_internal",
+ "//src/main/protobuf:invocation_policy_java_proto",
+ "//src/test/java/com/google/devtools/build/lib:actions_testutil",
+ "//src/test/java/com/google/devtools/build/lib:analysis_testutil",
+ "//src/test/java/com/google/devtools/build/lib:foundations_testutil",
+ "//src/test/java/com/google/devtools/build/lib:integration_testutil",
"//src/test/java/com/google/devtools/build/lib:packages_testutil",
"//src/test/java/com/google/devtools/build/lib:testutil",
+ "//src/test/java/com/google/devtools/build/lib/skyframe:testutil",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
],
)
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
new file mode 100644
index 0000000..5a02509
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
@@ -0,0 +1,384 @@
+// 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.buildtool.util;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.analysis.AnalysisOptions;
+import com.google.devtools.build.lib.analysis.AnalysisPhaseCompleteEvent;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.ServerDirectories;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
+import com.google.devtools.build.lib.buildtool.BuildResult;
+import com.google.devtools.build.lib.buildtool.BuildTool;
+import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
+import com.google.devtools.build.lib.exec.BinTools;
+import com.google.devtools.build.lib.exec.ExecutionOptions;
+import com.google.devtools.build.lib.exec.local.LocalExecutionOptions;
+import com.google.devtools.build.lib.network.ConnectivityModule;
+import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
+import com.google.devtools.build.lib.pkgcache.LoadingOptions;
+import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
+import com.google.devtools.build.lib.runtime.BlazeCommand;
+import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler;
+import com.google.devtools.build.lib.runtime.BlazeCommandResult;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.ClientOptions;
+import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.runtime.CommandStartEvent;
+import com.google.devtools.build.lib.runtime.CommonCommandOptions;
+import com.google.devtools.build.lib.runtime.GotOptionsEvent;
+import com.google.devtools.build.lib.runtime.KeepGoingOption;
+import com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption;
+import com.google.devtools.build.lib.runtime.commands.BuildCommand;
+import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
+import com.google.devtools.build.lib.sandbox.SandboxOptions;
+import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
+import com.google.devtools.build.lib.util.ExitCode;
+import com.google.devtools.build.lib.util.io.OutErr;
+import com.google.devtools.build.lib.vfs.OutputService;
+import com.google.devtools.common.options.InvocationPolicyEnforcer;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper for {@link BlazeRuntime} for testing purposes that makes it possible to exercise
+ * (most) of the build machinery in integration tests. Note that {@code BlazeCommandDispatcher}
+ * is not exercised here.
+ */
+public class BlazeRuntimeWrapper {
+ private final BlazeRuntime runtime;
+ private CommandEnvironment env;
+ private final EventCollectionApparatus events;
+ private boolean commandCreated;
+
+ private BuildRequest lastRequest;
+ private BuildResult lastResult;
+ private BuildConfigurationCollection configurations;
+ private ImmutableSet<ConfiguredTarget> topLevelTargets;
+
+ private OptionsParser optionsParser;
+ private ImmutableList.Builder<String> optionsToParse = new ImmutableList.Builder<>();
+
+ private Consumer<EventBus> eventBusReceiver;
+
+ public BlazeRuntimeWrapper(
+ EventCollectionApparatus events, ServerDirectories serverDirectories,
+ BlazeDirectories directories, BinTools binTools, BlazeRuntime.Builder builder)
+ throws Exception {
+ this.events = events;
+ runtime =
+ builder
+ .setServerDirectories(serverDirectories)
+ .addBlazeModule(
+ new BlazeModule() {
+ @Override
+ public void beforeCommand(CommandEnvironment env) {
+ // This only does something interesting for tests that create their own
+ // BlazeCommandDispatcher. :-(
+ if (BlazeRuntimeWrapper.this.env != env) {
+ BlazeRuntimeWrapper.this.env = env;
+ BlazeRuntimeWrapper.this.lastRequest = null;
+ BlazeRuntimeWrapper.this.lastResult = null;
+ resetOptions();
+ env.getEventBus().register(this);
+ }
+ }
+
+ @Subscribe
+ public void analysisPhaseComplete(AnalysisPhaseCompleteEvent e) {
+ topLevelTargets = ImmutableSet.copyOf(e.getTopLevelTargets());
+ }
+ })
+ .addBlazeModule(
+ new BlazeModule() {
+ @Override
+ public void beforeCommand(CommandEnvironment env) {
+ BlazeRuntimeWrapper.this.events.initExternal(env.getReporter());
+ }
+ })
+ .build();
+ runtime.initWorkspace(directories, binTools);
+ optionsParser = createOptionsParser();
+ }
+
+ @Command(name = "build", builds = true, help = "", shortDescription = "")
+ private static class DummyBuildCommand {}
+
+ public OptionsParser createOptionsParser() {
+ Set<Class<? extends OptionsBase>> options =
+ new HashSet<>(
+ ImmutableList.of(
+ BuildRequestOptions.class,
+ ExecutionOptions.class,
+ LocalExecutionOptions.class,
+ CommonCommandOptions.class,
+ ClientOptions.class,
+ LoadingOptions.class,
+ AnalysisOptions.class,
+ KeepGoingOption.class,
+ LoadingPhaseThreadsOption.class,
+ PackageCacheOptions.class,
+ StarlarkSemanticsOptions.class,
+ BlazeCommandEventHandler.Options.class,
+ ConnectivityModule.ConnectivityOptions.class,
+ SandboxOptions.class));
+
+ for (BlazeModule module : runtime.getBlazeModules()) {
+ Iterables.addAll(options, module.getCommonCommandOptions());
+ Iterables.addAll(
+ options, module.getCommandOptions(DummyBuildCommand.class.getAnnotation(Command.class)));
+ }
+ options.addAll(runtime.getRuleClassProvider().getConfigurationOptions());
+ return OptionsParser.newOptionsParser(options);
+ }
+
+ private void enforceTestInvocationPolicy(OptionsParser parser) {
+ InvocationPolicyEnforcer optionsPolicyEnforcer =
+ new InvocationPolicyEnforcer(runtime.getModuleInvocationPolicy());
+ try {
+ optionsPolicyEnforcer.enforce(parser);
+ } catch (OptionsParsingException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public BlazeRuntime getRuntime() {
+ return runtime;
+ }
+
+ /**
+ * If called with a non-null argument, all new EventBuses are posted on creation to the given
+ * receiver.
+ */
+ public void setEventBusReceiver(Consumer<EventBus> receiver) {
+ eventBusReceiver = receiver;
+ }
+
+ public final CommandEnvironment newCommand() throws Exception {
+ return newCommand(BuildCommand.class);
+ }
+
+ /** Creates a new command environment; executeBuild does this automatically if you do not. */
+ public final CommandEnvironment newCommand(Class<? extends BlazeCommand> buildCommand)
+ throws Exception {
+ initializeOptionsParser();
+ commandCreated = true;
+ if (env != null) {
+ runtime.afterCommand(env, BlazeCommandResult.exitCode(ExitCode.SUCCESS));
+ }
+
+ if (optionsParser == null) {
+ throw new IllegalArgumentException("The options parser must be initialized before creating "
+ + "a new command environment");
+ }
+
+ env =
+ runtime
+ .getWorkspace()
+ .initCommand(
+ buildCommand.getAnnotation(Command.class), optionsParser, new ArrayList<>());
+ return env;
+ }
+
+ /**
+ * Returns the command environment. You must call {@link #newCommand()} before calling this
+ * method.
+ */
+ public CommandEnvironment getCommandEnvironment() {
+ // In these tests, calls to the CommandEnvironment are not always in correct order; this is OK
+ // for unit tests. So return an environment here, that has a forced command id to allow tests to
+ // stay simple.
+ try {
+ env.getCommandId();
+ } catch (IllegalArgumentException e) {
+ // Ignored, as we know that tests deviate from normal calling order.
+ }
+
+ return env;
+ }
+
+ public SkyframeExecutor getSkyframeExecutor() {
+ return runtime.getWorkspace().getSkyframeExecutor();
+ }
+
+ public void resetOptions() {
+ optionsToParse = new ImmutableList.Builder<>();
+ }
+
+ public void addOptions(String... args) {
+ addOptions(ImmutableList.copyOf(args));
+ }
+
+ public void addOptions(List<String> args) {
+ optionsToParse.addAll(args);
+ }
+
+ public ImmutableList<String> getOptions() {
+ return optionsToParse.build();
+ }
+
+ public <O extends OptionsBase> O getOptions(Class<O> optionsClass) {
+ return optionsParser.getOptions(optionsClass);
+ }
+
+ protected void finalizeBuildResult(@SuppressWarnings("unused") BuildResult request) {}
+
+ /**
+ * Initializes a new options parser, parsing all the options set by {@link
+ * #addOptions(String...)}.
+ */
+ public void initializeOptionsParser() throws OptionsParsingException {
+ // Create the options parser and parse all the options collected so far
+ optionsParser = createOptionsParser();
+ optionsParser.parse(optionsToParse.build());
+ // Enforce the test invocation policy once the options have been added
+ enforceTestInvocationPolicy(optionsParser);
+ }
+
+ public void executeBuild(List<String> targets) throws Exception {
+ if (!commandCreated) {
+ // If you didn't create a command we do it for you
+ newCommand();
+ }
+ commandCreated = false;
+ BuildTool buildTool = new BuildTool(env);
+ PrintStream origSystemOut = System.out;
+ PrintStream origSystemErr = System.err;
+ try {
+ OutErr outErr = env.getReporter().getOutErr();
+ System.setOut(new PrintStream(outErr.getOutputStream(), /*autoflush=*/ true));
+ System.setErr(new PrintStream(outErr.getErrorStream(), /*autoflush=*/ true));
+
+ // This cannot go into newCommand, because we hook up the EventCollectionApparatus as a
+ // module, and after that ran, further changes to the apparatus aren't reflected on the
+ // reporter.
+ for (BlazeModule module : getRuntime().getBlazeModules()) {
+ module.beforeCommand(env);
+ }
+ if (eventBusReceiver != null) {
+ eventBusReceiver.accept(env.getEventBus());
+ }
+ env.getEventBus()
+ .post(
+ new GotOptionsEvent(
+ getRuntime().getStartupOptionsProvider(),
+ optionsParser,
+ InvocationPolicy.getDefaultInstance()));
+ // This roughly mimics what BlazeRuntime#beforeCommand does in practice.
+ env.throwPendingException();
+
+ // In this test we are allowed to omit the beforeCommand; so force setting of a command
+ // id in the CommandEnvironment, as we will need it in a moment even though we deviate from
+ // normal calling order.
+ try {
+ env.getCommandId();
+ } catch (IllegalArgumentException e) {
+ // Ignored, as we know the test deviates from normal calling order.
+ }
+
+ OutputService outputService = null;
+ BlazeModule outputModule = null;
+ for (BlazeModule module : runtime.getBlazeModules()) {
+ OutputService moduleService = module.getOutputService();
+ if (moduleService != null) {
+ if (outputService != null) {
+ throw new IllegalStateException(
+ String.format(
+ "More than one module (%s and %s) returns an output service",
+ module.getClass(), outputModule.getClass()));
+ }
+ outputService = moduleService;
+ outputModule = module;
+ }
+ }
+ getSkyframeExecutor().setOutputService(outputService);
+ env.setOutputServiceForTesting(outputService);
+
+ env.getEventBus()
+ .post(
+ new CommandStartEvent(
+ "build", env.getCommandId(), env.getClientEnv(), env.getWorkingDirectory(),
+ env.getDirectories(), 0));
+
+ lastRequest = createRequest("build", targets);
+ lastResult = new BuildResult(lastRequest.getStartTime());
+ boolean success = false;
+
+ for (BlazeModule module : getRuntime().getBlazeModules()) {
+ env.getSkyframeExecutor().injectExtraPrecomputedValues(module.getPrecomputedValues());
+ }
+
+ try {
+ buildTool.buildTargets(lastRequest, lastResult, null);
+ success = true;
+ } finally {
+ env
+ .getTimestampGranularityMonitor()
+ .waitForTimestampGranularity(lastRequest.getOutErr());
+ this.configurations = lastResult.getBuildConfigurationCollection();
+ finalizeBuildResult(lastResult);
+ buildTool.stopRequest(
+ lastResult, null, success ? ExitCode.SUCCESS : ExitCode.BUILD_FAILURE);
+ getSkyframeExecutor().notifyCommandComplete(env.getReporter());
+ }
+ } finally {
+ System.setOut(origSystemOut);
+ System.setErr(origSystemErr);
+ }
+ }
+
+ public BuildRequest createRequest(String commandName, List<String> targets) {
+ return BuildRequest.create(
+ commandName,
+ optionsParser,
+ null,
+ targets,
+ env.getReporter().getOutErr(),
+ env.getCommandId(),
+ runtime.getClock().currentTimeMillis());
+ }
+
+ public BuildRequest getLastRequest() {
+ return lastRequest;
+ }
+
+ public BuildResult getLastResult() {
+ return lastResult;
+ }
+
+ public BuildConfigurationCollection getConfigurationCollection() {
+ return configurations;
+ }
+
+ public ImmutableSet<ConfiguredTarget> getTopLevelTargets() {
+ return topLevelTargets;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java
new file mode 100644
index 0000000..28c9f40
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BuildIntegrationTestCase.java
@@ -0,0 +1,787 @@
+// 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.buildtool.util;
+
+import static com.google.common.base.Throwables.throwIfUnchecked;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.eventbus.SubscriberExceptionContext;
+import com.google.common.eventbus.SubscriberExceptionHandler;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionGraph;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.ServerDirectories;
+import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+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.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.skylark.StarlarkTransition.TransitionException;
+import com.google.devtools.build.lib.analysis.util.AnalysisMock;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil.DummyWorkspaceStatusActionContext;
+import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.buildtool.BuildResult;
+import com.google.devtools.build.lib.buildtool.BuildTool;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
+import com.google.devtools.build.lib.exec.BinTools;
+import com.google.devtools.build.lib.exec.ExecutorBuilder;
+import com.google.devtools.build.lib.includescanning.IncludeScanningModule;
+import com.google.devtools.build.lib.integration.util.IntegrationMock;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.util.MockToolsConfig;
+import com.google.devtools.build.lib.pkgcache.PackageManager;
+import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.BlazeServerStartupOptions;
+import com.google.devtools.build.lib.runtime.BlazeWorkspace;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.runtime.WorkspaceBuilder;
+import com.google.devtools.build.lib.shell.AbnormalTerminationException;
+import com.google.devtools.build.lib.shell.Command;
+import com.google.devtools.build.lib.shell.CommandException;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
+import com.google.devtools.build.lib.skyframe.PrecomputedValue;
+import com.google.devtools.build.lib.skyframe.PrecomputedValue.Injected;
+import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
+import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
+import com.google.devtools.build.lib.standalone.StandaloneModule;
+import com.google.devtools.build.lib.testutil.Suite;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.testutil.TestConstants.InternalTestExecutionMode;
+import com.google.devtools.build.lib.testutil.TestFileOutErr;
+import com.google.devtools.build.lib.testutil.TestSpec;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.CommandBuilder;
+import com.google.devtools.build.lib.util.CommandUtils;
+import com.google.devtools.build.lib.util.LoggingUtil;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.util.io.OutErr;
+import com.google.devtools.build.lib.util.io.RecordingOutErr;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.util.FileSystems;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * A base class for integration tests that use the {@link BuildTool}. These tests basically run a
+ * little build and check what happens.
+ *
+ * <p>All integration tests are at least size medium.
+ */
+@TestSpec(size = Suite.MEDIUM_TESTS)
+public abstract class BuildIntegrationTestCase {
+
+ /** Thrown when an integration test case fails. */
+ public static class IntegrationTestExecException extends ExecException {
+ public IntegrationTestExecException(String message) {
+ super(message);
+ }
+
+ public IntegrationTestExecException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ @Override
+ public ActionExecutionException toActionExecutionException(
+ String messagePrefix, boolean verboseFailures, Action action) {
+ return new ActionExecutionException(messagePrefix + getMessage(), getCause(), action, true);
+ }
+ }
+
+ protected FileSystem fileSystem;
+ protected EventCollectionApparatus events = new EventCollectionApparatus();
+ protected OutErr outErr = OutErr.SYSTEM_OUT_ERR;
+ protected Path testRoot;
+ protected ServerDirectories serverDirectories;
+ protected BlazeDirectories directories;
+ protected MockToolsConfig mockToolsConfig;
+ protected BinTools binTools;
+
+ protected BlazeRuntimeWrapper runtimeWrapper;
+ protected Path outputBase;
+ protected String outputBaseName = "outputBase";
+
+ private Path workspace;
+ protected RecordingExceptionHandler subscriberException = new RecordingExceptionHandler();
+
+ @Before
+ public final void createFilesAndMocks() throws Exception {
+ runPriorToBeforeMethods();
+ events.setFailFast(false);
+ // TODO(mschaller): This will ignore any attempt by Blaze modules to provide a filesystem;
+ // consider something better.
+ this.fileSystem = createFileSystem();
+ this.testRoot = createTestRoot(fileSystem);
+
+ outputBase = testRoot.getRelative(outputBaseName);
+ outputBase.createDirectoryAndParents();
+ workspace = testRoot.getRelative(getDesiredWorkspaceRelative());
+ beforeCreatingWorkspace(workspace);
+ workspace.createDirectoryAndParents();
+ serverDirectories = createServerDirectories();
+ directories =
+ new BlazeDirectories(
+ serverDirectories,
+ workspace,
+ /* defaultSystemJavabase= */ null,
+ TestConstants.PRODUCT_NAME);
+ binTools =
+ IntegrationMock.get().getIntegrationBinTools(fileSystem, directories);
+ mockToolsConfig = new MockToolsConfig(workspace, realFileSystem());
+ setupMockTools();
+ createRuntimeWrapper();
+ }
+
+ protected ServerDirectories createServerDirectories() {
+ return new ServerDirectories(
+ /*installBase=*/ outputBase,
+ /*outputBase=*/ outputBase,
+ /*outputUserRoot=*/ outputBase,
+ /*execRootBase=*/ outputBase.getRelative("execroot"),
+ // Arbitrary install base hash.
+ /*installMD5=*/ "83bc4458738962b9b77480bac76164a9");
+ }
+
+ protected void createRuntimeWrapper() throws Exception {
+ runtimeWrapper =
+ new BlazeRuntimeWrapper(
+ events,
+ serverDirectories,
+ directories,
+ binTools,
+ getRuntimeBuilder().setEventBusExceptionHandler(subscriberException)) {
+ @Override
+ protected void finalizeBuildResult(BuildResult result) {
+ finishBuildResult(result);
+ }
+ };
+ setupOptions();
+ }
+
+ protected void runPriorToBeforeMethods() throws Exception {
+ // Allows tests such as SkyframeIntegrationInvalidationTest to execute code before all @Before
+ // methods are being run.
+ }
+
+ @After
+ public final void cleanUp() throws Exception {
+ if (subscriberException.getException() != null) {
+ throwIfUnchecked(subscriberException.getException());
+ throw new RuntimeException(subscriberException.getException());
+ }
+ LoggingUtil.installRemoteLoggerForTesting(null);
+ testRoot.deleteTreesBelow(); // (comment out during debugging)
+ }
+
+ /**
+ * A helper class that can be used to record exceptions that occur on the event bus, by passing an
+ * instance of it to BlazeRuntime#setEventBusExceptionHandler.
+ */
+ public static final class RecordingExceptionHandler implements SubscriberExceptionHandler {
+ private Throwable exception;
+
+ @Override
+ public void handleException(Throwable exception, SubscriberExceptionContext context) {
+ System.err.println("subscriber exception: ");
+ exception.printStackTrace();
+ if (this.exception == null) {
+ this.exception = exception;
+ }
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+ }
+
+ /**
+ * Returns the relative path (from {@code testRoot}) to the desired workspace. This method may be
+ * called in {@link #createFilesAndMocks}, so overrides this method should not use any variables
+ * that may not have been initialized yet.
+ */
+ protected PathFragment getDesiredWorkspaceRelative() {
+ return PathFragment.create("workspace");
+ }
+
+ protected InternalTestExecutionMode getInternalTestExecutionMode() {
+ return InternalTestExecutionMode.NORMAL;
+ }
+
+ /**
+ * Called in #setUp before creating the workspace directory. Subclasses should override this
+ * if they want to a non-standard filesystem setup, e.g. introduce symlinked directories.
+ */
+ protected void beforeCreatingWorkspace(@SuppressWarnings("unused") Path workspace)
+ throws Exception {}
+
+ protected void finishBuildResult(@SuppressWarnings("unused") BuildResult result) {}
+
+ protected boolean realFileSystem() {
+ return true;
+ }
+
+ protected FileSystem createFileSystem() throws Exception {
+ return FileSystems.getNativeFileSystem();
+ }
+
+ protected Path createTestRoot(FileSystem fileSystem) {
+ return fileSystem.getPath(TestUtils.tmpDir());
+ }
+
+ // This is only here to support HaskellNonIntegrationTest. You should not call or override this
+ // method.
+ protected void setupMockTools() throws IOException {
+ // (Almost) every integration test calls BuildView.doLoadingPhase, which loads the default
+ // crosstool, etc. So we create these package here.
+ AnalysisMock.get().setupMockClient(mockToolsConfig);
+ }
+
+ protected BlazeModule getBuildInfoModule() {
+ return new BlazeModule() {
+ @Override
+ public void workspaceInit(
+ BlazeRuntime runtime, BlazeDirectories directories, WorkspaceBuilder builder) {
+ builder.setWorkspaceStatusActionFactory(
+ new AnalysisTestUtil.DummyWorkspaceStatusActionFactory());
+ }
+
+ @Override
+ public void executorInit(
+ CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) {
+ builder.addActionContext(new DummyWorkspaceStatusActionContext());
+ }
+ };
+ }
+
+ // There should be one getSpawnModule at a time, as we lack infrastructure to decide from
+ // which Module to take the SpawnActionContext for specific actions.
+ protected BlazeModule getSpawnModule() {
+ return new StandaloneModule();
+ }
+
+ /** Gets a module containing rules (by default, using the TestRuleClassProvider) */
+ protected BlazeModule getRulesModule() {
+ return TestRuleModule.getModule();
+ }
+
+ private BlazeModule getNoResolvedFileModule() {
+ return new BlazeModule() {
+ @Override
+ public ImmutableList<Injected> getPrecomputedValues() {
+ return ImmutableList.of(
+ PrecomputedValue.injected(
+ RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE, Optional.absent()));
+ }
+ };
+ }
+
+ protected BlazeRuntime.Builder getRuntimeBuilder() throws Exception {
+ OptionsParser startupOptionsParser = OptionsParser.newOptionsParser(getStartupOptionClasses());
+ startupOptionsParser.parse(getStartupOptions());
+ return new BlazeRuntime.Builder()
+ .setFileSystem(fileSystem)
+ .setProductName(TestConstants.PRODUCT_NAME)
+ .setStartupOptionsProvider(startupOptionsParser)
+ .addBlazeModule(getNoResolvedFileModule())
+ .addBlazeModule(getSpawnModule())
+ .addBlazeModule(new IncludeScanningModule())
+ .addBlazeModule(getBuildInfoModule())
+ .addBlazeModule(getRulesModule());
+ }
+
+ protected List<String> getStartupOptions() {
+ return ImmutableList.of();
+ }
+
+ protected ImmutableList<Class<? extends OptionsBase>> getStartupOptionClasses() {
+ return ImmutableList.of(BlazeServerStartupOptions.class);
+ }
+
+ protected void setupOptions() throws Exception {
+ runtimeWrapper.resetOptions();
+
+ runtimeWrapper.addOptions(
+ // Set visibility to public so that test cases don't have to bother
+ // with visibility declarations
+ "--default_visibility=public",
+
+ // Don't show progress messages unless we need to, to keep the noise down.
+ "--noshow_progress",
+
+ // Don't use ijars, because we don't have the executable in these tests
+ "--nouse_ijars");
+
+ runtimeWrapper.addOptions("--experimental_extended_sanity_checks");
+ runtimeWrapper.addOptions(TestConstants.PRODUCT_SPECIFIC_FLAGS);
+ }
+
+ protected void resetOptions() {
+ runtimeWrapper.resetOptions();
+ }
+
+ protected void addOptions(String... args) throws Exception {
+ runtimeWrapper.addOptions(args);
+ }
+
+ protected OptionsParser createOptionsParser() {
+ return runtimeWrapper.createOptionsParser();
+ }
+
+ protected Action getGeneratingAction(Artifact artifact) {
+ ActionAnalysisMetadata action = getActionGraph().getGeneratingAction(artifact);
+
+ if (action != null) {
+ Preconditions.checkState(
+ action instanceof Action,
+ "%s is not a proper Action object",
+ action.prettyPrint());
+ return (Action) action;
+ } else {
+ return null;
+ }
+ }
+ /**
+ * Returns the path to the executable that label "target" identifies.
+ *
+ * <p>Assumes that the specified target is executable, i.e. defines getExecutable; use {@link
+ * #getArtifacts} instead if this is not the case.
+ *
+ * @param target the label of the target whose executable location is requested.
+ */
+ protected Path getExecutableLocation(String target)
+ throws LabelSyntaxException, NoSuchPackageException, NoSuchTargetException,
+ InterruptedException, TransitionException, InvalidConfigurationException {
+ return getExecutable(getConfiguredTarget(target)).getPath();
+ }
+
+ /**
+ * Given a label (which has typically, but not necessarily, just been built), returns the
+ * collection of files that it produces.
+ *
+ * @param target the label of the target whose artifacts are requested.
+ */
+ protected Iterable<Artifact> getArtifacts(String target)
+ throws LabelSyntaxException, NoSuchPackageException, NoSuchTargetException,
+ InterruptedException, TransitionException, InvalidConfigurationException {
+ return getFilesToBuild(getConfiguredTarget(target));
+ }
+
+ /**
+ * Given a label (which has typically, but not necessarily, just been built), returns the
+ * configured target for it using the request configuration.
+ *
+ * @param target the label of the requested target.
+ */
+ protected ConfiguredTarget getConfiguredTarget(String target)
+ throws LabelSyntaxException, NoSuchPackageException, NoSuchTargetException,
+ InterruptedException, TransitionException, InvalidConfigurationException {
+ getPackageManager()
+ .getTarget(events.reporter(), Label.parseAbsolute(target, ImmutableMap.of()));
+ return getSkyframeExecutor()
+ .getConfiguredTargetForTesting(events.reporter(), label(target), getTargetConfiguration());
+ }
+
+ protected ConfiguredTarget getConfiguredTarget(
+ ExtendedEventHandler eventHandler, Label label, BuildConfiguration config)
+ throws TransitionException, InvalidConfigurationException {
+ return getSkyframeExecutor().getConfiguredTargetForTesting(eventHandler, label, config);
+ }
+
+ /**
+ * Gets all the already computed configured targets.
+ */
+ protected Iterable<ConfiguredTarget> getAllConfiguredTargets() {
+ return SkyframeExecutorTestUtils.getAllExistingConfiguredTargets(getSkyframeExecutor());
+ }
+
+ /** Gets an existing configured target. */
+ protected ConfiguredTarget getExistingConfiguredTarget(String target)
+ throws InterruptedException, LabelSyntaxException {
+ ConfiguredTarget existingConfiguredTarget =
+ SkyframeExecutorTestUtils.getExistingConfiguredTarget(
+ getSkyframeExecutor(), label(target), getTargetConfiguration());
+ assertWithMessage(target).that(existingConfiguredTarget).isNotNull();
+ return existingConfiguredTarget;
+ }
+
+ protected BuildConfigurationCollection getConfigurationCollection() {
+ return runtimeWrapper.getConfigurationCollection();
+ }
+
+ /**
+ * Returns the target configuration for the most recent build, as created in Blaze's master
+ * configuration creation phase.
+ *
+ * <p>Tries to find the configuration used by all of the top-level targets in the last invocation.
+ * If they used multiple different configurations, or if none of them had a configuration, then
+ * falls back to the base top-level configuration.
+ */
+ protected BuildConfiguration getTargetConfiguration() throws InterruptedException {
+ BuildConfiguration baseConfiguration =
+ Iterables.getOnlyElement(getConfigurationCollection().getTargetConfigurations());
+ BuildResult result = getResult();
+ if (result == null) {
+ return baseConfiguration;
+ }
+ Set<BuildConfiguration> topLevelTargetConfigurations =
+ result
+ .getActualTargets()
+ .stream()
+ .map((ct) -> getConfiguration(ct))
+ .filter((config) -> config != null)
+ .collect(toImmutableSet());
+ if (topLevelTargetConfigurations.size() != 1) {
+ return baseConfiguration;
+ }
+ return Iterables.getOnlyElement(topLevelTargetConfigurations);
+ }
+
+ protected BuildConfiguration getHostConfiguration() {
+ return getConfigurationCollection().getHostConfiguration();
+ }
+
+ protected TopLevelArtifactContext getTopLevelArtifactContext() {
+ return getRequest().getTopLevelArtifactContext();
+ }
+
+ /**
+ * Convenience wrapper around buildTool.syncPackageCache() and buildTool.build() that creates and
+ * executes a BuildRequest. Returns the BuildRequest on success (it is also subsequently
+ * accessible via {@link #getRequest}, even in case of abnormal termination). Also redirects the
+ * output from the reporter's event handler to go to this.OutErr during the build, and redirects
+ * System.out/System.err to go via the reporter (and hence to this.OutErr) during the build.
+ */
+ public BuildResult buildTarget(String... targets) throws Exception {
+ events.setOutErr(this.outErr);
+ runtimeWrapper.executeBuild(Arrays.asList(targets));
+ return runtimeWrapper.getLastResult();
+ }
+
+ /**
+ * Create a BuildRequest for the specified list of targets, using the
+ * currently-installed request options.
+ */
+ protected BuildRequest createRequest(String... targets) throws Exception {
+ return createNewRequest("BuildIntegrationTestCase", targets);
+ }
+
+ /**
+ * Creates a BuildRequest for either blaze build or blaze analyze, using the
+ * currently-installed request options.
+ * @param commandName blaze build or analyze command
+ * @param targets the targets to be built
+ */
+ protected BuildRequest createNewRequest(String commandName, String... targets) throws Exception {
+ runtimeWrapper.initializeOptionsParser();
+ return runtimeWrapper.createRequest(commandName, Arrays.asList(targets));
+ }
+
+ /**
+ * Utility function: parse a string as a label.
+ */
+ protected static Label label(String labelString) throws LabelSyntaxException {
+ return Label.parseAbsolute(labelString, ImmutableMap.of());
+ }
+
+ protected String run(Artifact executable, String... arguments) throws Exception {
+ Map<String, String> environment = null;
+ return run(executable.getPath(), null, environment, arguments);
+ }
+
+ /**
+ * This runs an executable using the executor instance configured for
+ * this test.
+ */
+ protected String run(Path executable, String... arguments) throws Exception {
+ Map<String, String> environment = null;
+ return run(executable, null, environment, arguments);
+ }
+
+ protected String run(Path executable, Path workingDirectory, String... arguments)
+ throws ExecException, InterruptedException {
+ return run(executable, workingDirectory, null, arguments);
+ }
+
+ protected String run(
+ Path executable, Path workingDirectory, Map<String, String> environment, String... arguments)
+ throws ExecException, InterruptedException {
+ RecordingOutErr outErr = new RecordingOutErr();
+ try {
+ run(executable, workingDirectory, outErr, environment, arguments);
+ } catch (ExecException e) {
+ throw new IntegrationTestExecException(
+ "failed to execute '"
+ + executable.getPathString()
+ + "'\n----- captured stdout:\n"
+ + outErr.outAsLatin1()
+ + "\n----- captured stderr:"
+ + outErr.errAsLatin1()
+ + "\n----- Reason",
+ e.getCause());
+ }
+
+ return outErr.outAsLatin1();
+ }
+
+ protected void run(Path executable, OutErr outErr, String... arguments) throws Exception {
+ run(executable, null, outErr, null, arguments);
+ }
+
+ private void run(
+ Path executable,
+ Path workingDirectory,
+ OutErr outErr,
+ Map<String, String> environment,
+ String... arguments)
+ throws ExecException, InterruptedException {
+ if (workingDirectory == null) {
+ workingDirectory = directories.getWorkspace();
+ }
+ List<String> argv = Lists.newArrayList(arguments);
+ argv.add(0, executable.toString());
+ Map<String, String> env =
+ (environment != null ? environment : getTargetConfiguration().getLocalShellEnvironment());
+ TestFileOutErr testOutErr = new TestFileOutErr();
+ try {
+ execute(workingDirectory, env, argv, testOutErr, /* verboseFailures= */ false);
+ } finally {
+ testOutErr.dumpOutAsLatin1(outErr.getOutputStream());
+ testOutErr.dumpErrAsLatin1(outErr.getErrorStream());
+ }
+ }
+
+ /**
+ * Writes a number of lines of text to a source file using Latin-1 encoding.
+ *
+ * @param relativePath the path relative to the workspace root.
+ * @param lines the lines of text to write to the file.
+ * @return the path of the created file.
+ * @throws IOException if the file could not be written.
+ */
+ public Path write(String relativePath, String... lines) throws IOException {
+ Path path = getWorkspace().getRelative(relativePath);
+ return writeAbsolute(path, lines);
+ }
+
+ /**
+ * Same as {@link #write}, but with an absolute path.
+ */
+ protected Path writeAbsolute(Path path, String... lines) throws IOException {
+ FileSystemUtils.writeIsoLatin1(path, lines);
+ return path;
+ }
+
+ /** Equivalent to {@code ln -s <target> <relativeLinkPath>}. */
+ protected void createSymlink(String target, String relativeLinkPath) throws IOException {
+ getWorkspace().getRelative(relativeLinkPath).createSymbolicLink(PathFragment.create(target));
+ }
+
+ /**
+ * The TimestampGranularityMonitor operates on the files created by the
+ * request and thus does not help here. Calling this method ensures that files
+ * we modify as part of the test environment are considered as changed.
+ */
+ protected static void waitForTimestampGranularity() throws Exception {
+ // Ext4 has a nanosecond granularity. Empirically, tmpfs supports ~5ms increments on
+ // Ubuntu Trusty.
+ Thread.sleep(10 /*ms*/);
+ }
+
+ /**
+ * Fork/exec/wait the specified command. A utility method for subclasses.
+ */
+ protected String exec(String... argv) throws CommandException {
+ return new String(new Command(argv).execute().getStdout());
+ }
+
+ /**
+ * Performs a local direct spawn execution given spawn information broken out
+ * into individual arguments. Directs standard out/err to {@code outErr}.
+ *
+ * @param workingDirectory the directory from which to execute the subprocess
+ * @param environment the environment map to provide to the subprocess. If
+ * null, the environment is inherited from the parent process.
+ * @param argv the argument vector including the command itself
+ * @param outErr the out+err stream pair to receive stdout and stderr from the
+ * subprocess
+ * @throws ExecException if any kind of abnormal termination or command
+ * exception occurs
+ */
+ public static void execute(
+ Path workingDirectory,
+ Map<String, String> environment,
+ List<String> argv,
+ FileOutErr outErr,
+ boolean verboseFailures)
+ throws ExecException {
+ Command command =
+ new CommandBuilder()
+ .addArgs(argv)
+ .setEnv(environment)
+ .setWorkingDir(workingDirectory)
+ .build();
+ try {
+ command.execute(outErr.getOutputStream(), outErr.getErrorStream());
+ } catch (AbnormalTerminationException e) { // non-zero exit or signal or I/O problem
+ IntegrationTestExecException e2 =
+ new IntegrationTestExecException(CommandUtils.describeCommandFailure(verboseFailures, e));
+ e2.initCause(e); // We don't pass cause=e to the ExecException constructor
+ // since we don't want it to contribute to the exception
+ // message again; it's already in describeCommandFailure().
+ throw e2;
+ } catch (CommandException e) {
+ IntegrationTestExecException e2 =
+ new IntegrationTestExecException(CommandUtils.describeCommandFailure(verboseFailures, e));
+ e2.initCause(e); // We don't pass cause=e to the ExecException constructor
+ // since we don't want it to contribute to the exception
+ // message again; it's already in describeCommandFailure().
+ throw e2;
+ }
+ }
+
+ protected String readContentAsLatin1String(Artifact artifact) throws IOException {
+ return new String(FileSystemUtils.readContentAsLatin1(artifact.getPath()));
+ }
+
+ /**
+ * Given a collection of Artifacts, returns a corresponding set of strings of
+ * the form "<root> <relpath>", such as "bin x/libx.a". Such strings make
+ * assertions easier to write.
+ *
+ * <p>The returned set preserves the order of the input.
+ */
+ protected Set<String> artifactsToStrings(Iterable<Artifact> artifacts) {
+ return AnalysisTestUtil.artifactsToStrings(getConfigurationCollection(), artifacts);
+ }
+
+ protected ActionsTestUtil actionsTestUtil() {
+ return new ActionsTestUtil(getActionGraph());
+ }
+
+ protected Artifact getExecutable(TransitiveInfoCollection target) {
+ return target.getProvider(FilesToRunProvider.class).getExecutable();
+ }
+
+ protected NestedSet<Artifact> getFilesToBuild(TransitiveInfoCollection target) {
+ return target.getProvider(FileProvider.class).getFilesToBuild();
+ }
+
+ protected final BuildConfiguration getConfiguration(ConfiguredTarget ct) {
+ return getSkyframeExecutor()
+ .getConfiguration(NullEventHandler.INSTANCE, ct.getConfigurationKey());
+ }
+
+ /**
+ * Returns the BuildRequest of the last call to buildTarget().
+ */
+ protected BuildRequest getRequest() {
+ return runtimeWrapper.getLastRequest();
+ }
+
+ /**
+ * Returns the BuildResultof the last call to buildTarget().
+ */
+ protected BuildResult getResult() {
+ return runtimeWrapper.getLastResult();
+ }
+
+ /**
+ * Returns the {@link BlazeRuntime} in use.
+ */
+ protected BlazeRuntime getRuntime() {
+ return runtimeWrapper.getRuntime();
+ }
+
+ protected BlazeWorkspace getBlazeWorkspace() {
+ return runtimeWrapper.getRuntime().getWorkspace();
+ }
+
+ protected ConfiguredTargetAndData getConfiguredTargetAndTarget(
+ ExtendedEventHandler eventHandler, Label label, BuildConfiguration config)
+ throws TransitionException, InvalidConfigurationException {
+ return getSkyframeExecutor().getConfiguredTargetAndDataForTesting(eventHandler, label, config);
+ }
+
+ protected ActionGraph getActionGraph() {
+ return getSkyframeExecutor().getActionGraph(events.reporter());
+ }
+
+ protected CommandEnvironment getCommandEnvironment() {
+ return runtimeWrapper.getCommandEnvironment();
+ }
+
+ public SkyframeExecutor getSkyframeExecutor() {
+ return runtimeWrapper.getSkyframeExecutor();
+ }
+
+ protected PackageManager getPackageManager() {
+ return getSkyframeExecutor().getPackageManager();
+ }
+
+ protected Path getOutputBase() {
+ return outputBase;
+ }
+
+ protected Path getWorkspace() {
+ return workspace;
+ }
+
+ /**
+ * Assertion-checks that the expected error was reported,
+ */
+ protected void assertContainsError(String expectedError) {
+ for (Event error : events.errors()) {
+ if (error.getMessage().contains(expectedError)) {
+ return;
+ }
+ }
+ fail("didn't find expected error: \"" + expectedError + "\"");
+ }
+}