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