/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube 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. * * 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.diqube.cache; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import org.diqube.cache.CountingCache.MemoryConsumptionProvider; import org.testng.Assert; import org.testng.annotations.Test; /** * Tests {@link CountingCache}. * * @author Bastian Gloeckle */ public class CountingCacheTest { private static final MemoryConsumptionProvider<CachedValue> MEM_PROV = (v) -> v.memorySize; @Test public void simpleAdditionCleanupAlways() { simpleAddition(true); } @Test public void simpleAdditionCleanupNever() { simpleAddition(false); } private void simpleAddition(boolean cleanupAlways) { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> cleanupAlways, MEM_PROV); // WHEN cache.offer(0, "1", value("1", 50)); cache.offer(1, "2", value("2", 50)); cache.offer(1, "2", value("2", 50)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), Arrays.asList("1"), "Expected to get correct cache entrie(s)"); Assert.assertEquals(getNames(cache.getAll(1)), Arrays.asList("2"), "Expected to get correct cache entrie(s)"); Assert.assertNotNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(1, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 2); } @Test public void simpleAdditionWithEvictCleanupAlways() { simpleAdditionWithEvict(true); } @Test public void simpleAdditionWithEvictCleanupNever() { simpleAdditionWithEvict(false); } private void simpleAdditionWithEvict(boolean cleanupAlways) { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> cleanupAlways, MEM_PROV); // WHEN cache.offer(0, "1", value("1", 50)); // count "2" = 2 cache.offer(1, "2", value("2", 51)); cache.offer(1, "2", value("2", 51)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), Arrays.asList(), "Expected to get correct cache entrie(s)"); Assert.assertEquals(getNames(cache.getAll(1)), Arrays.asList("2"), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(1, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 1); } @Test public void firstAdditionTooBigCleanupAlways() { firstAdditionTooBig(true); } @Test public void firstAdditionTooBigCleanupNever() { firstAdditionTooBig(false); } private void firstAdditionTooBig(boolean cleanupAlways) { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> cleanupAlways, MEM_PROV); // WHEN cache.offer(0, "1", value("1", 101)); cache.offer(0, "2", value("2", 99)); // THEN // expected: nothing cached. Because both entries have the same count - the first one is too big, the second is // though ordered after the first, so it will not be cached. Assert.assertEquals(getNames(cache.getAll(0)), Arrays.asList(), "Expected to get correct cache entrie(s)"); Assert.assertEquals(getNames(cache.getAll(1)), Arrays.asList(), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNull(cache.get(1, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 0); } @Test public void firstAdditionTooBigSecondMoreCountCleanupAlways() { firstAdditionTooBigSecondMoreCount(true); } @Test public void firstAdditionTooBigSecondMoreCountCleanupNever() { firstAdditionTooBigSecondMoreCount(false); } private void firstAdditionTooBigSecondMoreCount(boolean cleanupAlways) { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> cleanupAlways, MEM_PROV); // WHEN cache.offer(0, "1", value("1", 101)); cache.offer(1, "2", value("2", 99)); cache.offer(1, "2", value("2", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), Arrays.asList(), "Expected to get correct cache entrie(s)"); Assert.assertEquals(getNames(cache.getAll(1)), Arrays.asList("2"), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(1, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 1); } @Test public void firstAdditionTooBigSecondMoreCountOnSingleTableShardCleanupAlways() { firstAdditionTooBigSecondMoreCountOnSingleTableShard(true); } @Test public void firstAdditionTooBigSecondMoreCountOnSingleTableShardCleanupNever() { firstAdditionTooBigSecondMoreCountOnSingleTableShard(false); } private void firstAdditionTooBigSecondMoreCountOnSingleTableShard(boolean cleanupAlways) { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> cleanupAlways, MEM_PROV); // WHEN cache.offer(0, "1", value("1", 101)); cache.offer(0, "2", value("2", 99)); cache.offer(0, "2", value("2", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), Arrays.asList("2"), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 1); } @Test public void countsDoNotGetLost() { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> true, MEM_PROV); // cleanup each time, internal cleanup should NOT remove counts when adding "3". // WHEN cache.offer(0, "1", value("1", 99)); cache.offer(0, "1", value("1", 99)); cache.offer(0, "2", value("2", 99)); cache.offer(0, "2", value("2", 99)); cache.offer(0, "3", value("3", 99)); cache.offer(0, "3", value("3", 99)); cache.offer(0, "3", value("3", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), Arrays.asList("3"), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "3"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 1); } @Test public void flaggedNotEvicted() throws InterruptedException { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> true, MEM_PROV); // cleanup each time, internal cleanup should NOT remove counts when adding "3". // WHEN cache.offer(0, "1", value("1", 99)); cache.flagAndGet(0, "1", System.nanoTime() + 5 * 1_000_000_000L); // 5s cache.offer(0, "2", value("2", 99)); cache.offer(0, "2", value("2", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), new HashSet<>(Arrays.asList("1", "2")), "Expected to get correct cache entrie(s)"); Assert.assertNotNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 2); Thread.sleep(6000); // sleep 6s // WHEN // execute "offer" so the cache executes un-flagging cache.offer(0, "3", value("3", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), new HashSet<>(Arrays.asList("2")), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 1); } @Test public void offerFlaggedNotEvicted() throws InterruptedException { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(100, () -> true, MEM_PROV); // cleanup each time, internal cleanup should NOT remove counts when adding "3". // WHEN cache.offerAndFlag(0, "1", value("1", 99), System.nanoTime() + 5 * 1_000_000_000L); // 5s cache.offer(0, "2", value("2", 99)); cache.offer(0, "2", value("2", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), new HashSet<>(Arrays.asList("1", "2")), "Expected to get correct cache entrie(s)"); Assert.assertNotNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 2); Thread.sleep(6000); // sleep 6s // WHEN // execute "offer" so the cache executes un-flagging cache.offer(0, "3", value("3", 99)); // THEN Assert.assertEquals(getNames(cache.getAll(0)), new HashSet<>(Arrays.asList("2")), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 1); } @Test public void notFlaggedNotEvictedUnderCap() throws InterruptedException { // GIVEN CountingCache<Integer, String, CachedValue> cache = new CountingCache<>(200, () -> true, MEM_PROV); // cleanup each time, internal cleanup should NOT remove counts when adding "3". // WHEN cache.offerAndFlag(0, "1", value("1", 99), System.nanoTime()); // remove flag right away right away. cache.offer(0, "2", value("2", 99)); cache.offer(0, "2", value("2", 99)); cache.offer(0, "2", value("2", 99)); cache.consolidate(); // THEN // "1" should still be available, since we have enough memory available currently. Assert.assertEquals(getNames(cache.getAll(0)), new HashSet<>(Arrays.asList("1", "2")), "Expected to get correct cache entrie(s)"); Assert.assertNotNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 2); // WHEN // now there's a new entry offered cache.offer(0, "3", value("3", 99)); cache.offer(0, "3", value("3", 99)); Assert.assertEquals(getNames(cache.getAll(0)), new HashSet<>(Arrays.asList("3", "2")), "Expected to get correct cache entrie(s)"); Assert.assertNull(cache.get(0, "1"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "2"), "Expected to get cached result"); Assert.assertNotNull(cache.get(0, "3"), "Expected to get cached result"); Assert.assertEquals(cache.size(), 2); } private CachedValue value(String name, long memorySize) { CachedValue res = new CachedValue(); res.name = name; res.memorySize = memorySize; return res; } private Set<String> getNames(Collection<CachedValue> shards) { return shards.stream().map(shard -> shard.name).collect(Collectors.toSet()); } private static class CachedValue { String name; long memorySize; } }