d0f960fc32df1d770fd9b119cf868e8c3e4d4396
[policy/pap.git] / main / src / test / java / org / onap / policy / pap / main / comm / TimerManagerTest.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP PAP
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.pap.main.comm;
22
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28
29 import java.util.concurrent.LinkedBlockingQueue;
30 import java.util.concurrent.Semaphore;
31 import java.util.concurrent.TimeUnit;
32 import java.util.function.Consumer;
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.onap.policy.pap.main.comm.TimerManager.Timer;
37
38 public class TimerManagerTest extends Threaded {
39     private static final String EXPECTED_EXCEPTION = "expected exception";
40     private static final String MGR_NAME = "my-manager";
41     private static final String NAME1 = "timer-A";
42     private static final String NAME2 = "timer-B";
43     private static final String NAME3 = "timer-C";
44
45     private static final long MGR_TIMEOUT_MS = 10000;
46
47     private MyManager mgr;
48
49     /*
50      * This is a field rather than a local variable to prevent checkstyle from complaining
51      * about the distance between its assignment and its use.
52      */
53     private long tcur;
54
55     /**
56      * Sets up.
57      *
58      * @throws Exception if an error occurs
59      */
60     @Before
61     public void setUp() throws Exception {
62         super.setUp();
63
64         mgr = new MyManager(MGR_NAME, MGR_TIMEOUT_MS);
65     }
66
67     @After
68     public void tearDown() throws Exception {
69         super.tearDown();
70     }
71
72     @Override
73     protected void stopThread() throws Exception {
74         if (mgr != null) {
75             mgr.stop();
76             mgr.allowSleep(10);
77         }
78     }
79
80     @Test
81     public void testTimerManager_testStop() throws Exception {
82         startThread(mgr);
83
84         mgr.stop();
85         assertTrue(waitStop());
86
87         // ensure we can call "stop" a second time
88         mgr.stop();
89     }
90
91     @Test
92     public void testRegister() throws Exception {
93         mgr.register(NAME2, mgr::addToQueue);
94         mgr.registerNewTime(NAME1, mgr::addToQueue);
95
96         // goes to the end of the queue
97         mgr.registerNewTime(NAME2, mgr::addToQueue);
98
99         startThread(mgr);
100
101         mgr.allowSleep(2);
102
103         assertEquals(NAME1, mgr.awaitTimer());
104         assertEquals(NAME2, mgr.awaitTimer());
105     }
106
107     @Test
108     public void testRun_Ex() throws Exception {
109         startThread(mgr);
110         mgr.register(NAME1, mgr::addToQueue);
111
112         mgr.awaitSleep();
113
114         // background thread is "sleeping" - now we can interrupt it
115         interruptThread();
116
117         assertTrue(waitStop());
118     }
119
120     @Test
121     public void testProcessTimers() throws Exception {
122         startThread(mgr);
123         mgr.register(NAME1, mgr::addToQueue);
124         mgr.awaitSleep();
125         mgr.allowSleep(1);
126
127         mgr.registerNewTime(NAME2, mgr::addToQueue);
128         mgr.awaitSleep();
129
130         // tell it to stop before returning from "sleep"
131         mgr.stop();
132         mgr.allowSleep(1);
133
134         assertTrue(waitStop());
135
136         assertEquals(NAME1, mgr.pollTimer());
137         assertNull(mgr.pollTimer());
138     }
139
140     @Test
141     public void testGetNextTimer() throws Exception {
142         startThread(mgr);
143         mgr.register(NAME1, mgr::addToQueue);
144         mgr.awaitSleep();
145         mgr.allowSleep(1);
146
147         mgr.registerNewTime(NAME2, mgr::addToQueue);
148         mgr.awaitSleep();
149     }
150
151     @Test
152     public void testProcessTimer_StopWhileWaiting() throws Exception {
153         startThread(mgr);
154         mgr.register(NAME1, mgr::addToQueue);
155         mgr.awaitSleep();
156         mgr.allowSleep(1);
157
158         mgr.registerNewTime(NAME2, mgr::addToQueue);
159         mgr.awaitSleep();
160
161         mgr.stop();
162         mgr.allowSleep(1);
163
164         assertTrue(waitStop());
165
166         // should have stopped after processing the first timer
167         assertEquals(NAME1, mgr.pollTimer());
168         assertNull(mgr.pollTimer());
169     }
170
171     @Test
172     public void testProcessTimer_CancelWhileWaiting() throws Exception {
173         startThread(mgr);
174         Timer timer = mgr.register(NAME1, mgr::addToQueue);
175         mgr.awaitSleep();
176
177         timer.cancel();
178         mgr.allowSleep(1);
179
180         mgr.registerNewTime(NAME2, mgr::addToQueue);
181         mgr.awaitSleep();
182         mgr.allowSleep(1);
183
184         mgr.registerNewTime(NAME1, mgr::addToQueue);
185         mgr.awaitSleep();
186
187         // should have fired timer 2, but not timer 1
188         assertEquals(NAME2, mgr.pollTimer());
189         assertNull(mgr.pollTimer());
190     }
191
192     @Test
193     public void testProcessTimer_TimerEx() throws Exception {
194
195         mgr.register(NAME1, name -> {
196             throw new RuntimeException(EXPECTED_EXCEPTION);
197         });
198
199         mgr.register(NAME2, mgr::addToQueue);
200
201         // same times, so only need one sleep
202         startThread(mgr);
203         mgr.awaitSleep();
204         mgr.allowSleep(1);
205
206         mgr.registerNewTime(NAME3, mgr::addToQueue);
207         mgr.awaitSleep();
208
209         // timer 1 fired but threw an exception, so only timer 2 should be in the queue
210         assertEquals(NAME2, mgr.pollTimer());
211     }
212
213     @Test
214     public void testTimerAwait() throws Exception {
215         startThread(mgr);
216
217         // same times - should only sleep once
218         mgr.register(NAME1, mgr::addToQueue);
219         mgr.register(NAME2, mgr::addToQueue);
220         mgr.awaitSleep();
221
222         tcur = mgr.currentTimeMillis();
223
224         mgr.allowSleep(1);
225
226         // next one will have a new timeout, so expect to sleep again
227         mgr.registerNewTime(NAME3, mgr::addToQueue);
228         mgr.awaitSleep();
229
230         long tcur2 = mgr.currentTimeMillis();
231         assertTrue(tcur2 >= tcur + MGR_TIMEOUT_MS);
232
233         assertEquals(NAME1, mgr.pollTimer());
234         assertEquals(NAME2, mgr.pollTimer());
235         assertNull(mgr.pollTimer());
236     }
237
238     @Test
239     public void testTimerCancel_WhileWaiting() throws Exception {
240         startThread(mgr);
241
242         Timer timer = mgr.register(NAME1, mgr::addToQueue);
243         mgr.awaitSleep();
244
245         // cancel while sleeping
246         timer.cancel();
247
248         mgr.registerNewTime(NAME2, mgr::addToQueue);
249
250         // allow it to sleep through both timers
251         mgr.allowSleep(2);
252
253         // only timer 2 should have fired
254         assertEquals(NAME2, mgr.awaitTimer());
255     }
256
257     @Test
258     public void testTimerCancel_ViaReplace() throws Exception {
259         startThread(mgr);
260
261         mgr.register(NAME1, name -> mgr.addToQueue("hello"));
262         mgr.awaitSleep();
263
264         // replace the timer while the background thread is sleeping
265         mgr.registerNewTime(NAME1, name -> mgr.addToQueue("world"));
266
267         // allow it to sleep through both timers
268         mgr.allowSleep(2);
269
270         // only timer 2 should have fired
271         assertEquals("world", mgr.awaitTimer());
272     }
273
274     @Test
275     public void testTimerToString() {
276         Timer timer = mgr.register(NAME1, mgr::addToQueue);
277         assertNotNull(timer.toString());
278         assertTrue(timer.toString().contains(NAME1));
279     }
280
281     @Test
282     public void testCurrentTimeMillis() {
283         long tbeg = System.currentTimeMillis();
284         long tcur = new TimerManager(MGR_NAME, MGR_TIMEOUT_MS).currentTimeMillis();
285         long tend = System.currentTimeMillis();
286
287         assertTrue(tcur >= tbeg);
288         assertTrue(tend >= tcur);
289     }
290
291     @Test
292     public void testSleep() throws Exception {
293         long tbeg = System.currentTimeMillis();
294         new TimerManager(MGR_NAME, MGR_TIMEOUT_MS).sleep(10);
295         long tend = System.currentTimeMillis();
296
297         assertTrue(tend >= tbeg + 10);
298     }
299
300
301     /**
302      * Timer Manager whose notions of time are controlled here. It also overrides the
303      * {@link #sleep(long)} method so that the test thread can control when the background
304      * timer thread finishes sleeping.
305      */
306     private static class MyManager extends TimerManager {
307         private final Object lockit = new Object();
308         private long curTime = 1000;
309         private int offset = 0;
310         private final Semaphore sleepEntered = new Semaphore(0);
311         private final Semaphore sleepsAllowed = new Semaphore(0);
312         private final LinkedBlockingQueue<String> results = new LinkedBlockingQueue<>();
313
314         public MyManager(String name, long waitTimeMs) {
315             super(name, waitTimeMs);
316         }
317
318         /**
319          * Registers a timer with a new starting time. Because the manager uses the
320          * current time when determining the expiration time, we have to temporarily
321          * fiddle with {@link #curTime}, but we leave it unchanged when we're done.
322          * Increases the {@link #offset} each time it's invoked.
323          *
324          * @return the new timer
325          */
326         public Timer registerNewTime(String timerName, Consumer<String> action) {
327             synchronized (lockit) {
328                 offset++;
329
330                 curTime += offset;
331                 Timer timer = super.register(timerName, action);
332                 curTime -= offset;
333
334                 return timer;
335             }
336         }
337
338         /**
339          * Allows the manager to "sleep" several times.
340          *
341          * @param ntimes the number of times the manager should sleep
342          */
343         public void allowSleep(int ntimes) {
344             sleepsAllowed.release(ntimes);
345         }
346
347         /**
348          * Waits for the manager to "sleep".
349          *
350          * @throws InterruptedException if the thread is interrupted while waiting for the
351          *         background thread to sleep
352          */
353         public void awaitSleep() throws InterruptedException {
354             if (!sleepEntered.tryAcquire(MAX_WAIT_MS, TimeUnit.MILLISECONDS)) {
355                 fail("background thread failed to sleep");
356             }
357         }
358
359         @Override
360         protected long currentTimeMillis() {
361             synchronized (lockit) {
362                 return curTime;
363             }
364         }
365
366         @Override
367         protected void sleep(long timeMs) throws InterruptedException {
368             sleepEntered.release();
369             sleepsAllowed.acquire();
370
371             synchronized (lockit) {
372                 curTime += timeMs;
373             }
374         }
375
376         /**
377          * Waits for a timer to fire.
378          *
379          * @return the message the timer added to {@link #results}
380          * @throws InterruptedException if this thread is interrupted while waiting
381          */
382         private String awaitTimer() throws InterruptedException {
383             return results.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS);
384         }
385
386         /**
387          * Adds a name to the queue.
388          *
389          * @param name the name to add
390          */
391         private void addToQueue(String name) {
392             results.add(name);
393         }
394
395         /**
396          * Polls to see if a timer has fired.
397          *
398          * @return the message the timer added to {@link #results}, or {@code null} if no
399          *         timer has fired yet
400          */
401         private String pollTimer() {
402             return results.poll();
403         }
404     }
405 }