Add test for font crash protection.
This test modifies the installed font file with block_device_writer.
FontManagerService should detect it and remove the modified file.
Bug: 176939176
Test: atest ApkVerityTest
Test: atest UpdatableSystemFontTest
Change-Id: I7da3f2911459619d5d56a94e091b912d67cb27d3
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index d809fe8..43a5078 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -16,8 +16,14 @@
name: "UpdatableSystemFontTest",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
- static_libs: ["frameworks-base-hostutils"],
+ static_libs: [
+ "block_device_writer_jar",
+ "frameworks-base-hostutils",
+ ],
test_suites: ["general-tests", "vts"],
+ target_required: [
+ "block_device_writer_module",
+ ],
data: [
":NotoColorEmojiTtf",
":UpdatableSystemFontTestCertDer",
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index efe5d70..7b919bd 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -21,6 +21,7 @@
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
+ <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
<option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
<option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
<option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" />
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 6d161a5..e249f8a9 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -21,7 +21,9 @@
import android.platform.test.annotations.RootPermissionTest;
+import com.android.blockdevicewriter.BlockDeviceWriter;
import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -34,6 +36,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -126,6 +130,44 @@
TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
}
+ @Test
+ public void reboot() throws Exception {
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+ String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPath).startsWith("/data/fonts/files/");
+
+ expectRemoteCommandToSucceed("stop");
+ expectRemoteCommandToSucceed("start");
+ waitUntilFontCommandIsReady();
+ String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPathAfterReboot).isEqualTo(fontPath);
+ }
+
+ @Test
+ public void reboot_clearDamagedFiles() throws Exception {
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+ String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPath).startsWith("/data/fonts/files/");
+ assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isTrue();
+
+ BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), fontPath, 0);
+ expectRemoteCommandToSucceed("stop");
+ // We have to make sure system_server is gone before dropping caches, because system_server
+ // process holds font memory maps and prevents cache eviction.
+ waitUntilSystemServerIsGone();
+ BlockDeviceWriter.assertFileNotOpen(getDevice(), fontPath);
+ BlockDeviceWriter.dropCaches(getDevice());
+ assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isFalse();
+
+ expectRemoteCommandToSucceed("start");
+ waitUntilFontCommandIsReady();
+ String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertWithMessage("Damaged file should be deleted")
+ .that(fontPathAfterReboot).startsWith("/system");
+ }
+
private String getFontPath(String fontFileName) throws Exception {
// TODO: add a dedicated command for testing.
String lines = expectRemoteCommandToSucceed("cmd font dump");
@@ -153,4 +195,39 @@
.that(result.getStatus())
.isNotEqualTo(CommandStatus.SUCCESS);
}
+
+ private void waitUntilFontCommandIsReady() {
+ waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+ try {
+ return getDevice().executeShellV2Command("cmd font status").getStatus()
+ == CommandStatus.SUCCESS;
+ } catch (DeviceNotAvailableException e) {
+ return false;
+ }
+ });
+ }
+
+ private void waitUntilSystemServerIsGone() {
+ waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+ try {
+ return getDevice().executeShellV2Command("pid system_server").getStatus()
+ == CommandStatus.FAILED;
+ } catch (DeviceNotAvailableException e) {
+ return false;
+ }
+ });
+ }
+
+ private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
+ long untilMillis = System.currentTimeMillis() + timeoutMillis;
+ do {
+ if (func.get()) return;
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new AssertionError("Interrupted", e);
+ }
+ } while (System.currentTimeMillis() < untilMillis);
+ throw new AssertionError("Timed out");
+ }
}