Migrate common/util to JUnit5
[yangtools.git] / common / util / src / test / java / org / opendaylight / yangtools / util / concurrent / DeadlockDetectingListeningExecutorServiceTest.java
1 /*
2  * Copyright (c) 2014 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.util.concurrent;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertNotNull;
12 import static org.junit.jupiter.api.Assertions.assertTrue;
13 import static org.opendaylight.yangtools.util.concurrent.AsyncNotifyingListeningExecutorServiceTest.testListenerCallback;
14 import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_CALLABLE;
15 import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE;
16 import static org.opendaylight.yangtools.util.concurrent.CommonTestUtils.SUBMIT_RUNNABLE_WITH_RESULT;
17
18 import com.google.common.util.concurrent.FutureCallback;
19 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListeningExecutorService;
21 import com.google.common.util.concurrent.MoreExecutors;
22 import com.google.common.util.concurrent.ThreadFactoryBuilder;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.Executor;
26 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.atomic.AtomicReference;
30 import java.util.function.Supplier;
31 import org.junit.jupiter.api.AfterEach;
32 import org.junit.jupiter.api.Test;
33 import org.opendaylight.yangtools.util.concurrent.CommonTestUtils.Invoker;
34
35 /**
36  * Unit tests for DeadlockDetectingListeningExecutorService.
37  *
38  * @author Thomas Pantelis
39  */
40 class DeadlockDetectingListeningExecutorServiceTest {
41
42     interface InitialInvoker {
43         void invokeExecutor(ListeningExecutorService executor, Runnable task);
44     }
45
46     static final InitialInvoker SUBMIT = ListeningExecutorService::submit;
47
48     static final InitialInvoker EXECUTE = Executor::execute;
49
50     public static class TestDeadlockException extends Exception {
51         @java.io.Serial
52         private static final long serialVersionUID = 1L;
53     }
54
55     private static final Supplier<Exception> DEADLOCK_EXECUTOR_SUPPLIER = TestDeadlockException::new;
56
57     DeadlockDetectingListeningExecutorService executor;
58
59     @AfterEach
60     void tearDown() {
61         if (executor != null) {
62             executor.shutdownNow();
63         }
64     }
65
66     DeadlockDetectingListeningExecutorService newExecutor() {
67         return new DeadlockDetectingListeningExecutorService(Executors.newSingleThreadExecutor(),
68                 DEADLOCK_EXECUTOR_SUPPLIER);
69     }
70
71     @Test
72     void testBlockingSubmitOffExecutor() throws Exception {
73
74         executor = newExecutor();
75
76         // Test submit with Callable.
77
78         var future = executor.submit(() -> "foo");
79
80         assertEquals("foo", future.get(5, TimeUnit.SECONDS), "Future result");
81
82         // Test submit with Runnable.
83
84         executor.submit(() -> { }).get();
85
86         // Test submit with Runnable and value.
87
88         future = executor.submit(() -> { }, "foo");
89
90         assertEquals("foo", future.get(5, TimeUnit.SECONDS), "Future result");
91     }
92
93     @Test
94     @SuppressWarnings("checkstyle:illegalThrows")
95     void testNonBlockingSubmitOnExecutorThread() throws Throwable {
96
97         executor = newExecutor();
98
99         testNonBlockingSubmitOnExecutorThread(SUBMIT, SUBMIT_CALLABLE);
100         testNonBlockingSubmitOnExecutorThread(SUBMIT, SUBMIT_RUNNABLE);
101         testNonBlockingSubmitOnExecutorThread(SUBMIT, SUBMIT_RUNNABLE_WITH_RESULT);
102
103         testNonBlockingSubmitOnExecutorThread(EXECUTE, SUBMIT_CALLABLE);
104     }
105
106     @SuppressWarnings("checkstyle:illegalThrows")
107     void testNonBlockingSubmitOnExecutorThread(final InitialInvoker initialInvoker, final Invoker invoker)
108             throws Throwable {
109
110         final var caughtEx = new AtomicReference<Throwable>();
111         final var futureCompletedLatch = new CountDownLatch(1);
112
113         final var task = (Runnable) () -> Futures.addCallback(invoker.invokeExecutor(executor, null),
114                 new FutureCallback<Object>() {
115                     @Override
116                     public void onSuccess(final Object result) {
117                         futureCompletedLatch.countDown();
118                     }
119
120                     @Override
121                     @SuppressWarnings("checkstyle:parameterName")
122                     public void onFailure(final Throwable t) {
123                         caughtEx.set(t);
124                         futureCompletedLatch.countDown();
125                     }
126                 }, MoreExecutors.directExecutor());
127
128         initialInvoker.invokeExecutor(executor, task);
129
130         assertTrue(futureCompletedLatch.await(5, TimeUnit.SECONDS),
131                 "Task did not complete - executor likely deadlocked");
132
133         if (caughtEx.get() != null) {
134             throw caughtEx.get();
135         }
136     }
137
138     @Test
139     void testBlockingSubmitOnExecutorThread() throws InterruptedException {
140
141         executor = newExecutor();
142
143         testBlockingSubmitOnExecutorThread(SUBMIT, SUBMIT_CALLABLE);
144         testBlockingSubmitOnExecutorThread(SUBMIT, SUBMIT_RUNNABLE);
145         testBlockingSubmitOnExecutorThread(SUBMIT, SUBMIT_RUNNABLE_WITH_RESULT);
146
147         testBlockingSubmitOnExecutorThread(EXECUTE, SUBMIT_CALLABLE);
148     }
149
150     @SuppressWarnings("checkstyle:illegalCatch")
151     void testBlockingSubmitOnExecutorThread(final InitialInvoker initialInvoker, final Invoker invoker)
152             throws InterruptedException {
153
154         final var caughtEx = new AtomicReference<Throwable>();
155         final var latch = new CountDownLatch(1);
156
157         final var task = (Runnable) () -> {
158
159             try {
160                 invoker.invokeExecutor(executor, null).get();
161             } catch (ExecutionException e) {
162                 caughtEx.set(e.getCause());
163             } catch (Throwable e) {
164                 caughtEx.set(e);
165             } finally {
166                 latch.countDown();
167             }
168         };
169
170         initialInvoker.invokeExecutor(executor, task);
171
172         assertTrue(latch.await(5, TimeUnit.SECONDS), "Task did not complete - executor likely deadlocked");
173         assertNotNull(caughtEx.get(), "Expected exception thrown");
174         assertEquals(TestDeadlockException.class, caughtEx.get().getClass(), "Caught exception type");
175     }
176
177     @Test
178     void testListenableFutureCallbackWithExecutor() throws InterruptedException {
179
180         final var listenerThreadPrefix = "ListenerThread";
181         ExecutorService listenerExecutor = Executors.newFixedThreadPool(1,
182                 new ThreadFactoryBuilder().setNameFormat(listenerThreadPrefix + "-%d").build());
183
184         executor = new DeadlockDetectingListeningExecutorService(
185             Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("SingleThread").build()),
186                 DEADLOCK_EXECUTOR_SUPPLIER, listenerExecutor);
187
188         try {
189             testListenerCallback(executor, SUBMIT_CALLABLE, listenerThreadPrefix);
190             testListenerCallback(executor, SUBMIT_RUNNABLE, listenerThreadPrefix);
191             testListenerCallback(executor, SUBMIT_RUNNABLE_WITH_RESULT, listenerThreadPrefix);
192         } finally {
193             listenerExecutor.shutdownNow();
194         }
195     }
196 }