blob: 7f28efb8efb9d925792e4f17997226fc36d92fc8 [file] [log] [blame]
// Copyright 2021 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.remote;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import build.bazel.remote.execution.v2.ActionResult;
import build.bazel.remote.execution.v2.Digest;
import build.bazel.remote.execution.v2.Directory;
import build.bazel.remote.execution.v2.DirectoryNode;
import build.bazel.remote.execution.v2.FileNode;
import build.bazel.remote.execution.v2.SymlinkNode;
import build.bazel.remote.execution.v2.Tree;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.clock.JavaClock;
import com.google.devtools.build.lib.remote.common.RemotePathResolver;
import com.google.devtools.build.lib.remote.util.DigestUtil;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.FileStatus;
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.Symlinks;
import com.google.devtools.build.lib.vfs.SyscallCache;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link UploadManifest}. */
@RunWith(JUnit4.class)
public class UploadManifestTest {
private static final FileStatus SPECIAL_FILE_STATUS =
new FileStatus() {
@Override
public boolean isFile() {
return true;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isSymbolicLink() {
return false;
}
@Override
public boolean isSpecialFile() {
return true;
}
@Override
public long getSize() {
return 0;
}
@Override
public long getLastModifiedTime() {
return 0;
}
@Override
public long getLastChangeTime() {
return 0;
}
@Override
public long getNodeId() {
return 0;
}
};
private static final FileStatus DIR_FILE_STATUS =
new FileStatus() {
@Override
public boolean isFile() {
return false;
}
@Override
public boolean isDirectory() {
return true;
}
@Override
public boolean isSymbolicLink() {
return false;
}
@Override
public boolean isSpecialFile() {
return false;
}
@Override
public long getSize() {
return 0;
}
@Override
public long getLastModifiedTime() {
return 0;
}
@Override
public long getLastChangeTime() {
return 0;
}
@Override
public long getNodeId() {
return 0;
}
};
private static final FileStatus SYMLINK_FILE_STATUS =
new FileStatus() {
@Override
public boolean isFile() {
return false;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isSymbolicLink() {
return true;
}
@Override
public boolean isSpecialFile() {
return false;
}
@Override
public long getSize() {
return 0;
}
@Override
public long getLastModifiedTime() {
return 0;
}
@Override
public long getLastChangeTime() {
return 0;
}
@Override
public long getNodeId() {
return 0;
}
};
private final DigestUtil digestUtil =
new DigestUtil(SyscallCache.NO_CACHE, DigestHashFunction.SHA256);
private Path execRoot;
private RemotePathResolver remotePathResolver;
@Before
public final void setUp() throws Exception {
FileSystem fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
execRoot = fs.getPath("/execroot");
execRoot.createDirectoryAndParents();
remotePathResolver = new RemotePathResolver.DefaultRemotePathResolver(execRoot);
}
@Test
public void actionResult_followSymlinks_absoluteFileSymlinkAsFile() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
Digest digest = digestUtil.compute(target);
assertThat(um.getDigestToFile()).containsExactly(digest, link);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputFilesBuilder().setPath("link").setDigest(digest).setIsExecutable(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_absoluteDirectorySymlinkAsDirectory() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path foo = execRoot.getRelative("dir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("link");
link.createSymbolicLink(dir);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
Digest digest = digestUtil.compute(foo);
assertThat(um.getDigestToFile()).containsExactly(digest, execRoot.getRelative("link/foo"));
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("foo").setDigest(digest)))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("link")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_absoluteFileSymlinkAsFile() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
Digest digest = digestUtil.compute(target);
assertThat(um.getDigestToFile()).containsExactly(digest, link);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputFilesBuilder().setPath("link").setDigest(digest).setIsExecutable(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_absoluteDirectorySymlinkAsDirectory() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path foo = execRoot.getRelative("dir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("link");
link.createSymbolicLink(dir);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
Digest digest = digestUtil.compute(foo);
assertThat(um.getDigestToFile()).containsExactly(digest, execRoot.getRelative("link/foo"));
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("foo").setDigest(digest)))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("link")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_relativeFileSymlinkAsFile() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
link.createSymbolicLink(target.relativeTo(execRoot));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
Digest digest = digestUtil.compute(target);
assertThat(um.getDigestToFile()).containsExactly(digest, link);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputFilesBuilder().setPath("link").setDigest(digest).setIsExecutable(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_relativeDirectorySymlinkAsDirectory() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path foo = execRoot.getRelative("dir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("link");
link.createSymbolicLink(dir.relativeTo(execRoot));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
Digest digest = digestUtil.compute(foo);
assertThat(um.getDigestToFile()).containsExactly(digest, execRoot.getRelative("link/foo"));
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("foo").setDigest(digest)))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("link")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_relativeFileSymlinkAsSymlink() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
link.createSymbolicLink(target.relativeTo(execRoot));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
assertThat(um.getDigestToFile()).isEmpty();
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputFileSymlinksBuilder().setPath("link").setTarget("target");
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_relativeDirectorySymlinkAsSymlink() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path file = execRoot.getRelative("dir/foo");
FileSystemUtils.writeContent(file, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("link");
link.createSymbolicLink(dir.relativeTo(execRoot));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
assertThat(um.getDigestToFile()).isEmpty();
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputDirectorySymlinksBuilder().setPath("link").setTarget("dir");
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_noAllowDanglingSymlinks_absoluteDanglingSymlinkError()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
IOException e = assertThrows(IOException.class, () -> um.addFiles(ImmutableList.of(link)));
assertThat(e).hasMessageThat().contains("dangling");
assertThat(e).hasMessageThat().contains("/execroot/link");
assertThat(e).hasMessageThat().contains("target");
}
@Test
public void
actionResult_noFollowSymlinks_allowDanglingSymlinks_noAllowAbsoluteSymlinks_absoluteDanglingSymlinkAsError()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ true,
/*allowAbsoluteSymlinks=*/ false);
IOException e = assertThrows(IOException.class, () -> um.addFiles(ImmutableList.of(link)));
assertThat(e).hasMessageThat().contains("absolute");
assertThat(e).hasMessageThat().contains("/execroot/link");
assertThat(e).hasMessageThat().contains("target");
}
@Test
public void
actionResult_noFollowSymlinks_allowDanglingSymlinks_allowAbsoluteSymlinks_absoluteDanglingSymlinkAsSymlink()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ true,
/*allowAbsoluteSymlinks=*/ true);
um.addFiles(ImmutableList.of(link));
assertThat(um.getDigestToFile()).isEmpty();
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputFileSymlinksBuilder().setPath("link").setTarget("/execroot/target");
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_noAllowDanglingSymlinks_relativeDanglingSymlinkError()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
link.createSymbolicLink(target.relativeTo(link.getParentDirectory()));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
IOException e = assertThrows(IOException.class, () -> um.addFiles(ImmutableList.of(link)));
assertThat(e).hasMessageThat().contains("dangling");
assertThat(e).hasMessageThat().contains("/execroot/link");
assertThat(e).hasMessageThat().contains("target");
}
@Test
public void actionResult_noFollowSymlinks_allowDanglingSymlinks_relativeDanglingSymlinkAsSymlink()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path link = execRoot.getRelative("link");
Path target = execRoot.getRelative("target");
link.createSymbolicLink(target.relativeTo(link.getParentDirectory()));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ true,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(link));
assertThat(um.getDigestToFile()).isEmpty();
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult.addOutputFileSymlinksBuilder().setPath("link").setTarget("target");
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_absoluteFileSymlinkInDirectoryAsFile() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
Digest digest = digestUtil.compute(target);
assertThat(um.getDigestToFile()).containsExactly(digest, link);
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("link").setDigest(digest)))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_absoluteDirectorySymlinkInDirectoryAsDirectory()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path bardir = execRoot.getRelative("bardir");
bardir.createDirectory();
Path foo = execRoot.getRelative("bardir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(bardir);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
Digest digest = digestUtil.compute(foo);
assertThat(um.getDigestToFile()).containsExactly(digest, execRoot.getRelative("dir/link/foo"));
Directory barDir =
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("foo").setDigest(digest))
.build();
Digest barDigest = digestUtil.compute(barDir);
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addDirectories(
DirectoryNode.newBuilder().setName("link").setDigest(barDigest)))
.addChildren(barDir)
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_absoluteFileSymlinkInDirectoryAsFile()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
Digest digest = digestUtil.compute(target);
assertThat(um.getDigestToFile()).containsExactly(digest, link);
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("link").setDigest(digest)))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_absoluteDirectorySymlinkInDirectoryAsDirectory()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path bardir = execRoot.getRelative("bardir");
bardir.createDirectory();
Path foo = execRoot.getRelative("bardir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(bardir);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
Digest digest = digestUtil.compute(foo);
assertThat(um.getDigestToFile()).containsExactly(digest, execRoot.getRelative("dir/link/foo"));
Directory barDir =
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("foo").setDigest(digest))
.build();
Digest barDigest = digestUtil.compute(barDir);
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addDirectories(
DirectoryNode.newBuilder().setName("link").setDigest(barDigest)))
.addChildren(barDir)
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_relativeFileSymlinkInDirectoryAsFile() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(PathFragment.create("../target"));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
Digest digest = digestUtil.compute(target);
assertThat(um.getDigestToFile()).containsExactly(digest, link);
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("link").setDigest(digest)))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_followSymlinks_relativeDirectorySymlinkInDirectoryAsDirectory()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path bardir = execRoot.getRelative("bardir");
bardir.createDirectory();
Path foo = execRoot.getRelative("bardir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(PathFragment.create("../bardir"));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
Digest digest = digestUtil.compute(foo);
assertThat(um.getDigestToFile()).containsExactly(digest, execRoot.getRelative("dir/link/foo"));
Directory barDir =
Directory.newBuilder()
.addFiles(FileNode.newBuilder().setName("foo").setDigest(digest))
.build();
Digest barDigest = digestUtil.compute(barDir);
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addDirectories(
DirectoryNode.newBuilder().setName("link").setDigest(barDigest)))
.addChildren(barDir)
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_relativeFileSymlinkInDirectoryAsSymlink()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("target");
FileSystemUtils.writeContent(target, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(PathFragment.create("../target"));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
assertThat(um.getDigestToFile()).isEmpty();
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addSymlinks(SymlinkNode.newBuilder().setName("link").setTarget("../target")))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void actionResult_noFollowSymlinks_relativeDirectorySymlinkInDirectoryAsSymlink()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path bardir = execRoot.getRelative("bardir");
bardir.createDirectory();
Path foo = execRoot.getRelative("bardir/foo");
FileSystemUtils.writeContent(foo, new byte[] {1, 2, 3, 4, 5});
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(PathFragment.create("../bardir"));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
assertThat(um.getDigestToFile()).isEmpty();
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addSymlinks(SymlinkNode.newBuilder().setName("link").setTarget("../bardir")))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void
actionResult_noFollowSymlinks_noAllowDanglingSymlinks_absoluteDanglingSymlinkInDirectory()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("target");
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
IOException e = assertThrows(IOException.class, () -> um.addFiles(ImmutableList.of(dir)));
assertThat(e).hasMessageThat().contains("dangling");
assertThat(e).hasMessageThat().contains("/execroot/dir/link");
assertThat(e).hasMessageThat().contains("/execroot/target");
}
@Test
public void
actionResult_noFollowSymlinks_allowDanglingSymlinks_absoluteDanglingSymlinkInDirectory()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("target");
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(target);
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ true,
/*allowAbsoluteSymlinks=*/ false);
IOException e = assertThrows(IOException.class, () -> um.addFiles(ImmutableList.of(dir)));
assertThat(e).hasMessageThat().contains("dangling");
assertThat(e).hasMessageThat().contains("/execroot/dir/link");
assertThat(e).hasMessageThat().contains("/execroot/target");
}
@Test
public void
actionResult_noFollowSymlinks_noAllowDanglingSymlinks_relativeDanglingSymlinkInDirectoryAsSymlink()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("dir/target");
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(target.relativeTo(link.getParentDirectory()));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
assertThat(um.getDigestToFile()).isEmpty();
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addSymlinks(SymlinkNode.newBuilder().setName("link").setTarget("target")))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
@Test
public void
actionResult_noFollowSymlinks_allowDanglingSymlinks_relativeDanglingSymlinkInDirectoryAsSymlink()
throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
Path target = execRoot.getRelative("dir/target");
Path link = execRoot.getRelative("dir/link");
link.createSymbolicLink(target.relativeTo(link.getParentDirectory()));
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ true,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
assertThat(um.getDigestToFile()).isEmpty();
Tree tree =
Tree.newBuilder()
.setRoot(
Directory.newBuilder()
.addSymlinks(SymlinkNode.newBuilder().setName("link").setTarget("target")))
.build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
// Tests to verify that files with an unsupported type (collectively, "special files") are
// rejected. We must use mocks since Bazel's filesystems don't support their creation.
@Test
public void actionResult_noFollowSymlinks_specialFileError() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = createDirectoryWithSpecialFile("dir", "special");
Path special = dir.getRelative("special");
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
UserExecException e =
assertThrows(UserExecException.class, () -> um.addFiles(ImmutableList.of(special)));
assertThat(e).hasMessageThat().contains("special file");
assertThat(e).hasMessageThat().contains("dir/special");
}
@Test
public void actionResult_followSymlinks_specialFileSymlinkError() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = createDirectoryWithSymlinkToSpecialFile("dir", "link", "special");
Path link = dir.getRelative("link");
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
UserExecException e =
assertThrows(UserExecException.class, () -> um.addFiles(ImmutableList.of(link)));
assertThat(e).hasMessageThat().contains("special file");
assertThat(e).hasMessageThat().contains("dir/link");
}
@Test
public void actionResult_noFollowSymlinks_specialFileInDirectoryError() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = createDirectoryWithSpecialFile("dir", "special");
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
UserExecException e =
assertThrows(UserExecException.class, () -> um.addFiles(ImmutableList.of(dir)));
assertThat(e).hasMessageThat().contains("special file");
assertThat(e).hasMessageThat().contains("dir/special");
}
@Test
public void actionResult_followSymlinks_specialFileSymlinkInDirectoryError() throws Exception {
ActionResult.Builder result = ActionResult.newBuilder();
Path dir = createDirectoryWithSymlinkToSpecialFile("dir", "link", "special");
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ true,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
UserExecException e =
assertThrows(UserExecException.class, () -> um.addFiles(ImmutableList.of(dir)));
assertThat(e).hasMessageThat().contains("special file");
assertThat(e).hasMessageThat().contains("dir/link");
}
@Test
public void actionResult_topologicallySortedAndDeduplicatedTree() throws Exception {
// Create 5^3 identical files named "dir/%d/%d/%d/file".
Path dir = execRoot.getRelative("dir");
dir.createDirectory();
byte[] fileContents = new byte[] {1, 2, 3, 4, 5};
final int childrenPerDirectory = 5;
for (int a = 0; a < childrenPerDirectory; a++) {
Path pathA = dir.getRelative(Integer.toString(a));
pathA.createDirectory();
for (int b = 0; b < childrenPerDirectory; b++) {
Path pathB = pathA.getRelative(Integer.toString(b));
pathB.createDirectory();
for (int c = 0; c < childrenPerDirectory; c++) {
Path pathC = pathB.getRelative(Integer.toString(c));
pathC.createDirectory();
Path file = pathC.getRelative("file");
FileSystemUtils.writeContent(file, fileContents);
}
}
}
ActionResult.Builder result = ActionResult.newBuilder();
UploadManifest um =
new UploadManifest(
digestUtil,
remotePathResolver,
result,
/*followSymlinks=*/ false,
/*allowDanglingSymlinks=*/ false,
/*allowAbsoluteSymlinks=*/ false);
um.addFiles(ImmutableList.of(dir));
// Even though we constructed 1 + 5 + 5^2 + 5^3 directories, the resulting
// Tree message should only contain four unique instances. The directories
// should also be topologically sorted.
List<Directory> children = new ArrayList<>();
Directory root =
Directory.newBuilder()
.addFiles(
FileNode.newBuilder().setName("file").setDigest(digestUtil.compute(fileContents)))
.build();
for (int depth = 0; depth < 3; depth++) {
Directory.Builder b = Directory.newBuilder();
Digest parentDigest = digestUtil.compute(root.toByteArray());
for (int i = 0; i < childrenPerDirectory; i++) {
b.addDirectories(
DirectoryNode.newBuilder().setName(Integer.toString(i)).setDigest(parentDigest));
}
children.add(0, root);
root = b.build();
}
Tree tree = Tree.newBuilder().setRoot(root).addAllChildren(children).build();
Digest treeDigest = digestUtil.compute(tree);
ActionResult.Builder expectedResult = ActionResult.newBuilder();
expectedResult
.addOutputDirectoriesBuilder()
.setPath("dir")
.setTreeDigest(treeDigest)
.setIsTopologicallySorted(true);
assertThat(result.build()).isEqualTo(expectedResult.build());
}
private Path createSpecialFile(String execPath) throws IOException {
Path special = mock(Path.class);
when(special.statIfFound(Symlinks.NOFOLLOW)).thenReturn(SPECIAL_FILE_STATUS);
when(special.relativeTo(execRoot))
.thenReturn(execRoot.getRelative(execPath).relativeTo(execRoot));
return special;
}
private Path createSymlinkToSpecialFile(String execPath, String target) throws IOException {
Path link = mock(Path.class);
when(link.statIfFound(Symlinks.NOFOLLOW)).thenReturn(SYMLINK_FILE_STATUS);
when(link.statIfFound(Symlinks.FOLLOW)).thenReturn(SPECIAL_FILE_STATUS);
when(link.relativeTo(execRoot)).thenReturn(execRoot.getRelative(execPath).relativeTo(execRoot));
when(link.readSymbolicLink()).thenReturn(PathFragment.create(target));
return link;
}
private Path createDirectoryWithSpecialFile(String dirExecPath, String specialName)
throws IOException {
Path special = createSpecialFile(dirExecPath + "/" + specialName);
Path dir = mock(Path.class);
when(dir.statIfFound(Symlinks.NOFOLLOW)).thenReturn(DIR_FILE_STATUS);
when(dir.readdir(Symlinks.NOFOLLOW))
.thenReturn(ImmutableList.of(new Dirent(specialName, Dirent.Type.UNKNOWN)));
when(dir.getRelative(specialName)).thenReturn(special);
return dir;
}
private Path createDirectoryWithSymlinkToSpecialFile(
String dirExecPath, String linkName, String specialName) throws IOException {
Path special = createSpecialFile(dirExecPath + "/" + specialName);
Path link = createSymlinkToSpecialFile(dirExecPath + "/" + linkName, specialName);
Path dir = mock(Path.class);
when(dir.statIfFound(Symlinks.NOFOLLOW)).thenReturn(DIR_FILE_STATUS);
when(dir.readdir(Symlinks.NOFOLLOW))
.thenReturn(
ImmutableList.of(
new Dirent(linkName, Dirent.Type.SYMLINK),
new Dirent(specialName, Dirent.Type.UNKNOWN)));
when(dir.relativeTo(execRoot))
.thenReturn(execRoot.getRelative(dirExecPath).relativeTo(execRoot));
when(dir.getRelative(linkName)).thenReturn(link);
when(dir.getRelative(specialName)).thenReturn(special);
return dir;
}
}