Aaron Kruglikov
Committed by Gerrit Code Review

Adds manually advancing timer.

Change-Id: I83936f4fff577b0f23404560c6ecfbc0c9f18e2e
......@@ -64,27 +64,40 @@ public final class TestUtils {
/**
* 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);
}
}
......
......@@ -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 @@ public class AbstractAccumulatorTest {
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 @@ public class AbstractAccumulatorTest {
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 @@ public class AbstractAccumulatorTest {
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 @@ public class AbstractAccumulatorTest {
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 @@ public class AbstractAccumulatorTest {
return ready;
}
}
}
......
/*
* 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;
}
}
}
/*
* 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