diff --git a/src/test/java/kiss/TestableScheduler.java b/src/test/java/kiss/TestableScheduler.java index 997856d46..dc93b80aa 100644 --- a/src/test/java/kiss/TestableScheduler.java +++ b/src/test/java/kiss/TestableScheduler.java @@ -9,9 +9,13 @@ */ package kiss; +import static java.util.concurrent.TimeUnit.*; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -19,7 +23,7 @@ public class TestableScheduler extends Scheduler { - private long awaitingLimit = 1000; + private long awaitingLimit = 3000; private final AtomicBoolean starting = new AtomicBoolean(); @@ -198,6 +202,111 @@ public boolean awaitExecutions(long required) { return true; } + public boolean await() { + return start().awaitIdling(); + } + + /** + * Freeze process. + */ + public void await(long time, TimeUnit unit) { + freezeNano(unit.toNanos(time)); + } + + /** + *
+ * Freeze process. + *
+ * + * @param time A nano time to freeze. + */ + private void freezeNano(long time) { + try { + long start = System.nanoTime(); + NANOSECONDS.sleep(time); + long end = System.nanoTime(); + + long remaining = start + time - end; + + if (0 < remaining) { + freezeNano(remaining); + } + } catch (InterruptedException e) { + throw new Error(e); + } + } + + private long marked; + + /** + * Record the current time. Hereafter, it is used as the start time when using + * {@link #elapse(int, TimeUnit)} or {@link #within(int, TimeUnit, Runnable)}. + */ + public final TestableScheduler mark() { + marked = System.nanoTime(); + + return this; + } + + /** + *+ * Waits for the specified time from the marked time. It does not wait if it has already passed. + *
+ *+ * chronus.mark(); + * asynchronous.process(); + * + * chronus.elapse(100, TimeUnit.MILLSECONDS); + * assert validation.code(); + *+ * + * @param amount Time amount. + * @param unit Time unit. + */ + public final TestableScheduler elapse(int amount, TimeUnit unit) { + long startTime = marked + unit.toNanos(amount); + + await(startTime - System.nanoTime(), NANOSECONDS); + + return this; + } + + /** + *
+ * Performs the specified operation if the specified time has not yet elapsed since the marked + * time. If it has already passed, do nothing. + *
+ *+ * chronus.mark(); + * synchronous.process(); + * + * chronus.within(100, TimeUnit.MILLSECONDS, () -> { + * assert validation.code(); + * }); + *+ * + * @param amount Time amount. + * @param unit Time unit. + * @param within Your process. + */ + public final TestableScheduler within(int amount, TimeUnit unit, Runnable within) { + if (within != null && System.nanoTime() < marked + unit.toNanos(amount)) { + within.run(); + } + return this; + } + + /** + * Create delayed {@link Executors} in the specified duration. + * + * @param time A delay time. + * @param unit A time unit. + * @return A delayed {@link Executor}. + */ + public Executor in(long time, TimeUnit unit) { + return task -> schedule(task, time, unit); + } + /** * {@inheritDoc} */ diff --git a/src/test/java/kiss/signal/OnTest.java b/src/test/java/kiss/signal/OnTest.java index 4c0b23f35..166f1a452 100644 --- a/src/test/java/kiss/signal/OnTest.java +++ b/src/test/java/kiss/signal/OnTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test; import kiss.I; +import kiss.TestableScheduler; class OnTest extends SignalTester { @@ -23,7 +24,8 @@ class OnTest extends SignalTester { @Test void on() { - monitor(signal -> signal.on(after).map(v -> Thread.currentThread().getName().contains("pool"))); + Thread now = Thread.currentThread(); + monitor(signal -> signal.on(after).map(v -> Thread.currentThread() != now)); main.emit("START"); assert main.value(); @@ -57,7 +59,7 @@ void complete() { void dispose() { // Signal#on doesn't guarantee the execution order of events (including COMPLETE) // Single thread executor will arrange all events in serial. (FIFO) - scheduler.configExecutionLimit(1); + scheduler = new TestableScheduler(1); monitor(signal -> signal.take(1).on(after)); diff --git a/src/test/java/kiss/signal/SignalCreationTest.java b/src/test/java/kiss/signal/SignalCreationTest.java index 2c206a09a..1caea45dc 100644 --- a/src/test/java/kiss/signal/SignalCreationTest.java +++ b/src/test/java/kiss/signal/SignalCreationTest.java @@ -143,7 +143,7 @@ void interval() { assert main.isNotCompleted(); assert main.isNotDisposed(); - scheduler.await(200, ms); + scheduler.await(); assert main.value(true, true, true); assert main.isCompleted(); assert main.isDisposed(); diff --git a/src/test/java/kiss/signal/SignalTester.java b/src/test/java/kiss/signal/SignalTester.java index 03f74b331..4f467efa1 100644 --- a/src/test/java/kiss/signal/SignalTester.java +++ b/src/test/java/kiss/signal/SignalTester.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; @@ -28,11 +27,11 @@ import java.util.function.Supplier; import java.util.stream.Stream; -import antibug.Chronus; import kiss.Disposable; import kiss.I; import kiss.Observer; import kiss.Signal; +import kiss.TestableScheduler; import kiss.WiseBiFunction; import kiss.WiseConsumer; import kiss.WiseFunction; @@ -76,7 +75,7 @@ public class SignalTester { protected final SignalSource another = new SignalSource(); /** The chrono scheduler. */ - protected final Chronus scheduler = new Chronus(() -> Executors.newScheduledThreadPool(16)); + protected TestableScheduler scheduler = new TestableScheduler(); /** The log manager. */ private Map