Home | History | Annotate | Download | only in io
      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 
     17 package libcore.io;
     18 
     19 import java.io.BufferedReader;
     20 import java.io.File;
     21 import java.io.FileReader;
     22 import java.io.FileWriter;
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.io.OutputStream;
     26 import java.io.Reader;
     27 import java.io.StringWriter;
     28 import java.io.Writer;
     29 import java.util.ArrayList;
     30 import java.util.Arrays;
     31 import java.util.List;
     32 import junit.framework.TestCase;
     33 import static libcore.io.DiskLruCache.JOURNAL_FILE;
     34 import static libcore.io.DiskLruCache.MAGIC;
     35 import static libcore.io.DiskLruCache.VERSION_1;
     36 import tests.io.MockOs;
     37 
     38 public final class DiskLruCacheTest extends TestCase {
     39     private final int appVersion = 100;
     40     private String javaTmpDir;
     41     private File cacheDir;
     42     private File journalFile;
     43     private DiskLruCache cache;
     44     private final MockOs mockOs = new MockOs();
     45 
     46     @Override public void setUp() throws Exception {
     47         super.setUp();
     48         javaTmpDir = System.getProperty("java.io.tmpdir");
     49         cacheDir = new File(javaTmpDir, "DiskLruCacheTest");
     50         cacheDir.mkdir();
     51         journalFile = new File(cacheDir, JOURNAL_FILE);
     52         for (File file : cacheDir.listFiles()) {
     53             file.delete();
     54         }
     55         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
     56         mockOs.install();
     57     }
     58 
     59     @Override protected void tearDown() throws Exception {
     60         mockOs.uninstall();
     61         cache.close();
     62         super.tearDown();
     63     }
     64 
     65     public void testEmptyCache() throws Exception {
     66         cache.close();
     67         assertJournalEquals();
     68     }
     69 
     70     public void testWriteAndReadEntry() throws Exception {
     71         DiskLruCache.Editor creator = cache.edit("k1");
     72         creator.set(0, "ABC");
     73         creator.set(1, "DE");
     74         assertNull(creator.getString(0));
     75         assertNull(creator.newInputStream(0));
     76         assertNull(creator.getString(1));
     77         assertNull(creator.newInputStream(1));
     78         creator.commit();
     79 
     80         DiskLruCache.Snapshot snapshot = cache.get("k1");
     81         assertEquals("ABC", snapshot.getString(0));
     82         assertEquals("DE", snapshot.getString(1));
     83     }
     84 
     85     public void testReadAndWriteEntryAcrossCacheOpenAndClose() throws Exception {
     86         DiskLruCache.Editor creator = cache.edit("k1");
     87         creator.set(0, "A");
     88         creator.set(1, "B");
     89         creator.commit();
     90         cache.close();
     91 
     92         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
     93         DiskLruCache.Snapshot snapshot = cache.get("k1");
     94         assertEquals("A", snapshot.getString(0));
     95         assertEquals("B", snapshot.getString(1));
     96         snapshot.close();
     97     }
     98 
     99     public void testJournalWithEditAndPublish() throws Exception {
    100         DiskLruCache.Editor creator = cache.edit("k1");
    101         assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed
    102         creator.set(0, "AB");
    103         creator.set(1, "C");
    104         creator.commit();
    105         cache.close();
    106         assertJournalEquals("DIRTY k1", "CLEAN k1 2 1");
    107     }
    108 
    109     public void testRevertedNewFileIsRemoveInJournal() throws Exception {
    110         DiskLruCache.Editor creator = cache.edit("k1");
    111         assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed
    112         creator.set(0, "AB");
    113         creator.set(1, "C");
    114         creator.abort();
    115         cache.close();
    116         assertJournalEquals("DIRTY k1", "REMOVE k1");
    117     }
    118 
    119     public void testUnterminatedEditIsRevertedOnClose() throws Exception {
    120         cache.edit("k1");
    121         cache.close();
    122         assertJournalEquals("DIRTY k1", "REMOVE k1");
    123     }
    124 
    125     public void testJournalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception {
    126         DiskLruCache.Editor creator = cache.edit("k1");
    127         assertNull(cache.get("k1"));
    128         creator.set(0, "A");
    129         creator.set(1, "BC");
    130         creator.commit();
    131         cache.close();
    132         assertJournalEquals("DIRTY k1", "CLEAN k1 1 2");
    133     }
    134 
    135     public void testJournalWithEditAndPublishAndRead() throws Exception {
    136         DiskLruCache.Editor k1Creator = cache.edit("k1");
    137         k1Creator.set(0, "AB");
    138         k1Creator.set(1, "C");
    139         k1Creator.commit();
    140         DiskLruCache.Editor k2Creator = cache.edit("k2");
    141         k2Creator.set(0, "DEF");
    142         k2Creator.set(1, "G");
    143         k2Creator.commit();
    144         DiskLruCache.Snapshot k1Snapshot = cache.get("k1");
    145         k1Snapshot.close();
    146         cache.close();
    147         assertJournalEquals("DIRTY k1", "CLEAN k1 2 1",
    148                 "DIRTY k2", "CLEAN k2 3 1",
    149                 "READ k1");
    150     }
    151 
    152     public void testCannotOperateOnEditAfterPublish() throws Exception {
    153         DiskLruCache.Editor editor = cache.edit("k1");
    154         editor.set(0, "A");
    155         editor.set(1, "B");
    156         editor.commit();
    157         assertInoperable(editor);
    158     }
    159 
    160     public void testCannotOperateOnEditAfterRevert() throws Exception {
    161         DiskLruCache.Editor editor = cache.edit("k1");
    162         editor.set(0, "A");
    163         editor.set(1, "B");
    164         editor.abort();
    165         assertInoperable(editor);
    166     }
    167 
    168     public void testExplicitRemoveAppliedToDiskImmediately() throws Exception {
    169         DiskLruCache.Editor editor = cache.edit("k1");
    170         editor.set(0, "ABC");
    171         editor.set(1, "B");
    172         editor.commit();
    173         File k1 = getCleanFile("k1", 0);
    174         assertEquals("ABC", readFile(k1));
    175         cache.remove("k1");
    176         assertFalse(k1.exists());
    177     }
    178 
    179     /**
    180      * Each read sees a snapshot of the file at the time read was called.
    181      * This means that two reads of the same key can see different data.
    182      */
    183     public void testReadAndWriteOverlapsMaintainConsistency() throws Exception {
    184         DiskLruCache.Editor v1Creator = cache.edit("k1");
    185         v1Creator.set(0, "AAaa");
    186         v1Creator.set(1, "BBbb");
    187         v1Creator.commit();
    188 
    189         DiskLruCache.Snapshot snapshot1 = cache.get("k1");
    190         InputStream inV1 = snapshot1.getInputStream(0);
    191         assertEquals('A', inV1.read());
    192         assertEquals('A', inV1.read());
    193 
    194         DiskLruCache.Editor v1Updater = cache.edit("k1");
    195         v1Updater.set(0, "CCcc");
    196         v1Updater.set(1, "DDdd");
    197         v1Updater.commit();
    198 
    199         DiskLruCache.Snapshot snapshot2 = cache.get("k1");
    200         assertEquals("CCcc", snapshot2.getString(0));
    201         assertEquals("DDdd", snapshot2.getString(1));
    202         snapshot2.close();
    203 
    204         assertEquals('a', inV1.read());
    205         assertEquals('a', inV1.read());
    206         assertEquals("BBbb", snapshot1.getString(1));
    207         snapshot1.close();
    208     }
    209 
    210     public void testOpenWithDirtyKeyDeletesAllFilesForThatKey() throws Exception {
    211         cache.close();
    212         File cleanFile0 = getCleanFile("k1", 0);
    213         File cleanFile1 = getCleanFile("k1", 1);
    214         File dirtyFile0 = getDirtyFile("k1", 0);
    215         File dirtyFile1 = getDirtyFile("k1", 1);
    216         writeFile(cleanFile0, "A");
    217         writeFile(cleanFile1, "B");
    218         writeFile(dirtyFile0, "C");
    219         writeFile(dirtyFile1, "D");
    220         createJournal("CLEAN k1 1 1", "DIRTY   k1");
    221         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    222         assertFalse(cleanFile0.exists());
    223         assertFalse(cleanFile1.exists());
    224         assertFalse(dirtyFile0.exists());
    225         assertFalse(dirtyFile1.exists());
    226         assertNull(cache.get("k1"));
    227     }
    228 
    229     public void testOpenWithInvalidVersionClearsDirectory() throws Exception {
    230         cache.close();
    231         generateSomeGarbageFiles();
    232         createJournalWithHeader(MAGIC, "0", "100", "2", "");
    233         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    234         assertGarbageFilesAllDeleted();
    235     }
    236 
    237     public void testOpenWithInvalidAppVersionClearsDirectory() throws Exception {
    238         cache.close();
    239         generateSomeGarbageFiles();
    240         createJournalWithHeader(MAGIC, "1", "101", "2", "");
    241         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    242         assertGarbageFilesAllDeleted();
    243     }
    244 
    245     public void testOpenWithInvalidValueCountClearsDirectory() throws Exception {
    246         cache.close();
    247         generateSomeGarbageFiles();
    248         createJournalWithHeader(MAGIC, "1", "100", "1", "");
    249         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    250         assertGarbageFilesAllDeleted();
    251     }
    252 
    253     public void testOpenWithInvalidBlankLineClearsDirectory() throws Exception {
    254         cache.close();
    255         generateSomeGarbageFiles();
    256         createJournalWithHeader(MAGIC, "1", "100", "2", "x");
    257         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    258         assertGarbageFilesAllDeleted();
    259     }
    260 
    261     public void testOpenWithInvalidJournalLineClearsDirectory() throws Exception {
    262         cache.close();
    263         generateSomeGarbageFiles();
    264         createJournal("CLEAN k1 1 1", "BOGUS");
    265         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    266         assertGarbageFilesAllDeleted();
    267         assertNull(cache.get("k1"));
    268     }
    269 
    270     public void testOpenWithInvalidFileSizeClearsDirectory() throws Exception {
    271         cache.close();
    272         generateSomeGarbageFiles();
    273         createJournal("CLEAN k1 0000x001 1");
    274         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    275         assertGarbageFilesAllDeleted();
    276         assertNull(cache.get("k1"));
    277     }
    278 
    279     public void testOpenWithTruncatedLineDiscardsThatLine() throws Exception {
    280         cache.close();
    281         writeFile(getCleanFile("k1", 0), "A");
    282         writeFile(getCleanFile("k1", 1), "B");
    283         Writer writer = new FileWriter(journalFile);
    284         writer.write(MAGIC + "\n" + VERSION_1 + "\n100\n2\n\nCLEAN k1 1 1"); // no trailing newline
    285         writer.close();
    286         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    287         assertNull(cache.get("k1"));
    288     }
    289 
    290     public void testOpenWithTooManyFileSizesClearsDirectory() throws Exception {
    291         cache.close();
    292         generateSomeGarbageFiles();
    293         createJournal("CLEAN k1 1 1 1");
    294         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    295         assertGarbageFilesAllDeleted();
    296         assertNull(cache.get("k1"));
    297     }
    298 
    299     public void testKeyWithSpaceNotPermitted() throws Exception {
    300         try {
    301             cache.edit("my key");
    302             fail();
    303         } catch (IllegalArgumentException expected) {
    304         }
    305     }
    306 
    307     public void testKeyWithNewlineNotPermitted() throws Exception {
    308         try {
    309             cache.edit("my\nkey");
    310             fail();
    311         } catch (IllegalArgumentException expected) {
    312         }
    313     }
    314 
    315     public void testKeyWithCarriageReturnNotPermitted() throws Exception {
    316         try {
    317             cache.edit("my\rkey");
    318             fail();
    319         } catch (IllegalArgumentException expected) {
    320         }
    321     }
    322 
    323     public void testNullKeyThrows() throws Exception {
    324         try {
    325             cache.edit(null);
    326             fail();
    327         } catch (NullPointerException expected) {
    328         }
    329     }
    330 
    331     public void testCreateNewEntryWithTooFewValuesFails() throws Exception {
    332         DiskLruCache.Editor creator = cache.edit("k1");
    333         creator.set(1, "A");
    334         try {
    335             creator.commit();
    336             fail();
    337         } catch (IllegalStateException expected) {
    338         }
    339 
    340         assertFalse(getCleanFile("k1", 0).exists());
    341         assertFalse(getCleanFile("k1", 1).exists());
    342         assertFalse(getDirtyFile("k1", 0).exists());
    343         assertFalse(getDirtyFile("k1", 1).exists());
    344         assertNull(cache.get("k1"));
    345 
    346         DiskLruCache.Editor creator2 = cache.edit("k1");
    347         creator2.set(0, "B");
    348         creator2.set(1, "C");
    349         creator2.commit();
    350     }
    351 
    352     public void testCreateNewEntryWithMissingFileAborts() throws Exception {
    353         DiskLruCache.Editor creator = cache.edit("k1");
    354         creator.set(0, "A");
    355         creator.set(1, "A");
    356         assertTrue(getDirtyFile("k1", 0).exists());
    357         assertTrue(getDirtyFile("k1", 1).exists());
    358         assertTrue(getDirtyFile("k1", 0).delete());
    359         assertFalse(getDirtyFile("k1", 0).exists());
    360         creator.commit();  // silently abort if file does not exist due to I/O issue
    361 
    362         assertFalse(getCleanFile("k1", 0).exists());
    363         assertFalse(getCleanFile("k1", 1).exists());
    364         assertFalse(getDirtyFile("k1", 0).exists());
    365         assertFalse(getDirtyFile("k1", 1).exists());
    366         assertNull(cache.get("k1"));
    367 
    368         DiskLruCache.Editor creator2 = cache.edit("k1");
    369         creator2.set(0, "B");
    370         creator2.set(1, "C");
    371         creator2.commit();
    372     }
    373 
    374     public void testRevertWithTooFewValues() throws Exception {
    375         DiskLruCache.Editor creator = cache.edit("k1");
    376         creator.set(1, "A");
    377         creator.abort();
    378         assertFalse(getCleanFile("k1", 0).exists());
    379         assertFalse(getCleanFile("k1", 1).exists());
    380         assertFalse(getDirtyFile("k1", 0).exists());
    381         assertFalse(getDirtyFile("k1", 1).exists());
    382         assertNull(cache.get("k1"));
    383     }
    384 
    385     public void testUpdateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception {
    386         DiskLruCache.Editor creator = cache.edit("k1");
    387         creator.set(0, "A");
    388         creator.set(1, "B");
    389         creator.commit();
    390 
    391         DiskLruCache.Editor updater = cache.edit("k1");
    392         updater.set(0, "C");
    393         updater.commit();
    394 
    395         DiskLruCache.Snapshot snapshot = cache.get("k1");
    396         assertEquals("C", snapshot.getString(0));
    397         assertEquals("B", snapshot.getString(1));
    398         snapshot.close();
    399     }
    400 
    401     public void testEvictOnInsert() throws Exception {
    402         cache.close();
    403         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    404 
    405         set("A", "a", "aaa"); // size 4
    406         set("B", "bb", "bbbb"); // size 6
    407         assertEquals(10, cache.size());
    408 
    409         // cause the size to grow to 12 should evict 'A'
    410         set("C", "c", "c");
    411         cache.flush();
    412         assertEquals(8, cache.size());
    413         assertAbsent("A");
    414         assertValue("B", "bb", "bbbb");
    415         assertValue("C", "c", "c");
    416 
    417         // causing the size to grow to 10 should evict nothing
    418         set("D", "d", "d");
    419         cache.flush();
    420         assertEquals(10, cache.size());
    421         assertAbsent("A");
    422         assertValue("B", "bb", "bbbb");
    423         assertValue("C", "c", "c");
    424         assertValue("D", "d", "d");
    425 
    426         // causing the size to grow to 18 should evict 'B' and 'C'
    427         set("E", "eeee", "eeee");
    428         cache.flush();
    429         assertEquals(10, cache.size());
    430         assertAbsent("A");
    431         assertAbsent("B");
    432         assertAbsent("C");
    433         assertValue("D", "d", "d");
    434         assertValue("E", "eeee", "eeee");
    435     }
    436 
    437     public void testEvictOnUpdate() throws Exception {
    438         cache.close();
    439         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    440 
    441         set("A", "a", "aa"); // size 3
    442         set("B", "b", "bb"); // size 3
    443         set("C", "c", "cc"); // size 3
    444         assertEquals(9, cache.size());
    445 
    446         // causing the size to grow to 11 should evict 'A'
    447         set("B", "b", "bbbb");
    448         cache.flush();
    449         assertEquals(8, cache.size());
    450         assertAbsent("A");
    451         assertValue("B", "b", "bbbb");
    452         assertValue("C", "c", "cc");
    453     }
    454 
    455     public void testEvictionHonorsLruFromCurrentSession() throws Exception {
    456         cache.close();
    457         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    458         set("A", "a", "a");
    459         set("B", "b", "b");
    460         set("C", "c", "c");
    461         set("D", "d", "d");
    462         set("E", "e", "e");
    463         cache.get("B").close(); // 'B' is now least recently used
    464 
    465         // causing the size to grow to 12 should evict 'A'
    466         set("F", "f", "f");
    467         // causing the size to grow to 12 should evict 'C'
    468         set("G", "g", "g");
    469         cache.flush();
    470         assertEquals(10, cache.size());
    471         assertAbsent("A");
    472         assertValue("B", "b", "b");
    473         assertAbsent("C");
    474         assertValue("D", "d", "d");
    475         assertValue("E", "e", "e");
    476         assertValue("F", "f", "f");
    477     }
    478 
    479     public void testEvictionHonorsLruFromPreviousSession() throws Exception {
    480         set("A", "a", "a");
    481         set("B", "b", "b");
    482         set("C", "c", "c");
    483         set("D", "d", "d");
    484         set("E", "e", "e");
    485         set("F", "f", "f");
    486         cache.get("B").close(); // 'B' is now least recently used
    487         assertEquals(12, cache.size());
    488         cache.close();
    489         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    490 
    491         set("G", "g", "g");
    492         cache.flush();
    493         assertEquals(10, cache.size());
    494         assertAbsent("A");
    495         assertValue("B", "b", "b");
    496         assertAbsent("C");
    497         assertValue("D", "d", "d");
    498         assertValue("E", "e", "e");
    499         assertValue("F", "f", "f");
    500         assertValue("G", "g", "g");
    501     }
    502 
    503     public void testCacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception {
    504         cache.close();
    505         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    506         set("A", "aaaaa", "aaaaaa"); // size=11
    507         cache.flush();
    508         assertAbsent("A");
    509     }
    510 
    511     public void testCacheSingleValueOfSizeGreaterThanMaxSize() throws Exception {
    512         cache.close();
    513         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    514         set("A", "aaaaaaaaaaa", "a"); // size=12
    515         cache.flush();
    516         assertAbsent("A");
    517     }
    518 
    519     public void testConstructorDoesNotAllowZeroCacheSize() throws Exception {
    520         try {
    521             DiskLruCache.open(cacheDir, appVersion, 2, 0);
    522             fail();
    523         } catch (IllegalArgumentException expected) {
    524         }
    525     }
    526 
    527     public void testConstructorDoesNotAllowZeroValuesPerEntry() throws Exception {
    528         try {
    529             DiskLruCache.open(cacheDir, appVersion, 0, 10);
    530             fail();
    531         } catch (IllegalArgumentException expected) {
    532         }
    533     }
    534 
    535     public void testRemoveAbsentElement() throws Exception {
    536         cache.remove("A");
    537     }
    538 
    539     public void testReadingTheSameStreamMultipleTimes() throws Exception {
    540         set("A", "a", "b");
    541         DiskLruCache.Snapshot snapshot = cache.get("A");
    542         assertSame(snapshot.getInputStream(0), snapshot.getInputStream(0));
    543         snapshot.close();
    544     }
    545 
    546     public void testRebuildJournalOnRepeatedReads() throws Exception {
    547         set("A", "a", "a");
    548         set("B", "b", "b");
    549         long lastJournalLength = 0;
    550         while (true) {
    551             long journalLength = journalFile.length();
    552             assertValue("A", "a", "a");
    553             assertValue("B", "b", "b");
    554             if (journalLength < lastJournalLength) {
    555                 System.out.printf("Journal compacted from %s bytes to %s bytes\n",
    556                         lastJournalLength, journalLength);
    557                 break; // test passed!
    558             }
    559             lastJournalLength = journalLength;
    560         }
    561     }
    562 
    563     public void testRebuildJournalOnRepeatedEdits() throws Exception {
    564         long lastJournalLength = 0;
    565         while (true) {
    566             long journalLength = journalFile.length();
    567             set("A", "a", "a");
    568             set("B", "b", "b");
    569             if (journalLength < lastJournalLength) {
    570                 System.out.printf("Journal compacted from %s bytes to %s bytes\n",
    571                         lastJournalLength, journalLength);
    572                 break;
    573             }
    574             lastJournalLength = journalLength;
    575         }
    576 
    577         // sanity check that a rebuilt journal behaves normally
    578         assertValue("A", "a", "a");
    579         assertValue("B", "b", "b");
    580     }
    581 
    582     /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */
    583     public void testRebuildJournalOnRepeatedReadsWithOpenAndClose() throws Exception {
    584         set("a", "a", "a");
    585         set("b", "b", "b");
    586         long lastJournalLength = 0;
    587         while (true) {
    588             long journalLength = journalFile.length();
    589             assertValue("a", "a", "a");
    590             assertValue("b", "b", "b");
    591             cache.close();
    592             cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    593             if (journalLength < lastJournalLength) {
    594                 System.out.printf("Journal compacted from %s bytes to %s bytes\n",
    595                         lastJournalLength, journalLength);
    596                 break; // test passed!
    597             }
    598             lastJournalLength = journalLength;
    599         }
    600     }
    601 
    602     /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */
    603     public void testRebuildJournalOnRepeatedEditsWithOpenAndClose() throws Exception {
    604         long lastJournalLength = 0;
    605         while (true) {
    606             long journalLength = journalFile.length();
    607             set("a", "a", "a");
    608             set("b", "b", "b");
    609             cache.close();
    610             cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
    611             if (journalLength < lastJournalLength) {
    612                 System.out.printf("Journal compacted from %s bytes to %s bytes\n",
    613                         lastJournalLength, journalLength);
    614                 break;
    615             }
    616             lastJournalLength = journalLength;
    617         }
    618     }
    619 
    620     public void testOpenCreatesDirectoryIfNecessary() throws Exception {
    621         cache.close();
    622         File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary");
    623         cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE);
    624         set("A", "a", "a");
    625         assertTrue(new File(dir, "A.0").exists());
    626         assertTrue(new File(dir, "A.1").exists());
    627         assertTrue(new File(dir, "journal").exists());
    628     }
    629 
    630     public void testFileDeletedExternally() throws Exception {
    631         set("A", "a", "a");
    632         getCleanFile("A", 1).delete();
    633         assertNull(cache.get("A"));
    634     }
    635 
    636     public void testFileBecomesInaccessibleDuringReadResultsInIoException() throws Exception {
    637         set("A", "aaaaa", "a");
    638         DiskLruCache.Snapshot snapshot = cache.get("A");
    639         InputStream in = snapshot.getInputStream(0);
    640         assertEquals('a', in.read());
    641         mockOs.enqueueFault("read");
    642         try {
    643             in.read();
    644             fail();
    645         } catch (IOException expected) {
    646         }
    647         snapshot.close();
    648     }
    649 
    650     public void testFileBecomesInaccessibleDuringWriteIsSilentlyDiscarded() throws Exception {
    651         set("A", "a", "a");
    652         DiskLruCache.Editor editor = cache.edit("A");
    653         OutputStream out0 = editor.newOutputStream(0);
    654         out0.write('b');
    655         out0.close();
    656         OutputStream out1 = editor.newOutputStream(1);
    657         out1.write('c');
    658         mockOs.enqueueFault("write");
    659         out1.write('c'); // this doesn't throw...
    660         out1.close();
    661         editor.commit(); // ... but this will abort
    662         assertAbsent("A");
    663     }
    664 
    665     public void testEditSameVersion() throws Exception {
    666         set("A", "a", "a");
    667         DiskLruCache.Snapshot snapshot = cache.get("A");
    668         DiskLruCache.Editor editor = snapshot.edit();
    669         editor.set(1, "a2");
    670         editor.commit();
    671         assertValue("A", "a", "a2");
    672     }
    673 
    674     public void testEditSnapshotAfterChangeAborted() throws Exception {
    675         set("A", "a", "a");
    676         DiskLruCache.Snapshot snapshot = cache.get("A");
    677         DiskLruCache.Editor toAbort = snapshot.edit();
    678         toAbort.set(0, "b");
    679         toAbort.abort();
    680         DiskLruCache.Editor editor = snapshot.edit();
    681         editor.set(1, "a2");
    682         editor.commit();
    683         assertValue("A", "a", "a2");
    684     }
    685 
    686     public void testEditSnapshotAfterChangeCommitted() throws Exception {
    687         set("A", "a", "a");
    688         DiskLruCache.Snapshot snapshot = cache.get("A");
    689         DiskLruCache.Editor toAbort = snapshot.edit();
    690         toAbort.set(0, "b");
    691         toAbort.commit();
    692         assertNull(snapshot.edit());
    693     }
    694 
    695     public void testEditSinceEvicted() throws Exception {
    696         cache.close();
    697         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    698         set("A", "aa", "aaa"); // size 5
    699         DiskLruCache.Snapshot snapshot = cache.get("A");
    700         set("B", "bb", "bbb"); // size 5
    701         set("C", "cc", "ccc"); // size 5; will evict 'A'
    702         cache.flush();
    703         assertNull(snapshot.edit());
    704     }
    705 
    706     public void testEditSinceEvictedAndRecreated() throws Exception {
    707         cache.close();
    708         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
    709         set("A", "aa", "aaa"); // size 5
    710         DiskLruCache.Snapshot snapshot = cache.get("A");
    711         set("B", "bb", "bbb"); // size 5
    712         set("C", "cc", "ccc"); // size 5; will evict 'A'
    713         set("A", "a", "aaaa"); // size 5; will evict 'B'
    714         cache.flush();
    715         assertNull(snapshot.edit());
    716     }
    717 
    718     private void assertJournalEquals(String... expectedBodyLines) throws Exception {
    719         List<String> expectedLines = new ArrayList<String>();
    720         expectedLines.add(MAGIC);
    721         expectedLines.add(VERSION_1);
    722         expectedLines.add("100");
    723         expectedLines.add("2");
    724         expectedLines.add("");
    725         expectedLines.addAll(Arrays.asList(expectedBodyLines));
    726         assertEquals(expectedLines, readJournalLines());
    727     }
    728 
    729     private void createJournal(String... bodyLines) throws Exception {
    730         createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines);
    731     }
    732 
    733     private void createJournalWithHeader(String magic, String version, String appVersion,
    734             String valueCount, String blank, String... bodyLines) throws Exception {
    735         Writer writer = new FileWriter(journalFile);
    736         writer.write(magic + "\n");
    737         writer.write(version + "\n");
    738         writer.write(appVersion + "\n");
    739         writer.write(valueCount + "\n");
    740         writer.write(blank + "\n");
    741         for (String line : bodyLines) {
    742             writer.write(line);
    743             writer.write('\n');
    744         }
    745         writer.close();
    746     }
    747 
    748     private List<String> readJournalLines() throws Exception {
    749         List<String> result = new ArrayList<String>();
    750         BufferedReader reader = new BufferedReader(new FileReader(journalFile));
    751         String line;
    752         while ((line = reader.readLine()) != null) {
    753             result.add(line);
    754         }
    755         reader.close();
    756         return result;
    757     }
    758 
    759     private File getCleanFile(String key, int index) {
    760         return new File(cacheDir, key + "." + index);
    761     }
    762 
    763     private File getDirtyFile(String key, int index) {
    764         return new File(cacheDir, key + "." + index + ".tmp");
    765     }
    766 
    767     private String readFile(File file) throws Exception {
    768         Reader reader = new FileReader(file);
    769         StringWriter writer = new StringWriter();
    770         char[] buffer = new char[1024];
    771         int count;
    772         while ((count = reader.read(buffer)) != -1) {
    773             writer.write(buffer, 0, count);
    774         }
    775         reader.close();
    776         return writer.toString();
    777     }
    778 
    779     public void writeFile(File file, String content) throws Exception {
    780         FileWriter writer = new FileWriter(file);
    781         writer.write(content);
    782         writer.close();
    783     }
    784 
    785     private void assertInoperable(DiskLruCache.Editor editor) throws Exception {
    786         try {
    787             editor.getString(0);
    788             fail();
    789         } catch (IllegalStateException expected) {
    790         }
    791         try {
    792             editor.set(0, "A");
    793             fail();
    794         } catch (IllegalStateException expected) {
    795         }
    796         try {
    797             editor.newInputStream(0);
    798             fail();
    799         } catch (IllegalStateException expected) {
    800         }
    801         try {
    802             editor.newOutputStream(0);
    803             fail();
    804         } catch (IllegalStateException expected) {
    805         }
    806         try {
    807             editor.commit();
    808             fail();
    809         } catch (IllegalStateException expected) {
    810         }
    811         try {
    812             editor.abort();
    813             fail();
    814         } catch (IllegalStateException expected) {
    815         }
    816     }
    817 
    818     private void generateSomeGarbageFiles() throws Exception {
    819         File dir1 = new File(cacheDir, "dir1");
    820         File dir2 = new File(dir1, "dir2");
    821         writeFile(getCleanFile("g1", 0), "A");
    822         writeFile(getCleanFile("g1", 1), "B");
    823         writeFile(getCleanFile("g2", 0), "C");
    824         writeFile(getCleanFile("g2", 1), "D");
    825         writeFile(getCleanFile("g2", 1), "D");
    826         writeFile(new File(cacheDir, "otherFile0"), "E");
    827         dir1.mkdir();
    828         dir2.mkdir();
    829         writeFile(new File(dir2, "otherFile1"), "F");
    830     }
    831 
    832     private void assertGarbageFilesAllDeleted() throws Exception {
    833         assertFalse(getCleanFile("g1", 0).exists());
    834         assertFalse(getCleanFile("g1", 1).exists());
    835         assertFalse(getCleanFile("g2", 0).exists());
    836         assertFalse(getCleanFile("g2", 1).exists());
    837         assertFalse(new File(cacheDir, "otherFile0").exists());
    838         assertFalse(new File(cacheDir, "dir1").exists());
    839     }
    840 
    841     private void set(String key, String value0, String value1) throws Exception {
    842         DiskLruCache.Editor editor = cache.edit(key);
    843         editor.set(0, value0);
    844         editor.set(1, value1);
    845         editor.commit();
    846     }
    847 
    848     private void assertAbsent(String key) throws Exception {
    849         DiskLruCache.Snapshot snapshot = cache.get(key);
    850         if (snapshot != null) {
    851             snapshot.close();
    852             fail();
    853         }
    854         assertFalse(getCleanFile(key, 0).exists());
    855         assertFalse(getCleanFile(key, 1).exists());
    856         assertFalse(getDirtyFile(key, 0).exists());
    857         assertFalse(getDirtyFile(key, 1).exists());
    858     }
    859 
    860     private void assertValue(String key, String value0, String value1) throws Exception {
    861         DiskLruCache.Snapshot snapshot = cache.get(key);
    862         assertEquals(value0, snapshot.getString(0));
    863         assertEquals(value1, snapshot.getString(1));
    864         assertTrue(getCleanFile(key, 0).exists());
    865         assertTrue(getCleanFile(key, 1).exists());
    866         snapshot.close();
    867     }
    868 }
    869