diff options
5 files changed, 229 insertions, 12 deletions
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 000000000000..8635bb0e8ab2 --- /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 e0d9fac0eba5..110dec6c33aa 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.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP 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 com.google.common.truth.Truth.assertThat 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 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { 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 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { .thenReturn(PROC_STATE_INVISIBLE) } - @After - fun tearDown() { - controller.tearDownChipView() - } - @Test fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() { val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) @@ -224,7 +218,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { 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 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { 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 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { 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 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() { 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 a95735e56f04..82cfab6fde06 100644 --- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java +++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java @@ -56,7 +56,6 @@ import java.util.Collections; @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 @@ public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestC */ 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 000000000000..778614b79fc8 --- /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 27a2cab1448e..153a8be06adc 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 org.mockito.Mockito; 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 @@ import java.util.concurrent.Future; // 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 abstract class SysuiTestCase { 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(); |