From 3a029b8e80b98930b64894831d77f86089c04dc4 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 30 Jun 2022 02:09:37 +0300 Subject: [PATCH] Fix ValueTimeMark adjustment in rare cases and properly test it --- .../src/kotlin/time/longSaturatedMath.kt | 14 ++++++++----- libraries/stdlib/test/time/TimeMarkTest.kt | 20 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt b/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt index cb526891001..c04782d8e9d 100644 --- a/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt +++ b/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt @@ -12,10 +12,10 @@ import kotlin.time.Duration.Companion.nanoseconds internal fun saturatingAdd(longNs: Long, duration: Duration): Long { val durationNs = duration.inWholeNanoseconds - if ((longNs - 1) or 1 == Long.MAX_VALUE) { // MIN_VALUE or MAX_VALUE - the reading is infinite + if (longNs.isSaturated()) { // MIN_VALUE or MAX_VALUE - the reading is infinite return checkInfiniteSumDefined(longNs, duration, durationNs) } - if ((durationNs - 1) or 1 == Long.MAX_VALUE) { // duration doesn't fit in Long nanos + if (durationNs.isSaturated()) { // duration doesn't fit in Long nanos return saturatingAddInHalves(longNs, duration) } @@ -33,16 +33,16 @@ private fun checkInfiniteSumDefined(longNs: Long, duration: Duration, durationNs private fun saturatingAddInHalves(longNs: Long, duration: Duration): Long { val half = duration / 2 - if ((half.inWholeNanoseconds - 1) or 1 == Long.MAX_VALUE) { + if (half.inWholeNanoseconds.isSaturated()) { // this will definitely saturate return (longNs + duration.toDouble(DurationUnit.NANOSECONDS)).toLong() } else { - return saturatingAdd(saturatingAdd(longNs, half), half) + return saturatingAdd(saturatingAdd(longNs, half), duration - half) } } internal fun saturatingDiff(valueNs: Long, originNs: Long): Duration { - if ((originNs - 1) or 1 == Long.MAX_VALUE) { // MIN_VALUE or MAX_VALUE + if (originNs.isSaturated()) { // MIN_VALUE or MAX_VALUE return -(originNs.toDuration(DurationUnit.DAYS)) // saturate to infinity } val result = valueNs - originNs @@ -53,3 +53,7 @@ internal fun saturatingDiff(valueNs: Long, originNs: Long): Duration { } return result.nanoseconds } + +@Suppress("NOTHING_TO_INLINE") +private inline fun Long.isSaturated(): Boolean = + (this - 1) or 1 == Long.MAX_VALUE // == either MAX_VALUE or MIN_VALUE diff --git a/libraries/stdlib/test/time/TimeMarkTest.kt b/libraries/stdlib/test/time/TimeMarkTest.kt index 3a740595f69..6dd650f2656 100644 --- a/libraries/stdlib/test/time/TimeMarkTest.kt +++ b/libraries/stdlib/test/time/TimeMarkTest.kt @@ -75,13 +75,15 @@ class TimeMarkTest { val longDuration = Long.MAX_VALUE.nanoseconds - val long2Duration = longDuration * 2 + val long2Duration = longDuration + 1001.milliseconds val pastMark = baseMark - longDuration val futureMark = pastMark + long2Duration - val sameMark = futureMark - longDuration - val elapsedDiff = sameMark.elapsedNow() - baseMark.elapsedNow() - assertTrue(elapsedDiff < 1.milliseconds, "$elapsedDiff") + val sameMark = futureMark - (long2Duration - longDuration) + + val elapsedDiff = (sameMark.elapsedNow() - baseMark.elapsedNow()).absoluteValue + val elapsedDiff2 = (baseMark.elapsedNow() - sameMark.elapsedNow()).absoluteValue + assertTrue(maxOf(elapsedDiff, elapsedDiff2) < 1.milliseconds, "$elapsedDiff, $elapsedDiff2") } @Test @@ -144,12 +146,14 @@ class TimeMarkTest { assertFailsWith { infinitePastMark + Duration.INFINITE } val longDuration = Long.MAX_VALUE.nanoseconds - val long2Duration = longDuration * 2 + val long2Duration = longDuration + 1001.milliseconds val pastMark = baseMark - longDuration val futureMark = pastMark + long2Duration - val sameMark = futureMark - longDuration - val elapsedDiff = sameMark.elapsedNow() - baseMark.elapsedNow() - assertTrue(elapsedDiff < 1.milliseconds, "$elapsedDiff") + val sameMark = futureMark - (long2Duration - longDuration) + + val elapsedDiff = (sameMark.elapsedNow() - baseMark.elapsedNow()).absoluteValue + val elapsedDiff2 = (baseMark.elapsedNow() - sameMark.elapsedNow()).absoluteValue + assertTrue(maxOf(elapsedDiff, elapsedDiff2) < 1.milliseconds, "$elapsedDiff, $elapsedDiff2") } }