Merge "Deprecate DefaultMonotonicFrameClock" into androidx-main
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/AutoTestFrameClock.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/AutoTestFrameClock.kt
new file mode 100644
index 0000000..b889918
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/AutoTestFrameClock.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import androidx.compose.runtime.MonotonicFrameClock
+import java.util.concurrent.atomic.AtomicLong
+
+class AutoTestFrameClock : MonotonicFrameClock {
+    private val time = AtomicLong(0)
+
+    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+        return onFrame(time.getAndAdd(16_000_000))
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
index 85aab9a..7e2327c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
@@ -522,7 +522,7 @@
 
     @OptIn(ExperimentalTestApi::class)
     @Test
-    fun scroller_coerce_whenScrollSmoothTo() = runBlocking {
+    fun scroller_coerce_whenScrollSmoothTo() = runBlocking(AutoTestFrameClock()) {
         val scrollState = ScrollState(initial = 0)
 
         createScrollableContent(isVertical = true, scrollState = scrollState)
@@ -571,7 +571,7 @@
 
     @OptIn(ExperimentalTestApi::class)
     @Test
-    fun scroller_restoresScrollerPosition() = runBlocking {
+    fun scroller_restoresScrollerPosition() = runBlocking(AutoTestFrameClock()) {
         val restorationTester = StateRestorationTester(rule)
         var scrollState: ScrollState? = null
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index bcc20f7..80001cb 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -392,7 +392,7 @@
 
     @Test
     @OptIn(ExperimentalTestApi::class)
-    fun scrollable_snappingScrolling() = runBlocking {
+    fun scrollable_snappingScrolling() = runBlocking(AutoTestFrameClock()) {
         var total = 0f
         val controller = ScrollableState(
             consumeScrollDelta = {
@@ -735,7 +735,7 @@
 
     @Test
     @OptIn(ExperimentalTestApi::class)
-    fun scrollable_nestedScrollBelow_listensDispatches() = runBlocking {
+    fun scrollable_nestedScrollBelow_listensDispatches() = runBlocking(AutoTestFrameClock()) {
         var value = 0f
         var expectedConsumed = 0f
         val controller = ScrollableState(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
index 247f9e1..f12af2d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
@@ -364,7 +364,7 @@
     }
 
     @Test
-    fun transformable_animateTo_zoom() = runBlocking {
+    fun transformable_animateTo_zoom() = runBlocking(AutoTestFrameClock()) {
         rule.mainClock.autoAdvance = false
         var cumulativeScale = 1.0f
         var callbackCount = 0
@@ -400,7 +400,7 @@
     }
 
     @Test
-    fun transformable_animateTo_rotate() = runBlocking {
+    fun transformable_animateTo_rotate() = runBlocking(AutoTestFrameClock()) {
         rule.mainClock.autoAdvance = false
         var totalRotation = 0f
         var callbackCount = 0
@@ -436,7 +436,7 @@
     }
 
     @Test
-    fun transformable_animateTo_pan() = runBlocking {
+    fun transformable_animateTo_pan() = runBlocking(AutoTestFrameClock()) {
         rule.mainClock.autoAdvance = false
         var totalPan = Offset.Zero
         var callbackCount = 0
@@ -531,7 +531,7 @@
     }
 
     @Test
-    fun transformable_stopTransformations() = runBlocking {
+    fun transformable_stopTransformations() = runBlocking(AutoTestFrameClock()) {
         rule.mainClock.autoAdvance = false
         var totalRotation = 0f
         var callbackCount = 0
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
index e9a4d58..3dfc426 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.advanceClockMillis
 import androidx.compose.animation.core.snap
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
@@ -77,6 +78,7 @@
 import com.google.common.truth.IntegerSubject
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
@@ -1308,7 +1310,7 @@
     }
 
     private fun LazyListState.scrollBy(offset: Dp) {
-        runBlocking {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
             animateScrollBy(with(rule.density) { offset.roundToPx().toFloat() }, snap())
         }
     }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt
index dd64524..f319f73 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt
@@ -17,13 +17,14 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.animation.core.snap
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.width
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -37,6 +38,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Before
 import org.junit.Rule
@@ -611,7 +613,7 @@
     }
 
     private fun LazyListState.scrollBy(offset: Dp) {
-        runBlocking {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
             animateScrollBy(with(rule.density) { offset.roundToPx().toFloat() }, snap())
         }
     }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
index c5403a7..99c95cb 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.animation.core.snap
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -54,6 +55,7 @@
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
@@ -1038,7 +1040,7 @@
     }
 
     private fun LazyListState.scrollBy(offset: Dp) {
-        runBlocking {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
             animateScrollBy(with(rule.density) { offset.roundToPx().toFloat() }, snap())
         }
     }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
index 18f6153..689da8c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.layout.Spacer
@@ -66,7 +67,7 @@
 
     @Test
     fun snapToItemTest() = runBlocking {
-        withContext(Dispatchers.Main) {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
             state.scrollToItem(3)
         }
         assertThat(state.firstVisibleItemIndex).isEqualTo(3)
@@ -83,7 +84,7 @@
         val expectedIndex = scrollDistance / itemSize // resolves to 3
         val expectedOffset = scrollDistance % itemSize // resolves to ~17.dp.toIntPx()
 
-        withContext(Dispatchers.Main) {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
             state.animateScrollBy(scrollDistance.toFloat())
         }
         assertThat(state.firstVisibleItemIndex).isEqualTo(expectedIndex)
@@ -92,7 +93,7 @@
 
     @Test
     fun smoothScrollToItemTest() = runBlocking {
-        withContext(Dispatchers.Main) {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
             state.animateScrollToItem(5, 10)
         }
         assertThat(state.firstVisibleItemIndex).isEqualTo(5)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AutoTestFrameClock.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AutoTestFrameClock.kt
new file mode 100644
index 0000000..5691a74
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/AutoTestFrameClock.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.runtime.MonotonicFrameClock
+import java.util.concurrent.atomic.AtomicLong
+
+class AutoTestFrameClock : MonotonicFrameClock {
+    private val time = AtomicLong(0)
+
+    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+        return onFrame(time.getAndAdd(16_000_000))
+    }
+}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
index 9e194c0..d204bf8 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
@@ -215,7 +215,7 @@
 
     @Test
     @LargeTest
-    fun backdropScaffold_revealAndConceal_manually(): Unit = runBlocking {
+    fun backdropScaffold_revealAndConceal_manually(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var scaffoldState: BackdropScaffoldState
         rule.setContent {
             scaffoldState = rememberBackdropScaffoldState(Concealed)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt
index 9516feb..c955f22 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BottomSheetScaffoldTest.kt
@@ -198,7 +198,7 @@
     }
 
     @Test
-    fun backdropScaffold_revealAndConceal_manually(): Unit = runBlocking {
+    fun backdropScaffold_revealAndConceal_manually(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var bottomSheetState: BottomSheetState
         rule.setContent {
             bottomSheetState = rememberBottomSheetState(BottomSheetValue.Collapsed)
@@ -382,7 +382,7 @@
     }
 
     @Test
-    fun bottomSheetScaffold_fab_position(): Unit = runBlocking {
+    fun bottomSheetScaffold_fab_position(): Unit = runBlocking(AutoTestFrameClock()) {
         val fabTag = "fab"
         var fabSize: IntSize = IntSize.Zero
         lateinit var scaffoldState: BottomSheetScaffoldState
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
index 6946763..6f885b0 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
@@ -243,7 +243,7 @@
 
     @Test
     @LargeTest
-    fun modalDrawer_openAndClose(): Unit = runBlocking {
+    fun modalDrawer_openAndClose(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var drawerState: DrawerState
         rule.setMaterialContent {
             drawerState = rememberDrawerState(DrawerValue.Closed)
@@ -274,7 +274,7 @@
 
     @Test
     @LargeTest
-    fun modalDrawer_bodyContent_clickable(): Unit = runBlocking {
+    fun modalDrawer_bodyContent_clickable(): Unit = runBlocking(AutoTestFrameClock()) {
         var drawerClicks = 0
         var bodyClicks = 0
         lateinit var drawerState: DrawerState
@@ -314,7 +314,9 @@
 
     @Test
     @LargeTest
-    fun modalDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking {
+    fun modalDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking(
+        AutoTestFrameClock()
+    ) {
         var bodyClicks = 0
         lateinit var drawerState: DrawerState
         rule.setMaterialContent {
@@ -421,7 +423,9 @@
 
     @Test
     @LargeTest
-    fun modalDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking {
+    fun modalDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking(
+        AutoTestFrameClock()
+    ) {
         lateinit var drawerState: DrawerState
         rule.setMaterialContent {
             drawerState = rememberDrawerState(DrawerValue.Closed)
@@ -457,7 +461,7 @@
     }
 
     @Test
-    fun bottomDrawer_bodyContent_clickable(): Unit = runBlocking {
+    fun bottomDrawer_bodyContent_clickable(): Unit = runBlocking(AutoTestFrameClock()) {
         var drawerClicks = 0
         var bodyClicks = 0
         lateinit var drawerState: BottomDrawerState
@@ -502,7 +506,9 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking {
+    fun bottomDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking(
+        AutoTestFrameClock()
+    ) {
         var bodyClicks = 0
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
@@ -548,7 +554,7 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_openBySwipe_shortDrawer(): Unit = runBlocking {
+    fun bottomDrawer_openBySwipe_shortDrawer(): Unit = runBlocking(AutoTestFrameClock()) {
         val contentTag = "contentTestTag"
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
@@ -589,7 +595,7 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_expandBySwipe_tallDrawer(): Unit = runBlocking {
+    fun bottomDrawer_expandBySwipe_tallDrawer(): Unit = runBlocking(AutoTestFrameClock()) {
         val contentTag = "contentTestTag"
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
@@ -656,7 +662,7 @@
     }
 
     @Test
-    fun bottomDrawer_openBySwipe_onBodyContent(): Unit = runBlocking {
+    fun bottomDrawer_openBySwipe_onBodyContent(): Unit = runBlocking(AutoTestFrameClock()) {
         val contentTag = "contentTestTag"
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
@@ -683,7 +689,7 @@
     }
 
     @Test
-    fun bottomDrawer_hasDismissAction_whenExpanded(): Unit = runBlocking {
+    fun bottomDrawer_hasDismissAction_whenExpanded(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
             drawerState = rememberBottomDrawerState(BottomDrawerValue.Expanded)
@@ -709,7 +715,9 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking {
+    fun bottomDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking(
+        AutoTestFrameClock()
+    ) {
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
             drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
@@ -749,7 +757,7 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_openAndClose_shortDrawer(): Unit = runBlocking {
+    fun bottomDrawer_openAndClose_shortDrawer(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
             drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
@@ -788,7 +796,7 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_openAndClose_tallDrawer(): Unit = runBlocking {
+    fun bottomDrawer_openAndClose_tallDrawer(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var drawerState: BottomDrawerState
         rule.setMaterialContent {
             drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
index e41fd75..c73ec0f 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
@@ -309,7 +309,7 @@
     }
 
     @Test
-    fun modalBottomSheet_showAndHide_manually(): Unit = runBlocking {
+    fun modalBottomSheet_showAndHide_manually(): Unit = runBlocking(AutoTestFrameClock()) {
         lateinit var sheetState: ModalBottomSheetState
         rule.setMaterialContent {
             sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
@@ -348,7 +348,9 @@
     }
 
     @Test
-    fun modalBottomSheet_showAndHide_manually_tallBottomSheet(): Unit = runBlocking {
+    fun modalBottomSheet_showAndHide_manually_tallBottomSheet(): Unit = runBlocking(
+        AutoTestFrameClock()
+    ) {
         lateinit var sheetState: ModalBottomSheetState
         rule.setMaterialContent {
             sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwipeableTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwipeableTest.kt
index a3a0cfb..e44274b 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwipeableTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwipeableTest.kt
@@ -403,7 +403,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_fixed_small() = runBlocking {
+    fun swipeable_thresholds_fixed_small() = runBlocking(AutoTestFrameClock()) {
         rule.mainClock.autoAdvance = false
         lateinit var state: SwipeableState<String>
         val offsetDp = with(rule.density) { 35.toDp() }
@@ -459,7 +459,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_fixed_large() = runBlocking {
+    fun swipeable_thresholds_fixed_large() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         val offsetDp = with(rule.density) { 65.toDp() }
         setSwipeableContent {
@@ -514,7 +514,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_fractional_half() = runBlocking {
+    fun swipeable_thresholds_fractional_half() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -568,7 +568,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_fractional_quarter() = runBlocking {
+    fun swipeable_thresholds_fractional_quarter() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -622,7 +622,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_fractional_threeQuarters() = runBlocking {
+    fun swipeable_thresholds_fractional_threeQuarters() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -676,7 +676,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_mixed() = runBlocking {
+    fun swipeable_thresholds_mixed() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         val offsetDp = with(rule.density) { 35.toDp() }
         setSwipeableContent {
@@ -737,7 +737,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_thresholds_custom() = runBlocking {
+    fun swipeable_thresholds_custom() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -997,7 +997,7 @@
      */
     @Test
     @LargeTest
-    fun swipeable_targetValue() = runBlocking {
+    fun swipeable_targetValue() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -1297,7 +1297,7 @@
      * Tests that 'snapTo' updates the state and offset immediately.
      */
     @Test
-    fun swipeable_snapTo() = runBlocking {
+    fun swipeable_snapTo() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -1326,7 +1326,7 @@
      * Tests that 'animateTo' starts an animation which updates the state and offset.
      */
     @Test
-    fun swipeable_animateTo() = runBlocking {
+    fun swipeable_animateTo() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -1358,7 +1358,7 @@
      * Tests that the 'onEnd' callback of 'animateTo' is invoked and with the correct end value.
      */
     @Test
-    fun swipeable_animateTo_onEnd() = runBlocking {
+    fun swipeable_animateTo_onEnd() = runBlocking(AutoTestFrameClock()) {
         lateinit var state: SwipeableState<String>
         setSwipeableContent {
             state = rememberSwipeableState("A")
@@ -1415,7 +1415,7 @@
      * Tests that the [SwipeableState] is restored, when created with [rememberSwipeableState].
      */
     @Test
-    fun swipeable_restoreSwipeableState() = runBlocking {
+    fun swipeable_restoreSwipeableState() = runBlocking(AutoTestFrameClock()) {
         val restorationTester = StateRestorationTester(rule)
         var state: SwipeableState<String>? = null
 
@@ -1690,7 +1690,7 @@
     }
 
     @Test
-    fun swipeable_nestedScroll_postFlings() = runBlocking {
+    fun swipeable_nestedScroll_postFlings() = runBlocking(AutoTestFrameClock()) {
         lateinit var swipeableState: SwipeableState<String>
         lateinit var anchors: MutableState<Map<Float, String>>
         lateinit var scrollState: ScrollState
diff --git a/compose/runtime/runtime/api/1.0.0-beta02.txt b/compose/runtime/runtime/api/1.0.0-beta02.txt
index d73a573..329d523 100644
--- a/compose/runtime/runtime/api/1.0.0-beta02.txt
+++ b/compose/runtime/runtime/api/1.0.0-beta02.txt
@@ -17,7 +17,7 @@
   }
 
   public final class ActualAndroid_androidKt {
-    method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
+    method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
   }
 
   public final class ActualJvm_jvmKt {
@@ -190,6 +190,7 @@
   }
 
   public final class MonotonicFrameClockKt {
+    method public static androidx.compose.runtime.MonotonicFrameClock getMonotonicFrameClock(kotlin.coroutines.CoroutineContext);
     method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index d73a573..329d523 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -17,7 +17,7 @@
   }
 
   public final class ActualAndroid_androidKt {
-    method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
+    method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
   }
 
   public final class ActualJvm_jvmKt {
@@ -190,6 +190,7 @@
   }
 
   public final class MonotonicFrameClockKt {
+    method public static androidx.compose.runtime.MonotonicFrameClock getMonotonicFrameClock(kotlin.coroutines.CoroutineContext);
     method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
diff --git a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt
index c803652..c51803e 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt
@@ -17,7 +17,7 @@
   }
 
   public final class ActualAndroid_androidKt {
-    method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
+    method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
   }
 
   public final class ActualJvm_jvmKt {
@@ -243,6 +243,7 @@
   }
 
   public final class MonotonicFrameClockKt {
+    method public static androidx.compose.runtime.MonotonicFrameClock getMonotonicFrameClock(kotlin.coroutines.CoroutineContext);
     method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index c803652..c51803e 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -17,7 +17,7 @@
   }
 
   public final class ActualAndroid_androidKt {
-    method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
+    method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
   }
 
   public final class ActualJvm_jvmKt {
@@ -243,6 +243,7 @@
   }
 
   public final class MonotonicFrameClockKt {
+    method public static androidx.compose.runtime.MonotonicFrameClock getMonotonicFrameClock(kotlin.coroutines.CoroutineContext);
     method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
diff --git a/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt b/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt
index d80e38d..552fff5 100644
--- a/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt
+++ b/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt
@@ -17,7 +17,7 @@
   }
 
   public final class ActualAndroid_androidKt {
-    method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
+    method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
   }
 
   public final class ActualJvm_jvmKt {
@@ -215,6 +215,7 @@
   }
 
   public final class MonotonicFrameClockKt {
+    method public static androidx.compose.runtime.MonotonicFrameClock getMonotonicFrameClock(kotlin.coroutines.CoroutineContext);
     method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index d80e38d..552fff5 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -17,7 +17,7 @@
   }
 
   public final class ActualAndroid_androidKt {
-    method public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
+    method @Deprecated public static androidx.compose.runtime.MonotonicFrameClock getDefaultMonotonicFrameClock();
   }
 
   public final class ActualJvm_jvmKt {
@@ -215,6 +215,7 @@
   }
 
   public final class MonotonicFrameClockKt {
+    method public static androidx.compose.runtime.MonotonicFrameClock getMonotonicFrameClock(kotlin.coroutines.CoroutineContext);
     method public static suspend inline <R> Object? withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
     method public static suspend <R> Object? withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R> onFrame, kotlin.coroutines.Continuation<? super R> p);
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
index f94428c..8c95361 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
@@ -19,6 +19,7 @@
 import android.view.View
 import android.widget.TextView
 import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.test.TestMonotonicFrameClock
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import junit.framework.TestCase.assertEquals
@@ -126,7 +127,7 @@
     @OptIn(ExperimentalCoroutinesApi::class)
     fun runningRecomposerFlow() = runBlockingTest {
         lateinit var recomposer: RecomposerInfo
-        val recomposerJob = launch {
+        val recomposerJob = launch(TestMonotonicFrameClock(this)) {
             withRunningRecomposer {
                 recomposer = it.asRecomposerInfo()
                 suspendCancellableCoroutine<Unit> { }
diff --git a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
index 7b49ff7..1c270d3 100644
--- a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
+++ b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
@@ -72,6 +72,10 @@
 // For local testing
 private const val DisallowDefaultMonotonicFrameClock = false
 
+@Deprecated(
+    "MonotonicFrameClocks are not globally applicable across platforms. " +
+        "Use an appropriate local clock."
+)
 actual val DefaultMonotonicFrameClock: MonotonicFrameClock by lazy {
     if (DisallowDefaultMonotonicFrameClock) error("Disallowed use of DefaultMonotonicFrameClock")
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
index 91193ee..585fe15 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
@@ -76,9 +76,11 @@
 /**
  * The [MonotonicFrameClock] used by [withFrameNanos] and [withFrameMillis] if one is not present
  * in the calling [kotlin.coroutines.CoroutineContext].
+ *
+ * This value is no longer used by compose runtime.
  */
-// Implementor's note:
-// This frame clock implementation should try to synchronize with the vsync rate of the device's
-// default display. Without this synchronization, any usage of this default clock will result
-// in inconsistent animation frame timing and associated visual artifacts.
+@Deprecated(
+    "MonotonicFrameClocks are not globally applicable across platforms. " +
+        "Use an appropriate local clock."
+)
 expect val DefaultMonotonicFrameClock: MonotonicFrameClock
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt
index 17d6bff..dcc1c59 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.kt
@@ -81,8 +81,9 @@
  * [CoroutineContext]'s [MonotonicFrameClock] or a default frame clock if one is not present
  * in the [CoroutineContext].
  */
+@OptIn(ExperimentalComposeApi::class)
 suspend fun <R> withFrameNanos(onFrame: (frameTimeMillis: Long) -> R): R =
-    (coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock).withFrameNanos(onFrame)
+    coroutineContext.monotonicFrameClock.withFrameNanos(onFrame)
 
 /**
  * Suspends until a new frame is requested, immediately invokes [onFrame] with the frame time
@@ -102,5 +103,17 @@
  * [CoroutineContext]'s [MonotonicFrameClock] or a default frame clock if one is not present
  * in the [CoroutineContext].
  */
+@OptIn(ExperimentalComposeApi::class)
 suspend fun <R> withFrameMillis(onFrame: (frameTimeMillis: Long) -> R): R =
-    (coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock).withFrameMillis(onFrame)
+    coroutineContext.monotonicFrameClock.withFrameMillis(onFrame)
+
+/**
+ * Returns the [MonotonicFrameClock] for this [CoroutineContext] or throws [IllegalStateException]
+ * if one is not present.
+ */
+@ExperimentalComposeApi
+val CoroutineContext.monotonicFrameClock: MonotonicFrameClock
+    get() = this[MonotonicFrameClock] ?: error(
+        "A MonotonicFrameClock is not available in this CoroutineContext. Callers should supply " +
+            "an appropriate MonotonicFrameClock using withContext."
+    )
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 0dfc7c5..3da5ece 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -608,10 +608,11 @@
         }
     }
 
+    @OptIn(ExperimentalComposeApi::class)
     private suspend fun recompositionRunner(
         block: suspend CoroutineScope.(parentFrameClock: MonotonicFrameClock) -> Unit
     ) {
-        val parentFrameClock = coroutineContext[MonotonicFrameClock] ?: DefaultMonotonicFrameClock
+        val parentFrameClock = coroutineContext.monotonicFrameClock
         withContext(broadcastFrameClock) {
             // Enforce mutual exclusion of callers; register self as current runner
             val callingJob = coroutineContext.job
diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
index b704879..6214d94 100644
--- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
+++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
@@ -60,6 +60,10 @@
  * obtained using [LaunchedEffect] or [rememberCoroutineScope] they also use
  * [MonotonicFrameClock] which is bound to the current window.
  */
+@Deprecated(
+    "MonotonicFrameClocks are not globally applicable across platforms. " +
+        "Use an appropriate local clock."
+)
 actual val DefaultMonotonicFrameClock: MonotonicFrameClock get() = SixtyFpsMonotonicFrameClock
 
 private object SixtyFpsMonotonicFrameClock : MonotonicFrameClock {
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/MonotonicFrameClockTest.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/MonotonicFrameClockTest.kt
new file mode 100644
index 0000000..b555bea
--- /dev/null
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/MonotonicFrameClockTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime
+
+import kotlinx.coroutines.runBlocking
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertSame
+
+class MonotonicFrameClockTest {
+    @ExperimentalComposeApi
+    @Test
+    fun monotonicFrameClockThrowsWhenAbsent() {
+        assertFailsWith<IllegalStateException> {
+            runBlocking {
+                coroutineContext.monotonicFrameClock
+            }
+        }
+    }
+
+    @ExperimentalComposeApi
+    @Test
+    fun monotonicFrameClockReturnsContextClock() {
+        val clock = object : MonotonicFrameClock {
+            override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+                error("not implemented")
+            }
+        }
+
+        val result = runBlocking(clock) {
+            coroutineContext.monotonicFrameClock
+        }
+
+        assertSame(clock, result)
+    }
+
+    @Test
+    fun withFrameNanosThrowsWithNoClock() {
+        assertFailsWith<IllegalStateException> {
+            runBlocking {
+                withFrameNanos {
+                    throw RuntimeException("withFrameNanos block should not be called")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun withFrameNanosCallsPresentClock() {
+        val clock = object : MonotonicFrameClock {
+            var callCount = 0
+            override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+                callCount++
+                return onFrame(0)
+            }
+        }
+        val expected = Any()
+        val result = runBlocking(clock) {
+            withFrameNanos { expected }
+        }
+        assertSame(expected, result, "expected value not returned from withFrameNanos")
+        assertEquals(1, clock.callCount, "withFrameNanos did not use supplied clock")
+    }
+}