/* * Copyright 2013-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.util.cache; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import com.facebook.buck.io.ArchiveMemberPath; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.zip.CustomJarOutputStream; import com.facebook.buck.zip.CustomZipOutputStream; import com.facebook.buck.zip.ZipOutputStreams; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import org.hamcrest.Matchers; import org.hamcrest.junit.ExpectedException; import org.junit.Rule; import org.junit.Test; public class DefaultFileHashCacheTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void whenPathIsPutCacheContainsPath() { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path path = new File("SomeClass.java").toPath(); HashCodeAndFileType value = HashCodeAndFileType.ofFile(HashCode.fromInt(42)); cache.loadingCache.put(path, value); assertTrue("Cache should contain path", cache.willGet(path)); } @Test public void whenPathIsPutPathGetReturnsHash() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path path = new File("SomeClass.java").toPath(); HashCodeAndFileType value = HashCodeAndFileType.ofFile(HashCode.fromInt(42)); cache.loadingCache.put(path, value); assertEquals("Cache should contain hash", value.getHashCode(), cache.get(path)); } @Test public void whenPathIsPutThenInvalidatedCacheDoesNotContainPath() { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path path = new File("SomeClass.java").toPath(); HashCodeAndFileType value = HashCodeAndFileType.ofFile(HashCode.fromInt(42)); cache.loadingCache.put(path, value); assertTrue("Cache should contain path", cache.willGet(path)); cache.invalidate(path); assertFalse("Cache should not contain pain", cache.willGet(path)); } @Test public void invalidatingNonExistentEntryDoesNotThrow() { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path path = new File("SomeClass.java").toPath(); assertFalse("Cache should not contain pain", cache.willGet(path)); cache.invalidate(path); assertFalse("Cache should not contain pain", cache.willGet(path)); } @Test public void getMissingPathThrows() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); expectedException.expect(IOException.class); cache.get(filesystem.getPath("hello.java")); } @Test public void whenPathsArePutThenInvalidateAllRemovesThem() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path path1 = Paths.get("path1"); filesystem.writeContentsToPath("contents1", path1); cache.get(path1); assertTrue(cache.willGet(path1)); Path path2 = Paths.get("path2"); filesystem.writeContentsToPath("contents2", path2); cache.get(path2); assertTrue(cache.willGet(path2)); // Verify that `invalidateAll` clears everything from the cache. assertFalse(cache.loadingCache.asMap().isEmpty()); cache.invalidateAll(); assertTrue(cache.loadingCache.asMap().isEmpty()); } @Test public void whenDirectoryIsPutThenInvalidatedCacheDoesNotContainPathOrChildren() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path dir = filesystem.getPath("dir"); filesystem.mkdirs(dir); Path child1 = dir.resolve("child1"); filesystem.touch(child1); Path child2 = dir.resolve("child2"); filesystem.touch(child2); cache.get(dir); assertTrue(cache.willGet(dir)); assertTrue(cache.willGet(child1)); assertTrue(cache.willGet(child2)); cache.invalidate(dir); assertNull(cache.loadingCache.getIfPresent(dir)); assertNull(cache.loadingCache.getIfPresent(child1)); assertNull(cache.loadingCache.getIfPresent(child2)); } @Test public void whenJarMemberWithHashInManifestIsQueriedThenCacheCorrectlyObtainsIt() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path abiJarPath = Paths.get("test-abi.jar"); Path memberPath = Paths.get("SomeClass.class"); String memberContents = "Some contents"; try (CustomJarOutputStream jar = ZipOutputStreams.newJarOutputStream(filesystem.newFileOutputStream(abiJarPath))) { jar.setEntryHashingEnabled(true); jar.writeEntry( memberPath.toString(), new ByteArrayInputStream(memberContents.getBytes(StandardCharsets.UTF_8))); } HashCode actual = cache.get(ArchiveMemberPath.of(abiJarPath, memberPath)); HashCode expected = Hashing.murmur3_128().hashString(memberContents, StandardCharsets.UTF_8); assertEquals(expected, actual); } @Test(expected = NoSuchFileException.class) public void whenJarMemberWithoutHashInManifestIsQueriedThenThrow() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path abiJarPath = Paths.get("test-abi.jar"); Path memberPath = Paths.get("Unhashed.txt"); String memberContents = "Some contents"; try (CustomJarOutputStream jar = ZipOutputStreams.newJarOutputStream(filesystem.newFileOutputStream(abiJarPath))) { jar.setEntryHashingEnabled(true); jar.writeEntry( "SomeClass.class", new ByteArrayInputStream(memberContents.getBytes(StandardCharsets.UTF_8))); jar.setEntryHashingEnabled(false); jar.writeEntry( memberPath.toString(), new ByteArrayInputStream(memberContents.getBytes(StandardCharsets.UTF_8))); } cache.get(ArchiveMemberPath.of(abiJarPath, memberPath)); } @Test(expected = UnsupportedOperationException.class) public void whenJarMemberWithoutManifestIsQueriedThenThrow() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path abiJarPath = Paths.get("no-manifest.jar"); Path memberPath = Paths.get("Empty.class"); try (JarOutputStream jar = new JarOutputStream(filesystem.newFileOutputStream(abiJarPath))) { jar.putNextEntry(new JarEntry(memberPath.toString())); jar.write("Contents".getBytes(StandardCharsets.UTF_8)); jar.closeEntry(); } cache.get(ArchiveMemberPath.of(abiJarPath, memberPath)); } @Test(expected = NoSuchFileException.class) public void whenJarMemberWithEmptyManifestIsQueriedThenThrow() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); Path abiJarPath = Paths.get("empty-manifest.jar"); Path memberPath = Paths.get("Empty.class"); try (CustomZipOutputStream jar = ZipOutputStreams.newOutputStream(filesystem.newFileOutputStream(abiJarPath))) { jar.writeEntry(JarFile.MANIFEST_NAME, new ByteArrayInputStream(new byte[0])); jar.writeEntry( memberPath.toString(), new ByteArrayInputStream("Contents".getBytes(StandardCharsets.UTF_8))); } cache.get(ArchiveMemberPath.of(abiJarPath, memberPath)); } @Test public void getSizeOfMissingPathThrows() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path input = filesystem.getPath("input"); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); expectedException.expect(RuntimeException.class); cache.getSize(input); } @Test public void getSizeOfFile() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path input = filesystem.getPath("input"); filesystem.writeBytesToPath(new byte[123], input); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); assertThat(cache.getSize(input), Matchers.equalTo(123L)); } @Test public void getSizeOfDirectory() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path input = filesystem.getPath("input"); filesystem.mkdirs(input); filesystem.writeBytesToPath(new byte[123], input.resolve("file1")); filesystem.writeBytesToPath(new byte[123], input.resolve("file2")); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); assertThat(cache.getSize(input), Matchers.equalTo(246L)); } @Test public void getFileSizeInvalidation() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path input = filesystem.getPath("input"); filesystem.writeBytesToPath(new byte[123], input); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.empty()); cache.getSize(input); cache.invalidate(input); assertNull(cache.sizeCache.getIfPresent(input)); } @Test public void thatBuckoutCacheWillGetIsCorrect() throws IOException { ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path buckOut = Paths.get("buck-out"); filesystem.mkdirs(buckOut); Path buckOutFile = buckOut.resolve("file.txt"); Path otherFile = Paths.get("file.txt"); filesystem.writeContentsToPath("data", buckOutFile); filesystem.writeContentsToPath("other data", otherFile); DefaultFileHashCache cache = new DefaultFileHashCache(filesystem, Optional.of(Paths.get("buck-out"))); assertTrue(cache.willGet(filesystem.getPath("buck-out/file.txt"))); assertFalse(cache.willGet(filesystem.getPath("file.txt"))); } }