Improve SimpleCacheTest

PiperOrigin-RevId: 313630376
This commit is contained in:
olly 2020-05-28 19:50:27 +01:00 committed by Oliver Woodman
parent 1cfb68bf68
commit cf726f0c60

View File

@ -18,9 +18,10 @@ package com.google.android.exoplayer2.upstream.cache;
import static com.google.android.exoplayer2.C.LENGTH_UNSET; import static com.google.android.exoplayer2.C.LENGTH_UNSET;
import static com.google.android.exoplayer2.util.Util.toByteArray; import static com.google.android.exoplayer2.util.Util.toByteArray;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
@ -32,7 +33,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.NavigableSet; import java.util.NavigableSet;
import java.util.Random; import java.util.Random;
import java.util.Set;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -44,26 +44,29 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class SimpleCacheTest { public class SimpleCacheTest {
private static final byte[] ENCRYPTED_INDEX_KEY = Util.getUtf8Bytes("Bar12345Bar12345");
private static final String KEY_1 = "key1"; private static final String KEY_1 = "key1";
private static final String KEY_2 = "key2"; private static final String KEY_2 = "key2";
private File testDir;
private File cacheDir; private File cacheDir;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
cacheDir = Util.createTempFile(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); testDir = Util.createTempFile(ApplicationProvider.getApplicationContext(), "SimpleCacheTest");
// Delete the file. SimpleCache initialization should create a directory with the same name. assertThat(testDir.delete()).isTrue();
assertThat(cacheDir.delete()).isTrue(); assertThat(testDir.mkdirs()).isTrue();
cacheDir = new File(testDir, "cache");
} }
@After @After
public void tearDown() { public void tearDown() {
Util.recursiveDelete(cacheDir); Util.recursiveDelete(testDir);
} }
@Test @Test
public void cacheInitialization() { public void newInstance_withEmptyDirectory() {
SimpleCache cache = getSimpleCache(); SimpleCache cache = getSimpleCache();
// Cache initialization should have created a non-negative UID. // Cache initialization should have created a non-negative UID.
@ -76,10 +79,13 @@ public class SimpleCacheTest {
cache.release(); cache.release();
cache = getSimpleCache(); cache = getSimpleCache();
assertThat(cache.getUid()).isEqualTo(uid); assertThat(cache.getUid()).isEqualTo(uid);
// Cache should be empty.
assertThat(cache.getKeys()).isEmpty();
} }
@Test @Test
public void cacheInitializationError() throws IOException { public void newInstance_withConflictingFile_fails() throws IOException {
// Creating a file where the cache should be will cause an error during initialization. // Creating a file where the cache should be will cause an error during initialization.
assertThat(cacheDir.createNewFile()).isTrue(); assertThat(cacheDir.createNewFile()).isTrue();
@ -90,52 +96,172 @@ public class SimpleCacheTest {
} }
@Test @Test
public void committingOneFile() throws Exception { public void newInstance_withExistingCacheDirectory_loadsCachedData() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0); // Write some data and metadata to the cache.
assertThat(cacheSpan1.isCached).isFalse(); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
assertThat(cacheSpan1.isOpenEnded()).isTrue(); addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(holeSpan);
ContentMetadataMutations mutations = new ContentMetadataMutations();
ContentMetadataMutations.setRedirectedUri(mutations, Uri.parse("https://redirect.google.com"));
simpleCache.applyContentMetadataMutations(KEY_1, mutations);
simpleCache.release();
assertThat(simpleCache.startReadWriteNonBlocking(KEY_1, 0)).isNull(); // Create a new instance pointing to the same directory.
simpleCache = getSimpleCache();
NavigableSet<CacheSpan> cachedSpans = simpleCache.getCachedSpans(KEY_1); // Read the cached data and metadata back.
assertThat(cachedSpans.isEmpty()).isTrue(); CacheSpan fileSpan = simpleCache.startReadWrite(KEY_1, 0);
assertThat(simpleCache.getCacheSpace()).isEqualTo(0); assertCachedDataReadCorrect(fileSpan);
assertThat(ContentMetadata.getRedirectedUri(simpleCache.getContentMetadata(KEY_1)))
.isEqualTo(Uri.parse("https://redirect.google.com"));
}
@Test
public void newInstance_withExistingCacheInstance_fails() {
getSimpleCache();
// Instantiation should fail because the directory is locked by the first instance.
assertThrows(IllegalStateException.class, this::getSimpleCache);
}
@Test
public void newInstance_withExistingCacheDirectory_resolvesInconsistentState() throws Exception {
SimpleCache simpleCache = getSimpleCache();
CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(holeSpan);
simpleCache.removeSpan(simpleCache.getCachedSpans(KEY_1).first());
// Don't release the cache. This means the index file won't have been written to disk after the
// span was removed. Move the cache directory instead, so we can reload it without failing the
// folder locking check.
File cacheDir2 = new File(testDir, "cache2");
cacheDir.renameTo(cacheDir2);
// Create a new instance pointing to the new directory.
simpleCache = new SimpleCache(cacheDir2, new NoOpCacheEvictor());
// The entry for KEY_1 should have been removed when the cache was reloaded.
assertThat(simpleCache.getCachedSpans(KEY_1)).isEmpty();
}
@Test
public void newInstance_withEncryptedIndex() throws Exception {
SimpleCache simpleCache = getEncryptedSimpleCache(ENCRYPTED_INDEX_KEY);
CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(holeSpan);
simpleCache.release();
// Create a new instance pointing to the same directory.
simpleCache = getEncryptedSimpleCache(ENCRYPTED_INDEX_KEY);
// Read the cached data back.
CacheSpan fileSpan = simpleCache.startReadWrite(KEY_1, 0);
assertCachedDataReadCorrect(fileSpan);
}
@Test
public void newInstance_withEncryptedIndexAndWrongKey_clearsCache() throws Exception {
SimpleCache simpleCache = getEncryptedSimpleCache(ENCRYPTED_INDEX_KEY);
// Write data.
CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(holeSpan);
simpleCache.release();
// Create a new instance pointing to the same directory, with a different key.
simpleCache = getEncryptedSimpleCache(Util.getUtf8Bytes("Foo12345Foo12345"));
// Cache should be cleared.
assertThat(simpleCache.getKeys()).isEmpty();
assertNoCacheFiles(cacheDir); assertNoCacheFiles(cacheDir);
}
@Test
public void newInstance_withEncryptedIndexAndNoKey_clearsCache() throws Exception {
SimpleCache simpleCache = getEncryptedSimpleCache(ENCRYPTED_INDEX_KEY);
// Write data.
CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(holeSpan);
simpleCache.release();
// Create a new instance pointing to the same directory, with no key.
simpleCache = getSimpleCache();
// Cache should be cleared.
assertThat(simpleCache.getKeys()).isEmpty();
assertNoCacheFiles(cacheDir);
}
@Test
public void write_oneLock_oneFile_thenRead() throws Exception {
SimpleCache simpleCache = getSimpleCache();
CacheSpan holeSpan1 = simpleCache.startReadWrite(KEY_1, 0);
assertThat(holeSpan1.isCached).isFalse();
assertThat(holeSpan1.isOpenEnded()).isTrue();
addCache(simpleCache, KEY_1, 0, 15); addCache(simpleCache, KEY_1, 0, 15);
Set<String> cachedKeys = simpleCache.getKeys(); CacheSpan readSpan = simpleCache.startReadWrite(KEY_1, 0);
assertThat(cachedKeys).containsExactly(KEY_1); assertThat(readSpan.position).isEqualTo(0);
cachedSpans = simpleCache.getCachedSpans(KEY_1); assertThat(readSpan.length).isEqualTo(15);
assertThat(cachedSpans).contains(cacheSpan1); assertCachedDataReadCorrect(readSpan);
assertThat(simpleCache.getCacheSpace()).isEqualTo(15); assertThat(simpleCache.getCacheSpace()).isEqualTo(15);
simpleCache.releaseHoleSpan(cacheSpan1);
CacheSpan cacheSpan2 = simpleCache.startReadWrite(KEY_1, 0);
assertThat(cacheSpan2.isCached).isTrue();
assertThat(cacheSpan2.isOpenEnded()).isFalse();
assertThat(cacheSpan2.length).isEqualTo(15);
assertCachedDataReadCorrect(cacheSpan2);
} }
@Test @Test
public void readCacheWithoutReleasingWriteCacheSpan() throws Exception { public void write_oneLock_twoFiles_thenRead() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15); addCache(simpleCache, KEY_1, 0, 7);
CacheSpan cacheSpan2 = simpleCache.startReadWrite(KEY_1, 0); addCache(simpleCache, KEY_1, 7, 8);
assertCachedDataReadCorrect(cacheSpan2);
simpleCache.releaseHoleSpan(cacheSpan1); CacheSpan readSpan1 = simpleCache.startReadWrite(KEY_1, 0);
assertThat(readSpan1.position).isEqualTo(0);
assertThat(readSpan1.length).isEqualTo(7);
assertCachedDataReadCorrect(readSpan1);
CacheSpan readSpan2 = simpleCache.startReadWrite(KEY_1, 7);
assertThat(readSpan2.position).isEqualTo(7);
assertThat(readSpan2.length).isEqualTo(8);
assertCachedDataReadCorrect(readSpan2);
assertThat(simpleCache.getCacheSpace()).isEqualTo(15);
} }
@Test @Test
public void setGetContentMetadata() throws Exception { public void write_differentKeyLocked_thenRead() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan holeSpan1 = simpleCache.startReadWrite(KEY_1, 50);
CacheSpan holeSpan2 = simpleCache.startReadWrite(KEY_2, 50);
assertThat(holeSpan1.isCached).isFalse();
assertThat(holeSpan1.isOpenEnded()).isTrue();
addCache(simpleCache, KEY_2, 0, 15);
CacheSpan readSpan = simpleCache.startReadWrite(KEY_2, 0);
assertThat(readSpan.length).isEqualTo(15);
assertCachedDataReadCorrect(readSpan);
assertThat(simpleCache.getCacheSpace()).isEqualTo(15);
}
@Test
public void write_sameKeyLocked_fails() throws Exception {
SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 50);
assertThat(simpleCache.startReadWriteNonBlocking(KEY_1, 25)).isNull();
}
@Test
public void applyContentMetadataMutations_setsContentLength() throws Exception {
SimpleCache simpleCache = getSimpleCache();
assertThat(ContentMetadata.getContentLength(simpleCache.getContentMetadata(KEY_1))) assertThat(ContentMetadata.getContentLength(simpleCache.getContentMetadata(KEY_1)))
.isEqualTo(LENGTH_UNSET); .isEqualTo(LENGTH_UNSET);
@ -144,85 +270,6 @@ public class SimpleCacheTest {
simpleCache.applyContentMetadataMutations(KEY_1, mutations); simpleCache.applyContentMetadataMutations(KEY_1, mutations);
assertThat(ContentMetadata.getContentLength(simpleCache.getContentMetadata(KEY_1))) assertThat(ContentMetadata.getContentLength(simpleCache.getContentMetadata(KEY_1)))
.isEqualTo(15); .isEqualTo(15);
simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
mutations = new ContentMetadataMutations();
ContentMetadataMutations.setContentLength(mutations, 150);
simpleCache.applyContentMetadataMutations(KEY_1, mutations);
assertThat(ContentMetadata.getContentLength(simpleCache.getContentMetadata(KEY_1)))
.isEqualTo(150);
addCache(simpleCache, KEY_1, 140, 10);
simpleCache.release();
// Check if values are kept after cache is reloaded.
SimpleCache simpleCache2 = getSimpleCache();
assertThat(ContentMetadata.getContentLength(simpleCache2.getContentMetadata(KEY_1)))
.isEqualTo(150);
// Removing the last span shouldn't cause the length be change next time cache loaded
CacheSpan lastSpan = simpleCache2.startReadWrite(KEY_1, 145);
simpleCache2.removeSpan(lastSpan);
simpleCache2.release();
simpleCache2 = getSimpleCache();
assertThat(ContentMetadata.getContentLength(simpleCache2.getContentMetadata(KEY_1)))
.isEqualTo(150);
}
@Test
public void reloadCache() throws Exception {
SimpleCache simpleCache = getSimpleCache();
// write data
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(cacheSpan1);
simpleCache.release();
// Reload cache
simpleCache = getSimpleCache();
// read data back
CacheSpan cacheSpan2 = simpleCache.startReadWrite(KEY_1, 0);
assertCachedDataReadCorrect(cacheSpan2);
}
@Test
public void reloadCacheWithoutRelease() throws Exception {
SimpleCache simpleCache = getSimpleCache();
// Write data for KEY_1.
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(cacheSpan1);
// Write and remove data for KEY_2.
CacheSpan cacheSpan2 = simpleCache.startReadWrite(KEY_2, 0);
addCache(simpleCache, KEY_2, 0, 15);
simpleCache.releaseHoleSpan(cacheSpan2);
simpleCache.removeSpan(simpleCache.getCachedSpans(KEY_2).first());
// Don't release the cache. This means the index file won't have been written to disk after the
// data for KEY_2 was removed. Move the cache instead, so we can reload it without failing the
// folder locking check.
File cacheDir2 =
Util.createTempFile(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
cacheDir2.delete();
cacheDir.renameTo(cacheDir2);
// Reload the cache from its new location.
simpleCache = new SimpleCache(cacheDir2, new NoOpCacheEvictor());
// Read data back for KEY_1.
CacheSpan cacheSpan3 = simpleCache.startReadWrite(KEY_1, 0);
assertCachedDataReadCorrect(cacheSpan3);
// Check the entry for KEY_2 was removed when the cache was reloaded.
assertThat(simpleCache.getCachedSpans(KEY_2)).isEmpty();
Util.recursiveDelete(cacheDir2);
} }
@Test @Test
@ -241,64 +288,6 @@ public class SimpleCacheTest {
assertThat(simpleCache.getCachedSpans(KEY_2)).hasSize(1); assertThat(simpleCache.getCachedSpans(KEY_2)).hasSize(1);
} }
@Test
public void encryptedIndex() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
SimpleCache simpleCache = getEncryptedSimpleCache(key);
// write data
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(cacheSpan1);
simpleCache.release();
// Reload cache
simpleCache = getEncryptedSimpleCache(key);
// read data back
CacheSpan cacheSpan2 = simpleCache.startReadWrite(KEY_1, 0);
assertCachedDataReadCorrect(cacheSpan2);
}
@Test
public void encryptedIndexWrongKey() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
SimpleCache simpleCache = getEncryptedSimpleCache(key);
// write data
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(cacheSpan1);
simpleCache.release();
// Reload cache
byte[] key2 = Util.getUtf8Bytes("Foo12345Foo12345"); // 128 bit key
simpleCache = getEncryptedSimpleCache(key2);
// Cache should be cleared
assertThat(simpleCache.getKeys()).isEmpty();
assertNoCacheFiles(cacheDir);
}
@Test
public void encryptedIndexLostKey() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
SimpleCache simpleCache = getEncryptedSimpleCache(key);
// write data
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15);
simpleCache.releaseHoleSpan(cacheSpan1);
simpleCache.release();
// Reload cache
simpleCache = getSimpleCache();
// Cache should be cleared
assertThat(simpleCache.getKeys()).isEmpty();
assertNoCacheFiles(cacheDir);
}
@Test @Test
public void getCachedLength_noCachedContent_returnsNegativeMaxHoleLength() { public void getCachedLength_noCachedContent_returnsNegativeMaxHoleLength() {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
@ -320,9 +309,9 @@ public class SimpleCacheTest {
@Test @Test
public void getCachedLength_returnsNegativeHoleLength() throws Exception { public void getCachedLength_returnsNegativeHoleLength() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0);
addCache(simpleCache, KEY_1, /* position= */ 50, /* length= */ 50); addCache(simpleCache, KEY_1, /* position= */ 50, /* length= */ 50);
simpleCache.releaseHoleSpan(cacheSpan); simpleCache.releaseHoleSpan(holeSpan);
assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100)) assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100))
.isEqualTo(-50); .isEqualTo(-50);
@ -341,9 +330,9 @@ public class SimpleCacheTest {
@Test @Test
public void getCachedLength_returnsCachedLength() throws Exception { public void getCachedLength_returnsCachedLength() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0);
addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 50); addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 50);
simpleCache.releaseHoleSpan(cacheSpan); simpleCache.releaseHoleSpan(holeSpan);
assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100)) assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100))
.isEqualTo(50); .isEqualTo(50);
@ -364,10 +353,10 @@ public class SimpleCacheTest {
@Test @Test
public void getCachedLength_withMultipleAdjacentSpans_returnsCachedLength() throws Exception { public void getCachedLength_withMultipleAdjacentSpans_returnsCachedLength() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0);
addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 25); addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 25);
addCache(simpleCache, KEY_1, /* position= */ 25, /* length= */ 25); addCache(simpleCache, KEY_1, /* position= */ 25, /* length= */ 25);
simpleCache.releaseHoleSpan(cacheSpan); simpleCache.releaseHoleSpan(holeSpan);
assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100)) assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100))
.isEqualTo(50); .isEqualTo(50);
@ -388,10 +377,10 @@ public class SimpleCacheTest {
@Test @Test
public void getCachedLength_withMultipleNonAdjacentSpans_returnsCachedLength() throws Exception { public void getCachedLength_withMultipleNonAdjacentSpans_returnsCachedLength() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0);
addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 10); addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 10);
addCache(simpleCache, KEY_1, /* position= */ 15, /* length= */ 35); addCache(simpleCache, KEY_1, /* position= */ 15, /* length= */ 35);
simpleCache.releaseHoleSpan(cacheSpan); simpleCache.releaseHoleSpan(holeSpan);
assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100)) assertThat(simpleCache.getCachedLength(KEY_1, /* position= */ 0, /* length= */ 100))
.isEqualTo(10); .isEqualTo(10);
@ -430,10 +419,10 @@ public class SimpleCacheTest {
@Test @Test
public void getCachedBytes_withMultipleAdjacentSpans_returnsCachedBytes() throws Exception { public void getCachedBytes_withMultipleAdjacentSpans_returnsCachedBytes() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0);
addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 25); addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 25);
addCache(simpleCache, KEY_1, /* position= */ 25, /* length= */ 25); addCache(simpleCache, KEY_1, /* position= */ 25, /* length= */ 25);
simpleCache.releaseHoleSpan(cacheSpan); simpleCache.releaseHoleSpan(holeSpan);
assertThat(simpleCache.getCachedBytes(KEY_1, /* position= */ 0, /* length= */ 100)) assertThat(simpleCache.getCachedBytes(KEY_1, /* position= */ 0, /* length= */ 100))
.isEqualTo(50); .isEqualTo(50);
@ -454,10 +443,10 @@ public class SimpleCacheTest {
@Test @Test
public void getCachedBytes_withMultipleNonAdjacentSpans_returnsCachedBytes() throws Exception { public void getCachedBytes_withMultipleNonAdjacentSpans_returnsCachedBytes() throws Exception {
SimpleCache simpleCache = getSimpleCache(); SimpleCache simpleCache = getSimpleCache();
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, /* position= */ 0);
addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 10); addCache(simpleCache, KEY_1, /* position= */ 0, /* length= */ 10);
addCache(simpleCache, KEY_1, /* position= */ 15, /* length= */ 35); addCache(simpleCache, KEY_1, /* position= */ 15, /* length= */ 35);
simpleCache.releaseHoleSpan(cacheSpan); simpleCache.releaseHoleSpan(holeSpan);
assertThat(simpleCache.getCachedBytes(KEY_1, /* position= */ 0, /* length= */ 100)) assertThat(simpleCache.getCachedBytes(KEY_1, /* position= */ 0, /* length= */ 100))
.isEqualTo(45); .isEqualTo(45);
@ -477,7 +466,7 @@ public class SimpleCacheTest {
/* Tests https://github.com/google/ExoPlayer/issues/3260 case. */ /* Tests https://github.com/google/ExoPlayer/issues/3260 case. */
@Test @Test
public void exceptionDuringEvictionByLeastRecentlyUsedCacheEvictorNotHang() throws Exception { public void exceptionDuringIndexStore_doesNotPreventEviction() throws Exception {
CachedContentIndex contentIndex = CachedContentIndex contentIndex =
Mockito.spy(new CachedContentIndex(TestUtil.getInMemoryDatabaseProvider())); Mockito.spy(new CachedContentIndex(TestUtil.getInMemoryDatabaseProvider()));
SimpleCache simpleCache = SimpleCache simpleCache =
@ -485,7 +474,7 @@ public class SimpleCacheTest {
cacheDir, new LeastRecentlyUsedCacheEvictor(20), contentIndex, /* fileIndex= */ null); cacheDir, new LeastRecentlyUsedCacheEvictor(20), contentIndex, /* fileIndex= */ null);
// Add some content. // Add some content.
CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, 0); CacheSpan holeSpan = simpleCache.startReadWrite(KEY_1, 0);
addCache(simpleCache, KEY_1, 0, 15); addCache(simpleCache, KEY_1, 0, 15);
// Make index.store() throw exception from now on. // Make index.store() throw exception from now on.
@ -496,55 +485,24 @@ public class SimpleCacheTest {
.when(contentIndex) .when(contentIndex)
.store(); .store();
// Adding more content will make LeastRecentlyUsedCacheEvictor evict previous content. // Adding more content should evict previous content.
try { assertThrows(CacheException.class, () -> addCache(simpleCache, KEY_1, 15, 15));
addCache(simpleCache, KEY_1, 15, 15); simpleCache.releaseHoleSpan(holeSpan);
assertWithMessage("Exception was expected").fail();
} catch (CacheException e) {
// do nothing.
}
simpleCache.releaseHoleSpan(cacheSpan); // Although store() failed, the first span should have been removed and the new one added.
// Although store() has failed, it should remove the first span and add the new one.
NavigableSet<CacheSpan> cachedSpans = simpleCache.getCachedSpans(KEY_1); NavigableSet<CacheSpan> cachedSpans = simpleCache.getCachedSpans(KEY_1);
assertThat(cachedSpans).isNotEmpty();
assertThat(cachedSpans).hasSize(1); assertThat(cachedSpans).hasSize(1);
assertThat(cachedSpans.pollFirst().position).isEqualTo(15); CacheSpan fileSpan = cachedSpans.first();
assertThat(fileSpan.position).isEqualTo(15);
assertThat(fileSpan.length).isEqualTo(15);
} }
@Test @Test
public void usingReleasedSimpleCacheThrowsException() throws Exception { public void usingReleasedCache_throwsException() {
SimpleCache simpleCache = new SimpleCache(cacheDir, new NoOpCacheEvictor()); SimpleCache simpleCache = new SimpleCache(cacheDir, new NoOpCacheEvictor());
simpleCache.release(); simpleCache.release();
assertThrows(
try { IllegalStateException.class, () -> simpleCache.startReadWriteNonBlocking(KEY_1, 0));
simpleCache.startReadWriteNonBlocking(KEY_1, 0);
assertWithMessage("Exception was expected").fail();
} catch (RuntimeException e) {
// Expected. Do nothing.
}
}
@Test
public void multipleSimpleCacheWithSameCacheDirThrowsException() throws Exception {
new SimpleCache(cacheDir, new NoOpCacheEvictor());
try {
new SimpleCache(cacheDir, new NoOpCacheEvictor());
assertWithMessage("Exception was expected").fail();
} catch (IllegalStateException e) {
// Expected. Do nothing.
}
}
@Test
public void multipleSimpleCacheWithSameCacheDirDoesNotThrowsExceptionAfterRelease()
throws Exception {
SimpleCache simpleCache = new SimpleCache(cacheDir, new NoOpCacheEvictor());
simpleCache.release();
new SimpleCache(cacheDir, new NoOpCacheEvictor());
} }
private SimpleCache getSimpleCache() { private SimpleCache getSimpleCache() {
@ -588,8 +546,7 @@ public class SimpleCacheTest {
private static byte[] generateData(String key, int position, int length) { private static byte[] generateData(String key, int position, int length) {
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
new Random((long) (key.hashCode() ^ position)).nextBytes(bytes); new Random(key.hashCode() ^ position).nextBytes(bytes);
return bytes; return bytes;
} }
} }