Skip to content
Snippets Groups Projects
Commit f0c113ea authored by Kohsuke Yatoh's avatar Kohsuke Yatoh
Browse files

Verify updated font is used in app process.

This CL adds a VTS test that:
1. Updates NotoColorEmoji font
2. Launches a test app that renders an emoji
3. Verifies that the updated NotoColorEmoji font file is used by the app
   process.

Bug: 180370569
Test: atest UpdatableSystemFontTest
Change-Id: I418d7cc23a290ebe4ae6e5b8af782b336497fbdd
parent 55c108fd
No related branches found
No related tags found
No related merge requests found
......@@ -19,6 +19,11 @@
<!-- This test requires root to side load fs-verity cert. -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="EmojiRenderingTestApp.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
......
// Copyright (C) 2021 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
android_test_helper_app {
name: "EmojiRenderingTestApp",
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.java"],
test_suites: [
"general-tests",
"vts",
],
}
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2021 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.emojirenderingtestapp">
<application>
<activity android:name=".EmojiRenderingTestActivity"/>
</application>
</manifest>
/*
* Copyright (C) 2021 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.emojirenderingtestapp;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
/** Test app to render an emoji. */
public class EmojiRenderingTestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout container = new LinearLayout(this);
container.setOrientation(LinearLayout.VERTICAL);
TextView textView = new TextView(this);
textView.setText("\uD83E\uDD72"); // 🥲
container.addView(textView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
setContentView(container);
}
}
......@@ -36,7 +36,6 @@ 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;
......@@ -47,6 +46,9 @@ import java.util.regex.Pattern;
@RunWith(DeviceJUnit4ClassRunner.class)
public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
private static final String SYSTEM_FONTS_DIR = "/system/fonts/";
private static final String DATA_FONTS_DIR = "/data/fonts/files/";
private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der";
private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)");
......@@ -72,6 +74,14 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG =
"/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig";
private static final String EMOJI_RENDERING_TEST_APP_ID = "com.android.emojirenderingtestapp";
private static final String EMOJI_RENDERING_TEST_ACTIVITY =
EMOJI_RENDERING_TEST_APP_ID + "/.EmojiRenderingTestActivity";
private interface ThrowingSupplier<T> {
T get() throws Exception;
}
@Rule
public final AddFsVerityCertRule mAddFsverityCertRule =
new AddFsVerityCertRule(this, CERT_PATH);
......@@ -91,7 +101,10 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith("/data/fonts/files/");
assertThat(fontPath).startsWith(DATA_FONTS_DIR);
// The updated font should be readable and unmodifiable.
expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
expectRemoteCommandToFail("echo -n '' >> " + fontPath);
}
@Test
......@@ -102,8 +115,12 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath2).startsWith("/data/fonts/files/");
assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
assertThat(fontPath2).isNotEqualTo(fontPath);
// The new file should be readable.
expectRemoteCommandToSucceed("cat " + fontPath2 + " > /dev/null");
// The old file should be still readable.
expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
}
@Test
......@@ -119,24 +136,13 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith("/data/fonts/files/");
assertThat(fontPath).startsWith(DATA_FONTS_DIR);
assertThat(fontPath2).isNotEqualTo(fontPath);
assertThat(fontPath2).startsWith("/data/fonts/files/");
assertThat(fontPath3).startsWith("/data/fonts/files/");
assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
assertThat(fontPath3).startsWith(DATA_FONTS_DIR);
assertThat(fontPath3).isNotEqualTo(fontPath);
}
@Test
public void updatedFont_dataFileIsImmutableAndReadable() throws Exception {
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith("/data");
expectRemoteCommandToFail("echo -n '' >> " + fontPath);
expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
}
@Test
public void updateFont_invalidCert() throws Exception {
expectRemoteCommandToFail(String.format("cmd font update %s %s",
......@@ -157,12 +163,38 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
}
@Test
public void launchApp() throws Exception {
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith(SYSTEM_FONTS_DIR);
expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
waitUntil(TimeUnit.SECONDS.toMillis(5), () ->
isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID));
}
@Test
public void launchApp_afterUpdateFont() throws Exception {
String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR);
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR);
expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
// The original font should NOT be opened by the app.
waitUntil(TimeUnit.SECONDS.toMillis(5), () ->
isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID)
&& !isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID));
}
@Test
public void reboot() throws Exception {
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith("/data/fonts/files/");
assertThat(fontPath).startsWith(DATA_FONTS_DIR);
expectRemoteCommandToSucceed("stop");
expectRemoteCommandToSucceed("start");
......@@ -210,16 +242,40 @@ public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
});
}
private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
private void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
long untilMillis = System.currentTimeMillis() + timeoutMillis;
do {
if (func.get()) return;
try {
if (func.get()) return;
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError("Interrupted", e);
} catch (Exception e) {
throw new AssertionError("Unexpected exception", e);
}
} while (System.currentTimeMillis() < untilMillis);
throw new AssertionError("Timed out");
}
private boolean isFileOpenedBy(String path, String appId) throws DeviceNotAvailableException {
String pid = pidOf(appId);
if (pid.isEmpty()) {
return false;
}
CommandResult result = getDevice().executeShellV2Command(
String.format("lsof -t -p %s '%s'", pid, path));
if (result.getStatus() != CommandStatus.SUCCESS) {
return false;
}
// The file is open if the output of lsof is non-empty.
return !result.getStdout().trim().isEmpty();
}
private String pidOf(String appId) throws DeviceNotAvailableException {
CommandResult result = getDevice().executeShellV2Command("pidof " + appId);
if (result.getStatus() != CommandStatus.SUCCESS) {
return "";
}
return result.getStdout().trim();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment