Wait for ANRd process to be killed before starting next test

We must wait for the ANRd process to be fully killed. If we simply click
on "close" button on the dialog, this is not sufficient. The system
would still be processing ANR and trying to kill the process. This
killing action will therefore impact the next 'am start' command. The
second activity will never be launched.

In this CL, add a wait by polling the 'ApplicationExitInfo' lists before
and after the 'close' button is clicked.

Bug: 196111779
Test: atest InputTests:AnrTest
Change-Id: I35e8f4ba302d0f1ed4a03be2257541a4e9a638f4
This commit is contained in:
Siarhei Vishniakou 2021-12-01 15:56:20 -08:00
parent 5e4a1205df
commit 461d4bad9d
2 changed files with 55 additions and 15 deletions

View File

@ -19,6 +19,7 @@ android_test {
"androidx.test.ext.junit",
"androidx.test.rules",
"services.core.unboosted",
"testables",
"truth-prebuilt",
"ub-uiautomator",
],

View File

@ -19,7 +19,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.filters.MediumTest
import android.app.ActivityManager
import android.app.ApplicationExitInfo
import android.graphics.Rect
import android.os.Build
import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.SystemClock
import android.provider.Settings
import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
@ -27,10 +31,13 @@ import android.support.test.uiautomator.By
import android.support.test.uiautomator.UiDevice
import android.support.test.uiautomator.UiObject2
import android.support.test.uiautomator.Until
import android.testing.PollingCheck
import android.view.InputDevice
import android.view.MotionEvent
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
@ -51,22 +58,28 @@ import org.junit.runner.RunWith
class AnrTest {
companion object {
private const val TAG = "AnrTest"
private const val ALL_PIDS = 0
private const val NO_MAX = 0
}
val mInstrumentation = InstrumentationRegistry.getInstrumentation()
var mHideErrorDialogs = 0
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private var hideErrorDialogs = 0
private lateinit var PACKAGE_NAME: String
private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
Build.HW_TIMEOUT_MULTIPLIER)
@Before
fun setUp() {
val contentResolver = mInstrumentation.targetContext.contentResolver
mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
val contentResolver = instrumentation.targetContext.contentResolver
hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName()
}
@After
fun tearDown() {
val contentResolver = mInstrumentation.targetContext.contentResolver
Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs)
val contentResolver = instrumentation.targetContext.contentResolver
Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs)
}
@Test
@ -86,19 +99,28 @@ class AnrTest {
private fun clickCloseAppOnAnrDialog() {
// Find anr dialog and kill app
val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val closeAppButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
if (closeAppButton == null) {
fail("Could not find anr dialog")
return
}
val initialReasons = getExitReasons()
closeAppButton.click()
/**
* We must wait for the app to be fully closed before exiting this test. This is because
* another test may again invoke 'am start' for the same activity.
* If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
* the killing logic will apply to the newly launched 'am start' instance, and the second
* test will fail because the unresponsive activity will never be launched.
*/
waitForNewExitReason(initialReasons[0].timestamp)
}
private fun clickWaitOnAnrDialog() {
// Find anr dialog and tap on wait
val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val waitButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
if (waitButton == null) {
@ -108,9 +130,27 @@ class AnrTest {
waitButton.click()
}
private fun getExitReasons(): List<ApplicationExitInfo> {
lateinit var infos: List<ApplicationExitInfo>
instrumentation.runOnMainSync {
val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)
infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
}
return infos
}
private fun waitForNewExitReason(previousExitTimestamp: Long) {
PollingCheck.waitFor {
getExitReasons()[0].timestamp > previousExitTimestamp
}
val reasons = getExitReasons()
assertTrue(reasons[0].timestamp > previousExitTimestamp)
assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
}
private fun triggerAnr() {
startUnresponsiveActivity()
val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val obj: UiObject2? = uiDevice.wait(Until.findObject(
By.text("Unresponsive gesture monitor")), 10000)
@ -125,15 +165,14 @@ class AnrTest {
MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
// Todo: replace using timeout from android.hardware.input.IInputManager
SystemClock.sleep(5000) // default ANR timeout for gesture monitors
SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
private fun startUnresponsiveActivity() {
val flags = " -W -n "
val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity"
mInstrumentation.uiAutomation.executeShellCommand(startCmd)
val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
instrumentation.uiAutomation.executeShellCommand(startCmd)
}
}
}