| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * 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.squareup.okhttp.internal; |
| |
| import com.squareup.okhttp.internal.io.FileSystem; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.concurrent.Executor; |
| import okio.BufferedSink; |
| import okio.BufferedSource; |
| import okio.Okio; |
| import okio.Source; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.rules.Timeout; |
| |
| import static com.squareup.okhttp.internal.DiskLruCache.JOURNAL_FILE; |
| import static com.squareup.okhttp.internal.DiskLruCache.JOURNAL_FILE_BACKUP; |
| import static com.squareup.okhttp.internal.DiskLruCache.MAGIC; |
| import static com.squareup.okhttp.internal.DiskLruCache.VERSION_1; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| public final class DiskLruCacheTest { |
| @Rule public final TemporaryFolder tempDir = new TemporaryFolder(); |
| @Rule public final Timeout timeout = new Timeout(30 * 1000); |
| |
| private final FaultyFileSystem fileSystem = new FaultyFileSystem(FileSystem.SYSTEM); |
| private final int appVersion = 100; |
| private File cacheDir; |
| private File journalFile; |
| private File journalBkpFile; |
| private final TestExecutor executor = new TestExecutor(); |
| |
| private DiskLruCache cache; |
| private final Deque<DiskLruCache> toClose = new ArrayDeque<>(); |
| |
| private void createNewCache() throws IOException { |
| createNewCacheWithSize(Integer.MAX_VALUE); |
| } |
| |
| private void createNewCacheWithSize(int maxSize) throws IOException { |
| cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, maxSize, executor); |
| synchronized (cache) { |
| cache.initialize(); |
| } |
| toClose.add(cache); |
| } |
| |
| @Before public void setUp() throws Exception { |
| cacheDir = tempDir.getRoot(); |
| journalFile = new File(cacheDir, JOURNAL_FILE); |
| journalBkpFile = new File(cacheDir, JOURNAL_FILE_BACKUP); |
| createNewCache(); |
| } |
| |
| @After public void tearDown() throws Exception { |
| while (!toClose.isEmpty()) { |
| toClose.pop().close(); |
| } |
| } |
| |
| @Test public void emptyCache() throws Exception { |
| cache.close(); |
| assertJournalEquals(); |
| } |
| |
| @Test public void validateKey() throws Exception { |
| String key = null; |
| try { |
| key = "has_space "; |
| cache.edit(key); |
| fail("Exepcting an IllegalArgumentException as the key was invalid."); |
| } catch (IllegalArgumentException iae) { |
| assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); |
| } |
| try { |
| key = "has_CR\r"; |
| cache.edit(key); |
| fail("Exepcting an IllegalArgumentException as the key was invalid."); |
| } catch (IllegalArgumentException iae) { |
| assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); |
| } |
| try { |
| key = "has_LF\n"; |
| cache.edit(key); |
| fail("Exepcting an IllegalArgumentException as the key was invalid."); |
| } catch (IllegalArgumentException iae) { |
| assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); |
| } |
| try { |
| key = "has_invalid/"; |
| cache.edit(key); |
| fail("Exepcting an IllegalArgumentException as the key was invalid."); |
| } catch (IllegalArgumentException iae) { |
| assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); |
| } |
| try { |
| key = "has_invalid\u2603"; |
| cache.edit(key); |
| fail("Exepcting an IllegalArgumentException as the key was invalid."); |
| } catch (IllegalArgumentException iae) { |
| assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); |
| } |
| try { |
| key = "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long_" |
| + "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long"; |
| cache.edit(key); |
| fail("Exepcting an IllegalArgumentException as the key was too long."); |
| } catch (IllegalArgumentException iae) { |
| assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage()); |
| } |
| |
| // Test valid cases. |
| |
| // Exactly 120. |
| key = "0123456789012345678901234567890123456789012345678901234567890123456789" |
| + "01234567890123456789012345678901234567890123456789"; |
| cache.edit(key).abort(); |
| // Contains all valid characters. |
| key = "abcdefghijklmnopqrstuvwxyz_0123456789"; |
| cache.edit(key).abort(); |
| // Contains dash. |
| key = "-20384573948576"; |
| cache.edit(key).abort(); |
| } |
| |
| @Test public void writeAndReadEntry() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 0, "ABC"); |
| setString(creator, 1, "DE"); |
| assertNull(creator.newSource(0)); |
| assertNull(creator.newSource(1)); |
| creator.commit(); |
| |
| DiskLruCache.Snapshot snapshot = cache.get("k1"); |
| assertSnapshotValue(snapshot, 0, "ABC"); |
| assertSnapshotValue(snapshot, 1, "DE"); |
| } |
| |
| @Test public void readAndWriteEntryAcrossCacheOpenAndClose() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 0, "A"); |
| setString(creator, 1, "B"); |
| creator.commit(); |
| cache.close(); |
| |
| createNewCache(); |
| DiskLruCache.Snapshot snapshot = cache.get("k1"); |
| assertSnapshotValue(snapshot, 0, "A"); |
| assertSnapshotValue(snapshot, 1, "B"); |
| snapshot.close(); |
| } |
| |
| @Test public void readAndWriteEntryWithoutProperClose() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 0, "A"); |
| setString(creator, 1, "B"); |
| creator.commit(); |
| |
| // Simulate a dirty close of 'cache' by opening the cache directory again. |
| createNewCache(); |
| DiskLruCache.Snapshot snapshot = cache.get("k1"); |
| assertSnapshotValue(snapshot, 0, "A"); |
| assertSnapshotValue(snapshot, 1, "B"); |
| snapshot.close(); |
| } |
| |
| @Test public void journalWithEditAndPublish() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed. |
| setString(creator, 0, "AB"); |
| setString(creator, 1, "C"); |
| creator.commit(); |
| cache.close(); |
| assertJournalEquals("DIRTY k1", "CLEAN k1 2 1"); |
| } |
| |
| @Test public void revertedNewFileIsRemoveInJournal() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed. |
| setString(creator, 0, "AB"); |
| setString(creator, 1, "C"); |
| creator.abort(); |
| cache.close(); |
| assertJournalEquals("DIRTY k1", "REMOVE k1"); |
| } |
| |
| @Test public void unterminatedEditIsRevertedOnClose() throws Exception { |
| cache.edit("k1"); |
| cache.close(); |
| assertJournalEquals("DIRTY k1", "REMOVE k1"); |
| } |
| |
| @Test public void journalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| assertNull(cache.get("k1")); |
| setString(creator, 0, "A"); |
| setString(creator, 1, "BC"); |
| creator.commit(); |
| cache.close(); |
| assertJournalEquals("DIRTY k1", "CLEAN k1 1 2"); |
| } |
| |
| @Test public void journalWithEditAndPublishAndRead() throws Exception { |
| DiskLruCache.Editor k1Creator = cache.edit("k1"); |
| setString(k1Creator, 0, "AB"); |
| setString(k1Creator, 1, "C"); |
| k1Creator.commit(); |
| DiskLruCache.Editor k2Creator = cache.edit("k2"); |
| setString(k2Creator, 0, "DEF"); |
| setString(k2Creator, 1, "G"); |
| k2Creator.commit(); |
| DiskLruCache.Snapshot k1Snapshot = cache.get("k1"); |
| k1Snapshot.close(); |
| cache.close(); |
| assertJournalEquals("DIRTY k1", "CLEAN k1 2 1", "DIRTY k2", "CLEAN k2 3 1", "READ k1"); |
| } |
| |
| @Test public void cannotOperateOnEditAfterPublish() throws Exception { |
| DiskLruCache.Editor editor = cache.edit("k1"); |
| setString(editor, 0, "A"); |
| setString(editor, 1, "B"); |
| editor.commit(); |
| assertInoperable(editor); |
| } |
| |
| @Test public void cannotOperateOnEditAfterRevert() throws Exception { |
| DiskLruCache.Editor editor = cache.edit("k1"); |
| setString(editor, 0, "A"); |
| setString(editor, 1, "B"); |
| editor.abort(); |
| assertInoperable(editor); |
| } |
| |
| @Test public void explicitRemoveAppliedToDiskImmediately() throws Exception { |
| DiskLruCache.Editor editor = cache.edit("k1"); |
| setString(editor, 0, "ABC"); |
| setString(editor, 1, "B"); |
| editor.commit(); |
| File k1 = getCleanFile("k1", 0); |
| assertEquals("ABC", readFile(k1)); |
| cache.remove("k1"); |
| assertFalse(fileSystem.exists(k1)); |
| } |
| |
| @Test public void removePreventsActiveEditFromStoringAValue() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Editor a = cache.edit("a"); |
| setString(a, 0, "a1"); |
| assertTrue(cache.remove("a")); |
| setString(a, 1, "a2"); |
| a.commit(); |
| assertAbsent("a"); |
| } |
| |
| /** |
| * Each read sees a snapshot of the file at the time read was called. |
| * This means that two reads of the same key can see different data. |
| */ |
| @Test public void readAndWriteOverlapsMaintainConsistency() throws Exception { |
| DiskLruCache.Editor v1Creator = cache.edit("k1"); |
| setString(v1Creator, 0, "AAaa"); |
| setString(v1Creator, 1, "BBbb"); |
| v1Creator.commit(); |
| |
| DiskLruCache.Snapshot snapshot1 = cache.get("k1"); |
| BufferedSource inV1 = Okio.buffer(snapshot1.getSource(0)); |
| assertEquals('A', inV1.readByte()); |
| assertEquals('A', inV1.readByte()); |
| |
| DiskLruCache.Editor v1Updater = cache.edit("k1"); |
| setString(v1Updater, 0, "CCcc"); |
| setString(v1Updater, 1, "DDdd"); |
| v1Updater.commit(); |
| |
| DiskLruCache.Snapshot snapshot2 = cache.get("k1"); |
| assertSnapshotValue(snapshot2, 0, "CCcc"); |
| assertSnapshotValue(snapshot2, 1, "DDdd"); |
| snapshot2.close(); |
| |
| assertEquals('a', inV1.readByte()); |
| assertEquals('a', inV1.readByte()); |
| assertSnapshotValue(snapshot1, 1, "BBbb"); |
| snapshot1.close(); |
| } |
| |
| @Test public void openWithDirtyKeyDeletesAllFilesForThatKey() throws Exception { |
| cache.close(); |
| File cleanFile0 = getCleanFile("k1", 0); |
| File cleanFile1 = getCleanFile("k1", 1); |
| File dirtyFile0 = getDirtyFile("k1", 0); |
| File dirtyFile1 = getDirtyFile("k1", 1); |
| writeFile(cleanFile0, "A"); |
| writeFile(cleanFile1, "B"); |
| writeFile(dirtyFile0, "C"); |
| writeFile(dirtyFile1, "D"); |
| createJournal("CLEAN k1 1 1", "DIRTY k1"); |
| createNewCache(); |
| assertFalse(fileSystem.exists(cleanFile0)); |
| assertFalse(fileSystem.exists(cleanFile1)); |
| assertFalse(fileSystem.exists(dirtyFile0)); |
| assertFalse(fileSystem.exists(dirtyFile1)); |
| assertNull(cache.get("k1")); |
| } |
| |
| @Test public void openWithInvalidVersionClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournalWithHeader(MAGIC, "0", "100", "2", ""); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| } |
| |
| @Test public void openWithInvalidAppVersionClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournalWithHeader(MAGIC, "1", "101", "2", ""); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| } |
| |
| @Test public void openWithInvalidValueCountClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournalWithHeader(MAGIC, "1", "100", "1", ""); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| } |
| |
| @Test public void openWithInvalidBlankLineClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournalWithHeader(MAGIC, "1", "100", "2", "x"); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| } |
| |
| @Test public void openWithInvalidJournalLineClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournal("CLEAN k1 1 1", "BOGUS"); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| assertNull(cache.get("k1")); |
| } |
| |
| @Test public void openWithInvalidFileSizeClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournal("CLEAN k1 0000x001 1"); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| assertNull(cache.get("k1")); |
| } |
| |
| @Test public void openWithTruncatedLineDiscardsThatLine() throws Exception { |
| cache.close(); |
| writeFile(getCleanFile("k1", 0), "A"); |
| writeFile(getCleanFile("k1", 1), "B"); |
| |
| BufferedSink sink = Okio.buffer(fileSystem.sink(journalFile)); |
| sink.writeUtf8(MAGIC + "\n" + VERSION_1 + "\n100\n2\n\nCLEAN k1 1 1"); // no trailing newline |
| sink.close(); |
| createNewCache(); |
| assertNull(cache.get("k1")); |
| |
| // The journal is not corrupt when editing after a truncated line. |
| set("k1", "C", "D"); |
| |
| cache.close(); |
| createNewCache(); |
| assertValue("k1", "C", "D"); |
| } |
| |
| @Test public void openWithTooManyFileSizesClearsDirectory() throws Exception { |
| cache.close(); |
| generateSomeGarbageFiles(); |
| createJournal("CLEAN k1 1 1 1"); |
| createNewCache(); |
| assertGarbageFilesAllDeleted(); |
| assertNull(cache.get("k1")); |
| } |
| |
| @Test public void keyWithSpaceNotPermitted() throws Exception { |
| try { |
| cache.edit("my key"); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| @Test public void keyWithNewlineNotPermitted() throws Exception { |
| try { |
| cache.edit("my\nkey"); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| @Test public void keyWithCarriageReturnNotPermitted() throws Exception { |
| try { |
| cache.edit("my\rkey"); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| @Test public void nullKeyThrows() throws Exception { |
| try { |
| cache.edit(null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| @Test public void createNewEntryWithTooFewValuesFails() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 1, "A"); |
| try { |
| creator.commit(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| |
| assertFalse(fileSystem.exists(getCleanFile("k1", 0))); |
| assertFalse(fileSystem.exists(getCleanFile("k1", 1))); |
| assertFalse(fileSystem.exists(getDirtyFile("k1", 0))); |
| assertFalse(fileSystem.exists(getDirtyFile("k1", 1))); |
| assertNull(cache.get("k1")); |
| |
| DiskLruCache.Editor creator2 = cache.edit("k1"); |
| setString(creator2, 0, "B"); |
| setString(creator2, 1, "C"); |
| creator2.commit(); |
| } |
| |
| @Test public void revertWithTooFewValues() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 1, "A"); |
| creator.abort(); |
| assertFalse(fileSystem.exists(getCleanFile("k1", 0))); |
| assertFalse(fileSystem.exists(getCleanFile("k1", 1))); |
| assertFalse(fileSystem.exists(getDirtyFile("k1", 0))); |
| assertFalse(fileSystem.exists(getDirtyFile("k1", 1))); |
| assertNull(cache.get("k1")); |
| } |
| |
| @Test public void updateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 0, "A"); |
| setString(creator, 1, "B"); |
| creator.commit(); |
| |
| DiskLruCache.Editor updater = cache.edit("k1"); |
| setString(updater, 0, "C"); |
| updater.commit(); |
| |
| DiskLruCache.Snapshot snapshot = cache.get("k1"); |
| assertSnapshotValue(snapshot, 0, "C"); |
| assertSnapshotValue(snapshot, 1, "B"); |
| snapshot.close(); |
| } |
| |
| @Test public void growMaxSize() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| set("a", "a", "aaa"); // size 4 |
| set("b", "bb", "bbbb"); // size 6 |
| cache.setMaxSize(20); |
| set("c", "c", "c"); // size 12 |
| assertEquals(12, cache.size()); |
| } |
| |
| @Test public void shrinkMaxSizeEvicts() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(20); |
| set("a", "a", "aaa"); // size 4 |
| set("b", "bb", "bbbb"); // size 6 |
| set("c", "c", "c"); // size 12 |
| cache.setMaxSize(10); |
| assertEquals(1, executor.jobs.size()); |
| } |
| |
| @Test public void evictOnInsert() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| |
| set("a", "a", "aaa"); // size 4 |
| set("b", "bb", "bbbb"); // size 6 |
| assertEquals(10, cache.size()); |
| |
| // Cause the size to grow to 12 should evict 'A'. |
| set("c", "c", "c"); |
| cache.flush(); |
| assertEquals(8, cache.size()); |
| assertAbsent("a"); |
| assertValue("b", "bb", "bbbb"); |
| assertValue("c", "c", "c"); |
| |
| // Causing the size to grow to 10 should evict nothing. |
| set("d", "d", "d"); |
| cache.flush(); |
| assertEquals(10, cache.size()); |
| assertAbsent("a"); |
| assertValue("b", "bb", "bbbb"); |
| assertValue("c", "c", "c"); |
| assertValue("d", "d", "d"); |
| |
| // Causing the size to grow to 18 should evict 'B' and 'C'. |
| set("e", "eeee", "eeee"); |
| cache.flush(); |
| assertEquals(10, cache.size()); |
| assertAbsent("a"); |
| assertAbsent("b"); |
| assertAbsent("c"); |
| assertValue("d", "d", "d"); |
| assertValue("e", "eeee", "eeee"); |
| } |
| |
| @Test public void evictOnUpdate() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| |
| set("a", "a", "aa"); // size 3 |
| set("b", "b", "bb"); // size 3 |
| set("c", "c", "cc"); // size 3 |
| assertEquals(9, cache.size()); |
| |
| // Causing the size to grow to 11 should evict 'A'. |
| set("b", "b", "bbbb"); |
| cache.flush(); |
| assertEquals(8, cache.size()); |
| assertAbsent("a"); |
| assertValue("b", "b", "bbbb"); |
| assertValue("c", "c", "cc"); |
| } |
| |
| @Test public void evictionHonorsLruFromCurrentSession() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| set("c", "c", "c"); |
| set("d", "d", "d"); |
| set("e", "e", "e"); |
| cache.get("b").close(); // 'B' is now least recently used. |
| |
| // Causing the size to grow to 12 should evict 'A'. |
| set("f", "f", "f"); |
| // Causing the size to grow to 12 should evict 'C'. |
| set("g", "g", "g"); |
| cache.flush(); |
| assertEquals(10, cache.size()); |
| assertAbsent("a"); |
| assertValue("b", "b", "b"); |
| assertAbsent("c"); |
| assertValue("d", "d", "d"); |
| assertValue("e", "e", "e"); |
| assertValue("f", "f", "f"); |
| } |
| |
| @Test public void evictionHonorsLruFromPreviousSession() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| set("c", "c", "c"); |
| set("d", "d", "d"); |
| set("e", "e", "e"); |
| set("f", "f", "f"); |
| cache.get("b").close(); // 'B' is now least recently used. |
| assertEquals(12, cache.size()); |
| cache.close(); |
| createNewCacheWithSize(10); |
| |
| set("g", "g", "g"); |
| cache.flush(); |
| assertEquals(10, cache.size()); |
| assertAbsent("a"); |
| assertValue("b", "b", "b"); |
| assertAbsent("c"); |
| assertValue("d", "d", "d"); |
| assertValue("e", "e", "e"); |
| assertValue("f", "f", "f"); |
| assertValue("g", "g", "g"); |
| } |
| |
| @Test public void cacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| set("a", "aaaaa", "aaaaaa"); // size=11 |
| cache.flush(); |
| assertAbsent("a"); |
| } |
| |
| @Test public void cacheSingleValueOfSizeGreaterThanMaxSize() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| set("a", "aaaaaaaaaaa", "a"); // size=12 |
| cache.flush(); |
| assertAbsent("a"); |
| } |
| |
| @Test public void constructorDoesNotAllowZeroCacheSize() throws Exception { |
| try { |
| DiskLruCache.create(fileSystem, cacheDir, appVersion, 2, 0); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| @Test public void constructorDoesNotAllowZeroValuesPerEntry() throws Exception { |
| try { |
| DiskLruCache.create(fileSystem, cacheDir, appVersion, 0, 10); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| @Test public void removeAbsentElement() throws Exception { |
| cache.remove("a"); |
| } |
| |
| @Test public void readingTheSameStreamMultipleTimes() throws Exception { |
| set("a", "a", "b"); |
| DiskLruCache.Snapshot snapshot = cache.get("a"); |
| assertSame(snapshot.getSource(0), snapshot.getSource(0)); |
| snapshot.close(); |
| } |
| |
| @Test public void rebuildJournalOnRepeatedReads() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| while (executor.jobs.isEmpty()) { |
| assertValue("a", "a", "a"); |
| assertValue("b", "b", "b"); |
| } |
| } |
| |
| @Test public void rebuildJournalOnRepeatedEdits() throws Exception { |
| while (executor.jobs.isEmpty()) { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| } |
| executor.jobs.removeFirst().run(); |
| |
| // Sanity check that a rebuilt journal behaves normally. |
| assertValue("a", "a", "a"); |
| assertValue("b", "b", "b"); |
| } |
| |
| /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */ |
| @Test public void rebuildJournalOnRepeatedReadsWithOpenAndClose() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| while (executor.jobs.isEmpty()) { |
| assertValue("a", "a", "a"); |
| assertValue("b", "b", "b"); |
| cache.close(); |
| createNewCache(); |
| } |
| } |
| |
| /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */ |
| @Test public void rebuildJournalOnRepeatedEditsWithOpenAndClose() throws Exception { |
| while (executor.jobs.isEmpty()) { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| cache.close(); |
| createNewCache(); |
| } |
| } |
| |
| @Test public void restoreBackupFile() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 0, "ABC"); |
| setString(creator, 1, "DE"); |
| creator.commit(); |
| cache.close(); |
| |
| fileSystem.rename(journalFile, journalBkpFile); |
| assertFalse(fileSystem.exists(journalFile)); |
| |
| createNewCache(); |
| |
| DiskLruCache.Snapshot snapshot = cache.get("k1"); |
| assertSnapshotValue(snapshot, 0, "ABC"); |
| assertSnapshotValue(snapshot, 1, "DE"); |
| |
| assertFalse(fileSystem.exists(journalBkpFile)); |
| assertTrue(fileSystem.exists(journalFile)); |
| } |
| |
| @Test public void journalFileIsPreferredOverBackupFile() throws Exception { |
| DiskLruCache.Editor creator = cache.edit("k1"); |
| setString(creator, 0, "ABC"); |
| setString(creator, 1, "DE"); |
| creator.commit(); |
| cache.flush(); |
| |
| copyFile(journalFile, journalBkpFile); |
| |
| creator = cache.edit("k2"); |
| setString(creator, 0, "F"); |
| setString(creator, 1, "GH"); |
| creator.commit(); |
| cache.close(); |
| |
| assertTrue(fileSystem.exists(journalFile)); |
| assertTrue(fileSystem.exists(journalBkpFile)); |
| |
| createNewCache(); |
| |
| DiskLruCache.Snapshot snapshotA = cache.get("k1"); |
| assertSnapshotValue(snapshotA, 0, "ABC"); |
| assertSnapshotValue(snapshotA, 1, "DE"); |
| |
| DiskLruCache.Snapshot snapshotB = cache.get("k2"); |
| assertSnapshotValue(snapshotB, 0, "F"); |
| assertSnapshotValue(snapshotB, 1, "GH"); |
| |
| assertFalse(fileSystem.exists(journalBkpFile)); |
| assertTrue(fileSystem.exists(journalFile)); |
| } |
| |
| @Test public void openCreatesDirectoryIfNecessary() throws Exception { |
| cache.close(); |
| File dir = tempDir.newFolder("testOpenCreatesDirectoryIfNecessary"); |
| cache = DiskLruCache.create(fileSystem, dir, appVersion, 2, Integer.MAX_VALUE); |
| set("a", "a", "a"); |
| assertTrue(fileSystem.exists(new File(dir, "a.0"))); |
| assertTrue(fileSystem.exists(new File(dir, "a.1"))); |
| assertTrue(fileSystem.exists(new File(dir, "journal"))); |
| } |
| |
| @Test public void fileDeletedExternally() throws Exception { |
| set("a", "a", "a"); |
| fileSystem.delete(getCleanFile("a", 1)); |
| assertNull(cache.get("a")); |
| } |
| |
| @Test public void editSameVersion() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Snapshot snapshot = cache.get("a"); |
| DiskLruCache.Editor editor = snapshot.edit(); |
| setString(editor, 1, "a2"); |
| editor.commit(); |
| assertValue("a", "a", "a2"); |
| } |
| |
| @Test public void editSnapshotAfterChangeAborted() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Snapshot snapshot = cache.get("a"); |
| DiskLruCache.Editor toAbort = snapshot.edit(); |
| setString(toAbort, 0, "b"); |
| toAbort.abort(); |
| DiskLruCache.Editor editor = snapshot.edit(); |
| setString(editor, 1, "a2"); |
| editor.commit(); |
| assertValue("a", "a", "a2"); |
| } |
| |
| @Test public void editSnapshotAfterChangeCommitted() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Snapshot snapshot = cache.get("a"); |
| DiskLruCache.Editor toAbort = snapshot.edit(); |
| setString(toAbort, 0, "b"); |
| toAbort.commit(); |
| assertNull(snapshot.edit()); |
| } |
| |
| @Test public void editSinceEvicted() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| set("a", "aa", "aaa"); // size 5 |
| DiskLruCache.Snapshot snapshot = cache.get("a"); |
| set("b", "bb", "bbb"); // size 5 |
| set("c", "cc", "ccc"); // size 5; will evict 'A' |
| cache.flush(); |
| assertNull(snapshot.edit()); |
| } |
| |
| @Test public void editSinceEvictedAndRecreated() throws Exception { |
| cache.close(); |
| createNewCacheWithSize(10); |
| set("a", "aa", "aaa"); // size 5 |
| DiskLruCache.Snapshot snapshot = cache.get("a"); |
| set("b", "bb", "bbb"); // size 5 |
| set("c", "cc", "ccc"); // size 5; will evict 'A' |
| set("a", "a", "aaaa"); // size 5; will evict 'B' |
| cache.flush(); |
| assertNull(snapshot.edit()); |
| } |
| |
| /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */ |
| @Test public void aggressiveClearingHandlesWrite() throws Exception { |
| fileSystem.deleteContents(tempDir.getRoot()); |
| set("a", "a", "a"); |
| assertValue("a", "a", "a"); |
| } |
| |
| /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */ |
| @Test public void aggressiveClearingHandlesEdit() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Editor a = cache.get("a").edit(); |
| fileSystem.deleteContents(tempDir.getRoot()); |
| setString(a, 1, "a2"); |
| a.commit(); |
| } |
| |
| @Test public void removeHandlesMissingFile() throws Exception { |
| set("a", "a", "a"); |
| getCleanFile("a", 0).delete(); |
| cache.remove("a"); |
| } |
| |
| /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */ |
| @Test public void aggressiveClearingHandlesPartialEdit() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| DiskLruCache.Editor a = cache.get("a").edit(); |
| setString(a, 0, "a1"); |
| fileSystem.deleteContents(tempDir.getRoot()); |
| setString(a, 1, "a2"); |
| a.commit(); |
| assertNull(cache.get("a")); |
| } |
| |
| /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */ |
| @Test public void aggressiveClearingHandlesRead() throws Exception { |
| fileSystem.deleteContents(tempDir.getRoot()); |
| assertNull(cache.get("a")); |
| } |
| |
| /** |
| * We had a long-lived bug where {@link DiskLruCache#trimToSize} could |
| * infinite loop if entries being edited required deletion for the operation |
| * to complete. |
| */ |
| @Test public void trimToSizeWithActiveEdit() throws Exception { |
| set("a", "a1234", "a1234"); |
| DiskLruCache.Editor a = cache.edit("a"); |
| setString(a, 0, "a123"); |
| |
| cache.setMaxSize(8); // Smaller than the sum of active edits! |
| cache.flush(); // Force trimToSize(). |
| assertEquals(0, cache.size()); |
| assertNull(cache.get("a")); |
| |
| // After the edit is completed, its entry is still gone. |
| setString(a, 1, "a1"); |
| a.commit(); |
| assertAbsent("a"); |
| assertEquals(0, cache.size()); |
| } |
| |
| @Test public void evictAll() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| cache.evictAll(); |
| assertEquals(0, cache.size()); |
| assertAbsent("a"); |
| assertAbsent("b"); |
| } |
| |
| @Test public void evictAllWithPartialCreate() throws Exception { |
| DiskLruCache.Editor a = cache.edit("a"); |
| setString(a, 0, "a1"); |
| setString(a, 1, "a2"); |
| cache.evictAll(); |
| assertEquals(0, cache.size()); |
| a.commit(); |
| assertAbsent("a"); |
| } |
| |
| @Test public void evictAllWithPartialEditDoesNotStoreAValue() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Editor a = cache.edit("a"); |
| setString(a, 0, "a1"); |
| setString(a, 1, "a2"); |
| cache.evictAll(); |
| assertEquals(0, cache.size()); |
| a.commit(); |
| assertAbsent("a"); |
| } |
| |
| @Test public void evictAllDoesntInterruptPartialRead() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Snapshot a = cache.get("a"); |
| assertSnapshotValue(a, 0, "a"); |
| cache.evictAll(); |
| assertEquals(0, cache.size()); |
| assertAbsent("a"); |
| assertSnapshotValue(a, 1, "a"); |
| a.close(); |
| } |
| |
| @Test public void editSnapshotAfterEvictAllReturnsNullDueToStaleValue() throws Exception { |
| set("a", "a", "a"); |
| DiskLruCache.Snapshot a = cache.get("a"); |
| cache.evictAll(); |
| assertEquals(0, cache.size()); |
| assertAbsent("a"); |
| assertNull(a.edit()); |
| a.close(); |
| } |
| |
| @Test public void iterator() throws Exception { |
| set("a", "a1", "a2"); |
| set("b", "b1", "b2"); |
| set("c", "c1", "c2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| |
| assertTrue(iterator.hasNext()); |
| DiskLruCache.Snapshot a = iterator.next(); |
| assertEquals("a", a.key()); |
| assertSnapshotValue(a, 0, "a1"); |
| assertSnapshotValue(a, 1, "a2"); |
| a.close(); |
| |
| assertTrue(iterator.hasNext()); |
| DiskLruCache.Snapshot b = iterator.next(); |
| assertEquals("b", b.key()); |
| assertSnapshotValue(b, 0, "b1"); |
| assertSnapshotValue(b, 1, "b2"); |
| b.close(); |
| |
| assertTrue(iterator.hasNext()); |
| DiskLruCache.Snapshot c = iterator.next(); |
| assertEquals("c", c.key()); |
| assertSnapshotValue(c, 0, "c1"); |
| assertSnapshotValue(c, 1, "c2"); |
| c.close(); |
| |
| assertFalse(iterator.hasNext()); |
| try { |
| iterator.next(); |
| fail(); |
| } catch (NoSuchElementException expected) { |
| } |
| } |
| |
| @Test public void iteratorElementsAddedDuringIterationAreOmitted() throws Exception { |
| set("a", "a1", "a2"); |
| set("b", "b1", "b2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| |
| DiskLruCache.Snapshot a = iterator.next(); |
| assertEquals("a", a.key()); |
| a.close(); |
| |
| set("c", "c1", "c2"); |
| |
| DiskLruCache.Snapshot b = iterator.next(); |
| assertEquals("b", b.key()); |
| b.close(); |
| |
| assertFalse(iterator.hasNext()); |
| } |
| |
| @Test public void iteratorElementsUpdatedDuringIterationAreUpdated() throws Exception { |
| set("a", "a1", "a2"); |
| set("b", "b1", "b2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| |
| DiskLruCache.Snapshot a = iterator.next(); |
| assertEquals("a", a.key()); |
| a.close(); |
| |
| set("b", "b3", "b4"); |
| |
| DiskLruCache.Snapshot b = iterator.next(); |
| assertEquals("b", b.key()); |
| assertSnapshotValue(b, 0, "b3"); |
| assertSnapshotValue(b, 1, "b4"); |
| b.close(); |
| } |
| |
| @Test public void iteratorElementsRemovedDuringIterationAreOmitted() throws Exception { |
| set("a", "a1", "a2"); |
| set("b", "b1", "b2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| |
| cache.remove("b"); |
| |
| DiskLruCache.Snapshot a = iterator.next(); |
| assertEquals("a", a.key()); |
| a.close(); |
| |
| assertFalse(iterator.hasNext()); |
| } |
| |
| @Test public void iteratorRemove() throws Exception { |
| set("a", "a1", "a2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| |
| DiskLruCache.Snapshot a = iterator.next(); |
| a.close(); |
| iterator.remove(); |
| |
| assertEquals(null, cache.get("a")); |
| } |
| |
| @Test public void iteratorRemoveBeforeNext() throws Exception { |
| set("a", "a1", "a2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| try { |
| iterator.remove(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| @Test public void iteratorRemoveOncePerCallToNext() throws Exception { |
| set("a", "a1", "a2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| |
| DiskLruCache.Snapshot a = iterator.next(); |
| iterator.remove(); |
| a.close(); |
| |
| try { |
| iterator.remove(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| @Test public void cacheClosedTruncatesIterator() throws Exception { |
| set("a", "a1", "a2"); |
| Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots(); |
| cache.close(); |
| assertFalse(iterator.hasNext()); |
| } |
| |
| @Test public void isClosed_uninitializedCache() throws Exception { |
| // Create an uninitialized cache. |
| cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor); |
| toClose.add(cache); |
| |
| assertFalse(cache.isClosed()); |
| cache.close(); |
| assertTrue(cache.isClosed()); |
| } |
| |
| @Test public void journalWriteFailsDuringEdit() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| |
| // We can't begin the edit if writing 'DIRTY' fails. |
| fileSystem.setFaulty(journalFile, true); |
| assertNull(cache.edit("c")); |
| |
| // Once the journal has a failure, subsequent writes aren't permitted. |
| fileSystem.setFaulty(journalFile, false); |
| assertNull(cache.edit("d")); |
| |
| // Confirm that the fault didn't corrupt entries stored before the fault was introduced. |
| cache.close(); |
| cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor); |
| assertValue("a", "a", "a"); |
| assertValue("b", "b", "b"); |
| assertAbsent("c"); |
| assertAbsent("d"); |
| } |
| |
| /** |
| * We had a bug where the cache was left in an inconsistent state after a journal write failed. |
| * https://github.com/square/okhttp/issues/1211 |
| */ |
| @Test public void journalWriteFailsDuringEditorCommit() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| |
| // Create an entry that fails to write to the journal during commit. |
| DiskLruCache.Editor editor = cache.edit("c"); |
| setString(editor, 0, "c"); |
| setString(editor, 1, "c"); |
| fileSystem.setFaulty(journalFile, true); |
| editor.commit(); |
| |
| // Once the journal has a failure, subsequent writes aren't permitted. |
| fileSystem.setFaulty(journalFile, false); |
| assertNull(cache.edit("d")); |
| |
| // Confirm that the fault didn't corrupt entries stored before the fault was introduced. |
| cache.close(); |
| cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor); |
| assertValue("a", "a", "a"); |
| assertValue("b", "b", "b"); |
| assertAbsent("c"); |
| assertAbsent("d"); |
| } |
| |
| @Test public void journalWriteFailsDuringEditorAbort() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| |
| // Create an entry that fails to write to the journal during abort. |
| DiskLruCache.Editor editor = cache.edit("c"); |
| setString(editor, 0, "c"); |
| setString(editor, 1, "c"); |
| fileSystem.setFaulty(journalFile, true); |
| editor.abort(); |
| |
| // Once the journal has a failure, subsequent writes aren't permitted. |
| fileSystem.setFaulty(journalFile, false); |
| assertNull(cache.edit("d")); |
| |
| // Confirm that the fault didn't corrupt entries stored before the fault was introduced. |
| cache.close(); |
| cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor); |
| assertValue("a", "a", "a"); |
| assertValue("b", "b", "b"); |
| assertAbsent("c"); |
| assertAbsent("d"); |
| } |
| |
| @Test public void journalWriteFailsDuringRemove() throws Exception { |
| set("a", "a", "a"); |
| set("b", "b", "b"); |
| |
| // Remove, but the journal write will fail. |
| fileSystem.setFaulty(journalFile, true); |
| assertTrue(cache.remove("a")); |
| |
| // Confirm that the entry was still removed. |
| fileSystem.setFaulty(journalFile, false); |
| cache.close(); |
| cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor); |
| assertAbsent("a"); |
| assertValue("b", "b", "b"); |
| } |
| |
| private void assertJournalEquals(String... expectedBodyLines) throws Exception { |
| List<String> expectedLines = new ArrayList<>(); |
| expectedLines.add(MAGIC); |
| expectedLines.add(VERSION_1); |
| expectedLines.add("100"); |
| expectedLines.add("2"); |
| expectedLines.add(""); |
| expectedLines.addAll(Arrays.asList(expectedBodyLines)); |
| assertEquals(expectedLines, readJournalLines()); |
| } |
| |
| private void createJournal(String... bodyLines) throws Exception { |
| createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines); |
| } |
| |
| private void createJournalWithHeader(String magic, String version, String appVersion, |
| String valueCount, String blank, String... bodyLines) throws Exception { |
| BufferedSink sink = Okio.buffer(fileSystem.sink(journalFile)); |
| sink.writeUtf8(magic + "\n"); |
| sink.writeUtf8(version + "\n"); |
| sink.writeUtf8(appVersion + "\n"); |
| sink.writeUtf8(valueCount + "\n"); |
| sink.writeUtf8(blank + "\n"); |
| for (String line : bodyLines) { |
| sink.writeUtf8(line); |
| sink.writeUtf8("\n"); |
| } |
| sink.close(); |
| } |
| |
| private List<String> readJournalLines() throws Exception { |
| List<String> result = new ArrayList<>(); |
| BufferedSource source = Okio.buffer(fileSystem.source(journalFile)); |
| for (String line; (line = source.readUtf8Line()) != null; ) { |
| result.add(line); |
| } |
| source.close(); |
| return result; |
| } |
| |
| private File getCleanFile(String key, int index) { |
| return new File(cacheDir, key + "." + index); |
| } |
| |
| private File getDirtyFile(String key, int index) { |
| return new File(cacheDir, key + "." + index + ".tmp"); |
| } |
| |
| private String readFile(File file) throws Exception { |
| BufferedSource source = Okio.buffer(fileSystem.source(file)); |
| String result = source.readUtf8(); |
| source.close(); |
| return result; |
| } |
| |
| public void writeFile(File file, String content) throws Exception { |
| BufferedSink sink = Okio.buffer(fileSystem.sink(file)); |
| sink.writeUtf8(content); |
| sink.close(); |
| } |
| |
| private static void assertInoperable(DiskLruCache.Editor editor) throws Exception { |
| try { |
| setString(editor, 0, "A"); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| editor.newSource(0); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| editor.newSink(0); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| editor.commit(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| try { |
| editor.abort(); |
| fail(); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| |
| private void generateSomeGarbageFiles() throws Exception { |
| File dir1 = new File(cacheDir, "dir1"); |
| File dir2 = new File(dir1, "dir2"); |
| writeFile(getCleanFile("g1", 0), "A"); |
| writeFile(getCleanFile("g1", 1), "B"); |
| writeFile(getCleanFile("g2", 0), "C"); |
| writeFile(getCleanFile("g2", 1), "D"); |
| writeFile(getCleanFile("g2", 1), "D"); |
| writeFile(new File(cacheDir, "otherFile0"), "E"); |
| writeFile(new File(dir2, "otherFile1"), "F"); |
| } |
| |
| private void assertGarbageFilesAllDeleted() throws Exception { |
| assertFalse(fileSystem.exists(getCleanFile("g1", 0))); |
| assertFalse(fileSystem.exists(getCleanFile("g1", 1))); |
| assertFalse(fileSystem.exists(getCleanFile("g2", 0))); |
| assertFalse(fileSystem.exists(getCleanFile("g2", 1))); |
| assertFalse(fileSystem.exists(new File(cacheDir, "otherFile0"))); |
| assertFalse(fileSystem.exists(new File(cacheDir, "dir1"))); |
| } |
| |
| private void set(String key, String value0, String value1) throws Exception { |
| DiskLruCache.Editor editor = cache.edit(key); |
| setString(editor, 0, value0); |
| setString(editor, 1, value1); |
| editor.commit(); |
| } |
| |
| public static void setString(DiskLruCache.Editor editor, int index, String value) throws IOException { |
| BufferedSink writer = Okio.buffer(editor.newSink(index)); |
| writer.writeUtf8(value); |
| writer.close(); |
| } |
| |
| private void assertAbsent(String key) throws Exception { |
| DiskLruCache.Snapshot snapshot = cache.get(key); |
| if (snapshot != null) { |
| snapshot.close(); |
| fail(); |
| } |
| assertFalse(fileSystem.exists(getCleanFile(key, 0))); |
| assertFalse(fileSystem.exists(getCleanFile(key, 1))); |
| assertFalse(fileSystem.exists(getDirtyFile(key, 0))); |
| assertFalse(fileSystem.exists(getDirtyFile(key, 1))); |
| } |
| |
| private void assertValue(String key, String value0, String value1) throws Exception { |
| DiskLruCache.Snapshot snapshot = cache.get(key); |
| assertSnapshotValue(snapshot, 0, value0); |
| assertSnapshotValue(snapshot, 1, value1); |
| assertTrue(fileSystem.exists(getCleanFile(key, 0))); |
| assertTrue(fileSystem.exists(getCleanFile(key, 1))); |
| snapshot.close(); |
| } |
| |
| private void assertSnapshotValue(DiskLruCache.Snapshot snapshot, int index, String value) |
| throws IOException { |
| assertEquals(value, sourceAsString(snapshot.getSource(index))); |
| assertEquals(value.length(), snapshot.getLength(index)); |
| } |
| |
| private String sourceAsString(Source source) throws IOException { |
| return source != null ? Okio.buffer(source).readUtf8() : null; |
| } |
| |
| private void copyFile(File from, File to) throws IOException { |
| Source source = fileSystem.source(from); |
| BufferedSink sink = Okio.buffer(fileSystem.sink(to)); |
| sink.writeAll(source); |
| source.close(); |
| sink.close(); |
| } |
| |
| private static class TestExecutor implements Executor { |
| final Deque<Runnable> jobs = new ArrayDeque<>(); |
| |
| @Override public void execute(Runnable command) { |
| jobs.addLast(command); |
| } |
| } |
| } |