// 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 org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.devtools.build.lib.vfs.FileSystem.NotASymlinkException; import org.junit.Before; import org.junit.Test; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collection; /** * This class handles the generic tests that any filesystem must pass. * *
Each filesystem-test should inherit from this class, thereby obtaining
* all the tests.
*/
public abstract class SymlinkAwareFileSystemTest extends FileSystemTest {
protected Path xLinkToFile;
protected Path xLinkToLinkToFile;
protected Path xLinkToDirectory;
protected Path xDanglingLink;
@Before
public final void createSymbolicLinks() throws Exception {
// % ls -lR
// -rw-rw-r-- xFile
// drwxrwxr-x xNonEmptyDirectory
// -rw-rw-r-- xNonEmptyDirectory/foo
// drwxrwxr-x xEmptyDirectory
// lrwxrwxr-x xLinkToFile -> xFile
// lrwxrwxr-x xLinkToDirectory -> xEmptyDirectory
// lrwxrwxr-x xLinkToLinkToFile -> xLinkToFile
// lrwxrwxr-x xDanglingLink -> xNothing
xLinkToFile = absolutize("xLinkToFile");
xLinkToLinkToFile = absolutize("xLinkToLinkToFile");
xLinkToDirectory = absolutize("xLinkToDirectory");
xDanglingLink = absolutize("xDanglingLink");
createSymbolicLink(xLinkToFile, xFile);
createSymbolicLink(xLinkToLinkToFile, xLinkToFile);
createSymbolicLink(xLinkToDirectory, xEmptyDirectory);
createSymbolicLink(xDanglingLink, xNothing);
}
@Test
public void testCreateLinkToFile() throws IOException {
Path newPath = xEmptyDirectory.getChild("new-file");
FileSystemUtils.createEmptyFile(newPath);
Path linkPath = xEmptyDirectory.getChild("some-link");
createSymbolicLink(linkPath, newPath);
assertTrue(linkPath.isSymbolicLink());
assertTrue(linkPath.isFile());
assertFalse(linkPath.isFile(Symlinks.NOFOLLOW));
assertTrue(linkPath.isFile(Symlinks.FOLLOW));
assertFalse(linkPath.isDirectory());
assertFalse(linkPath.isDirectory(Symlinks.NOFOLLOW));
assertFalse(linkPath.isDirectory(Symlinks.FOLLOW));
if (supportsSymlinks) {
assertEquals(newPath.toString().length(), linkPath.getFileSize(Symlinks.NOFOLLOW));
assertEquals(newPath.getFileSize(Symlinks.NOFOLLOW), linkPath.getFileSize());
}
assertThat(linkPath.getParentDirectory().getDirectoryEntries()).hasSize(2);
assertThat(linkPath.getParentDirectory().getDirectoryEntries()).containsExactly(newPath,
linkPath);
}
@Test
public void testCreateLinkToDirectory() throws IOException {
Path newPath = xEmptyDirectory.getChild("new-file");
newPath.createDirectory();
Path linkPath = xEmptyDirectory.getChild("some-link");
createSymbolicLink(linkPath, newPath);
assertTrue(linkPath.isSymbolicLink());
assertFalse(linkPath.isFile());
assertTrue(linkPath.isDirectory());
assertThat(linkPath.getParentDirectory().getDirectoryEntries()).hasSize(2);
assertThat(linkPath.getParentDirectory().
getDirectoryEntries()).containsExactly(newPath, linkPath);
}
@Test
public void testFileCanonicalPath() throws IOException {
Path newPath = absolutize("new-file");
FileSystemUtils.createEmptyFile(newPath);
newPath = newPath.resolveSymbolicLinks();
Path link1 = absolutize("some-link");
Path link2 = absolutize("some-link2");
createSymbolicLink(link1, newPath);
createSymbolicLink(link2, link1);
assertCanonicalPathsMatch(newPath, link1, link2);
}
@Test
public void testDirectoryCanonicalPath() throws IOException {
Path newPath = absolutize("new-folder");
newPath.createDirectory();
newPath = newPath.resolveSymbolicLinks();
Path newFile = newPath.getChild("file");
FileSystemUtils.createEmptyFile(newFile);
Path link1 = absolutize("some-link");
Path link2 = absolutize("some-link2");
createSymbolicLink(link1, newPath);
createSymbolicLink(link2, link1);
Path linkFile1 = link1.getChild("file");
Path linkFile2 = link2.getChild("file");
assertCanonicalPathsMatch(newFile, linkFile1, linkFile2);
}
private void assertCanonicalPathsMatch(Path newPath, Path link1, Path link2)
throws IOException {
assertEquals(newPath, link1.resolveSymbolicLinks());
assertEquals(newPath, link2.resolveSymbolicLinks());
}
//
// createDirectory
//
@Test
public void testCreateDirectoryWhereDanglingSymlinkAlreadyExists() {
try {
xDanglingLink.createDirectory();
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xDanglingLink + " (File exists)");
}
assertTrue(xDanglingLink.isSymbolicLink()); // still a symbolic link
assertFalse(xDanglingLink.isDirectory(Symlinks.FOLLOW)); // link still dangles
}
@Test
public void testCreateDirectoryWhereSymlinkAlreadyExists() {
try {
xLinkToDirectory.createDirectory();
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xLinkToDirectory + " (File exists)");
}
assertTrue(xLinkToDirectory.isSymbolicLink()); // still a symbolic link
assertTrue(xLinkToDirectory.isDirectory(Symlinks.FOLLOW)); // link still points to dir
}
// createSymbolicLink(PathFragment)
@Test
public void testCreateSymbolicLinkFromFragment() throws IOException {
String[] linkTargets = {
"foo",
"foo/bar",
".",
"..",
"../foo",
"../../foo",
"../../../../../../../../../../../../../../../../../../../../../foo",
"/foo",
"/foo/bar",
"/..",
"/foo/../bar",
};
Path linkPath = absolutize("link");
for (String linkTarget : linkTargets) {
PathFragment relative = new PathFragment(linkTarget);
linkPath.delete();
createSymbolicLink(linkPath, relative);
if (supportsSymlinks) {
assertEquals(linkTarget.length(), linkPath.getFileSize(Symlinks.NOFOLLOW));
assertEquals(relative, linkPath.readSymbolicLink());
}
}
}
@Test
public void testLinkToRootResolvesCorrectly() throws IOException {
Path rootPath = testFS.getPath("/");
try {
rootPath.getChild("testDir").createDirectory();
} catch (IOException e) {
// Do nothing. This is a real FS, and we don't have permission.
}
Path linkPath = absolutize("link");
createSymbolicLink(linkPath, rootPath);
// resolveSymbolicLinks requires an existing path:
try {
linkPath.getRelative("test").resolveSymbolicLinks();
fail();
} catch (FileNotFoundException e) { /* ok */ }
// The path may not be a symlink, neither on Darwin nor on Linux.
String nonLinkEntry = null;
for (Path p : testFS.getDirectoryEntries(rootPath)) {
if (!p.isSymbolicLink() && p.isDirectory()) {
nonLinkEntry = p.getBaseName();
break;
}
}
assertNotNull(nonLinkEntry);
Path rootChild = testFS.getPath("/" + nonLinkEntry);
assertEquals(rootChild, linkPath.getRelative(nonLinkEntry).resolveSymbolicLinks());
}
@Test
public void testLinkToFragmentContainingLinkResolvesCorrectly() throws IOException {
Path link1 = absolutize("link1");
PathFragment link1target = new PathFragment("link2/foo");
Path link2 = absolutize("link2");
Path link2target = xNonEmptyDirectory;
createSymbolicLink(link1, link1target); // ln -s link2/foo link1
createSymbolicLink(link2, link2target); // ln -s xNonEmptyDirectory link2
// link1 --> xNonEmptyDirectory/foo
assertEquals(link1.resolveSymbolicLinks(), link2target.getRelative("foo"));
}
//
// readSymbolicLink / resolveSymbolicLinks
//
@Test
public void testRecursiveSymbolicLink() throws IOException {
Path link = absolutize("recursive-link");
createSymbolicLink(link, link);
if (supportsSymlinks) {
try {
link.resolveSymbolicLinks();
fail();
} catch (IOException e) {
assertThat(e).hasMessage(link + " (Too many levels of symbolic links)");
}
}
}
@Test
public void testMutuallyRecursiveSymbolicLinks() throws IOException {
Path link1 = absolutize("link1");
Path link2 = absolutize("link2");
createSymbolicLink(link2, link1);
createSymbolicLink(link1, link2);
if (supportsSymlinks) {
try {
link1.resolveSymbolicLinks();
fail();
} catch (IOException e) {
assertThat(e).hasMessage(link1 + " (Too many levels of symbolic links)");
}
}
}
@Test
public void testResolveSymbolicLinksENOENT() {
if (supportsSymlinks) {
try {
xDanglingLink.resolveSymbolicLinks();
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xNothing + " (No such file or directory)");
}
}
}
@Test
public void testResolveSymbolicLinksENOTDIR() throws IOException {
if (supportsSymlinks) {
Path badLinkTarget = xFile.getChild("bad"); // parent is not a directory!
Path badLink = absolutize("badLink");
createSymbolicLink(badLink, badLinkTarget);
try {
badLink.resolveSymbolicLinks();
fail();
} catch (IOException e) {
// ok. Ideally we would assert "(Not a directory)" in the error
// message, but that would require yet another stat in the
// implementation.
}
}
}
@Test
public void testResolveSymbolicLinksWithUplevelRefs() throws IOException {
if (supportsSymlinks) {
// Create a series of links that refer to xFile as ./xFile,
// ./../foo/xFile, ./../../bar/foo/xFile, etc. They should all resolve
// to xFile.
Path ancestor = xFile;
String prefix = "./";
while ((ancestor = ancestor.getParentDirectory()) != null) {
xLinkToFile.delete();
createSymbolicLink(xLinkToFile, new PathFragment(prefix + xFile.relativeTo(ancestor)));
assertEquals(xFile, xLinkToFile.resolveSymbolicLinks());
prefix += "../";
}
}
}
@Test
public void testReadSymbolicLink() throws IOException {
if (supportsSymlinks) {
assertEquals(xNothing.toString(),
xDanglingLink.readSymbolicLink().toString());
}
assertEquals(xFile.toString(),
xLinkToFile.readSymbolicLink().toString());
assertEquals(xEmptyDirectory.toString(),
xLinkToDirectory.readSymbolicLink().toString());
try {
xFile.readSymbolicLink(); // not a link
fail();
} catch (NotASymlinkException e) {
assertThat(e).hasMessage(xFile.toString());
}
try {
xNothing.readSymbolicLink(); // nothing there
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xNothing + " (No such file or directory)");
}
}
@Test
public void testCannotCreateSymbolicLinkWithReadOnlyParent()
throws IOException {
xEmptyDirectory.setWritable(false);
Path xChildOfReadonlyDir = xEmptyDirectory.getChild("x");
if (supportsSymlinks) {
try {
xChildOfReadonlyDir.createSymbolicLink(xNothing);
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xChildOfReadonlyDir + " (Permission denied)");
}
}
}
//
// createSymbolicLink
//
@Test
public void testCanCreateDanglingLink() throws IOException {
Path newPath = absolutize("non-existing-dir/new-file");
Path someLink = absolutize("dangling-link");
createSymbolicLink(someLink, newPath);
assertTrue(someLink.isSymbolicLink());
assertTrue(someLink.exists(Symlinks.NOFOLLOW)); // the link itself exists
assertFalse(someLink.exists()); // ...but the referent doesn't
if (supportsSymlinks) {
try {
someLink.resolveSymbolicLinks();
} catch (FileNotFoundException e) {
assertThat(e).hasMessage(newPath.getParentDirectory() + " (No such file or directory)");
}
}
}
@Test
public void testCannotCreateSymbolicLinkWithoutParent() throws IOException {
Path xChildOfMissingDir = xNothing.getChild("x");
if (supportsSymlinks) {
try {
xChildOfMissingDir.createSymbolicLink(xFile);
fail();
} catch (FileNotFoundException e) {
assertThat(e.getMessage()).endsWith(" (No such file or directory)");
}
}
}
@Test
public void testCreateSymbolicLinkWhereNothingExists() throws IOException {
createSymbolicLink(xNothing, xFile);
assertTrue(xNothing.isSymbolicLink());
}
@Test
public void testCreateSymbolicLinkWhereDirectoryAlreadyExists() {
try {
createSymbolicLink(xEmptyDirectory, xFile);
fail();
} catch (IOException e) { // => couldn't be created
assertThat(e).hasMessage(xEmptyDirectory + " (File exists)");
}
assertTrue(xEmptyDirectory.isDirectory(Symlinks.NOFOLLOW));
}
@Test
public void testCreateSymbolicLinkWhereFileAlreadyExists() {
try {
createSymbolicLink(xFile, xEmptyDirectory);
fail();
} catch (IOException e) { // => couldn't be created
assertThat(e).hasMessage(xFile + " (File exists)");
}
assertTrue(xFile.isFile(Symlinks.NOFOLLOW));
}
@Test
public void testCreateSymbolicLinkWhereDanglingSymlinkAlreadyExists() {
try {
createSymbolicLink(xDanglingLink, xFile);
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xDanglingLink + " (File exists)");
}
assertTrue(xDanglingLink.isSymbolicLink()); // still a symbolic link
assertFalse(xDanglingLink.isDirectory()); // link still dangles
}
@Test
public void testCreateSymbolicLinkWhereSymlinkAlreadyExists() {
try {
createSymbolicLink(xLinkToDirectory, xNothing);
fail();
} catch (IOException e) {
assertThat(e).hasMessage(xLinkToDirectory + " (File exists)");
}
assertTrue(xLinkToDirectory.isSymbolicLink()); // still a symbolic link
assertTrue(xLinkToDirectory.isDirectory()); // link still points to dir
}
@Test
public void testDeleteLink() throws IOException {
Path newPath = xEmptyDirectory.getChild("new-file");
Path someLink = xEmptyDirectory.getChild("a-link");
FileSystemUtils.createEmptyFile(newPath);
createSymbolicLink(someLink, newPath);
assertEquals(xEmptyDirectory.getDirectoryEntries().size(), 2);
assertTrue(someLink.delete());
assertEquals(xEmptyDirectory.getDirectoryEntries().size(), 1);
assertThat(xEmptyDirectory.getDirectoryEntries()).containsExactly(newPath);
}
// Testing the links
@Test
public void testLinkFollowedToDirectory() throws IOException {
Path theDirectory = absolutize("foo/");
assertTrue(theDirectory.createDirectory());
Path newPath1 = absolutize("foo/new-file-1");
Path newPath2 = absolutize("foo/new-file-2");
Path newPath3 = absolutize("foo/new-file-3");
FileSystemUtils.createEmptyFile(newPath1);
FileSystemUtils.createEmptyFile(newPath2);
FileSystemUtils.createEmptyFile(newPath3);
Path linkPath = absolutize("link");
createSymbolicLink(linkPath, theDirectory);
Path resultPath1 = absolutize("link/new-file-1");
Path resultPath2 = absolutize("link/new-file-2");
Path resultPath3 = absolutize("link/new-file-3");
assertThat(linkPath.getDirectoryEntries()).containsExactly(resultPath1, resultPath2,
resultPath3);
}
@Test
public void testDanglingLinkIsNoFile() throws IOException {
Path newPath1 = absolutize("new-file-1");
Path newPath2 = absolutize("new-file-2");
FileSystemUtils.createEmptyFile(newPath1);
assertTrue(newPath2.createDirectory());
Path linkPath1 = absolutize("link1");
Path linkPath2 = absolutize("link2");
createSymbolicLink(linkPath1, newPath1);
createSymbolicLink(linkPath2, newPath2);
newPath1.delete();
newPath2.delete();
assertFalse(linkPath1.isFile());
assertFalse(linkPath2.isDirectory());
}
@Test
public void testWriteOnLinkChangesFile() throws IOException {
Path testFile = absolutize("test-file");
FileSystemUtils.createEmptyFile(testFile);
String testData = "abc19";
Path testLink = absolutize("a-link");
createSymbolicLink(testLink, testFile);
FileSystemUtils.writeContentAsLatin1(testLink, testData);
String resultData =
new String(FileSystemUtils.readContentAsLatin1(testFile));
assertEquals(testData,resultData);
}
//
// Symlink tests:
//
@Test
public void testExistsWithSymlinks() throws IOException {
Path a = absolutize("a");
Path b = absolutize("b");
FileSystemUtils.createEmptyFile(b);
createSymbolicLink(a, b); // ln -sf "b" "a"
assertTrue(a.exists()); // = exists(FOLLOW)
assertTrue(b.exists()); // = exists(FOLLOW)
assertTrue(a.exists(Symlinks.FOLLOW));
assertTrue(b.exists(Symlinks.FOLLOW));
assertTrue(a.exists(Symlinks.NOFOLLOW));
assertTrue(b.exists(Symlinks.NOFOLLOW));
b.delete(); // "a" is now a dangling link
assertFalse(a.exists()); // = exists(FOLLOW)
assertFalse(b.exists()); // = exists(FOLLOW)
assertFalse(a.exists(Symlinks.FOLLOW));
assertFalse(b.exists(Symlinks.FOLLOW));
assertTrue(a.exists(Symlinks.NOFOLLOW)); // symlink still exists
assertFalse(b.exists(Symlinks.NOFOLLOW));
}
@Test
public void testIsDirectoryWithSymlinks() throws IOException {
Path a = absolutize("a");
Path b = absolutize("b");
b.createDirectory();
createSymbolicLink(a, b); // ln -sf "b" "a"
assertTrue(a.isDirectory()); // = isDirectory(FOLLOW)
assertTrue(b.isDirectory()); // = isDirectory(FOLLOW)
assertTrue(a.isDirectory(Symlinks.FOLLOW));
assertTrue(b.isDirectory(Symlinks.FOLLOW));
assertFalse(a.isDirectory(Symlinks.NOFOLLOW)); // it's a link!
assertTrue(b.isDirectory(Symlinks.NOFOLLOW));
b.delete(); // "a" is now a dangling link
assertFalse(a.isDirectory()); // = isDirectory(FOLLOW)
assertFalse(b.isDirectory()); // = isDirectory(FOLLOW)
assertFalse(a.isDirectory(Symlinks.FOLLOW));
assertFalse(b.isDirectory(Symlinks.FOLLOW));
assertFalse(a.isDirectory(Symlinks.NOFOLLOW));
assertFalse(b.isDirectory(Symlinks.NOFOLLOW));
}
@Test
public void testIsFileWithSymlinks() throws IOException {
Path a = absolutize("a");
Path b = absolutize("b");
FileSystemUtils.createEmptyFile(b);
createSymbolicLink(a, b); // ln -sf "b" "a"
assertTrue(a.isFile()); // = isFile(FOLLOW)
assertTrue(b.isFile()); // = isFile(FOLLOW)
assertTrue(a.isFile(Symlinks.FOLLOW));
assertTrue(b.isFile(Symlinks.FOLLOW));
assertFalse(a.isFile(Symlinks.NOFOLLOW)); // it's a link!
assertTrue(b.isFile(Symlinks.NOFOLLOW));
b.delete(); // "a" is now a dangling link
assertFalse(a.isFile()); // = isFile()
assertFalse(b.isFile()); // = isFile()
assertFalse(a.isFile());
assertFalse(b.isFile());
assertFalse(a.isFile(Symlinks.NOFOLLOW));
assertFalse(b.isFile(Symlinks.NOFOLLOW));
}
@Test
public void testGetDirectoryEntriesOnLinkToDirectory() throws Exception {
Path fooAlias = xNothing.getChild("foo");
createSymbolicLink(xNothing, xNonEmptyDirectory);
Collection