Merge "Fix PackageWatchdog and add PackageWatchdogTest"
This commit is contained in:
committed by
Android (Google) Code Review
commit
0b22084bf3
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
@@ -29,6 +30,7 @@ import android.util.Slog;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
@@ -46,8 +48,10 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Monitors the health of packages on the system and notifies interested observers when packages
|
||||
@@ -58,7 +62,7 @@ public class PackageWatchdog {
|
||||
// Duration to count package failures before it resets to 0
|
||||
private static final int TRIGGER_DURATION_MS = 60000;
|
||||
// Number of package failures within the duration above before we notify observers
|
||||
private static final int TRIGGER_FAILURE_COUNT = 5;
|
||||
static final int TRIGGER_FAILURE_COUNT = 5;
|
||||
private static final int DB_VERSION = 1;
|
||||
private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
|
||||
private static final String TAG_PACKAGE = "package";
|
||||
@@ -75,20 +79,13 @@ public class PackageWatchdog {
|
||||
// Handler to run package cleanup runnables
|
||||
private final Handler mTimerHandler;
|
||||
private final Handler mIoHandler;
|
||||
// Contains (observer-name -> external-observer-handle) that have been registered during the
|
||||
// current boot.
|
||||
// It is populated when observers call #registerHealthObserver and it does not survive reboots.
|
||||
@GuardedBy("mLock")
|
||||
final ArrayMap<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>();
|
||||
// Contains (observer-name -> internal-observer-handle) that have ever been registered from
|
||||
// Contains (observer-name -> observer-handle) that have ever been registered from
|
||||
// previous boots. Observers with all packages expired are periodically pruned.
|
||||
// It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
|
||||
@GuardedBy("mLock")
|
||||
final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
|
||||
private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
|
||||
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
|
||||
private final AtomicFile mPolicyFile =
|
||||
new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
|
||||
"package-watchdog.xml"));
|
||||
private final AtomicFile mPolicyFile;
|
||||
// Runnable to prune monitored packages that have expired
|
||||
private final Runnable mPackageCleanup;
|
||||
// Last SystemClock#uptimeMillis a package clean up was executed.
|
||||
@@ -98,14 +95,32 @@ public class PackageWatchdog {
|
||||
// 0 if mPackageCleanup not running.
|
||||
private long mDurationAtLastReschedule;
|
||||
|
||||
// TODO(zezeozue): Remove redundant context param
|
||||
private PackageWatchdog(Context context) {
|
||||
mContext = context;
|
||||
mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
|
||||
"package-watchdog.xml"));
|
||||
mTimerHandler = new Handler(Looper.myLooper());
|
||||
mIoHandler = BackgroundThread.getHandler();
|
||||
mPackageCleanup = this::rescheduleCleanup;
|
||||
loadFromFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
|
||||
* and creates package-watchdog.xml in an apps data directory.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
PackageWatchdog(Context context, Looper looper) {
|
||||
mContext = context;
|
||||
mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
|
||||
mTimerHandler = new Handler(looper);
|
||||
mIoHandler = mTimerHandler;
|
||||
mPackageCleanup = this::rescheduleCleanup;
|
||||
loadFromFile();
|
||||
}
|
||||
|
||||
|
||||
/** Creates or gets singleton instance of PackageWatchdog. */
|
||||
public static PackageWatchdog getInstance(Context context) {
|
||||
synchronized (PackageWatchdog.class) {
|
||||
@@ -124,7 +139,10 @@ public class PackageWatchdog {
|
||||
*/
|
||||
public void registerHealthObserver(PackageHealthObserver observer) {
|
||||
synchronized (mLock) {
|
||||
mRegisteredObservers.put(observer.getName(), observer);
|
||||
ObserverInternal internalObserver = mAllObservers.get(observer.getName());
|
||||
if (internalObserver != null) {
|
||||
internalObserver.mRegisteredObserver = observer;
|
||||
}
|
||||
if (mDurationAtLastReschedule == 0) {
|
||||
// Nothing running, schedule
|
||||
rescheduleCleanup();
|
||||
@@ -143,7 +161,7 @@ public class PackageWatchdog {
|
||||
* or {@code durationMs} is less than 1
|
||||
*/
|
||||
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
|
||||
int durationMs) {
|
||||
long durationMs) {
|
||||
if (packageNames.isEmpty() || durationMs < 1) {
|
||||
throw new IllegalArgumentException("Observation not started, no packages specified"
|
||||
+ "or invalid duration");
|
||||
@@ -180,11 +198,32 @@ public class PackageWatchdog {
|
||||
public void unregisterHealthObserver(PackageHealthObserver observer) {
|
||||
synchronized (mLock) {
|
||||
mAllObservers.remove(observer.getName());
|
||||
mRegisteredObservers.remove(observer.getName());
|
||||
}
|
||||
saveToFileAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns packages observed by {@code observer}
|
||||
*
|
||||
* @return an empty set if {@code observer} has some packages observerd from a previous boot
|
||||
* but has not registered itself in the current boot to receive notifications. Returns null
|
||||
* if there are no active packages monitored from any boot.
|
||||
*/
|
||||
@Nullable
|
||||
public Set<String> getPackages(PackageHealthObserver observer) {
|
||||
synchronized (mLock) {
|
||||
for (int i = 0; i < mAllObservers.size(); i++) {
|
||||
if (observer.getName().equals(mAllObservers.keyAt(i))) {
|
||||
if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
|
||||
return mAllObservers.valueAt(i).mPackages.keySet();
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(zezeozue:) Accept current versionCodes of failing packages?
|
||||
/**
|
||||
* Called when a process fails either due to a crash or ANR.
|
||||
@@ -198,33 +237,35 @@ public class PackageWatchdog {
|
||||
public void onPackageFailure(String[] packages) {
|
||||
ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
|
||||
synchronized (mLock) {
|
||||
if (mRegisteredObservers.isEmpty()) {
|
||||
if (mAllObservers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int pIndex = 0; pIndex < packages.length; pIndex++) {
|
||||
// Observers interested in receiving packageName failures
|
||||
List<PackageHealthObserver> observersToNotify = new ArrayList<>();
|
||||
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
|
||||
// Observers interested in receiving packageName failures
|
||||
List<PackageHealthObserver> observersToNotify = new ArrayList<>();
|
||||
PackageHealthObserver activeObserver =
|
||||
mRegisteredObservers.get(mAllObservers.valueAt(oIndex).mName);
|
||||
if (activeObserver != null) {
|
||||
observersToNotify.add(activeObserver);
|
||||
}
|
||||
|
||||
// Save interested observers and notify them outside the lock
|
||||
if (!observersToNotify.isEmpty()) {
|
||||
packagesToReport.put(packages[pIndex], observersToNotify);
|
||||
PackageHealthObserver registeredObserver =
|
||||
mAllObservers.valueAt(oIndex).mRegisteredObserver;
|
||||
if (registeredObserver != null) {
|
||||
observersToNotify.add(registeredObserver);
|
||||
}
|
||||
}
|
||||
// Save interested observers and notify them outside the lock
|
||||
if (!observersToNotify.isEmpty()) {
|
||||
packagesToReport.put(packages[pIndex], observersToNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify observers
|
||||
for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
|
||||
List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
|
||||
String packageName = packages[pIndex];
|
||||
for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
|
||||
if (observers.get(oIndex).onHealthCheckFailed(packages[pIndex])) {
|
||||
PackageHealthObserver observer = observers.get(oIndex);
|
||||
if (mAllObservers.get(observer.getName()).onPackageFailure(packageName)
|
||||
&& observer.onHealthCheckFailed(packageName)) {
|
||||
// Observer has handled, do not notify others
|
||||
break;
|
||||
}
|
||||
@@ -275,10 +316,12 @@ public class PackageWatchdog {
|
||||
// O if mPackageCleanup not running
|
||||
long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
|
||||
? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
|
||||
// O if mPackageCleanup not running
|
||||
// Less than O if mPackageCleanup unexpectedly didn't run yet even though
|
||||
// and we are past the last duration scheduled to run
|
||||
long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
|
||||
|
||||
if (mUptimeAtLastRescheduleMs == 0 || nextDurationToScheduleMs < remainingDurationMs) {
|
||||
if (mUptimeAtLastRescheduleMs == 0
|
||||
|| remainingDurationMs <= 0
|
||||
|| nextDurationToScheduleMs < remainingDurationMs) {
|
||||
// First schedule or an earlier reschedule
|
||||
pruneObservers(elapsedDurationMs);
|
||||
mTimerHandler.removeCallbacks(mPackageCleanup);
|
||||
@@ -305,6 +348,7 @@ public class PackageWatchdog {
|
||||
}
|
||||
}
|
||||
Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
|
||||
|
||||
return shortestDurationMs;
|
||||
}
|
||||
|
||||
@@ -409,6 +453,8 @@ public class PackageWatchdog {
|
||||
static class ObserverInternal {
|
||||
public final String mName;
|
||||
public final ArrayMap<String, MonitoredPackage> mPackages;
|
||||
@Nullable
|
||||
public PackageHealthObserver mRegisteredObserver;
|
||||
|
||||
ObserverInternal(String name, List<MonitoredPackage> packages) {
|
||||
mName = name;
|
||||
|
||||
34
tests/PackageWatchdog/Android.mk
Normal file
34
tests/PackageWatchdog/Android.mk
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
# PackageWatchdogTest
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
LOCAL_PACKAGE_NAME := PackageWatchdogTest
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
junit \
|
||||
frameworks-base-testutils \
|
||||
android-support-test \
|
||||
services
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := \
|
||||
android.test.runner
|
||||
|
||||
LOCAL_PRIVATE_PLATFORM_APIS := true
|
||||
LOCAL_COMPATIBILITY_SUITE := device-tests
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
28
tests/PackageWatchdog/AndroidManifest.xml
Normal file
28
tests/PackageWatchdog/AndroidManifest.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.packagewatchdog" >
|
||||
|
||||
<application android:debuggable="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
|
||||
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.tests.packagewatchdog"
|
||||
android:label="PackageWatchdog Test"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.test.TestLooper;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.PackageWatchdog.PackageHealthObserver;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on
|
||||
// behavior of observers receiving crash notifications or not to determine if it's registered
|
||||
/**
|
||||
* Test PackageWatchdog.
|
||||
*/
|
||||
public class PackageWatchdogTest {
|
||||
private static final String APP_A = "com.package.a";
|
||||
private static final String APP_B = "com.package.b";
|
||||
private static final String OBSERVER_NAME_1 = "observer1";
|
||||
private static final String OBSERVER_NAME_2 = "observer2";
|
||||
private static final String OBSERVER_NAME_3 = "observer3";
|
||||
private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
|
||||
private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
|
||||
private TestLooper mTestLooper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mTestLooper = new TestLooper();
|
||||
mTestLooper.startAutoDispatch();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
new File(InstrumentationRegistry.getContext().getFilesDir(),
|
||||
"package-watchdog.xml").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test registration, unregistration, package expiry and duration reduction
|
||||
*/
|
||||
@Test
|
||||
public void testRegistration() throws Exception {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
|
||||
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
|
||||
TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
|
||||
|
||||
// Start observing for observer1 which will be unregistered
|
||||
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
// Start observing for observer2 which will expire
|
||||
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
|
||||
// Start observing for observer3 which will have expiry duration reduced
|
||||
watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
|
||||
|
||||
// Verify packages observed at start
|
||||
// 1
|
||||
assertEquals(1, watchdog.getPackages(observer1).size());
|
||||
assertTrue(watchdog.getPackages(observer1).contains(APP_A));
|
||||
// 2
|
||||
assertEquals(2, watchdog.getPackages(observer2).size());
|
||||
assertTrue(watchdog.getPackages(observer2).contains(APP_A));
|
||||
assertTrue(watchdog.getPackages(observer2).contains(APP_B));
|
||||
// 3
|
||||
assertEquals(1, watchdog.getPackages(observer3).size());
|
||||
assertTrue(watchdog.getPackages(observer3).contains(APP_A));
|
||||
|
||||
// Then unregister observer1
|
||||
watchdog.unregisterHealthObserver(observer1);
|
||||
|
||||
// Verify observer2 and observer3 left
|
||||
// 1
|
||||
assertNull(watchdog.getPackages(observer1));
|
||||
// 2
|
||||
assertEquals(2, watchdog.getPackages(observer2).size());
|
||||
assertTrue(watchdog.getPackages(observer2).contains(APP_A));
|
||||
assertTrue(watchdog.getPackages(observer2).contains(APP_B));
|
||||
// 3
|
||||
assertEquals(1, watchdog.getPackages(observer3).size());
|
||||
assertTrue(watchdog.getPackages(observer3).contains(APP_A));
|
||||
|
||||
// Then advance time a little and run messages in Handlers so observer2 expires
|
||||
Thread.sleep(SHORT_DURATION);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Verify observer3 left with reduced expiry duration
|
||||
// 1
|
||||
assertNull(watchdog.getPackages(observer1));
|
||||
// 2
|
||||
assertNull(watchdog.getPackages(observer2));
|
||||
// 3
|
||||
assertEquals(1, watchdog.getPackages(observer3).size());
|
||||
assertTrue(watchdog.getPackages(observer3).contains(APP_A));
|
||||
|
||||
// Then advance time some more and run messages in Handlers so observer3 expires
|
||||
Thread.sleep(LONG_DURATION);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Verify observer3 expired
|
||||
// 1
|
||||
assertNull(watchdog.getPackages(observer1));
|
||||
// 2
|
||||
assertNull(watchdog.getPackages(observer2));
|
||||
// 3
|
||||
assertNull(watchdog.getPackages(observer3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test package observers are persisted and loaded on startup
|
||||
*/
|
||||
@Test
|
||||
public void testPersistence() throws Exception {
|
||||
PackageWatchdog watchdog1 = createWatchdog();
|
||||
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
|
||||
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
|
||||
|
||||
watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
|
||||
|
||||
// Verify 2 observers are registered and saved internally
|
||||
// 1
|
||||
assertEquals(1, watchdog1.getPackages(observer1).size());
|
||||
assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
|
||||
// 2
|
||||
assertEquals(2, watchdog1.getPackages(observer2).size());
|
||||
assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
|
||||
assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
|
||||
|
||||
|
||||
// Then advance time and run IO Handler so file is saved
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Then start a new watchdog
|
||||
PackageWatchdog watchdog2 = createWatchdog();
|
||||
|
||||
// Verify the new watchdog loads observers on startup but nothing registered
|
||||
assertEquals(0, watchdog2.getPackages(observer1).size());
|
||||
assertEquals(0, watchdog2.getPackages(observer2).size());
|
||||
// Verify random observer not saved returns null
|
||||
assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
|
||||
|
||||
// Then regiser observer1
|
||||
watchdog2.registerHealthObserver(observer1);
|
||||
watchdog2.registerHealthObserver(observer2);
|
||||
|
||||
// Verify 2 observers are registered after reload
|
||||
// 1
|
||||
assertEquals(1, watchdog1.getPackages(observer1).size());
|
||||
assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
|
||||
// 2
|
||||
assertEquals(2, watchdog1.getPackages(observer2).size());
|
||||
assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
|
||||
assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test package failure under threshold does not notify observers
|
||||
*/
|
||||
@Test
|
||||
public void testNoPackageFailureBeforeThreshold() throws Exception {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
|
||||
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
|
||||
|
||||
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
|
||||
// Then fail APP_A below the threshold
|
||||
for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
|
||||
watchdog.onPackageFailure(new String[]{APP_A});
|
||||
}
|
||||
|
||||
// Verify that observers are not notified
|
||||
assertEquals(0, observer1.mFailedPackages.size());
|
||||
assertEquals(0, observer2.mFailedPackages.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test package failure and notifies all observer since none handles the failure
|
||||
*/
|
||||
@Test
|
||||
public void testPackageFailureNotifyAll() throws Exception {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
|
||||
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
|
||||
|
||||
// Start observing for observer1 and observer2 without handling failures
|
||||
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
|
||||
|
||||
// Then fail APP_A and APP_B above the threshold
|
||||
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
|
||||
watchdog.onPackageFailure(new String[]{APP_A, APP_B});
|
||||
}
|
||||
|
||||
// Verify all observers are notifed of all package failures
|
||||
List<String> observer1Packages = observer1.mFailedPackages;
|
||||
List<String> observer2Packages = observer2.mFailedPackages;
|
||||
assertEquals(2, observer1Packages.size());
|
||||
assertEquals(1, observer2Packages.size());
|
||||
assertEquals(APP_A, observer1Packages.get(0));
|
||||
assertEquals(APP_B, observer1Packages.get(1));
|
||||
assertEquals(APP_A, observer2Packages.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test package failure and notifies only one observer because it handles the failure
|
||||
*/
|
||||
@Test
|
||||
public void testPackageFailureNotifyOne() throws Exception {
|
||||
PackageWatchdog watchdog = createWatchdog();
|
||||
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */);
|
||||
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */);
|
||||
|
||||
// Start observing for observer1 and observer2 with failure handling
|
||||
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
|
||||
|
||||
// Then fail APP_A above the threshold
|
||||
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
|
||||
watchdog.onPackageFailure(new String[]{APP_A});
|
||||
}
|
||||
|
||||
// Verify only one observer is notifed
|
||||
assertEquals(1, observer1.mFailedPackages.size());
|
||||
assertEquals(APP_A, observer1.mFailedPackages.get(0));
|
||||
assertEquals(0, observer2.mFailedPackages.size());
|
||||
}
|
||||
|
||||
private PackageWatchdog createWatchdog() {
|
||||
return new PackageWatchdog(InstrumentationRegistry.getContext(),
|
||||
mTestLooper.getLooper());
|
||||
}
|
||||
|
||||
private static class TestObserver implements PackageHealthObserver {
|
||||
private final String mName;
|
||||
private boolean mShouldHandle;
|
||||
final List<String> mFailedPackages = new ArrayList<>();
|
||||
|
||||
TestObserver(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
TestObserver(String name, boolean shouldHandle) {
|
||||
mName = name;
|
||||
mShouldHandle = shouldHandle;
|
||||
}
|
||||
|
||||
public boolean onHealthCheckFailed(String packageName) {
|
||||
mFailedPackages.add(packageName);
|
||||
return mShouldHandle;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user