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
17package libcore.io;
18
19import java.io.BufferedReader;
20import java.io.File;
21import java.io.FileReader;
22import java.io.FileWriter;
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.OutputStream;
26import java.io.Reader;
27import java.io.StringWriter;
28import java.io.Writer;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.List;
32import junit.framework.TestCase;
33import static libcore.io.DiskLruCache.JOURNAL_FILE;
34import static libcore.io.DiskLruCache.MAGIC;
35import static libcore.io.DiskLruCache.VERSION_1;
36import tests.io.MockOs;
37
38public 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    public void testOpenCreatesDirectoryIfNecessary() throws Exception {
583        cache.close();
584        File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary");
585        cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE);
586        set("A", "a", "a");
587        assertTrue(new File(dir, "A.0").exists());
588        assertTrue(new File(dir, "A.1").exists());
589        assertTrue(new File(dir, "journal").exists());
590    }
591
592    public void testFileDeletedExternally() throws Exception {
593        set("A", "a", "a");
594        getCleanFile("A", 1).delete();
595        assertNull(cache.get("A"));
596    }
597
598    public void testFileBecomesInaccessibleDuringReadResultsInIoException() throws Exception {
599        set("A", "aaaaa", "a");
600        DiskLruCache.Snapshot snapshot = cache.get("A");
601        InputStream in = snapshot.getInputStream(0);
602        assertEquals('a', in.read());
603        mockOs.enqueueFault("read");
604        try {
605            in.read();
606            fail();
607        } catch (IOException expected) {
608        }
609        snapshot.close();
610    }
611
612    public void testFileBecomesInaccessibleDuringWriteIsSilentlyDiscarded() throws Exception {
613        set("A", "a", "a");
614        DiskLruCache.Editor editor = cache.edit("A");
615        OutputStream out0 = editor.newOutputStream(0);
616        out0.write('b');
617        out0.close();
618        OutputStream out1 = editor.newOutputStream(1);
619        out1.write('c');
620        mockOs.enqueueFault("write");
621        out1.write('c'); // this doesn't throw...
622        out1.close();
623        editor.commit(); // ... but this will abort
624        assertAbsent("A");
625    }
626
627    public void testEditSameVersion() throws Exception {
628        set("A", "a", "a");
629        DiskLruCache.Snapshot snapshot = cache.get("A");
630        DiskLruCache.Editor editor = snapshot.edit();
631        editor.set(1, "a2");
632        editor.commit();
633        assertValue("A", "a", "a2");
634    }
635
636    public void testEditSnapshotAfterChangeAborted() throws Exception {
637        set("A", "a", "a");
638        DiskLruCache.Snapshot snapshot = cache.get("A");
639        DiskLruCache.Editor toAbort = snapshot.edit();
640        toAbort.set(0, "b");
641        toAbort.abort();
642        DiskLruCache.Editor editor = snapshot.edit();
643        editor.set(1, "a2");
644        editor.commit();
645        assertValue("A", "a", "a2");
646    }
647
648    public void testEditSnapshotAfterChangeCommitted() throws Exception {
649        set("A", "a", "a");
650        DiskLruCache.Snapshot snapshot = cache.get("A");
651        DiskLruCache.Editor toAbort = snapshot.edit();
652        toAbort.set(0, "b");
653        toAbort.commit();
654        assertNull(snapshot.edit());
655    }
656
657    public void testEditSinceEvicted() throws Exception {
658        cache.close();
659        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
660        set("A", "aa", "aaa"); // size 5
661        DiskLruCache.Snapshot snapshot = cache.get("A");
662        set("B", "bb", "bbb"); // size 5
663        set("C", "cc", "ccc"); // size 5; will evict 'A'
664        cache.flush();
665        assertNull(snapshot.edit());
666    }
667
668    public void testEditSinceEvictedAndRecreated() throws Exception {
669        cache.close();
670        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
671        set("A", "aa", "aaa"); // size 5
672        DiskLruCache.Snapshot snapshot = cache.get("A");
673        set("B", "bb", "bbb"); // size 5
674        set("C", "cc", "ccc"); // size 5; will evict 'A'
675        set("A", "a", "aaaa"); // size 5; will evict 'B'
676        cache.flush();
677        assertNull(snapshot.edit());
678    }
679
680    private void assertJournalEquals(String... expectedBodyLines) throws Exception {
681        List<String> expectedLines = new ArrayList<String>();
682        expectedLines.add(MAGIC);
683        expectedLines.add(VERSION_1);
684        expectedLines.add("100");
685        expectedLines.add("2");
686        expectedLines.add("");
687        expectedLines.addAll(Arrays.asList(expectedBodyLines));
688        assertEquals(expectedLines, readJournalLines());
689    }
690
691    private void createJournal(String... bodyLines) throws Exception {
692        createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines);
693    }
694
695    private void createJournalWithHeader(String magic, String version, String appVersion,
696            String valueCount, String blank, String... bodyLines) throws Exception {
697        Writer writer = new FileWriter(journalFile);
698        writer.write(magic + "\n");
699        writer.write(version + "\n");
700        writer.write(appVersion + "\n");
701        writer.write(valueCount + "\n");
702        writer.write(blank + "\n");
703        for (String line : bodyLines) {
704            writer.write(line);
705            writer.write('\n');
706        }
707        writer.close();
708    }
709
710    private List<String> readJournalLines() throws Exception {
711        List<String> result = new ArrayList<String>();
712        BufferedReader reader = new BufferedReader(new FileReader(journalFile));
713        String line;
714        while ((line = reader.readLine()) != null) {
715            result.add(line);
716        }
717        reader.close();
718        return result;
719    }
720
721    private File getCleanFile(String key, int index) {
722        return new File(cacheDir, key + "." + index);
723    }
724
725    private File getDirtyFile(String key, int index) {
726        return new File(cacheDir, key + "." + index + ".tmp");
727    }
728
729    private String readFile(File file) throws Exception {
730        Reader reader = new FileReader(file);
731        StringWriter writer = new StringWriter();
732        char[] buffer = new char[1024];
733        int count;
734        while ((count = reader.read(buffer)) != -1) {
735            writer.write(buffer, 0, count);
736        }
737        reader.close();
738        return writer.toString();
739    }
740
741    public void writeFile(File file, String content) throws Exception {
742        FileWriter writer = new FileWriter(file);
743        writer.write(content);
744        writer.close();
745    }
746
747    private void assertInoperable(DiskLruCache.Editor editor) throws Exception {
748        try {
749            editor.getString(0);
750            fail();
751        } catch (IllegalStateException expected) {
752        }
753        try {
754            editor.set(0, "A");
755            fail();
756        } catch (IllegalStateException expected) {
757        }
758        try {
759            editor.newInputStream(0);
760            fail();
761        } catch (IllegalStateException expected) {
762        }
763        try {
764            editor.newOutputStream(0);
765            fail();
766        } catch (IllegalStateException expected) {
767        }
768        try {
769            editor.commit();
770            fail();
771        } catch (IllegalStateException expected) {
772        }
773        try {
774            editor.abort();
775            fail();
776        } catch (IllegalStateException expected) {
777        }
778    }
779
780    private void generateSomeGarbageFiles() throws Exception {
781        File dir1 = new File(cacheDir, "dir1");
782        File dir2 = new File(dir1, "dir2");
783        writeFile(getCleanFile("g1", 0), "A");
784        writeFile(getCleanFile("g1", 1), "B");
785        writeFile(getCleanFile("g2", 0), "C");
786        writeFile(getCleanFile("g2", 1), "D");
787        writeFile(getCleanFile("g2", 1), "D");
788        writeFile(new File(cacheDir, "otherFile0"), "E");
789        dir1.mkdir();
790        dir2.mkdir();
791        writeFile(new File(dir2, "otherFile1"), "F");
792    }
793
794    private void assertGarbageFilesAllDeleted() throws Exception {
795        assertFalse(getCleanFile("g1", 0).exists());
796        assertFalse(getCleanFile("g1", 1).exists());
797        assertFalse(getCleanFile("g2", 0).exists());
798        assertFalse(getCleanFile("g2", 1).exists());
799        assertFalse(new File(cacheDir, "otherFile0").exists());
800        assertFalse(new File(cacheDir, "dir1").exists());
801    }
802
803    private void set(String key, String value0, String value1) throws Exception {
804        DiskLruCache.Editor editor = cache.edit(key);
805        editor.set(0, value0);
806        editor.set(1, value1);
807        editor.commit();
808    }
809
810    private void assertAbsent(String key) throws Exception {
811        DiskLruCache.Snapshot snapshot = cache.get(key);
812        if (snapshot != null) {
813            snapshot.close();
814            fail();
815        }
816        assertFalse(getCleanFile(key, 0).exists());
817        assertFalse(getCleanFile(key, 1).exists());
818        assertFalse(getDirtyFile(key, 0).exists());
819        assertFalse(getDirtyFile(key, 1).exists());
820    }
821
822    private void assertValue(String key, String value0, String value1) throws Exception {
823        DiskLruCache.Snapshot snapshot = cache.get(key);
824        assertEquals(value0, snapshot.getString(0));
825        assertEquals(value1, snapshot.getString(1));
826        assertTrue(getCleanFile(key, 0).exists());
827        assertTrue(getCleanFile(key, 1).exists());
828        snapshot.close();
829    }
830}
831