Skip to content

Commit bab2fcd

Browse files
runningcodeclaude
andcommitted
test(checkin): Pin the check-in duration to the options ticker (JAVA-576)
The duration was measured off System.nanoTime() directly, so a test could only assert it was some nonzero float. Now that the clock arrives through SentryOptions.getMonotonicTicker(), a test can hand the check-in path a ticker it controls and assert an exact value. Two paths are worth pinning: the callable returning normally, where only the terminal check-in carries a duration and the IN_PROGRESS one does not, and the callable throwing, where the finally block still has to report the time that passed. Both fail against the pre-refactor code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0b7e6e7 commit bab2fcd

1 file changed

Lines changed: 68 additions & 0 deletions

File tree

‎sentry/src/test/java/io/sentry/util/CheckInUtilsTest.kt‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.util
22

33
import com.google.common.truth.Truth.assertThat
4+
import io.sentry.CheckIn
45
import io.sentry.CheckInStatus
56
import io.sentry.FilterString
67
import io.sentry.IScopes
@@ -10,18 +11,26 @@ import io.sentry.MonitorSchedule
1011
import io.sentry.MonitorScheduleUnit
1112
import io.sentry.Sentry
1213
import io.sentry.SentryOptions
14+
import io.sentry.time.MonotonicTicker
15+
import io.sentry.time.TestMonotonicTicker
1316
import java.lang.AssertionError
1417
import java.lang.RuntimeException
18+
import java.util.concurrent.TimeUnit.MILLISECONDS
19+
import java.util.concurrent.TimeUnit.SECONDS
1520
import kotlin.test.Test
1621
import kotlin.test.assertEquals
22+
import kotlin.test.assertFailsWith
1723
import kotlin.test.assertFalse
1824
import kotlin.test.assertSame
1925
import kotlin.test.assertTrue
2026
import org.mockito.Mockito
2127
import org.mockito.kotlin.any
28+
import org.mockito.kotlin.argumentCaptor
2229
import org.mockito.kotlin.check
2330
import org.mockito.kotlin.inOrder
2431
import org.mockito.kotlin.mock
32+
import org.mockito.kotlin.times
33+
import org.mockito.kotlin.verify
2534
import org.mockito.kotlin.whenever
2635

2736
class CheckInUtilsTest {
@@ -329,4 +338,63 @@ class CheckInUtilsTest {
329338
assertThat(tick).isAtLeast(before)
330339
assertThat(tick).isAtMost(after)
331340
}
341+
342+
@Test
343+
fun `reports the duration the ticker advanced by`() {
344+
val ticker = TestMonotonicTicker()
345+
346+
val checkIns =
347+
captureCheckIns(ticker) {
348+
CheckInUtils.withCheckIn("monitor-1") { ticker.advance(1500, MILLISECONDS) }
349+
}
350+
351+
assertThat(checkIns.map { it.status })
352+
.containsExactly(CheckInStatus.IN_PROGRESS.apiName(), CheckInStatus.OK.apiName())
353+
.inOrder()
354+
assertThat(checkIns.first().duration).isNull()
355+
assertThat(checkIns.last().duration).isEqualTo(1.5)
356+
}
357+
358+
@Test
359+
fun `reports the duration when the callable throws`() {
360+
val ticker = TestMonotonicTicker()
361+
362+
val checkIns =
363+
captureCheckIns(ticker) {
364+
assertFailsWith<RuntimeException> {
365+
CheckInUtils.withCheckIn("monitor-1") {
366+
ticker.advance(2, SECONDS)
367+
throw RuntimeException("thrown on purpose")
368+
}
369+
}
370+
}
371+
372+
assertThat(checkIns.last().status).isEqualTo(CheckInStatus.ERROR.apiName())
373+
assertThat(checkIns.last().duration).isEqualTo(2.0)
374+
}
375+
376+
/**
377+
* Runs [block] against scopes whose options tick on [ticker], and returns every check-in it
378+
* captured, in order.
379+
*/
380+
private fun captureCheckIns(ticker: MonotonicTicker, block: () -> Unit): List<CheckIn> {
381+
Mockito.mockStatic(Sentry::class.java).use { sentry ->
382+
val scopes = mock<IScopes>()
383+
val options =
384+
object : SentryOptions() {
385+
override fun getMonotonicTicker(): MonotonicTicker = ticker
386+
}
387+
sentry.`when`<Any> { Sentry.getCurrentScopes() }.thenReturn(scopes)
388+
sentry.`when`<Any> { Sentry.forkedScopes(any()) }.then { scopes.forkedScopes("test") }
389+
whenever(scopes.forkedScopes(any())).thenReturn(scopes)
390+
whenever(scopes.makeCurrent()).thenReturn(mock<ISentryLifecycleToken>())
391+
whenever(scopes.options).thenReturn(options)
392+
393+
block()
394+
395+
val captor = argumentCaptor<CheckIn>()
396+
verify(scopes, times(2)).captureCheckIn(captor.capture())
397+
return captor.allValues
398+
}
399+
}
332400
}

0 commit comments

Comments
 (0)