Adds manually advancing timer.

Change-Id: I83936f4fff577b0f23404560c6ecfbc0c9f18e2e
diff --git a/utils/junit/src/main/java/org/onlab/junit/TestUtils.java b/utils/junit/src/main/java/org/onlab/junit/TestUtils.java
index 1afc494..686a9a5 100644
--- a/utils/junit/src/main/java/org/onlab/junit/TestUtils.java
+++ b/utils/junit/src/main/java/org/onlab/junit/TestUtils.java
@@ -64,27 +64,40 @@
     /**
      * Gets the field, bypassing scope restriction.
      *
-     * @param subject Object where the field belongs
+     * @param subject   Object where the field belongs
      * @param fieldName name of the field to get
+     * @param <T>       subject type
+     * @param <U>       fieldO value type
      * @return value of the field.
-     * @param <T> subject type
-     * @param <U> field value type
      * @throws TestUtilsException if there are reflection errors while getting
-     * the field
+     *                            the field
      */
     public static <T, U> U getField(T subject, String fieldName)
             throws TestUtilsException {
         try {
+            NoSuchFieldException exception = null;
             @SuppressWarnings("unchecked")
-            Class<T> clazz = (Class<T>) subject.getClass();
-            Field field = clazz.getDeclaredField(fieldName);
-            field.setAccessible(true);
+            Class clazz = subject.getClass();
+            while (clazz != null) {
+                try {
+                    Field field = clazz.getDeclaredField(fieldName);
+                    field.setAccessible(true);
 
-            @SuppressWarnings("unchecked")
-            U result = (U) field.get(subject);
-            return result;
-        } catch (NoSuchFieldException | SecurityException |
-                 IllegalArgumentException | IllegalAccessException e) {
+                    @SuppressWarnings("unchecked")
+                    U result = (U) field.get(subject);
+                    return result;
+                } catch (NoSuchFieldException e) {
+                    exception = e;
+                    if (clazz == clazz.getSuperclass()) {
+                        break;
+                    }
+                    clazz = clazz.getSuperclass();
+                }
+            }
+            throw new TestUtilsException("Field not found. " + fieldName, exception);
+
+        } catch (SecurityException |
+                IllegalArgumentException | IllegalAccessException e) {
             throw new TestUtilsException("getField failed", e);
         }
     }
diff --git a/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java b/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java
index 02f0deb..db7224a 100644
--- a/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java
+++ b/utils/misc/src/test/java/org/onlab/util/AbstractAccumulatorTest.java
@@ -15,23 +15,24 @@
  */
 package org.onlab.util;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.List;
-import java.util.Timer;
 import java.util.stream.IntStream;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.onlab.junit.TestTools.assertAfter;
-import static org.onlab.junit.TestTools.delay;
 
 /**
  * Tests the operation of the accumulator.
  */
 public class AbstractAccumulatorTest {
 
-    private final Timer timer = new Timer();
+
+    private final ManuallyAdvancingTimer timer = new ManuallyAdvancingTimer();
+
 
     @Test
     public void basics() throws Exception {
@@ -42,7 +43,6 @@
         assertEquals("incorrect idle ms", 70, accumulator.maxIdleMillis());
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void eventTrigger() {
         TestAccumulator accumulator = new TestAccumulator();
@@ -52,43 +52,40 @@
         accumulator.add(new TestItem("d"));
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("e"));
-        delay(20);
+        timer.advanceTimeMillis(20, 10);
         assertFalse("should have fired", accumulator.batch.isEmpty());
         assertEquals("incorrect batch", "abcde", accumulator.batch);
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void timeTrigger() {
         TestAccumulator accumulator = new TestAccumulator();
         accumulator.add(new TestItem("a"));
-        delay(30);
+        timer.advanceTimeMillis(30, 1);
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("b"));
-        delay(30);
+        timer.advanceTimeMillis(30, 1);
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("c"));
-        delay(30);
+        timer.advanceTimeMillis(30, 1);
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("d"));
-        delay(60);
+        timer.advanceTimeMillis(10, 10);
         assertFalse("should have fired", accumulator.batch.isEmpty());
         assertEquals("incorrect batch", "abcd", accumulator.batch);
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void idleTrigger() {
         TestAccumulator accumulator = new TestAccumulator();
         accumulator.add(new TestItem("a"));
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("b"));
-        delay(80);
+        timer.advanceTimeMillis(70, 10);
         assertFalse("should have fired", accumulator.batch.isEmpty());
         assertEquals("incorrect batch", "ab", accumulator.batch);
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void readyIdleTrigger() {
         TestAccumulator accumulator = new TestAccumulator();
@@ -96,30 +93,28 @@
         accumulator.add(new TestItem("a"));
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("b"));
-        delay(80);
+        timer.advanceTimeMillis(80, 1);
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.ready = true;
-        delay(80);
+        timer.advanceTimeMillis(80, 10);
         assertFalse("should have fired", accumulator.batch.isEmpty());
         assertEquals("incorrect batch", "ab", accumulator.batch);
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void readyLongTrigger() {
         TestAccumulator accumulator = new TestAccumulator();
         accumulator.ready = false;
-        delay(120);
+        timer.advanceTimeMillis(120, 1);
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.add(new TestItem("a"));
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.ready = true;
-        delay(80);
+        timer.advanceTimeMillis(120, 10);
         assertFalse("should have fired", accumulator.batch.isEmpty());
         assertEquals("incorrect batch", "a", accumulator.batch);
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void readyMaxTrigger() {
         TestAccumulator accumulator = new TestAccumulator();
@@ -133,16 +128,16 @@
         assertTrue("should not have fired yet", accumulator.batch.isEmpty());
         accumulator.ready = true;
         accumulator.add(new TestItem("g"));
-        delay(5);
+        timer.advanceTimeMillis(10, 10);
         assertFalse("should have fired", accumulator.batch.isEmpty());
         assertEquals("incorrect batch", "abcdefg", accumulator.batch);
     }
 
-    @Ignore("FIXME: timing sensitive test failing randomly.")
     @Test
     public void stormTest() {
         TestAccumulator accumulator = new TestAccumulator();
         IntStream.range(0, 1000).forEach(i -> accumulator.add(new TestItem("#" + i)));
+        timer.advanceTimeMillis(1);
         assertAfter(100, () -> assertEquals("wrong item count", 1000, accumulator.itemCount));
         assertEquals("wrong batch count", 200, accumulator.batchCount);
     }
@@ -180,5 +175,4 @@
             return ready;
         }
     }
-
 }
diff --git a/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java b/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java
new file mode 100644
index 0000000..4116cbe
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.util;
+
+import com.google.common.collect.Lists;
+import org.onlab.junit.TestUtils;
+import org.slf4j.Logger;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.junit.TestTools.delay;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Provides manually scheduled timer utility. All schedulable methods are subject to overflow (you can set a period of
+ * max long).  Additionally if a skip skips a period of time greater than one period for a periodic task that task will
+ * only be executed once for that skip and scheduled it's period after the last execution.
+ */
+public class ManuallyAdvancingTimer extends java.util.Timer {
+
+    /* States whether or not the static values from timer task have been set ensures population will only occur once.*/
+    private boolean staticsPopulated = false;
+
+    /* Virgin value from timer task */
+    private int virginState;
+
+    /* Scheduled value from timer task */
+    private int scheduledState;
+
+    /* Executed value from timer task */
+    private int executedState;
+
+    /* Cancelled value from timer task */
+    private int cancelledState;
+
+    private final Logger logger = getLogger(getClass());
+
+    /* Service for executing timer tasks */
+    private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+    /* Internal time representation independent of system time, manually advanced */
+    private final TimerKeeper timerKeeper = new TimerKeeper();
+
+    /* Data structure for tracking tasks */
+    private final TaskQueue queue = new TaskQueue();
+
+    @Override
+    public void schedule(TimerTask task, long delay) {
+        if (!staticsPopulated) {
+            populateStatics(task);
+        }
+        if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
+                timerKeeper.currentTimeInMillis() - delay, 0)) {
+            logger.error("Failed to submit task");
+        }
+    }
+
+    @Override
+    public void schedule(TimerTask task, Date time) {
+        if (!staticsPopulated) {
+            populateStatics(task);
+        }
+        if (!submitTask(task, time.getTime(), 0)) {
+            logger.error("Failed to submit task");
+        }
+    }
+
+    @Override
+    public void schedule(TimerTask task, long delay, long period) {
+        if (!staticsPopulated) {
+            populateStatics(task);
+        }
+        if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
+                timerKeeper.currentTimeInMillis() - delay, period)) {
+            logger.error("Failed to submit task");
+        }
+    }
+
+    @Override
+    public void schedule(TimerTask task, Date firstTime, long period) {
+        if (!staticsPopulated) {
+            populateStatics(task);
+        }
+        if (!submitTask(task, firstTime.getTime(), period)) {
+            logger.error("Failed to submit task");
+        }
+    }
+
+    /*################################################WARNING################################################*/
+    /* Schedule at fixed rate methods do not work exactly as in the java timer. They are clones of the periodic
+    *scheduling methods. */
+    @Override
+    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
+        if (!staticsPopulated) {
+            populateStatics(task);
+        }
+        if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
+                timerKeeper.currentTimeInMillis() - delay, period)) {
+            logger.error("Failed to submit task");
+        }
+    }
+
+    @Override
+    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
+        if (!staticsPopulated) {
+            populateStatics(task);
+        }
+        if (!submitTask(task, firstTime.getTime(), period)) {
+            logger.error("Failed to submit task");
+        }
+    }
+
+    @Override
+    public void cancel() {
+        executorService.shutdown();
+        queue.clear();
+    }
+
+    @Override
+    public int purge() {
+        return queue.removeCancelled();
+    }
+
+    /**
+     * Returns the virtual current time in millis.
+     *
+     * @return long representing simulated current time.
+     */
+    public long currentTimeInMillis() {
+        return timerKeeper.currentTimeInMillis();
+    }
+
+    /**
+     * Returns the new simulated current time in millis after advancing the absolute value of millis to advance.
+     * Triggers event execution of all events scheduled for execution at times up to and including the returned time.
+     * Passing in the number zero has no effect.
+     *
+     * @param millisToAdvance the number of millis to advance.
+     * @return a long representing the current simulated time in millis
+     */
+    public long advanceTimeMillis(long millisToAdvance) {
+        return timerKeeper.advanceTimeMillis(millisToAdvance);
+    }
+
+    /**
+     * Advances the virtual time a certain number of millis triggers execution delays a certain amount to
+     * allow time for execution.
+     *
+     * @param virtualTimeAdvance the time to be advances in millis of simulated time.
+     * @param realTimeDelay      the time to delay in real time to allow for processing.
+     */
+    public void advanceTimeMillis(long virtualTimeAdvance, int realTimeDelay) {
+        timerKeeper.advanceTimeMillis(virtualTimeAdvance);
+        delay(realTimeDelay);
+    }
+
+    /**
+     * Sets up the task and submits it to the queue.
+     *
+     * @param task    the task to be added to the queue
+     * @param runtime the first runtime of the task
+     * @param period  the period between runs thereafter
+     * @return returns true if the task was successfully submitted, false otherwise
+     */
+    private boolean submitTask(TimerTask task, long runtime, long period) {
+        checkNotNull(task);
+        try {
+            TestUtils.setField(task, "state", scheduledState);
+            TestUtils.setField(task, "nextExecutionTime", runtime);
+            TestUtils.setField(task, "period", period);
+        } catch (TestUtils.TestUtilsException e) {
+            e.printStackTrace();
+            return false;
+        }
+        queue.insertOrdered(task);
+        return true;
+    }
+
+    /**
+     * Executes the given task (only if it is in the scheduled state) and proceeds to reschedule it or mark it as
+     * executed.  Does not remove from the queue (this must be done outside).
+     *
+     * @param task the timer task to be executed
+     */
+    private boolean executeTask(TimerTask task) {
+        checkNotNull(task);
+        int currentState;
+        try {
+            currentState = TestUtils.getField(task, "state");
+        } catch (TestUtils.TestUtilsException e) {
+            logger.error("Could not get state of task.");
+            e.printStackTrace();
+            return false;
+        }
+        //If cancelled or already executed stop here.
+        if (currentState == executedState || currentState == cancelledState) {
+            return false;
+        } else if (currentState == virginState) {
+            logger.error("Task was set for execution without being scheduled.");
+            return false;
+        } else if (currentState == scheduledState) {
+            long period;
+
+            try {
+                period = TestUtils.getField(task, "period");
+            } catch (TestUtils.TestUtilsException e) {
+                logger.error("Could not read period of task.");
+                e.printStackTrace();
+                return false;
+            }
+            //Period of zero means one time execution.
+            if (period == 0) {
+                try {
+                    TestUtils.setField(task, "state", executedState);
+                } catch (TestUtils.TestUtilsException e) {
+                    logger.error("Could not set executed state.");
+                    e.printStackTrace();
+                    return false;
+                }
+                executorService.execute(task);
+                return true;
+            } else {
+                //Calculate next execution time, using absolute value of period
+                long nextTime = (period > 0) ? (timerKeeper.currentTimeInMillis() + period) :
+                        (timerKeeper.currentTimeInMillis() - period);
+                try {
+                    TestUtils.setField(task, "nextExecutionTime", nextTime);
+                } catch (TestUtils.TestUtilsException e) {
+                    logger.error("Could not set next execution time.");
+                    e.printStackTrace();
+                    return false;
+                }
+                //Schedule next execution
+                queue.insertOrdered(task);
+                executorService.execute(task);
+                return true;
+            }
+        }
+        logger.error("State property of {} is in an illegal state and did not execute.", task);
+        return false;
+    }
+
+    /**
+     * Executes all tasks in the queue scheduled for execution up to and including the current time.
+     *
+     * @return the total number of tasks run, -1 if failure
+     */
+    private int executeEventsUpToPresent() {
+        int totalRun = 0;
+        if (queue.isEmpty()) {
+            return -1;
+        }
+        TimerTask currTask = queue.peek();
+        long currExecTime;
+        try {
+            currExecTime = TestUtils.getField(currTask, "nextExecutionTime");
+        } catch (TestUtils.TestUtilsException e) {
+            e.printStackTrace();
+            throw new RuntimeException("Could not get nextExecutionTime");
+        }
+        while (currExecTime <= timerKeeper.currentTimeInMillis()) {
+            if (executeTask(queue.pop())) {
+                totalRun++;
+            }
+            if (queue.isEmpty()) {
+                break;
+            }
+            currTask = queue.peek();
+            try {
+                currExecTime = TestUtils.getField(currTask, "nextExecutionTime");
+            } catch (TestUtils.TestUtilsException e) {
+                e.printStackTrace();
+                throw new RuntimeException("Could not get nextExecutionTime");
+            }
+        }
+        return totalRun;
+    }
+
+    /**
+     * Populates the static fields from timer task. Should only be called once.
+     */
+    private void populateStatics(TimerTask task) {
+        try {
+            virginState = TestUtils.getField(task, "VIRGIN");
+            scheduledState = TestUtils.getField(task, "SCHEDULED");
+            executedState = TestUtils.getField(task, "EXECUTED");
+            cancelledState = TestUtils.getField(task, "CANCELLED");
+            staticsPopulated = true;
+        } catch (TestUtils.TestUtilsException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * A class used to maintain the virtual time.
+     */
+    private class TimerKeeper {
+
+        private long currentTime = 0;
+
+        /**
+         * Returns the virtual current time in millis.
+         *
+         * @return long representing simulated current time.
+         */
+        long currentTimeInMillis() {
+            return currentTime;
+        }
+
+        /**
+         * Returns the new simulated current time in millis after advancing the absolute value of millis to advance.
+         * Triggers event execution of all events scheduled for execution at times up to and including the returned
+         * time. Passing in the number zero has no effect.
+         *
+         * @param millisToAdvance the number of millis to advance.
+         * @return a long representing the current simulated time in millis
+         */
+        long advanceTimeMillis(long millisToAdvance) {
+            currentTime = (millisToAdvance >= 0) ? (currentTime + millisToAdvance) : (currentTime - millisToAdvance);
+            if (millisToAdvance != 0) {
+                executeEventsUpToPresent();
+            }
+            return currentTime;
+        }
+    }
+
+    /**
+     * A queue backed by a linked list. Keeps elements sorted in ascending order of execution time.  All calls are safe
+     * even on empty queue's.
+     */
+    private class TaskQueue {
+        private final LinkedList<TimerTask> taskList = Lists.newLinkedList();
+
+        /**
+         * Adds the task to the queue in ascending order of scheduled execution. If execution time has already passed
+         * execute immediately.
+         *
+         * @param task the task to be added to the queue
+         */
+        void insertOrdered(TimerTask task) {
+            //Using O(N) insertion because random access is expensive in linked lists worst case is 2N links followed
+            // for binary insertion vs N for simple insertion.
+            checkNotNull(task);
+            if (!staticsPopulated) {
+                populateStatics(task);
+            }
+            long insertTime;
+            try {
+                insertTime = TestUtils.getField(task, "nextExecutionTime");
+                TestUtils.setField(task, "state", scheduledState);
+            } catch (TestUtils.TestUtilsException e) {
+                e.printStackTrace();
+                return;
+            }
+            //If the task was scheduled in the past or for the current time run it immediately and do not add to the
+            // queue, subsequent executions will be scheduled as normal
+            if (insertTime <= timerKeeper.currentTimeInMillis()) {
+                executeTask(task);
+                return;
+            }
+
+            Iterator<TimerTask> iter = taskList.iterator();
+            int positionCounter = 0;
+            long nextTaskTime;
+            TimerTask currentTask;
+            while (iter.hasNext()) {
+                currentTask = iter.next();
+                try {
+                    nextTaskTime = TestUtils.getField(currentTask, "nextExecutionTime");
+                } catch (TestUtils.TestUtilsException e) {
+                    e.printStackTrace();
+                    return;
+                }
+                if (insertTime < nextTaskTime) {
+                    taskList.add(positionCounter, task);
+                    return;
+                }
+                positionCounter++;
+            }
+            taskList.addLast(task);
+        }
+
+        /**
+         * Returns the first item in the queue (next scheduled for execution) without removing it, returns null if the
+         * queue is empty.
+         *
+         * @return the next TimerTask to run or null if the queue is empty
+         */
+        TimerTask peek() {
+            if (taskList.isEmpty()) {
+                return null;
+            }
+            return taskList.getFirst();
+        }
+
+        /**
+         * Returns and removes the first item in the queue or null if it is empty.
+         *
+         * @return the first element of the queue or null if the queue is empty
+         */
+        TimerTask pop() {
+            if (taskList.isEmpty()) {
+                return null;
+            }
+            return taskList.pop();
+        }
+
+        /**
+         * Performs a sort on the set of timer tasks, earliest task is first. Does nothing if queue is empty.
+         */
+        void sort() {
+            if (taskList.isEmpty()) {
+                return;
+            }
+            taskList.sort((o1, o2) -> {
+                checkNotNull(o1);
+                checkNotNull(o2);
+                long executionTimeOne;
+                long executionTimeTwo;
+                try {
+                    executionTimeOne = TestUtils.getField(o1, "nextExecutionTime");
+                    executionTimeTwo = TestUtils.getField(o2, "nextExecutionTime");
+                } catch (TestUtils.TestUtilsException e) {
+                    e.printStackTrace();
+                    throw new RuntimeException("Could not get next execution time.");
+                }
+                if (executionTimeOne == executionTimeTwo) {
+                    return 0;
+                } else if (executionTimeOne < executionTimeTwo) {
+                    return -1;
+                } else {
+                    return 1;
+                }
+            });
+        }
+
+        /**
+         * Returns whether the queue is currently empty.
+         *
+         * @return true if the queue is empty, false otherwise
+         */
+        boolean isEmpty() {
+            return taskList.isEmpty();
+        }
+
+        /**
+         * Clears the underlying list of the queue.
+         */
+        void clear() {
+            taskList.clear();
+        }
+
+        /**
+         * Removes all cancelled tasks from the queue. Has no effect on behavior.
+         *
+         * @return returns the total number of items removed, -1 if list is empty or failure occurs.
+         */
+        int removeCancelled() {
+            if (taskList.isEmpty()) {
+                return -1;
+            }
+            int removedCount = 0;
+            Iterator<TimerTask> taskIterator = taskList.iterator();
+            TimerTask currTask;
+            int currState;
+            while (taskIterator.hasNext()) {
+                currTask = taskIterator.next();
+                try {
+                    currState = TestUtils.getField(currTask, "state");
+                } catch (TestUtils.TestUtilsException e) {
+                    logger.error("Could not get task state.");
+                    e.printStackTrace();
+                    return -1;
+                }
+                if (currState == cancelledState) {
+                    removedCount++;
+                    taskIterator.remove();
+                }
+            }
+            return removedCount;
+        }
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimerTest.java b/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimerTest.java
new file mode 100644
index 0000000..b8e1e85
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimerTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.util;
+
+import com.google.common.collect.Lists;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.onlab.junit.TestTools.delay;
+
+/**
+ * Testing class for manually advancing timer.
+ */
+public class ManuallyAdvancingTimerTest {
+
+    private ManuallyAdvancingTimer timer;
+
+    /* Generates unique id's for TestTasks */
+    private AtomicInteger idGenerator;
+
+    /* Tracks TestTasks in order of creation, tasks are automatically added at creation. */
+    private ArrayList<TestTask> taskList;
+
+    /* Total number of tasks run */
+    private AtomicInteger tasksRunCount;
+
+    // FIXME if this class fails first try increasing the real time delay to account for heavy system load.
+    private static final int REAL_TIME_DELAY = 1;
+
+    /**
+     * Sets up the testing environment.
+     */
+    @Before
+    public void setup() {
+        timer = new ManuallyAdvancingTimer();
+        idGenerator = new AtomicInteger(1);
+        tasksRunCount = new AtomicInteger(0);
+        taskList = Lists.newArrayList();
+    }
+
+    /**
+     * Tests the one time schedule with delay.
+     *
+     * @throws Exception throws an exception if the test fails
+     */
+    @Test
+    public void testScheduleByDelay() throws Exception {
+        /* Test scheduling in the future as normal. */
+        timer.schedule(new TestTask(), 10);
+        timer.advanceTimeMillis(5);
+        assertFalse(taskList.get(0).hasRun());
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertTrue(taskList.get(0).hasRun());
+
+        /* Test scheduling with negative numbers */
+        timer.schedule(new TestTask(), -10);
+        timer.advanceTimeMillis(5);
+        assertFalse(taskList.get(1).hasRun());
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertTrue(taskList.get(1).hasRun());
+
+        /* Reset list, counter and timer for next test */
+        taskList.clear();
+        idGenerator.set(1);
+        tasksRunCount.set(0);
+
+        for (int i = 0; i < 50; i++) {
+            timer.schedule(new TestTask(), i);
+        }
+        /* Test that a task scheduled for present is run and not placed in the queue */
+        assertEquals("Only the first task should have run.", 1, tasksRunCount.get());
+
+        for (int i = 2; i <= 50; i++) {
+            timer.advanceTimeMillis(1, REAL_TIME_DELAY);
+            assertEquals("One task should be executed per loop", i, tasksRunCount.get());
+        }
+        /* Below tests ordered insertion, this will only be done once, it is the same for all schedule methods. */
+
+        tasksRunCount.set(0);
+
+        for (int i = 0; i < 10; i++) {
+            timer.schedule(new TestTask(), 500);
+        }
+
+        assertEquals("No new tasks should have been run  since run count reset.", 0, tasksRunCount.get());
+        timer.schedule(new TestTask(), 10);
+        assertEquals("No new tasks should have been run  since run count reset.", 0, tasksRunCount.get());
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertEquals("One new tasks should have been run  since run count reset.", 1, tasksRunCount.get());
+        timer.advanceTimeMillis(510, REAL_TIME_DELAY);
+        assertEquals("Eleven new tasks should have been run  since run count reset.", 11, tasksRunCount.get());
+    }
+
+    /**
+     * Tests scheduling for a particular date or time which may be in the past.
+     *
+     * @throws Exception throws an exception if the test fails
+     */
+    @Test
+    public void testScheduleByDate() throws Exception {
+        /* Tests basic scheduling for future times. */
+        timer.schedule(new TestTask(), new Date(10));
+        timer.advanceTimeMillis(5);
+        assertFalse(taskList.get(0).hasRun());
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertTrue(taskList.get(0).hasRun());
+
+        /* Test scheduling with past times numbers */
+        timer.schedule(new TestTask(), new Date(0));
+        delay(REAL_TIME_DELAY);
+        assertTrue(taskList.get(1).hasRun());
+
+        /* Tests cancellation on non-periodic events */
+        TestTask task = new TestTask();
+        timer.schedule(task, new Date(timer.currentTimeInMillis() + 10));
+        task.cancel();
+        timer.advanceTimeMillis(12, REAL_TIME_DELAY);
+        assertFalse(task.hasRun());
+
+    }
+
+    /**
+     * Test scheduling beginning after a delay and recurring periodically.
+     *
+     * @throws Exception throws an exception if the test fails
+     */
+    @Test
+    public void testScheduleByDelayPeriodic() throws Exception {
+        /* Test straightforward periodic execution */
+        timer.schedule(new TestTask(), 0, 10);
+        delay(REAL_TIME_DELAY);
+        assertEquals("Task should have run once when added.", 1, taskList.get(0).timesRun());
+
+        /* Tests whether things that are not added to the queue are scheduled for future executions (ones which execute
+        immediately on add). */
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertEquals("Task should have run once when added.", 2, taskList.get(0).timesRun());
+
+        /* Tests whether cancellation works on periodic events. */
+        taskList.get(0).cancel();
+
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertEquals("The task should not have run another time.", 2, taskList.get(0).timesRun());
+
+        TestTask task = new TestTask();
+        timer.schedule(task, 0, 10);
+        timer.advanceTimeMillis(100, REAL_TIME_DELAY);
+        assertEquals("Should have run immeditaley and subsequently once during the larger skip", task.timesRun(), 2);
+
+    }
+
+    /**
+     * Test scheduling beginning at a specified date and recurring periodically.
+     *
+     * @throws Exception throws an exception if the test fails
+     */
+    @Test
+    public void testScheduleByDatePeriodic() throws Exception {
+        /* Test straightforward periodic execution */
+        timer.schedule(new TestTask(), new Date(timer.currentTimeInMillis()), 10);
+        delay(REAL_TIME_DELAY);
+        assertEquals("Task should have run once when added.", 1, taskList.get(0).timesRun());
+
+        /* Tests whether things that are not added to the queue are scheduled for future executions (ones which execute
+        immediately on add). */
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertEquals("Task should have run once when added.", 2, taskList.get(0).timesRun());
+
+        /* Tests whether cancellation works on periodic events. */
+        taskList.get(0).cancel();
+
+        timer.advanceTimeMillis(10, REAL_TIME_DELAY);
+        assertEquals("The task should not have run another time.", 2, taskList.get(0).timesRun());
+
+        TestTask task = new TestTask();
+        timer.schedule(task, new Date(timer.currentTimeInMillis()), 10);
+        timer.advanceTimeMillis(100, REAL_TIME_DELAY);
+        assertEquals("Should have run immediately and subsequently once during the larger skip", task.timesRun(), 2);
+    }
+
+    /* Schedule at fixed rate runs exactly like the two scheduling methods just tested so tests are not included */
+
+    /**
+     * Timer task with added functions to make it better for testing.
+     */
+    private class TestTask extends TimerTask {
+
+        /* Remains true once the task has been run at least once */
+        private boolean hasRun;
+
+        /* Unique id per event. */
+        private int id;
+
+        /* Specifies the number of times an event has run */
+        private int timesRun;
+
+        /**
+         * Constructor initializes id, timesRun, and id fields.
+         */
+        public TestTask() {
+            id = idGenerator.getAndIncrement();
+            timesRun = 0;
+            hasRun = false;
+            taskList.add(this);
+        }
+
+        @Override
+        public void run() {
+            this.hasRun = true;
+            tasksRunCount.incrementAndGet();
+            timesRun++;
+        }
+
+        /**
+         * Returns whether this event has run.
+         *
+         * @return true if the event has run, false otherwise.
+         */
+        public boolean hasRun() {
+            return hasRun;
+        }
+
+        /**
+         * Returns the number of times this task has run.
+         *
+         * @return an int representing the number of times this task has been run
+         */
+        public int timesRun() {
+            return timesRun;
+        }
+
+        /**
+         * Returns the unique identifier of this task.
+         *
+         * @return a unique integer identifier
+         */
+        public int getId() {
+            return id;
+        }
+    }
+}
\ No newline at end of file