Merge "Add an overload of waitUntil that takes a custom message." into androidx-main am: 6bb44267cf
Original change: https://android-review.googlesource.com/c/platform/frameworks/support/+/2924898
Change-Id: I05e43d35bf381e282b5bf2a550aa7d92c9c07611
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/compose/ui/ui-test-junit4/api/current.txt b/compose/ui/ui-test-junit4/api/current.txt
index 0364dec..edfb551 100644
--- a/compose/ui/ui-test-junit4/api/current.txt
+++ b/compose/ui/ui-test-junit4/api/current.txt
@@ -59,6 +59,7 @@
method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
method public void waitForIdle();
+ method public default void waitUntil(String conditionDescription, optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.txt b/compose/ui/ui-test-junit4/api/restricted_current.txt
index 0364dec..edfb551 100644
--- a/compose/ui/ui-test-junit4/api/restricted_current.txt
+++ b/compose/ui/ui-test-junit4/api/restricted_current.txt
@@ -59,6 +59,7 @@
method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
method public void waitForIdle();
+ method public default void waitUntil(String conditionDescription, optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public void waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
index 0ee15e0..149daf9 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
@@ -303,7 +303,15 @@
override suspend fun awaitIdle() = composeTest.awaitIdle()
override fun waitUntil(timeoutMillis: Long, condition: () -> Boolean) =
- composeTest.waitUntil(timeoutMillis, condition)
+ composeTest.waitUntil(conditionDescription = null, timeoutMillis, condition)
+
+ override fun waitUntil(
+ conditionDescription: String,
+ timeoutMillis: Long,
+ condition: () -> Boolean
+ ) {
+ composeTest.waitUntil(conditionDescription, timeoutMillis, condition)
+ }
@ExperimentalTestApi
override fun waitUntilNodeCount(matcher: SemanticsMatcher, count: Int, timeoutMillis: Long) =
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
index 9fd4197..43987e6 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
+++ b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
@@ -91,7 +91,15 @@
override suspend fun awaitIdle() = composeTest.awaitIdle()
override fun waitUntil(timeoutMillis: Long, condition: () -> Boolean) =
- composeTest.waitUntil(timeoutMillis, condition)
+ composeTest.waitUntil(conditionDescription = null, timeoutMillis, condition)
+
+ override fun waitUntil(
+ conditionDescription: String,
+ timeoutMillis: Long,
+ condition: () -> Boolean
+ ) {
+ composeTest.waitUntil(conditionDescription, timeoutMillis, condition)
+ }
@ExperimentalTestApi
override fun waitUntilNodeCount(matcher: SemanticsMatcher, count: Int, timeoutMillis: Long) =
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
index 9ee2023..14c64be 100644
--- a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
+++ b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
@@ -133,6 +133,38 @@
fun waitUntil(timeoutMillis: Long = 1_000, condition: () -> Boolean)
/**
+ * Blocks until the given condition is satisfied.
+ *
+ * In case the main clock auto advancement is enabled (by default is), this will also keep
+ * advancing the clock on a frame by frame basis and yield for other async work at the end of
+ * each frame. If the advancement of the main clock is not enabled this will work as a
+ * countdown latch without any other advancements.
+ *
+ * There is also [MainTestClock.advanceTimeUntil] which is faster as it does not yield back
+ * the UI thread.
+ *
+ * This method should be used in cases where [MainTestClock.advanceTimeUntil]
+ * is not enough.
+ *
+ * @param timeoutMillis The time after which this method throws an exception if the given
+ * condition is not satisfied. This is the wall clock time not the main clock one.
+ * @param conditionDescription A human-readable description of [condition] that will be included
+ * in the timeout exception if thrown.
+ * @param condition Condition that must be satisfied in order for this method to successfully
+ * finish.
+ *
+ * @throws androidx.compose.ui.test.ComposeTimeoutException If the condition is not satisfied
+ * after [timeoutMillis].
+ */
+ fun waitUntil(
+ conditionDescription: String,
+ timeoutMillis: Long = 1_000,
+ condition: () -> Boolean
+ ) {
+ waitUntil(timeoutMillis, condition)
+ }
+
+ /**
* Blocks until the number of nodes matching the given [matcher] is equal to the given [count].
*
* @see ComposeTestRule.waitUntil
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 32645f8..eab3e6b 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -100,7 +100,7 @@
method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
method public void waitForIdle();
- method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
+ method public void waitUntil(optional String? conditionDescription, optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
property public abstract androidx.compose.ui.unit.Density density;
property public abstract androidx.compose.ui.test.MainTestClock mainClock;
}
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index bdff3c0..1dfc373 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -100,7 +100,7 @@
method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
method public void waitForIdle();
- method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
+ method public void waitUntil(optional String? conditionDescription, optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
property public abstract androidx.compose.ui.unit.Density density;
property public abstract androidx.compose.ui.test.MainTestClock mainClock;
}
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt
index 3da62e9..12f2c73 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt
@@ -53,6 +53,22 @@
)
@Test
+ fun waitUntil_includesConditionDescription_whenSpecified() {
+ rule.setContent {
+ TaggedBox()
+ }
+
+ expectError<ComposeTimeoutException>(
+ // This is actually regex, so special characters need to be escaped.
+ expectedMessage = "Condition \\(foo\\) still not satisfied after $Timeout ms"
+ ) {
+ rule.waitUntil("foo", timeoutMillis = Timeout) {
+ false
+ }
+ }
+ }
+
+ @Test
fun waitUntilNodeCount_succeedsWhen_nodeCountCorrect() {
rule.setContent {
TaggedBox()
@@ -72,7 +88,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(exactly 2 nodes match \\(TestTag = 'TestTag'\\)\\) " +
+ "still not satisfied after $Timeout ms"
) {
rule.waitUntilNodeCount(hasTestTag(TestTag), 2, Timeout)
}
@@ -95,7 +112,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(at least one node matches " +
+ "\\(TestTag = 'TestTag'\\)\\) still not satisfied after $Timeout ms"
) {
rule.waitUntilAtLeastOneExists(hasTestTag(TestTag), Timeout)
}
@@ -118,7 +136,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(exactly 1 nodes match \\(TestTag = 'TestTag'\\)\\) " +
+ "still not satisfied after $Timeout ms"
) {
rule.waitUntilExactlyOneExists(hasTestTag(TestTag), Timeout)
}
@@ -140,7 +159,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(exactly 0 nodes match \\(TestTag = 'TestTag'\\)\\) " +
+ "still not satisfied after $Timeout ms"
) {
rule.waitUntilDoesNotExist(hasTestTag(TestTag), timeoutMillis = Timeout)
}
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt
index 0d78ba2..bf923fd 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt
@@ -72,7 +72,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(exactly 2 nodes match \\(TestTag = 'TestTag'\\)\\) " +
+ "still not satisfied after $Timeout ms"
) {
waitUntilNodeCount(hasTestTag(TestTag), 2, Timeout)
}
@@ -95,7 +96,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(at least one node matches " +
+ "\\(TestTag = 'TestTag'\\)\\) still not satisfied after $Timeout ms"
) {
waitUntilAtLeastOneExists(hasTestTag(TestTag), Timeout)
}
@@ -118,7 +120,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(exactly 1 nodes match \\(TestTag = 'TestTag'\\)\\) " +
+ "still not satisfied after $Timeout ms"
) {
waitUntilExactlyOneExists(hasTestTag(TestTag), Timeout)
}
@@ -140,7 +143,8 @@
}
expectError<ComposeTimeoutException>(
- expectedMessage = "Condition still not satisfied after $Timeout ms"
+ expectedMessage = "Condition \\(exactly 0 nodes match \\(TestTag = 'TestTag'\\)\\) " +
+ "still not satisfied after $Timeout ms"
) {
waitUntilDoesNotExist(hasTestTag(TestTag), timeoutMillis = Timeout)
}
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
index 72a79c6..887b0d9 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
@@ -425,7 +425,11 @@
coroutineExceptionHandler.throwUncaught()
}
- override fun waitUntil(timeoutMillis: Long, condition: () -> Boolean) {
+ override fun waitUntil(
+ conditionDescription: String?,
+ timeoutMillis: Long,
+ condition: () -> Boolean
+ ) {
val startTime = System.nanoTime()
while (!condition()) {
if (mainClockImpl.autoAdvance) {
@@ -435,7 +439,7 @@
Thread.sleep(10)
if (System.nanoTime() - startTime > timeoutMillis * NanoSecondsPerMilliSecond) {
throw ComposeTimeoutException(
- "Condition still not satisfied after $timeoutMillis ms"
+ buildWaitUntilTimeoutMessage(timeoutMillis, conditionDescription)
)
}
}
@@ -556,7 +560,11 @@
actual fun <T> runOnIdle(action: () -> T): T
actual fun waitForIdle()
actual suspend fun awaitIdle()
- actual fun waitUntil(timeoutMillis: Long, condition: () -> Boolean)
+ actual fun waitUntil(
+ conditionDescription: String?,
+ timeoutMillis: Long,
+ condition: () -> Boolean
+ )
actual fun registerIdlingResource(idlingResource: IdlingResource)
actual fun unregisterIdlingResource(idlingResource: IdlingResource)
actual fun setContent(composable: @Composable () -> Unit)
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
index 3414dab..52c4acc 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
@@ -158,13 +158,19 @@
*
* @param timeoutMillis The time after which this method throws an exception if the given
* condition is not satisfied. This observes wall clock time, not [frame time][mainClock].
+ * @param conditionDescription An optional human-readable description of [condition] that will
+ * be included in the timeout exception if thrown.
* @param condition Condition that must be satisfied in order for this method to successfully
* finish.
*
* @throws androidx.compose.ui.test.ComposeTimeoutException If the condition is not satisfied
* after [timeoutMillis] (in wall clock time).
*/
- fun waitUntil(timeoutMillis: Long = 1_000, condition: () -> Boolean)
+ fun waitUntil(
+ conditionDescription: String? = null,
+ timeoutMillis: Long = 1_000,
+ condition: () -> Boolean
+ )
/**
* Registers an [IdlingResource] in this test.
@@ -192,7 +198,7 @@
* @see ComposeUiTest.waitUntil
*
* @param matcher The matcher that will be used to filter nodes.
- * @param count The number of nodes that are expected to
+ * @param count The number of nodes that are expected to be matched.
* @param timeoutMillis The time after which this method throws an exception if the number of nodes
* that match the [matcher] is not [count]. This observes wall clock time, not frame time.
*
@@ -205,7 +211,7 @@
count: Int,
timeoutMillis: Long = 1_000L
) {
- waitUntil(timeoutMillis) {
+ waitUntil("exactly $count nodes match (${matcher.description})", timeoutMillis) {
onAllNodes(matcher).fetchSemanticsNodes().size == count
}
}
@@ -227,7 +233,7 @@
matcher: SemanticsMatcher,
timeoutMillis: Long = 1_000L
) {
- waitUntil(timeoutMillis) {
+ waitUntil("at least one node matches (${matcher.description})", timeoutMillis) {
onAllNodes(matcher).fetchSemanticsNodes().isNotEmpty()
}
}
@@ -269,3 +275,16 @@
) = waitUntilNodeCount(matcher, 0, timeoutMillis)
internal const val NanoSecondsPerMilliSecond = 1_000_000L
+
+internal fun buildWaitUntilTimeoutMessage(
+ timeoutMillis: Long,
+ conditionDescription: String?
+): String = buildString {
+ append("Condition ")
+ if (conditionDescription != null) {
+ append('(')
+ append(conditionDescription)
+ append(") ")
+ }
+ append("still not satisfied after $timeoutMillis ms")
+}
diff --git a/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
index f619bc3..5ee58ae 100644
--- a/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
+++ b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
@@ -143,13 +143,17 @@
return action().also { waitForIdle() }
}
- override fun waitUntil(timeoutMillis: Long, condition: () -> Boolean) {
+ override fun waitUntil(
+ conditionDescription: String?,
+ timeoutMillis: Long,
+ condition: () -> Boolean
+ ) {
val startTime = System.nanoTime()
while (!condition()) {
renderNextFrame()
if (System.nanoTime() - startTime > timeoutMillis * NanoSecondsPerMilliSecond) {
throw ComposeTimeoutException(
- "Condition still not satisfied after $timeoutMillis ms"
+ buildWaitUntilTimeoutMessage(timeoutMillis, conditionDescription)
)
}
}
@@ -222,7 +226,11 @@
actual fun <T> runOnIdle(action: () -> T): T
actual fun waitForIdle()
actual suspend fun awaitIdle()
- actual fun waitUntil(timeoutMillis: Long, condition: () -> Boolean)
+ actual fun waitUntil(
+ conditionDescription: String?,
+ timeoutMillis: Long,
+ condition: () -> Boolean
+ )
actual fun registerIdlingResource(idlingResource: IdlingResource)
actual fun unregisterIdlingResource(idlingResource: IdlingResource)
actual fun setContent(composable: @Composable () -> Unit)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoTest.kt
index a0f6645..362847e 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoTest.kt
@@ -34,7 +34,7 @@
fun launchFragment_windowInfo_isWindowFocused_true() {
runComposeUiTest {
launchFragmentInContainer<TestFragment>().onFragment {
- waitUntil(5_000) { it.isWindowFocused == true }
+ waitUntil("isWindowFocused", timeoutMillis = 5_000) { it.isWindowFocused == true }
}
}
}