• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(60 * 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 
createNewCache()65   private void createNewCache() throws IOException {
66     createNewCacheWithSize(Integer.MAX_VALUE);
67   }
68 
createNewCacheWithSize(int maxSize)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 
setUp()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 
tearDown()84   @After public void tearDown() throws Exception {
85     while (!toClose.isEmpty()) {
86       toClose.pop().close();
87     }
88   }
89 
emptyCache()90   @Test public void emptyCache() throws Exception {
91     cache.close();
92     assertJournalEquals();
93   }
94 
validateKey()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 
writeAndReadEntry()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 
readAndWriteEntryAcrossCacheOpenAndClose()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 
readAndWriteEntryWithoutProperClose()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 
journalWithEditAndPublish()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 
revertedNewFileIsRemoveInJournal()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 
unterminatedEditIsRevertedOnClose()216   @Test public void unterminatedEditIsRevertedOnClose() throws Exception {
217     cache.edit("k1");
218     cache.close();
219     assertJournalEquals("DIRTY k1", "REMOVE k1");
220   }
221 
journalDoesNotIncludeReadOfYetUnpublishedValue()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 
journalWithEditAndPublishAndRead()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 
cannotOperateOnEditAfterPublish()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 
cannotOperateOnEditAfterRevert()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 
explicitRemoveAppliedToDiskImmediately()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 
removePreventsActiveEditFromStoringAValue()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    */
readAndWriteOverlapsMaintainConsistency()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 
openWithDirtyKeyDeletesAllFilesForThatKey()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 
openWithInvalidVersionClearsDirectory()334   @Test public void openWithInvalidVersionClearsDirectory() throws Exception {
335     cache.close();
336     generateSomeGarbageFiles();
337     createJournalWithHeader(MAGIC, "0", "100", "2", "");
338     createNewCache();
339     assertGarbageFilesAllDeleted();
340   }
341 
openWithInvalidAppVersionClearsDirectory()342   @Test public void openWithInvalidAppVersionClearsDirectory() throws Exception {
343     cache.close();
344     generateSomeGarbageFiles();
345     createJournalWithHeader(MAGIC, "1", "101", "2", "");
346     createNewCache();
347     assertGarbageFilesAllDeleted();
348   }
349 
openWithInvalidValueCountClearsDirectory()350   @Test public void openWithInvalidValueCountClearsDirectory() throws Exception {
351     cache.close();
352     generateSomeGarbageFiles();
353     createJournalWithHeader(MAGIC, "1", "100", "1", "");
354     createNewCache();
355     assertGarbageFilesAllDeleted();
356   }
357 
openWithInvalidBlankLineClearsDirectory()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 
openWithInvalidJournalLineClearsDirectory()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 
openWithInvalidFileSizeClearsDirectory()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 
openWithTruncatedLineDiscardsThatLine()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 
openWithTooManyFileSizesClearsDirectory()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 
keyWithSpaceNotPermitted()412   @Test public void keyWithSpaceNotPermitted() throws Exception {
413     try {
414       cache.edit("my key");
415       fail();
416     } catch (IllegalArgumentException expected) {
417     }
418   }
419 
keyWithNewlineNotPermitted()420   @Test public void keyWithNewlineNotPermitted() throws Exception {
421     try {
422       cache.edit("my\nkey");
423       fail();
424     } catch (IllegalArgumentException expected) {
425     }
426   }
427 
keyWithCarriageReturnNotPermitted()428   @Test public void keyWithCarriageReturnNotPermitted() throws Exception {
429     try {
430       cache.edit("my\rkey");
431       fail();
432     } catch (IllegalArgumentException expected) {
433     }
434   }
435 
nullKeyThrows()436   @Test public void nullKeyThrows() throws Exception {
437     try {
438       cache.edit(null);
439       fail();
440     } catch (NullPointerException expected) {
441     }
442   }
443 
createNewEntryWithTooFewValuesFails()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 
revertWithTooFewValues()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 
updateExistingEntryWithTooFewValuesReusesPreviousValues()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 
growMaxSize()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 
shrinkMaxSizeEvicts()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 
evictOnInsert()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 
evictOnUpdate()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 
evictionHonorsLruFromCurrentSession()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 
evictionHonorsLruFromPreviousSession()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 
cacheSingleEntryOfSizeGreaterThanMaxSize()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 
cacheSingleValueOfSizeGreaterThanMaxSize()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 
constructorDoesNotAllowZeroCacheSize()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 
constructorDoesNotAllowZeroValuesPerEntry()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 
removeAbsentElement()646   @Test public void removeAbsentElement() throws Exception {
647     cache.remove("a");
648   }
649 
readingTheSameStreamMultipleTimes()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 
rebuildJournalOnRepeatedReads()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 
rebuildJournalOnRepeatedEdits()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> */
rebuildJournalOnRepeatedReadsWithOpenAndClose()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> */
rebuildJournalOnRepeatedEditsWithOpenAndClose()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 
restoreBackupFile()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 
journalFileIsPreferredOverBackupFile()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 
openCreatesDirectoryIfNecessary()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 
fileDeletedExternally()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 
editSameVersion()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 
editSnapshotAfterChangeAborted()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 
editSnapshotAfterChangeCommitted()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 
editSinceEvicted()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 
editSinceEvictedAndRecreated()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> */
aggressiveClearingHandlesWrite()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> */
aggressiveClearingHandlesEdit()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 
removeHandlesMissingFile()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> */
aggressiveClearingHandlesPartialEdit()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> */
aggressiveClearingHandlesRead()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    */
trimToSizeWithActiveEdit()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 
evictAll()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 
evictAllWithPartialCreate()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 
evictAllWithPartialEditDoesNotStoreAValue()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 
evictAllDoesntInterruptPartialRead()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 
editSnapshotAfterEvictAllReturnsNullDueToStaleValue()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 
iterator()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 
iteratorElementsAddedDuringIterationAreOmitted()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 
iteratorElementsUpdatedDuringIterationAreUpdated()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 
iteratorElementsRemovedDuringIterationAreOmitted()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 
iteratorRemove()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 
iteratorRemoveBeforeNext()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 
iteratorRemoveOncePerCallToNext()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 
cacheClosedTruncatesIterator()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 
isClosed_uninitializedCache()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 
journalWriteFailsDuringEdit()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    */
journalWriteFailsDuringEditorCommit()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 
journalWriteFailsDuringEditorAbort()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 
journalWriteFailsDuringRemove()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 
noSizeCorruptionAfterCreatorDetached()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 
noSizeCorruptionAfterEditorDetached()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 
noNewSourceAfterEditorDetached()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 
editsDiscardedAfterEditorDetached()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 
abortAfterDetach()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 
assertJournalEquals(String... expectedBodyLines)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 
createJournal(String... bodyLines)1246   private void createJournal(String... bodyLines) throws Exception {
1247     createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines);
1248   }
1249 
createJournalWithHeader(String magic, String version, String appVersion, String valueCount, String blank, String... bodyLines)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 
readJournalLines()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 
getCleanFile(String key, int index)1275   private File getCleanFile(String key, int index) {
1276     return new File(cacheDir, key + "." + index);
1277   }
1278 
getDirtyFile(String key, int index)1279   private File getDirtyFile(String key, int index) {
1280     return new File(cacheDir, key + "." + index + ".tmp");
1281   }
1282 
readFile(File file)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 
writeFile(File file, String content)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 
assertInoperable(DiskLruCache.Editor editor)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 
generateSomeGarbageFiles()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 
assertGarbageFilesAllDeleted()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 
set(String key, String value0, String value1)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 
setString(DiskLruCache.Editor editor, int index, String value)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 
assertAbsent(String key)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 
assertValue(String key, String value0, String value1)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 
assertSnapshotValue(DiskLruCache.Snapshot snapshot, int index, String value)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 
sourceAsString(Source source)1385   private String sourceAsString(Source source) throws IOException {
1386     return source != null ? Okio.buffer(source).readUtf8() : null;
1387   }
1388 
copyFile(File from, File to)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 
execute(Runnable command)1400     @Override public void execute(Runnable command) {
1401       jobs.addLast(command);
1402     }
1403   }
1404 }
1405