/* * 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.artifact_cache; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import com.facebook.buck.io.BorrowablePath; import com.facebook.buck.io.LazyPath; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; public class MultiArtifactCacheTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); private static final RuleKey dummyRuleKey = new RuleKey("76b1c1beae69428db2d1befb31cf743ac8ce90df"); private static final LazyPath dummyFile = LazyPath.ofInstance(Paths.get("dummy")); // An cache which always returns errors from fetching. class ErroringArtifactCache extends NoopArtifactCache { @Override public CacheResult fetch(RuleKey ruleKey, LazyPath output) { return CacheResult.error("cache", "error"); } } @Test public void testCacheFetch() throws InterruptedException, IOException { DummyArtifactCache dummyArtifactCache1 = new DummyArtifactCache(); DummyArtifactCache dummyArtifactCache2 = new DummyArtifactCache(); MultiArtifactCache multiArtifactCache = new MultiArtifactCache(ImmutableList.of(dummyArtifactCache1, dummyArtifactCache2)); assertEquals( "Fetch should fail", CacheResultType.MISS, multiArtifactCache.fetch(dummyRuleKey, dummyFile).getType()); dummyArtifactCache1.store( ArtifactInfo.builder().addRuleKeys(dummyRuleKey).build(), BorrowablePath.notBorrowablePath(dummyFile.get())); assertEquals( "Fetch should succeed after store", CacheResultType.HIT, multiArtifactCache.fetch(dummyRuleKey, dummyFile).getType()); dummyArtifactCache1.reset(); dummyArtifactCache2.reset(); dummyArtifactCache2.store( ArtifactInfo.builder().addRuleKeys(dummyRuleKey).build(), BorrowablePath.notBorrowablePath(dummyFile.get())); assertEquals( "Fetch should succeed after store", CacheResultType.HIT, multiArtifactCache.fetch(dummyRuleKey, dummyFile).getType()); multiArtifactCache.close(); } @Test public void testPropagateOnlyCacheStore() throws InterruptedException, IOException, ExecutionException { DummyArtifactCache dummyArtifactCache1 = new DummyArtifactCache() { @Override public CacheReadMode getCacheReadMode() { return CacheReadMode.PASSTHROUGH; } }; DummyArtifactCache dummyArtifactCache2 = new DummyArtifactCache(); MultiArtifactCache multiArtifactCache = new MultiArtifactCache(ImmutableList.of(dummyArtifactCache1, dummyArtifactCache2)); assertEquals(CacheReadMode.READWRITE, multiArtifactCache.getCacheReadMode()); multiArtifactCache .store( ArtifactInfo.builder().addRuleKeys(dummyRuleKey).build(), BorrowablePath.notBorrowablePath(dummyFile.get())) .get(); assertEquals( "This cache is PASSTHROUGH, store on the mulit-cache should not write to it", CacheResultType.MISS, dummyArtifactCache1.fetch(dummyRuleKey, dummyFile).getType()); assertEquals(CacheResultType.HIT, dummyArtifactCache2.fetch(dummyRuleKey, dummyFile).getType()); multiArtifactCache.close(); } @Test public void testPropagateOnlyCacheGet() throws InterruptedException, IOException, ExecutionException { DummyArtifactCache dummyArtifactCache1 = new DummyArtifactCache() { @Override public CacheReadMode getCacheReadMode() { return CacheReadMode.PASSTHROUGH; } }; DummyArtifactCache dummyArtifactCache2 = new DummyArtifactCache(); MultiArtifactCache multiArtifactCache = new MultiArtifactCache(ImmutableList.of(dummyArtifactCache1, dummyArtifactCache2)); dummyArtifactCache2 .store( ArtifactInfo.builder().addRuleKeys(dummyRuleKey).build(), BorrowablePath.notBorrowablePath(dummyFile.get())) .get(); assertEquals( "Fetch should find artifact that's present in one of the caches.", CacheResultType.HIT, multiArtifactCache.fetch(dummyRuleKey, dummyFile).getType()); assertEquals( "Fetch should have propagated the artifact.", CacheResultType.HIT, dummyArtifactCache1.fetch(dummyRuleKey, dummyFile).getType()); multiArtifactCache.close(); } @Test public void testCacheStore() throws InterruptedException, IOException { DummyArtifactCache dummyArtifactCache1 = new DummyArtifactCache(); DummyArtifactCache dummyArtifactCache2 = new DummyArtifactCache(); MultiArtifactCache multiArtifactCache = new MultiArtifactCache(ImmutableList.of(dummyArtifactCache1, dummyArtifactCache2)); multiArtifactCache.store( ArtifactInfo.builder().addRuleKeys(dummyRuleKey).build(), BorrowablePath.notBorrowablePath(dummyFile.get())); assertEquals( "MultiArtifactCache.store() should store to all contained ArtifactCaches", dummyArtifactCache1.storeKey, dummyRuleKey); assertEquals( "MultiArtifactCache.store() should store to all contained ArtifactCaches", dummyArtifactCache2.storeKey, dummyRuleKey); multiArtifactCache.close(); } private static class SimpleArtifactCache implements ArtifactCache { private final ProjectFilesystem filesystem; @Nullable public AtomicReference<RuleKey> storedKey; public SimpleArtifactCache(ProjectFilesystem filesystem) { this.filesystem = filesystem; this.storedKey = new AtomicReference<>(null); } @Override public CacheResult fetch(RuleKey ruleKey, LazyPath output) { if (ruleKey.equals(storedKey.get())) { try { filesystem.touch(output.get()); } catch (IOException e) { throw new RuntimeException(e); } return CacheResult.hit("cache"); } else { return CacheResult.miss(); } } @Override public ListenableFuture<Void> store(ArtifactInfo info, BorrowablePath output) { if (output.canBorrow()) { try { filesystem.deleteFileAtPath(output.getPath()); } catch (IOException e) { throw new RuntimeException(e); } } storedKey.set(Iterables.getFirst(info.getRuleKeys(), null)); return Futures.immediateFuture(null); } @Override public CacheReadMode getCacheReadMode() { return CacheReadMode.READWRITE; } @Override public void close() {} } @Test public void testCacheStoreWithBorrowablePathConsumingCache() throws InterruptedException, IOException, ExecutionException { ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot()); Path fetchFile = filesystem.resolve("fetch_file"); SimpleArtifactCache fakeDirCache = new SimpleArtifactCache(filesystem); SimpleArtifactCache fakeReadOnlyCache = new SimpleArtifactCache(filesystem) { @Override public CacheReadMode getCacheReadMode() { return CacheReadMode.READONLY; } }; MultiArtifactCache multiArtifactCache = new MultiArtifactCache(ImmutableList.of(fakeDirCache, fakeReadOnlyCache)); fakeReadOnlyCache.storedKey.set(dummyRuleKey); multiArtifactCache.fetch(dummyRuleKey, LazyPath.ofInstance(fetchFile)); assertThat( "The .get() call should not delete the path it's fetching.", filesystem.exists(fetchFile), Matchers.is(true)); multiArtifactCache.close(); } @Test public void preserveErrorsFromInnerCache() throws InterruptedException, IOException { ErroringArtifactCache inner = new ErroringArtifactCache(); MultiArtifactCache cache = new MultiArtifactCache(ImmutableList.of(inner)); CacheResult result = cache.fetch(dummyRuleKey, dummyFile); assertSame(result.getType(), CacheResultType.ERROR); cache.close(); } @Test public void cacheFetchPushesMetadataToHigherCache() throws Exception { InMemoryArtifactCache cache1 = new InMemoryArtifactCache(); InMemoryArtifactCache cache2 = new InMemoryArtifactCache(); MultiArtifactCache multiArtifactCache = new MultiArtifactCache(ImmutableList.of(cache1, cache2)); LazyPath output = LazyPath.ofInstance(tmp.newFile()); ImmutableMap<String, String> metadata = ImmutableMap.of("hello", "world"); cache2.store( ArtifactInfo.builder().addRuleKeys(dummyRuleKey).setMetadata(metadata).build(), new byte[0]); multiArtifactCache.fetch(dummyRuleKey, output); CacheResult result = cache1.fetch(dummyRuleKey, output); assertThat(result.getType(), Matchers.equalTo(CacheResultType.HIT)); assertThat(result.getMetadata(), Matchers.equalTo(metadata)); multiArtifactCache.close(); } }