Home | History | Annotate | Download | only in internal
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.squareup.okhttp.internal;
     17 
     18 import com.squareup.okhttp.internal.io.FileSystem;
     19 import java.io.File;
     20 import java.io.IOException;
     21 import java.util.ArrayDeque;
     22 import java.util.ArrayList;
     23 import java.util.Arrays;
     24 import java.util.Deque;
     25 import java.util.Iterator;
     26 import java.util.List;
     27 import java.util.NoSuchElementException;
     28 import java.util.concurrent.Executor;
     29 import okio.BufferedSink;
     30 import okio.BufferedSource;
     31 import okio.Okio;
     32 import okio.Source;
     33 import org.junit.After;
     34 import org.junit.Before;
     35 import org.junit.Rule;
     36 import org.junit.Test;
     37 import org.junit.rules.TemporaryFolder;
     38 import org.junit.rules.Timeout;
     39 
     40 import static com.squareup.okhttp.internal.DiskLruCache.JOURNAL_FILE;
     41 import static com.squareup.okhttp.internal.DiskLruCache.JOURNAL_FILE_BACKUP;
     42 import static com.squareup.okhttp.internal.DiskLruCache.MAGIC;
     43 import static com.squareup.okhttp.internal.DiskLruCache.VERSION_1;
     44 import static org.junit.Assert.assertEquals;
     45 import static org.junit.Assert.assertFalse;
     46 import static org.junit.Assert.assertNull;
     47 import static org.junit.Assert.assertSame;
     48 import static org.junit.Assert.assertTrue;
     49 import static org.junit.Assert.fail;
     50 
     51 public final class DiskLruCacheTest {
     52   @Rule public final TemporaryFolder tempDir = new TemporaryFolder();
     53   @Rule public final Timeout timeout = new Timeout(30 * 1000);
     54 
     55   private final FaultyFileSystem fileSystem = new FaultyFileSystem(FileSystem.SYSTEM);
     56   private final int appVersion = 100;
     57   private File cacheDir;
     58   private File journalFile;
     59   private File journalBkpFile;
     60   private final TestExecutor executor = new TestExecutor();
     61 
     62   private DiskLruCache cache;
     63   private final Deque<DiskLruCache> toClose = new ArrayDeque<>();
     64 
     65   private void createNewCache() throws IOException {
     66     createNewCacheWithSize(Integer.MAX_VALUE);
     67   }
     68 
     69   private void createNewCacheWithSize(int maxSize) throws IOException {
     70     cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, maxSize, executor);
     71     synchronized (cache) {
     72       cache.initialize();
     73     }
     74     toClose.add(cache);
     75   }
     76 
     77   @Before public void setUp() throws Exception {
     78     cacheDir = tempDir.getRoot();
     79     journalFile = new File(cacheDir, JOURNAL_FILE);
     80     journalBkpFile = new File(cacheDir, JOURNAL_FILE_BACKUP);
     81     createNewCache();
     82   }
     83 
     84   @After public void tearDown() throws Exception {
     85     while (!toClose.isEmpty()) {
     86       toClose.pop().close();
     87     }
     88   }
     89 
     90   @Test public void emptyCache() throws Exception {
     91     cache.close();
     92     assertJournalEquals();
     93   }
     94 
     95   @Test public void validateKey() throws Exception {
     96     String key = null;
     97     try {
     98       key = "has_space ";
     99       cache.edit(key);
    100       fail("Exepcting an IllegalArgumentException as the key was invalid.");
    101     } catch (IllegalArgumentException iae) {
    102       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
    103     }
    104     try {
    105       key = "has_CR\r";
    106       cache.edit(key);
    107       fail("Exepcting an IllegalArgumentException as the key was invalid.");
    108     } catch (IllegalArgumentException iae) {
    109       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
    110     }
    111     try {
    112       key = "has_LF\n";
    113       cache.edit(key);
    114       fail("Exepcting an IllegalArgumentException as the key was invalid.");
    115     } catch (IllegalArgumentException iae) {
    116       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
    117     }
    118     try {
    119       key = "has_invalid/";
    120       cache.edit(key);
    121       fail("Exepcting an IllegalArgumentException as the key was invalid.");
    122     } catch (IllegalArgumentException iae) {
    123       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
    124     }
    125     try {
    126       key = "has_invalid\u2603";
    127       cache.edit(key);
    128       fail("Exepcting an IllegalArgumentException as the key was invalid.");
    129     } catch (IllegalArgumentException iae) {
    130       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
    131     }
    132     try {
    133       key = "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long_"
    134           + "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long";
    135       cache.edit(key);
    136       fail("Exepcting an IllegalArgumentException as the key was too long.");
    137     } catch (IllegalArgumentException iae) {
    138       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
    139     }
    140 
    141     // Test valid cases.
    142 
    143     // Exactly 120.
    144     key = "0123456789012345678901234567890123456789012345678901234567890123456789"
    145         + "01234567890123456789012345678901234567890123456789";
    146     cache.edit(key).abort();
    147     // Contains all valid characters.
    148     key = "abcdefghijklmnopqrstuvwxyz_0123456789";
    149     cache.edit(key).abort();
    150     // Contains dash.
    151     key = "-20384573948576";
    152     cache.edit(key).abort();
    153   }
    154 
    155   @Test public void writeAndReadEntry() throws Exception {
    156     DiskLruCache.Editor creator = cache.edit("k1");
    157     setString(creator, 0, "ABC");
    158     setString(creator, 1, "DE");
    159     assertNull(creator.newSource(0));
    160     assertNull(creator.newSource(1));
    161     creator.commit();
    162 
    163     DiskLruCache.Snapshot snapshot = cache.get("k1");
    164     assertSnapshotValue(snapshot, 0, "ABC");
    165     assertSnapshotValue(snapshot, 1, "DE");
    166   }
    167 
    168   @Test public void readAndWriteEntryAcrossCacheOpenAndClose() throws Exception {
    169     DiskLruCache.Editor creator = cache.edit("k1");
    170     setString(creator, 0, "A");
    171     setString(creator, 1, "B");
    172     creator.commit();
    173     cache.close();
    174 
    175     createNewCache();
    176     DiskLruCache.Snapshot snapshot = cache.get("k1");
    177     assertSnapshotValue(snapshot, 0, "A");
    178     assertSnapshotValue(snapshot, 1, "B");
    179     snapshot.close();
    180   }
    181 
    182   @Test public void readAndWriteEntryWithoutProperClose() throws Exception {
    183     DiskLruCache.Editor creator = cache.edit("k1");
    184     setString(creator, 0, "A");
    185     setString(creator, 1, "B");
    186     creator.commit();
    187 
    188     // Simulate a dirty close of 'cache' by opening the cache directory again.
    189     createNewCache();
    190     DiskLruCache.Snapshot snapshot = cache.get("k1");
    191     assertSnapshotValue(snapshot, 0, "A");
    192     assertSnapshotValue(snapshot, 1, "B");
    193     snapshot.close();
    194   }
    195 
    196   @Test public void journalWithEditAndPublish() throws Exception {
    197     DiskLruCache.Editor creator = cache.edit("k1");
    198     assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed.
    199     setString(creator, 0, "AB");
    200     setString(creator, 1, "C");
    201     creator.commit();
    202     cache.close();
    203     assertJournalEquals("DIRTY k1", "CLEAN k1 2 1");
    204   }
    205 
    206   @Test public void revertedNewFileIsRemoveInJournal() throws Exception {
    207     DiskLruCache.Editor creator = cache.edit("k1");
    208     assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed.
    209     setString(creator, 0, "AB");
    210     setString(creator, 1, "C");
    211     creator.abort();
    212     cache.close();
    213     assertJournalEquals("DIRTY k1", "REMOVE k1");
    214   }
    215 
    216   @Test public void unterminatedEditIsRevertedOnClose() throws Exception {
    217     cache.edit("k1");
    218     cache.close();
    219     assertJournalEquals("DIRTY k1", "REMOVE k1");
    220   }
    221 
    222   @Test public void journalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception {
    223     DiskLruCache.Editor creator = cache.edit("k1");
    224     assertNull(cache.get("k1"));
    225     setString(creator, 0, "A");
    226     setString(creator, 1, "BC");
    227     creator.commit();
    228     cache.close();
    229     assertJournalEquals("DIRTY k1", "CLEAN k1 1 2");
    230   }
    231 
    232   @Test public void journalWithEditAndPublishAndRead() throws Exception {
    233     DiskLruCache.Editor k1Creator = cache.edit("k1");
    234     setString(k1Creator, 0, "AB");
    235     setString(k1Creator, 1, "C");
    236     k1Creator.commit();
    237     DiskLruCache.Editor k2Creator = cache.edit("k2");
    238     setString(k2Creator, 0, "DEF");
    239     setString(k2Creator, 1, "G");
    240     k2Creator.commit();
    241     DiskLruCache.Snapshot k1Snapshot = cache.get("k1");
    242     k1Snapshot.close();
    243     cache.close();
    244     assertJournalEquals("DIRTY k1", "CLEAN k1 2 1", "DIRTY k2", "CLEAN k2 3 1", "READ k1");
    245   }
    246 
    247   @Test public void cannotOperateOnEditAfterPublish() throws Exception {
    248     DiskLruCache.Editor editor = cache.edit("k1");
    249     setString(editor, 0, "A");
    250     setString(editor, 1, "B");
    251     editor.commit();
    252     assertInoperable(editor);
    253   }
    254 
    255   @Test public void cannotOperateOnEditAfterRevert() throws Exception {
    256     DiskLruCache.Editor editor = cache.edit("k1");
    257     setString(editor, 0, "A");
    258     setString(editor, 1, "B");
    259     editor.abort();
    260     assertInoperable(editor);
    261   }
    262 
    263   @Test public void explicitRemoveAppliedToDiskImmediately() throws Exception {
    264     DiskLruCache.Editor editor = cache.edit("k1");
    265     setString(editor, 0, "ABC");
    266     setString(editor, 1, "B");
    267     editor.commit();
    268     File k1 = getCleanFile("k1", 0);
    269     assertEquals("ABC", readFile(k1));
    270     cache.remove("k1");
    271     assertFalse(fileSystem.exists(k1));
    272   }
    273 
    274   @Test public void removePreventsActiveEditFromStoringAValue() throws Exception {
    275     set("a", "a", "a");
    276     DiskLruCache.Editor a = cache.edit("a");
    277     setString(a, 0, "a1");
    278     assertTrue(cache.remove("a"));
    279     setString(a, 1, "a2");
    280     a.commit();
    281     assertAbsent("a");
    282   }
    283 
    284   /**
    285    * Each read sees a snapshot of the file at the time read was called.
    286    * This means that two reads of the same key can see different data.
    287    */
    288   @Test public void readAndWriteOverlapsMaintainConsistency() throws Exception {
    289     DiskLruCache.Editor v1Creator = cache.edit("k1");
    290     setString(v1Creator, 0, "AAaa");
    291     setString(v1Creator, 1, "BBbb");
    292     v1Creator.commit();
    293 
    294     DiskLruCache.Snapshot snapshot1 = cache.get("k1");
    295     BufferedSource inV1 = Okio.buffer(snapshot1.getSource(0));
    296     assertEquals('A', inV1.readByte());
    297     assertEquals('A', inV1.readByte());
    298 
    299     DiskLruCache.Editor v1Updater = cache.edit("k1");
    300     setString(v1Updater, 0, "CCcc");
    301     setString(v1Updater, 1, "DDdd");
    302     v1Updater.commit();
    303 
    304     DiskLruCache.Snapshot snapshot2 = cache.get("k1");
    305     assertSnapshotValue(snapshot2, 0, "CCcc");
    306     assertSnapshotValue(snapshot2, 1, "DDdd");
    307     snapshot2.close();
    308 
    309     assertEquals('a', inV1.readByte());
    310     assertEquals('a', inV1.readByte());
    311     assertSnapshotValue(snapshot1, 1, "BBbb");
    312     snapshot1.close();
    313   }
    314 
    315   @Test public void openWithDirtyKeyDeletesAllFilesForThatKey() throws Exception {
    316     cache.close();
    317     File cleanFile0 = getCleanFile("k1", 0);
    318     File cleanFile1 = getCleanFile("k1", 1);
    319     File dirtyFile0 = getDirtyFile("k1", 0);
    320     File dirtyFile1 = getDirtyFile("k1", 1);
    321     writeFile(cleanFile0, "A");
    322     writeFile(cleanFile1, "B");
    323     writeFile(dirtyFile0, "C");
    324     writeFile(dirtyFile1, "D");
    325     createJournal("CLEAN k1 1 1", "DIRTY   k1");
    326     createNewCache();
    327     assertFalse(fileSystem.exists(cleanFile0));
    328     assertFalse(fileSystem.exists(cleanFile1));
    329     assertFalse(fileSystem.exists(dirtyFile0));
    330     assertFalse(fileSystem.exists(dirtyFile1));
    331     assertNull(cache.get("k1"));
    332   }
    333 
    334   @Test public void openWithInvalidVersionClearsDirectory() throws Exception {
    335     cache.close();
    336     generateSomeGarbageFiles();
    337     createJournalWithHeader(MAGIC, "0", "100", "2", "");
    338     createNewCache();
    339     assertGarbageFilesAllDeleted();
    340   }
    341 
    342   @Test public void openWithInvalidAppVersionClearsDirectory() throws Exception {
    343     cache.close();
    344     generateSomeGarbageFiles();
    345     createJournalWithHeader(MAGIC, "1", "101", "2", "");
    346     createNewCache();
    347     assertGarbageFilesAllDeleted();
    348   }
    349 
    350   @Test public void openWithInvalidValueCountClearsDirectory() throws Exception {
    351     cache.close();
    352     generateSomeGarbageFiles();
    353     createJournalWithHeader(MAGIC, "1", "100", "1", "");
    354     createNewCache();
    355     assertGarbageFilesAllDeleted();
    356   }
    357 
    358   @Test public void openWithInvalidBlankLineClearsDirectory() throws Exception {
    359     cache.close();
    360     generateSomeGarbageFiles();
    361     createJournalWithHeader(MAGIC, "1", "100", "2", "x");
    362     createNewCache();
    363     assertGarbageFilesAllDeleted();
    364   }
    365 
    366   @Test public void openWithInvalidJournalLineClearsDirectory() throws Exception {
    367     cache.close();
    368     generateSomeGarbageFiles();
    369     createJournal("CLEAN k1 1 1", "BOGUS");
    370     createNewCache();
    371     assertGarbageFilesAllDeleted();
    372     assertNull(cache.get("k1"));
    373   }
    374 
    375   @Test public void openWithInvalidFileSizeClearsDirectory() throws Exception {
    376     cache.close();
    377     generateSomeGarbageFiles();
    378     createJournal("CLEAN k1 0000x001 1");
    379     createNewCache();
    380     assertGarbageFilesAllDeleted();
    381     assertNull(cache.get("k1"));
    382   }
    383 
    384   @Test public void openWithTruncatedLineDiscardsThatLine() throws Exception {
    385     cache.close();
    386     writeFile(getCleanFile("k1", 0), "A");
    387     writeFile(getCleanFile("k1", 1), "B");
    388 
    389     BufferedSink sink = Okio.buffer(fileSystem.sink(journalFile));
    390     sink.writeUtf8(MAGIC + "\n" + VERSION_1 + "\n100\n2\n\nCLEAN k1 1 1"); // no trailing newline
    391     sink.close();
    392     createNewCache();
    393     assertNull(cache.get("k1"));
    394 
    395     // The journal is not corrupt when editing after a truncated line.
    396     set("k1", "C", "D");
    397 
    398     cache.close();
    399     createNewCache();
    400     assertValue("k1", "C", "D");
    401   }
    402 
    403   @Test public void openWithTooManyFileSizesClearsDirectory() throws Exception {
    404     cache.close();
    405     generateSomeGarbageFiles();
    406     createJournal("CLEAN k1 1 1 1");
    407     createNewCache();
    408     assertGarbageFilesAllDeleted();
    409     assertNull(cache.get("k1"));
    410   }
    411 
    412   @Test public void keyWithSpaceNotPermitted() throws Exception {
    413     try {
    414       cache.edit("my key");
    415       fail();
    416     } catch (IllegalArgumentException expected) {
    417     }
    418   }
    419 
    420   @Test public void keyWithNewlineNotPermitted() throws Exception {
    421     try {
    422       cache.edit("my\nkey");
    423       fail();
    424     } catch (IllegalArgumentException expected) {
    425     }
    426   }
    427 
    428   @Test public void keyWithCarriageReturnNotPermitted() throws Exception {
    429     try {
    430       cache.edit("my\rkey");
    431       fail();
    432     } catch (IllegalArgumentException expected) {
    433     }
    434   }
    435 
    436   @Test public void nullKeyThrows() throws Exception {
    437     try {
    438       cache.edit(null);
    439       fail();
    440     } catch (NullPointerException expected) {
    441     }
    442   }
    443 
    444   @Test public void createNewEntryWithTooFewValuesFails() throws Exception {
    445     DiskLruCache.Editor creator = cache.edit("k1");
    446     setString(creator, 1, "A");
    447     try {
    448       creator.commit();
    449       fail();
    450     } catch (IllegalStateException expected) {
    451     }
    452 
    453     assertFalse(fileSystem.exists(getCleanFile("k1", 0)));
    454     assertFalse(fileSystem.exists(getCleanFile("k1", 1)));
    455     assertFalse(fileSystem.exists(getDirtyFile("k1", 0)));
    456     assertFalse(fileSystem.exists(getDirtyFile("k1", 1)));
    457     assertNull(cache.get("k1"));
    458 
    459     DiskLruCache.Editor creator2 = cache.edit("k1");
    460     setString(creator2, 0, "B");
    461     setString(creator2, 1, "C");
    462     creator2.commit();
    463   }
    464 
    465   @Test public void revertWithTooFewValues() throws Exception {
    466     DiskLruCache.Editor creator = cache.edit("k1");
    467     setString(creator, 1, "A");
    468     creator.abort();
    469     assertFalse(fileSystem.exists(getCleanFile("k1", 0)));
    470     assertFalse(fileSystem.exists(getCleanFile("k1", 1)));
    471     assertFalse(fileSystem.exists(getDirtyFile("k1", 0)));
    472     assertFalse(fileSystem.exists(getDirtyFile("k1", 1)));
    473     assertNull(cache.get("k1"));
    474   }
    475 
    476   @Test public void updateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception {
    477     DiskLruCache.Editor creator = cache.edit("k1");
    478     setString(creator, 0, "A");
    479     setString(creator, 1, "B");
    480     creator.commit();
    481 
    482     DiskLruCache.Editor updater = cache.edit("k1");
    483     setString(updater, 0, "C");
    484     updater.commit();
    485 
    486     DiskLruCache.Snapshot snapshot = cache.get("k1");
    487     assertSnapshotValue(snapshot, 0, "C");
    488     assertSnapshotValue(snapshot, 1, "B");
    489     snapshot.close();
    490   }
    491 
    492   @Test public void growMaxSize() throws Exception {
    493     cache.close();
    494     createNewCacheWithSize(10);
    495     set("a", "a", "aaa"); // size 4
    496     set("b", "bb", "bbbb"); // size 6
    497     cache.setMaxSize(20);
    498     set("c", "c", "c"); // size 12
    499     assertEquals(12, cache.size());
    500   }
    501 
    502   @Test public void shrinkMaxSizeEvicts() throws Exception {
    503     cache.close();
    504     createNewCacheWithSize(20);
    505     set("a", "a", "aaa"); // size 4
    506     set("b", "bb", "bbbb"); // size 6
    507     set("c", "c", "c"); // size 12
    508     cache.setMaxSize(10);
    509     assertEquals(1, executor.jobs.size());
    510   }
    511 
    512   @Test public void evictOnInsert() throws Exception {
    513     cache.close();
    514     createNewCacheWithSize(10);
    515 
    516     set("a", "a", "aaa"); // size 4
    517     set("b", "bb", "bbbb"); // size 6
    518     assertEquals(10, cache.size());
    519 
    520     // Cause the size to grow to 12 should evict 'A'.
    521     set("c", "c", "c");
    522     cache.flush();
    523     assertEquals(8, cache.size());
    524     assertAbsent("a");
    525     assertValue("b", "bb", "bbbb");
    526     assertValue("c", "c", "c");
    527 
    528     // Causing the size to grow to 10 should evict nothing.
    529     set("d", "d", "d");
    530     cache.flush();
    531     assertEquals(10, cache.size());
    532     assertAbsent("a");
    533     assertValue("b", "bb", "bbbb");
    534     assertValue("c", "c", "c");
    535     assertValue("d", "d", "d");
    536 
    537     // Causing the size to grow to 18 should evict 'B' and 'C'.
    538     set("e", "eeee", "eeee");
    539     cache.flush();
    540     assertEquals(10, cache.size());
    541     assertAbsent("a");
    542     assertAbsent("b");
    543     assertAbsent("c");
    544     assertValue("d", "d", "d");
    545     assertValue("e", "eeee", "eeee");
    546   }
    547 
    548   @Test public void evictOnUpdate() throws Exception {
    549     cache.close();
    550     createNewCacheWithSize(10);
    551 
    552     set("a", "a", "aa"); // size 3
    553     set("b", "b", "bb"); // size 3
    554     set("c", "c", "cc"); // size 3
    555     assertEquals(9, cache.size());
    556 
    557     // Causing the size to grow to 11 should evict 'A'.
    558     set("b", "b", "bbbb");
    559     cache.flush();
    560     assertEquals(8, cache.size());
    561     assertAbsent("a");
    562     assertValue("b", "b", "bbbb");
    563     assertValue("c", "c", "cc");
    564   }
    565 
    566   @Test public void evictionHonorsLruFromCurrentSession() throws Exception {
    567     cache.close();
    568     createNewCacheWithSize(10);
    569     set("a", "a", "a");
    570     set("b", "b", "b");
    571     set("c", "c", "c");
    572     set("d", "d", "d");
    573     set("e", "e", "e");
    574     cache.get("b").close(); // 'B' is now least recently used.
    575 
    576     // Causing the size to grow to 12 should evict 'A'.
    577     set("f", "f", "f");
    578     // Causing the size to grow to 12 should evict 'C'.
    579     set("g", "g", "g");
    580     cache.flush();
    581     assertEquals(10, cache.size());
    582     assertAbsent("a");
    583     assertValue("b", "b", "b");
    584     assertAbsent("c");
    585     assertValue("d", "d", "d");
    586     assertValue("e", "e", "e");
    587     assertValue("f", "f", "f");
    588   }
    589 
    590   @Test public void evictionHonorsLruFromPreviousSession() throws Exception {
    591     set("a", "a", "a");
    592     set("b", "b", "b");
    593     set("c", "c", "c");
    594     set("d", "d", "d");
    595     set("e", "e", "e");
    596     set("f", "f", "f");
    597     cache.get("b").close(); // 'B' is now least recently used.
    598     assertEquals(12, cache.size());
    599     cache.close();
    600     createNewCacheWithSize(10);
    601 
    602     set("g", "g", "g");
    603     cache.flush();
    604     assertEquals(10, cache.size());
    605     assertAbsent("a");
    606     assertValue("b", "b", "b");
    607     assertAbsent("c");
    608     assertValue("d", "d", "d");
    609     assertValue("e", "e", "e");
    610     assertValue("f", "f", "f");
    611     assertValue("g", "g", "g");
    612   }
    613 
    614   @Test public void cacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception {
    615     cache.close();
    616     createNewCacheWithSize(10);
    617     set("a", "aaaaa", "aaaaaa"); // size=11
    618     cache.flush();
    619     assertAbsent("a");
    620   }
    621 
    622   @Test public void cacheSingleValueOfSizeGreaterThanMaxSize() throws Exception {
    623     cache.close();
    624     createNewCacheWithSize(10);
    625     set("a", "aaaaaaaaaaa", "a"); // size=12
    626     cache.flush();
    627     assertAbsent("a");
    628   }
    629 
    630   @Test public void constructorDoesNotAllowZeroCacheSize() throws Exception {
    631     try {
    632       DiskLruCache.create(fileSystem, cacheDir, appVersion, 2, 0);
    633       fail();
    634     } catch (IllegalArgumentException expected) {
    635     }
    636   }
    637 
    638   @Test public void constructorDoesNotAllowZeroValuesPerEntry() throws Exception {
    639     try {
    640       DiskLruCache.create(fileSystem, cacheDir, appVersion, 0, 10);
    641       fail();
    642     } catch (IllegalArgumentException expected) {
    643     }
    644   }
    645 
    646   @Test public void removeAbsentElement() throws Exception {
    647     cache.remove("a");
    648   }
    649 
    650   @Test public void readingTheSameStreamMultipleTimes() throws Exception {
    651     set("a", "a", "b");
    652     DiskLruCache.Snapshot snapshot = cache.get("a");
    653     assertSame(snapshot.getSource(0), snapshot.getSource(0));
    654     snapshot.close();
    655   }
    656 
    657   @Test public void rebuildJournalOnRepeatedReads() throws Exception {
    658     set("a", "a", "a");
    659     set("b", "b", "b");
    660     while (executor.jobs.isEmpty()) {
    661       assertValue("a", "a", "a");
    662       assertValue("b", "b", "b");
    663     }
    664   }
    665 
    666   @Test public void rebuildJournalOnRepeatedEdits() throws Exception {
    667     while (executor.jobs.isEmpty()) {
    668       set("a", "a", "a");
    669       set("b", "b", "b");
    670     }
    671     executor.jobs.removeFirst().run();
    672 
    673     // Sanity check that a rebuilt journal behaves normally.
    674     assertValue("a", "a", "a");
    675     assertValue("b", "b", "b");
    676   }
    677 
    678   /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */
    679   @Test public void rebuildJournalOnRepeatedReadsWithOpenAndClose() throws Exception {
    680     set("a", "a", "a");
    681     set("b", "b", "b");
    682     while (executor.jobs.isEmpty()) {
    683       assertValue("a", "a", "a");
    684       assertValue("b", "b", "b");
    685       cache.close();
    686       createNewCache();
    687     }
    688   }
    689 
    690   /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */
    691   @Test public void rebuildJournalOnRepeatedEditsWithOpenAndClose() throws Exception {
    692     while (executor.jobs.isEmpty()) {
    693       set("a", "a", "a");
    694       set("b", "b", "b");
    695       cache.close();
    696       createNewCache();
    697     }
    698   }
    699 
    700   @Test public void restoreBackupFile() throws Exception {
    701     DiskLruCache.Editor creator = cache.edit("k1");
    702     setString(creator, 0, "ABC");
    703     setString(creator, 1, "DE");
    704     creator.commit();
    705     cache.close();
    706 
    707     fileSystem.rename(journalFile, journalBkpFile);
    708     assertFalse(fileSystem.exists(journalFile));
    709 
    710     createNewCache();
    711 
    712     DiskLruCache.Snapshot snapshot = cache.get("k1");
    713     assertSnapshotValue(snapshot, 0, "ABC");
    714     assertSnapshotValue(snapshot, 1, "DE");
    715 
    716     assertFalse(fileSystem.exists(journalBkpFile));
    717     assertTrue(fileSystem.exists(journalFile));
    718   }
    719 
    720   @Test public void journalFileIsPreferredOverBackupFile() throws Exception {
    721     DiskLruCache.Editor creator = cache.edit("k1");
    722     setString(creator, 0, "ABC");
    723     setString(creator, 1, "DE");
    724     creator.commit();
    725     cache.flush();
    726 
    727     copyFile(journalFile, journalBkpFile);
    728 
    729     creator = cache.edit("k2");
    730     setString(creator, 0, "F");
    731     setString(creator, 1, "GH");
    732     creator.commit();
    733     cache.close();
    734 
    735     assertTrue(fileSystem.exists(journalFile));
    736     assertTrue(fileSystem.exists(journalBkpFile));
    737 
    738     createNewCache();
    739 
    740     DiskLruCache.Snapshot snapshotA = cache.get("k1");
    741     assertSnapshotValue(snapshotA, 0, "ABC");
    742     assertSnapshotValue(snapshotA, 1, "DE");
    743 
    744     DiskLruCache.Snapshot snapshotB = cache.get("k2");
    745     assertSnapshotValue(snapshotB, 0, "F");
    746     assertSnapshotValue(snapshotB, 1, "GH");
    747 
    748     assertFalse(fileSystem.exists(journalBkpFile));
    749     assertTrue(fileSystem.exists(journalFile));
    750   }
    751 
    752   @Test public void openCreatesDirectoryIfNecessary() throws Exception {
    753     cache.close();
    754     File dir = tempDir.newFolder("testOpenCreatesDirectoryIfNecessary");
    755     cache = DiskLruCache.create(fileSystem, dir, appVersion, 2, Integer.MAX_VALUE);
    756     set("a", "a", "a");
    757     assertTrue(fileSystem.exists(new File(dir, "a.0")));
    758     assertTrue(fileSystem.exists(new File(dir, "a.1")));
    759     assertTrue(fileSystem.exists(new File(dir, "journal")));
    760   }
    761 
    762   @Test public void fileDeletedExternally() throws Exception {
    763     set("a", "a", "a");
    764     fileSystem.delete(getCleanFile("a", 1));
    765     assertNull(cache.get("a"));
    766   }
    767 
    768   @Test public void editSameVersion() throws Exception {
    769     set("a", "a", "a");
    770     DiskLruCache.Snapshot snapshot = cache.get("a");
    771     DiskLruCache.Editor editor = snapshot.edit();
    772     setString(editor, 1, "a2");
    773     editor.commit();
    774     assertValue("a", "a", "a2");
    775   }
    776 
    777   @Test public void editSnapshotAfterChangeAborted() throws Exception {
    778     set("a", "a", "a");
    779     DiskLruCache.Snapshot snapshot = cache.get("a");
    780     DiskLruCache.Editor toAbort = snapshot.edit();
    781     setString(toAbort, 0, "b");
    782     toAbort.abort();
    783     DiskLruCache.Editor editor = snapshot.edit();
    784     setString(editor, 1, "a2");
    785     editor.commit();
    786     assertValue("a", "a", "a2");
    787   }
    788 
    789   @Test public void editSnapshotAfterChangeCommitted() throws Exception {
    790     set("a", "a", "a");
    791     DiskLruCache.Snapshot snapshot = cache.get("a");
    792     DiskLruCache.Editor toAbort = snapshot.edit();
    793     setString(toAbort, 0, "b");
    794     toAbort.commit();
    795     assertNull(snapshot.edit());
    796   }
    797 
    798   @Test public void editSinceEvicted() throws Exception {
    799     cache.close();
    800     createNewCacheWithSize(10);
    801     set("a", "aa", "aaa"); // size 5
    802     DiskLruCache.Snapshot snapshot = cache.get("a");
    803     set("b", "bb", "bbb"); // size 5
    804     set("c", "cc", "ccc"); // size 5; will evict 'A'
    805     cache.flush();
    806     assertNull(snapshot.edit());
    807   }
    808 
    809   @Test public void editSinceEvictedAndRecreated() throws Exception {
    810     cache.close();
    811     createNewCacheWithSize(10);
    812     set("a", "aa", "aaa"); // size 5
    813     DiskLruCache.Snapshot snapshot = cache.get("a");
    814     set("b", "bb", "bbb"); // size 5
    815     set("c", "cc", "ccc"); // size 5; will evict 'A'
    816     set("a", "a", "aaaa"); // size 5; will evict 'B'
    817     cache.flush();
    818     assertNull(snapshot.edit());
    819   }
    820 
    821   /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */
    822   @Test public void aggressiveClearingHandlesWrite() throws Exception {
    823     fileSystem.deleteContents(tempDir.getRoot());
    824     set("a", "a", "a");
    825     assertValue("a", "a", "a");
    826   }
    827 
    828   /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */
    829   @Test public void aggressiveClearingHandlesEdit() throws Exception {
    830     set("a", "a", "a");
    831     DiskLruCache.Editor a = cache.get("a").edit();
    832     fileSystem.deleteContents(tempDir.getRoot());
    833     setString(a, 1, "a2");
    834     a.commit();
    835   }
    836 
    837   @Test public void removeHandlesMissingFile() throws Exception {
    838     set("a", "a", "a");
    839     getCleanFile("a", 0).delete();
    840     cache.remove("a");
    841   }
    842 
    843   /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */
    844   @Test public void aggressiveClearingHandlesPartialEdit() throws Exception {
    845     set("a", "a", "a");
    846     set("b", "b", "b");
    847     DiskLruCache.Editor a = cache.get("a").edit();
    848     setString(a, 0, "a1");
    849     fileSystem.deleteContents(tempDir.getRoot());
    850     setString(a, 1, "a2");
    851     a.commit();
    852     assertNull(cache.get("a"));
    853   }
    854 
    855   /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/2">Issue #2</a> */
    856   @Test public void aggressiveClearingHandlesRead() throws Exception {
    857     fileSystem.deleteContents(tempDir.getRoot());
    858     assertNull(cache.get("a"));
    859   }
    860 
    861   /**
    862    * We had a long-lived bug where {@link DiskLruCache#trimToSize} could
    863    * infinite loop if entries being edited required deletion for the operation
    864    * to complete.
    865    */
    866   @Test public void trimToSizeWithActiveEdit() throws Exception {
    867     set("a", "a1234", "a1234");
    868     DiskLruCache.Editor a = cache.edit("a");
    869     setString(a, 0, "a123");
    870 
    871     cache.setMaxSize(8); // Smaller than the sum of active edits!
    872     cache.flush(); // Force trimToSize().
    873     assertEquals(0, cache.size());
    874     assertNull(cache.get("a"));
    875 
    876     // After the edit is completed, its entry is still gone.
    877     setString(a, 1, "a1");
    878     a.commit();
    879     assertAbsent("a");
    880     assertEquals(0, cache.size());
    881   }
    882 
    883   @Test public void evictAll() throws Exception {
    884     set("a", "a", "a");
    885     set("b", "b", "b");
    886     cache.evictAll();
    887     assertEquals(0, cache.size());
    888     assertAbsent("a");
    889     assertAbsent("b");
    890   }
    891 
    892   @Test public void evictAllWithPartialCreate() throws Exception {
    893     DiskLruCache.Editor a = cache.edit("a");
    894     setString(a, 0, "a1");
    895     setString(a, 1, "a2");
    896     cache.evictAll();
    897     assertEquals(0, cache.size());
    898     a.commit();
    899     assertAbsent("a");
    900   }
    901 
    902   @Test public void evictAllWithPartialEditDoesNotStoreAValue() throws Exception {
    903     set("a", "a", "a");
    904     DiskLruCache.Editor a = cache.edit("a");
    905     setString(a, 0, "a1");
    906     setString(a, 1, "a2");
    907     cache.evictAll();
    908     assertEquals(0, cache.size());
    909     a.commit();
    910     assertAbsent("a");
    911   }
    912 
    913   @Test public void evictAllDoesntInterruptPartialRead() throws Exception {
    914     set("a", "a", "a");
    915     DiskLruCache.Snapshot a = cache.get("a");
    916     assertSnapshotValue(a, 0, "a");
    917     cache.evictAll();
    918     assertEquals(0, cache.size());
    919     assertAbsent("a");
    920     assertSnapshotValue(a, 1, "a");
    921     a.close();
    922   }
    923 
    924   @Test public void editSnapshotAfterEvictAllReturnsNullDueToStaleValue() throws Exception {
    925     set("a", "a", "a");
    926     DiskLruCache.Snapshot a = cache.get("a");
    927     cache.evictAll();
    928     assertEquals(0, cache.size());
    929     assertAbsent("a");
    930     assertNull(a.edit());
    931     a.close();
    932   }
    933 
    934   @Test public void iterator() throws Exception {
    935     set("a", "a1", "a2");
    936     set("b", "b1", "b2");
    937     set("c", "c1", "c2");
    938     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
    939 
    940     assertTrue(iterator.hasNext());
    941     DiskLruCache.Snapshot a = iterator.next();
    942     assertEquals("a", a.key());
    943     assertSnapshotValue(a, 0, "a1");
    944     assertSnapshotValue(a, 1, "a2");
    945     a.close();
    946 
    947     assertTrue(iterator.hasNext());
    948     DiskLruCache.Snapshot b = iterator.next();
    949     assertEquals("b", b.key());
    950     assertSnapshotValue(b, 0, "b1");
    951     assertSnapshotValue(b, 1, "b2");
    952     b.close();
    953 
    954     assertTrue(iterator.hasNext());
    955     DiskLruCache.Snapshot c = iterator.next();
    956     assertEquals("c", c.key());
    957     assertSnapshotValue(c, 0, "c1");
    958     assertSnapshotValue(c, 1, "c2");
    959     c.close();
    960 
    961     assertFalse(iterator.hasNext());
    962     try {
    963       iterator.next();
    964       fail();
    965     } catch (NoSuchElementException expected) {
    966     }
    967   }
    968 
    969   @Test public void iteratorElementsAddedDuringIterationAreOmitted() throws Exception {
    970     set("a", "a1", "a2");
    971     set("b", "b1", "b2");
    972     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
    973 
    974     DiskLruCache.Snapshot a = iterator.next();
    975     assertEquals("a", a.key());
    976     a.close();
    977 
    978     set("c", "c1", "c2");
    979 
    980     DiskLruCache.Snapshot b = iterator.next();
    981     assertEquals("b", b.key());
    982     b.close();
    983 
    984     assertFalse(iterator.hasNext());
    985   }
    986 
    987   @Test public void iteratorElementsUpdatedDuringIterationAreUpdated() throws Exception {
    988     set("a", "a1", "a2");
    989     set("b", "b1", "b2");
    990     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
    991 
    992     DiskLruCache.Snapshot a = iterator.next();
    993     assertEquals("a", a.key());
    994     a.close();
    995 
    996     set("b", "b3", "b4");
    997 
    998     DiskLruCache.Snapshot b = iterator.next();
    999     assertEquals("b", b.key());
   1000     assertSnapshotValue(b, 0, "b3");
   1001     assertSnapshotValue(b, 1, "b4");
   1002     b.close();
   1003   }
   1004 
   1005   @Test public void iteratorElementsRemovedDuringIterationAreOmitted() throws Exception {
   1006     set("a", "a1", "a2");
   1007     set("b", "b1", "b2");
   1008     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
   1009 
   1010     cache.remove("b");
   1011 
   1012     DiskLruCache.Snapshot a = iterator.next();
   1013     assertEquals("a", a.key());
   1014     a.close();
   1015 
   1016     assertFalse(iterator.hasNext());
   1017   }
   1018 
   1019   @Test public void iteratorRemove() throws Exception {
   1020     set("a", "a1", "a2");
   1021     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
   1022 
   1023     DiskLruCache.Snapshot a = iterator.next();
   1024     a.close();
   1025     iterator.remove();
   1026 
   1027     assertEquals(null, cache.get("a"));
   1028   }
   1029 
   1030   @Test public void iteratorRemoveBeforeNext() throws Exception {
   1031     set("a", "a1", "a2");
   1032     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
   1033     try {
   1034       iterator.remove();
   1035       fail();
   1036     } catch (IllegalStateException expected) {
   1037     }
   1038   }
   1039 
   1040   @Test public void iteratorRemoveOncePerCallToNext() throws Exception {
   1041     set("a", "a1", "a2");
   1042     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
   1043 
   1044     DiskLruCache.Snapshot a = iterator.next();
   1045     iterator.remove();
   1046     a.close();
   1047 
   1048     try {
   1049       iterator.remove();
   1050       fail();
   1051     } catch (IllegalStateException expected) {
   1052     }
   1053   }
   1054 
   1055   @Test public void cacheClosedTruncatesIterator() throws Exception {
   1056     set("a", "a1", "a2");
   1057     Iterator<DiskLruCache.Snapshot> iterator = cache.snapshots();
   1058     cache.close();
   1059     assertFalse(iterator.hasNext());
   1060   }
   1061 
   1062   @Test public void isClosed_uninitializedCache() throws Exception {
   1063     // Create an uninitialized cache.
   1064     cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor);
   1065     toClose.add(cache);
   1066 
   1067     assertFalse(cache.isClosed());
   1068     cache.close();
   1069     assertTrue(cache.isClosed());
   1070   }
   1071 
   1072   @Test public void journalWriteFailsDuringEdit() throws Exception {
   1073     set("a", "a", "a");
   1074     set("b", "b", "b");
   1075 
   1076     // We can't begin the edit if writing 'DIRTY' fails.
   1077     fileSystem.setFaulty(journalFile, true);
   1078     assertNull(cache.edit("c"));
   1079 
   1080     // Once the journal has a failure, subsequent writes aren't permitted.
   1081     fileSystem.setFaulty(journalFile, false);
   1082     assertNull(cache.edit("d"));
   1083 
   1084     // Confirm that the fault didn't corrupt entries stored before the fault was introduced.
   1085     cache.close();
   1086     cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor);
   1087     assertValue("a", "a", "a");
   1088     assertValue("b", "b", "b");
   1089     assertAbsent("c");
   1090     assertAbsent("d");
   1091   }
   1092 
   1093   /**
   1094    * We had a bug where the cache was left in an inconsistent state after a journal write failed.
   1095    * https://github.com/square/okhttp/issues/1211
   1096    */
   1097   @Test public void journalWriteFailsDuringEditorCommit() throws Exception {
   1098     set("a", "a", "a");
   1099     set("b", "b", "b");
   1100 
   1101     // Create an entry that fails to write to the journal during commit.
   1102     DiskLruCache.Editor editor = cache.edit("c");
   1103     setString(editor, 0, "c");
   1104     setString(editor, 1, "c");
   1105     fileSystem.setFaulty(journalFile, true);
   1106     editor.commit();
   1107 
   1108     // Once the journal has a failure, subsequent writes aren't permitted.
   1109     fileSystem.setFaulty(journalFile, false);
   1110     assertNull(cache.edit("d"));
   1111 
   1112     // Confirm that the fault didn't corrupt entries stored before the fault was introduced.
   1113     cache.close();
   1114     cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor);
   1115     assertValue("a", "a", "a");
   1116     assertValue("b", "b", "b");
   1117     assertAbsent("c");
   1118     assertAbsent("d");
   1119   }
   1120 
   1121   @Test public void journalWriteFailsDuringEditorAbort() throws Exception {
   1122     set("a", "a", "a");
   1123     set("b", "b", "b");
   1124 
   1125     // Create an entry that fails to write to the journal during abort.
   1126     DiskLruCache.Editor editor = cache.edit("c");
   1127     setString(editor, 0, "c");
   1128     setString(editor, 1, "c");
   1129     fileSystem.setFaulty(journalFile, true);
   1130     editor.abort();
   1131 
   1132     // Once the journal has a failure, subsequent writes aren't permitted.
   1133     fileSystem.setFaulty(journalFile, false);
   1134     assertNull(cache.edit("d"));
   1135 
   1136     // Confirm that the fault didn't corrupt entries stored before the fault was introduced.
   1137     cache.close();
   1138     cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor);
   1139     assertValue("a", "a", "a");
   1140     assertValue("b", "b", "b");
   1141     assertAbsent("c");
   1142     assertAbsent("d");
   1143   }
   1144 
   1145   @Test public void journalWriteFailsDuringRemove() throws Exception {
   1146     set("a", "a", "a");
   1147     set("b", "b", "b");
   1148 
   1149     // Remove, but the journal write will fail.
   1150     fileSystem.setFaulty(journalFile, true);
   1151     assertTrue(cache.remove("a"));
   1152 
   1153     // Confirm that the entry was still removed.
   1154     fileSystem.setFaulty(journalFile, false);
   1155     cache.close();
   1156     cache = new DiskLruCache(fileSystem, cacheDir, appVersion, 2, Integer.MAX_VALUE, executor);
   1157     assertAbsent("a");
   1158     assertValue("b", "b", "b");
   1159   }
   1160 
   1161   @Test public void noSizeCorruptionAfterCreatorDetached() throws Exception {
   1162     // Create an editor for k1. Detach it by clearing the cache.
   1163     DiskLruCache.Editor editor = cache.edit("k1");
   1164     setString(editor, 0, "a");
   1165     setString(editor, 1, "a");
   1166     cache.evictAll();
   1167 
   1168     // Create a new value in its place.
   1169     set("k1", "bb", "bb");
   1170     assertEquals(4, cache.size());
   1171 
   1172     // Committing the detached editor should not change the cache's size.
   1173     editor.commit();
   1174     assertEquals(4, cache.size());
   1175     assertValue("k1", "bb", "bb");
   1176   }
   1177 
   1178   @Test public void noSizeCorruptionAfterEditorDetached() throws Exception {
   1179     set("k1", "a", "a");
   1180 
   1181     // Create an editor for k1. Detach it by clearing the cache.
   1182     DiskLruCache.Editor editor = cache.edit("k1");
   1183     setString(editor, 0, "bb");
   1184     setString(editor, 1, "bb");
   1185     cache.evictAll();
   1186 
   1187     // Create a new value in its place.
   1188     set("k1", "ccc", "ccc");
   1189     assertEquals(6, cache.size());
   1190 
   1191     // Committing the detached editor should not change the cache's size.
   1192     editor.commit();
   1193     assertEquals(6, cache.size());
   1194     assertValue("k1", "ccc", "ccc");
   1195   }
   1196 
   1197   @Test public void noNewSourceAfterEditorDetached() throws Exception {
   1198     set("k1", "a", "a");
   1199 
   1200     DiskLruCache.Editor editor = cache.edit("k1");
   1201     cache.evictAll();
   1202 
   1203     assertNull(editor.newSource(0));
   1204   }
   1205 
   1206   @Test public void editsDiscardedAfterEditorDetached() throws Exception {
   1207     set("k1", "a", "a");
   1208 
   1209     // Create an editor, then detach it.
   1210     DiskLruCache.Editor editor = cache.edit("k1");
   1211     BufferedSink sink = Okio.buffer(editor.newSink(0));
   1212     cache.evictAll();
   1213 
   1214     // Create another value in its place.
   1215     set("k1", "ccc", "ccc");
   1216 
   1217     // Complete the original edit. It goes into a black hole.
   1218     sink.writeUtf8("bb");
   1219     sink.close();
   1220 
   1221     assertValue("k1", "ccc", "ccc");
   1222   }
   1223 
   1224   @Test public void abortAfterDetach() throws Exception {
   1225     set("k1", "a", "a");
   1226 
   1227     DiskLruCache.Editor editor = cache.edit("k1");
   1228     cache.evictAll();
   1229 
   1230     editor.abort();
   1231     assertEquals(0, cache.size());
   1232     assertAbsent("k1");
   1233   }
   1234 
   1235   private void assertJournalEquals(String... expectedBodyLines) throws Exception {
   1236     List<String> expectedLines = new ArrayList<>();
   1237     expectedLines.add(MAGIC);
   1238     expectedLines.add(VERSION_1);
   1239     expectedLines.add("100");
   1240     expectedLines.add("2");
   1241     expectedLines.add("");
   1242     expectedLines.addAll(Arrays.asList(expectedBodyLines));
   1243     assertEquals(expectedLines, readJournalLines());
   1244   }
   1245 
   1246   private void createJournal(String... bodyLines) throws Exception {
   1247     createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines);
   1248   }
   1249 
   1250   private void createJournalWithHeader(String magic, String version, String appVersion,
   1251       String valueCount, String blank, String... bodyLines) throws Exception {
   1252     BufferedSink sink = Okio.buffer(fileSystem.sink(journalFile));
   1253     sink.writeUtf8(magic + "\n");
   1254     sink.writeUtf8(version + "\n");
   1255     sink.writeUtf8(appVersion + "\n");
   1256     sink.writeUtf8(valueCount + "\n");
   1257     sink.writeUtf8(blank + "\n");
   1258     for (String line : bodyLines) {
   1259       sink.writeUtf8(line);
   1260       sink.writeUtf8("\n");
   1261     }
   1262     sink.close();
   1263   }
   1264 
   1265   private List<String> readJournalLines() throws Exception {
   1266     List<String> result = new ArrayList<>();
   1267     BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
   1268     for (String line; (line = source.readUtf8Line()) != null; ) {
   1269       result.add(line);
   1270     }
   1271     source.close();
   1272     return result;
   1273   }
   1274 
   1275   private File getCleanFile(String key, int index) {
   1276     return new File(cacheDir, key + "." + index);
   1277   }
   1278 
   1279   private File getDirtyFile(String key, int index) {
   1280     return new File(cacheDir, key + "." + index + ".tmp");
   1281   }
   1282 
   1283   private String readFile(File file) throws Exception {
   1284     BufferedSource source = Okio.buffer(fileSystem.source(file));
   1285     String result = source.readUtf8();
   1286     source.close();
   1287     return result;
   1288   }
   1289 
   1290   public void writeFile(File file, String content) throws Exception {
   1291     BufferedSink sink = Okio.buffer(fileSystem.sink(file));
   1292     sink.writeUtf8(content);
   1293     sink.close();
   1294   }
   1295 
   1296   private static void assertInoperable(DiskLruCache.Editor editor) throws Exception {
   1297     try {
   1298       setString(editor, 0, "A");
   1299       fail();
   1300     } catch (IllegalStateException expected) {
   1301     }
   1302     try {
   1303       editor.newSource(0);
   1304       fail();
   1305     } catch (IllegalStateException expected) {
   1306     }
   1307     try {
   1308       editor.newSink(0);
   1309       fail();
   1310     } catch (IllegalStateException expected) {
   1311     }
   1312     try {
   1313       editor.commit();
   1314       fail();
   1315     } catch (IllegalStateException expected) {
   1316     }
   1317     try {
   1318       editor.abort();
   1319       fail();
   1320     } catch (IllegalStateException expected) {
   1321     }
   1322   }
   1323 
   1324   private void generateSomeGarbageFiles() throws Exception {
   1325     File dir1 = new File(cacheDir, "dir1");
   1326     File dir2 = new File(dir1, "dir2");
   1327     writeFile(getCleanFile("g1", 0), "A");
   1328     writeFile(getCleanFile("g1", 1), "B");
   1329     writeFile(getCleanFile("g2", 0), "C");
   1330     writeFile(getCleanFile("g2", 1), "D");
   1331     writeFile(getCleanFile("g2", 1), "D");
   1332     writeFile(new File(cacheDir, "otherFile0"), "E");
   1333     writeFile(new File(dir2, "otherFile1"), "F");
   1334   }
   1335 
   1336   private void assertGarbageFilesAllDeleted() throws Exception {
   1337     assertFalse(fileSystem.exists(getCleanFile("g1", 0)));
   1338     assertFalse(fileSystem.exists(getCleanFile("g1", 1)));
   1339     assertFalse(fileSystem.exists(getCleanFile("g2", 0)));
   1340     assertFalse(fileSystem.exists(getCleanFile("g2", 1)));
   1341     assertFalse(fileSystem.exists(new File(cacheDir, "otherFile0")));
   1342     assertFalse(fileSystem.exists(new File(cacheDir, "dir1")));
   1343   }
   1344 
   1345   private void set(String key, String value0, String value1) throws Exception {
   1346     DiskLruCache.Editor editor = cache.edit(key);
   1347     setString(editor, 0, value0);
   1348     setString(editor, 1, value1);
   1349     editor.commit();
   1350   }
   1351 
   1352   public static void setString(DiskLruCache.Editor editor, int index, String value) throws IOException {
   1353     BufferedSink writer = Okio.buffer(editor.newSink(index));
   1354     writer.writeUtf8(value);
   1355     writer.close();
   1356   }
   1357 
   1358   private void assertAbsent(String key) throws Exception {
   1359     DiskLruCache.Snapshot snapshot = cache.get(key);
   1360     if (snapshot != null) {
   1361       snapshot.close();
   1362       fail();
   1363     }
   1364     assertFalse(fileSystem.exists(getCleanFile(key, 0)));
   1365     assertFalse(fileSystem.exists(getCleanFile(key, 1)));
   1366     assertFalse(fileSystem.exists(getDirtyFile(key, 0)));
   1367     assertFalse(fileSystem.exists(getDirtyFile(key, 1)));
   1368   }
   1369 
   1370   private void assertValue(String key, String value0, String value1) throws Exception {
   1371     DiskLruCache.Snapshot snapshot = cache.get(key);
   1372     assertSnapshotValue(snapshot, 0, value0);
   1373     assertSnapshotValue(snapshot, 1, value1);
   1374     assertTrue(fileSystem.exists(getCleanFile(key, 0)));
   1375     assertTrue(fileSystem.exists(getCleanFile(key, 1)));
   1376     snapshot.close();
   1377   }
   1378 
   1379   private void assertSnapshotValue(DiskLruCache.Snapshot snapshot, int index, String value)
   1380       throws IOException {
   1381     assertEquals(value, sourceAsString(snapshot.getSource(index)));
   1382     assertEquals(value.length(), snapshot.getLength(index));
   1383   }
   1384 
   1385   private String sourceAsString(Source source) throws IOException {
   1386     return source != null ? Okio.buffer(source).readUtf8() : null;
   1387   }
   1388 
   1389   private void copyFile(File from, File to) throws IOException {
   1390     Source source = fileSystem.source(from);
   1391     BufferedSink sink = Okio.buffer(fileSystem.sink(to));
   1392     sink.writeAll(source);
   1393     source.close();
   1394     sink.close();
   1395   }
   1396 
   1397   private static class TestExecutor implements Executor {
   1398     final Deque<Runnable> jobs = new ArrayDeque<>();
   1399 
   1400     @Override public void execute(Runnable command) {
   1401       jobs.addLast(command);
   1402     }
   1403   }
   1404 }
   1405