blob: 4b903dc36b62948ebac099086f26a37a606ff506 [file] [log] [blame]
/*
* 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.cts.userspacereboot.host;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.RequiresDevice;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
/**
* Host side CTS tests verifying userspace reboot functionality.
*/
@RequiresDevice
@RunWith(DeviceJUnit4ClassRunner.class)
public class UserspaceRebootHostTest extends BaseHostJUnit4Test {
private static final String USERSPACE_REBOOT_SUPPORTED_PROP =
"init.userspace_reboot.is_supported";
private static final String BASIC_TEST_APP_APK = "BasicUserspaceRebootTestApp.apk";
private static final String BASIC_TEST_APP_PACKAGE_NAME =
"com.android.cts.userspacereboot.basic";
private static final String BOOT_COMPLETED_TEST_APP_APK =
"BootCompletedUserspaceRebootTestApp.apk";
private static final String BOOT_COMPLETED_TEST_APP_PACKAGE_NAME =
"com.android.cts.userspacereboot.bootcompleted";
private void runDeviceTest(String pkgName, String className, String testName) throws Exception {
runDeviceTests(pkgName, pkgName + "." + className, testName);
}
private void runDeviceTest(String pkgName, String className, String testName, Duration timeout)
throws Exception {
runDeviceTests(
getDevice(), pkgName, pkgName + "." + className, testName, timeout.toMillis());
}
private void installApk(String apkFileName) throws Exception {
CompatibilityBuildHelper helper = new CompatibilityBuildHelper(getBuild());
getDevice().installPackage(helper.getTestFile(apkFileName), false, true,
getDevice().isAppEnumerationSupported()
? new String[]{"--force-queryable"}
: new String[]{});
}
/**
* Sets up device to run a test case.
*/
@Before
public void setUp() throws Exception {
getDevice().uninstallPackage(BASIC_TEST_APP_PACKAGE_NAME);
getDevice().uninstallPackage(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME);
}
/**
* Cleans up device after a test case.
*/
@After
public void cleanUp() throws Exception {
getDevice().uninstallPackage(BASIC_TEST_APP_PACKAGE_NAME);
getDevice().uninstallPackage(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME);
getDevice().disableAdbRoot();
}
/**
* Asserts that only file-based encrypted devices can support userspace reboot.
*/
@Test
public void testOnlyFbeDevicesSupportUserspaceReboot() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
assertThat(getDevice().getProperty("ro.crypto.state")).isEqualTo("encrypted");
assertThat(getDevice().getProperty("ro.crypto.type")).isEqualTo("file");
}
/**
* Tests that on devices supporting userspace reboot {@code
* PowerManager.isRebootingUserspaceSupported()} returns {@code true}.
*/
@Test
public void testDeviceSupportsUserspaceReboot() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
installApk(BASIC_TEST_APP_APK);
runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest",
"testUserspaceRebootIsSupported");
}
/**
* Tests that on devices not supporting userspace reboot {@code
* PowerManager.isRebootingUserspaceSupported()} returns {@code false}.
*/
@Test
public void testDeviceDoesNotSupportUserspaceReboot() throws Exception {
assumeFalse("Userspace reboot supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
installApk(BASIC_TEST_APP_APK);
// Also verify that PowerManager.isRebootingUserspaceSupported will return true
runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest",
"testUserspaceRebootIsNotSupported");
}
/**
* Tests that userspace reboot succeeds and doesn't fall back to full reboot.
*/
@Test
public void testUserspaceReboot() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
rebootUserspaceAndWaitForBootComplete();
assertUserspaceRebootSucceed();
}
/**
* Tests that userspace reboot with fs-checkpointing succeeds and doesn't fall back to full
* reboot.
*/
@Test
public void testUserspaceRebootWithCheckpoint() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
assumeTrue("Device doesn't support fs checkpointing", isFsCheckpointingSupported());
CommandResult result = getDevice().executeShellV2Command("sm start-checkpoint 1");
Thread.sleep(500);
assertWithMessage("Failed to start checkpoint : %s", result.getStderr()).that(
result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
rebootUserspaceAndWaitForBootComplete();
assertUserspaceRebootSucceed();
}
/**
* Tests that CE storage is unlocked after userspace reboot.
*/
@Test
public void testUserspaceReboot_verifyCeStorageIsUnlocked() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
try {
getDevice().executeShellV2Command("cmd lock_settings set-pin 1543");
installApk(BOOT_COMPLETED_TEST_APP_APK);
installApk(BASIC_TEST_APP_APK);
prepareForCeTestCases();
rebootUserspaceAndWaitForBootComplete();
assertUserspaceRebootSucceed();
// Now it's time to verify our assumptions.
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"testVerifyCeStorageUnlocked");
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"testVerifyReceivedLockedBootCompletedBroadcast", Duration.ofMinutes(3));
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"testVerifyReceivedBootCompletedBroadcast", Duration.ofMinutes(6));
} finally {
getDevice().executeShellV2Command("cmd lock_settings clear --old 1543");
getDevice().executeShellV2Command("reboot");
}
}
/**
* Tests that CE storage is unlocked after userspace reboot with fs-checkpointing.
*/
@Test
public void testUserspaceRebootWithCheckpoint_verifyCeStorageIsUnlocked() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
assumeTrue("Device doesn't support fs checkpointing", isFsCheckpointingSupported());
try {
CommandResult result = getDevice().executeShellV2Command("sm start-checkpoint 1");
Thread.sleep(500);
assertWithMessage("Failed to start checkpoint : %s", result.getStderr()).that(
result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
getDevice().executeShellV2Command("cmd lock_settings set-pin 1543");
installApk(BOOT_COMPLETED_TEST_APP_APK);
installApk(BASIC_TEST_APP_APK);
prepareForCeTestCases();
rebootUserspaceAndWaitForBootComplete();
assertUserspaceRebootSucceed();
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"testVerifyCeStorageUnlocked");
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"testVerifyReceivedLockedBootCompletedBroadcast", Duration.ofMinutes(3));
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"testVerifyReceivedBootCompletedBroadcast", Duration.ofMinutes(6));
} finally {
getDevice().executeShellV2Command("cmd lock_settings clear --old 1543");
getDevice().executeShellV2Command("reboot");
}
}
private void prepareForCeTestCases() throws Exception {
runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
"prepareFile");
runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest", "prepareFile");
// In order to test that broadcasts are correctly sent, we need to have a separate app that
// is going to be listen for them. Unfortunately, we can't use BOOT_COMPLETED_TEST_APP_APK
// because every call to `am instrument` force stops an app. This doesn't play well with
// BOOT_COMPLETED broadcast, which is not sent to stopped apps.
// Send an intent to our "broadcast listening" test app to kick it out from stopped state.
getDevice().executeShellV2Command("am start -a android.intent.action.MAIN"
+ " --user 0"
+ " -c android.intent.category.LAUNCHER "
+ BASIC_TEST_APP_PACKAGE_NAME + "/.LauncherActivity");
// Wait enough for PackageManager to persist new state of test app.
// I wish there was a better way to synchronize here...
Thread.sleep(15000);
}
/**
* Asserts that fallback to hard reboot is triggered when a native process fails to stop in a
* given timeout.
*/
@Test
@RequiresDevice // TODO(b/154709530): Remove dependency on physical device
public void testUserspaceRebootFailsKillingProcesses() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
assumeTrue("This test requires root", getDevice().enableAdbRoot());
final String sigkillTimeout =
getProperty("init.userspace_reboot.sigkill.timeoutmillis", "");
final String sigtermTimeout =
getProperty("init.userspace_reboot.sigterm.timeoutmillis", "");
try {
// Explicitly set a very low value to make sure that safety mechanism kicks in.
getDevice().setProperty("init.userspace_reboot.sigkill.timeoutmillis", "10");
getDevice().setProperty("init.userspace_reboot.sigterm.timeoutmillis", "10");
rebootUserspaceAndWaitForBootComplete();
assertUserspaceRebootFailed();
assertLastBootReasonIs("userspace_failed,shutdown_aborted,sigkill");
} finally {
getDevice().setProperty("init.userspace_reboot.sigkill.timeoutmillis", sigkillTimeout);
getDevice().setProperty("init.userspace_reboot.sigterm.timeoutmillis", sigtermTimeout);
}
}
/**
* Asserts that fallback to hard reboot is triggered when userspace reboot fails to finish in a
* given time.
*/
@Test
public void testUserspaceRebootWatchdogTriggers() throws Exception {
assumeTrue("Userspace reboot not supported on the device",
getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
assumeTrue("This test requires root", getDevice().enableAdbRoot());
final String defaultValue = getProperty("init.userspace_reboot.watchdog.timeoutmillis", "");
try {
// Explicitly set a very low value to make sure that safety mechanism kicks in.
getDevice().setProperty("init.userspace_reboot.watchdog.timeoutmillis", "1000");
rebootUserspaceAndWaitForBootComplete();
assertUserspaceRebootFailed();
assertLastBootReasonIs("userspace_failed,watchdog_triggered,failed_to_boot");
} finally {
getDevice().setProperty("init.userspace_reboot.watchdog.timeoutmillis", defaultValue);
}
}
// TODO(b/135984674): add test case that forces unmount of f2fs userdata.
/**
* Returns {@code true} if device supports fs-checkpointing.
*/
private boolean isFsCheckpointingSupported() throws Exception {
CommandResult result = getDevice().executeShellV2Command("sm supports-checkpoint");
assertWithMessage("Failed to check if fs checkpointing is supported : %s",
result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
return "true".equals(result.getStdout().trim());
}
/**
* Reboots a device and waits for the boot to complete.
*
* <p>Before rebooting, sets a value of sysprop {@code test.userspace_reboot.requested} to 1.
* Querying this property is then used in {@link #assertUserspaceRebootSucceed()} to assert that
* userspace reboot succeeded.
*/
private void rebootUserspaceAndWaitForBootComplete() throws Exception {
Duration timeout = Duration.ofMillis(getDevice().getIntProperty(
"init.userspace_reboot.watchdog.timeoutmillis", 0)).plusMinutes(2);
setProperty("test.userspace_reboot.requested", "1");
getDevice().rebootUserspaceUntilOnline();
assertWithMessage("Device did not boot within %s", timeout).that(
getDevice().waitForBootComplete(timeout.toMillis())).isTrue();
}
/**
* Asserts that userspace reboot succeeded by querying the value of {@code
* test.userspace_reboot.requested} property.
*/
private void assertUserspaceRebootSucceed() throws Exception {
// If userspace reboot fails and fallback to hard reboot is triggered then
// test.userspace_reboot.requested won't be set.
final String bootReason = getProperty("sys.boot.reason.last", "");
final boolean result = getDevice().getBooleanProperty("test.userspace_reboot.requested",
false);
assertWithMessage(
"Userspace reboot failed and fallback to full reboot was triggered. Boot reason: "
+ "%s", bootReason).that(result).isTrue();
}
/**
* Asserts that userspace reboot fails by querying the value of {@code
* test.userspace_reboot.requested} property.
*/
private void assertUserspaceRebootFailed() throws Exception {
// If userspace reboot fails and fallback to hard reboot is triggered then
// test.userspace_reboot.requested won't be set.
final boolean result = getDevice().getBooleanProperty("test.userspace_reboot.requested",
false);
assertWithMessage("Fallback to full reboot wasn't triggered").that(result).isFalse();
}
/**
* A wrapper over {@code adb shell setprop name value}.
*
* This is a temporary workaround until issues with {@code getDevice().setProperty()} API are
* resolved.
*/
private void setProperty(String name, String value) throws Exception {
final String cmd = String.format("\"setprop %s %s\"", name, value);
final CommandResult result = getDevice().executeShellV2Command(cmd);
assertWithMessage("Failed to call adb shell %s: %s", cmd, result.getStderr())
.that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
}
/**
* Asserts that normalized value of {@code sys.boot.reason.last} is equal to {@code expected}.
*/
private void assertLastBootReasonIs(final String expected) throws Exception {
String reason = getProperty("sys.boot.reason.last", "");
if (reason.startsWith("reboot,")) {
reason = reason.substring("reboot,".length());
}
assertThat(reason).isEqualTo(expected);
}
/**
* A wrapper over {@code getDevice().getProperty(name)} API that returns {@code defaultValue} if
* property with the given {@code name} doesn't exist.
*/
private String getProperty(String name, String defaultValue) throws Exception {
String ret = getDevice().getProperty(name);
return ret == null ? defaultValue : ret;
}
}