/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.test.utils;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.util.LruCache;
import org.catrobat.catroid.utils.ExpiringLruMemoryCache;
import org.catrobat.catroid.utils.ExpiringLruMemoryObjectCache;
import java.lang.reflect.Constructor;
public final class ExpiringLruMemoryCacheTest extends InstrumentationTestCase {
private static final class TestClock implements ExpiringLruMemoryCache.ClockInterface {
public long elapsedTime = 0;
@Override
public long elapsedRealTime() {
return (elapsedTime != 0) ? elapsedTime : SystemClock.elapsedRealtime();
}
}
private static final long EXPIRE_TIME = 300; // 300ms (in ms)
private ExpiringLruMemoryObjectCache<String> textCache;
private TestClock testClock;
public ExpiringLruMemoryCacheTest() {
super();
}
@Override
protected void setUp() throws Exception {
super.setUp();
testClock = new TestClock();
int maxNumOfEntries = 2; // number of entries
// this bypasses the getInstance() singleton method
Class[] constructorArgs = new Class[] { Long.TYPE, LruCache.class, ExpiringLruMemoryCache.ClockInterface.class };
Constructor<ExpiringLruMemoryObjectCache> textCacheConstructor = ExpiringLruMemoryObjectCache.class.getDeclaredConstructor(constructorArgs);
textCacheConstructor.setAccessible(true);
// use introspection for accessing private constructor
LruCache<String, String> lruTextCache = new LruCache<String, String>(maxNumOfEntries) {
@Override
protected void entryRemoved(boolean evicted, String key, String oldValue, String newValue) {
textCache.removeExpiryTime(key);
}
@Override
protected int sizeOf(String key, String value) {
return 1;
}
};
textCache = (ExpiringLruMemoryObjectCache<String>) textCacheConstructor.newInstance(Long.valueOf(EXPIRE_TIME),
lruTextCache, testClock);
}
//----------------------------------------------------------------------------------------------
// text cache tests
//----------------------------------------------------------------------------------------------
public void testFetchingNotExistingKeyShouldFail() {
final String key = "a";
assertNull("Fetching a not existing cache-entry must return null", textCache.get(key));
}
public void testFetchingExpiryTimeForNotExistingKeyShouldReturnZero() {
final String key = "a";
assertEquals("getExpiryTime() should return zero for non existing entries!", 0, textCache.getExpiryTime(key));
}
public void testShouldReturnTextForNonExpiredText() {
final String key = "a";
final String value = "A";
textCache.put(key, value);
String resultA = textCache.get(key);
assertNotNull("Cache must NOT return null for non-expired entry", resultA);
assertEquals("Cache entry differs from original value", value, resultA);
}
public void testShouldReturnNullForExpiredText() {
final String key = "a";
final String value = "A";
testClock.elapsedTime = SystemClock.elapsedRealtime();
textCache.put(key, value);
testClock.elapsedTime += EXPIRE_TIME + 100; // simulate wait until key gets expired!
assertNull("Cache must return null for expired entries", textCache.get(key));
}
public void testRemoveNonExpiredText() {
final String key = "a";
final String value = "A";
textCache.put(key, value);
assertNotNull("remove() must return removed entry but returned null", textCache.remove(key));
assertNull("Remove entry still available!", textCache.get(key));
}
public void testExpiryTimeForNonExpiredText() {
final String key = "a";
final String value = "A";
testClock.elapsedTime = SystemClock.elapsedRealtime();
textCache.put(key, value);
final long expiryTime = textCache.getExpiryTime(key);
assertTrue("Key does not exist or is not valid any more!", expiryTime != 0);
assertEquals("Actual expiry-time differs from expected time", testClock.elapsedTime + EXPIRE_TIME, expiryTime);
}
public void testAccessingTextShouldNotIncreaseExpiryTime() {
final String key = "a";
final String value = "A";
testClock.elapsedTime = SystemClock.elapsedRealtime();
textCache.put(key, value); // create key
final long initialExpiryTime = textCache.getExpiryTime(key);
assertTrue("Key does not exist or is not valid any more!", initialExpiryTime != 0);
testClock.elapsedTime += EXPIRE_TIME - 100; // simulate wait 200ms! key should remain valid!
assertNotNull("Entry not available any more but expiry-time not exhausted", textCache.get(key));
assertEquals("Key does not exist or expiry time changed unexpectedly!",
initialExpiryTime, textCache.getExpiryTime(key));
testClock.elapsedTime += EXPIRE_TIME - 100; // simulate wait another 200ms!
assertNull("Entry should not be valid/available any more!", textCache.get(key));
assertEquals("Key has not been removed!", 0, textCache.getExpiryTime(key));
}
public void testRemovingExpiryTimeOfText() {
final String key = "a";
final String value = "A";
textCache.put(key, value);
textCache.removeExpiryTime(key);
assertEquals("Expiry-time should have been invalidated but is not 0!", 0, textCache.getExpiryTime(key));
}
public void testRemovingExpiryTimeOfTextShouldRemoveCacheEntry() {
final String key = "a";
final String value = "A";
textCache.put(key, value);
textCache.removeExpiryTime(key);
assertNull("Entry with invalidated expiry-time still available!", textCache.get(key));
}
public void testExceedingMaxSizeShouldRemoveLeastRecentlyUsedTextEntryAndRemoveExpiryTime() {
final String keyA = "a";
final String valueA = "A";
final String keyB = "b";
final String valueB = "B";
final String keyC = "c";
final String valueC = "C";
textCache.put(keyA, valueA);
textCache.put(keyB, valueB);
final long expiryTimeB = textCache.getExpiryTime(keyB);
// we are at 2, which is our maximum
// => let's access "b" multiple times and never "use" "a"
textCache.get(keyB);
textCache.get(keyB);
textCache.get(keyB);
// now add another, which should evict "a"
textCache.put(keyC, valueC);
assertNotNull("Replaced entry still available, but should have been overwritten!", textCache.get(keyC));
assertTrue("Expiry time of replaced entry not updated!", textCache.getExpiryTime(keyC) != 0);
assertNotNull("Not-replaced entry has been removed unexpectedly!", textCache.get(keyB));
assertEquals("Expiry-time of not-replaced entry has been unexpectedly updated!",
expiryTimeB, textCache.getExpiryTime(keyB));
assertNull("LRU algorithm did not removed least recently used entry", textCache.get(keyA));
assertEquals("LRU algorithm did not invalidate expiry time of least recently used entry",
0, textCache.getExpiryTime(keyA));
}
}