diff --git a/date-time/src/main/java/com/pkb/common/datetime/AutoAdvanceFakeDateTimeService.java b/date-time/src/main/java/com/pkb/common/datetime/AutoAdvanceFakeDateTimeService.java new file mode 100644 index 00000000..ab66c054 --- /dev/null +++ b/date-time/src/main/java/com/pkb/common/datetime/AutoAdvanceFakeDateTimeService.java @@ -0,0 +1,28 @@ +package com.pkb.common.datetime; + +import java.time.Clock; +import java.time.temporal.TemporalAmount; + +/** + * Override of FakeDateTimeService that supports automatically advancing + * time by a fixed amount each time the services is asked for a time. + * DO NOT USE in E2E under any circumstances, it will cause extreme + * flakiness. This is intended for use in unit and integration tests only + * for classes/subsystems that use temporal ordering. + */ +public class AutoAdvanceFakeDateTimeService extends FakeDateTimeService { + + private TemporalAmount autoAdvanceDuration = null; + + @Override + public Clock clock() { + if (this.currentFixedClock != null && autoAdvanceDuration != null) { + moveTime(autoAdvanceDuration); + } + return super.clock(); + } + + public void setAutoAdvanceDuration(TemporalAmount autoAdvanceDuration) { + this.autoAdvanceDuration = autoAdvanceDuration; + } +} diff --git a/date-time/src/main/java/com/pkb/common/datetime/DateTimeService.java b/date-time/src/main/java/com/pkb/common/datetime/DateTimeService.java index 50d1f61d..ac9e25bb 100644 --- a/date-time/src/main/java/com/pkb/common/datetime/DateTimeService.java +++ b/date-time/src/main/java/com/pkb/common/datetime/DateTimeService.java @@ -1,5 +1,7 @@ package com.pkb.common.datetime; +import io.vavr.Tuple2; + import java.text.ParsePosition; import java.time.Clock; import java.time.Instant; @@ -12,11 +14,10 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalUnit; import java.util.Date; -import io.vavr.Tuple2; - @SuppressWarnings({ "UseOfObsoleteDateTimeApi", "SSBasedInspection" }) public interface DateTimeService { @@ -33,11 +34,12 @@ public interface DateTimeService { void moveTime(long amountToAdd, TemporalUnit unit); + void moveTime(TemporalAmount duration); + long nowNanoTime(); /** - * @throws IllegalStateException - * outside of testing environments + * @throws IllegalStateException outside of testing environments */ void forgetFixedCurrentTimeForTesting(); @@ -88,9 +90,6 @@ default ZonedDateTime firstDayOfMonth() { /** * DO NOT USE THIS! Only here temporarily to fix up some legacy (static) code. - * @param input - * @param formatter - * @return */ static Tuple2 parseToInstantBackwardCompatibleWayStatic(String input, DateTimeFormatter formatter){ ParsePosition remainder = new ParsePosition(0); @@ -103,9 +102,6 @@ default Tuple2 parseToInstantBackwardCompatibleWay(Strin /** * @deprecated Use {@link java.time} instead of {@link Date}, then there is no need for this method. - * @param input - * @param formatter - * @return */ @Deprecated default String dateToString(Date input, DateTimeFormatter formatter) { diff --git a/date-time/src/main/java/com/pkb/common/datetime/DefaultDateTimeService.java b/date-time/src/main/java/com/pkb/common/datetime/DefaultDateTimeService.java index 14e8f20d..4ae09b32 100644 --- a/date-time/src/main/java/com/pkb/common/datetime/DefaultDateTimeService.java +++ b/date-time/src/main/java/com/pkb/common/datetime/DefaultDateTimeService.java @@ -1,6 +1,7 @@ package com.pkb.common.datetime; import java.time.Clock; +import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalUnit; public class DefaultDateTimeService implements DateTimeService { @@ -20,6 +21,11 @@ public void moveTime(long amountToAdd, TemporalUnit unit) { throw new IllegalStateException("Not currently in a test environment"); } + @Override + public void moveTime(TemporalAmount duration) { + throw new IllegalStateException("Not currently in a test environment"); + } + @Override public long nowNanoTime() { return System.nanoTime(); diff --git a/date-time/src/main/java/com/pkb/common/datetime/FakeDateTimeService.java b/date-time/src/main/java/com/pkb/common/datetime/FakeDateTimeService.java index 132bfe50..95a207de 100644 --- a/date-time/src/main/java/com/pkb/common/datetime/FakeDateTimeService.java +++ b/date-time/src/main/java/com/pkb/common/datetime/FakeDateTimeService.java @@ -1,20 +1,21 @@ package com.pkb.common.datetime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.lang.invoke.MethodHandles; import java.time.Clock; import java.time.Instant; import java.time.ZonedDateTime; +import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalUnit; import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class FakeDateTimeService implements DateTimeService { private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private volatile ZonedDateTime currentFixedTime; - private volatile Clock currentFixedClock; + protected volatile ZonedDateTime currentFixedTime; + protected volatile Clock currentFixedClock; private final DateTimeService fallbackService; @@ -44,6 +45,11 @@ public void moveTime(long amountToAdd, TemporalUnit unit) { fixTime(currentFixedTime.plus(amountToAdd, unit)); } + @Override + public void moveTime(TemporalAmount duration) { + fixTime(currentFixedTime.plus(duration)); + } + @Override public long nowNanoTime() { Instant now = now(); diff --git a/date-time/src/test/java/com/pkb/common/datetime/AutoAdvanceFakeDateTimeServiceTest.java b/date-time/src/test/java/com/pkb/common/datetime/AutoAdvanceFakeDateTimeServiceTest.java new file mode 100644 index 00000000..72d91c42 --- /dev/null +++ b/date-time/src/test/java/com/pkb/common/datetime/AutoAdvanceFakeDateTimeServiceTest.java @@ -0,0 +1,51 @@ +package com.pkb.common.datetime; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.not; + +class AutoAdvanceFakeDateTimeServiceTest { + + private AutoAdvanceFakeDateTimeService underTest = new AutoAdvanceFakeDateTimeService(); + + @Test + void testAdvancesTimeBetweenCalls() { + underTest.setFixedCurrentTimeForTesting("2020-01-01T00:00:00Z"); + underTest.setAutoAdvanceDuration(Duration.of(1, ChronoUnit.DAYS)); + + assertThat(underTest.now(), equalTo(Instant.parse("2020-01-02T00:00:00Z"))); + assertThat(underTest.now(), equalTo(Instant.parse("2020-01-03T00:00:00Z"))); + } + + @Test + void testStopsAdvancingWhenCleared() { + underTest.setFixedCurrentTimeForTesting("2020-01-01T00:00:00Z"); + underTest.setAutoAdvanceDuration(Duration.of(1, ChronoUnit.DAYS)); + + assertThat(underTest.now(), equalTo(Instant.parse("2020-01-02T00:00:00Z"))); + underTest.setAutoAdvanceDuration(null); + assertThat(underTest.now(), equalTo(Instant.parse("2020-01-02T00:00:00Z"))); + } + + @Test + void testDoesNothingIfTimeNotFixed() throws InterruptedException { + + underTest.setAutoAdvanceDuration(Duration.of(1, ChronoUnit.DAYS)); + + Instant start = underTest.now(); + Thread.sleep(1); + Instant end = underTest.now(); + + + assertThat(start, not(end)); + assertThat(Duration.between(start, end), lessThan(Duration.of(1, ChronoUnit.SECONDS))); + } + +} diff --git a/testsupport/src/main/java/com/pkb/common/testsupport/camel/route/AbstractTestSupportCamelRouteBuilder.java b/testsupport/src/main/java/com/pkb/common/testsupport/camel/route/AbstractTestSupportCamelRouteBuilder.java index 94a58298..41686f43 100644 --- a/testsupport/src/main/java/com/pkb/common/testsupport/camel/route/AbstractTestSupportCamelRouteBuilder.java +++ b/testsupport/src/main/java/com/pkb/common/testsupport/camel/route/AbstractTestSupportCamelRouteBuilder.java @@ -81,6 +81,16 @@ public void configure() throws Exception { if (config().getShouldStartListener()) { from(testControlRequestSubscription) .threads(1) //One thread is enough + // We really want to just enable timeout, we don't need circuit breaker in e2e + .circuitBreaker() + .faultToleranceConfiguration() + .timeoutEnabled(config().testControlTimeoutEnabled()) + .timeoutDuration(config().testControlTimeoutMillis()) + .delay(config().testControlHandlerDelayMillis()) + .requestVolumeThreshold(config().testControlHandlerRequestVolumeThreshold()) + .failureRatio(config().testControlHandlerFailureRatio()) + .successThreshold(config().testControlHandlerSuccessThreshold()) + .end() .routeId("testSupportReceiver") .unmarshal().avro(TestControlRequest.getClassSchema()) .log(config().getApplicationName() + ": receiving a ${mandatoryBodyAs(" + TestControlRequest.class.getCanonicalName() + ").getMessageType} request") diff --git a/testsupport/src/main/java/com/pkb/common/testsupport/config/ITestControlServiceConfig.java b/testsupport/src/main/java/com/pkb/common/testsupport/config/ITestControlServiceConfig.java index 71a74e1c..f22110d5 100644 --- a/testsupport/src/main/java/com/pkb/common/testsupport/config/ITestControlServiceConfig.java +++ b/testsupport/src/main/java/com/pkb/common/testsupport/config/ITestControlServiceConfig.java @@ -39,7 +39,7 @@ public interface ITestControlServiceConfig { boolean getShouldStartListener(); DateTimeService getDateTimeService(); - + ConfigStorage getConfigStorage(); Set getClearables(); @@ -47,4 +47,28 @@ public interface ITestControlServiceConfig { DetailLoggingProvider getTestLoggingService(); PubSubNamespaceService getNamespaceService(); + + default boolean testControlTimeoutEnabled() { + return true; + } + + default long testControlTimeoutMillis() { + return 30000; + } + + default long testControlHandlerDelayMillis() { + return 0; + } + + default int testControlHandlerRequestVolumeThreshold() { + return 1000; + } + + default int testControlHandlerFailureRatio() { + return 100; + } + + default int testControlHandlerSuccessThreshold() { + return 100; + } }