| // 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"); | |
| } | |
| 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), | |
| 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; | |
| } | |
| } |