blob: 4f898d94887a1fa43ef2263d686c622120154f77 [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 static com.google.common.truth.TruthJUnit.assume;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
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.util.ActionsTestUtil;
import com.google.devtools.build.lib.exec.BinTools;
import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext;
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.testutil.Scratch;
import com.google.devtools.build.lib.util.OS;
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 com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.IOException;
import java.time.Duration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link LinuxSandboxedSpawnRunner}. */
@RunWith(TestParameterInjector.class)
public final class LinuxSandboxedSpawnRunnerTest extends SandboxedSpawnRunnerTestCase {
/** Tree deleter to use by default for all tests. */
private static final TreeDeleter treeDeleter = new SynchronousTreeDeleter();
@Before
public void assumeRunningOnLinux() {
assume().that(OS.getCurrent()).isEqualTo(OS.LINUX);
}
@Test
public void exec_echoCommand_executesSuccessfully() throws Exception {
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(createCommandEnvironment());
Spawn spawn = new SpawnBuilder("echo", "echolalia").build();
Path stdout = testRoot.getChild("stdout");
SpawnExecutionContext policy = createSpawnExecutionContext(spawn, stdout);
SpawnResult spawnResult = runner.exec(spawn, policy);
assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.SUCCESS);
assertThat(spawnResult.exitCode()).isEqualTo(0);
assertThat(spawnResult.setupSuccess()).isTrue();
assertThat(spawnResult.getWallTimeInMs()).isGreaterThan(0);
assertThat(FileSystemUtils.readLines(stdout, UTF_8)).containsExactly("echolalia");
}
@Test
public void exec_commandWithParamFiles_executesSuccessfully() throws Exception {
CommandEnvironment commandEnvironment = createCommandEnvironment();
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(commandEnvironment);
Spawn spawn =
new SpawnBuilder("cp", "params/param-file", "out")
.withInput(
new ParamFileActionInput(
PathFragment.create("params/param-file"),
ImmutableList.of("--foo", "--bar"),
ParameterFileType.UNQUOTED,
UTF_8))
.withOutput("out")
.build();
SpawnExecutionContext policy = createSpawnExecutionContext(spawn);
SpawnResult spawnResult = runner.exec(spawn, policy);
assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.SUCCESS);
Path paramFile = commandEnvironment.getExecRoot().getRelative("out");
assertThat(paramFile.exists()).isTrue();
assertThat(FileSystemUtils.readLines(paramFile, UTF_8))
.containsExactly("--foo", "--bar")
.inOrder();
}
@Test
public void exec_spawnRunningBinTool_executesSuccessfully() throws Exception {
CommandEnvironment commandEnvironment = createCommandEnvironment();
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(commandEnvironment);
BinTools.PathActionInput pathActionInput =
new BinTools.PathActionInput(
new Scratch().file("/execRoot/tool", "#!/bin/bash", "echo hello > $1"),
PathFragment.create("_bin/tool"));
Artifact output =
ActionsTestUtil.createArtifact(
ArtifactRoot.asDerivedRoot(
commandEnvironment.getExecRoot(), RootType.Output, "blaze-out"),
commandEnvironment.getExecRoot().getRelative("blaze-out/output"));
Spawn spawn =
new SpawnBuilder("_bin/tool", output.getExecPathString())
.withInput(pathActionInput)
.withOutput(output)
.build();
SpawnExecutionContext policy = createSpawnExecutionContext(spawn);
SpawnResult spawnResult = runner.exec(spawn, policy);
assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.SUCCESS);
assertThat(FileSystemUtils.readLines(output.getPath(), UTF_8)).containsExactly("hello");
}
@Test
public void exec_collectsExecutionStatistics() throws Exception {
CommandEnvironment commandEnvironment = createCommandEnvironment();
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(commandEnvironment);
Path cpuTimeSpenderPath =
SpawnRunnerTestUtil.copyCpuTimeSpenderIntoPath(commandEnvironment.getExecRoot());
Duration minimumWallTimeToSpend = Duration.ofSeconds(10);
// Because of e.g. interference, wall time taken may be much larger than CPU time used.
Duration maximumWallTimeToSpend = Duration.ofSeconds(40);
Duration minimumUserTimeToSpend = minimumWallTimeToSpend;
Duration maximumUserTimeToSpend = minimumUserTimeToSpend.plus(Duration.ofSeconds(2));
Duration minimumSystemTimeToSpend = Duration.ZERO;
Duration maximumSystemTimeToSpend = minimumSystemTimeToSpend.plus(Duration.ofSeconds(2));
Spawn spawn =
new SpawnBuilder(
cpuTimeSpenderPath.getPathString(),
String.valueOf(minimumUserTimeToSpend.getSeconds()),
String.valueOf(minimumSystemTimeToSpend.getSeconds()))
.build();
SpawnExecutionContextForTesting policy = createSpawnExecutionContext(spawn);
SpawnResult spawnResult = runner.exec(spawn, policy);
assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.SUCCESS);
assertThat(spawnResult.exitCode()).isEqualTo(0);
assertThat(spawnResult.setupSuccess()).isTrue();
assertThat(spawnResult.getWallTimeInMs()).isAtLeast((int) minimumWallTimeToSpend.toMillis());
assertThat(spawnResult.getWallTimeInMs()).isAtMost((int) maximumWallTimeToSpend.toMillis());
assertThat(spawnResult.getUserTimeInMs()).isAtLeast((int) minimumUserTimeToSpend.toMillis());
assertThat(spawnResult.getUserTimeInMs()).isAtMost((int) maximumUserTimeToSpend.toMillis());
assertThat(spawnResult.getSystemTimeInMs())
.isAtLeast((int) minimumSystemTimeToSpend.toMillis());
assertThat(spawnResult.getSystemTimeInMs()).isAtMost((int) maximumSystemTimeToSpend.toMillis());
assertThat(spawnResult.getNumBlockOutputOperations()).isAtLeast(0L);
assertThat(spawnResult.getNumBlockInputOperations()).isAtLeast(0L);
assertThat(spawnResult.getNumInvoluntaryContextSwitches()).isAtLeast(0L);
}
@Test
public void hermeticTmp_tmpCreatedAndMounted() throws Exception {
runtimeWrapper.addOptions("--incompatible_sandbox_hermetic_tmp");
CommandEnvironment commandEnvironment = createCommandEnvironment();
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(commandEnvironment);
Spawn spawn = new SpawnBuilder().build();
SandboxedSpawn sandboxedSpawn = runner.prepareSpawn(spawn, createSpawnExecutionContext(spawn));
Path sandboxPath =
sandboxedSpawn.getSandboxExecRoot().getParentDirectory().getParentDirectory();
Path hermeticTmpPath = sandboxPath.getRelative("_hermetic_tmp");
assertThat(hermeticTmpPath.isDirectory()).isTrue();
assertThat(sandboxedSpawn).isInstanceOf(SymlinkedSandboxedSpawn.class);
String args = String.join(" ", sandboxedSpawn.getArguments());
assertThat(args).contains("-w /tmp");
assertThat(args).contains("-M " + hermeticTmpPath + " -m /tmp");
}
@Test
public void hermeticTmp_sandboxTmpfsOnTmp_tmpNotCreatedOrMounted() throws Exception {
runtimeWrapper.addOptions("--incompatible_sandbox_hermetic_tmp", "--sandbox_tmpfs_path=/tmp");
CommandEnvironment commandEnvironment = createCommandEnvironment();
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(commandEnvironment);
Spawn spawn = new SpawnBuilder().build();
SandboxedSpawn sandboxedSpawn = runner.prepareSpawn(spawn, createSpawnExecutionContext(spawn));
Path sandboxPath =
sandboxedSpawn.getSandboxExecRoot().getParentDirectory().getParentDirectory();
Path hermeticTmpPath = sandboxPath.getRelative("_hermetic_tmp");
assertThat(hermeticTmpPath.isDirectory()).isFalse();
assertThat(sandboxedSpawn).isInstanceOf(SymlinkedSandboxedSpawn.class);
String args = String.join(" ", sandboxedSpawn.getArguments());
assertThat(args).contains("-w /tmp");
assertThat(args).contains("-e /tmp");
assertThat(args).doesNotContain("-m /tmp");
}
@Test
public void hermeticTmp_sandboxTmpfsUnderTmp_tmpNotCreatedOrMounted() throws Exception {
runtimeWrapper.addOptions(
"--incompatible_sandbox_hermetic_tmp", "--sandbox_tmpfs_path=/tmp/subdir");
CommandEnvironment commandEnvironment = createCommandEnvironment();
LinuxSandboxedSpawnRunner runner = setupSandboxAndCreateRunner(commandEnvironment);
Spawn spawn = new SpawnBuilder().build();
SandboxedSpawn sandboxedSpawn = runner.prepareSpawn(spawn, createSpawnExecutionContext(spawn));
Path sandboxPath =
sandboxedSpawn.getSandboxExecRoot().getParentDirectory().getParentDirectory();
Path hermeticTmpPath = sandboxPath.getRelative("_hermetic_tmp");
assertThat(hermeticTmpPath.isDirectory()).isFalse();
assertThat(sandboxedSpawn).isInstanceOf(SymlinkedSandboxedSpawn.class);
String args = String.join(" ", sandboxedSpawn.getArguments());
assertThat(args).contains("-w /tmp");
assertThat(args).contains("-e /tmp");
assertThat(args).doesNotContain("-m /tmp");
}
private static LinuxSandboxedSpawnRunner setupSandboxAndCreateRunner(
CommandEnvironment commandEnvironment) throws IOException {
Path execRoot = commandEnvironment.getExecRoot();
execRoot.createDirectory();
SpawnRunnerTestUtil.copyLinuxSandboxIntoPath(execRoot);
Path sandboxBase = execRoot.getRelative("sandbox");
sandboxBase.createDirectory();
return LinuxSandboxedStrategy.create(
new SandboxHelpers(),
commandEnvironment,
sandboxBase,
/*timeoutKillDelay=*/ Duration.ofSeconds(2),
/*sandboxfsProcess=*/ null,
/*sandboxfsMapSymlinkTargets=*/ false,
treeDeleter);
}
private SpawnExecutionContextForTesting createSpawnExecutionContext(Spawn spawn) {
return createSpawnExecutionContext(spawn, testRoot.getChild("stdout"));
}
private SpawnExecutionContextForTesting createSpawnExecutionContext(Spawn spawn, Path stdout) {
FileOutErr fileOutErr = new FileOutErr(stdout, testRoot.getChild("stderr"));
return new SpawnExecutionContextForTesting(spawn, fileOutErr, Duration.ofMinutes(1));
}
private CommandEnvironment createCommandEnvironment() throws Exception {
CommandEnvironment commandEnvironment = runtimeWrapper.newCommand();
commandEnvironment.setWorkspaceName("workspace");
commandEnvironment
.getLocalResourceManager()
.setAvailableResources(LocalHostCapacity.getLocalHostCapacity());
return commandEnvironment;
}
}