diff options
11 files changed, 432 insertions, 130 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java index d1a1783e60..3baa7adbf7 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java @@ -16,7 +16,6 @@ package com.google.devtools.build.lib.sandbox; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; -import com.google.common.base.Throwables; import com.google.common.collect.ForwardingMap; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; @@ -33,14 +32,8 @@ import com.google.devtools.build.lib.actions.SpawnActionContext; import com.google.devtools.build.lib.actions.UserExecException; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.config.RunUnder; -import com.google.devtools.build.lib.events.Event; -import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.rules.cpp.CppCompileAction; import com.google.devtools.build.lib.rules.test.TestRunnerAction; -import com.google.devtools.build.lib.runtime.BlazeRuntime; -import com.google.devtools.build.lib.shell.AbnormalTerminationException; -import com.google.devtools.build.lib.shell.CommandException; -import com.google.devtools.build.lib.shell.TerminationStatus; import com.google.devtools.build.lib.standalone.StandaloneSpawnStrategy; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.unix.FilesystemUtils; @@ -70,10 +63,11 @@ import java.util.concurrent.atomic.AtomicInteger; public class LinuxSandboxedStrategy implements SpawnActionContext { private final ExecutorService backgroundWorkers; - private final BlazeRuntime blazeRuntime; + private final ImmutableMap<String, String> clientEnv; private final BlazeDirectories blazeDirs; private final Path execRoot; private final boolean verboseFailures; + private final boolean sandboxDebug; private final StandaloneSpawnStrategy standaloneStrategy; private final UUID uuid = UUID.randomUUID(); private final AtomicInteger execCounter = new AtomicInteger(); @@ -112,12 +106,17 @@ public class LinuxSandboxedStrategy implements SpawnActionContext { } public LinuxSandboxedStrategy( - BlazeRuntime blazeRuntime, boolean verboseFailures, ExecutorService backgroundWorkers) { - this.blazeRuntime = blazeRuntime; - this.blazeDirs = blazeRuntime.getDirectories(); + Map<String, String> clientEnv, + BlazeDirectories blazeDirs, + ExecutorService backgroundWorkers, + boolean verboseFailures, + boolean sandboxDebug) { + this.clientEnv = ImmutableMap.copyOf(clientEnv); + this.blazeDirs = blazeDirs; this.execRoot = blazeDirs.getExecRoot(); - this.verboseFailures = verboseFailures; this.backgroundWorkers = backgroundWorkers; + this.verboseFailures = verboseFailures; + this.sandboxDebug = sandboxDebug; this.standaloneStrategy = new StandaloneSpawnStrategy(blazeDirs.getExecRoot(), verboseFailures); } @@ -163,7 +162,7 @@ public class LinuxSandboxedStrategy implements SpawnActionContext { try { final NamespaceSandboxRunner runner = - new NamespaceSandboxRunner(execRoot, sandboxPath, mounts, verboseFailures); + new NamespaceSandboxRunner(execRoot, sandboxPath, mounts, verboseFailures, sandboxDebug); try { runner.run( spawn.getArguments(), @@ -197,18 +196,8 @@ public class LinuxSandboxedStrategy implements SpawnActionContext { } }); } - } catch (AbnormalTerminationException e) { - TerminationStatus status = e.getResult().getTerminationStatus(); - boolean timedOut = !status.exited() && (status.getTerminatingSignal() == 14 /* SIGALRM */); - throw new UserExecException("Error during execution of spawn", e, timedOut); - } catch (CommandException e) { - throw new UserExecException("Error during execution of spawn", e); } catch (IOException e) { - EventHandler handler = actionExecutionContext.getExecutor().getEventHandler(); - handler.handle( - Event.error( - "I/O error during sandboxed execution:\n" + Throwables.getStackTraceAsString(e))); - throw new UserExecException("Could not execute spawn", e); + throw new UserExecException("I/O error during sandboxed execution", e); } } @@ -477,7 +466,7 @@ public class LinuxSandboxedStrategy implements SpawnActionContext { source = blazeDirs.getExecRoot().getRelative(sourceFragment); } else { List<Path> searchPath = - SearchPath.parse(blazeDirs.getFileSystem(), blazeRuntime.getClientEnv().get("PATH")); + SearchPath.parse(blazeDirs.getFileSystem(), clientEnv.get("PATH")); source = SearchPath.which(searchPath, runUnder.getCommand()); } if (source != null) { diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java index 1667a03c54..1c2034a12d 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java @@ -18,11 +18,15 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import com.google.devtools.build.lib.actions.ActionInput; +import com.google.devtools.build.lib.actions.UserExecException; import com.google.devtools.build.lib.analysis.config.BinTools; import com.google.devtools.build.lib.runtime.BlazeRuntime; +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.shell.TerminationStatus; import com.google.devtools.build.lib.unix.FilesystemUtils; +import com.google.devtools.build.lib.util.CommandFailureUtils; import com.google.devtools.build.lib.util.OsUtils; import com.google.devtools.build.lib.util.io.FileOutErr; import com.google.devtools.build.lib.vfs.FileSystemUtils; @@ -46,15 +50,21 @@ public class NamespaceSandboxRunner { private final Path sandboxPath; private final Path sandboxExecRoot; private final ImmutableMap<Path, Path> mounts; - private final boolean debug; + private final boolean verboseFailures; + private final boolean sandboxDebug; public NamespaceSandboxRunner( - Path execRoot, Path sandboxPath, ImmutableMap<Path, Path> mounts, boolean debug) { + Path execRoot, + Path sandboxPath, + ImmutableMap<Path, Path> mounts, + boolean verboseFailures, + boolean sandboxDebug) { this.execRoot = execRoot; this.sandboxPath = sandboxPath; this.sandboxExecRoot = sandboxPath.getRelative(execRoot.asFragment().relativeTo("/")); this.mounts = mounts; - this.debug = debug; + this.verboseFailures = verboseFailures; + this.sandboxDebug = sandboxDebug; } static boolean isSupported(BlazeRuntime runtime) { @@ -99,14 +109,14 @@ public class NamespaceSandboxRunner { FileOutErr outErr, Collection<? extends ActionInput> outputs, int timeout) - throws IOException, CommandException { + throws IOException, UserExecException { createFileSystem(outputs); List<String> args = new ArrayList<>(); args.add(execRoot.getRelative("_bin/namespace-sandbox").getPathString()); - if (debug) { + if (sandboxDebug) { args.add("-D"); } @@ -137,12 +147,25 @@ public class NamespaceSandboxRunner { Command cmd = new Command(args.toArray(new String[0]), env, cwd); - cmd.execute( - /* stdin */ new byte[] {}, - Command.NO_OBSERVER, - outErr.getOutputStream(), - outErr.getErrorStream(), - /* killSubprocessOnInterrupt */ true); + try { + cmd.execute( + /* stdin */ new byte[] {}, + Command.NO_OBSERVER, + outErr.getOutputStream(), + outErr.getErrorStream(), + /* killSubprocessOnInterrupt */ true); + } catch (CommandException e) { + boolean timedOut = false; + if (e instanceof AbnormalTerminationException) { + TerminationStatus status = + ((AbnormalTerminationException) e).getResult().getTerminationStatus(); + timedOut = !status.exited() && (status.getTerminatingSignal() == 14 /* SIGALRM */); + } + String message = + CommandFailureUtils.describeCommandFailure( + verboseFailures, spawnArguments, env, cwd.getPath()); + throw new UserExecException(message, e, timedOut); + } copyOutputs(outputs); } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java index 20b64c87ca..61cd2cdbd1 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java @@ -35,10 +35,17 @@ public class SandboxActionContextProvider extends ActionContextProvider { public SandboxActionContextProvider( BlazeRuntime runtime, BuildRequest buildRequest, ExecutorService backgroundWorkers) { boolean verboseFailures = buildRequest.getOptions(ExecutionOptions.class).verboseFailures; + boolean sandboxDebug = buildRequest.getOptions(SandboxOptions.class).sandboxDebug; Builder<ActionContext> strategies = ImmutableList.builder(); if (OS.getCurrent() == OS.LINUX) { - strategies.add(new LinuxSandboxedStrategy(runtime, verboseFailures, backgroundWorkers)); + strategies.add( + new LinuxSandboxedStrategy( + runtime.getClientEnv(), + runtime.getDirectories(), + backgroundWorkers, + verboseFailures, + sandboxDebug)); } this.strategies = strategies.build(); diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java index ad4fa50dc3..4c13ddb7ec 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java @@ -28,4 +28,14 @@ public class SandboxOptions extends OptionsBase { help = "Do not print a warning when sandboxed execution is not supported on this system." ) public boolean ignoreUnsupportedSandboxing; + + @Option( + name = "sandbox_debug", + defaultValue = "false", + category = "strategy", + help = + "Let the sandbox print debug information on execution. This might help developers of " + + "Bazel or Skylark rules with debugging failures due to missing input files, etc." + ) + public boolean sandboxDebug; } diff --git a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java index ed034d5e49..ae03d191ad 100644 --- a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java +++ b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java @@ -41,7 +41,6 @@ import java.util.List; @ExecutionStrategy(name = { "standalone" }, contextType = SpawnActionContext.class) public class StandaloneSpawnStrategy implements SpawnActionContext { private final boolean verboseFailures; - private final Path processWrapper; public StandaloneSpawnStrategy(Path execRoot, boolean verboseFailures) { @@ -70,7 +69,7 @@ public class StandaloneSpawnStrategy implements SpawnActionContext { try { timeout = Integer.parseInt(timeoutStr); } catch (NumberFormatException e) { - throw new UserExecException("could not parse timeout: " + e); + throw new UserExecException("could not parse timeout: ", e); } } @@ -115,7 +114,7 @@ public class StandaloneSpawnStrategy implements SpawnActionContext { } catch (CommandException e) { String message = CommandFailureUtils.describeCommandFailure( verboseFailures, spawn.getArguments(), spawn.getEnvironment(), cwd); - throw new UserExecException(String.format("%s: %s", message, e)); + throw new UserExecException(message, e); } } diff --git a/src/test/java/BUILD b/src/test/java/BUILD index 8315e1ccc0..9f3ead75c8 100644 --- a/src/test/java/BUILD +++ b/src/test/java/BUILD @@ -586,8 +586,15 @@ java_test( args = ["com.google.devtools.build.lib.AllTests"], data = [":embedded_scripts"], deps = [ + ":actions_testutil", ":analysis_testutil", + ":foundations_testutil", ":testutil", + "//src/main/java:actions", + "//src/main/java:analysis-exec-rules-skyframe", + "//src/main/java:events", + "//src/main/java:options", + "//src/main/java:shell", "//src/main/java:vfs", "//src/main/java/com/google/devtools/build/lib/sandbox", "//third_party:guava", diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTest.java index 74333e3d27..f91bb0dbb8 100644 --- a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTest.java +++ b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTest.java @@ -20,20 +20,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.sandbox.LinuxSandboxedStrategy.MountMap; -import com.google.devtools.build.lib.testutil.TestUtils; -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.UnixFileSystem; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; @@ -53,28 +47,7 @@ import java.util.Map.Entry; * tree of files given only the set of input files. */ @RunWith(JUnit4.class) -public class LinuxSandboxedStrategyTest { - private FileSystem testFS; - private Path workingDir; - private Path fakeSandboxDir; - - @Before - public void setUp() throws Exception { - testFS = new UnixFileSystem(); - workingDir = testFS.getPath(new File(TestUtils.tmpDir()).getCanonicalPath()); - fakeSandboxDir = workingDir.getRelative("sandbox"); - fakeSandboxDir.createDirectory(); - } - - @After - public void tearDown() throws Exception { - FileSystemUtils.deleteTreesBelow(workingDir); - } - - private Path getSandboxPath(Path entry) { - return fakeSandboxDir.getRelative(entry.asFragment().relativeTo("/")); - } - +public class LinuxSandboxedStrategyTest extends LinuxSandboxedStrategyTestCase { /** * Strips the working directory (which can be very long) from the file names in the input map, to * make assertion failures easier to read. @@ -82,42 +55,30 @@ public class LinuxSandboxedStrategyTest { private ImmutableMap<String, String> userFriendlyMap(Map<Path, Path> input) { ImmutableMap.Builder<String, String> userFriendlyMap = ImmutableMap.builder(); for (Entry<Path, Path> entry : input.entrySet()) { - String key = entry.getKey().getPathString().replace(workingDir.getPathString(), ""); - String value = entry.getValue().getPathString().replace(workingDir.getPathString(), ""); + String key = entry.getKey().getPathString().replace(workspaceDir.getPathString(), ""); + String value = entry.getValue().getPathString().replace(workspaceDir.getPathString(), ""); userFriendlyMap.put(key, value); } return userFriendlyMap.build(); } - private void createTreeStructure(Map<String, String> linksAndFiles) throws IOException { - for (Entry<String, String> entry : linksAndFiles.entrySet()) { - Path filePath = workingDir.getRelative(entry.getKey()); - String linkTarget = entry.getValue(); - - FileSystemUtils.createDirectoryAndParents(filePath.getParentDirectory()); - - if (!linkTarget.isEmpty()) { - filePath.createSymbolicLink(new PathFragment(linkTarget)); - } else if (filePath.getPathString().endsWith("/")) { - filePath.createDirectory(); - } else { - FileSystemUtils.createEmptyFile(filePath); - } - } - } - /** * Takes a map of file specifications, creates the necessary files / symlinks / dirs, * mounts files listed in customMount at their canonical location in the sandbox and returns the * output of {@code LinuxSandboxedStrategy#fixMounts} for it. */ + private ImmutableMap<String, String> userFriendlyMounts( + Map<String, String> linksAndFiles, List<String> customMounts) throws IOException { + return userFriendlyMap(mounts(linksAndFiles, customMounts)); + } + private ImmutableMap<Path, Path> mounts( Map<String, String> linksAndFiles, List<String> customMounts) throws IOException { createTreeStructure(linksAndFiles); ImmutableMap.Builder<Path, Path> mounts = ImmutableMap.builder(); for (String customMount : customMounts) { - Path customMountPath = workingDir.getRelative(customMount); + Path customMountPath = workspaceDir.getRelative(customMount); mounts.put(getSandboxPath(customMountPath), customMountPath); } return LinuxSandboxedStrategy.validateMounts( @@ -126,45 +87,57 @@ public class LinuxSandboxedStrategyTest { fakeSandboxDir, LinuxSandboxedStrategy.withRecursedDirs(mounts.build()))); } - private ImmutableMap<String, String> userFriendlyMounts( - Map<String, String> linksAndFiles, List<String> customMounts) throws IOException { - return userFriendlyMap(mounts(linksAndFiles, customMounts)); - } - /** * Takes a map of file specifications, creates the necessary files / symlinks / dirs, * mounts the first file of the specification at its canonical location in the sandbox and returns * the output of {@code LinuxSandboxedStrategy#fixMounts} for it. */ - private Map<Path, Path> mounts(Map<String, String> linksAndFiles) throws IOException { - return mounts( - linksAndFiles, ImmutableList.of(Iterables.getFirst(linksAndFiles.keySet(), null))); - } - private Map<String, String> userFriendlyMounts(Map<String, String> linksAndFiles) throws IOException { return userFriendlyMap(mounts(linksAndFiles)); } + private Map<Path, Path> mounts(Map<String, String> linksAndFiles) throws IOException { + return mounts( + linksAndFiles, ImmutableList.of(Iterables.getFirst(linksAndFiles.keySet(), null))); + } + /** * Returns a map of mount entries for a list files, which can be used to assert that all * expected mounts have been made by the LinuxSandboxedStrategy. */ + private ImmutableMap<String, String> userFriendlyAsserts(List<String> asserts) { + return userFriendlyMap(asserts(asserts)); + } + private ImmutableMap<Path, Path> asserts(List<String> asserts) { ImmutableMap.Builder<Path, Path> pathifiedAsserts = ImmutableMap.builder(); for (String fileName : asserts) { - Path inputPath = workingDir.getRelative(fileName); + Path inputPath = workspaceDir.getRelative(fileName); pathifiedAsserts.put(getSandboxPath(inputPath), inputPath); } return pathifiedAsserts.build(); } - private ImmutableMap<String, String> userFriendlyAsserts(List<String> asserts) { - return userFriendlyMap(asserts(asserts)); + private void createTreeStructure(Map<String, String> linksAndFiles) throws IOException { + for (Entry<String, String> entry : linksAndFiles.entrySet()) { + Path filePath = workspaceDir.getRelative(entry.getKey()); + String linkTarget = entry.getValue(); + + FileSystemUtils.createDirectoryAndParents(filePath.getParentDirectory()); + + if (!linkTarget.isEmpty()) { + filePath.createSymbolicLink(new PathFragment(linkTarget)); + } else if (filePath.getPathString().endsWith("/")) { + filePath.createDirectory(); + } else { + FileSystemUtils.createEmptyFile(filePath); + } + } } @Test - public void resolvesRelativeFileToFileSymlinkInSameDir() throws IOException { + public void testResolvesRelativeFileToFileSymlinkInSameDir() throws IOException { Map<String, String> testFiles = new LinkedHashMap<>(); testFiles.put("symlink.txt", "goal.txt"); testFiles.put("goal.txt", ""); @@ -177,7 +150,7 @@ public class LinuxSandboxedStrategyTest { } @Test - public void resolvesRelativeFileToFileSymlinkInSubDir() throws IOException { + public void testResolvesRelativeFileToFileSymlinkInSubDir() throws IOException { Map<String, String> testFiles = ImmutableMap.of( "symlink.txt", "x/goal.txt", @@ -188,7 +161,7 @@ public class LinuxSandboxedStrategyTest { } @Test - public void resolvesRelativeFileToFileSymlinkInParentDir() throws IOException { + public void testResolvesRelativeFileToFileSymlinkInParentDir() throws IOException { Map<String, String> testFiles = ImmutableMap.of( "x/symlink.txt", "../goal.txt", @@ -200,7 +173,7 @@ public class LinuxSandboxedStrategyTest { } @Test - public void recursesSubDirs() throws IOException { + public void testRecursesSubDirs() throws IOException { ImmutableList<String> inputFile = ImmutableList.of("a/b"); Map<String, String> testFiles = @@ -219,7 +192,7 @@ public class LinuxSandboxedStrategyTest { * Test that the algorithm correctly identifies and refuses symlink loops. */ @Test - public void catchesSymlinkLoop() throws IOException { + public void testCatchesSymlinkLoop() throws IOException { try { mounts( ImmutableMap.of( @@ -231,7 +204,7 @@ public class LinuxSandboxedStrategyTest { .hasMessage( String.format( "%s (Too many levels of symbolic links)", - workingDir.getRelative("a").getPathString())); + workspaceDir.getRelative("a").getPathString())); } } @@ -240,7 +213,7 @@ public class LinuxSandboxedStrategyTest { * directories (e.g. "a -> dir/file/file"). */ @Test - public void catchesIllegalSymlink() throws IOException { + public void testCatchesIllegalSymlink() throws IOException { try { mounts( ImmutableMap.of( @@ -250,19 +223,20 @@ public class LinuxSandboxedStrategyTest { } catch (IOException e) { assertThat(e) .hasMessage( - String.format("%s (Not a directory)", workingDir.getRelative("a/c").getPathString())); + String.format( + "%s (Not a directory)", workspaceDir.getRelative("a/c").getPathString())); } } @Test public void testParseManifestFile() throws IOException { - Path targetDir = workingDir.getRelative("runfiles"); + Path targetDir = workspaceDir.getRelative("runfiles"); targetDir.createDirectory(); - Path testFile = workingDir.getRelative("testfile"); + Path testFile = workspaceDir.getRelative("testfile"); FileSystemUtils.createEmptyFile(testFile); - Path manifestFile = workingDir.getRelative("MANIFEST"); + Path manifestFile = workspaceDir.getRelative("MANIFEST"); FileSystemUtils.writeContent( manifestFile, Charset.defaultCharset(), @@ -279,47 +253,47 @@ public class LinuxSandboxedStrategyTest { fakeSandboxDir.getRelative("runfiles/x/testfile"), testFile, fakeSandboxDir.getRelative("runfiles/x/emptyfile"), - testFS.getPath("/dev/null")))); + fileSystem.getPath("/dev/null")))); } @Test public void testMountMapWithNormalMounts() throws IOException { // Allowed: Just two normal mounts (a -> sandbox/a, b -> sandbox/b) MountMap<Path, Path> mounts = new MountMap<>(); - mounts.put(fakeSandboxDir.getRelative("a"), workingDir.getRelative("a")); - mounts.put(fakeSandboxDir.getRelative("b"), workingDir.getRelative("b")); + mounts.put(fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("a")); + mounts.put(fakeSandboxDir.getRelative("b"), workspaceDir.getRelative("b")); assertThat(mounts) .isEqualTo( ImmutableMap.of( - fakeSandboxDir.getRelative("a"), workingDir.getRelative("a"), - fakeSandboxDir.getRelative("b"), workingDir.getRelative("b"))); + fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("a"), + fakeSandboxDir.getRelative("b"), workspaceDir.getRelative("b"))); } @Test public void testMountMapWithSameMountTwice() throws IOException { // Allowed: Mount same thing twice (a -> sandbox/a, a -> sandbox/a, b -> sandbox/b) MountMap<Path, Path> mounts = new MountMap<>(); - mounts.put(fakeSandboxDir.getRelative("a"), workingDir.getRelative("a")); - mounts.put(fakeSandboxDir.getRelative("a"), workingDir.getRelative("a")); - mounts.put(fakeSandboxDir.getRelative("b"), workingDir.getRelative("b")); + mounts.put(fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("a")); + mounts.put(fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("a")); + mounts.put(fakeSandboxDir.getRelative("b"), workspaceDir.getRelative("b")); assertThat(mounts) .isEqualTo( ImmutableMap.of( - fakeSandboxDir.getRelative("a"), workingDir.getRelative("a"), - fakeSandboxDir.getRelative("b"), workingDir.getRelative("b"))); + fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("a"), + fakeSandboxDir.getRelative("b"), workspaceDir.getRelative("b"))); } @Test public void testMountMapWithOneThingTwoTargets() throws IOException { // Allowed: Mount one thing in two targets (x -> sandbox/a, x -> sandbox/b) MountMap<Path, Path> mounts = new MountMap<>(); - mounts.put(fakeSandboxDir.getRelative("a"), workingDir.getRelative("x")); - mounts.put(fakeSandboxDir.getRelative("b"), workingDir.getRelative("x")); + mounts.put(fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("x")); + mounts.put(fakeSandboxDir.getRelative("b"), workspaceDir.getRelative("x")); assertThat(mounts) .isEqualTo( ImmutableMap.of( - fakeSandboxDir.getRelative("a"), workingDir.getRelative("x"), - fakeSandboxDir.getRelative("b"), workingDir.getRelative("x"))); + fakeSandboxDir.getRelative("a"), workspaceDir.getRelative("x"), + fakeSandboxDir.getRelative("b"), workspaceDir.getRelative("x"))); } @Test @@ -327,17 +301,18 @@ public class LinuxSandboxedStrategyTest { // Forbidden: Mount two things onto the same target (x -> sandbox/a, y -> sandbox/a) try { MountMap<Path, Path> mounts = new MountMap<>(); - mounts.put(fakeSandboxDir.getRelative("x"), workingDir.getRelative("a")); - mounts.put(fakeSandboxDir.getRelative("x"), workingDir.getRelative("b")); + mounts.put(fakeSandboxDir.getRelative("x"), workspaceDir.getRelative("a")); + mounts.put(fakeSandboxDir.getRelative("x"), workspaceDir.getRelative("b")); fail(); } catch (IllegalArgumentException e) { assertThat(e) .hasMessage( String.format( "Cannot mount both '%s' and '%s' onto '%s'", - workingDir.getRelative("a"), - workingDir.getRelative("b"), + workspaceDir.getRelative("a"), + workspaceDir.getRelative("b"), fakeSandboxDir.getRelative("x"))); } } + } diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTestCase.java b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTestCase.java new file mode 100644 index 0000000000..7fb6c8bf4d --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTestCase.java @@ -0,0 +1,126 @@ +// Copyright 2015 Google Inc. 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 com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.eventbus.EventBus; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.devtools.build.lib.actions.ActionContextProvider; +import com.google.devtools.build.lib.actions.BlazeExecutor; +import com.google.devtools.build.lib.actions.Executor.ActionContext; +import com.google.devtools.build.lib.actions.SpawnActionContext; +import com.google.devtools.build.lib.analysis.BlazeDirectories; +import com.google.devtools.build.lib.events.PrintingEventHandler; +import com.google.devtools.build.lib.events.Reporter; +import com.google.devtools.build.lib.exec.ExecutionOptions; +import com.google.devtools.build.lib.testutil.BlazeTestUtils; +import com.google.devtools.build.lib.testutil.TestFileOutErr; +import com.google.devtools.build.lib.testutil.TestUtils; +import com.google.devtools.build.lib.util.BlazeClock; +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.util.FileSystems; +import com.google.devtools.common.options.OptionsParser; + +import org.junit.Before; + +import java.io.IOException; + +/** + * Common parts of all {@link LinuxSandboxedStrategy} tests. + */ +public class LinuxSandboxedStrategyTestCase { + private Reporter reporter = new Reporter(PrintingEventHandler.ERRORS_AND_WARNINGS_TO_STDERR); + private Path outputBase; + + protected FileSystem fileSystem; + protected Path workspaceDir; + protected Path fakeSandboxDir; + protected Path fakeSandboxExecRoot; + + protected BlazeExecutor executor; + protected BlazeDirectories blazeDirs; + + protected TestFileOutErr outErr = new TestFileOutErr(); + + protected String out() { + return outErr.outAsLatin1(); + } + + protected String err() { + return outErr.errAsLatin1(); + } + + protected Path getSandboxPath(Path entry) { + return fakeSandboxDir.getRelative(entry.asFragment().relativeTo("/")); + } + + @Before + public void setUp() throws Exception { + Path testRoot = createTestRoot(); + + workspaceDir = testRoot.getRelative("workspace"); + workspaceDir.createDirectory(); + + outputBase = testRoot.getRelative("outputBase"); + outputBase.createDirectory(); + + fakeSandboxDir = testRoot.getRelative("sandbox"); + fakeSandboxDir.createDirectory(); + + blazeDirs = new BlazeDirectories(outputBase, outputBase, workspaceDir); + BlazeTestUtils.getIntegrationBinTools(blazeDirs); + + OptionsParser optionsParser = + OptionsParser.newOptionsParser(ExecutionOptions.class, SandboxOptions.class); + optionsParser.parse("--verbose_failures"); + + EventBus bus = new EventBus(); + + this.executor = + new BlazeExecutor( + blazeDirs.getExecRoot(), + blazeDirs.getOutputPath(), + reporter, + bus, + BlazeClock.instance(), + optionsParser, + /* verboseFailures */ true, + /* showSubcommands */ false, + ImmutableList.<ActionContext>of(), + ImmutableMap.<String, SpawnActionContext>of( + "", + new LinuxSandboxedStrategy( + ImmutableMap.<String, String>of(), + blazeDirs, + MoreExecutors.newDirectExecutorService(), + true, + false)), + ImmutableList.<ActionContextProvider>of()); + } + + private Path createTestRoot() throws IOException { + fileSystem = FileSystems.initDefaultAsNative(); + Path testRoot = fileSystem.getPath(TestUtils.tmpDir()); + try { + FileSystemUtils.deleteTreesBelow(testRoot); + } catch (IOException e) { + System.err.println("Failed to remove directory " + testRoot + ": " + e.getMessage()); + throw e; + } + return testRoot; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/LocalLinuxSandboxedStrategyTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/LocalLinuxSandboxedStrategyTest.java new file mode 100644 index 0000000000..7563d1fadb --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/sandbox/LocalLinuxSandboxedStrategyTest.java @@ -0,0 +1,93 @@ +// Copyright 2015 Google Inc. 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 org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionMetadata; +import com.google.devtools.build.lib.actions.BaseSpawn; +import com.google.devtools.build.lib.actions.ResourceSet; +import com.google.devtools.build.lib.actions.Spawn; +import com.google.devtools.build.lib.actions.UserExecException; +import com.google.devtools.build.lib.actions.util.ActionsTestUtil; +import com.google.devtools.build.lib.exec.SingleBuildFileCache; +import com.google.devtools.build.lib.shell.BadExitStatusException; +import com.google.devtools.build.lib.testutil.TestSpec; +import com.google.devtools.build.lib.util.CommandFailureUtils; +import com.google.devtools.build.lib.util.OS; +import com.google.devtools.build.lib.vfs.Path; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.Map; + +/** + * Tests for {@code LinuxSandboxedStrategy} that must run locally, because they need to actually + * run the namespace-sandbox binary. + */ +@TestSpec(localOnly = true, supportedOs = OS.LINUX) +@RunWith(JUnit4.class) +public class LocalLinuxSandboxedStrategyTest extends LinuxSandboxedStrategyTestCase { + protected Spawn createSpawn(String... arguments) { + Map<String, String> environment = ImmutableMap.<String, String>of(); + Map<String, String> executionInfo = ImmutableMap.<String, String>of(); + ActionMetadata action = new ActionsTestUtil.NullAction(); + ResourceSet localResources = ResourceSet.ZERO; + return new BaseSpawn( + Arrays.asList(arguments), environment, executionInfo, action, localResources); + } + + protected ActionExecutionContext createContext() { + Path execRoot = executor.getExecRoot(); + return new ActionExecutionContext( + executor, + new SingleBuildFileCache(execRoot.getPathString(), execRoot.getFileSystem()), + null, + outErr, + null); + } + + @Test + public void testExecutionSuccess() throws Exception { + Spawn spawn = createSpawn("/bin/sh", "-c", "echo Hello, world.; touch dummy"); + executor.getSpawnActionContext(spawn.getMnemonic()).exec(spawn, createContext()); + assertThat(out()).isEqualTo("Hello, world.\n"); + assertThat(err()).isEmpty(); + } + + @Test + public void testExecutionFailurePrintsCorrectMessage() throws Exception { + Spawn spawn = createSpawn("/bin/sh", "-c", "echo ERROR >&2; exit 1"); + try { + executor.getSpawnActionContext(spawn.getMnemonic()).exec(spawn, createContext()); + fail(); + } catch (UserExecException e) { + assertThat(err()).isEqualTo("ERROR\n"); + assertThat(e.getMessage()) + .startsWith( + CommandFailureUtils.describeCommandFailure( + true, + spawn.getArguments(), + spawn.getEnvironment(), + blazeDirs.getExecRoot().toString())); + assertThat(e.getCause()).isInstanceOf(BadExitStatusException.class); + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxLocalTests.java b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxLocalTests.java new file mode 100644 index 0000000000..5a72adeeac --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxLocalTests.java @@ -0,0 +1,38 @@ +// Copyright 2015 Google Inc. 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 com.google.common.base.Predicates; +import com.google.devtools.build.lib.testutil.BlazeTestSuiteBuilder; +import com.google.devtools.build.lib.testutil.CustomSuite; + +import org.junit.runner.RunWith; + +import java.util.Set; + +/** + * Test suite that runs all tests that are local-only. + */ +@RunWith(CustomSuite.class) +public class SandboxLocalTests extends BlazeTestSuiteBuilder { + public static Set<Class<?>> suite() { + return new SandboxLocalTests() + .getBuilder() + .matchClasses( + Predicates.and( + BlazeTestSuiteBuilder.TEST_IS_LOCAL_ONLY, + BlazeTestSuiteBuilder.TEST_SUPPORTS_CURRENT_OS)) + .create(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxTests.java b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxTests.java new file mode 100644 index 0000000000..ee676cc244 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxTests.java @@ -0,0 +1,35 @@ +// Copyright 2015 Google Inc. 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 com.google.common.base.Predicates; +import com.google.devtools.build.lib.testutil.BlazeTestSuiteBuilder; +import com.google.devtools.build.lib.testutil.CustomSuite; + +import org.junit.runner.RunWith; + +import java.util.Set; + +/** + * Test suite that runs all tests that are not local-only. + */ +@RunWith(CustomSuite.class) +public class SandboxTests extends BlazeTestSuiteBuilder { + public static Set<Class<?>> suite() { + return new SandboxTests() + .getBuilder() + .matchClasses(Predicates.not(BlazeTestSuiteBuilder.TEST_IS_LOCAL_ONLY)) + .create(); + } +} |