From 9ff5e5fdb9bcbbb0d77784a7755e61b7e4387590 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sun, 27 Jun 2021 10:31:25 +1000 Subject: [PATCH] Clock improvements and test re-org --- src/main/java/org/dataloader/DataLoader.java | 22 +++++-- .../java/org/dataloader/DataLoaderHelper.java | 4 +- .../java/org/dataloader/ClockDataLoader.java | 15 +++++ .../DataLoaderMapBatchLoaderTest.java | 4 +- .../java/org/dataloader/DataLoaderTest.java | 4 +- .../org/dataloader/DataLoaderTimeTest.java | 57 ++++++----------- src/test/java/org/dataloader/TestKit.java | 23 ------- .../dataloader/{ => fixtures}/JsonObject.java | 14 +++-- .../java/org/dataloader/fixtures/TestKit.java | 61 +++++++++++++++++++ .../org/dataloader/fixtures/TestingClock.java | 38 ++++++++++++ 10 files changed, 164 insertions(+), 78 deletions(-) create mode 100644 src/test/java/org/dataloader/ClockDataLoader.java delete mode 100644 src/test/java/org/dataloader/TestKit.java rename src/test/java/org/dataloader/{ => fixtures}/JsonObject.java (72%) create mode 100644 src/test/java/org/dataloader/fixtures/TestKit.java create mode 100644 src/test/java/org/dataloader/fixtures/TestingClock.java diff --git a/src/main/java/org/dataloader/DataLoader.java b/src/main/java/org/dataloader/DataLoader.java index 8c4779f..18e7900 100644 --- a/src/main/java/org/dataloader/DataLoader.java +++ b/src/main/java/org/dataloader/DataLoader.java @@ -23,6 +23,7 @@ import org.dataloader.stats.StatisticsCollector; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; @@ -404,19 +405,21 @@ public DataLoader(BatchLoader batchLoadFunction, DataLoaderOptions options this((Object) batchLoadFunction, options); } + @VisibleForTesting DataLoader(Object batchLoadFunction, DataLoaderOptions options) { + this(batchLoadFunction, options, Clock.systemUTC()); + } + + @VisibleForTesting + DataLoader(Object batchLoadFunction, DataLoaderOptions options, Clock clock) { DataLoaderOptions loaderOptions = options == null ? new DataLoaderOptions() : options; this.futureCache = determineCacheMap(loaderOptions); // order of keys matter in data loader this.stats = nonNull(loaderOptions.getStatisticsCollector()); - this.helper = new DataLoaderHelper<>(this, batchLoadFunction, loaderOptions, this.futureCache, this.stats, clock()); + this.helper = new DataLoaderHelper<>(this, batchLoadFunction, loaderOptions, this.futureCache, this.stats, clock); } - @VisibleForTesting - Clock clock() { - return Clock.systemUTC(); - } @SuppressWarnings("unchecked") private CacheMap> determineCacheMap(DataLoaderOptions loaderOptions) { @@ -433,6 +436,15 @@ public Instant getLastDispatchTime() { return helper.getLastDispatchTime(); } + /** + * This returns the {@link Duration} since the data loader was dispatched. When the data loader is created this is zero. + * + * @return the time duration since the last dispatch + */ + public Duration getTimeSinceDispatch() { + return Duration.between(helper.getLastDispatchTime(), helper.now()); + } + /** * Requests to load the data with the specified key asynchronously, and returns a future of the resulting value. diff --git a/src/main/java/org/dataloader/DataLoaderHelper.java b/src/main/java/org/dataloader/DataLoaderHelper.java index 52b2f3a..bd93814 100644 --- a/src/main/java/org/dataloader/DataLoaderHelper.java +++ b/src/main/java/org/dataloader/DataLoaderHelper.java @@ -79,8 +79,8 @@ Object getCallContext() { this.lastDispatchTime.set(now()); } - private Instant now() { - return Instant.now(clock); + Instant now() { + return clock.instant(); } public Instant getLastDispatchTime() { diff --git a/src/test/java/org/dataloader/ClockDataLoader.java b/src/test/java/org/dataloader/ClockDataLoader.java new file mode 100644 index 0000000..4b16e78 --- /dev/null +++ b/src/test/java/org/dataloader/ClockDataLoader.java @@ -0,0 +1,15 @@ +package org.dataloader; + +import java.time.Clock; + +public class ClockDataLoader extends DataLoader { + + ClockDataLoader(Object batchLoadFunction, Clock clock) { + this(batchLoadFunction, null, clock); + } + + ClockDataLoader(Object batchLoadFunction, DataLoaderOptions options, Clock clock) { + super(batchLoadFunction, options, clock); + } + +} diff --git a/src/test/java/org/dataloader/DataLoaderMapBatchLoaderTest.java b/src/test/java/org/dataloader/DataLoaderMapBatchLoaderTest.java index 2abdf1d..0fced79 100644 --- a/src/test/java/org/dataloader/DataLoaderMapBatchLoaderTest.java +++ b/src/test/java/org/dataloader/DataLoaderMapBatchLoaderTest.java @@ -16,8 +16,8 @@ import static org.awaitility.Awaitility.await; import static org.dataloader.DataLoaderFactory.newDataLoader; import static org.dataloader.DataLoaderOptions.newOptions; -import static org.dataloader.TestKit.futureError; -import static org.dataloader.TestKit.listFrom; +import static org.dataloader.fixtures.TestKit.futureError; +import static org.dataloader.fixtures.TestKit.listFrom; import static org.dataloader.impl.CompletableFutureKit.cause; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; diff --git a/src/test/java/org/dataloader/DataLoaderTest.java b/src/test/java/org/dataloader/DataLoaderTest.java index f418b9c..18aa1ba 100644 --- a/src/test/java/org/dataloader/DataLoaderTest.java +++ b/src/test/java/org/dataloader/DataLoaderTest.java @@ -16,6 +16,8 @@ package org.dataloader; +import org.dataloader.fixtures.JsonObject; +import org.dataloader.fixtures.TestKit; import org.dataloader.fixtures.User; import org.dataloader.fixtures.UserManager; import org.dataloader.impl.CompletableFutureKit; @@ -37,7 +39,7 @@ import static org.awaitility.Awaitility.await; import static org.dataloader.DataLoaderFactory.newDataLoader; import static org.dataloader.DataLoaderOptions.newOptions; -import static org.dataloader.TestKit.listFrom; +import static org.dataloader.fixtures.TestKit.listFrom; import static org.dataloader.impl.CompletableFutureKit.cause; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; diff --git a/src/test/java/org/dataloader/DataLoaderTimeTest.java b/src/test/java/org/dataloader/DataLoaderTimeTest.java index f3d1678..ee73d85 100644 --- a/src/test/java/org/dataloader/DataLoaderTimeTest.java +++ b/src/test/java/org/dataloader/DataLoaderTimeTest.java @@ -1,68 +1,45 @@ package org.dataloader; +import org.dataloader.fixtures.TestingClock; import org.junit.Test; -import java.time.Clock; -import java.time.Duration; import java.time.Instant; -import java.time.ZoneId; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; +import static org.dataloader.fixtures.TestKit.keysAsValues; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; @SuppressWarnings("UnusedReturnValue") public class DataLoaderTimeTest { - private BatchLoader keysAsValues() { - return CompletableFuture::completedFuture; - } - - AtomicReference clockRef = new AtomicReference<>(); @Test public void should_set_and_instant_if_dispatched() { - Clock clock = zeroEpoch(); - clockRef.set(clock); - - Instant startInstant = now(); - @SuppressWarnings("deprecation") - DataLoader dataLoader = new DataLoader(keysAsValues()) { - @Override - Clock clock() { - return clockRef.get(); - } - }; + TestingClock clock = new TestingClock(); + DataLoader dataLoader = new ClockDataLoader<>(keysAsValues(), clock); + Instant then = clock.instant(); - long sinceMS = msSince(dataLoader.getLastDispatchTime()); + long sinceMS = dataLoader.getTimeSinceDispatch().toMillis(); assertThat(sinceMS, equalTo(0L)); - assertThat(startInstant, equalTo(dataLoader.getLastDispatchTime())); + assertThat(then, equalTo(dataLoader.getLastDispatchTime())); - jump(clock, 1000); - dataLoader.dispatch(); + then = clock.instant(); + clock.jump(1000); - sinceMS = msSince(dataLoader.getLastDispatchTime()); + sinceMS = dataLoader.getTimeSinceDispatch().toMillis(); assertThat(sinceMS, equalTo(1000L)); - } + assertThat(then, equalTo(dataLoader.getLastDispatchTime())); - private long msSince(Instant lastDispatchTime) { - return Duration.between(lastDispatchTime, now()).toMillis(); - } + // dispatch and hence reset the time of last dispatch + then = clock.instant(); + dataLoader.dispatch(); - private Instant now() { - return Instant.now(clockRef.get()); - } + sinceMS = dataLoader.getTimeSinceDispatch().toMillis(); + assertThat(sinceMS, equalTo(0L)); + assertThat(then, equalTo(dataLoader.getLastDispatchTime())); - private Clock jump(Clock clock, int millis) { - clock = Clock.offset(clock, Duration.ofMillis(millis)); - clockRef.set(clock); - return clock; } - private Clock zeroEpoch() { - return Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault()); - } } diff --git a/src/test/java/org/dataloader/TestKit.java b/src/test/java/org/dataloader/TestKit.java deleted file mode 100644 index 82f73d6..0000000 --- a/src/test/java/org/dataloader/TestKit.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.dataloader; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static org.dataloader.impl.CompletableFutureKit.failedFuture; - -public class TestKit { - - public static Collection listFrom(int i, int max) { - List ints = new ArrayList<>(); - for (int j = i; j < max; j++) { - ints.add(j); - } - return ints; - } - - static CompletableFuture futureError() { - return failedFuture(new IllegalStateException("Error")); - } -} diff --git a/src/test/java/org/dataloader/JsonObject.java b/src/test/java/org/dataloader/fixtures/JsonObject.java similarity index 72% rename from src/test/java/org/dataloader/JsonObject.java rename to src/test/java/org/dataloader/fixtures/JsonObject.java index 7509aec..525793c 100644 --- a/src/test/java/org/dataloader/JsonObject.java +++ b/src/test/java/org/dataloader/fixtures/JsonObject.java @@ -1,14 +1,14 @@ -package org.dataloader; +package org.dataloader.fixtures; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; -class JsonObject { +public class JsonObject { private final Map values; - JsonObject() { + public JsonObject() { values = new LinkedHashMap<>(); } @@ -19,8 +19,12 @@ public JsonObject put(String key, Object value) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } JsonObject that = (JsonObject) o; diff --git a/src/test/java/org/dataloader/fixtures/TestKit.java b/src/test/java/org/dataloader/fixtures/TestKit.java new file mode 100644 index 0000000..1242114 --- /dev/null +++ b/src/test/java/org/dataloader/fixtures/TestKit.java @@ -0,0 +1,61 @@ +package org.dataloader.fixtures; + +import org.dataloader.BatchLoader; +import org.dataloader.DataLoader; +import org.dataloader.DataLoaderFactory; +import org.dataloader.DataLoaderOptions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import static org.dataloader.impl.CompletableFutureKit.failedFuture; + +public class TestKit { + + public static BatchLoader keysAsValues() { + return CompletableFuture::completedFuture; + } + + public static BatchLoader keysAsValues(List> loadCalls) { + return keys -> { + List ks = new ArrayList<>(keys); + loadCalls.add(ks); + @SuppressWarnings("unchecked") + List values = keys.stream() + .map(k -> (V) k) + .collect(Collectors.toList()); + return CompletableFuture.completedFuture(values); + }; + } + + public static DataLoader idLoader(List> loadCalls) { + return idLoader(null, loadCalls); + } + + public static DataLoader idLoader(DataLoaderOptions options, List> loadCalls) { + return DataLoaderFactory.newDataLoader(keysAsValues(loadCalls), options); + } + + public static Collection listFrom(int i, int max) { + List ints = new ArrayList<>(); + for (int j = i; j < max; j++) { + ints.add(j); + } + return ints; + } + + public static CompletableFuture futureError() { + return failedFuture(new IllegalStateException("Error")); + } + + public static void snooze(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/dataloader/fixtures/TestingClock.java b/src/test/java/org/dataloader/fixtures/TestingClock.java new file mode 100644 index 0000000..6c29526 --- /dev/null +++ b/src/test/java/org/dataloader/fixtures/TestingClock.java @@ -0,0 +1,38 @@ +package org.dataloader.fixtures; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; + +/** + * A mutable (but time fixed) clock that can jump forward or back in time + */ +public class TestingClock extends Clock { + + private Clock clock; + + public TestingClock() { + clock = Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault()); + } + + public Clock jump(int millisDelta) { + clock = Clock.offset(clock, Duration.ofMillis(millisDelta)); + return clock; + } + + @Override + public ZoneId getZone() { + return clock.getZone(); + } + + @Override + public Clock withZone(ZoneId zone) { + return clock.withZone(zone); + } + + @Override + public Instant instant() { + return clock.instant(); + } +}