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

Commit b521848a authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topics "cr-flagReset3", "cr-flagReset4" into main

* changes:
  Add tests for RescueParty explicit health checks
  Verify that FlagReset is triggered during bootloop
parents 103d8b43 9e4fa17e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ android_test {
        "mockito-target-extended-minus-junit4",
        "services.core",
        "services.net",
        "platform-parametric-runner-lib-no-robo",
        "truth",
    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
        "true": [
+220 −27
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
@@ -32,43 +33,74 @@ import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
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.Settings;
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;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.RescueParty.RescuePartyObserver;
import com.android.server.am.SettingsToPropertiesMapper;
import com.android.server.crashrecovery.CrashRecoveryUtils;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;

import java.lang.reflect.Field;
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;


/**
 * Test RescueParty.
 */
@RunWith(ParameterizedAndroidJunit4.class)
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() {
        return FlagsParameterization.allCombinationsOf(Flags.FLAG_FLAG_RESET_DISABLED,
                Flags.FLAG_FLAG_RESET_ENABLED);
    }

    private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;

    private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
@@ -79,6 +111,9 @@ public class RescuePartyTest {
            "persist.device_config.configuration.disable_rescue_party";
    private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
            "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;
@@ -91,10 +126,16 @@ 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;

    public RescuePartyTest(FlagsParameterization flags) {
        mSetFlagsRule = new SetFlagsRule(flags);
    }

    // Mock only sysprop apis
    private PackageWatchdog.BootThreshold mSpyBootThreshold;

@@ -106,15 +147,15 @@ public class RescuePartyTest {
                        .strictness(Strictness.LENIENT)
                        .spyStatic(DeviceConfig.class)
                        .spyStatic(SystemProperties.class)
                        .spyStatic(Settings.Global.class)
                        .spyStatic(Settings.Secure.class)
                        .spyStatic(SettingsToPropertiesMapper.class)
                        .spyStatic(RecoverySystem.class)
                        .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);
@@ -169,42 +210,79 @@ 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)
                .when(() -> PackageWatchdog.getInstance(mMockContext));
        mockCrashRecoveryProperties(mMockPackageWatchdog);

        // Mock CrashRecoveryUtils
        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
    public void testBootLoopNoFlags() {
        // this is old test where the flag needs to be disabled
    public void testBootLoopExecution_flagResetEnabled() {
        assumeTrue(RescueParty.isFlagResetEnabled());
        noteBoot(1);
        verify(() -> DeviceConfig.resetToDefaults(anyInt(), eq(NAMESPACE_TO_RESET1)));
        verify(() -> DeviceConfig.resetToDefaults(anyInt(), eq(NAMESPACE_TO_RESET2)));
        assertFalse(RescueParty.isRecoveryTriggeredReboot());

        noteBoot(2);
        assertTrue(RescueParty.isRebootPropertySet());

        setCrashRecoveryPropAttemptingReboot(false);
        noteBoot(3);
        assertTrue(RescueParty.isFactoryResetPropertySet());
    }

    @Test
    public void testBootLoopExecution_flagResetDisabled() {
        assumeFalse(RescueParty.isFlagResetEnabled());
        noteBoot(1);
        assertTrue(RescueParty.isRebootPropertySet());

@@ -368,13 +446,123 @@ public class RescuePartyTest {
    }

    @Test
    public void testBootLoopLevelsNoFlags() {
    public void testBootLoopLevels_flagResetEnabled() {
        assumeTrue(RescueParty.isFlagResetEnabled());
        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);

        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_40, observer.onBootLoop(1));
        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, observer.onBootLoop(2));
        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_100, observer.onBootLoop(3));
        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_100, observer.onBootLoop(4));
    }

    @Test
    public void testBootLoopLevels_flagResetDisabled() {
        assumeFalse(RescueParty.isFlagResetEnabled());
        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);

        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, observer.onBootLoop(1));
        assertEquals(PackageHealthObserverImpact.USER_IMPACT_LEVEL_100, observer.onBootLoop(2));
        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);
@@ -448,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
@@ -520,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);
    }
}