mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
test(sidecar): pin that a slow read counts toward the stability streak
This commit is contained in:
1 parent
9f1dfe27ba
commit
b1bf39bac5
2 files changed
+56
-7
No files matched your search
@@ -88,8 +88,14 @@ internal const val TRANSITION_POLL_INTERVAL_MILLIS = 100L
|
||||
// NavHost cross-fade is a 700ms tween (Compose navigation's default enter and
|
||||
// exit), it starts when the action lands rather than when the snapshot begins,
|
||||
// and the streak above has to fit after it. Measured on the emulator, a 600ms
|
||||
// cap left about a third of fades unfinished. A layout that holds two routes at
|
||||
// rest costs the full cap once per step and no more.
|
||||
// cap left about a third of fades unfinished.
|
||||
//
|
||||
// A layout that holds two routes at rest pays the full cap on every snapshot
|
||||
// RPC it is read with, and the runner issues more than one: fetchSyncedState
|
||||
// (internal/runner) re-fetches a tree it considers transitional up to 4 times.
|
||||
// It stops early once two consecutive fetches come back byte-identical, so such
|
||||
// a layout costs 2 caps on a still tree and up to 4 on one that jitters
|
||||
// underneath. Per step, not once per step.
|
||||
internal const val TRANSITION_POLL_CAP_MILLIS = 1500L
|
||||
|
||||
// awaitSettledTree reads the hierarchy and, while the tree it gets back holds
|
||||
@@ -123,11 +129,14 @@ internal fun awaitSettledTree(read: () -> String): String {
|
||||
// state stable.
|
||||
//
|
||||
// The streak is measured from the start of the read that opened the current
|
||||
// run of identical snapshots, not from when that read returned: a hierarchy
|
||||
// fetch is not instantaneous, and the UI changing mid-fetch would have changed
|
||||
// the snapshot, so the fetch's own duration is evidence of stability. On
|
||||
// Android a fetch costs more than the poll interval, so charging it to the
|
||||
// streak is the difference between two reads and four.
|
||||
// run of identical snapshots, not from when that read returned, so a slow read
|
||||
// is charged to the streak. That is a deliberate trade and not a free one: the
|
||||
// quiet the poll actually OBSERVED spans the last read's start back to the
|
||||
// first read's return, which is shorter than streakMillis by up to the two
|
||||
// reads' durations. On Android a hierarchy fetch costs more than the poll
|
||||
// interval, so charging it is the difference between two reads and four, and a
|
||||
// caller wanting the full streak observed has to widen streakMillis rather than
|
||||
// assume it. StabilityPollTest.slowSnapshotReadsCountTowardTheStreak pins this.
|
||||
internal fun pollUntilStable(
|
||||
timeoutMillis: Long,
|
||||
streakMillis: Long = MIN_STABLE_STREAK_MILLIS,
|
||||
|
||||
@@ -16,6 +16,46 @@ class StabilityPollTest {
|
||||
assertTrue(elapsed < 3000L, "should not run to cap when stable, elapsed=${elapsed}ms")
|
||||
}
|
||||
|
||||
@Test fun slowSnapshotReadsCountTowardTheStreak() {
|
||||
// The streak runs from the START of the read that opened the run of
|
||||
// identical snapshots, so the read's own duration is charged to it.
|
||||
// Every other test here uses an instant lambda and so passes under
|
||||
// either semantics; this one is the difference. StubDriverBackend's
|
||||
// waitForIdle polls a real `uiautomator dump`, which costs hundreds of
|
||||
// milliseconds, so the slow read is the case it runs in.
|
||||
val readMillis = 400L
|
||||
val sampleStarts = mutableListOf<Long>()
|
||||
val sampleEnds = mutableListOf<Long>()
|
||||
val start = System.currentTimeMillis()
|
||||
pollUntilStable(5000L) {
|
||||
sampleStarts += System.currentTimeMillis() - start
|
||||
Thread.sleep(readMillis)
|
||||
sampleEnds += System.currentTimeMillis() - start
|
||||
"stable"
|
||||
}
|
||||
val elapsed = System.currentTimeMillis() - start
|
||||
|
||||
// One read plus one interval plus one read already clears 750ms, so the
|
||||
// poll settles for two samples where an instant read takes four.
|
||||
assertEquals(
|
||||
2,
|
||||
sampleStarts.size,
|
||||
"a ${readMillis}ms read should clear the streak in two samples, starts=$sampleStarts",
|
||||
)
|
||||
assertTrue(elapsed >= MIN_STABLE_STREAK_MILLIS, "elapsed=${elapsed}ms")
|
||||
|
||||
// What the poll actually watched: the last read began this long after
|
||||
// the first one returned. It is the poll interval, not the streak, and
|
||||
// a caller that needs MIN_STABLE_STREAK_MILLIS of observed quiet has to
|
||||
// ask for a wider streak rather than assume this one delivers it.
|
||||
val observedQuiet = sampleStarts.last() - sampleEnds.first()
|
||||
assertTrue(
|
||||
observedQuiet < MIN_STABLE_STREAK_MILLIS,
|
||||
"the poll returned having observed ${observedQuiet}ms of quiet, not " +
|
||||
"${MIN_STABLE_STREAK_MILLIS}ms; if that changed, the streak semantics changed",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun streakResetsOnAnyChange() {
|
||||
// A late transition that fires after the prior streak has already
|
||||
// begun must reset the clock: the post-transition stable window has
|
||||
|
||||
Reference in new issue
Block a user