package com.apollographql.apollo.cache.normalized.lru;
import com.apollographql.apollo.cache.ApolloCacheHeaders;
import com.apollographql.apollo.cache.CacheHeaders;
import com.apollographql.apollo.cache.normalized.NormalizedCache;
import com.apollographql.apollo.cache.normalized.Record;
import com.apollographql.apollo.cache.normalized.RecordFieldAdapter;
import com.squareup.moshi.Moshi;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.google.common.truth.Truth.assertThat;
public class LruNormalizedCacheTest {
private RecordFieldAdapter basicFieldAdapter;
@Before public void createFieldAdapter() {
basicFieldAdapter = RecordFieldAdapter.create(new Moshi.Builder().build());
}
@Test
public void testEvictionPolicyBuilder() {
final EvictionPolicy policy = EvictionPolicy.builder()
.maxSizeBytes(100)
.maxEntries(50)
.expireAfterAccess(5, TimeUnit.HOURS)
.expireAfterWrite(10, TimeUnit.DAYS)
.build();
assertThat(policy.maxSizeBytes().get()).isEqualTo(100);
assertThat(policy.maxEntries().get()).isEqualTo(50);
assertThat(policy.expireAfterAccess().get()).isEqualTo(5);
assertThat(policy.expireAfterAccessTimeUnit().get()).isEqualTo(TimeUnit.HOURS);
assertThat(policy.expireAfterWrite().get()).isEqualTo(10);
assertThat(policy.expireAfterWriteTimeUnit().get()).isEqualTo(TimeUnit.DAYS);
}
@Test
public void testSaveAndLoad_singleRecord() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(10 * 1024).build
()).createNormalizedCache(basicFieldAdapter);
Record testRecord = createTestRecord("1");
lruCache.merge(testRecord, CacheHeaders.NONE);
assertTestRecordPresentAndAccurate(testRecord, lruCache);
}
@Test
public void testSaveAndLoad_multipleRecord_readSingle() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(10 * 1024).build
()).createNormalizedCache(basicFieldAdapter);
Record testRecord1 = createTestRecord("1");
Record testRecord2 = createTestRecord("2");
Record testRecord3 = createTestRecord("3");
List<Record> records = Arrays.asList(testRecord1, testRecord2, testRecord3);
lruCache.merge(records, CacheHeaders.NONE);
assertTestRecordPresentAndAccurate(testRecord1, lruCache);
assertTestRecordPresentAndAccurate(testRecord2, lruCache);
assertTestRecordPresentAndAccurate(testRecord3, lruCache);
}
@Test
public void testSaveAndLoad_multipleRecord_readMultiple() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(10 * 1024).build
()).createNormalizedCache(basicFieldAdapter);
Record testRecord1 = createTestRecord("1");
Record testRecord2 = createTestRecord("2");
Record testRecord3 = createTestRecord("3");
List<Record> inputRecords = Arrays.asList(testRecord1, testRecord2, testRecord3);
lruCache.merge(inputRecords, CacheHeaders.NONE);
final Collection<Record> readRecords = lruCache.loadRecords(Arrays.asList("key1", "key2", "key3"),
CacheHeaders.NONE);
//noinspection ResultOfMethodCallIgnored
assertThat(readRecords).containsExactlyElementsIn(inputRecords);
}
@Test
public void testLoad_recordNotPresent() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(10 * 1024).build
()).createNormalizedCache(basicFieldAdapter);
final Record record = lruCache.loadRecord("key1", CacheHeaders.NONE);
assertThat(record).isNull();
}
@Test
public void testEviction() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(2000)
.build()).createNormalizedCache(basicFieldAdapter);
Record.Builder testRecord1Builder = Record.builder("key1");
testRecord1Builder.addField("a", new String(new byte[1100]));
Record testRecord1 = testRecord1Builder.build();
Record.Builder testRecord2Builder = Record.builder("key2");
testRecord2Builder.addField("a", new String(new byte[1100]));
Record testRecord2 = testRecord2Builder.build();
Record.Builder testRecord3Builder = Record.builder("key3");
testRecord3Builder.addField("a", new String(new byte[10]));
Record testRecord3 = testRecord3Builder.build();
List<Record> records = Arrays.asList(
testRecord1,
testRecord2,
testRecord3
);
lruCache.merge(records, CacheHeaders.NONE);
//Cache does not reveal exactly how it handles eviction, but appears
//to evict more than is strictly necessary. Regardless, any sane eviction
//strategy should leave the third record in this test case, and evict the first record.
assertThat(lruCache.loadRecord("key1", CacheHeaders.NONE)).isNull();
assertThat(lruCache.loadRecord("key3", CacheHeaders.NONE)).isNotNull();
}
@Test
public void testEviction_recordChange() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(2000)
.build()).createNormalizedCache(basicFieldAdapter);
Record.Builder testRecord1Builder = Record.builder("key1");
testRecord1Builder.addField("a", new String(new byte[10]));
Record testRecord1 = testRecord1Builder.build();
Record.Builder testRecord2Builder = Record.builder("key2");
testRecord2Builder.addField("a", new String(new byte[10]));
Record testRecord2 = testRecord2Builder.build();
Record.Builder testRecord3Builder = Record.builder("key3");
testRecord3Builder.addField("a", new String(new byte[10]));
Record testRecord3 = testRecord3Builder.build();
List<Record> records = Arrays.asList(
testRecord1,
testRecord2,
testRecord3
);
lruCache.merge(records, CacheHeaders.NONE);
//All records should present
assertThat(lruCache.loadRecord("key1", CacheHeaders.NONE)).isNotNull();
assertThat(lruCache.loadRecord("key2", CacheHeaders.NONE)).isNotNull();
assertThat(lruCache.loadRecord("key3", CacheHeaders.NONE)).isNotNull();
Record.Builder largeTestRecordBuilder = Record.builder("key1");
largeTestRecordBuilder.addField("a", new String(new byte[2000]));
Record largeTestRecord = largeTestRecordBuilder.build();
lruCache.merge(largeTestRecord, CacheHeaders.NONE);
//The large record (Record 1) should be evicted. the other small records should remain.
assertThat(lruCache.loadRecord("key1", CacheHeaders.NONE)).isNull();
assertThat(lruCache.loadRecord("key2", CacheHeaders.NONE)).isNotNull();
assertThat(lruCache.loadRecord("key3", CacheHeaders.NONE)).isNotNull();
}
@Test
public void testDualCacheSingleRecord() {
LruNormalizedCacheFactory secondaryCacheFactory = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION);
LruNormalizedCache primaryCache = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION,
secondaryCacheFactory).createNormalizedCache(basicFieldAdapter);
Record.Builder recordBuilder = Record.builder("root");
recordBuilder.addField("bar", "bar");
final Record record = recordBuilder.build();
primaryCache.merge(record, CacheHeaders.NONE);
//verify write through behavior
assertThat(primaryCache.loadRecord("root",
CacheHeaders.NONE).field("bar")).isEqualTo("bar");
assertThat(primaryCache.secondaryCache().loadRecord("root",
CacheHeaders.NONE).field("bar")).isEqualTo("bar");
}
@Test
public void testDualCacheMultipleRecord() {
LruNormalizedCacheFactory secondaryCacheFactory = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION);
LruNormalizedCache primaryCache = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION,
secondaryCacheFactory).createNormalizedCache(basicFieldAdapter);
Record.Builder recordBuilder = Record.builder("root1");
recordBuilder.addField("bar", "bar");
final Record record1 = recordBuilder.build();
recordBuilder = Record.builder("root2");
recordBuilder.addField("bar", "bar");
final Record record2 = recordBuilder.build();
recordBuilder = Record.builder("root3");
recordBuilder.addField("bar", "bar");
final Record record3 = recordBuilder.build();
Collection<Record> records = Arrays.asList(record1, record2, record3);
Collection<String> keys = Arrays.asList(record1.key(), record2.key(), record3.key());
primaryCache.merge(records, CacheHeaders.NONE);
assertThat(primaryCache.loadRecords(keys, CacheHeaders.NONE).size()).isEqualTo(3);
//verify write through behavior
assertThat(primaryCache.loadRecords(keys, CacheHeaders.NONE).size()).isEqualTo(3);
assertThat(primaryCache.secondaryCache()
.loadRecords(keys, CacheHeaders.NONE).size()).isEqualTo(3);
}
@Test
public void testDualCache_recordNotPresent() {
LruNormalizedCacheFactory secondaryCacheFactory = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION);
LruNormalizedCache primaryCacheStore = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION,
secondaryCacheFactory).createNormalizedCache(basicFieldAdapter);
assertThat(primaryCacheStore.loadRecord("not_present_id", CacheHeaders.NONE)).isNull();
}
@Test
public void testClearAll() {
LruNormalizedCacheFactory secondaryCacheFactory = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION);
LruNormalizedCache primaryCacheStore = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION,
secondaryCacheFactory).createNormalizedCache(basicFieldAdapter);
Record record = Record.builder("key").build();
primaryCacheStore.merge(record, CacheHeaders.NONE);
primaryCacheStore.clearAll();
assertThat(primaryCacheStore.loadRecord("key", CacheHeaders.NONE));
}
@Test
public void testClearPrimaryCache() {
LruNormalizedCacheFactory secondaryCacheFactory = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION);
LruNormalizedCache primaryCache = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION,
secondaryCacheFactory).createNormalizedCache(basicFieldAdapter);
Record record = Record.builder("key").build();
primaryCache.merge(record, CacheHeaders.NONE);
primaryCache.clearPrimaryCache();
assertThat(primaryCache.secondaryCache()
.loadRecord("key", CacheHeaders.NONE)).isNotNull();
assertThat(primaryCache.secondaryCache()
.loadRecord("key", CacheHeaders.NONE)).isNotNull();
}
@Test
public void testClearSecondaryCache() {
LruNormalizedCacheFactory secondaryCacheFactory = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION);
LruNormalizedCache primaryCache = new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION,
secondaryCacheFactory).createNormalizedCache(basicFieldAdapter);
Record record = Record.builder("key").build();
primaryCache.merge(record, CacheHeaders.NONE);
primaryCache.clearSecondaryCache();
assertThat(primaryCache.secondaryCache().loadRecord("key", CacheHeaders.NONE)).isNull();
}
// Tests for StandardCacheHeader compliance.
@Test
public void testHeader_evictAfterRead() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(10 * 1024).build
()).createNormalizedCache(basicFieldAdapter);
Record testRecord = createTestRecord("1");
lruCache.merge(testRecord, CacheHeaders.NONE);
final Record record =
lruCache.loadRecord("key1", CacheHeaders.builder().addHeader(ApolloCacheHeaders.EVICT_AFTER_READ, "true")
.build());
assertThat(record).isNotNull();
final Record nullRecord =
lruCache.loadRecord("key1", CacheHeaders.builder().addHeader(ApolloCacheHeaders.EVICT_AFTER_READ, "true")
.build());
assertThat(nullRecord).isNull();
}
@Test
public void testHeader_noCache() {
LruNormalizedCache lruCache = new LruNormalizedCacheFactory(EvictionPolicy.builder().maxSizeBytes(10 * 1024).build
()).createNormalizedCache(basicFieldAdapter);
Record testRecord = createTestRecord("1");
lruCache.merge(testRecord, CacheHeaders.builder().addHeader(ApolloCacheHeaders.DO_NOT_STORE, "true").build());
final Record record =
lruCache.loadRecord("key1", CacheHeaders.NONE);
assertThat(record).isNull();
}
private void assertTestRecordPresentAndAccurate(Record testRecord, NormalizedCache store) {
final Record cacheRecord1 = store.loadRecord(testRecord.key(), CacheHeaders.NONE);
assertThat(cacheRecord1.key()).isEqualTo(testRecord.key());
assertThat(cacheRecord1.field("a")).isEqualTo(testRecord.field("a"));
assertThat(cacheRecord1.field("b")).isEqualTo(testRecord.field("b"));
}
private Record createTestRecord(String id) {
Record.Builder testRecord = Record.builder("key" + id);
testRecord.addField("a", "stringValueA" + id);
testRecord.addField("b", "stringValueB" + id);
return testRecord.build();
}
}