diff options
| author | 2019-01-29 16:04:45 +0900 | |
|---|---|---|
| committer | 2019-02-18 14:17:38 +0900 | |
| commit | 585f293075c8f7f0f9fc9132c13a2f77ee84b45d (patch) | |
| tree | a69d415d6cfdf6499d39472d19eadd25d95fd973 | |
| parent | 2f040befbd02291e4e1e79574baf2fbe804cd0cd (diff) | |
Add test to ProcessRecord ANR function
The test will cover ProcessRecord#appNotResponding. Status related to
the Process will be tested. It covers the code path related to parent
process to avoid possible NPE in future.
Necessary refactor is there to support the test.
Test: atest ProcessRecordTests
Bug: 117927743
Change-Id: I127020177be0db82158c20102323458f718d3c60
3 files changed, 223 insertions, 25 deletions
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index fcc857b7a75d..51b1ae197821 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -55,6 +55,7 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BatteryStatsImpl; @@ -73,7 +74,7 @@ import java.util.List; * Full information about a particular process that * is currently running. */ -final class ProcessRecord implements WindowProcessListener { +class ProcessRecord implements WindowProcessListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessRecord" : TAG_AM; private final ActivityManagerService mService; // where we came from @@ -1303,6 +1304,27 @@ final class ProcessRecord implements WindowProcessListener { ServerProtoEnums.DATA_APP; } + /** + * Unless configured otherwise, swallow ANRs in background processes & kill the process. + * Non-private access is for tests only. + */ + @VisibleForTesting + boolean isSilentAnr() { + return !getShowBackground() && !isInterestingForBackgroundTraces(); + } + + /** Non-private access is for tests only. */ + @VisibleForTesting + List<ProcessRecord> getLruProcessList() { + return mService.mProcessList.mLruProcesses; + } + + /** Non-private access is for tests only. */ + @VisibleForTesting + boolean isMonitorCpuUsage() { + return mService.MONITOR_CPU_USAGE; + } + void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, String annotation) { @@ -1312,16 +1334,10 @@ final class ProcessRecord implements WindowProcessListener { mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr", true)); long anrTime = SystemClock.uptimeMillis(); - if (ActivityManagerService.MONITOR_CPU_USAGE) { + if (isMonitorCpuUsage()) { mService.updateCpuStatsNow(); } - // Unless configured otherwise, swallow ANRs in background processes & kill the process. - boolean showBackground = Settings.Secure.getInt(mService.mContext.getContentResolver(), - Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; - - boolean isSilentANR; - synchronized (mService) { // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down. if (mService.mAtmInternal.isShuttingDown()) { @@ -1353,8 +1369,7 @@ final class ProcessRecord implements WindowProcessListener { firstPids.add(pid); // Don't dump other PIDs if it's a background ANR - isSilentANR = !showBackground && !isInterestingForBackgroundTraces(); - if (!isSilentANR) { + if (!isSilentAnr()) { int parentPid = pid; if (parentProcess != null && parentProcess.getPid() > 0) { parentPid = parentProcess.getPid(); @@ -1363,8 +1378,8 @@ final class ProcessRecord implements WindowProcessListener { if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID); - for (int i = mService.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) { - ProcessRecord r = mService.mProcessList.mLruProcesses.get(i); + for (int i = getLruProcessList().size() - 1; i >= 0; i--) { + ProcessRecord r = getLruProcessList().get(i); if (r != null && r.thread != null) { int myPid = r.pid; if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) { @@ -1405,7 +1420,7 @@ final class ProcessRecord implements WindowProcessListener { // don't dump native PIDs for background ANRs unless it is the process of interest String[] nativeProcs = null; - if (isSilentANR) { + if (isSilentAnr()) { for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) { if (NATIVE_STACKS_OF_INTEREST[i].equals(processName)) { nativeProcs = new String[] { processName }; @@ -1429,11 +1444,11 @@ final class ProcessRecord implements WindowProcessListener { // For background ANRs, don't pass the ProcessCpuTracker to // avoid spending 1/2 second collecting stats to rank lastPids. File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, - (isSilentANR) ? null : processCpuTracker, (isSilentANR) ? null : lastPids, + (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids, nativePids); String cpuInfo = null; - if (ActivityManagerService.MONITOR_CPU_USAGE) { + if (isMonitorCpuUsage()) { mService.updateCpuStatsNow(); synchronized (mService.mProcessCpuTracker) { cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime); @@ -1477,9 +1492,13 @@ final class ProcessRecord implements WindowProcessListener { } synchronized (mService) { - mService.mBatteryStatsService.noteProcessAnr(processName, uid); + // mBatteryStatsService can be null if the AMS is constructed with injector only. This + // will only happen in tests. + if (mService.mBatteryStatsService != null) { + mService.mBatteryStatsService.noteProcessAnr(processName, uid); + } - if (isSilentANR) { + if (isSilentAnr()) { kill("bg anr", true); return; } @@ -1488,20 +1507,28 @@ final class ProcessRecord implements WindowProcessListener { makeAppNotRespondingLocked(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); - // Bring up the infamous App Not Responding dialog - Message msg = Message.obtain(); - msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; - msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem); + // mUiHandler can be null if the AMS is constructed with injector only. This will only + // happen in tests. + if (mService.mUiHandler != null) { + // Bring up the infamous App Not Responding dialog + Message msg = Message.obtain(); + msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; + msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem); - mService.mUiHandler.sendMessage(msg); + mService.mUiHandler.sendMessage(msg); + } } } private void makeAppNotRespondingLocked(String activity, String shortMsg, String longMsg) { setNotResponding(true); - notRespondingReport = mService.mAppErrors.generateProcessError(this, - ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, - activity, shortMsg, longMsg, null); + // mAppErrors can be null if the AMS is constructed with injector only. This will only + // happen in tests. + if (mService.mAppErrors != null) { + notRespondingReport = mService.mAppErrors.generateProcessError(this, + ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, + activity, shortMsg, longMsg, null); + } startAppProblemLocked(); getWindowProcessController().stopFreezingActivities(); } @@ -1539,4 +1566,9 @@ final class ProcessRecord implements WindowProcessListener { (info != null && "com.android.systemui".equals(info.packageName)) || (hasTopUi() || hasOverlayUi()); } + + private boolean getShowBackground() { + return Settings.Secure.getInt(mService.mContext.getContentResolver(), + Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 258819fdece9..4a9e41e261ba 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -762,6 +762,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mGlobalLock; } + /** For test purpose only. */ + @VisibleForTesting + public ActivityTaskManagerInternal getAtmInternal() { + return mInternal; + } + public void initialize(IntentFirewall intentFirewall, PendingIntentController intentController, Looper looper) { mH = new H(looper); diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java new file mode 100644 index 000000000000..5b85980d3f1c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2019 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.server.am; + +import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.FlakyTest; + +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Collections; + +/** + * Build/Install/Run: + * atest FrameworksServicesTests:ProcessRecordTests + */ +@Presubmit +@FlakyTest(detail = "Promote to presubmit when shown to be stable.") +public class ProcessRecordTests { + private static Context sContext; + private static ActivityManagerService sService; + + private ProcessRecord mProcessRecord; + + @BeforeClass + public static void setUpOnce() throws Exception { + sContext = getInstrumentation().getTargetContext(); + + // We need to run with dexmaker share class loader to make use of ActivityTaskManagerService + // from wm package. + runWithDexmakerShareClassLoader(() -> { + sService = mock(ActivityManagerService.class); + sService.mActivityTaskManager = new ActivityTaskManagerService(sContext); + sService.mActivityTaskManager.initialize(null, null, sContext.getMainLooper()); + sService.mAtmInternal = sService.mActivityTaskManager.getAtmInternal(); + }); + } + + @Before + public void setUpProcess() throws Exception { + // Need to run with dexmaker share class loader to mock package private class. + runWithDexmakerShareClassLoader(() -> { + mProcessRecord = spy(new ProcessRecord(sService, sContext.getApplicationInfo(), + "name", 12345)); + doNothing().when(mProcessRecord).startAppProblemLocked(); + doReturn(false).when(mProcessRecord).isSilentAnr(); + doReturn(false).when(mProcessRecord).isMonitorCpuUsage(); + doReturn(Collections.emptyList()).when(mProcessRecord).getLruProcessList(); + }); + } + + + /** + * This test verifies the process default status. If this doesn't pass, none of the other tests + * should be able to pass. + */ + @Test + public void testProcessDefaultAnrRelatedStatus() { + assertFalse(mProcessRecord.isNotResponding()); + assertFalse(mProcessRecord.isCrashing()); + assertFalse(mProcessRecord.killedByAm); + assertFalse(mProcessRecord.killed); + } + + /** + * This test verifies that if the process is crashing, Anr will do nothing. + */ + @Test + public void testAnrWhenCrash() { + mProcessRecord.setCrashing(true); + assertTrue(mProcessRecord.isCrashing()); + mProcessRecord.appNotResponding(null, null, null, null, false, "Test ANR when crash"); + assertFalse(mProcessRecord.isNotResponding()); + assertFalse(mProcessRecord.killedByAm); + assertFalse(mProcessRecord.killed); + } + + /** + * This test verifies that if the process is killed by AM, Anr will do nothing. + */ + @Test + public void testAnrWhenKilledByAm() { + mProcessRecord.killedByAm = true; + mProcessRecord.appNotResponding(null, null, null, null, false, + "Test ANR when killed by AM"); + assertFalse(mProcessRecord.isNotResponding()); + assertFalse(mProcessRecord.isCrashing()); + assertFalse(mProcessRecord.killed); + } + + /** + * This test verifies that if the process is killed, Anr will do nothing. + */ + @Test + public void testAnrWhenKilled() { + mProcessRecord.killed = true; + mProcessRecord.appNotResponding(null, null, null, null, false, "Test ANR when killed"); + assertFalse(mProcessRecord.isNotResponding()); + assertFalse(mProcessRecord.isCrashing()); + assertFalse(mProcessRecord.killedByAm); + } + + /** + * This test verifies that non-silent ANR can run through successfully and the corresponding + * flags can be set correctly. + */ + @Test + public void testNonSilentAnr() { + mProcessRecord.appNotResponding(null, null, null, null, false, "Test non-silent ANR"); + assertTrue(mProcessRecord.isNotResponding()); + assertFalse(mProcessRecord.isCrashing()); + assertFalse(mProcessRecord.killedByAm); + assertFalse(mProcessRecord.killed); + } + + /** + * This test verifies that silent ANR can run through successfully and the corresponding flags + * can be set correctly. + */ + @Test + public void testSilentAnr() { + // Silent Anr will run through even without a parent process, and directly killed by AM. + doReturn(true).when(mProcessRecord).isSilentAnr(); + mProcessRecord.appNotResponding(null, null, null, null, false, "Test silent ANR"); + assertTrue(mProcessRecord.isNotResponding()); + assertFalse(mProcessRecord.isCrashing()); + assertTrue(mProcessRecord.killedByAm); + assertTrue(mProcessRecord.killed); + } +} |