package org.oddjob.scheduling;
import java.text.ParseException;
import java.util.Date;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
import org.apache.log4j.Logger;
import org.mockito.Mockito;
import org.mockito.internal.matchers.CapturingMatcher;
import org.oddjob.FailedToStopException;
import org.oddjob.Stoppable;
import org.oddjob.arooa.utils.DateHelper;
import org.oddjob.framework.SimpleJob;
import org.oddjob.jobs.structural.SequentialJob;
import org.oddjob.schedules.Interval;
import org.oddjob.schedules.IntervalTo;
import org.oddjob.schedules.Schedule;
import org.oddjob.schedules.ScheduleContext;
import org.oddjob.schedules.ScheduleResult;
import org.oddjob.schedules.SimpleInterval;
import org.oddjob.schedules.SimpleScheduleResult;
import org.oddjob.schedules.schedules.DailySchedule;
import org.oddjob.scheduling.state.TimerState;
import org.oddjob.state.FlagState;
import org.oddjob.state.JobState;
import org.oddjob.state.ParentState;
import org.oddjob.tools.ManualClock;
import org.oddjob.tools.StateSteps;
public class TimerStopTest extends TestCase {
private static final Logger logger = Logger.getLogger(TimerStopTest.class);
@Override
protected void setUp() throws Exception {
logger.info("---------------- " + getName() + " ---------------");
}
private class NeverRan extends SimpleJob {
@Override
protected int execute() throws Throwable {
throw new RuntimeException("Unexpected.");
}
}
public void testStopBeforeRunning() throws InterruptedException, FailedToStopException {
DefaultExecutors services = new DefaultExecutors();
Timer test = new Timer();
test.setScheduleExecutorService(
services.getScheduledExecutor());
test.setSchedule(new Schedule() {
public IntervalTo nextDue(ScheduleContext context) {
return new IntervalTo(Interval.END_OF_TIME);
}
});
NeverRan job = new NeverRan();
test.setJob(job);
test.run();
test.stop();
assertEquals(JobState.READY,
job.lastStateEvent().getState());
assertEquals(TimerState.STARTABLE,
test.lastStateEvent().getState());
test.force();
assertEquals(TimerState.COMPLETE,
test.lastStateEvent().getState());
services.stop();
}
private static class RunningJob extends SimpleJob
implements Stoppable {
final Exchanger<Void> running = new Exchanger<Void>();
@Override
protected int execute() throws Throwable {
running.exchange(null);
synchronized (this) {
try {
logger.info("Running job waiting.");
wait();
}
catch (InterruptedException e) {
logger.info("Running job interrupted.");
}
};
return 0;
}
@Override
protected void onStop() {
synchronized (this) {
notifyAll();
}
}
}
public void testStopWhenRunning() throws InterruptedException, FailedToStopException {
DefaultExecutors services = new DefaultExecutors();
Timer test = new Timer();
test.setScheduleExecutorService(
services.getScheduledExecutor());
test.setSchedule(new Schedule() {
public IntervalTo nextDue(ScheduleContext context) {
return new IntervalTo(new Date());
}
});
RunningJob job = new RunningJob();
test.setJob(job);
test.run();
job.running.exchange(null);
test.stop();
assertFalse(Thread.interrupted());
assertEquals(TimerState.STARTABLE,
test.lastStateEvent().getState());
assertEquals(JobState.COMPLETE,
job.lastStateEvent().getState());
services.stop();
}
private static class RunOnceJob extends SimpleJob {
boolean ran;
@Override
protected int execute() throws Throwable {
if (ran) {
throw new Exception("Unexpected.");
}
return 0;
}
}
public void testStopBetweenSchedules() throws InterruptedException, Throwable {
DefaultExecutors services = new DefaultExecutors();
services.setPoolSize(5);
final Timer test = new Timer();
test.setScheduleExecutorService(
services.getScheduledExecutor());
test.setSchedule(new Schedule() {
public IntervalTo nextDue(ScheduleContext context) {
if (context.getData("done") == null) {
context.putData("done", new Object());
return new IntervalTo(new Date());
}
else {
return new IntervalTo(Interval.END_OF_TIME);
}
}
});
RunOnceJob job = new RunOnceJob();
test.setJob(job);
StateSteps testStates = new StateSteps(test);
testStates.startCheck(TimerState.STARTABLE,
TimerState.STARTING, TimerState.ACTIVE,
TimerState.STARTED);
logger.info("** First Run **");
test.run();
testStates.checkWait();
testStates.startCheck(TimerState.STARTED,
TimerState.STARTABLE);
test.stop();
testStates.checkWait();
assertEquals(JobState.COMPLETE,
job.lastStateEvent().getState());
testStates.startCheck(TimerState.STARTABLE,
TimerState.STARTING, TimerState.STARTED);
logger.info("** Second Run **");
test.run();
testStates.checkNow();
assertEquals(new IntervalTo(Interval.END_OF_TIME), test.getCurrent());
test.stop();
services.stop();
}
/** Tracking down a bug with MissedSkipped when stop and started in
* the current interval re-runs the current interval.
* @throws ParseException
* @throws FailedToStopException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void testStopBetweenScheduleWithSkippedRuns() throws ParseException, FailedToStopException {
final ScheduledFuture<?> future = Mockito.mock(ScheduledFuture.class);
ScheduledExecutorService executor =
Mockito.mock(ScheduledExecutorService.class);
CapturingMatcher<Runnable> runnable =
new CapturingMatcher<Runnable>();
CapturingMatcher<Long> delay = new CapturingMatcher<Long>();
Mockito.when(executor.schedule(
Mockito.argThat(runnable),
Mockito.longThat(delay),
Mockito.eq(TimeUnit.MILLISECONDS))).thenReturn(
(ScheduledFuture) future);
Timer test = new Timer();
test.setSchedule(new DailySchedule());
test.setClock(new ManualClock("2012-01-24 01:00"));
test.setScheduleExecutorService(executor);
test.setJob(new RunOnceJob());
test.setSkipMissedRuns(true);
test.run();
assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState());
ScheduleResult expectedCurrent1 = new SimpleScheduleResult(
new SimpleInterval(
DateHelper.parseDateTime("2012-01-24 00:00"),
DateHelper.parseDateTime("2012-01-25 00:00")));
assertEquals(new Long(0), delay.getLastValue());
assertEquals(expectedCurrent1, test.getCurrent());
runnable.getLastValue().run();
ScheduleResult expectedCurrent2 = new SimpleScheduleResult(
new SimpleInterval(
DateHelper.parseDateTime("2012-01-25 00:00"),
DateHelper.parseDateTime("2012-01-26 00:00")));
assertEquals(expectedCurrent2, test.getCurrent());
assertEquals(new Long(23 * 60 * 60 * 1000L), delay.getLastValue());
test.stop();
assertEquals(TimerState.STARTABLE, test.lastStateEvent().getState());
test.run();
assertEquals(TimerState.STARTED, test.lastStateEvent().getState());
assertEquals(expectedCurrent2, test.getCurrent());
assertEquals(new Long(23 * 60 * 60 * 1000L), delay.getLastValue());
test.stop();
assertEquals(TimerState.STARTABLE, test.lastStateEvent().getState());
test.setJob(null);
test.setJob(new RunOnceJob());
test.setClock(new ManualClock("2012-01-28 01:00"));
test.run();
assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState());
ScheduleResult expectedCurrent3 = new SimpleScheduleResult(
new SimpleInterval(
DateHelper.parseDateTime("2012-01-28 00:00"),
DateHelper.parseDateTime("2012-01-29 00:00")));
assertEquals(new Long(0), delay.getLastValue());
assertEquals(expectedCurrent3, test.getCurrent());
runnable.getLastValue().run();
ScheduleResult expectedCurrent4 = new SimpleScheduleResult(
new SimpleInterval(
DateHelper.parseDateTime("2012-01-29 00:00"),
DateHelper.parseDateTime("2012-01-30 00:00")));
assertEquals(expectedCurrent4, test.getCurrent());
assertEquals(new Long(23 * 60 * 60 * 1000L), delay.getLastValue());
test.stop();
assertEquals(TimerState.STARTABLE, test.lastStateEvent().getState());
}
/**
* This is what happens when a Retry or an unfinished sequential
* is stopped.
* @throws ParseException
* @throws FailedToStopException
* @throws InterruptedException
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testWhenScheduledChildGoesToReady() throws ParseException, FailedToStopException, InterruptedException {
final ScheduledFuture<?> future = Mockito.mock(ScheduledFuture.class);
ScheduledExecutorService executor =
Mockito.mock(ScheduledExecutorService.class);
CapturingMatcher<Runnable> runnable =
new CapturingMatcher<Runnable>();
CapturingMatcher<Long> delay = new CapturingMatcher<Long>();
Mockito.when(executor.schedule(
Mockito.argThat(runnable),
Mockito.longThat(delay),
Mockito.eq(TimeUnit.MILLISECONDS))).thenReturn(
(ScheduledFuture) future);
Timer test = new Timer();
test.setSchedule(new DailySchedule());
test.setClock(new ManualClock("2012-07-11 01:00"));
test.setScheduleExecutorService(executor);
SequentialJob child = new SequentialJob();
test.setJob(child);
test.run();
Mockito.verify(executor).schedule(
Mockito.argThat(runnable),
Mockito.longThat(delay),
Mockito.eq(TimeUnit.MILLISECONDS));
assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState());
ScheduleResult expectedCurrent1 = new SimpleScheduleResult(
new SimpleInterval(
DateHelper.parseDateTime("2012-07-11 00:00"),
DateHelper.parseDateTime("2012-07-12 00:00")));
assertEquals(new Long(0), delay.getLastValue());
assertEquals(expectedCurrent1, test.getCurrent());
runnable.getLastValue().run();
Mockito.verifyNoMoreInteractions(executor);
assertEquals(ParentState.READY, child.lastStateEvent().getState());
FlagState flag = new FlagState();
child.setJobs(0, flag);
flag.run();
assertEquals(ParentState.COMPLETE, child.lastStateEvent().getState());
Mockito.verify(executor, Mockito.times(2)).schedule(
Mockito.argThat(runnable),
Mockito.longThat(delay),
Mockito.eq(TimeUnit.MILLISECONDS));
ScheduleResult expectedCurrent2 = new SimpleScheduleResult(
new SimpleInterval(
DateHelper.parseDateTime("2012-07-12 00:00"),
DateHelper.parseDateTime("2012-07-13 00:00")));
assertEquals(expectedCurrent2, test.getCurrent());
assertEquals(new Long(23 * 60 * 60 * 1000L), delay.getLastValue());
test.stop();
assertEquals(TimerState.STARTABLE, test.lastStateEvent().getState());
}
}