Merge "Create onTeardown and use it." into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 0000000..8635bb0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+ // None of these inner classes should be run except as part of this utilities-testing test
+ class HasTeardown {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun teardownRuns() {
+ val result = JUnitCore().run(HasTeardown::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(HasTeardown.teardownWasRun).isTrue()
+ }
+
+ class FirstTeardownFails {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { fail("One fails") }
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun allTeardownsRun() {
+ val result = JUnitCore().run(FirstTeardownFails::class.java)
+ assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+ assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+ }
+
+ class ThreeTeardowns {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ messages.clear()
+ }
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown { messages.add("A") }
+ teardownRule.onTeardown { messages.add("B") }
+ teardownRule.onTeardown { messages.add("C") }
+ }
+
+ companion object {
+ val messages = mutableListOf<String>()
+ }
+ }
+
+ @Test
+ fun reverseOrder() {
+ val result = JUnitCore().run(ThreeTeardowns::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+ }
+
+ class TryToDoABadThing {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown {
+ teardownRule.onTeardown {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ @Test
+ fun prohibitTeardownDuringTeardown() {
+ val result = JUnitCore().run(TryToDoABadThing::class.java)
+ assertThat(result.failures.map { it.message })
+ .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac..110dec6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -58,7 +57,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -144,6 +142,7 @@
controller.start()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
+ onTeardown { controller.tearDownChipView() }
val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@
.thenReturn(PROC_STATE_INVISIBLE)
}
- @After
- fun tearDown() {
- controller.tearDownChipView()
- }
-
@Test
fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@
private fun createCallNotifEntry(
callStyle: Notification.CallStyle,
- nullContentIntent: Boolean = false
+ nullContentIntent: Boolean = false,
): NotificationEntry {
val notificationEntryBuilder = NotificationEntryBuilder()
notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e..82cfab6 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
private static final String TAG = "AAA++VerifyTest";
private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@
*/
private boolean isTestClass(Class<?> loadedClass) {
try {
+ if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+ return false;
+ }
if (Modifier.isAbstract(loadedClass.getModifiers())) {
logDebug(String.format("Skipping abstract class %s: not a test",
loadedClass.getName()));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 0000000..778614b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ * val oldTimeout = getGlobalTimeout()
+ * teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ * overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ * val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ * teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+ private var canAdd = true
+ private val teardowns = mutableListOf<() -> Unit>()
+
+ fun onTeardown(teardownRunnable: () -> Unit) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add(teardownRunnable)
+ }
+
+ fun onTeardown(teardownRunnable: Runnable) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add { teardownRunnable.run() }
+ }
+
+ override fun finished(description: Description?) {
+ canAdd = false
+ val errors = mutableListOf<Throwable>()
+ teardowns.reversed().forEach {
+ try {
+ it()
+ } catch (e: Throwable) {
+ errors.add(e)
+ }
+ }
+ MultipleFailureException.assertEmpty(errors)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 27a2cab..153a8be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -69,6 +71,17 @@
// background on Ravenwood is available at go/ravenwood-docs
@DisabledOnRavenwood
public abstract class SysuiTestCase {
+ /**
+ * Especially when self-testing test utilities, we may have classes that look like test
+ * classes, but we don't expect to ever actually run as a top-level test.
+ * For example, {@link com.android.systemui.TryToDoABadThing}.
+ * Verifying properties on these as a part of structural tests like
+ * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+ * look more confusing, so this lets us skip when appropriate.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SkipSysuiVerification {
+ }
private static final String TAG = "SysuiTestCase";
@@ -172,6 +185,15 @@
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+ /**
+ * Schedule a cleanup routine to happen when the test state is torn down.
+ */
+ protected void onTeardown(Runnable tearDownRunnable) {
+ mTearDownRule.onTeardown(tearDownRunnable);
+ }
+
// set the highest order so it's the innermost rule
@Rule(order = Integer.MAX_VALUE)
public TestWithLooperRule mlooperRule = new TestWithLooperRule();