Aaron Kruglikov
Committed by Gerrit Code Review

Adds manually advancing timer.

Change-Id: I83936f4fff577b0f23404560c6ecfbc0c9f18e2e
...@@ -66,24 +66,37 @@ public final class TestUtils { ...@@ -66,24 +66,37 @@ public final class TestUtils {
66 * 66 *
67 * @param subject Object where the field belongs 67 * @param subject Object where the field belongs
68 * @param fieldName name of the field to get 68 * @param fieldName name of the field to get
69 - * @return value of the field.
70 * @param <T> subject type 69 * @param <T> subject type
71 - * @param <U> field value type 70 + * @param <U> fieldO value type
71 + * @return value of the field.
72 * @throws TestUtilsException if there are reflection errors while getting 72 * @throws TestUtilsException if there are reflection errors while getting
73 * the field 73 * the field
74 */ 74 */
75 public static <T, U> U getField(T subject, String fieldName) 75 public static <T, U> U getField(T subject, String fieldName)
76 throws TestUtilsException { 76 throws TestUtilsException {
77 try { 77 try {
78 + NoSuchFieldException exception = null;
78 @SuppressWarnings("unchecked") 79 @SuppressWarnings("unchecked")
79 - Class<T> clazz = (Class<T>) subject.getClass(); 80 + Class clazz = subject.getClass();
81 + while (clazz != null) {
82 + try {
80 Field field = clazz.getDeclaredField(fieldName); 83 Field field = clazz.getDeclaredField(fieldName);
81 field.setAccessible(true); 84 field.setAccessible(true);
82 85
83 @SuppressWarnings("unchecked") 86 @SuppressWarnings("unchecked")
84 U result = (U) field.get(subject); 87 U result = (U) field.get(subject);
85 return result; 88 return result;
86 - } catch (NoSuchFieldException | SecurityException | 89 + } catch (NoSuchFieldException e) {
90 + exception = e;
91 + if (clazz == clazz.getSuperclass()) {
92 + break;
93 + }
94 + clazz = clazz.getSuperclass();
95 + }
96 + }
97 + throw new TestUtilsException("Field not found. " + fieldName, exception);
98 +
99 + } catch (SecurityException |
87 IllegalArgumentException | IllegalAccessException e) { 100 IllegalArgumentException | IllegalAccessException e) {
88 throw new TestUtilsException("getField failed", e); 101 throw new TestUtilsException("getField failed", e);
89 } 102 }
......
...@@ -15,23 +15,24 @@ ...@@ -15,23 +15,24 @@
15 */ 15 */
16 package org.onlab.util; 16 package org.onlab.util;
17 17
18 -import org.junit.Ignore;
19 import org.junit.Test; 18 import org.junit.Test;
20 19
21 import java.util.List; 20 import java.util.List;
22 -import java.util.Timer;
23 import java.util.stream.IntStream; 21 import java.util.stream.IntStream;
24 22
25 -import static org.junit.Assert.*; 23 +import static org.junit.Assert.assertEquals;
24 +import static org.junit.Assert.assertFalse;
25 +import static org.junit.Assert.assertTrue;
26 import static org.onlab.junit.TestTools.assertAfter; 26 import static org.onlab.junit.TestTools.assertAfter;
27 -import static org.onlab.junit.TestTools.delay;
28 27
29 /** 28 /**
30 * Tests the operation of the accumulator. 29 * Tests the operation of the accumulator.
31 */ 30 */
32 public class AbstractAccumulatorTest { 31 public class AbstractAccumulatorTest {
33 32
34 - private final Timer timer = new Timer(); 33 +
34 + private final ManuallyAdvancingTimer timer = new ManuallyAdvancingTimer();
35 +
35 36
36 @Test 37 @Test
37 public void basics() throws Exception { 38 public void basics() throws Exception {
...@@ -42,7 +43,6 @@ public class AbstractAccumulatorTest { ...@@ -42,7 +43,6 @@ public class AbstractAccumulatorTest {
42 assertEquals("incorrect idle ms", 70, accumulator.maxIdleMillis()); 43 assertEquals("incorrect idle ms", 70, accumulator.maxIdleMillis());
43 } 44 }
44 45
45 - @Ignore("FIXME: timing sensitive test failing randomly.")
46 @Test 46 @Test
47 public void eventTrigger() { 47 public void eventTrigger() {
48 TestAccumulator accumulator = new TestAccumulator(); 48 TestAccumulator accumulator = new TestAccumulator();
...@@ -52,43 +52,40 @@ public class AbstractAccumulatorTest { ...@@ -52,43 +52,40 @@ public class AbstractAccumulatorTest {
52 accumulator.add(new TestItem("d")); 52 accumulator.add(new TestItem("d"));
53 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 53 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
54 accumulator.add(new TestItem("e")); 54 accumulator.add(new TestItem("e"));
55 - delay(20); 55 + timer.advanceTimeMillis(20, 10);
56 assertFalse("should have fired", accumulator.batch.isEmpty()); 56 assertFalse("should have fired", accumulator.batch.isEmpty());
57 assertEquals("incorrect batch", "abcde", accumulator.batch); 57 assertEquals("incorrect batch", "abcde", accumulator.batch);
58 } 58 }
59 59
60 - @Ignore("FIXME: timing sensitive test failing randomly.")
61 @Test 60 @Test
62 public void timeTrigger() { 61 public void timeTrigger() {
63 TestAccumulator accumulator = new TestAccumulator(); 62 TestAccumulator accumulator = new TestAccumulator();
64 accumulator.add(new TestItem("a")); 63 accumulator.add(new TestItem("a"));
65 - delay(30); 64 + timer.advanceTimeMillis(30, 1);
66 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 65 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
67 accumulator.add(new TestItem("b")); 66 accumulator.add(new TestItem("b"));
68 - delay(30); 67 + timer.advanceTimeMillis(30, 1);
69 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 68 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
70 accumulator.add(new TestItem("c")); 69 accumulator.add(new TestItem("c"));
71 - delay(30); 70 + timer.advanceTimeMillis(30, 1);
72 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 71 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
73 accumulator.add(new TestItem("d")); 72 accumulator.add(new TestItem("d"));
74 - delay(60); 73 + timer.advanceTimeMillis(10, 10);
75 assertFalse("should have fired", accumulator.batch.isEmpty()); 74 assertFalse("should have fired", accumulator.batch.isEmpty());
76 assertEquals("incorrect batch", "abcd", accumulator.batch); 75 assertEquals("incorrect batch", "abcd", accumulator.batch);
77 } 76 }
78 77
79 - @Ignore("FIXME: timing sensitive test failing randomly.")
80 @Test 78 @Test
81 public void idleTrigger() { 79 public void idleTrigger() {
82 TestAccumulator accumulator = new TestAccumulator(); 80 TestAccumulator accumulator = new TestAccumulator();
83 accumulator.add(new TestItem("a")); 81 accumulator.add(new TestItem("a"));
84 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 82 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
85 accumulator.add(new TestItem("b")); 83 accumulator.add(new TestItem("b"));
86 - delay(80); 84 + timer.advanceTimeMillis(70, 10);
87 assertFalse("should have fired", accumulator.batch.isEmpty()); 85 assertFalse("should have fired", accumulator.batch.isEmpty());
88 assertEquals("incorrect batch", "ab", accumulator.batch); 86 assertEquals("incorrect batch", "ab", accumulator.batch);
89 } 87 }
90 88
91 - @Ignore("FIXME: timing sensitive test failing randomly.")
92 @Test 89 @Test
93 public void readyIdleTrigger() { 90 public void readyIdleTrigger() {
94 TestAccumulator accumulator = new TestAccumulator(); 91 TestAccumulator accumulator = new TestAccumulator();
...@@ -96,30 +93,28 @@ public class AbstractAccumulatorTest { ...@@ -96,30 +93,28 @@ public class AbstractAccumulatorTest {
96 accumulator.add(new TestItem("a")); 93 accumulator.add(new TestItem("a"));
97 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 94 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
98 accumulator.add(new TestItem("b")); 95 accumulator.add(new TestItem("b"));
99 - delay(80); 96 + timer.advanceTimeMillis(80, 1);
100 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 97 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
101 accumulator.ready = true; 98 accumulator.ready = true;
102 - delay(80); 99 + timer.advanceTimeMillis(80, 10);
103 assertFalse("should have fired", accumulator.batch.isEmpty()); 100 assertFalse("should have fired", accumulator.batch.isEmpty());
104 assertEquals("incorrect batch", "ab", accumulator.batch); 101 assertEquals("incorrect batch", "ab", accumulator.batch);
105 } 102 }
106 103
107 - @Ignore("FIXME: timing sensitive test failing randomly.")
108 @Test 104 @Test
109 public void readyLongTrigger() { 105 public void readyLongTrigger() {
110 TestAccumulator accumulator = new TestAccumulator(); 106 TestAccumulator accumulator = new TestAccumulator();
111 accumulator.ready = false; 107 accumulator.ready = false;
112 - delay(120); 108 + timer.advanceTimeMillis(120, 1);
113 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 109 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
114 accumulator.add(new TestItem("a")); 110 accumulator.add(new TestItem("a"));
115 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 111 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
116 accumulator.ready = true; 112 accumulator.ready = true;
117 - delay(80); 113 + timer.advanceTimeMillis(120, 10);
118 assertFalse("should have fired", accumulator.batch.isEmpty()); 114 assertFalse("should have fired", accumulator.batch.isEmpty());
119 assertEquals("incorrect batch", "a", accumulator.batch); 115 assertEquals("incorrect batch", "a", accumulator.batch);
120 } 116 }
121 117
122 - @Ignore("FIXME: timing sensitive test failing randomly.")
123 @Test 118 @Test
124 public void readyMaxTrigger() { 119 public void readyMaxTrigger() {
125 TestAccumulator accumulator = new TestAccumulator(); 120 TestAccumulator accumulator = new TestAccumulator();
...@@ -133,16 +128,16 @@ public class AbstractAccumulatorTest { ...@@ -133,16 +128,16 @@ public class AbstractAccumulatorTest {
133 assertTrue("should not have fired yet", accumulator.batch.isEmpty()); 128 assertTrue("should not have fired yet", accumulator.batch.isEmpty());
134 accumulator.ready = true; 129 accumulator.ready = true;
135 accumulator.add(new TestItem("g")); 130 accumulator.add(new TestItem("g"));
136 - delay(5); 131 + timer.advanceTimeMillis(10, 10);
137 assertFalse("should have fired", accumulator.batch.isEmpty()); 132 assertFalse("should have fired", accumulator.batch.isEmpty());
138 assertEquals("incorrect batch", "abcdefg", accumulator.batch); 133 assertEquals("incorrect batch", "abcdefg", accumulator.batch);
139 } 134 }
140 135
141 - @Ignore("FIXME: timing sensitive test failing randomly.")
142 @Test 136 @Test
143 public void stormTest() { 137 public void stormTest() {
144 TestAccumulator accumulator = new TestAccumulator(); 138 TestAccumulator accumulator = new TestAccumulator();
145 IntStream.range(0, 1000).forEach(i -> accumulator.add(new TestItem("#" + i))); 139 IntStream.range(0, 1000).forEach(i -> accumulator.add(new TestItem("#" + i)));
140 + timer.advanceTimeMillis(1);
146 assertAfter(100, () -> assertEquals("wrong item count", 1000, accumulator.itemCount)); 141 assertAfter(100, () -> assertEquals("wrong item count", 1000, accumulator.itemCount));
147 assertEquals("wrong batch count", 200, accumulator.batchCount); 142 assertEquals("wrong batch count", 200, accumulator.batchCount);
148 } 143 }
...@@ -180,5 +175,4 @@ public class AbstractAccumulatorTest { ...@@ -180,5 +175,4 @@ public class AbstractAccumulatorTest {
180 return ready; 175 return ready;
181 } 176 }
182 } 177 }
183 -
184 } 178 }
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onlab.util;
18 +
19 +import com.google.common.collect.Lists;
20 +import org.onlab.junit.TestUtils;
21 +import org.slf4j.Logger;
22 +
23 +import java.util.Date;
24 +import java.util.Iterator;
25 +import java.util.LinkedList;
26 +import java.util.TimerTask;
27 +import java.util.concurrent.ExecutorService;
28 +import java.util.concurrent.Executors;
29 +
30 +import static com.google.common.base.Preconditions.checkNotNull;
31 +import static org.onlab.junit.TestTools.delay;
32 +import static org.slf4j.LoggerFactory.getLogger;
33 +
34 +
35 +/**
36 + * Provides manually scheduled timer utility. All schedulable methods are subject to overflow (you can set a period of
37 + * max long). Additionally if a skip skips a period of time greater than one period for a periodic task that task will
38 + * only be executed once for that skip and scheduled it's period after the last execution.
39 + */
40 +public class ManuallyAdvancingTimer extends java.util.Timer {
41 +
42 + /* States whether or not the static values from timer task have been set ensures population will only occur once.*/
43 + private boolean staticsPopulated = false;
44 +
45 + /* Virgin value from timer task */
46 + private int virginState;
47 +
48 + /* Scheduled value from timer task */
49 + private int scheduledState;
50 +
51 + /* Executed value from timer task */
52 + private int executedState;
53 +
54 + /* Cancelled value from timer task */
55 + private int cancelledState;
56 +
57 + private final Logger logger = getLogger(getClass());
58 +
59 + /* Service for executing timer tasks */
60 + private final ExecutorService executorService = Executors.newSingleThreadExecutor();
61 +
62 + /* Internal time representation independent of system time, manually advanced */
63 + private final TimerKeeper timerKeeper = new TimerKeeper();
64 +
65 + /* Data structure for tracking tasks */
66 + private final TaskQueue queue = new TaskQueue();
67 +
68 + @Override
69 + public void schedule(TimerTask task, long delay) {
70 + if (!staticsPopulated) {
71 + populateStatics(task);
72 + }
73 + if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
74 + timerKeeper.currentTimeInMillis() - delay, 0)) {
75 + logger.error("Failed to submit task");
76 + }
77 + }
78 +
79 + @Override
80 + public void schedule(TimerTask task, Date time) {
81 + if (!staticsPopulated) {
82 + populateStatics(task);
83 + }
84 + if (!submitTask(task, time.getTime(), 0)) {
85 + logger.error("Failed to submit task");
86 + }
87 + }
88 +
89 + @Override
90 + public void schedule(TimerTask task, long delay, long period) {
91 + if (!staticsPopulated) {
92 + populateStatics(task);
93 + }
94 + if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
95 + timerKeeper.currentTimeInMillis() - delay, period)) {
96 + logger.error("Failed to submit task");
97 + }
98 + }
99 +
100 + @Override
101 + public void schedule(TimerTask task, Date firstTime, long period) {
102 + if (!staticsPopulated) {
103 + populateStatics(task);
104 + }
105 + if (!submitTask(task, firstTime.getTime(), period)) {
106 + logger.error("Failed to submit task");
107 + }
108 + }
109 +
110 + /*################################################WARNING################################################*/
111 + /* Schedule at fixed rate methods do not work exactly as in the java timer. They are clones of the periodic
112 + *scheduling methods. */
113 + @Override
114 + public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
115 + if (!staticsPopulated) {
116 + populateStatics(task);
117 + }
118 + if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
119 + timerKeeper.currentTimeInMillis() - delay, period)) {
120 + logger.error("Failed to submit task");
121 + }
122 + }
123 +
124 + @Override
125 + public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
126 + if (!staticsPopulated) {
127 + populateStatics(task);
128 + }
129 + if (!submitTask(task, firstTime.getTime(), period)) {
130 + logger.error("Failed to submit task");
131 + }
132 + }
133 +
134 + @Override
135 + public void cancel() {
136 + executorService.shutdown();
137 + queue.clear();
138 + }
139 +
140 + @Override
141 + public int purge() {
142 + return queue.removeCancelled();
143 + }
144 +
145 + /**
146 + * Returns the virtual current time in millis.
147 + *
148 + * @return long representing simulated current time.
149 + */
150 + public long currentTimeInMillis() {
151 + return timerKeeper.currentTimeInMillis();
152 + }
153 +
154 + /**
155 + * Returns the new simulated current time in millis after advancing the absolute value of millis to advance.
156 + * Triggers event execution of all events scheduled for execution at times up to and including the returned time.
157 + * Passing in the number zero has no effect.
158 + *
159 + * @param millisToAdvance the number of millis to advance.
160 + * @return a long representing the current simulated time in millis
161 + */
162 + public long advanceTimeMillis(long millisToAdvance) {
163 + return timerKeeper.advanceTimeMillis(millisToAdvance);
164 + }
165 +
166 + /**
167 + * Advances the virtual time a certain number of millis triggers execution delays a certain amount to
168 + * allow time for execution.
169 + *
170 + * @param virtualTimeAdvance the time to be advances in millis of simulated time.
171 + * @param realTimeDelay the time to delay in real time to allow for processing.
172 + */
173 + public void advanceTimeMillis(long virtualTimeAdvance, int realTimeDelay) {
174 + timerKeeper.advanceTimeMillis(virtualTimeAdvance);
175 + delay(realTimeDelay);
176 + }
177 +
178 + /**
179 + * Sets up the task and submits it to the queue.
180 + *
181 + * @param task the task to be added to the queue
182 + * @param runtime the first runtime of the task
183 + * @param period the period between runs thereafter
184 + * @return returns true if the task was successfully submitted, false otherwise
185 + */
186 + private boolean submitTask(TimerTask task, long runtime, long period) {
187 + checkNotNull(task);
188 + try {
189 + TestUtils.setField(task, "state", scheduledState);
190 + TestUtils.setField(task, "nextExecutionTime", runtime);
191 + TestUtils.setField(task, "period", period);
192 + } catch (TestUtils.TestUtilsException e) {
193 + e.printStackTrace();
194 + return false;
195 + }
196 + queue.insertOrdered(task);
197 + return true;
198 + }
199 +
200 + /**
201 + * Executes the given task (only if it is in the scheduled state) and proceeds to reschedule it or mark it as
202 + * executed. Does not remove from the queue (this must be done outside).
203 + *
204 + * @param task the timer task to be executed
205 + */
206 + private boolean executeTask(TimerTask task) {
207 + checkNotNull(task);
208 + int currentState;
209 + try {
210 + currentState = TestUtils.getField(task, "state");
211 + } catch (TestUtils.TestUtilsException e) {
212 + logger.error("Could not get state of task.");
213 + e.printStackTrace();
214 + return false;
215 + }
216 + //If cancelled or already executed stop here.
217 + if (currentState == executedState || currentState == cancelledState) {
218 + return false;
219 + } else if (currentState == virginState) {
220 + logger.error("Task was set for execution without being scheduled.");
221 + return false;
222 + } else if (currentState == scheduledState) {
223 + long period;
224 +
225 + try {
226 + period = TestUtils.getField(task, "period");
227 + } catch (TestUtils.TestUtilsException e) {
228 + logger.error("Could not read period of task.");
229 + e.printStackTrace();
230 + return false;
231 + }
232 + //Period of zero means one time execution.
233 + if (period == 0) {
234 + try {
235 + TestUtils.setField(task, "state", executedState);
236 + } catch (TestUtils.TestUtilsException e) {
237 + logger.error("Could not set executed state.");
238 + e.printStackTrace();
239 + return false;
240 + }
241 + executorService.execute(task);
242 + return true;
243 + } else {
244 + //Calculate next execution time, using absolute value of period
245 + long nextTime = (period > 0) ? (timerKeeper.currentTimeInMillis() + period) :
246 + (timerKeeper.currentTimeInMillis() - period);
247 + try {
248 + TestUtils.setField(task, "nextExecutionTime", nextTime);
249 + } catch (TestUtils.TestUtilsException e) {
250 + logger.error("Could not set next execution time.");
251 + e.printStackTrace();
252 + return false;
253 + }
254 + //Schedule next execution
255 + queue.insertOrdered(task);
256 + executorService.execute(task);
257 + return true;
258 + }
259 + }
260 + logger.error("State property of {} is in an illegal state and did not execute.", task);
261 + return false;
262 + }
263 +
264 + /**
265 + * Executes all tasks in the queue scheduled for execution up to and including the current time.
266 + *
267 + * @return the total number of tasks run, -1 if failure
268 + */
269 + private int executeEventsUpToPresent() {
270 + int totalRun = 0;
271 + if (queue.isEmpty()) {
272 + return -1;
273 + }
274 + TimerTask currTask = queue.peek();
275 + long currExecTime;
276 + try {
277 + currExecTime = TestUtils.getField(currTask, "nextExecutionTime");
278 + } catch (TestUtils.TestUtilsException e) {
279 + e.printStackTrace();
280 + throw new RuntimeException("Could not get nextExecutionTime");
281 + }
282 + while (currExecTime <= timerKeeper.currentTimeInMillis()) {
283 + if (executeTask(queue.pop())) {
284 + totalRun++;
285 + }
286 + if (queue.isEmpty()) {
287 + break;
288 + }
289 + currTask = queue.peek();
290 + try {
291 + currExecTime = TestUtils.getField(currTask, "nextExecutionTime");
292 + } catch (TestUtils.TestUtilsException e) {
293 + e.printStackTrace();
294 + throw new RuntimeException("Could not get nextExecutionTime");
295 + }
296 + }
297 + return totalRun;
298 + }
299 +
300 + /**
301 + * Populates the static fields from timer task. Should only be called once.
302 + */
303 + private void populateStatics(TimerTask task) {
304 + try {
305 + virginState = TestUtils.getField(task, "VIRGIN");
306 + scheduledState = TestUtils.getField(task, "SCHEDULED");
307 + executedState = TestUtils.getField(task, "EXECUTED");
308 + cancelledState = TestUtils.getField(task, "CANCELLED");
309 + staticsPopulated = true;
310 + } catch (TestUtils.TestUtilsException e) {
311 + e.printStackTrace();
312 + }
313 + }
314 +
315 + /**
316 + * A class used to maintain the virtual time.
317 + */
318 + private class TimerKeeper {
319 +
320 + private long currentTime = 0;
321 +
322 + /**
323 + * Returns the virtual current time in millis.
324 + *
325 + * @return long representing simulated current time.
326 + */
327 + long currentTimeInMillis() {
328 + return currentTime;
329 + }
330 +
331 + /**
332 + * Returns the new simulated current time in millis after advancing the absolute value of millis to advance.
333 + * Triggers event execution of all events scheduled for execution at times up to and including the returned
334 + * time. Passing in the number zero has no effect.
335 + *
336 + * @param millisToAdvance the number of millis to advance.
337 + * @return a long representing the current simulated time in millis
338 + */
339 + long advanceTimeMillis(long millisToAdvance) {
340 + currentTime = (millisToAdvance >= 0) ? (currentTime + millisToAdvance) : (currentTime - millisToAdvance);
341 + if (millisToAdvance != 0) {
342 + executeEventsUpToPresent();
343 + }
344 + return currentTime;
345 + }
346 + }
347 +
348 + /**
349 + * A queue backed by a linked list. Keeps elements sorted in ascending order of execution time. All calls are safe
350 + * even on empty queue's.
351 + */
352 + private class TaskQueue {
353 + private final LinkedList<TimerTask> taskList = Lists.newLinkedList();
354 +
355 + /**
356 + * Adds the task to the queue in ascending order of scheduled execution. If execution time has already passed
357 + * execute immediately.
358 + *
359 + * @param task the task to be added to the queue
360 + */
361 + void insertOrdered(TimerTask task) {
362 + //Using O(N) insertion because random access is expensive in linked lists worst case is 2N links followed
363 + // for binary insertion vs N for simple insertion.
364 + checkNotNull(task);
365 + if (!staticsPopulated) {
366 + populateStatics(task);
367 + }
368 + long insertTime;
369 + try {
370 + insertTime = TestUtils.getField(task, "nextExecutionTime");
371 + TestUtils.setField(task, "state", scheduledState);
372 + } catch (TestUtils.TestUtilsException e) {
373 + e.printStackTrace();
374 + return;
375 + }
376 + //If the task was scheduled in the past or for the current time run it immediately and do not add to the
377 + // queue, subsequent executions will be scheduled as normal
378 + if (insertTime <= timerKeeper.currentTimeInMillis()) {
379 + executeTask(task);
380 + return;
381 + }
382 +
383 + Iterator<TimerTask> iter = taskList.iterator();
384 + int positionCounter = 0;
385 + long nextTaskTime;
386 + TimerTask currentTask;
387 + while (iter.hasNext()) {
388 + currentTask = iter.next();
389 + try {
390 + nextTaskTime = TestUtils.getField(currentTask, "nextExecutionTime");
391 + } catch (TestUtils.TestUtilsException e) {
392 + e.printStackTrace();
393 + return;
394 + }
395 + if (insertTime < nextTaskTime) {
396 + taskList.add(positionCounter, task);
397 + return;
398 + }
399 + positionCounter++;
400 + }
401 + taskList.addLast(task);
402 + }
403 +
404 + /**
405 + * Returns the first item in the queue (next scheduled for execution) without removing it, returns null if the
406 + * queue is empty.
407 + *
408 + * @return the next TimerTask to run or null if the queue is empty
409 + */
410 + TimerTask peek() {
411 + if (taskList.isEmpty()) {
412 + return null;
413 + }
414 + return taskList.getFirst();
415 + }
416 +
417 + /**
418 + * Returns and removes the first item in the queue or null if it is empty.
419 + *
420 + * @return the first element of the queue or null if the queue is empty
421 + */
422 + TimerTask pop() {
423 + if (taskList.isEmpty()) {
424 + return null;
425 + }
426 + return taskList.pop();
427 + }
428 +
429 + /**
430 + * Performs a sort on the set of timer tasks, earliest task is first. Does nothing if queue is empty.
431 + */
432 + void sort() {
433 + if (taskList.isEmpty()) {
434 + return;
435 + }
436 + taskList.sort((o1, o2) -> {
437 + checkNotNull(o1);
438 + checkNotNull(o2);
439 + long executionTimeOne;
440 + long executionTimeTwo;
441 + try {
442 + executionTimeOne = TestUtils.getField(o1, "nextExecutionTime");
443 + executionTimeTwo = TestUtils.getField(o2, "nextExecutionTime");
444 + } catch (TestUtils.TestUtilsException e) {
445 + e.printStackTrace();
446 + throw new RuntimeException("Could not get next execution time.");
447 + }
448 + if (executionTimeOne == executionTimeTwo) {
449 + return 0;
450 + } else if (executionTimeOne < executionTimeTwo) {
451 + return -1;
452 + } else {
453 + return 1;
454 + }
455 + });
456 + }
457 +
458 + /**
459 + * Returns whether the queue is currently empty.
460 + *
461 + * @return true if the queue is empty, false otherwise
462 + */
463 + boolean isEmpty() {
464 + return taskList.isEmpty();
465 + }
466 +
467 + /**
468 + * Clears the underlying list of the queue.
469 + */
470 + void clear() {
471 + taskList.clear();
472 + }
473 +
474 + /**
475 + * Removes all cancelled tasks from the queue. Has no effect on behavior.
476 + *
477 + * @return returns the total number of items removed, -1 if list is empty or failure occurs.
478 + */
479 + int removeCancelled() {
480 + if (taskList.isEmpty()) {
481 + return -1;
482 + }
483 + int removedCount = 0;
484 + Iterator<TimerTask> taskIterator = taskList.iterator();
485 + TimerTask currTask;
486 + int currState;
487 + while (taskIterator.hasNext()) {
488 + currTask = taskIterator.next();
489 + try {
490 + currState = TestUtils.getField(currTask, "state");
491 + } catch (TestUtils.TestUtilsException e) {
492 + logger.error("Could not get task state.");
493 + e.printStackTrace();
494 + return -1;
495 + }
496 + if (currState == cancelledState) {
497 + removedCount++;
498 + taskIterator.remove();
499 + }
500 + }
501 + return removedCount;
502 + }
503 + }
504 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onlab.util;
18 +
19 +import com.google.common.collect.Lists;
20 +import org.junit.Before;
21 +import org.junit.Test;
22 +
23 +import java.util.ArrayList;
24 +import java.util.Date;
25 +import java.util.TimerTask;
26 +import java.util.concurrent.atomic.AtomicInteger;
27 +
28 +import static org.junit.Assert.assertEquals;
29 +import static org.junit.Assert.assertFalse;
30 +import static org.junit.Assert.assertTrue;
31 +import static org.onlab.junit.TestTools.delay;
32 +
33 +/**
34 + * Testing class for manually advancing timer.
35 + */
36 +public class ManuallyAdvancingTimerTest {
37 +
38 + private ManuallyAdvancingTimer timer;
39 +
40 + /* Generates unique id's for TestTasks */
41 + private AtomicInteger idGenerator;
42 +
43 + /* Tracks TestTasks in order of creation, tasks are automatically added at creation. */
44 + private ArrayList<TestTask> taskList;
45 +
46 + /* Total number of tasks run */
47 + private AtomicInteger tasksRunCount;
48 +
49 + // FIXME if this class fails first try increasing the real time delay to account for heavy system load.
50 + private static final int REAL_TIME_DELAY = 1;
51 +
52 + /**
53 + * Sets up the testing environment.
54 + */
55 + @Before
56 + public void setup() {
57 + timer = new ManuallyAdvancingTimer();
58 + idGenerator = new AtomicInteger(1);
59 + tasksRunCount = new AtomicInteger(0);
60 + taskList = Lists.newArrayList();
61 + }
62 +
63 + /**
64 + * Tests the one time schedule with delay.
65 + *
66 + * @throws Exception throws an exception if the test fails
67 + */
68 + @Test
69 + public void testScheduleByDelay() throws Exception {
70 + /* Test scheduling in the future as normal. */
71 + timer.schedule(new TestTask(), 10);
72 + timer.advanceTimeMillis(5);
73 + assertFalse(taskList.get(0).hasRun());
74 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
75 + assertTrue(taskList.get(0).hasRun());
76 +
77 + /* Test scheduling with negative numbers */
78 + timer.schedule(new TestTask(), -10);
79 + timer.advanceTimeMillis(5);
80 + assertFalse(taskList.get(1).hasRun());
81 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
82 + assertTrue(taskList.get(1).hasRun());
83 +
84 + /* Reset list, counter and timer for next test */
85 + taskList.clear();
86 + idGenerator.set(1);
87 + tasksRunCount.set(0);
88 +
89 + for (int i = 0; i < 50; i++) {
90 + timer.schedule(new TestTask(), i);
91 + }
92 + /* Test that a task scheduled for present is run and not placed in the queue */
93 + assertEquals("Only the first task should have run.", 1, tasksRunCount.get());
94 +
95 + for (int i = 2; i <= 50; i++) {
96 + timer.advanceTimeMillis(1, REAL_TIME_DELAY);
97 + assertEquals("One task should be executed per loop", i, tasksRunCount.get());
98 + }
99 + /* Below tests ordered insertion, this will only be done once, it is the same for all schedule methods. */
100 +
101 + tasksRunCount.set(0);
102 +
103 + for (int i = 0; i < 10; i++) {
104 + timer.schedule(new TestTask(), 500);
105 + }
106 +
107 + assertEquals("No new tasks should have been run since run count reset.", 0, tasksRunCount.get());
108 + timer.schedule(new TestTask(), 10);
109 + assertEquals("No new tasks should have been run since run count reset.", 0, tasksRunCount.get());
110 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
111 + assertEquals("One new tasks should have been run since run count reset.", 1, tasksRunCount.get());
112 + timer.advanceTimeMillis(510, REAL_TIME_DELAY);
113 + assertEquals("Eleven new tasks should have been run since run count reset.", 11, tasksRunCount.get());
114 + }
115 +
116 + /**
117 + * Tests scheduling for a particular date or time which may be in the past.
118 + *
119 + * @throws Exception throws an exception if the test fails
120 + */
121 + @Test
122 + public void testScheduleByDate() throws Exception {
123 + /* Tests basic scheduling for future times. */
124 + timer.schedule(new TestTask(), new Date(10));
125 + timer.advanceTimeMillis(5);
126 + assertFalse(taskList.get(0).hasRun());
127 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
128 + assertTrue(taskList.get(0).hasRun());
129 +
130 + /* Test scheduling with past times numbers */
131 + timer.schedule(new TestTask(), new Date(0));
132 + delay(REAL_TIME_DELAY);
133 + assertTrue(taskList.get(1).hasRun());
134 +
135 + /* Tests cancellation on non-periodic events */
136 + TestTask task = new TestTask();
137 + timer.schedule(task, new Date(timer.currentTimeInMillis() + 10));
138 + task.cancel();
139 + timer.advanceTimeMillis(12, REAL_TIME_DELAY);
140 + assertFalse(task.hasRun());
141 +
142 + }
143 +
144 + /**
145 + * Test scheduling beginning after a delay and recurring periodically.
146 + *
147 + * @throws Exception throws an exception if the test fails
148 + */
149 + @Test
150 + public void testScheduleByDelayPeriodic() throws Exception {
151 + /* Test straightforward periodic execution */
152 + timer.schedule(new TestTask(), 0, 10);
153 + delay(REAL_TIME_DELAY);
154 + assertEquals("Task should have run once when added.", 1, taskList.get(0).timesRun());
155 +
156 + /* Tests whether things that are not added to the queue are scheduled for future executions (ones which execute
157 + immediately on add). */
158 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
159 + assertEquals("Task should have run once when added.", 2, taskList.get(0).timesRun());
160 +
161 + /* Tests whether cancellation works on periodic events. */
162 + taskList.get(0).cancel();
163 +
164 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
165 + assertEquals("The task should not have run another time.", 2, taskList.get(0).timesRun());
166 +
167 + TestTask task = new TestTask();
168 + timer.schedule(task, 0, 10);
169 + timer.advanceTimeMillis(100, REAL_TIME_DELAY);
170 + assertEquals("Should have run immeditaley and subsequently once during the larger skip", task.timesRun(), 2);
171 +
172 + }
173 +
174 + /**
175 + * Test scheduling beginning at a specified date and recurring periodically.
176 + *
177 + * @throws Exception throws an exception if the test fails
178 + */
179 + @Test
180 + public void testScheduleByDatePeriodic() throws Exception {
181 + /* Test straightforward periodic execution */
182 + timer.schedule(new TestTask(), new Date(timer.currentTimeInMillis()), 10);
183 + delay(REAL_TIME_DELAY);
184 + assertEquals("Task should have run once when added.", 1, taskList.get(0).timesRun());
185 +
186 + /* Tests whether things that are not added to the queue are scheduled for future executions (ones which execute
187 + immediately on add). */
188 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
189 + assertEquals("Task should have run once when added.", 2, taskList.get(0).timesRun());
190 +
191 + /* Tests whether cancellation works on periodic events. */
192 + taskList.get(0).cancel();
193 +
194 + timer.advanceTimeMillis(10, REAL_TIME_DELAY);
195 + assertEquals("The task should not have run another time.", 2, taskList.get(0).timesRun());
196 +
197 + TestTask task = new TestTask();
198 + timer.schedule(task, new Date(timer.currentTimeInMillis()), 10);
199 + timer.advanceTimeMillis(100, REAL_TIME_DELAY);
200 + assertEquals("Should have run immediately and subsequently once during the larger skip", task.timesRun(), 2);
201 + }
202 +
203 + /* Schedule at fixed rate runs exactly like the two scheduling methods just tested so tests are not included */
204 +
205 + /**
206 + * Timer task with added functions to make it better for testing.
207 + */
208 + private class TestTask extends TimerTask {
209 +
210 + /* Remains true once the task has been run at least once */
211 + private boolean hasRun;
212 +
213 + /* Unique id per event. */
214 + private int id;
215 +
216 + /* Specifies the number of times an event has run */
217 + private int timesRun;
218 +
219 + /**
220 + * Constructor initializes id, timesRun, and id fields.
221 + */
222 + public TestTask() {
223 + id = idGenerator.getAndIncrement();
224 + timesRun = 0;
225 + hasRun = false;
226 + taskList.add(this);
227 + }
228 +
229 + @Override
230 + public void run() {
231 + this.hasRun = true;
232 + tasksRunCount.incrementAndGet();
233 + timesRun++;
234 + }
235 +
236 + /**
237 + * Returns whether this event has run.
238 + *
239 + * @return true if the event has run, false otherwise.
240 + */
241 + public boolean hasRun() {
242 + return hasRun;
243 + }
244 +
245 + /**
246 + * Returns the number of times this task has run.
247 + *
248 + * @return an int representing the number of times this task has been run
249 + */
250 + public int timesRun() {
251 + return timesRun;
252 + }
253 +
254 + /**
255 + * Returns the unique identifier of this task.
256 + *
257 + * @return a unique integer identifier
258 + */
259 + public int getId() {
260 + return id;
261 + }
262 + }
263 +}
...\ No newline at end of file ...\ No newline at end of file