blob: bbb372b5e2ea01da83c49946e766b72beb546349 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream.cache;
import static com.google.android.exoplayer2.testutil.TestUtil.createTestFile;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.net.Uri;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests {@link CachedContentIndex}. */
@RunWith(AndroidJUnit4.class)
public class CachedContentIndexTest {
private final byte[] testIndexV1File = {
0, 0, 0, 1, // version
0, 0, 0, 0, // flags
0, 0, 0, 2, // number_of_CachedContent
0, 0, 0, 5, // cache_id 5
0, 5, 65, 66, 67, 68, 69, // cache_key "ABCDE"
0, 0, 0, 0, 0, 0, 0, 10, // original_content_length
0, 0, 0, 2, // cache_id 2
0, 5, 75, 76, 77, 78, 79, // cache_key "KLMNO"
0, 0, 0, 0, 0, 0, 10, 0, // original_content_length
(byte) 0xF6, (byte) 0xFB, 0x50, 0x41 // hashcode_of_CachedContent_array
};
private final byte[] testIndexV2File = {
0, 0, 0, 2, // version
0, 0, 0, 0, // flags
0, 0, 0, 2, // number_of_CachedContent
0, 0, 0, 5, // cache_id 5
0, 5, 65, 66, 67, 68, 69, // cache_key "ABCDE"
0, 0, 0, 2, // metadata count
0, 9, 101, 120, 111, 95, 114, 101, 100, 105, 114, // "exo_redir"
0, 0, 0, 5, // value length
97, 98, 99, 100, 101, // Redirected Uri "abcde"
0, 7, 101, 120, 111, 95, 108, 101, 110, // "exo_len"
0, 0, 0, 8, // value length
0, 0, 0, 0, 0, 0, 0, 10, // original_content_length
0, 0, 0, 2, // cache_id 2
0, 5, 75, 76, 77, 78, 79, // cache_key "KLMNO"
0, 0, 0, 1, // metadata count
0, 7, 101, 120, 111, 95, 108, 101, 110, // "exo_len"
0, 0, 0, 8, // value length
0, 0, 0, 0, 0, 0, 10, 0, // original_content_length
0x12, 0x15, 0x66, (byte) 0x8A // hashcode_of_CachedContent_array
};
private File cacheDir;
@Before
public void setUp() throws Exception {
cacheDir =
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
}
@After
public void tearDown() {
Util.recursiveDelete(cacheDir);
}
@Test
public void addGetRemove() throws Exception {
final String key1 = "key1";
final String key2 = "key2";
final String key3 = "key3";
CachedContentIndex index = newInstance();
// Add two CachedContents with add methods
CachedContent cachedContent1 = index.getOrAdd(key1);
CachedContent cachedContent2 = index.getOrAdd(key2);
assertThat(cachedContent1.id != cachedContent2.id).isTrue();
// add a span
int cacheFileLength = 20;
File cacheSpanFile =
SimpleCacheSpan.getCacheFile(
cacheDir, cachedContent1.id, /* position= */ 10, /* timestamp= */ 30);
createTestFile(cacheSpanFile, cacheFileLength);
SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(cacheSpanFile, cacheFileLength, index);
assertThat(span).isNotNull();
cachedContent1.addSpan(span);
// Check if they are added and get method returns null if the key isn't found
assertThat(index.get(key1)).isEqualTo(cachedContent1);
assertThat(index.get(key2)).isEqualTo(cachedContent2);
assertThat(index.get(key3)).isNull();
// test getAll()
Collection<CachedContent> cachedContents = index.getAll();
assertThat(cachedContents).containsExactly(cachedContent1, cachedContent2);
// test getKeys()
Set<String> keys = index.getKeys();
assertThat(keys).containsExactly(key1, key2);
// test getKeyForId()
assertThat(index.getKeyForId(cachedContent1.id)).isEqualTo(key1);
assertThat(index.getKeyForId(cachedContent2.id)).isEqualTo(key2);
// test remove()
index.maybeRemove(key2);
index.maybeRemove(key3);
assertThat(index.get(key1)).isEqualTo(cachedContent1);
assertThat(index.get(key2)).isNull();
assertThat(cacheSpanFile.exists()).isTrue();
// test removeEmpty()
index.getOrAdd(key2);
index.removeEmpty();
assertThat(index.get(key1)).isEqualTo(cachedContent1);
assertThat(index.get(key2)).isNull();
assertThat(cacheSpanFile.exists()).isTrue();
}
@Test
public void legacyStoreAndLoad() throws Exception {
assertStoredAndLoadedEqual(newLegacyInstance(), newLegacyInstance());
}
@Test
public void legacyLoadV1() throws Exception {
CachedContentIndex index = newLegacyInstance();
FileOutputStream fos =
new FileOutputStream(new File(cacheDir, CachedContentIndex.FILE_NAME_ATOMIC));
fos.write(testIndexV1File);
fos.close();
index.initialize(/* uid= */ 0);
assertThat(index.getAll()).hasSize(2);
assertThat(index.assignIdForKey("ABCDE")).isEqualTo(5);
ContentMetadata metadata = index.get("ABCDE").getMetadata();
assertThat(ContentMetadata.getContentLength(metadata)).isEqualTo(10);
assertThat(index.assignIdForKey("KLMNO")).isEqualTo(2);
ContentMetadata metadata2 = index.get("KLMNO").getMetadata();
assertThat(ContentMetadata.getContentLength(metadata2)).isEqualTo(2560);
}
@Test
public void legacyLoadV2() throws Exception {
CachedContentIndex index = newLegacyInstance();
FileOutputStream fos =
new FileOutputStream(new File(cacheDir, CachedContentIndex.FILE_NAME_ATOMIC));
fos.write(testIndexV2File);
fos.close();
index.initialize(/* uid= */ 0);
assertThat(index.getAll()).hasSize(2);
assertThat(index.assignIdForKey("ABCDE")).isEqualTo(5);
ContentMetadata metadata = index.get("ABCDE").getMetadata();
assertThat(ContentMetadata.getContentLength(metadata)).isEqualTo(10);
assertThat(ContentMetadata.getRedirectedUri(metadata)).isEqualTo(Uri.parse("abcde"));
assertThat(index.assignIdForKey("KLMNO")).isEqualTo(2);
ContentMetadata metadata2 = index.get("KLMNO").getMetadata();
assertThat(ContentMetadata.getContentLength(metadata2)).isEqualTo(2560);
}
@Test
public void assignIdForKeyAndGetKeyForId() {
CachedContentIndex index = newInstance();
final String key1 = "key1";
final String key2 = "key2";
int id1 = index.assignIdForKey(key1);
int id2 = index.assignIdForKey(key2);
assertThat(index.getKeyForId(id1)).isEqualTo(key1);
assertThat(index.getKeyForId(id2)).isEqualTo(key2);
assertThat(id1 != id2).isTrue();
assertThat(index.assignIdForKey(key1)).isEqualTo(id1);
assertThat(index.assignIdForKey(key2)).isEqualTo(id2);
}
@Test
public void getNewId() {
SparseArray<String> idToKey = new SparseArray<>();
assertThat(CachedContentIndex.getNewId(idToKey)).isEqualTo(0);
idToKey.put(10, "");
assertThat(CachedContentIndex.getNewId(idToKey)).isEqualTo(11);
idToKey.put(Integer.MAX_VALUE, "");
assertThat(CachedContentIndex.getNewId(idToKey)).isEqualTo(0);
idToKey.put(0, "");
assertThat(CachedContentIndex.getNewId(idToKey)).isEqualTo(1);
}
@Test
public void legacyEncryption() throws Exception {
byte[] key = Util.getUtf8Bytes("Bar12345Bar12345"); // 128 bit key
byte[] key2 = Util.getUtf8Bytes("Foo12345Foo12345"); // 128 bit key
assertStoredAndLoadedEqual(newLegacyInstance(key), newLegacyInstance(key));
// Rename the index file from the test above
File file1 = new File(cacheDir, CachedContentIndex.FILE_NAME_ATOMIC);
File file2 = new File(cacheDir, "file2compare");
assertThat(file1.renameTo(file2)).isTrue();
// Write a new index file
assertStoredAndLoadedEqual(newLegacyInstance(key), newLegacyInstance(key));
assertThat(file1.length()).isEqualTo(file2.length());
// Assert file content is different
FileInputStream fis1 = new FileInputStream(file1);
FileInputStream fis2 = new FileInputStream(file2);
for (int b; (b = fis1.read()) == fis2.read(); ) {
assertThat(b != -1).isTrue();
}
boolean threw = false;
try {
assertStoredAndLoadedEqual(newLegacyInstance(key), newLegacyInstance(key2));
} catch (AssertionError e) {
threw = true;
}
assertWithMessage("Encrypted index file can not be read with different encryption key")
.that(threw)
.isTrue();
try {
assertStoredAndLoadedEqual(newLegacyInstance(key), newLegacyInstance());
} catch (AssertionError e) {
threw = true;
}
assertWithMessage("Encrypted index file can not be read without encryption key")
.that(threw)
.isTrue();
// Non encrypted index file can be read even when encryption key provided.
assertStoredAndLoadedEqual(newLegacyInstance(), newLegacyInstance(key));
// Test multiple store() calls
CachedContentIndex index = newLegacyInstance(key);
index.getOrAdd("key3");
index.store();
assertStoredAndLoadedEqual(index, newLegacyInstance(key));
}
@Test
public void removeEmptyNotLockedCachedContent() {
CachedContentIndex index = newInstance();
CachedContent cachedContent = index.getOrAdd("key1");
index.maybeRemove(cachedContent.key);
assertThat(index.get(cachedContent.key)).isNull();
}
@Test
public void cantRemoveNotEmptyCachedContent() throws Exception {
CachedContentIndex index = newInstance();
CachedContent cachedContent = index.getOrAdd("key1");
long cacheFileLength = 20;
File cacheFile =
SimpleCacheSpan.getCacheFile(
cacheDir, cachedContent.id, /* position= */ 10, /* timestamp= */ 30);
createTestFile(cacheFile, cacheFileLength);
SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(cacheFile, cacheFileLength, index);
cachedContent.addSpan(span);
index.maybeRemove(cachedContent.key);
assertThat(index.get(cachedContent.key)).isNotNull();
}
@Test
public void cantRemoveLockedCachedContent() {
CachedContentIndex index = newInstance();
CachedContent cachedContent = index.getOrAdd("key1");
cachedContent.setLocked(true);
index.maybeRemove(cachedContent.key);
assertThat(index.get(cachedContent.key)).isNotNull();
}
private void assertStoredAndLoadedEqual(CachedContentIndex index, CachedContentIndex index2)
throws IOException {
ContentMetadataMutations mutations1 = new ContentMetadataMutations();
ContentMetadataMutations.setContentLength(mutations1, 2560);
index.getOrAdd("KLMNO").applyMetadataMutations(mutations1);
ContentMetadataMutations mutations2 = new ContentMetadataMutations();
ContentMetadataMutations.setContentLength(mutations2, 10);
ContentMetadataMutations.setRedirectedUri(mutations2, Uri.parse("abcde"));
index.getOrAdd("ABCDE").applyMetadataMutations(mutations2);
index.store();
index2.initialize(/* uid= */ 0);
Set<String> keys = index.getKeys();
Set<String> keys2 = index2.getKeys();
assertThat(keys2).isEqualTo(keys);
for (String key : keys) {
assertThat(index2.get(key)).isEqualTo(index.get(key));
}
}
private CachedContentIndex newInstance() {
return new CachedContentIndex(TestUtil.getInMemoryDatabaseProvider());
}
private CachedContentIndex newLegacyInstance() {
return newLegacyInstance(null);
}
private CachedContentIndex newLegacyInstance(@Nullable byte[] key) {
return new CachedContentIndex(
/* databaseProvider= */ null,
cacheDir,
/* legacyStorageSecretKey= */ key,
/* legacyStorageEncrypt= */ key != null,
/* preferLegacyStorage= */ true);
}
}