/* * Copyright 2016-present Facebook, Inc. * * 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.facebook.buck.util; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; public class MoreSuppliersTest { @Test public void weakMemoizeShouldMemoize() { Supplier<Object> supplier = MoreSuppliers.weakMemoize(Object::new); Object a = supplier.get(); Object b = supplier.get(); Assert.assertSame("Supplier should have cached the instance", a, b); } @Test public void weakMemoizeShouldRunDelegateOnlyOnceOnConcurrentAccess() throws Exception { final int numFetchers = 10; final Semaphore semaphore = new Semaphore(0); class TestDelegate implements Supplier<Object> { private int timesCalled = 0; public int getTimesCalled() { return timesCalled; } @Override public Object get() { try { // Wait for all the fetch threads to be ready. semaphore.acquire(numFetchers); Thread.sleep(50); // Give other threads a chance to catch up. timesCalled++; return new Object(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { semaphore.release(numFetchers); } } } TestDelegate delegate = new TestDelegate(); final Supplier<Object> supplier = MoreSuppliers.weakMemoize(delegate); ExecutorService threadPool = Executors.newFixedThreadPool(numFetchers); try { ListeningExecutorService executor = MoreExecutors.listeningDecorator(threadPool); class Fetcher implements Callable<Object> { @Override public Object call() { // Signal that this particular fetcher is ready. semaphore.release(); return supplier.get(); } } ImmutableList.Builder<Callable<Object>> fetcherBuilder = ImmutableList.builder(); for (int i = 0; i < numFetchers; i++) { fetcherBuilder.add(new Fetcher()); } @SuppressWarnings("unchecked") List<ListenableFuture<Object>> futures = (List<ListenableFuture<Object>>) (List<?>) executor.invokeAll(fetcherBuilder.build()); // Wait for all fetchers to finish. List<Object> results = Futures.allAsList(futures).get(); Assert.assertEquals("should only have been called once", 1, delegate.getTimesCalled()); Assert.assertThat( "all result items are the same", ImmutableSet.copyOf(results), Matchers.hasSize(1)); Preconditions.checkState( threadPool.shutdownNow().isEmpty(), "All jobs should have completed"); Preconditions.checkState( threadPool.awaitTermination(10, TimeUnit.SECONDS), "Thread pool should terminate in a reasonable amount of time"); } finally { // In case exceptions were thrown, attempt to shut down the thread pool. threadPool.shutdownNow(); threadPool.awaitTermination(10, TimeUnit.SECONDS); } } @Test public void weakMemoizeShouldNotMemoizeSupplierThatIsAlreadyWeakMemoized() { Supplier<Object> supplier = MoreSuppliers.weakMemoize(Object::new); Supplier<Object> twiceMemoized = MoreSuppliers.weakMemoize(supplier); Assert.assertSame("should have just returned the same instance", supplier, twiceMemoized); } }