diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TimeTravel.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TimeTravel.java new file mode 100644 index 000000000..2287fe6c5 --- /dev/null +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TimeTravel.java @@ -0,0 +1,8 @@ +package org.briarproject.bramble.test; + +public interface TimeTravel { + + void setCurrentTimeMillis(long now) throws InterruptedException; + + void addCurrentTimeMillis(long add) throws InterruptedException; +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/BrambleCoreIntegrationTestEagerSingletons.java b/bramble-core/src/test/java/org/briarproject/bramble/BrambleCoreIntegrationTestEagerSingletons.java index 679bf0719..0916b217b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/BrambleCoreIntegrationTestEagerSingletons.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/BrambleCoreIntegrationTestEagerSingletons.java @@ -1,18 +1,18 @@ package org.briarproject.bramble; -import org.briarproject.bramble.system.DefaultTaskSchedulerModule; +import org.briarproject.bramble.system.TimeTravelModule; public interface BrambleCoreIntegrationTestEagerSingletons extends BrambleCoreEagerSingletons { - void inject(DefaultTaskSchedulerModule.EagerSingletons init); + void inject(TimeTravelModule.EagerSingletons init); class Helper { public static void injectEagerSingletons( BrambleCoreIntegrationTestEagerSingletons c) { BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c); - c.inject(new DefaultTaskSchedulerModule.EagerSingletons()); + c.inject(new TimeTravelModule.EagerSingletons()); } } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/system/TestTaskScheduler.java b/bramble-core/src/test/java/org/briarproject/bramble/system/TestTaskScheduler.java new file mode 100644 index 000000000..928c2b3a9 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/system/TestTaskScheduler.java @@ -0,0 +1,110 @@ +package org.briarproject.bramble.system; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.system.TaskScheduler; + +import java.util.Queue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.junit.Assert.fail; + +@NotNullByDefault +class TestTaskScheduler implements TaskScheduler { + + private final Queue queue = new PriorityBlockingQueue<>(); + private final Clock clock; + + TestTaskScheduler(Clock clock) { + this.clock = clock; + } + + @Override + public Cancellable schedule(Runnable task, Executor executor, long delay, + TimeUnit unit) { + AtomicBoolean cancelled = new AtomicBoolean(false); + return schedule(task, executor, delay, unit, cancelled); + } + + @Override + public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, + long delay, long interval, TimeUnit unit) { + AtomicBoolean cancelled = new AtomicBoolean(false); + return scheduleWithFixedDelay(task, executor, delay, interval, unit, + cancelled); + } + + private Cancellable schedule(Runnable task, Executor executor, long delay, + TimeUnit unit, AtomicBoolean cancelled) { + long delayMillis = MILLISECONDS.convert(delay, unit); + long dueMillis = clock.currentTimeMillis() + delayMillis; + Task t = new Task(task, executor, dueMillis, cancelled); + queue.add(t); + return t; + } + + private Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, + long delay, long interval, TimeUnit unit, AtomicBoolean cancelled) { + // All executions of this periodic task share a cancelled flag + Runnable wrapped = () -> { + task.run(); + scheduleWithFixedDelay(task, executor, interval, interval, unit, + cancelled); + }; + return schedule(wrapped, executor, delay, unit, cancelled); + } + + void runTasks() throws InterruptedException { + long now = clock.currentTimeMillis(); + while (true) { + Task t = queue.peek(); + if (t == null || t.dueMillis > now) return; + t = queue.poll(); + if (!t.run().await(1, MINUTES)) fail(); + } + } + + private static class Task + implements Cancellable, Comparable { + + private final Runnable task; + private final Executor executor; + private final long dueMillis; + private final AtomicBoolean cancelled; + + private Task(Runnable task, Executor executor, long dueMillis, + AtomicBoolean cancelled) { + this.task = task; + this.executor = executor; + this.dueMillis = dueMillis; + this.cancelled = cancelled; + } + + @SuppressWarnings("UseCompareMethod") // Animal Sniffer + @Override + public int compareTo(Task task) { + return Long.valueOf(dueMillis).compareTo(task.dueMillis); + } + + public CountDownLatch run() { + if (cancelled.get()) return new CountDownLatch(0); + CountDownLatch latch = new CountDownLatch(1); + executor.execute(() -> { + task.run(); + latch.countDown(); + }); + return latch; + } + + @Override + public void cancel() { + cancelled.set(true); + } + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/system/TimeTravelModule.java b/bramble-core/src/test/java/org/briarproject/bramble/system/TimeTravelModule.java new file mode 100644 index 000000000..bbdae5133 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/system/TimeTravelModule.java @@ -0,0 +1,96 @@ +package org.briarproject.bramble.system; + +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.system.TaskScheduler; +import org.briarproject.bramble.test.SettableClock; +import org.briarproject.bramble.test.TimeTravel; + +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicLong; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class TimeTravelModule { + + public static class EagerSingletons { + @Inject + TaskScheduler scheduler; + } + + private final ScheduledExecutorService scheduledExecutorService; + private final Clock clock; + private final TaskScheduler taskScheduler; + private final TimeTravel timeTravel; + + public TimeTravelModule() { + this(false); + } + + public TimeTravelModule(boolean travel) { + // Discard tasks that are submitted during shutdown + RejectedExecutionHandler policy = + new ScheduledThreadPoolExecutor.DiscardPolicy(); + scheduledExecutorService = + new ScheduledThreadPoolExecutor(1, policy); + if (travel) { + AtomicLong time = new AtomicLong(System.currentTimeMillis()); + clock = new SettableClock(time); + TestTaskScheduler testTaskScheduler = new TestTaskScheduler(clock); + taskScheduler = testTaskScheduler; + timeTravel = new TimeTravel() { + @Override + public void setCurrentTimeMillis(long now) + throws InterruptedException { + time.set(now); + testTaskScheduler.runTasks(); + } + + @Override + public void addCurrentTimeMillis(long add) + throws InterruptedException { + time.addAndGet(add); + testTaskScheduler.runTasks(); + } + }; + } else { + clock = new SystemClock(); + taskScheduler = new TaskSchedulerImpl(scheduledExecutorService); + timeTravel = new TimeTravel() { + @Override + public void setCurrentTimeMillis(long now) { + throw new UnsupportedOperationException(); + } + + @Override + public void addCurrentTimeMillis(long add) { + throw new UnsupportedOperationException(); + } + }; + } + } + + @Provides + Clock provideClock() { + return clock; + } + + @Provides + @Singleton + TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager) { + lifecycleManager.registerForShutdown(scheduledExecutorService); + return taskScheduler; + } + + @Provides + TimeTravel provideTimeTravel() { + return timeTravel; + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java index 778219038..7caf6c131 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java @@ -3,8 +3,8 @@ package org.briarproject.bramble.test; import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.battery.DefaultBatteryManagerModule; import org.briarproject.bramble.event.DefaultEventExecutorModule; -import org.briarproject.bramble.system.DefaultTaskSchedulerModule; import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule; +import org.briarproject.bramble.system.TimeTravelModule; import dagger.Module; import dagger.Provides; @@ -12,12 +12,11 @@ import dagger.Provides; @Module(includes = { DefaultBatteryManagerModule.class, DefaultEventExecutorModule.class, - DefaultTaskSchedulerModule.class, DefaultWakefulIoExecutorModule.class, - TestClockModule.class, TestDatabaseConfigModule.class, TestPluginConfigModule.class, - TestSecureRandomModule.class + TestSecureRandomModule.class, + TimeTravelModule.class }) public class BrambleCoreIntegrationTestModule { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestClockModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestClockModule.java deleted file mode 100644 index b40bf54ec..000000000 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestClockModule.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.briarproject.bramble.test; - -import org.briarproject.bramble.api.system.Clock; -import org.briarproject.bramble.system.SystemClock; - -import java.util.concurrent.atomic.AtomicLong; - -import dagger.Module; -import dagger.Provides; - -@Module -public class TestClockModule { - - private final Clock clock; - - public TestClockModule() { - clock = new SystemClock(); - } - - public TestClockModule(AtomicLong time) { - clock = new SettableClock(time); - } - - @Provides - Clock provideClock() { - return clock; - } -} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index 7d7a844de..d397d7da0 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -13,6 +13,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; +import org.briarproject.bramble.test.TimeTravel; import org.briarproject.briar.api.attachment.AttachmentReader; import org.briarproject.briar.api.autodelete.AutoDeleteManager; import org.briarproject.briar.api.avatar.AvatarManager; @@ -137,6 +138,8 @@ public interface BriarIntegrationTestComponent Clock getClock(); + TimeTravel getTimeTravel(); + class Helper { public static void injectEagerSingletons(