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