blob: 0941f8072ee45f4ef891215afef25b0c230d6dd7 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.io;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.io.ProjectFilesystem.CopySourceMode;
import com.facebook.buck.testutil.WatchEvents;
import com.facebook.buck.testutil.integration.ZipInspector;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Set;
/** Unit test for {@link com.facebook.buck.io.ProjectFilesystem}. */
public class ProjectFilesystemTest {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
private ProjectFilesystem filesystem;
@Before
public void setUp() {
filesystem = new ProjectFilesystem(tmp.getRoot().toPath());
}
@Test
public void testIsFile() throws IOException {
tmp.newFolder("foo");
tmp.newFile("foo/bar.txt");
assertTrue(filesystem.isFile(Paths.get("foo/bar.txt")));
assertFalse(filesystem.isFile(Paths.get("i_do_not_exist")));
assertFalse(
"foo/ is a directory, but not an ordinary file",
filesystem.isFile(Paths.get("foo")));
}
@Test
public void testIsDirectory() throws IOException {
File dir = tmp.newFolder("src");
File file = tmp.newFile("BUCK");
assertTrue(filesystem.isDirectory(Paths.get(dir.getName())));
assertFalse(filesystem.isDirectory(Paths.get(file.getName())));
}
@Test
public void testMkdirsCanCreateNestedFolders() throws IOException {
filesystem.mkdirs(new File("foo/bar/baz").toPath());
assertTrue(new File(tmp.getRoot(), "foo/bar/baz").isDirectory());
}
@Test
public void testCreateParentDirs() throws IOException {
Path pathRelativeToProjectRoot = Paths.get("foo/bar/baz.txt");
filesystem.createParentDirs(pathRelativeToProjectRoot);
assertTrue(new File(tmp.getRoot(), "foo").isDirectory());
assertTrue(new File(tmp.getRoot(), "foo/bar").isDirectory());
assertFalse(
"createParentDirs() should create directories, but not the leaf/file part of the path.",
new File(tmp.getRoot(), "foo/bar/baz.txt").exists());
}
@Test(expected = NullPointerException.class)
public void testReadFirstLineRejectsNullString() {
filesystem.readFirstLine(/* pathRelativeToProjectRoot */ (String) null);
}
@Test(expected = NullPointerException.class)
public void testReadFirstLineRejectsNullPath() {
filesystem.readFirstLine(/* pathRelativeToProjectRoot */ (Path) null);
}
@Test
public void testReadFirstLineToleratesNonExistentFile() {
assertEquals(Optional.absent(), filesystem.readFirstLine("foo.txt"));
}
@Test
public void testReadFirstLineWithEmptyFile() throws IOException {
File emptyFile = tmp.newFile("foo.txt");
Files.write(new byte[0], emptyFile);
assertTrue(emptyFile.isFile());
assertEquals(Optional.absent(), filesystem.readFirstLine("foo.txt"));
}
@Test
public void testReadFirstLineFromMultiLineFile() throws IOException {
File multiLineFile = tmp.newFile("foo.txt");
Files.write("foo\nbar\nbaz\n", multiLineFile, Charsets.UTF_8);
assertEquals(Optional.of("foo"), filesystem.readFirstLine("foo.txt"));
}
@Test
public void getReaderIfFileExists() throws IOException {
File file = tmp.newFile("foo.txt");
Files.write("fooooo\nbar\nbaz\n", file, Charsets.UTF_8);
assertEquals(
"fooooo\nbar\nbaz\n",
CharStreams.toString(filesystem.getReaderIfFileExists(Paths.get("foo.txt")).get()));
}
@Test
public void getReaderIfFileExistsNoFile() throws IOException {
assertEquals(Optional.absent(), filesystem.getReaderIfFileExists(Paths.get("foo.txt")));
}
@Test
public void testGetFileSize() throws IOException {
File wordsFile = tmp.newFile("words.txt");
String content = "Here\nare\nsome\nwords.\n";
Files.write(content, wordsFile, Charsets.UTF_8);
assertEquals(content.length(), filesystem.getFileSize(Paths.get("words.txt")));
}
@Test(expected = IOException.class)
public void testGetFileSizeThrowsForNonExistentFile() throws IOException {
filesystem.getFileSize(Paths.get("words.txt"));
}
@Test
public void testWriteLinesToPath() throws IOException {
Iterable<String> lines = ImmutableList.of("foo", "bar", "baz");
filesystem.writeLinesToPath(lines, Paths.get("lines.txt"));
String contents = Files.toString(new File(tmp.getRoot(), "lines.txt"), Charsets.UTF_8);
assertEquals("foo\nbar\nbaz\n", contents);
}
@Test
public void testWriteBytesToPath() throws IOException {
String content = "Hello, World!";
byte[] bytes = content.getBytes();
filesystem.writeBytesToPath(bytes, Paths.get("hello.txt"));
assertEquals(
content,
Files.toString(new File(tmp.getRoot(), "hello.txt"), Charsets.UTF_8));
}
@Test
public void testCopyToPath() throws IOException {
InputStream inputStream = new ByteArrayInputStream("Hello, world!".getBytes());
filesystem.copyToPath(inputStream, Paths.get("bytes.txt"));
assertEquals(
"The bytes on disk should match those from the InputStream.",
"Hello, world!",
Files.toString(new File(tmp.getRoot(), "bytes.txt"), Charsets.UTF_8));
}
@Test
public void testCopyToPathWithOptions() throws IOException {
InputStream inputStream = new ByteArrayInputStream("hello!".getBytes());
filesystem.copyToPath(inputStream, Paths.get("replace_me.txt"));
inputStream = new ByteArrayInputStream("hello again!".getBytes());
filesystem.copyToPath(
inputStream,
Paths.get("replace_me.txt"),
StandardCopyOption.REPLACE_EXISTING);
assertEquals(
"The bytes on disk should match those from the second InputStream.",
"hello again!",
Files.toString(new File(tmp.getRoot(), "replace_me.txt"), Charsets.UTF_8));
}
@Test
public void testCopyFolder() throws IOException {
// Build up a directory of dummy files.
tmp.newFolder("src");
tmp.newFolder("src/com");
tmp.newFolder("src/com/example");
tmp.newFolder("src/com/example/foo");
tmp.newFile("src/com/example/foo/Foo.java");
tmp.newFile("src/com/example/foo/package.html");
tmp.newFolder("src/com/example/bar");
tmp.newFile("src/com/example/bar/Bar.java");
tmp.newFile("src/com/example/bar/package.html");
// Copy the contents of src/ to dest/.
tmp.newFolder("dest");
filesystem.copyFolder(Paths.get("src"), Paths.get("dest"));
assertTrue(new File(tmp.getRoot(), "dest/com/example/foo/Foo.java").exists());
assertTrue(new File(tmp.getRoot(), "dest/com/example/foo/package.html").exists());
assertTrue(new File(tmp.getRoot(), "dest/com/example/bar/Bar.java").exists());
assertTrue(new File(tmp.getRoot(), "dest/com/example/bar/package.html").exists());
}
@Test
public void testCopyFolderAndContents() throws IOException {
// Build up a directory of dummy files.
tmp.newFolder("src");
tmp.newFolder("src/com");
tmp.newFolder("src/com/example");
tmp.newFolder("src/com/example/foo");
tmp.newFile("src/com/example/foo/Foo.java");
tmp.newFile("src/com/example/foo/package.html");
tmp.newFolder("src/com/example/bar");
tmp.newFile("src/com/example/bar/Bar.java");
tmp.newFile("src/com/example/bar/package.html");
// Copy the contents of src/ to dest/ (including src itself).
tmp.newFolder("dest");
filesystem.copy(Paths.get("src"), Paths.get("dest"), CopySourceMode.DIRECTORY_AND_CONTENTS);
assertTrue(new File(tmp.getRoot(), "dest/src/com/example/foo/Foo.java").exists());
assertTrue(new File(tmp.getRoot(), "dest/src/com/example/foo/package.html").exists());
assertTrue(new File(tmp.getRoot(), "dest/src/com/example/bar/Bar.java").exists());
assertTrue(new File(tmp.getRoot(), "dest/src/com/example/bar/package.html").exists());
}
@Test
public void testCopyFile() throws IOException {
tmp.newFolder("foo");
File file = tmp.newFile("foo/bar.txt");
String content = "Hello, World!";
Files.write(content, file, Charsets.UTF_8);
filesystem.copyFile(Paths.get("foo/bar.txt"), Paths.get("foo/baz.txt"));
assertEquals(
content,
Files.toString(new File(tmp.getRoot(), "foo/baz.txt"), Charsets.UTF_8));
}
@Test
public void testDeleteFileAtPath() throws IOException {
Path path = Paths.get("foo.txt");
File file = tmp.newFile(path.toString());
assertTrue(file.exists());
filesystem.deleteFileAtPath(path);
assertFalse(file.exists());
}
@Test
public void testCreateContextStringForModifyEvent() throws IOException {
Path file = tmp.newFile("foo.txt").toPath();
WatchEvent<Path> modifyEvent = WatchEvents.createPathEvent(
file,
StandardWatchEventKinds.ENTRY_MODIFY);
assertEquals(
file.toAbsolutePath().toString(),
filesystem.createContextString(modifyEvent));
}
@Test
public void testCreateContextStringForOverflowEvent() {
WatchEvent<Object> overflowEvent = new WatchEvent<Object>() {
@Override
public Kind<Object> kind() {
return StandardWatchEventKinds.OVERFLOW;
}
@Override
public int count() {
return 0;
}
@Override
public Object context() {
return new Object() {
@Override
public String toString() {
return "I am the context string.";
}
};
}
};
assertEquals("I am the context string.", filesystem.createContextString(overflowEvent));
}
@Test
public void testWalkFileTreeWhenProjectRootIsNotWorkingDir() throws IOException {
tmp.newFolder("dir");
tmp.newFile("dir/file.txt");
tmp.newFolder("dir/dir2");
tmp.newFile("dir/dir2/file2.txt");
final ImmutableList.Builder<String> fileNames = ImmutableList.builder();
filesystem.walkRelativeFileTree(
Paths.get("dir"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
fileNames.add(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}
});
assertThat(fileNames.build(), containsInAnyOrder("file.txt", "file2.txt"));
}
@Test
public void testWalkFileTreeWhenProjectRootIsWorkingDir() throws IOException {
ProjectFilesystem projectFilesystem = new ProjectFilesystem(Paths.get("."));
final ImmutableList.Builder<String> fileNames = ImmutableList.builder();
Path pathRelativeToProjectRoot = Paths.get(
"test/com/facebook/buck/io/testdata/directory_traversal_ignore_paths");
projectFilesystem.walkRelativeFileTree(
pathRelativeToProjectRoot,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
fileNames.add(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}
});
assertThat(
fileNames.build(), containsInAnyOrder(
"file",
"a_file",
"b_file",
"b_c_file",
"b_d_file"));
}
@Test
public void testWalkFileTreeFollowsSymlinks() throws IOException {
tmp.newFolder("dir");
tmp.newFile("dir/file.txt");
java.nio.file.Files.createSymbolicLink(
tmp.getRoot().toPath().resolve("linkdir"),
tmp.getRoot().toPath().resolve("dir"));
final ImmutableList.Builder<Path> filePaths = ImmutableList.builder();
filesystem.walkRelativeFileTree(
Paths.get(""), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
filePaths.add(file);
return FileVisitResult.CONTINUE;
}
});
assertThat(
filePaths.build(),
containsInAnyOrder(Paths.get("dir/file.txt"), Paths.get("linkdir/file.txt")));
}
@Test
public void whenContextNullThenCreateContextStringReturnsValidString() {
ProjectFilesystem projectFilesystem = new ProjectFilesystem(Paths.get("."));
assertThat(
"Context string should contain null.",
projectFilesystem.createContextString(WatchEvents.createOverflowEvent()),
Matchers.containsString("null"));
}
@Test
public void testGetFilesUnderPath() throws IOException {
tmp.newFile("file1");
tmp.newFolder("dir1");
tmp.newFile("dir1/file2");
tmp.newFolder("dir1/dir2");
tmp.newFile("dir1/dir2/file3");
assertThat(
filesystem.getFilesUnderPath(
Paths.get("dir1"),
Predicates.<Path>alwaysTrue(),
EnumSet.noneOf(FileVisitOption.class)),
containsInAnyOrder(Paths.get("dir1/file2"), Paths.get("dir1/dir2/file3")));
assertThat(
filesystem.getFilesUnderPath(
Paths.get("dir1"),
Predicates.equalTo(Paths.get("dir1/dir2/file3")),
EnumSet.noneOf(FileVisitOption.class)),
containsInAnyOrder(Paths.get("dir1/dir2/file3")));
assertThat(
filesystem.getFilesUnderPath(Paths.get("dir1")),
containsInAnyOrder(Paths.get("dir1/file2"), Paths.get("dir1/dir2/file3")));
assertThat(
filesystem.getFilesUnderPath(
Paths.get("dir1"),
Predicates.equalTo(Paths.get("dir1/file2"))),
containsInAnyOrder(Paths.get("dir1/file2")));
}
@Test
public void testCreateZipPreservesExecutablePermissions() throws IOException {
// Create a empty executable file.
File exe = tmp.newFile("test.exe");
exe.setExecutable(true);
// Archive it into a zipfile using `ProjectFileSystem.createZip`.
File zipFile = new File(tmp.getRoot().toPath().toString() + "/test.zip");
filesystem.createZip(ImmutableList.of(exe.toPath()), zipFile);
// Now unpack the archive (using apache's common-compress, as it preserves
// executable permissions) and verify that the archive entry has executable
// permissions.
try (ZipFile zip = new ZipFile(zipFile)) {
Enumeration<ZipArchiveEntry> entries = zip.getEntries();
assertTrue(entries.hasMoreElements());
ZipArchiveEntry entry = entries.nextElement();
Set<PosixFilePermission> permissions =
MorePosixFilePermissions.fromMode(entry.getExternalAttributes() >> 16);
assertTrue(permissions.contains(PosixFilePermission.OWNER_EXECUTE));
assertFalse(entries.hasMoreElements());
}
}
@Test
public void testCreateReadOnlyFileSetsPermissions() throws IOException {
Path path = Paths.get("hello.txt");
ImmutableSet<PosixFilePermission> permissions =
ImmutableSet.of(
PosixFilePermission.OWNER_READ,
PosixFilePermission.GROUP_READ,
PosixFilePermission.OTHERS_READ);
filesystem.writeContentsToPath(
"hello world",
path,
PosixFilePermissions.asFileAttribute(permissions));
// The umask may restrict the actual permissions on the filesystem:
// https://fburl.com/26569549
// So the best we can do is to check that the actual permissions are a
// strict subset of the expected permissions.
PosixFileAttributes attrs = filesystem.readAttributes(path, PosixFileAttributes.class);
assertTrue(permissions.containsAll(attrs.permissions()));
}
@Test
public void testCreateZip() throws IOException {
tmp.newFolder("foo");
tmp.newFile("foo/bar.txt");
tmp.newFile("foo/baz.txt");
File output = tmp.newFile("out.zip");
filesystem.createZip(
ImmutableList.of(Paths.get("foo/bar.txt"), Paths.get("foo/baz.txt")),
output);
ZipInspector zipInspector = new ZipInspector(output);
assertEquals(
ImmutableSet.of("foo/bar.txt", "foo/baz.txt"),
zipInspector.getZipFileEntries());
}
@Test
public void testCreateZipWithEmptyDir() throws IOException {
tmp.newFolder("foo");
tmp.newFile("foo/bar.txt");
tmp.newFile("foo/baz.txt");
tmp.newFolder("empty");
File output = tmp.newFile("out.zip");
filesystem.createZip(
ImmutableList.of(Paths.get("foo/bar.txt"), Paths.get("foo/baz.txt"), Paths.get("empty")),
output);
ZipInspector zipInspector = new ZipInspector(output);
assertEquals(
ImmutableSet.of("foo/bar.txt", "foo/baz.txt", "empty/"),
zipInspector.getZipFileEntries());
}
@Test
public void testCreateZipWithAdditionalFiles() throws IOException {
tmp.newFolder("foo");
tmp.newFile("foo/bar.txt");
tmp.newFile("foo/baz.txt");
File output = tmp.newFile("out.zip");
filesystem.createZip(
ImmutableList.of(Paths.get("foo/bar.txt"), Paths.get("foo/baz.txt")),
output,
ImmutableMap.of(Paths.get("log/info.txt"), "hello"));
ZipInspector zipInspector = new ZipInspector(output);
assertEquals(
ImmutableSet.of("foo/bar.txt", "foo/baz.txt", "log/info.txt"),
zipInspector.getZipFileEntries());
}
@Test
public void testIsSymLinkReturnsTrueForSymLink() throws IOException {
Path rootPath = tmp.getRoot().toPath();
java.nio.file.Files.createSymbolicLink(rootPath.resolve("foo"), rootPath.resolve("bar"));
assertTrue(filesystem.isSymLink(Paths.get("foo")));
}
@Test
public void testIsSymLinkReturnsFalseForFile() throws IOException {
tmp.newFile("foo");
assertFalse(filesystem.isSymLink(Paths.get("foo")));
}
@Test
public void testIsSymLinkReturnsFalseForNotExistent() throws IOException {
assertFalse(filesystem.isSymLink(Paths.get("foo")));
}
@Test
public void testSortedDirectoryContents() throws IOException {
tmp.newFolder("foo");
Path a = tmp.newFile("foo/a.txt").toPath();
java.nio.file.Files.setLastModifiedTime(a, FileTime.fromMillis(1000));
Path b = tmp.newFile("foo/b.txt").toPath();
java.nio.file.Files.setLastModifiedTime(b, FileTime.fromMillis(2000));
Path c = tmp.newFile("foo/c.txt").toPath();
java.nio.file.Files.setLastModifiedTime(c, FileTime.fromMillis(3000));
tmp.newFile("foo/non-matching");
assertEquals(
ImmutableSet.of(c, b, a),
filesystem.getSortedMatchingDirectoryContents(
Paths.get("foo"),
"*.txt"));
}
}