blob: 0e88c4f384a8aee5416aa78a9ef5b3130cdfca5d [file] [log] [blame]
// Copyright 2022 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.sandbox;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
import com.google.devtools.build.lib.actions.LocalHostCapacity;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.SpawnResult.Status;
import com.google.devtools.build.lib.exec.TreeDeleter;
import com.google.devtools.build.lib.exec.util.SpawnBuilder;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.sandbox.SpawnRunnerTestUtil.SpawnExecutionContextForTesting;
import com.google.devtools.build.lib.util.io.FileOutErr;
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 java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link DarwinSandboxedSpawnRunner}.
*
* <p>These tests do not require macOS to run because we have no easy means of expressing that
* requirement. Instead, we just implement "unit-like" tests by mocking out the tools that this
* spawn runner requires and rely on our shell-level integration tests to validate this properly.
*/
@RunWith(JUnit4.class)
public final class DarwinSandboxedSpawnRunnerTest extends SandboxedSpawnRunnerTestCase {
/** Tree deleter to use by default for all tests. */
private static final TreeDeleter treeDeleter = new SynchronousTreeDeleter();
/** Environment for the running test. */
private CommandEnvironment commandEnvironment;
/** Path to the base of the sandbox to pass to the spawn runner. */
private Path sandboxBase;
/** Location of the real {@code getconf} binary; saved while the test is running. */
private String oldGetconf;
/** Location of the real {@code sandbox-exec} binary; saved while the test is running. */
private String oldSandboxExec;
@Before
public void setUp() throws Exception {
commandEnvironment = runtimeWrapper.newCommand();
commandEnvironment.setWorkspaceName("workspace");
commandEnvironment
.getLocalResourceManager()
.setAvailableResources(LocalHostCapacity.getLocalHostCapacity());
Path execRoot = commandEnvironment.getExecRoot();
execRoot.createDirectory();
SpawnRunnerTestUtil.copyProcessWrapperIntoPath(execRoot);
sandboxBase = execRoot.getRelative("sandbox");
sandboxBase.createDirectory();
// The mock getconf tool always prints an arbitrary path regardless of the arguments it
// receives. We must print a syntactically-valid path, however, to not confuse the consumer
// of this output.
Path getconf = execRoot.getRelative("getconf");
FileSystemUtils.writeContentAsLatin1(getconf, "#!/bin/sh\necho /tmp");
getconf.setExecutable(true);
oldGetconf = DarwinSandboxedSpawnRunner.getconfBinary;
DarwinSandboxedSpawnRunner.getconfBinary = getconf.toString();
// The mock sandbox-exec just executes the given command and returns its output.
Path sandboxExec = execRoot.getRelative("sandbox-exec");
FileSystemUtils.writeContentAsLatin1(sandboxExec,
"#!/bin/sh\n"
+ "shift\n" // Skip -f flag.
+ "shift\n" // Skip target of -f flag.
+ "exec \"$@\"\n"); // Remaining arguments are the process-wrapper's ones.
sandboxExec.setExecutable(true);
oldSandboxExec = DarwinSandboxedSpawnRunner.sandboxExecBinary;
DarwinSandboxedSpawnRunner.sandboxExecBinary = sandboxExec.toString();
}
@After
public void tearDown() {
DarwinSandboxedSpawnRunner.sandboxExecBinary = oldSandboxExec;
DarwinSandboxedSpawnRunner.getconfBinary = oldGetconf;
}
private void doSimpleExecutionTest(DarwinSandboxedSpawnRunner runner) throws Exception {
Spawn spawn = new SpawnBuilder("/bin/sh", "-c", "exit 42").build();
FileOutErr fileOutErr =
new FileOutErr(testRoot.getChild("stdout"), testRoot.getChild("stderr"));
SpawnExecutionContextForTesting policy =
new SpawnExecutionContextForTesting(spawn, fileOutErr, Duration.ofMinutes(1));
SpawnResult spawnResult = runner.execAsync(spawn, policy).get();
assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.NON_ZERO_EXIT);
assertThat(spawnResult.exitCode()).isEqualTo(42);
}
@Test
public void testSimpleExecution() throws Exception {
DarwinSandboxedSpawnRunner runner =
new DarwinSandboxedSpawnRunner(
new SandboxHelpers(/*delayVirtualInputMaterialization=*/ true),
commandEnvironment,
sandboxBase,
/* sandboxfsProcess= */ null,
/* sandboxfsMapSymlinkTargets= */ false,
treeDeleter);
doSimpleExecutionTest(runner);
}
@Test
public void testSupportsParamFiles() throws Exception {
DarwinSandboxedSpawnRunner runner =
new DarwinSandboxedSpawnRunner(
new SandboxHelpers(/*delayVirtualInputMaterialization=*/ true),
commandEnvironment,
sandboxBase,
/* sandboxfsProcess= */ null,
/* sandboxfsMapSymlinkTargets= */ false,
treeDeleter);
Spawn spawn =
new SpawnBuilder("cp", "params/param-file", "out")
.withInput(
new ParamFileActionInput(
PathFragment.create("params/param-file"),
ImmutableList.of("--foo", "--bar"),
ParameterFileType.UNQUOTED,
StandardCharsets.UTF_8))
.withOutput("out")
.build();
FileOutErr fileOutErr =
new FileOutErr(testRoot.getChild("stdout"), testRoot.getChild("stderr"));
SpawnExecutionContextForTesting policy =
new SpawnExecutionContextForTesting(spawn, fileOutErr, Duration.ofMinutes(1));
SpawnResult spawnResult = runner.execAsync(spawn, policy).get();
assertThat(spawnResult.status()).isEqualTo(Status.SUCCESS);
Path paramFile = commandEnvironment.getExecRoot().getRelative("out");
assertThat(paramFile.exists()).isTrue();
try (InputStream inputStream = paramFile.getInputStream()) {
assertThat(
new String(ByteStreams.toByteArray(inputStream), StandardCharsets.UTF_8).split("\n"))
.asList()
.containsExactly("--foo", "--bar");
}
}
@Test
public void testSimpleExecutionWithSandboxfs() throws Exception {
Path mountPoint = commandEnvironment.getExecRoot().getRelative("mount");
mountPoint.createDirectoryAndParents();
SandboxfsProcess sandboxfsProcess = new FakeSandboxfsProcess(
mountPoint.getFileSystem(), mountPoint.asFragment());
DarwinSandboxedSpawnRunner runner =
new DarwinSandboxedSpawnRunner(
new SandboxHelpers(/*delayVirtualInputMaterialization=*/ true),
commandEnvironment,
sandboxBase,
sandboxfsProcess,
/* sandboxfsMapSymlinkTargets= */ false,
treeDeleter);
doSimpleExecutionTest(runner);
}
}