Introduce TestClock.plusAssign(Duration) and hide implementation details

TestClock.plusAssign(Duration) is for advancing TestClock time.
Hide reading value, do not allow to provide initial reading,
fix clock unit to nanoseconds at construction time.
This commit is contained in:
Ilya Gorbunov
2019-08-08 22:40:36 +03:00
parent 4de9361c37
commit f889d25287
5 changed files with 111 additions and 20 deletions
+41 -10
View File
@@ -5,8 +5,6 @@
package kotlin.time package kotlin.time
import kotlin.js.JsName
/** /**
* The most precise clock available in the platform. * The most precise clock available in the platform.
* *
@@ -64,19 +62,52 @@ public abstract class AbstractDoubleClock(protected val unit: DurationUnit) : Cl
/** /**
* A clock that has programmatically updatable readings. It is useful as a predictable source of time in tests. * A clock that has programmatically updatable readings. It is useful as a predictable source of time in tests.
* *
* @param reading The initial value of the clock reading. * The current clock reading value can be advanced by the specified duration amount with the operator [plusAssign]:
* @param unit The unit of time in which [reading] value is expressed.
* *
* @property reading Gets or sets this clock's current reading value. * ```
* val clock = TestClock()
* clock += 10.seconds
* ```
*
* Implementation note: the current clock reading value is stored as a [Long] number of nanoseconds,
* thus it's capable to represent a time range of approximately ±292 years.
* Should the reading value overflow as the result of [plusAssign] operation, an [IllegalStateException] is thrown.
*/ */
@SinceKotlin("1.3") @SinceKotlin("1.3")
@ExperimentalTime @ExperimentalTime
public class TestClock( public class TestClock : AbstractLongClock(unit = DurationUnit.NANOSECONDS) {
@JsName("readingValue") private var reading: Long = 0L
public var reading: Long = 0L,
unit: DurationUnit = DurationUnit.NANOSECONDS
) : AbstractLongClock(unit) {
override fun read(): Long = reading override fun read(): Long = reading
/**
* Advances the current reading value of this clock by the specified [duration].
*
* [duration] value is rounded down towards zero when converting it to a [Long] number of nanoseconds.
* For example, if the duration being added is `0.6.nanoseconds`, the clock reading won't advance because
* the duration value will be rounded to zero nanoseconds.
*
* @throws IllegalStateException when the reading value overflows as the result of this operation.
*/
public operator fun plusAssign(duration: Duration) {
val delta = duration.toDouble(unit)
val longDelta = delta.toLong()
reading = if (longDelta != Long.MIN_VALUE && longDelta != Long.MAX_VALUE) {
// when delta fits in long, add it as long
val newReading = reading + longDelta
if (reading xor longDelta >= 0 && reading xor newReading < 0) overflow(duration)
newReading
} else {
// when delta is greater than long, add it as double
val newReading = reading + delta
if (newReading > Long.MAX_VALUE || newReading < Long.MIN_VALUE) overflow(duration)
newReading.toLong()
}
}
private fun overflow(duration: Duration) {
throw IllegalStateException("TestClock will overflow if its reading ${reading}ns is advanced by $duration.")
}
} }
/* /*
+2 -2
View File
@@ -12,7 +12,7 @@ class ClockMarkTest {
@Test @Test
fun adjustment() { fun adjustment() {
val clock = TestClock(unit = DurationUnit.NANOSECONDS) val clock = TestClock()
val mark = clock.mark() val mark = clock.mark()
val markFuture1 = mark + 1.milliseconds val markFuture1 = mark + 1.milliseconds
@@ -21,7 +21,7 @@ class ClockMarkTest {
val markPast1 = mark - 1.milliseconds val markPast1 = mark - 1.milliseconds
val markPast2 = markFuture1 + (-2).milliseconds val markPast2 = markFuture1 + (-2).milliseconds
clock.reading = 500_000L clock += 500_000.nanoseconds
val elapsed = mark.elapsed() val elapsed = mark.elapsed()
val elapsedFromFuture = elapsed - 1.milliseconds val elapsedFromFuture = elapsed - 1.milliseconds
@@ -47,11 +47,12 @@ class MeasureTimeTest {
} }
@Test fun measureTimeTestClock() { @Test
val clock = TestClock(unit = DurationUnit.NANOSECONDS) fun measureTimeTestClock() {
val clock = TestClock()
val expectedNs = Random.nextLong(1_000_000_000L) val expectedNs = Random.nextLong(1_000_000_000L)
val elapsed = clock.measureTime { val elapsed = clock.measureTime {
clock.reading += expectedNs clock += expectedNs.nanoseconds
} }
assertEquals(expectedNs.nanoseconds, elapsed) assertEquals(expectedNs.nanoseconds, elapsed)
@@ -59,7 +60,7 @@ class MeasureTimeTest {
val expectedResult: Long val expectedResult: Long
val (result, elapsed2) = clock.measureTimedValue { val (result, elapsed2) = clock.measureTimedValue {
clock.reading += expectedNs clock += expectedNs.nanoseconds
expectedResult = expectedNs expectedResult = expectedNs
expectedNs expectedNs
} }
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:UseExperimental(ExperimentalTime::class)
package test.time
import kotlin.test.*
import kotlin.time.*
class TestClockTest {
@Test
fun overflows() {
for (enormousDuration in listOf(Duration.INFINITE, Double.MAX_VALUE.nanoseconds, Long.MAX_VALUE.nanoseconds * 2)) {
assertFailsWith<IllegalStateException>(enormousDuration.toString()) { TestClock() += enormousDuration }
assertFailsWith<IllegalStateException>((-enormousDuration).toString()) { TestClock() += -enormousDuration }
}
val moderatePositiveDuration = Long.MAX_VALUE.takeHighestOneBit().nanoseconds
val borderlinePositiveDuration = Long.MAX_VALUE.nanoseconds // rounded to 2.0^63, which is slightly more than Long.MAX_VALUE
val borderlineNegativeDuration = Long.MIN_VALUE.nanoseconds
run {
val clock = TestClock()
clock += moderatePositiveDuration
assertFailsWith<IllegalStateException>("Should overflow positive") { clock += moderatePositiveDuration }
}
run {
val clock = TestClock()
clock += borderlinePositiveDuration
assertFailsWith<IllegalStateException>("Should overflow positive") { clock += 1.nanoseconds }
}
run {
val clock = TestClock()
clock += borderlineNegativeDuration
assertFailsWith<IllegalStateException>("Should overflow negative") { clock += -1.nanoseconds }
}
run {
val clock = TestClock()
clock += moderatePositiveDuration
// does not overflow event if duration doesn't fit in long
clock += -moderatePositiveDuration + borderlineNegativeDuration
}
}
@Test
fun nanosecondRounding() {
val clock = TestClock()
val mark = clock.mark()
repeat(10_000) {
clock += 0.9.nanoseconds
assertEquals(Duration.ZERO, mark.elapsed())
}
clock += 1.9.nanoseconds
assertEquals(1.nanoseconds, mark.elapsed())
}
}
@@ -5390,10 +5390,7 @@ public final class kotlin/time/MonoClock : kotlin/time/AbstractLongClock, kotlin
public final class kotlin/time/TestClock : kotlin/time/AbstractLongClock { public final class kotlin/time/TestClock : kotlin/time/AbstractLongClock {
public fun <init> ()V public fun <init> ()V
public fun <init> (JLjava/util/concurrent/TimeUnit;)V public final fun plusAssign-LRDsOJo (D)V
public synthetic fun <init> (JLjava/util/concurrent/TimeUnit;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getReading ()J
public final fun setReading (J)V
} }
public final class kotlin/time/TimedValue { public final class kotlin/time/TimedValue {