/* * Copyright 2014 serso aka se.solovyev * * 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Contact details * * Email: se.solovyev@gmail.com * Site: http://se.solovyev.org */ package org.solovyev.android.checkout; import org.junit.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import javax.annotation.Nullable; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.solovyev.android.checkout.Billing.DAY; public class ConcurrentCacheTest extends CacheTestBase { @Test public void testShouldPutAllEntries() throws Exception { final Cache mockCache = mock(Cache.class); final ConcurrentCache cache = new ConcurrentCache(mockCache); final Cache.Entry entry = newEntry(); cache.put(newKey(), entry); cache.put(newKey(), entry); cache.put(newKey(), entry); verify(mockCache, times(3)).put(any(Cache.Key.class), eq(entry)); } @Test public void testShouldReturnNotExpiredValue() throws Exception { final ConcurrentCache cache = new ConcurrentCache(new MapCache()); final Cache.Entry expected = newEntry(); final Cache.Key key = newKey(); cache.put(key, expected); final Cache.Entry actual = cache.get(key); assertSame(expected, actual); } @Test public void testShouldReturnNullIfValueExpired() throws Exception { final ConcurrentCache cache = new ConcurrentCache(new MapCache()); final Cache.Entry entry = newEntry(-DAY); final Cache.Key key = newKey(); cache.put(key, entry); final Cache.Entry actual = cache.get(key); assertNull(actual); } @Test public void testShouldPutOnlyIfNotExists() throws Exception { final ConcurrentCache cache = new ConcurrentCache(new MapCache()); final Cache.Entry entry = newEntry(); final Cache.Entry newEntry = newEntry(); final Cache.Key key = newKey(); cache.put(key, entry); cache.putIfNotExist(key, newEntry); assertSame(entry, cache.get(key)); } @Test public void testShouldCallInit() throws Exception { final Cache mockCache = mock(Cache.class); final ConcurrentCache cache = new ConcurrentCache(mockCache); cache.init(); verify(mockCache, times(1)).init(); } @Test public void testShouldRemoveValue() throws Exception { final ConcurrentCache cache = new ConcurrentCache(new MapCache()); final Cache.Key key = newKey(); final Cache.Entry entry = newEntry(); cache.put(key, entry); assertSame(entry, cache.get(key)); cache.remove(key); assertNull(cache.get(key)); } @Test public void testShouldRemoveAllEntriesOfType() throws Exception { final ConcurrentCache cache = new ConcurrentCache(new MapCache()); final Cache.Key k1 = newKey(); cache.put(k1, newEntry()); final Cache.Key k11 = new Cache.Key(k1.type, k1.key + "test"); cache.put(k11, newEntry()); final Cache.Key k2 = new Cache.Key(k1.type + 1, "test"); cache.put(k2, newEntry()); final Cache.Key k3 = new Cache.Key(k1.type + 2, "test"); cache.put(k3, newEntry()); assertNotNull(cache.get(k1)); assertNotNull(cache.get(k11)); assertNotNull(cache.get(k2)); assertNotNull(cache.get(k3)); cache.removeAll(k1.type); assertNull(cache.get(k1)); assertNull(cache.get(k11)); assertNotNull(cache.get(k2)); assertNotNull(cache.get(k3)); } @Test public void testShouldRemoveAllEntries() throws Exception { final ConcurrentCache cache = new ConcurrentCache(new MapCache()); final Cache.Key k1 = newKey(); cache.put(k1, newEntry()); final Cache.Key k2 = newKey(); cache.put(k2, newEntry()); final Cache.Key k3 = newKey(); cache.put(k3, newEntry()); cache.clear(); assertNull(cache.get(k1)); assertNull(cache.get(k2)); assertNull(cache.get(k3)); } @Test public void testShouldBlockOperations() throws Exception { final OneThreadCache c = new OneThreadCache(); final ConcurrentCache cache = new ConcurrentCache(c); final Executor executor = Executors.newFixedThreadPool(20); final Random random = new Random(System.currentTimeMillis()); for (int i = 0; i < 100; i++) { executor.execute(new Runnable() { @Override public void run() { switch (random.nextInt(6)) { case 0: cache.get(newKey()); break; case 1: cache.put(newKey(), newEntry()); break; case 2: cache.init(); break; case 3: cache.remove(newKey()); break; case 4: cache.removeAll(newKey().type); break; case 5: cache.clear(); break; } } }); } for (AssertionError exception : c.mException) { throw exception; } } private static final class OneThreadCache implements Cache { @Nonnull private final AtomicBoolean mLock = new AtomicBoolean(); @Nonnull private final List<AssertionError> mException = Collections.synchronizedList(new ArrayList<AssertionError>()); @Nullable @Override public Entry get(@Nonnull Key key) { doOnThread(); return null; } private void doOnThread() { if (mLock.getAndSet(true)) { mException.add(new AssertionError()); } try { Thread.sleep(10); } catch (InterruptedException e) { mException.add(new AssertionError()); } mLock.set(false); } @Override public void put(@Nonnull Key key, @Nonnull Entry entry) { doOnThread(); } @Override public void init() { doOnThread(); } @Override public void remove(@Nonnull Key key) { doOnThread(); } @Override public void removeAll(int type) { doOnThread(); } @Override public void clear() { doOnThread(); } } }