From 450b1b2af676c34aa08f91a95c00bb9ee57c2546 Mon Sep 17 00:00:00 2001 From: Philipp Wollermann Date: Wed, 2 Sep 2015 14:51:19 +0000 Subject: sandbox: When spawn.getInputs() contains a directory, recurse into it and mount the individual files. -- MOS_MIGRATED_REVID=102142064 --- .../lib/sandbox/LinuxSandboxedStrategyTest.java | 253 +++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTest.java (limited to 'src/test/java/com/google/devtools/build/lib') 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 new file mode 100644 index 0000000000..e71f800fe7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategyTest.java @@ -0,0 +1,253 @@ +// 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.ImmutableList; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +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.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +/** + * Tests for {@code LinuxSandboxedStrategy}. + * + *

The general idea for each test is to provide a file tree consisting of symlinks, directories + * and empty files and then handing that together with an arbitrary number of input files (what + * would be specified in the "srcs" attribute, for example) to the LinuxSandboxedStrategy. + * + *

The algorithm that processes the mounts must then always find (and thus mount) the expected + * 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("/")); + } + + /** + * Strips the working directory (which can be very long) from the file names in the input map, to + * make assertion failures easier to read. + */ + private ImmutableSetMultimap getReadableMap(Multimap input) { + ImmutableSetMultimap.Builder readableMap = ImmutableSetMultimap.builder(); + for (Entry entry : input.entries()) { + String key = entry.getKey().getPathString().replace(workingDir.getPathString(), ""); + String value = entry.getValue().getPathString().replace(workingDir.getPathString(), ""); + readableMap.put(key, value); + } + return readableMap.build(); + } + + private void createTreeStructure(Multimap linksAndFiles) throws IOException { + for (Entry entry : linksAndFiles.entries()) { + 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 Multimap 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 ImmutableSetMultimap mounts( + Multimap linksAndFiles, List customMounts) throws IOException { + createTreeStructure(linksAndFiles); + + ImmutableSetMultimap.Builder mounts = ImmutableSetMultimap.builder(); + for (String customMount : customMounts) { + Path customMountPath = workingDir.getRelative(customMount); + mounts.put(customMountPath, getSandboxPath(customMountPath)); + } + return LinuxSandboxedStrategy.validateMounts( + fakeSandboxDir, + LinuxSandboxedStrategy.withResolvedSymlinks( + fakeSandboxDir, LinuxSandboxedStrategy.withRecursedDirs(mounts.build()))); + } + + private ImmutableSetMultimap readableMounts( + Multimap linksAndFiles, List customMounts) throws IOException { + return getReadableMap(mounts(linksAndFiles, customMounts)); + } + + /** + * Takes a Multimap 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 ImmutableSetMultimap mounts(Multimap linksAndFiles) + throws IOException { + return mounts(linksAndFiles, ImmutableList.of(Iterables.getFirst(linksAndFiles.keys(), null))); + } + + private ImmutableSetMultimap readableMounts( + Multimap linksAndFiles) throws IOException { + return getReadableMap(mounts(linksAndFiles)); + } + + /** + * Returns a Multimap of mount entries for a list files, which can be used to assert that all + * expected mounts have been made by the LinuxSandboxedStrategy. + */ + private ImmutableSetMultimap asserts(List asserts) { + ImmutableSetMultimap.Builder pathifiedAsserts = ImmutableSetMultimap.builder(); + for (String fileName : asserts) { + Path inputPath = workingDir.getRelative(fileName); + pathifiedAsserts.put(inputPath, getSandboxPath(inputPath)); + } + return pathifiedAsserts.build(); + } + + private ImmutableSetMultimap readableAsserts(List asserts) { + return getReadableMap(asserts(asserts)); + } + + @Test + public void resolvesRelativeFileToFileSymlinkInSameDir() throws IOException { + Multimap testFiles = LinkedHashMultimap.create(); + testFiles.put("symlink.txt", "goal.txt"); + testFiles.put("goal.txt", ""); + + List assertMounts = new ArrayList<>(); + assertMounts.add("symlink.txt"); + assertMounts.add("goal.txt"); + + assertThat(readableMounts(testFiles)).containsExactly(readableAsserts(assertMounts)); + } + + @Test + public void resolvesRelativeFileToFileSymlinkInSubDir() throws IOException { + Multimap testFiles = + ImmutableSetMultimap.of( + "symlink.txt", "x/goal.txt", + "x/goal.txt", ""); + + List assertMounts = ImmutableList.of("symlink.txt", "x/goal.txt"); + assertThat(readableMounts(testFiles)).containsExactly(readableAsserts(assertMounts)); + } + + @Test + public void resolvesRelativeFileToFileSymlinkInParentDir() throws IOException { + Multimap testFiles = + ImmutableSetMultimap.of( + "x/symlink.txt", "../goal.txt", + "goal.txt", ""); + + List assertMounts = ImmutableList.of("x/symlink.txt", "goal.txt"); + + assertThat(readableMounts(testFiles)).containsExactly(readableAsserts(assertMounts)); + } + + @Test + public void recursesSubDirs() throws IOException { + ImmutableList inputFile = ImmutableList.of("a/b"); + + Multimap testFiles = + ImmutableSetMultimap.of( + "a/b/x.txt", "", + "a/b/y.txt", "z.txt", + "a/b/z.txt", ""); + + List assertMounts = ImmutableList.of("a/b/x.txt", "a/b/y.txt", "a/b/z.txt"); + + assertThat(readableMounts(testFiles, inputFile)).containsExactly(readableAsserts(assertMounts)); + } + + /** + * Test that the algorithm correctly identifies and refuses symlink loops. + */ + @Test + public void catchesSymlinkLoop() throws IOException { + try { + mounts( + ImmutableSetMultimap.of( + "a", "b", + "b", "a")); + fail(); + } catch (IOException e) { + assertThat(e) + .hasMessage( + String.format( + "%s (Too many levels of symbolic links)", + workingDir.getRelative("a").getPathString())); + } + } + + /** + * Test that the algorithm correctly detects and refuses symlinks whose subcomponents are not all + * directories (e.g. "a -> dir/file/file"). + */ + @Test + public void catchesIllegalSymlink() throws IOException { + try { + mounts( + ImmutableSetMultimap.of( + "b", "a/c", + "a", "")); + fail(); + } catch (IOException e) { + assertThat(e) + .hasMessage( + String.format("%s (Not a directory)", workingDir.getRelative("a/c").getPathString())); + } + } +} -- cgit v1.2.3