Delete UnionFileSystem.

It was used in a single, Google-internal test which I cleaned up as pre-work for this change.

RELNOTES: None.
PiperOrigin-RevId: 203727293
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
deleted file mode 100644
index 2136633..0000000
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
+++ /dev/null
@@ -1,513 +0,0 @@
-// Copyright 2014 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.vfs;
-
-import com.google.common.base.Preconditions;
-import com.google.devtools.build.lib.concurrent.ThreadSafety;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import javax.annotation.Nullable;
-
-/**
- * Presents a unified view of multiple virtual {@link FileSystem} instances, to which requests are
- * delegated based on a {@link PathFragment} prefix mapping. If multiple prefixes apply to a given
- * path, the *longest* (i.e. most specific) match is used. The order in which the delegates are
- * specified does not influence the mapping.
- *
- * <p>Paths are preserved absolutely, contrary to how "mount" works, e.g.: /foo/bar maps to /foo/bar
- * on the delegate, even if it is mounted at /foo.
- *
- * <p>For example: "/in" maps to InFileSystem, "/" maps to OtherFileSystem. Reading from
- * "/in/base/BUILD" through the UnionFileSystem will delegate the read operation to InFileSystem,
- * which will read "/in/base/BUILD" relative to its root. ("mount" behavior would remap it to
- * "/base/BUILD" on the delegate).
- *
- * <p>Intra-filesystem symbolic links are resolved to their ultimate targets. Cross-filesystem links
- * are not currently supported.
- */
-@ThreadSafety.ThreadSafe
-public final class UnionFileSystem extends FileSystem {
-
-  private static class FileSystemAndPrefix {
-    final PathFragment prefix;
-    final FileSystem fileSystem;
-
-    public FileSystemAndPrefix(PathFragment prefix, FileSystem fileSystem) {
-      this.prefix = prefix;
-      this.fileSystem = fileSystem;
-    }
-  }
-
-  // List of file systems and their mappings, sorted by prefix length descending.
-  private final List<FileSystemAndPrefix> fileSystems;
-  private final FileSystem rootFileSystem;
-
-  // True if the file path is case-sensitive on all the FileSystem
-  // or False if they are all case-insensitive, otherwise error.
-  private final boolean isCaseSensitive;
-
-  /**
-   * Creates a new modifiable UnionFileSystem with prefix mappings specified by a map.
-   *
-   * @param prefixMapping map of path prefixes to {@link FileSystem}s
-   * @param rootFileSystem root for default requests; i.e. mapping of "/"
-   */
-  public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping, FileSystem rootFileSystem) {
-    super();
-    Preconditions.checkNotNull(prefixMapping);
-    Preconditions.checkNotNull(rootFileSystem);
-    Preconditions.checkArgument(rootFileSystem != this, "Circular root filesystem.");
-    Preconditions.checkArgument(
-        prefixMapping.keySet().stream().noneMatch(p -> p.getPathString().equals("/")),
-        "Attempted to specify an explicit root prefix mapping; "
-            + "please use the rootFileSystem argument instead.");
-
-    this.fileSystems = new ArrayList<>();
-    this.rootFileSystem = rootFileSystem;
-    this.isCaseSensitive = rootFileSystem.isFilePathCaseSensitive();
-
-    for (Map.Entry<PathFragment, FileSystem> prefix : prefixMapping.entrySet()) {
-      FileSystem delegate = prefix.getValue();
-      Preconditions.checkArgument(
-          delegate.isFilePathCaseSensitive() == this.isCaseSensitive,
-          "The case sensitiveness of FileSystem are different in UnionFileSystem");
-      PathFragment prefixPath = prefix.getKey();
-
-      // Extra slash prevents within-directory mappings, which Path can't handle.
-      fileSystems.add(new FileSystemAndPrefix(prefixPath, delegate));
-    }
-    // Order by length descending. This ensures that more specific mapping takes precedence
-    // when we try to find the file system of a given path.
-    Comparator<FileSystemAndPrefix> comparator =
-        Comparator.comparing(f -> f.prefix.getPathString().length());
-    fileSystems.sort(comparator.reversed());
-  }
-
-  /**
-   * Retrieves the filesystem delegate of a path mapping. Does not follow symlinks (but you can call
-   * on a path preprocessed with {@link #resolveSymbolicLinks} to support this use case).
-   *
-   * @param path the {@link Path} to map to a filesystem
-   * @throws IllegalArgumentException if no delegate exists for the path
-   */
-  FileSystem getDelegate(Path path) {
-    Preconditions.checkNotNull(path);
-    FileSystem delegate = null;
-    // Linearly iterate over each mapped file system and find the one that handles this path.
-    // For small number of mappings, this will be more efficient than using a trie
-    for (FileSystemAndPrefix fileSystemAndPrefix : this.fileSystems) {
-      if (path.startsWith(fileSystemAndPrefix.prefix)) {
-        delegate = fileSystemAndPrefix.fileSystem;
-        break;
-      }
-    }
-    return delegate != null ? delegate : rootFileSystem;
-  }
-
-  // Associates the path with the root of the given delegate filesystem.
-  // Necessary to avoid null pointer problems inside of the delegates.
-  Path adjustPath(Path path, FileSystem delegate) {
-    return delegate.getPath(path.getPathString());
-  }
-
-  /**
-   * Follow a symbolic link once using the appropriate delegate filesystem, also resolving parent
-   * directory symlinks.
-   *
-   * @param path {@link Path} to the symbolic link
-   */
-  @Override
-  protected PathFragment readSymbolicLink(Path path) throws IOException {
-    Preconditions.checkNotNull(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.readSymbolicLink(adjustPath(path, delegate));
-  }
-
-  @Override
-  protected PathFragment resolveOneLink(Path path) throws IOException {
-    Preconditions.checkNotNull(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.resolveOneLink(adjustPath(path, delegate));
-  }
-
-  private void checkModifiable(Path path) {
-    if (!supportsModifications(path)) {
-      throw new UnsupportedOperationException(
-          String.format("Modifications to this %s are disabled.", getClass().getSimpleName()));
-    }
-  }
-
-  @Override
-  public boolean supportsModifications(Path path) {
-    FileSystem delegate = getDelegate(path);
-    path = adjustPath(path, delegate);
-    return delegate.supportsModifications(path);
-  }
-
-  @Override
-  public boolean supportsSymbolicLinksNatively(Path path) {
-    FileSystem delegate = getDelegate(path);
-    path = adjustPath(path, delegate);
-    return delegate.supportsSymbolicLinksNatively(path);
-  }
-
-  @Override
-  public boolean supportsHardLinksNatively(Path path) {
-    FileSystem delegate = getDelegate(path);
-    path = adjustPath(path, delegate);
-    return delegate.supportsHardLinksNatively(path);
-  }
-
-  @Override
-  public boolean isFilePathCaseSensitive() {
-    return isCaseSensitive;
-  }
-
-  @Override
-  public String getFileSystemType(Path path) {
-    try {
-      path = internalResolveSymlink(path);
-    } catch (IOException e) {
-      return "unknown";
-    }
-    FileSystem delegate = getDelegate(path);
-    return delegate.getFileSystemType(path);
-  }
-
-  @Override
-  protected byte[] getDigest(Path path, DigestHashFunction hashFunction) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.getDigest(adjustPath(path, delegate), hashFunction);
-  }
-
-  @Override
-  public boolean createDirectory(Path path) throws IOException {
-    checkModifiable(path);
-    // When creating the exact directory that is mapped,
-    // create it on both the parent's delegate and the path's delegate.
-    // This is necessary both for the parent to see the directory and for the
-    // delegate to use it.
-    // This is present to address this problematic case:
-    //   / -> RootFs
-    //   /foo -> FooFs
-    //   mkdir /foo
-    //   ls / ("foo" would be missing if not created on the parent)
-    //   ls /foo (would fail if foo weren't also present on the child)
-    FileSystem delegate = getDelegate(path);
-    Path parent = path.getParentDirectory();
-    if (parent != null) {
-      parent = internalResolveSymlink(parent);
-      FileSystem parentDelegate = getDelegate(parent);
-      if (parentDelegate != delegate) {
-        // There's a possibility it already exists on the parent, so don't die
-        // if the directory can't be created there.
-        parentDelegate.createDirectory(adjustPath(path, parentDelegate));
-      }
-    }
-    return delegate.createDirectory(adjustPath(path, delegate));
-  }
-
-  @Override
-  public void createDirectoryAndParents(Path path) throws IOException {
-    checkModifiable(path);
-    FileSystem delegate = getDelegate(path);
-    delegate.createDirectoryAndParents(adjustPath(path, delegate));
-  }
-
-  @Override
-  protected long getFileSize(Path path, boolean followSymlinks) throws IOException {
-    path = followSymlinks ? internalResolveSymlink(path) : path;
-    FileSystem delegate = getDelegate(path);
-    return delegate.getFileSize(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  public boolean delete(Path path) throws IOException {
-    checkModifiable(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.delete(adjustPath(path, delegate));
-  }
-
-  @Override
-  protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException {
-    path = followSymlinks ? internalResolveSymlink(path) : path;
-    FileSystem delegate = getDelegate(path);
-    return delegate.getLastModifiedTime(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  public void setLastModifiedTime(Path path, long newTime) throws IOException {
-    path = internalResolveSymlink(path);
-    checkModifiable(path);
-    FileSystem delegate = getDelegate(path);
-    delegate.setLastModifiedTime(adjustPath(path, delegate), newTime);
-  }
-
-  @Override
-  protected boolean isSymbolicLink(Path path) {
-    FileSystem delegate = getDelegate(path);
-    path = adjustPath(path, delegate);
-    return delegate.isSymbolicLink(path);
-  }
-
-  @Override
-  protected boolean isDirectory(Path path, boolean followSymlinks) {
-    try {
-      path = followSymlinks ? internalResolveSymlink(path) : path;
-    } catch (IOException e) {
-      return false;
-    }
-    FileSystem delegate = getDelegate(path);
-    return delegate.isDirectory(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  protected boolean isFile(Path path, boolean followSymlinks) {
-    try {
-      path = followSymlinks ? internalResolveSymlink(path) : path;
-    } catch (IOException e) {
-      return false;
-    }
-    FileSystem delegate = getDelegate(path);
-    return delegate.isFile(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  protected boolean isSpecialFile(Path path, boolean followSymlinks) {
-    try {
-      path = followSymlinks ? internalResolveSymlink(path) : path;
-    } catch (IOException e) {
-      return false;
-    }
-    FileSystem delegate = getDelegate(path);
-    return delegate.isSpecialFile(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException {
-    checkModifiable(linkPath);
-    if (!supportsSymbolicLinksNatively(linkPath)) {
-      throw new UnsupportedOperationException(
-          "Attempted to create a symlink, but symlink support is disabled.");
-    }
-
-    FileSystem delegate = getDelegate(linkPath);
-    delegate.createSymbolicLink(adjustPath(linkPath, delegate), targetFragment);
-  }
-
-  @Override
-  protected boolean exists(Path path, boolean followSymlinks) {
-    try {
-      path = followSymlinks ? internalResolveSymlink(path) : path;
-    } catch (IOException e) {
-      return false;
-    }
-    FileSystem delegate = getDelegate(path);
-    return delegate.exists(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  protected FileStatus stat(Path path, boolean followSymlinks) throws IOException {
-    path = followSymlinks ? internalResolveSymlink(path) : path;
-    FileSystem delegate = getDelegate(path);
-    return delegate.stat(adjustPath(path, delegate), false);
-  }
-
-  // Needs to be overridden for the delegation logic, because the
-  // UnixFileSystem implements statNullable and stat as separate codepaths.
-  // More generally, we wish to delegate all filesystem operations.
-  @Override
-  protected FileStatus statNullable(Path path, boolean followSymlinks) {
-    try {
-      path = followSymlinks ? internalResolveSymlink(path) : path;
-    } catch (IOException e) {
-      return null;
-    }
-    FileSystem delegate = getDelegate(path);
-    return delegate.statNullable(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  @Nullable
-  protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException {
-    path = followSymlinks ? internalResolveSymlink(path) : path;
-    FileSystem delegate = getDelegate(path);
-    return delegate.statIfFound(adjustPath(path, delegate), false);
-  }
-
-  /**
-   * Retrieves the directory entries for the specified path under the assumption that {@code
-   * resolvedPath} is the resolved path of {@code path} in one of the underlying file systems.
-   *
-   * @param path the {@link Path} whose children are to be retrieved
-   */
-  @Override
-  protected Collection<String> getDirectoryEntries(Path path) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    Path resolvedPath = adjustPath(path, delegate);
-    return delegate.getDirectoryEntries(resolvedPath);
-  }
-
-  // No need for the more complex logic of getDirectoryEntries; it calls it implicitly.
-  @Override
-  protected Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException {
-    path = followSymlinks ? internalResolveSymlink(path) : path;
-    FileSystem delegate = getDelegate(path);
-    return delegate.readdir(adjustPath(path, delegate), false);
-  }
-
-  @Override
-  protected boolean isReadable(Path path) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.isReadable(adjustPath(path, delegate));
-  }
-
-  @Override
-  protected void setReadable(Path path, boolean readable) throws IOException {
-    path = internalResolveSymlink(path);
-    checkModifiable(path);
-    FileSystem delegate = getDelegate(path);
-    delegate.setReadable(adjustPath(path, delegate), readable);
-  }
-
-  @Override
-  protected boolean isWritable(Path path) throws IOException {
-    if (!supportsModifications(path)) {
-      return false;
-    }
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.isWritable(adjustPath(path, delegate));
-  }
-
-  @Override
-  public void setWritable(Path path, boolean writable) throws IOException {
-    checkModifiable(path);
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    delegate.setWritable(adjustPath(path, delegate), writable);
-  }
-
-  @Override
-  protected boolean isExecutable(Path path) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.isExecutable(adjustPath(path, delegate));
-  }
-
-  @Override
-  protected void setExecutable(Path path, boolean executable) throws IOException {
-    path = internalResolveSymlink(path);
-    checkModifiable(path);
-    FileSystem delegate = getDelegate(path);
-    delegate.setExecutable(adjustPath(path, delegate), executable);
-  }
-
-  @Override
-  protected byte[] getFastDigest(Path path, DigestHashFunction hashFunction) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.getFastDigest(adjustPath(path, delegate), hashFunction);
-  }
-
-  @Override
-  public byte[] getxattr(Path path, String name) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.getxattr(adjustPath(path, delegate), name);
-  }
-
-  @Override
-  protected InputStream getInputStream(Path path) throws IOException {
-    path = internalResolveSymlink(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.getInputStream(adjustPath(path, delegate));
-  }
-
-  @Override
-  protected OutputStream getOutputStream(Path path, boolean append) throws IOException {
-    path = internalResolveSymlink(path);
-    checkModifiable(path);
-    FileSystem delegate = getDelegate(path);
-    return delegate.getOutputStream(adjustPath(path, delegate), append);
-  }
-
-  @Override
-  public void renameTo(Path sourcePath, Path targetPath) throws IOException {
-    sourcePath = internalResolveSymlink(sourcePath);
-    FileSystem sourceDelegate = getDelegate(sourcePath);
-    if (!sourceDelegate.supportsModifications(sourcePath)) {
-      throw new UnsupportedOperationException(
-          String.format(
-              "The filesystem for the source path %s does not support modifications.",
-              sourcePath.getPathString()));
-    }
-    sourcePath = adjustPath(sourcePath, sourceDelegate);
-
-    FileSystem targetDelegate = getDelegate(targetPath);
-    if (!targetDelegate.supportsModifications(targetPath)) {
-      throw new UnsupportedOperationException(
-          String.format(
-              "The filesystem for the target path %s does not support modifications.",
-              targetPath.getPathString()));
-    }
-    targetPath = adjustPath(targetPath, targetDelegate);
-
-    if (sourceDelegate == targetDelegate) {
-      // Easy, same filesystem.
-      sourceDelegate.renameTo(sourcePath, targetPath);
-      return;
-    } else {
-      // Copy across filesystems, then delete.
-      // copyFile throws on failure, so delete will never be reached if it fails.
-      FileSystemUtils.copyFile(sourcePath, targetPath);
-      sourceDelegate.delete(sourcePath);
-    }
-  }
-
-  @Override
-  protected void createFSDependentHardLink(Path linkPath, Path originalPath) throws IOException {
-    checkModifiable(linkPath);
-
-    originalPath = internalResolveSymlink(originalPath);
-    FileSystem originalDelegate = getDelegate(originalPath);
-    FileSystem linkDelegate = getDelegate(linkPath);
-
-    if (!originalDelegate.equals(linkDelegate)
-        || !linkDelegate.supportsHardLinksNatively(linkPath)) {
-      throw new UnsupportedOperationException(
-          "Attempted to create a hard link, but hard link support is disabled.");
-    }
-    linkDelegate.createFSDependentHardLink(
-        adjustPath(linkPath, linkDelegate), adjustPath(originalPath, originalDelegate));
-  }
-
-  private Path internalResolveSymlink(Path path) throws IOException {
-    while (isSymbolicLink(path)) {
-      PathFragment pathFragment = resolveOneLink(path);
-      path = path.getRelative(pathFragment);
-    }
-    return path;
-  }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
deleted file mode 100644
index b34c0c0..0000000
--- a/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-// Copyright 2014 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.vfs;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.clock.BlazeClock;
-import com.google.devtools.build.lib.clock.Clock;
-import com.google.devtools.build.lib.unix.UnixFileSystem;
-import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Tests for the UnionFileSystem, both of generic FileSystem functionality (inherited) and tests of
- * UnionFileSystem-specific behavior.
- */
-@RunWith(JUnit4.class)
-public class UnionFileSystemTest extends SymlinkAwareFileSystemTest {
-  private XAttrInMemoryFs inDelegate;
-  private XAttrInMemoryFs outDelegate;
-  private XAttrInMemoryFs defaultDelegate;
-  private UnionFileSystem unionfs;
-
-  private static final String XATTR_VAL = "SOME_XATTR_VAL";
-  private static final String XATTR_KEY = "SOME_XATTR_KEY";
-
-  private void setupDelegateFileSystems() {
-    inDelegate = new XAttrInMemoryFs(BlazeClock.instance());
-    outDelegate = new XAttrInMemoryFs(BlazeClock.instance());
-    defaultDelegate = new XAttrInMemoryFs(BlazeClock.instance());
-
-    unionfs = createDefaultUnionFileSystem();
-  }
-
-  private UnionFileSystem createDefaultUnionFileSystem() {
-    return new UnionFileSystem(
-        ImmutableMap.of(
-            PathFragment.create("/in"), inDelegate,
-            PathFragment.create("/out"), outDelegate),
-        defaultDelegate);
-  }
-
-  @Override
-  protected FileSystem getFreshFileSystem() {
-    // Executed with each new test because it is called by super.setUp().
-    setupDelegateFileSystems();
-    return unionfs;
-  }
-
-  @Override
-  public void destroyFileSystem(FileSystem fileSystem) {
-    // Nothing.
-  }
-
-  // Tests of UnionFileSystem-specific behavior below.
-
-  @Test
-  public void testBasicDelegation() throws Exception {
-    unionfs = createDefaultUnionFileSystem();
-    Path fooPath = unionfs.getPath("/foo");
-    Path inPath = unionfs.getPath("/in");
-    Path outPath = unionfs.getPath("/out/in.txt");
-    assertThat(unionfs.getDelegate(inPath)).isSameAs(inDelegate);
-    assertThat(unionfs.getDelegate(outPath)).isSameAs(outDelegate);
-    assertThat(unionfs.getDelegate(fooPath)).isSameAs(defaultDelegate);
-  }
-
-  @Test
-  public void testBasicXattr() throws Exception {
-    Path fooPath = unionfs.getPath("/foo");
-    Path inPath = unionfs.getPath("/in");
-    Path outPath = unionfs.getPath("/out/in.txt");
-
-    assertThat(inPath.getxattr(XATTR_KEY)).isEqualTo(XATTR_VAL.getBytes(UTF_8));
-    assertThat(outPath.getxattr(XATTR_KEY)).isEqualTo(XATTR_VAL.getBytes(UTF_8));
-    assertThat(fooPath.getxattr(XATTR_KEY)).isEqualTo(XATTR_VAL.getBytes(UTF_8));
-    assertThat(inPath.getxattr("not_key")).isNull();
-    assertThat(outPath.getxattr("not_key")).isNull();
-    assertThat(fooPath.getxattr("not_key")).isNull();
-  }
-
-  @Test
-  public void testDefaultFileSystemRequired() throws Exception {
-    try {
-      new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(), null);
-      fail("Able to create a UnionFileSystem with no default!");
-    } catch (NullPointerException expected) {
-      // OK - should fail in this case.
-    }
-  }
-
-  // Check for appropriate registration and lookup of delegate filesystems based
-  // on path prefixes, including non-canonical paths.
-  @Test
-  public void testPrefixDelegation() throws Exception {
-    unionfs =
-        new UnionFileSystem(
-            ImmutableMap.<PathFragment, FileSystem>of(
-                PathFragment.create("/foo"), inDelegate,
-                PathFragment.create("/foo/bar"), outDelegate),
-            defaultDelegate);
-
-    assertThat(unionfs.getDelegate(unionfs.getPath("/foo/foo.txt"))).isSameAs(inDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/foo/bar/foo.txt"))).isSameAs(outDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/foo/bar/../foo.txt"))).isSameAs(inDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/bar/foo.txt"))).isSameAs(defaultDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/foo/bar/../.."))).isSameAs(defaultDelegate);
-  }
-
-  // Checks that files cannot be modified when the filesystem is created
-  // read-only, even if the delegate filesystems are read/write.
-  @Test
-  public void testModificationFlag() throws Exception {
-    unionfs =
-        new UnionFileSystem(
-            ImmutableMap.of(
-                PathFragment.create("/rw"), new XAttrInMemoryFs(BlazeClock.instance()),
-                PathFragment.create("/ro"),
-                    new XAttrInMemoryFs(BlazeClock.instance()) {
-                      @Override
-                      public boolean supportsModifications(Path path) {
-                        return false;
-                      }
-                    }),
-            defaultDelegate);
-    Path rwPath = unionfs.getPath("/rw/foo.txt");
-    Path roPath = unionfs.getPath("/ro/foo.txt");
-    assertThat(unionfs.supportsModifications(rwPath)).isTrue();
-    assertThat(unionfs.supportsModifications(roPath)).isFalse();
-  }
-
-  // Checks that roots of delegate filesystems are created outside of the
-  // delegate filesystems; i.e. they can be seen from the filesystem of the parent.
-  @Test
-  public void testDelegateRootDirectoryCreation() throws Exception {
-    Path foo = unionfs.getPath("/foo");
-    Path bar = unionfs.getPath("/bar");
-    Path out = unionfs.getPath("/out");
-    assertThat(unionfs.createDirectory(foo)).isTrue();
-    assertThat(unionfs.createDirectory(bar)).isTrue();
-    assertThat(unionfs.createDirectory(out)).isTrue();
-    Path outFile = unionfs.getPath("/out/in");
-    FileSystemUtils.writeContentAsLatin1(outFile, "Out");
-
-    // FileSystemTest.setUp() silently creates the test root on the filesystem...
-    Path testDirUnderRoot = unionfs.getPath(workingDir.asFragment().subFragment(0, 1));
-    assertThat(unionfs.getDirectoryEntries(unionfs.getPath("/")))
-        .containsExactly(
-            foo.getBaseName(),
-            bar.getBaseName(),
-            out.getBaseName(),
-            testDirUnderRoot.getBaseName());
-    assertThat(unionfs.getDirectoryEntries(out)).containsExactly(outFile.getBaseName());
-
-    assertThat(defaultDelegate).isSameAs(unionfs.getDelegate(foo));
-    assertThat(unionfs.adjustPath(foo, defaultDelegate).asFragment()).isEqualTo(foo.asFragment());
-    assertThat(defaultDelegate).isSameAs(unionfs.getDelegate(bar));
-    assertThat(outDelegate).isSameAs(unionfs.getDelegate(outFile));
-    assertThat(outDelegate).isSameAs(unionfs.getDelegate(out));
-
-    // As a fragment (i.e. without filesystem or root info), the path name should be preserved.
-    assertThat(unionfs.adjustPath(outFile, outDelegate).asFragment())
-        .isEqualTo(outFile.asFragment());
-  }
-
-  // Ensure that the right filesystem is still chosen when paths contain "..".
-  @Test
-  public void testDelegationOfUpLevelReferences() throws Exception {
-    assertThat(unionfs.getDelegate(unionfs.getPath("/in/../foo.txt"))).isSameAs(defaultDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/out/../in"))).isSameAs(inDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/out/../in/../out/foo.txt")))
-        .isSameAs(outDelegate);
-    assertThat(unionfs.getDelegate(unionfs.getPath("/in/./foo.txt"))).isSameAs(inDelegate);
-  }
-
-  // Basic *explicit* cross-filesystem symlink check.
-  // Note: This does not work implicitly yet, as the next test illustrates.
-  @Test
-  public void testCrossDeviceSymlinks() throws Exception {
-    assertThat(unionfs.createDirectory(unionfs.getPath("/out"))).isTrue();
-
-    // Create an "/in" directory directly on the output delegate to bypass the
-    // UnionFileSystem's mapping.
-    assertThat(inDelegate.getPath("/in").createDirectory()).isTrue();
-    try (OutputStream outStream = inDelegate.getPath("/in/bar.txt").getOutputStream()) {
-      outStream.write('i');
-    }
-
-    Path outFoo = unionfs.getPath("/out/foo");
-    unionfs.createSymbolicLink(outFoo, PathFragment.create("../in/bar.txt"));
-    assertThat(unionfs.stat(outFoo, false).isSymbolicLink()).isTrue();
-
-    try {
-      unionfs.stat(outFoo, true).isFile();
-      fail("Stat on cross-device symlink succeeded!");
-    } catch (FileNotFoundException expected) {
-      // OK
-    }
-
-    Path resolved = unionfs.resolveSymbolicLinks(outFoo);
-    assertThat(resolved.getFileSystem()).isSameAs(unionfs);
-    int barChar = -1;
-    try (InputStream barInput = resolved.getInputStream()) {
-      barChar = barInput.read();
-    }
-    assertThat(barChar).isEqualTo('i');
-  }
-
-  @Test
-  public void testNoDelegateLeakage() throws Exception {
-    assertThat(unionfs.getPath("/in/foo.txt").getFileSystem()).isSameAs(unionfs);
-    assertThat(unionfs.getPath("/in/foo/bar").getParentDirectory().getFileSystem())
-        .isSameAs(unionfs);
-    unionfs.createDirectory(unionfs.getPath("/out"));
-    unionfs.createDirectory(unionfs.getPath("/out/foo"));
-    unionfs.createDirectory(unionfs.getPath("/out/foo/bar"));
-  }
-
-  // Write using the VFS through a UnionFileSystem and check that the file can
-  // be read back in the same location using standard Java IO.
-  // There is a similar test in UnixFileSystem, but this is essential to ensure
-  // that paths aren't being remapped in some nasty way on the underlying FS.
-  @Test
-  public void testDelegateOperationsReflectOnLocalFilesystem() throws Exception {
-    unionfs =
-        new UnionFileSystem(
-            ImmutableMap.<PathFragment, FileSystem>of(
-                workingDir.getParentDirectory().asFragment(), new UnixFileSystem()),
-            defaultDelegate);
-    // This is a child of the current tmpdir, and doesn't exist on its own.
-    // It would be created in setup(), but of course, that didn't use a UnixFileSystem.
-    unionfs.createDirectory(workingDir);
-    Path testFile = unionfs.getPath(workingDir.getRelative("test_file").asFragment());
-    assertThat(testFile.asFragment().startsWith(workingDir.asFragment())).isTrue();
-    String testString = "This is a test file";
-    FileSystemUtils.writeContentAsLatin1(testFile, testString);
-    try {
-      assertThat(new String(FileSystemUtils.readContentAsLatin1(testFile))).isEqualTo(testString);
-    } finally {
-      testFile.delete();
-      assertThat(unionfs.delete(workingDir)).isTrue();
-    }
-  }
-
-  // Regression test for [UnionFS: Directory creation across mapping fails.]
-  @Test
-  public void testCreateParentsAcrossMapping() throws Exception {
-    unionfs =
-        new UnionFileSystem(
-            ImmutableMap.<PathFragment, FileSystem>of(PathFragment.create("/out/dir"), outDelegate),
-            defaultDelegate);
-    Path outDir = unionfs.getPath("/out/dir/biz/bang");
-    FileSystemUtils.createDirectoryAndParents(outDir);
-    assertThat(outDir.isDirectory()).isTrue();
-  }
-
-  private static class XAttrInMemoryFs extends InMemoryFileSystem {
-    public XAttrInMemoryFs(Clock clock) {
-      super(clock);
-    }
-
-    @Override
-    public byte[] getxattr(Path path, String name) {
-      assertThat(path.getFileSystem()).isSameAs(this);
-      return (name.equals(XATTR_KEY)) ? XATTR_VAL.getBytes(UTF_8) : null;
-    }
-  }
-}