/* * Copyright 2014-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.testutil; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.timing.SettableFakeClock; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.io.CharStreams; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.junit.Rule; import org.junit.Test; public class FakeProjectFilesystemTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); @Test public void testFilesystemReturnsAddedContents() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.writeContentsToPath("Some content", Paths.get("A.txt")); Optional<String> contents; contents = filesystem.readFileIfItExists(Paths.get("A.txt")); assertTrue("Fake file system must return added file contents.", contents.isPresent()); assertEquals("Some content", contents.get()); contents = filesystem.readFileIfItExists(Paths.get("B.txt")); assertFalse( "Fake file system must not return non-existing file contents", contents.isPresent()); } @Test public void testReadLines() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.writeContentsToPath("line one.\nline two.\n", Paths.get("A.txt")); filesystem.writeLinesToPath(ImmutableList.of(), Paths.get("B.txt")); filesystem.writeContentsToPath("\n", Paths.get("C.txt")); MoreAsserts.assertIterablesEquals( ImmutableList.of("line one.", "line two."), filesystem.readLines(Paths.get("A.txt"))); MoreAsserts.assertIterablesEquals(ImmutableList.of(), filesystem.readLines(Paths.get("B.txt"))); MoreAsserts.assertIterablesEquals( ImmutableList.of(""), filesystem.readLines(Paths.get("C.txt"))); MoreAsserts.assertIterablesEquals(ImmutableList.of(), filesystem.readLines(Paths.get("D.txt"))); } @Test public void testTouch() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.touch(Paths.get("A.txt")); filesystem.touch(Paths.get("A/B.txt")); assertTrue(filesystem.exists(Paths.get("A.txt"))); assertTrue(filesystem.exists(Paths.get("A/B.txt"))); } @Test public void testWalkRelativeFileTree() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.touch(Paths.get("root/A.txt")); filesystem.touch(Paths.get("root/A/B/C.txt")); filesystem.touch(Paths.get("root/A/B.txt")); final List<Path> filesVisited = new ArrayList<>(); FileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { filesVisited.add(path); return FileVisitResult.CONTINUE; } }; filesystem.walkRelativeFileTree(Paths.get("root"), fileVisitor); assertThat( filesVisited, containsInAnyOrder( Paths.get("root/A.txt"), Paths.get("root/A/B/C.txt"), Paths.get("root/A/B.txt"))); filesVisited.clear(); filesystem.walkRelativeFileTree(Paths.get("root/A"), fileVisitor); assertThat( filesVisited, containsInAnyOrder(Paths.get("root/A/B/C.txt"), Paths.get("root/A/B.txt"))); } @Test public void testWalkRelativeFileTreeWhenPathIsAFile() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.touch(Paths.get("A.txt")); final List<Path> filesVisited = new ArrayList<>(); FileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { filesVisited.add(path); return FileVisitResult.CONTINUE; } }; filesystem.walkRelativeFileTree(Paths.get("A.txt"), fileVisitor); // Despite the awkward name, "contains" implies an exact match. assertThat(filesVisited, contains(Paths.get("A.txt"))); } @Test public void testNewFileInputStream() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); Path path = Paths.get("hello.txt"); filesystem.writeContentsToPath("hello world", path); InputStreamReader reader = new InputStreamReader(filesystem.newFileInputStream(path), Charsets.UTF_8); String contents = CharStreams.toString(reader); assertEquals("hello world", contents); } @Test public void testAllExistingFileSystem() throws IOException { AllExistingProjectFilesystem filesystem = new AllExistingProjectFilesystem(); assertTrue(filesystem.exists(Paths.get("somepath.txt"))); } @Test public void testWriteContentsWithDefaultFileAttributes() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); Path path = Paths.get("hello.txt"); filesystem.writeContentsToPath("hello world", path); assertEquals(ImmutableSet.<FileAttribute<?>>of(), filesystem.getFileAttributesAtPath(path)); } @Test public void testWriteContentsWithSpecifiedFileAttributes() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); ImmutableSet<PosixFilePermission> permissions = ImmutableSet.of( PosixFilePermission.OWNER_READ, PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ); FileAttribute<?> attribute = PosixFilePermissions.asFileAttribute(permissions); Path path = Paths.get("hello.txt"); filesystem.writeContentsToPath("hello world", Paths.get("hello.txt"), attribute); assertEquals(ImmutableSet.of(attribute), filesystem.getFileAttributesAtPath(path)); } @Test public void testFileOutputStream() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); Path filePath = Paths.get("somefile.txt"); try (OutputStream stream = filesystem.newFileOutputStream(filePath)) { stream.write("hello world".getBytes(StandardCharsets.UTF_8)); } assertEquals("hello world", filesystem.readFileIfItExists(filePath).get()); } @Test public void testFlushThenCloseFileOutputStream() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); Path filePath = Paths.get("somefile.txt"); OutputStream stream = filesystem.newFileOutputStream(filePath); stream.write("hello world".getBytes(StandardCharsets.UTF_8)); stream.flush(); stream.close(); assertEquals("hello world", filesystem.readFileIfItExists(filePath).get()); } @Test public void testSingleElementMkdirsExists() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.mkdirs(Paths.get("foo")); assertTrue(filesystem.isDirectory(Paths.get("foo"))); assertFalse(filesystem.isDirectory(Paths.get("foo/bar"))); } @Test public void testEachElementOfMkdirsExists() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.mkdirs(Paths.get("foo/bar/baz")); assertTrue(filesystem.isDirectory(Paths.get("foo"))); assertTrue(filesystem.isDirectory(Paths.get("foo/bar"))); assertTrue(filesystem.isDirectory(Paths.get("foo/bar/baz"))); assertFalse(filesystem.isDirectory(Paths.get("foo/bar/baz/blech"))); } @Test public void testDirectoryDoesNotExistAfterRmdir() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.mkdirs(Paths.get("foo")); filesystem.deleteRecursivelyIfExists(Paths.get("foo")); assertFalse(filesystem.isDirectory(Paths.get("foo"))); } @Test public void testDirectoryExistsIfIsDirectory() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.mkdirs(Paths.get("foo")); assertTrue(filesystem.isDirectory(Paths.get("foo"))); assertTrue(filesystem.exists(Paths.get("foo"))); } @Test public void testIsFileAfterTouch() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.touch(Paths.get("foo")); assertFalse(filesystem.isDirectory(Paths.get("foo"))); assertTrue(filesystem.isFile(Paths.get("foo"))); assertTrue(filesystem.exists(Paths.get("foo"))); } @Test(expected = NoSuchFileException.class) public void fileModifiedTimeThrowsIfDoesNotExist() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.getLastModifiedTime(Paths.get("foo")); } @Test public void fileModifiedTimeIsSetOnInitialWrite() throws IOException { SettableFakeClock clock = new SettableFakeClock(49152, 0); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(clock); filesystem.touch(Paths.get("foo")); assertEquals(filesystem.getLastModifiedTime(Paths.get("foo")), FileTime.fromMillis(49152)); } @Test public void fileModifiedTimeIsUpdatedOnSubsequentWrite() throws IOException { SettableFakeClock clock = new SettableFakeClock(49152, 0); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(clock); filesystem.touch(Paths.get("foo")); clock.setCurrentTimeMillis(64738); filesystem.touch(Paths.get("foo")); assertEquals(filesystem.getLastModifiedTime(Paths.get("foo")), FileTime.fromMillis(64738)); } @Test public void fileModifiedTimeIsSetOnMkdirs() throws IOException { SettableFakeClock clock = new SettableFakeClock(49152, 0); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(clock); filesystem.mkdirs(Paths.get("foo/bar/baz")); assertEquals(filesystem.getLastModifiedTime(Paths.get("foo")), FileTime.fromMillis(49152)); assertEquals(filesystem.getLastModifiedTime(Paths.get("foo/bar")), FileTime.fromMillis(49152)); assertEquals( filesystem.getLastModifiedTime(Paths.get("foo/bar/baz")), FileTime.fromMillis(49152)); } @Test public void testWritingAFileAddsParentDirectories() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.writeContentsToPath("hello", Paths.get("test/one/two/three.txt")); assertTrue(filesystem.exists(Paths.get("test/one/two"))); assertTrue(filesystem.exists(Paths.get("test/one"))); assertTrue(filesystem.exists(Paths.get("test"))); } @Test public void testIsSymLinkReturnsTrueForSymLink() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.createSymLink(Paths.get("foo"), Paths.get("bar"), false); assertTrue(filesystem.isSymLink(Paths.get("foo"))); } @Test public void testIsSymLinkReturnsFalseForFile() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.touch(Paths.get("foo")); assertFalse(filesystem.isSymLink(Paths.get("foo"))); } @Test public void testIsSymLinkReturnsFalseForNotExistent() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); assertFalse(filesystem.isSymLink(Paths.get("foo"))); } @Test public void testSortedDirectoryContents() throws IOException { SettableFakeClock clock = new SettableFakeClock(1000, 0); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(clock); filesystem.mkdirs(Paths.get("foo")); Path a = Paths.get("foo/a.txt"); filesystem.touch(a); clock.setCurrentTimeMillis(2000); Path b = Paths.get("foo/b.txt"); filesystem.touch(b); clock.setCurrentTimeMillis(3000); Path c = Paths.get("foo/c.txt"); filesystem.touch(c); filesystem.touch(Paths.get("foo/non-matching")); assertEquals( ImmutableSet.of(c, b, a), filesystem.getMtimeSortedMatchingDirectoryContents(Paths.get("foo"), "*.txt")); } @Test public void testGetFilesUnderPath() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); Path bar = Paths.get("foo/bar.txt"); Path baz = Paths.get("foo/baz/blech.txt"); filesystem.touch(bar); filesystem.touch(baz); assertEquals(ImmutableSet.of(bar, baz), filesystem.getFilesUnderPath(Paths.get("foo"))); } @Test public void testDirectoryContentsAreInSortedOrder() throws IOException { SettableFakeClock clock = new SettableFakeClock(1000, 0); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(clock); filesystem.touch(Paths.get("foo/foo")); filesystem.touch(Paths.get("foo/bar")); filesystem.touch(Paths.get("foo/baz")); assertThat( filesystem.getDirectoryContents(Paths.get("foo")), // Yes, contains() is an order-dependent matcher, despite its name. contains(Paths.get("foo/bar"), Paths.get("foo/baz"), Paths.get("foo/foo"))); FakeProjectFilesystem filesystem2 = new FakeProjectFilesystem(clock); filesystem2.touch(Paths.get("foo/baz")); filesystem2.touch(Paths.get("foo/bar")); filesystem2.touch(Paths.get("foo/foo")); assertThat( filesystem2.getDirectoryContents(Paths.get("foo")), contains(Paths.get("foo/bar"), Paths.get("foo/baz"), Paths.get("foo/foo"))); } @Test public void testCreateZip() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); byte[] contents = "contents".getBytes(StandardCharsets.UTF_8); Path file = Paths.get("file"); filesystem.writeBytesToPath(contents, file); Path dir = Paths.get("dir"); filesystem.mkdirs(dir); filesystem.writeBytesToPath(contents, dir.resolve("file")); Path output = tmp.newFile("output.zip"); filesystem.createZip(ImmutableList.of(file, dir, dir.resolve("file")), output); try (Zip zip = new Zip(output, /* forWriting */ false)) { assertEquals(ImmutableSet.of("", "dir/"), zip.getDirNames()); assertEquals(ImmutableSet.of("file", "dir/file"), zip.getFileNames()); assertArrayEquals(contents, zip.readFully("file")); assertArrayEquals(contents, zip.readFully("dir/file")); } } @Test public void filesystemWalkIsInSortedOrder() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.mkdirs(Paths.get("foo")); filesystem.touch(Paths.get("foo/bbbb")); filesystem.touch(Paths.get("foo/aaaa")); filesystem.mkdirs(Paths.get("bar")); filesystem.touch(Paths.get("bar/aaaa")); filesystem.touch(Paths.get("bar/cccc")); filesystem.touch(Paths.get("bar/bbbb")); AccumulatingFileVisitor visitor = new AccumulatingFileVisitor(); filesystem.walkRelativeFileTree(Paths.get(""), visitor); assertThat( visitor.getSeen(), contains( Paths.get("bar/aaaa"), Paths.get("bar/bbbb"), Paths.get("bar/cccc"), Paths.get("foo/aaaa"), Paths.get("foo/bbbb"))); } @Test public void testReadFileAttributes() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.touch(Paths.get("foo")); filesystem.mkdirs(Paths.get("bar")); filesystem.touch(Paths.get("bar/aaaa")); BasicFileAttributes attrs; attrs = filesystem.readAttributes(Paths.get("foo"), BasicFileAttributes.class); assertFalse(attrs.isDirectory()); assertTrue(attrs.isRegularFile()); attrs = filesystem.readAttributes(Paths.get("bar"), BasicFileAttributes.class); assertTrue(attrs.isDirectory()); assertFalse(attrs.isRegularFile()); attrs = filesystem.readAttributes(Paths.get("bar/aaaa"), BasicFileAttributes.class); assertFalse(attrs.isDirectory()); assertTrue(attrs.isRegularFile()); } @Test(expected = IOException.class) public void testReadFileAttributesMissingFileThrows() throws IOException { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); filesystem.readAttributes(Paths.get("foo"), BasicFileAttributes.class); } private static class AccumulatingFileVisitor implements FileVisitor<Path> { private final List<Path> seen = new ArrayList<>(); public ImmutableList<Path> getSeen() { return ImmutableList.copyOf(seen); } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { seen.add(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { throw exc; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (exc != null) { throw exc; } return FileVisitResult.CONTINUE; } } }