aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test
diff options
context:
space:
mode:
authorGravatar ulfjack <ulfjack@google.com>2017-04-07 11:21:38 +0000
committerGravatar Marcel Hlopko <hlopko@google.com>2017-04-07 16:44:43 +0200
commit3a081f7030616b7ccfbf56cb3955c9533d758f40 (patch)
tree6e4930d85adf5ad4407299094ae2ab98cb020616 /src/test
parent1fde2308afc7b323ca3c57b7176c0ea18ad52bbb (diff)
Open source LocalSpawnRunner
The LocalSpawnRunner is a non-sandboxed local execution implementation, which will replace the current StandaloneSpawnStrategy. The code has been around for a long time and has seen a lot of bugfixes. It also supports local prefetching, which is required for Google. I have a follow-up change to make it support Windows, so it's not a drop-in replacement for StandaloneSpawnStrategy yet. PiperOrigin-RevId: 152486973
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/com/google/devtools/build/lib/BUILD1
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java324
-rw-r--r--src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java (renamed from src/test/java/com/google/devtools/build/lib/remote/FakeOwner.java)38
-rw-r--r--src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java118
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/CachedLocalSpawnRunnerTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java1
7 files changed, 473 insertions, 12 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index a6ce260446..c2b5b8f2b5 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -1057,6 +1057,7 @@ java_test(
srcs = glob(["remote/*.java"]),
test_class = "com.google.devtools.build.lib.AllTests",
deps = [
+ ":analysis_testutil",
":foundations_testutil",
":test_runner",
":testutil",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java
index c2563f3920..c318413c05 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionTestCase.java
@@ -42,7 +42,7 @@ public abstract class FileWriteActionTestCase extends BuildViewTestCase {
private Artifact outputArtifact;
private Path output;
private Executor executor;
- private ActionExecutionContext context;
+ protected ActionExecutionContext context;
@Before
public final void createAction() throws Exception {
diff --git a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
new file mode 100644
index 0000000000..8fba192e15
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
@@ -0,0 +1,324 @@
+// Copyright 2017 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.exec.local;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionInputFileCache;
+import com.google.devtools.build.lib.actions.ResourceManager;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.exec.ActionInputPrefetcher;
+import com.google.devtools.build.lib.exec.SpawnResult;
+import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
+import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
+import com.google.devtools.build.lib.exec.util.SpawnBuilder;
+import com.google.devtools.build.lib.shell.JavaSubprocessFactory;
+import com.google.devtools.build.lib.shell.Subprocess;
+import com.google.devtools.build.lib.shell.SubprocessBuilder;
+import com.google.devtools.build.lib.util.NetUtil;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.common.options.Options;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.SortedMap;
+import java.util.regex.Pattern;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for {@link LocalSpawnRunner}.
+ */
+@RunWith(JUnit4.class)
+public class LocalSpawnRunnerTest {
+ private static class FinishedSubprocess implements Subprocess {
+ private final int exitCode;
+
+ public FinishedSubprocess(int exitCode) {
+ this.exitCode = exitCode;
+ }
+
+ @Override
+ public boolean destroy() {
+ return false;
+ }
+
+ @Override
+ public int exitValue() {
+ return exitCode;
+ }
+
+ @Override
+ public boolean finished() {
+ return true;
+ }
+
+ @Override
+ public boolean timedout() {
+ return false;
+ }
+
+ @Override
+ public void waitFor() throws InterruptedException {
+ // Do nothing.
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return ByteStreams.nullOutputStream();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ }
+
+ private static final Spawn SIMPLE_SPAWN =
+ new SpawnBuilder("/bin/echo", "Hi!").withEnvironment("VARIABLE", "value").build();
+
+ private static final class SubprocessInterceptor implements Subprocess.Factory {
+ @Override
+ public Subprocess create(SubprocessBuilder params) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private FileSystem fs;
+ private final ActionInputFileCache mockFileCache = mock(ActionInputFileCache.class);
+ private final ResourceManager resourceManager = ResourceManager.instanceForTestingOnly();
+
+ private FileOutErr outErr;
+ private long timeoutMillis = 0;
+ private boolean calledLockOutputFiles;
+
+ private final SpawnExecutionPolicy policy = new SpawnExecutionPolicy() {
+ @Override
+ public boolean shouldPrefetchInputsForLocalExecution(Spawn spawn) {
+ // TODO(ulfjack): Test local prefetching.
+ return false;
+ }
+
+ @Override
+ public void lockOutputFiles() throws InterruptedException {
+ calledLockOutputFiles = true;
+ }
+
+ @Override
+ public ActionInputFileCache getActionInputFileCache() {
+ return mockFileCache;
+ }
+
+ @Override
+ public long getTimeoutMillis() {
+ return timeoutMillis;
+ }
+
+ @Override
+ public FileOutErr getFileOutErr() {
+ return outErr;
+ }
+
+ @Override
+ public SortedMap<PathFragment, ActionInput> getInputMapping() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void report(ProgressStatus state) {
+ // TODO(ulfjack): Test that the right calls are made.
+ }
+ };
+
+ @Before
+ public final void setup() throws Exception {
+ fs = new InMemoryFileSystem();
+ // Prevent any subprocess execution at all.
+ SubprocessBuilder.setSubprocessFactory(new SubprocessInterceptor());
+ resourceManager.setAvailableResources(
+ ResourceSet.create(/*memoryMb=*/1, /*cpuUsage=*/1, /*ioUsage=*/1, /*localTestCount=*/1));
+ }
+
+ @After
+ public final void tearDown() {
+ SubprocessBuilder.setSubprocessFactory(JavaSubprocessFactory.INSTANCE);
+ }
+
+ @Test
+ public void vanillaZeroExit() throws Exception {
+ Subprocess.Factory factory = mock(Subprocess.Factory.class);
+ ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
+ when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(0));
+ SubprocessBuilder.setSubprocessFactory(factory);
+
+ LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
+ options.localSigkillGraceSeconds = 456;
+ LocalSpawnRunner runner = new LocalSpawnRunner(
+ fs.getPath("/execroot"), ActionInputPrefetcher.NONE, options, resourceManager);
+
+ timeoutMillis = 123 * 1000L;
+ outErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
+ SpawnResult result = runner.exec(SIMPLE_SPAWN, policy);
+ verify(factory).create(any(SubprocessBuilder.class));
+ assertThat(result.status()).isEqualTo(SpawnResult.Status.SUCCESS);
+ assertThat(result.exitCode()).isEqualTo(0);
+ assertThat(result.setupSuccess()).isTrue();
+ assertThat(result.getExecutorHostName()).isEqualTo(NetUtil.findShortHostName());
+
+ assertThat(captor.getValue().getArgv())
+ .isEqualTo(ImmutableList.of(
+ // process-wrapper timeout grace_time stdout stderr
+ "/execroot/_bin/process-wrapper", "123.0", "456.0", "/out/stdout", "/out/stderr",
+ "/bin/echo", "Hi!"));
+ assertThat(captor.getValue().getEnv()).containsExactly("VARIABLE", "value");
+
+ assertThat(calledLockOutputFiles).isTrue();
+ }
+
+ @Test
+ public void nonZeroExit() throws Exception {
+ Subprocess.Factory factory = mock(Subprocess.Factory.class);
+ ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
+ when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(3));
+ SubprocessBuilder.setSubprocessFactory(factory);
+
+ LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
+ LocalSpawnRunner runner = new LocalSpawnRunner(
+ fs.getPath("/execroot"), ActionInputPrefetcher.NONE, options, resourceManager);
+
+ outErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
+ SpawnResult result = runner.exec(SIMPLE_SPAWN, policy);
+ verify(factory).create(any(SubprocessBuilder.class));
+ assertThat(result.status()).isEqualTo(SpawnResult.Status.SUCCESS);
+ assertThat(result.exitCode()).isEqualTo(3);
+ assertThat(result.setupSuccess()).isTrue();
+ assertThat(result.getExecutorHostName()).isEqualTo(NetUtil.findShortHostName());
+
+ assertThat(captor.getValue().getArgv())
+ .isEqualTo(ImmutableList.of(
+ // process-wrapper timeout grace_time stdout stderr
+ "/execroot/_bin/process-wrapper", "0.0", "15.0", "/out/stdout", "/out/stderr",
+ "/bin/echo", "Hi!"));
+ assertThat(captor.getValue().getEnv()).containsExactly("VARIABLE", "value");
+
+ assertThat(calledLockOutputFiles).isTrue();
+ }
+
+ @Test
+ public void processStartupThrows() throws Exception {
+ Subprocess.Factory factory = mock(Subprocess.Factory.class);
+ ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
+ when(factory.create(captor.capture())).thenThrow(new IOException("I'm sorry, Dave"));
+ SubprocessBuilder.setSubprocessFactory(factory);
+
+ LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
+ LocalSpawnRunner runner = new LocalSpawnRunner(
+ fs.getPath("/execroot"), ActionInputPrefetcher.NONE, options, resourceManager);
+
+ outErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
+ SpawnResult result = runner.exec(SIMPLE_SPAWN, policy);
+ verify(factory).create(any(SubprocessBuilder.class));
+ assertThat(result.status()).isEqualTo(SpawnResult.Status.EXECUTION_FAILED);
+ assertThat(result.exitCode()).isEqualTo(-1);
+ assertThat(result.setupSuccess()).isFalse();
+ assertThat(result.getWallTimeMillis()).isEqualTo(0);
+ assertThat(result.getExecutorHostName()).isEqualTo(NetUtil.findShortHostName());
+
+ assertThat(calledLockOutputFiles).isTrue();
+ }
+
+ @Test
+ public void disallowLocalExecution() throws Exception {
+ LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
+ options.allowedLocalAction = Pattern.compile("none");
+ LocalSpawnRunner runner = new LocalSpawnRunner(
+ fs.getPath("/execroot"), ActionInputPrefetcher.NONE, options, resourceManager);
+
+ outErr = new FileOutErr();
+ SpawnResult reply = runner.exec(SIMPLE_SPAWN, policy);
+ assertThat(reply.status()).isEqualTo(SpawnResult.Status.LOCAL_ACTION_NOT_ALLOWED);
+ assertThat(reply.exitCode()).isEqualTo(-1);
+ assertThat(reply.setupSuccess()).isFalse();
+ assertThat(reply.getWallTimeMillis()).isEqualTo(0);
+ assertThat(reply.getExecutorHostName()).isEqualTo(NetUtil.findShortHostName());
+
+ // TODO(ulfjack): Maybe we should only lock after checking?
+ assertThat(calledLockOutputFiles).isTrue();
+ }
+
+ @Test
+ public void interruptedException() throws Exception {
+ Subprocess.Factory factory = mock(Subprocess.Factory.class);
+ ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
+ when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(3) {
+ private boolean destroyed;
+
+ @Override
+ public boolean destroy() {
+ destroyed = true;
+ return true;
+ }
+
+ @Override
+ public void waitFor() throws InterruptedException {
+ if (!destroyed) {
+ throw new InterruptedException();
+ }
+ }
+ });
+ SubprocessBuilder.setSubprocessFactory(factory);
+
+ LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
+ LocalSpawnRunner runner = new LocalSpawnRunner(
+ fs.getPath("/execroot"), ActionInputPrefetcher.NONE, options, resourceManager);
+
+ outErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
+ try {
+ runner.exec(SIMPLE_SPAWN, policy);
+ fail();
+ } catch (InterruptedException expected) {
+ // Clear the interrupted status or subsequent tests in the same process will fail.
+ Thread.interrupted();
+ }
+ assertThat(calledLockOutputFiles).isTrue();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/remote/FakeOwner.java b/src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java
index f1654b4727..20ec2b5af9 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/FakeOwner.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java
@@ -1,4 +1,4 @@
-// Copyright 2017 The Bazel Authors. All rights reserved.
+// Copyright 2017 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.
@@ -11,9 +11,7 @@
// 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.remote;
-
-import static org.mockito.Mockito.mock;
+package com.google.devtools.build.lib.exec.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -23,20 +21,38 @@ import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.RunfilesSupplier;
-
-/** A fake implementation of ActionExecutionMetadata as needed for SpawnRunner test. */
-final class FakeOwner implements ActionExecutionMetadata {
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.AspectDescriptor;
+import javax.annotation.Nullable;
+
+/**
+ * Fake implementation of {@link ActionExecutionMetadata} for testing.
+ */
+public final class FakeOwner implements ActionExecutionMetadata {
private final String mnemonic;
private final String progressMessage;
+ @Nullable private final String ownerLabel;
- FakeOwner(String mnemonic, String progressMessage) {
+ public FakeOwner(String mnemonic, String progressMessage, @Nullable String ownerLabel) {
this.mnemonic = mnemonic;
this.progressMessage = progressMessage;
+ this.ownerLabel = ownerLabel;
+ }
+
+ public FakeOwner(String mnemonic, String progressMessage) {
+ this(mnemonic, progressMessage, null);
}
@Override
public ActionOwner getOwner() {
- return mock(ActionOwner.class);
+ return ActionOwner.create(
+ ownerLabel == null ? null : Label.parseAbsoluteUnchecked(ownerLabel),
+ /*aspectDescriptors=*/ ImmutableList.<AspectDescriptor>of(),
+ /*location=*/ null,
+ mnemonic,
+ /*targetKind=*/ null,
+ "configurationChecksum",
+ "additionalProgressInfo");
}
@Override
@@ -101,7 +117,7 @@ final class FakeOwner implements ActionExecutionMetadata {
@Override
public String getKey() {
- throw new UnsupportedOperationException();
+ return "MockOwner.getKey";
}
@Override
@@ -117,7 +133,7 @@ final class FakeOwner implements ActionExecutionMetadata {
@Override
public Iterable<Artifact> getInputFilesForExtraAction(
ActionExecutionContext actionExecutionContext) {
- return ImmutableList.<Artifact>of();
+ return ImmutableList.of();
}
@Override
diff --git a/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java b/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java
new file mode 100644
index 0000000000..b98af7ea2a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java
@@ -0,0 +1,118 @@
+// Copyright 2017 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.exec.util;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionInputHelper;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.EmptyRunfilesSupplier;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.SimpleSpawn;
+import com.google.devtools.build.lib.actions.Spawn;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Builder class to create {@link Spawn} instances for testing.
+ */
+public final class SpawnBuilder {
+ private String mnemonic = "Mnemonic";
+ private String progressMessage = "progress message";
+ @Nullable private String ownerLabel;
+ private final List<String> args;
+ private final Map<String, String> environment = new HashMap<>();
+ private final Map<String, String> executionInfo = new HashMap<>();
+ private final List<ActionInput> inputs = new ArrayList<>();
+ private final List<ActionInput> outputs = new ArrayList<>();
+
+ public SpawnBuilder(String... args) {
+ this.args = ImmutableList.copyOf(args);
+ }
+
+ public Spawn build() {
+ ActionExecutionMetadata owner = new FakeOwner(mnemonic, progressMessage, ownerLabel);
+ return new SimpleSpawn(
+ owner,
+ ImmutableList.copyOf(args),
+ ImmutableMap.copyOf(environment),
+ ImmutableMap.copyOf(executionInfo),
+ /*runfilesSupplier=*/EmptyRunfilesSupplier.INSTANCE,
+ ImmutableList.copyOf(inputs),
+ /*tools=*/ImmutableList.<Artifact>of(),
+ /*filesetManifests=*/ImmutableList.<Artifact>of(),
+ ImmutableList.copyOf(outputs),
+ ResourceSet.ZERO);
+ }
+
+ public SpawnBuilder withMnemonic(String mnemonic) {
+ this.mnemonic = Preconditions.checkNotNull(mnemonic);
+ return this;
+ }
+
+ public SpawnBuilder withProgressMessage(String progressMessage) {
+ this.progressMessage = progressMessage;
+ return this;
+ }
+
+ public SpawnBuilder withOwnerLabel(String ownerLabel) {
+ this.ownerLabel = ownerLabel;
+ return this;
+ }
+
+ public SpawnBuilder withEnvironment(String key, String value) {
+ this.environment.put(key, value);
+ return this;
+ }
+
+ public SpawnBuilder withExecutionInfo(String key, String value) {
+ this.executionInfo.put(key, value);
+ return this;
+ }
+
+ public SpawnBuilder withInput(ActionInput input) {
+ this.inputs.add(input);
+ return this;
+ }
+
+ public SpawnBuilder withInput(String name) {
+ this.inputs.add(ActionInputHelper.fromPath(name));
+ return this;
+ }
+
+ public SpawnBuilder withInputs(String... names) {
+ for (String name : names) {
+ this.inputs.add(ActionInputHelper.fromPath(name));
+ }
+ return this;
+ }
+
+ public SpawnBuilder withOutput(String name) {
+ this.outputs.add(ActionInputHelper.fromPath(name));
+ return this;
+ }
+
+ public SpawnBuilder withOutputs(String... names) {
+ for (String name : names) {
+ this.outputs.add(ActionInputHelper.fromPath(name));
+ }
+ return this;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/remote/CachedLocalSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/CachedLocalSpawnRunnerTest.java
index d27e7c06b3..1456239e55 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/CachedLocalSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/CachedLocalSpawnRunnerTest.java
@@ -34,6 +34,7 @@ import com.google.devtools.build.lib.exec.SpawnResult.Status;
import com.google.devtools.build.lib.exec.SpawnRunner;
import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
+import com.google.devtools.build.lib.exec.util.FakeOwner;
import com.google.devtools.build.lib.remote.ContentDigests.ActionKey;
import com.google.devtools.build.lib.remote.RemoteProtocol.ActionResult;
import com.google.devtools.build.lib.remote.RemoteProtocol.ContentDigest;
diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
index 5957ef385f..54575cb311 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java
@@ -32,6 +32,7 @@ import com.google.devtools.build.lib.exec.SpawnInputExpander;
import com.google.devtools.build.lib.exec.SpawnResult;
import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
+import com.google.devtools.build.lib.exec.util.FakeOwner;
import com.google.devtools.build.lib.remote.RemoteProtocol.ActionResult;
import com.google.devtools.build.lib.remote.RemoteProtocol.ContentDigest;
import com.google.devtools.build.lib.remote.RemoteProtocol.ExecuteReply;