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