/* * Copyright (C) 2015 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.android.ide.common.caching; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import org.junit.Test; import java.util.concurrent.CountDownLatch; /** */ public class CreatingCacheTest { private static class FakeFactory implements CreatingCache.ValueFactory<String, String> { @Override @NonNull public String create(@NonNull String key) { return key; } } private static class DelayedFactory implements CreatingCache.ValueFactory<String, String> { @NonNull private final CountDownLatch mLatch; public DelayedFactory(@NonNull CountDownLatch latch) { mLatch = latch; } @Override @NonNull public String create(@NonNull String key) { try { mLatch.await(); } catch (InterruptedException ignored) { } return key; } } @Test public void testSingleThread() throws Exception { CreatingCache<String, String> cache = new CreatingCache<String, String>(new FakeFactory()); String value1 = cache.get("key"); assertEquals("key", value1); String value2 = cache.get("key"); assertEquals("key", value2); //noinspection StringEquality assertTrue("repetitive calls give same instance", value1 == value2); } private static class CacheRunnable implements Runnable { @NonNull private final CreatingCache<String, String> mCache; @Nullable private final CountDownLatch mLatch; private String mResult; private InterruptedException mException; CacheRunnable(@NonNull CreatingCache<String, String> cache) { this(cache, null); } /** * Creates a runnable, that will notify when it's pending on a query. * * @param cache the cache to query * @param latch the latch to countdown when the query is being processed. */ CacheRunnable( @NonNull CreatingCache<String, String> cache, @Nullable CountDownLatch latch) { mCache = cache; mLatch = latch; } @Override public void run() { if (mLatch != null) { mResult = mCache.get("foo", new CreatingCache.QueryListener() { @Override public void onQueryState(@NonNull CreatingCache.State state) { mLatch.countDown(); } }); } else { mResult = mCache.get("foo"); } } public String getResult() { return mResult; } public InterruptedException getException() { return mException; } } @Test public void testMultiThread() throws Exception { // the latch that controls whether the factory will "create" an item. CountDownLatch factoryLatch = new CountDownLatch(1); CreatingCache<String, String> cache = new CreatingCache<String, String>(new DelayedFactory(factoryLatch)); // the latch that will be released when the runnable1 is pending its query. CountDownLatch latch1 = new CountDownLatch(1); CacheRunnable runnable1 = new CacheRunnable(cache, latch1); Thread t1 = new Thread(runnable1); t1.start(); // wait on thread1 being waiting on the query, before creating thread2 latch1.await(); // the latch that will be released when the runnable1 is pending its query. CountDownLatch latch2 = new CountDownLatch(1); CacheRunnable runnable2 = new CacheRunnable(cache,latch2); Thread t2 = new Thread(runnable2); t2.start(); // wait on thread2 being waiting on the query, before releasing the factory latch2.await(); factoryLatch.countDown(); // wait on threads being done. t1.join(); t2.join(); assertEquals("foo", runnable1.getResult()); assertEquals("foo", runnable2.getResult()); //noinspection StringEquality assertTrue("repetitive calls give same instance", runnable1.getResult() == runnable2.getResult()); } @Test(expected = IllegalStateException.class) public void testClear() throws Exception { // the latch that controls whether the factory will "create" an item. // this is never released in this test since we want to try clearing the cache while an // item is pending creation. CountDownLatch factoryLatch = new CountDownLatch(1); final CreatingCache<String, String> cache = new CreatingCache<String, String>(new DelayedFactory(factoryLatch)); // the latch that will be released when the thread is pending its query. CountDownLatch latch = new CountDownLatch(1); new Thread(new CacheRunnable(cache, latch)).start(); // wait on thread to be waiting, before trying to clear the cache. latch.await(); cache.clear(); } }