Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e4fa17e authored by Harshit Mahajan's avatar Harshit Mahajan
Browse files

Add tests for RescueParty explicit health checks

RescueParty would trigger device config monitoring after initialization
of it's observer.
When the flags are updated, observer will receive callback for the same
and trigger explicit network health check with PackageWatchdog.
PackageWatchdog will inform RescueParty if the network check fails.

Bug: 397776123
Test: atest RescuePartyTest
Flag: com.android.crashrecovery.flags.flag_reset_enabled
Change-Id: Ib92b5e33a628a8b0433b2967b66f179f7213911c
parent 708012f4
Loading
Loading
Loading
Loading
+151 −16
Original line number Diff line number Diff line
@@ -37,17 +37,24 @@ import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;

import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.TestLooperManager;
import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
import android.util.Slog;

import com.android.crashrecovery.flags.Flags;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -74,6 +81,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

/**
@@ -83,6 +91,9 @@ import java.util.concurrent.TimeUnit;
public class RescuePartyTest {

    @Rule public final SetFlagsRule mSetFlagsRule;
    private HandlerThread mTestHandlerThread;
    private TestLooperManager mTestLooperManager;
    private Handler mTestHandler;

    @Parameters(name = "{0}")
    public static List<FlagsParameterization> getParams() {
@@ -102,6 +113,7 @@ public class RescuePartyTest {
            "persist.device_config.configuration.disable_rescue_party_factory_reset";
    private static final String NAMESPACE_TO_RESET1 = "foo_ns";
    private static final String NAMESPACE_TO_RESET2 = "bar_ns";
    private static final String NETWORK_STACK_PACKAGE_NAME = "com.android.networkstack";

    private MockitoSession mSession;
    private HashMap<String, String> mSystemSettingsMap;
@@ -114,6 +126,8 @@ public class RescuePartyTest {
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private PackageWatchdog mMockPackageWatchdog;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private OnPropertiesChangedListener mMockOnPropertiesChangedListener;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private ContentResolver mMockContentResolver;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private PackageManager mPackageManager;
@@ -137,9 +151,11 @@ public class RescuePartyTest {
                        .spyStatic(RescueParty.class)
                        .spyStatic(PackageWatchdog.class)
                        .spyStatic(CrashRecoveryUtils.class)
                        .spyStatic(Slog.class)
                        .startMocking();
        mSystemSettingsMap = new HashMap<>();
        mNamespacesWiped = new HashSet<>();
        mCrashRecoveryPropertiesMap = new HashMap<>();

        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
@@ -194,21 +210,16 @@ public class RescuePartyTest {
        ).when(() -> SystemProperties.getLong(anyString(), anyLong()));

        // Mock DeviceConfig
        doAnswer((Answer<Boolean>) invocationOnMock -> true)
                .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
                        anyBoolean()));
        doAnswer((Answer<Void>) invocationOnMock -> null)
                .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
        doAnswer((Answer<Boolean>) invocationOnMock -> {
                    DeviceConfig.Properties properties = invocationOnMock.getArgument(0);
                    String namespace = properties.getNamespace();
                    // record a wipe
                    if (properties.getKeyset().isEmpty()) {
        doAnswer((Answer<Void>) invocationOnMock -> {
            String namespace = invocationOnMock.getArgument(1);
            mNamespacesWiped.add(namespace);
                    }
                    return true;
                }
        ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class)));
            return null;
        }).when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
        doAnswer((Answer<Void>) invocationOnMock -> {
            mMockOnPropertiesChangedListener = invocationOnMock.getArgument(2);
            return null;
        }).when(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(),
                        any(Executor.class), any(OnPropertiesChangedListener.class)));

        // Mock PackageWatchdog
        doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
@@ -219,17 +230,38 @@ public class RescuePartyTest {
        doAnswer((Answer<Set<String>>) invocationOnMock ->
                Set.of(NAMESPACE_TO_RESET1, NAMESPACE_TO_RESET2))
                .when(CrashRecoveryUtils::getFlagNamespacesInModules);
        doAnswer((Answer<String>) invocationOnMock -> NETWORK_STACK_PACKAGE_NAME)
                .when(() -> CrashRecoveryUtils.getNetworkStackPackageName(mMockContext));

        // Mock Slog
        doAnswer((Answer<Integer>) invocationOnMock -> 0)
                .when(() -> android.util.Slog.wtf(anyString(), anyString()));

        doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());

        setCrashRecoveryPropRescueBootCount(0);
        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));

        mTestHandlerThread = new HandlerThread("TestHandlerThread");
        mTestHandlerThread.start();
        mTestLooperManager = new TestLooperManager(mTestHandlerThread.getLooper());
        mTestHandler =  new Handler(mTestHandlerThread.getLooper());

    }

    @After
    public void tearDown() throws Exception {
        mSession.finishMocking();
        if (mTestLooperManager != null) {
            mTestLooperManager.release();
        }
        if (mTestHandlerThread != null) {
            mTestHandlerThread.quitSafely();
        }
        mSystemSettingsMap.clear();
        mNamespacesWiped.clear();
        mCrashRecoveryPropertiesMap.clear();
    }

    @Test
@@ -434,6 +466,104 @@ public class RescuePartyTest {
        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_100, observer.onBootLoop(3));
    }

    @Test
    public void testExplicitHealthCheckTriggersAfterFlagUpdate() throws Exception {
        assumeTrue(RescueParty.isFlagResetEnabled());

        Properties properties1 = new Properties.Builder(NAMESPACE_TO_RESET1)
                .setString("KEY1", "VALUE1").build();
        Properties properties2 = new Properties.Builder(NAMESPACE_TO_RESET2)
                .setString("KEY1", "VALUE1").build();
        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
        injectTestHandler(observer);

        verify(CrashRecoveryUtils::getFlagNamespacesInModules);
        verify(() -> CrashRecoveryUtils.getNetworkStackPackageName(mMockContext));
        observer.initializeDeviceConfigMonitoringIfRequiredAsync();
        mTestLooperManager.execute(mTestLooperManager.next());

        verify(() -> DeviceConfig.addOnPropertiesChangedListener(eq(NAMESPACE_TO_RESET1),
                any(Executor.class), any(OnPropertiesChangedListener.class)));
        verify(() -> DeviceConfig.addOnPropertiesChangedListener(eq(NAMESPACE_TO_RESET2),
                any(Executor.class), any(OnPropertiesChangedListener.class)));

        // verify that startExplicitHealthCheck was not triggered immediately
        mMockOnPropertiesChangedListener.onPropertiesChanged(properties1);
        mMockOnPropertiesChangedListener.onPropertiesChanged(properties2);
        verify(mMockPackageWatchdog, times(0)).startExplicitHealthCheck(any(List.class),
                any(Long.class), eq(observer));

        // Reset the delay to 1 sec and update property
        Field mDefaultHealthCheckTriggerDelayField = observer.getClass()
                .getDeclaredField("DEFAULT_HEALTH_CHECK_TRIGGER_DELAY_MS");
        mDefaultHealthCheckTriggerDelayField.setAccessible(true);
        mDefaultHealthCheckTriggerDelayField.set(observer, 1000);
        mMockOnPropertiesChangedListener.onPropertiesChanged(properties1);
        mMockOnPropertiesChangedListener.onPropertiesChanged(properties2);
        mTestLooperManager.execute(mTestLooperManager.next());

        // verify that explicit health check was triggered exactly once.
        verify(mMockPackageWatchdog).startExplicitHealthCheck(any(List.class),
                eq(observer.DEFAULT_OBSERVING_DURATION_MS),
                eq(observer));
    }

    @Test
    public void testObserverCreation_emptyNamespaces() throws Exception {
        assumeTrue(RescueParty.isFlagResetEnabled());
        doAnswer((Answer<Set<String>>) invocationOnMock -> Set.of())
                .when(CrashRecoveryUtils::getFlagNamespacesInModules);

        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
        injectTestHandler(observer);

        observer.initializeDeviceConfigMonitoringIfRequiredAsync();

        // Verify no listeners are added
        verify(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(),
                any(Executor.class), any(OnPropertiesChangedListener.class)), times(0));
    }

    @Test
    public void testObserverCreation_nullNetworkPackage() throws Exception {
        assumeTrue(RescueParty.isFlagResetEnabled());
        doAnswer((Answer<String>) invocationOnMock -> null)
                .when(() -> CrashRecoveryUtils.getNetworkStackPackageName(mMockContext));

        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
        injectTestHandler(observer);

        verify(() -> android.util.Slog.wtf(eq(RescueParty.TAG),
                eq("Unable to find NetworkPackage for monitoring network health")));

        observer.initializeDeviceConfigMonitoringIfRequiredAsync();

        // Verify no listeners are added
        verify(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(),
                any(Executor.class), any(OnPropertiesChangedListener.class)), times(0));
    }

    @Test
    public void testObserverCreation_emptyNamespaces_nullNetworkPackage() throws Exception {
        assumeTrue(RescueParty.isFlagResetEnabled());
        doAnswer((Answer<Set<String>>) invocationOnMock -> Set.of())
                .when(CrashRecoveryUtils::getFlagNamespacesInModules);
        doAnswer((Answer<String>) invocationOnMock -> null)
                .when(() -> CrashRecoveryUtils.getNetworkStackPackageName(mMockContext));

        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
        injectTestHandler(observer);

        verify(() -> android.util.Slog.wtf(eq(RescueParty.TAG),
                eq("Unable to find NetworkPackage for monitoring network health")));

        observer.initializeDeviceConfigMonitoringIfRequiredAsync();

        // Verify no listeners are added
        verify(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(),
                any(Executor.class), any(OnPropertiesChangedListener.class)), times(0));
    }

    private void noteBoot(int mitigationCount) {
        RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount);
    }
@@ -506,7 +636,6 @@ public class RescuePartyTest {
            mSpyBootThreshold = spy(watchdog.new BootThreshold(
                    PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
                    PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
            mCrashRecoveryPropertiesMap = new HashMap<>();

            doAnswer((Answer<Integer>) invocationOnMock -> {
                String storedValue = mCrashRecoveryPropertiesMap
@@ -578,4 +707,10 @@ public class RescuePartyTest {
        mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset",
                Long.toString(value));
    }

    private void injectTestHandler(RescuePartyObserver observer) throws Exception {
        Field mHandlerField = observer.getClass().getDeclaredField("mHandler");
        mHandlerField.setAccessible(true);
        mHandlerField.set(observer, mTestHandler);
    }
}