Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c9cd8b1 authored by Mike Schneider's avatar Mike Schneider
Browse files

use `runMonotonicClockTest` in `ComposeMotionValueToolkit`

- This significantly speeds up tests, compared to launching a compose
  activity for every test.
- The duplicate last frame is removed, requiring a golden update
- Compose and View based versions do now create the exact same goldens.

Test: Existing unit test
Flag: EXEMPT TEST_ONLY
Bug: 409930448
Change-Id: Ia416f9528254073fe61906762adbccabd73b7608
parent 3c812619
Loading
Loading
Loading
Loading
+69 −71
Original line number Diff line number Diff line
@@ -18,16 +18,16 @@

package com.android.mechanics.testing

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.runtime.snapshots.Snapshot
import com.android.mechanics.DistanceGestureContext
import com.android.mechanics.MotionValue
import com.android.mechanics.debug.FrameData
import com.android.mechanics.spec.InputDirection
import com.android.mechanics.spec.MotionSpec
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -36,16 +36,15 @@ import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.runBlocking
import platform.test.motion.MotionTestRule
import platform.test.motion.compose.runMonotonicClockTest
import platform.test.motion.golden.FrameId
import platform.test.motion.golden.TimeSeries
import platform.test.motion.golden.TimestampFrameId

/** Toolkit to support [MotionValue] motion tests. */
class ComposeMotionValueToolkit(val composeTestRule: ComposeContentTestRule) :
    MotionValueToolkit<MotionValue, DistanceGestureContext>() {
data object ComposeMotionValueToolkit : MotionValueToolkit<MotionValue, DistanceGestureContext>() {

    override fun goldenTest(
        motionTestRule: MotionTestRule<*>,
@@ -58,8 +57,7 @@ class ComposeMotionValueToolkit(val composeTestRule: ComposeContentTestRule) :
        stableThreshold: Float,
        verifyTimeSeries: TimeSeries.() -> VerifyTimeSeriesResult,
        testInput: suspend InputScope<MotionValue, DistanceGestureContext>.() -> Unit,
    ) = runTest {
        with(composeTestRule) {
    ) = runMonotonicClockTest {
        val frameEmitter = MutableStateFlow<Long>(0)

        val testHarness =
@@ -75,23 +73,13 @@ class ComposeMotionValueToolkit(val composeTestRule: ComposeContentTestRule) :
        val underTest = testHarness.underTest
        val derived = testHarness.derived

            val inspectors = buildMap {
                put(underTest, underTest.debugInspector())
                derived.forEach { put(it, it.debugInspector()) }
            }
        val motionValues = derived + underTest

            setContent {
                LaunchedEffect(Unit) {
                    launch { underTest.keepRunning() }
                    derived.forEach { launch { it.keepRunning() } }
                }
            }
        val inspectors = motionValues.map { it to it.debugInspector() }.toMap()
        val keepRunningJobs = motionValues.map { launch { it.keepRunning() } }

        val recordingJob = launch { testInput.invoke(testHarness) }

            waitForIdle()
            mainClock.autoAdvance = false

        val frameIds = mutableListOf<FrameId>()
        val frameData = mutableMapOf<MotionValue, MutableList<FrameData>>()

@@ -101,14 +89,25 @@ class ComposeMotionValueToolkit(val composeTestRule: ComposeContentTestRule) :
                frameData.computeIfAbsent(motionValue) { mutableListOf() }.add(inspector.frame)
            }
        }

            val startFrameTime = mainClock.currentTime
            recordFrame(TimestampFrameId(mainClock.currentTime - startFrameTime))
        runBlocking(Dispatchers.Main) {
            val startFrameTime = testScheduler.currentTime
            while (!recordingJob.isCompleted) {
                frameEmitter.tryEmit(mainClock.currentTime + 16)
                runCurrent()
                mainClock.advanceTimeByFrame()
                recordFrame(TimestampFrameId(mainClock.currentTime - startFrameTime))
                recordFrame(TimestampFrameId(testScheduler.currentTime - startFrameTime))

                // Emulate setting input *before* the frame advances. This ensures the `testInput`
                // coroutine will continue if needed. The specific value for frameEmitter is
                // irrelevant, it only requires to be unique per frame.
                frameEmitter.tryEmit(testScheduler.currentTime)
                testScheduler.runCurrent()
                // Whenever keepRunning was suspended, allow the snapshotFlow to wake up
                Snapshot.sendApplyNotifications()

                // Now advance the test clock
                testScheduler.advanceTimeBy(FrameDuration)
                // Since the tests capture the debugInspector output, make sure keepRunning()
                // was able to complete the frame.
                testScheduler.runCurrent()
            }
        }

        val timeSeries =
@@ -116,8 +115,7 @@ class ComposeMotionValueToolkit(val composeTestRule: ComposeContentTestRule) :
                frameIds,
                frameData.entries
                    .map { (motionValue, frameData) ->
                            val prefix =
                                if (motionValue == underTest) "" else "${motionValue.label}-"
                        val prefix = if (motionValue == underTest) "" else "${motionValue.label}-"
                        prefix to frameData
                    }
                    .sortedBy { it.first },
@@ -125,10 +123,10 @@ class ComposeMotionValueToolkit(val composeTestRule: ComposeContentTestRule) :
            )

        inspectors.values.forEach { it.dispose() }
        keepRunningJobs.forEach { it.cancel() }
        verifyTimeSeries(motionTestRule, timeSeries, verifyTimeSeries)
    }
}
}

private class ComposeMotionValueTestHarness(
    initialInput: Float,
+5 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import com.android.mechanics.spec.SemanticKey
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.sign
import kotlin.time.Duration.Companion.milliseconds
import platform.test.motion.MotionTestRule
import platform.test.motion.RecordedMotion.Companion.create
import platform.test.motion.golden.DataPoint
@@ -217,4 +218,8 @@ sealed class MotionValueToolkit<MotionValueType, GestureContextType> {
            motionTestRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
        }
    }

    companion object {
        val FrameDuration = 16.milliseconds
    }
}
+15 −15
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ class ViewMotionValueToolkit(private val animatorTestRule: AnimatorTestRule) :
                        frameEmitter.asStateFlow(),
                        createDerived,
                    )
                    .also { animatorTestRule.initNewAnimators() }
            }

        val underTest = testHarness.underTest
@@ -89,10 +90,13 @@ class ViewMotionValueToolkit(private val animatorTestRule: AnimatorTestRule) :
        runBlocking(Dispatchers.Main) {
            val startFrameTime = animatorTestRule.currentTime
            while (!recordingJob.isCompleted) {
                frameEmitter.tryEmit(animatorTestRule.currentTime + FrameDurationMs)
                runCurrent()
                recordFrame(TimestampFrameId(animatorTestRule.currentTime - startFrameTime))
                animatorTestRule.advanceTimeBy(FrameDurationMs)

                frameEmitter.tryEmit(animatorTestRule.currentTime)
                runCurrent()

                animatorTestRule.advanceTimeBy(FrameDuration.inWholeMilliseconds)
                runCurrent()
            }

            val timeSeries =
@@ -103,10 +107,6 @@ class ViewMotionValueToolkit(private val animatorTestRule: AnimatorTestRule) :
            verifyTimeSeries(motionTestRule, timeSeries, verifyTimeSeries)
        }
    }

    companion object {
        const val FrameDurationMs = 16L
    }
}

private class ViewMotionValueTestHarness(
+1 −11
Original line number Diff line number Diff line
@@ -14,8 +14,7 @@
    176,
    192,
    208,
    224,
    240
    224
  ],
  "features": [
    {
@@ -36,7 +35,6 @@
        0,
        0,
        0,
        0,
        0
      ]
    },
@@ -58,7 +56,6 @@
        "Min",
        "Min",
        "Min",
        "Min",
        "Min"
      ]
    },
@@ -80,7 +77,6 @@
        0.97079945,
        0.9824491,
        0.98952854,
        1,
        1
      ]
    },
@@ -102,7 +98,6 @@
        1,
        1,
        1,
        1,
        1
      ]
    },
@@ -166,10 +161,6 @@
          "stiffness": 1400,
          "dampingRatio": 1
        },
        {
          "stiffness": 1400,
          "dampingRatio": 1
        },
        {
          "stiffness": 1400,
          "dampingRatio": 1
@@ -194,7 +185,6 @@
        false,
        false,
        false,
        true,
        true
      ]
    }
+2 −12
Original line number Diff line number Diff line
@@ -2,8 +2,7 @@
  "frame_ids": [
    0,
    16,
    32,
    48
    32
  ],
  "features": [
    {
@@ -12,7 +11,6 @@
      "data_points": [
        0,
        0.5,
        1.1,
        1.1
      ]
    },
@@ -20,7 +18,6 @@
      "name": "gestureDirection",
      "type": "string",
      "data_points": [
        "Max",
        "Max",
        "Max",
        "Max"
@@ -32,8 +29,7 @@
      "data_points": [
        0,
        0,
        0.05119291,
        0.095428914
        0.05119291
      ]
    },
    {
@@ -42,7 +38,6 @@
      "data_points": [
        0,
        0,
        0.55,
        0.55
      ]
    },
@@ -58,10 +53,6 @@
          "stiffness": 100000,
          "dampingRatio": 1
        },
        {
          "stiffness": 700,
          "dampingRatio": 0.9
        },
        {
          "stiffness": 700,
          "dampingRatio": 0.9
@@ -74,7 +65,6 @@
      "data_points": [
        true,
        true,
        false,
        false
      ]
    }
Loading