/* * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.util.concurrent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_CALLABLE; import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE; import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE_WITH_RESULT; import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import org.junit.After; import org.junit.Test; import org.opendaylight.yangtools.util.concurrent.CommonTestUtils.Invoker; /** * Unit tests for AsyncNotifyingListeningExecutorService. * * @author Thomas Pantelis */ public class AsyncNotifyingListeningExecutorServiceTest { private ExecutorService listenerExecutor; private AsyncNotifyingListeningExecutorService testExecutor; @After public void tearDown() { if (listenerExecutor != null ) { listenerExecutor.shutdownNow(); } if (testExecutor != null ) { testExecutor.shutdownNow(); } } @Test public void testListenerCallbackWithExecutor() throws InterruptedException { String listenerThreadPrefix = "ListenerThread"; listenerExecutor = Executors.newFixedThreadPool( 3, new ThreadFactoryBuilder().setNameFormat( listenerThreadPrefix + "-%d" ).build() ); testExecutor = new AsyncNotifyingListeningExecutorService( Executors.newSingleThreadExecutor( new ThreadFactoryBuilder().setNameFormat( "SingleThread" ).build() ), listenerExecutor ); testListenerCallback( testExecutor, SUBMIT_CALLABLE, listenerThreadPrefix ); testListenerCallback( testExecutor, SUBMIT_RUNNABLE, listenerThreadPrefix ); testListenerCallback( testExecutor, SUBMIT_RUNNABLE_WITH_RESULT, listenerThreadPrefix ); } @Test public void testListenerCallbackWithNoExecutor() throws InterruptedException { String listenerThreadPrefix = "SingleThread"; testExecutor = new AsyncNotifyingListeningExecutorService( Executors.newSingleThreadExecutor( new ThreadFactoryBuilder().setNameFormat( listenerThreadPrefix ).build() ), null ); testListenerCallback( testExecutor, SUBMIT_CALLABLE, listenerThreadPrefix ); testListenerCallback( testExecutor, SUBMIT_RUNNABLE, listenerThreadPrefix ); testListenerCallback( testExecutor, SUBMIT_RUNNABLE_WITH_RESULT, listenerThreadPrefix ); } static void testListenerCallback( AsyncNotifyingListeningExecutorService executor, Invoker invoker, final String expListenerThreadPrefix ) throws InterruptedException { AtomicReference<AssertionError> assertError = new AtomicReference<>(); CountDownLatch futureNotifiedLatch = new CountDownLatch( 1 ); CountDownLatch blockTaskLatch = new CountDownLatch( 1 ); // The blockTaskLatch is used to block the task from completing until we've added // our listener to the Future. Otherwise, if the task completes quickly and the Future is // set to done before we've added our listener, the call to ListenableFuture#addListener // will immediately notify synchronously on this thread as Futures#addCallback defaults to // a same thread executor. This would erroneously fail the test. ListenableFuture<?> future = invoker.invokeExecutor( executor, blockTaskLatch ); addCallback( future, futureNotifiedLatch, expListenerThreadPrefix, assertError ); // Now that we've added our listener, signal the latch to let the task complete. blockTaskLatch.countDown(); assertTrue( "ListenableFuture callback was not notified of onSuccess", futureNotifiedLatch.await( 5, TimeUnit.SECONDS ) ); if (assertError.get() != null ) { throw assertError.get(); } // Add another listener - since the Future is already complete, we expect the listener to be // notified inline on this thread when it's added. futureNotifiedLatch = new CountDownLatch( 1 ); addCallback( future, futureNotifiedLatch, Thread.currentThread().getName(), assertError ); assertTrue( "ListenableFuture callback was not notified of onSuccess", futureNotifiedLatch.await( 5, TimeUnit.SECONDS ) ); if (assertError.get() != null ) { throw assertError.get(); } } static void addCallback( ListenableFuture<?> future, final CountDownLatch futureNotifiedLatch, final String expListenerThreadPrefix, final AtomicReference<AssertionError> assertError ) { Futures.addCallback( future, new FutureCallback<Object>() { @Override public void onSuccess( Object result ) { try { String theadName = Thread.currentThread().getName(); assertTrue( "ListenableFuture callback was not notified on the listener executor." + " Expected thread name prefix \"" + expListenerThreadPrefix + "\". Actual thread name \"" + theadName + "\"", theadName.startsWith( expListenerThreadPrefix ) ); } catch( AssertionError e ) { assertError.set( e ); } finally { futureNotifiedLatch.countDown(); } } @Override public void onFailure( @Nonnull Throwable t ) { // Shouldn't happen t.printStackTrace(); } } ); } @Test public void testDelegatedMethods() throws InterruptedException { Runnable task = () -> { }; List<Runnable> taskList = Lists.newArrayList(); ExecutorService mockDelegate = mock( ExecutorService.class ); doNothing().when( mockDelegate ).execute( task ); doNothing().when( mockDelegate ).shutdown(); doReturn( taskList ).when( mockDelegate ).shutdownNow(); doReturn( true ).when( mockDelegate ).awaitTermination( 3, TimeUnit.SECONDS ); doReturn( true ).when( mockDelegate ).isShutdown(); doReturn( true ).when( mockDelegate ).isTerminated(); AsyncNotifyingListeningExecutorService executor = new AsyncNotifyingListeningExecutorService( mockDelegate, null ); executor.execute( task ); executor.shutdown(); assertEquals( "awaitTermination", true, executor.awaitTermination( 3, TimeUnit.SECONDS ) ); assertSame( "shutdownNow", taskList, executor.shutdownNow() ); assertEquals( "isShutdown", true, executor.isShutdown() ); assertEquals( "isTerminated", true, executor.isTerminated() ); verify( mockDelegate ).execute( task ); verify( mockDelegate ).shutdown(); verify( mockDelegate ).awaitTermination( 3, TimeUnit.SECONDS ); verify( mockDelegate ).shutdownNow(); verify( mockDelegate ).isShutdown(); verify( mockDelegate ).isTerminated(); } }