blob: 3bfef46017d581e4e14a2806f1fb1904d8132646 [file] [log] [blame]
/**
* 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.apache.aurora.scheduler.testing;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.aurora.common.collections.Pair;
import org.apache.aurora.common.quantity.Amount;
import org.apache.aurora.common.quantity.Time;
import org.apache.aurora.common.util.testing.FakeClock;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import static org.easymock.EasyMock.expectLastCall;
import static org.junit.Assert.assertEquals;
/**
* A simulated scheduled executor that records scheduled work and executes it when the clock is
* advanced past their execution time.
*/
public final class FakeScheduledExecutor extends FakeClock {
private final List<Pair<Long, Runnable>> deferredWork = Lists.newArrayList();
private FakeScheduledExecutor() { }
// TODO(wfarner): Rename to fromScheduledExecutor().
public static FakeScheduledExecutor scheduleExecutor(ScheduledExecutorService mock) {
FakeScheduledExecutor executor = new FakeScheduledExecutor();
mock.schedule(
EasyMock.<Runnable>anyObject(),
EasyMock.anyLong(),
EasyMock.anyObject());
expectLastCall().andAnswer(answerSchedule(executor)).anyTimes();
mock.execute(EasyMock.anyObject());
expectLastCall().andAnswer(answerExecute()).anyTimes();
return executor;
}
private static IAnswer<Object> answerExecuteWithDelay(FakeScheduledExecutor executor) {
return () -> {
Object[] args = EasyMock.getCurrentArguments();
Runnable work = (Runnable) args[0];
@SuppressWarnings("unchecked")
long value = (long) args[1];
@SuppressWarnings("unchecked")
TimeUnit unit = (TimeUnit) args[2];
addDelayedWork(executor, TimeUnit.MILLISECONDS.convert(value, unit), work);
return null;
};
}
public static FakeScheduledExecutor fromScheduledExecutorService(ScheduledExecutorService mock) {
FakeScheduledExecutor executor = new FakeScheduledExecutor();
mock.schedule(
EasyMock.<Runnable>anyObject(),
EasyMock.anyLong(),
EasyMock.<TimeUnit>anyObject());
expectLastCall().andAnswer(answerExecuteWithDelay(executor)).anyTimes();
mock.execute(EasyMock.anyObject());
expectLastCall().andAnswer(answerExecute()).anyTimes();
return executor;
}
private static IAnswer<Void> answerExecute() {
return () -> {
Object[] args = EasyMock.getCurrentArguments();
Runnable work = (Runnable) args[0];
work.run();
return null;
};
}
private static IAnswer<Object> answerSchedule(FakeScheduledExecutor executor) {
return () -> {
Object[] args = EasyMock.getCurrentArguments();
Runnable work = (Runnable) args[0];
long value = (Long) args[1];
TimeUnit unit = (TimeUnit) args[2];
addDelayedWork(executor, toMillis(value, unit), work);
return null;
};
}
private static long toMillis(long value, TimeUnit unit) {
return TimeUnit.MILLISECONDS.convert(value, unit);
}
public static FakeScheduledExecutor scheduleAtFixedRateExecutor(
ScheduledExecutorService mock,
int maxInvocations) {
return scheduleAtFixedRateExecutor(mock, 1, maxInvocations);
}
public static FakeScheduledExecutor scheduleAtFixedRateExecutor(
ScheduledExecutorService mock,
int maxSchedules,
int maxInvocations) {
FakeScheduledExecutor executor = new FakeScheduledExecutor();
mock.schedule(EasyMock.<Runnable>anyObject(), EasyMock.anyLong(), EasyMock.anyObject());
expectLastCall().andAnswer(() -> {
((Runnable) EasyMock.getCurrentArguments()[0]).run();
return null;
}).anyTimes();
mock.scheduleAtFixedRate(
EasyMock.anyObject(),
EasyMock.anyLong(),
EasyMock.anyLong(),
EasyMock.anyObject());
expectLastCall()
.andAnswer(answerScheduleAtFixedRate(executor, maxInvocations))
.times(maxSchedules);
return executor;
}
private static IAnswer<ScheduledFuture<?>> answerScheduleAtFixedRate(
FakeScheduledExecutor executor,
int workCount) {
return () -> {
Object[] args = EasyMock.getCurrentArguments();
Runnable work = (Runnable) args[0];
long initialDelay = (Long) args[1];
long period = (Long) args[2];
TimeUnit unit = (TimeUnit) args[3];
for (int i = 0; i <= workCount; i++) {
addDelayedWork(executor, toMillis(initialDelay, unit) + i * toMillis(period, unit), work);
}
return null;
};
}
private static void addDelayedWork(
FakeScheduledExecutor executor,
long delayMillis,
Runnable work) {
Preconditions.checkArgument(delayMillis > 0);
executor.deferredWork.add(Pair.of(executor.nowMillis() + delayMillis, work));
}
@Override
public void setNowMillis(long nowMillis) {
throw new UnsupportedOperationException();
}
@Override
public void advance(Amount<Long, Time> period) {
super.advance(period);
Iterator<Pair<Long, Runnable>> entries = deferredWork.iterator();
List<Runnable> toExecute = Lists.newArrayList();
while (entries.hasNext()) {
Pair<Long, Runnable> next = entries.next();
if (next.getFirst() <= nowMillis()) {
entries.remove();
toExecute.add(next.getSecond());
}
}
for (Runnable work : toExecute) {
work.run();
}
}
public int countDeferredWork() {
return deferredWork.size();
}
public void assertEmpty() {
assertEquals(ImmutableList.<Pair<Long, Runnable>>of(), deferredWork);
}
}