blob: 2aedad2d4144693c9ea2e064623b987fdad9fcad [file] [log] [blame]
/*
* Copyright (C) 2010 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 android.os.cts;
import static android.os.Build.VERSION.ACTIVE_CODENAMES;
import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
import android.os.Build;
import android.os.SystemProperties;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.RestrictedBuildTest;
import junit.framework.TestCase;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class BuildTest extends TestCase {
private static final String RO_PRODUCT_CPU_ABILIST = "ro.product.cpu.abilist";
private static final String RO_PRODUCT_CPU_ABILIST32 = "ro.product.cpu.abilist32";
private static final String RO_PRODUCT_CPU_ABILIST64 = "ro.product.cpu.abilist64";
/**
* Verify that the values of the various CPU ABI fields are consistent.
*/
@AppModeFull(reason = "Instant apps cannot access APIs")
public void testCpuAbi() throws Exception {
runTestCpuAbiCommon();
if (android.os.Process.is64Bit()) {
runTestCpuAbi64();
} else {
runTestCpuAbi32();
}
}
/**
* Verify that the CPU ABI fields on device match the permitted ABIs defined by CDD.
*/
public void testCpuAbi_valuesMatchPermitted() throws Exception {
// The permitted ABIs are listed in https://developer.android.com/ndk/guides/abis.
Set<String> just32 = new HashSet<>(Arrays.asList("armeabi", "armeabi-v7a", "x86"));
Set<String> just64 = new HashSet<>(Arrays.asList("x86_64", "arm64-v8a"));
Set<String> all = new HashSet<>();
all.addAll(just32);
all.addAll(just64);
Set<String> allAndEmpty = new HashSet<>(all);
allAndEmpty.add("");
// The cpu abi fields on the device must match the permitted values.
assertValueIsAllowed(all, Build.CPU_ABI);
// CPU_ABI2 will be empty when the device does not support a secondary CPU architecture.
assertValueIsAllowed(allAndEmpty, Build.CPU_ABI2);
// The supported abi fields on the device must match the permitted values.
assertValuesAreAllowed(all, Build.SUPPORTED_ABIS);
assertValuesAreAllowed(just32, Build.SUPPORTED_32_BIT_ABIS);
assertValuesAreAllowed(just64, Build.SUPPORTED_64_BIT_ABIS);
}
private void runTestCpuAbiCommon() throws Exception {
// The build property must match Build.SUPPORTED_ABIS exactly.
final String[] abiListProperty = getStringList(RO_PRODUCT_CPU_ABILIST);
assertEquals(Arrays.toString(abiListProperty), Arrays.toString(Build.SUPPORTED_ABIS));
List<String> abiList = Arrays.asList(abiListProperty);
// Every supported 32 bit ABI must be present in Build.SUPPORTED_ABIS.
for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
assertTrue(abiList.contains(abi));
assertFalse(Build.is64BitAbi(abi));
}
// Every supported 64 bit ABI must be present in Build.SUPPORTED_ABIS.
for (String abi : Build.SUPPORTED_64_BIT_ABIS) {
assertTrue(abiList.contains(abi));
assertTrue(Build.is64BitAbi(abi));
}
// Build.CPU_ABI and Build.CPU_ABI2 must be present in Build.SUPPORTED_ABIS.
assertTrue(abiList.contains(Build.CPU_ABI));
if (!Build.CPU_ABI2.isEmpty()) {
assertTrue(abiList.contains(Build.CPU_ABI2));
}
}
private void runTestCpuAbi32() throws Exception {
List<String> abi32 = Arrays.asList(Build.SUPPORTED_32_BIT_ABIS);
assertTrue(abi32.contains(Build.CPU_ABI));
if (!Build.CPU_ABI2.isEmpty()) {
assertTrue(abi32.contains(Build.CPU_ABI2));
}
}
private void runTestCpuAbi64() {
List<String> abi64 = Arrays.asList(Build.SUPPORTED_64_BIT_ABIS);
assertTrue(abi64.contains(Build.CPU_ABI));
if (!Build.CPU_ABI2.isEmpty()) {
assertTrue(abi64.contains(Build.CPU_ABI2));
}
}
private String[] getStringList(String property) throws IOException {
String value = getProperty(property);
if (value.isEmpty()) {
return new String[0];
} else {
return value.split(",");
}
}
/**
* @param property name passed to getprop
*/
static String getProperty(String property)
throws IOException {
Process process = new ProcessBuilder("getprop", property).start();
Scanner scanner = null;
String line = "";
try {
scanner = new Scanner(process.getInputStream());
line = scanner.nextLine();
} finally {
if (scanner != null) {
scanner.close();
}
}
return line;
}
/**
* @param message shown when the test fails
* @param property name passed to getprop
* @param expected value of the property
*/
private void assertProperty(String message, String property, String expected)
throws IOException {
Process process = new ProcessBuilder("getprop", property).start();
Scanner scanner = null;
try {
scanner = new Scanner(process.getInputStream());
String line = scanner.nextLine();
assertEquals(message + " Value found: " + line , expected, line);
assertFalse(scanner.hasNext());
} finally {
if (scanner != null) {
scanner.close();
}
}
}
/**
* Check that a property is not set by scanning through the list of properties returned by
* getprop, since calling getprop on an property set to "" and on a non-existent property
* yields the same output.
*
* @param message shown when the test fails
* @param property name passed to getprop
*/
private void assertNoPropertySet(String message, String property) throws IOException {
Process process = new ProcessBuilder("getprop").start();
Scanner scanner = null;
try {
scanner = new Scanner(process.getInputStream());
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
assertFalse(message + "Property found: " + line,
line.startsWith("[" + property + "]"));
}
} finally {
if (scanner != null) {
scanner.close();
}
}
}
private static void assertValueIsAllowed(Set<String> allowedValues, String actualValue) {
assertTrue("Expected one of " + allowedValues + ", but was: '" + actualValue + "'",
allowedValues.contains(actualValue));
}
private static void assertValuesAreAllowed(Set<String> allowedValues, String[] actualValues) {
for (String actualValue : actualValues) {
assertValueIsAllowed(allowedValues, actualValue);
}
}
private static final Pattern BOARD_PATTERN =
Pattern.compile("^([0-9A-Za-z._-]+)$");
private static final Pattern BRAND_PATTERN =
Pattern.compile("^([0-9A-Za-z._-]+)$");
private static final Pattern DEVICE_PATTERN =
Pattern.compile("^([0-9A-Za-z._-]+)$");
private static final Pattern ID_PATTERN =
Pattern.compile("^([0-9A-Za-z._-]+)$");
private static final Pattern HARDWARE_PATTERN =
Pattern.compile("^([0-9A-Za-z.,_-]+)$");
private static final Pattern PRODUCT_PATTERN =
Pattern.compile("^([0-9A-Za-z._-]+)$");
private static final Pattern SOC_MANUFACTURER_PATTERN =
Pattern.compile("^([0-9A-Za-z ]+)$");
private static final Pattern SOC_MODEL_PATTERN =
Pattern.compile("^([0-9A-Za-z ._/+-]+)$");
private static final Pattern SERIAL_NUMBER_PATTERN =
Pattern.compile("^([0-9A-Za-z]{6,20})$");
private static final Pattern SKU_PATTERN =
Pattern.compile("^([0-9A-Za-z.,_-]+)$");
private static final Pattern TAGS_PATTERN =
Pattern.compile("^([0-9A-Za-z.,_-]+)$");
private static final Pattern TYPE_PATTERN =
Pattern.compile("^([0-9A-Za-z._-]+)$");
/** Tests that check for valid values of constants in Build. */
public void testBuildConstants() {
// Build.VERSION.* constants tested by BuildVersionTest
assertTrue(BOARD_PATTERN.matcher(Build.BOARD).matches());
assertTrue(BRAND_PATTERN.matcher(Build.BRAND).matches());
assertTrue(DEVICE_PATTERN.matcher(Build.DEVICE).matches());
// Build.FINGERPRINT tested by BuildVersionTest
assertTrue(HARDWARE_PATTERN.matcher(Build.HARDWARE).matches());
assertNotEmpty(Build.HOST);
assertTrue(ID_PATTERN.matcher(Build.ID).matches());
assertNotEmpty(Build.MANUFACTURER);
assertNotEmpty(Build.MODEL);
assertEquals(Build.SOC_MANUFACTURER, Build.SOC_MANUFACTURER.trim());
assertTrue(SOC_MANUFACTURER_PATTERN.matcher(Build.SOC_MANUFACTURER).matches());
if (getVendorPartitionVersion() > Build.VERSION_CODES.R) {
assertFalse(Build.SOC_MANUFACTURER.equals(Build.UNKNOWN));
}
assertEquals(Build.SOC_MODEL, Build.SOC_MODEL.trim());
assertTrue(SOC_MODEL_PATTERN.matcher(Build.SOC_MODEL).matches());
if (getVendorPartitionVersion() > Build.VERSION_CODES.R) {
assertFalse(Build.SOC_MODEL.equals(Build.UNKNOWN));
}
assertTrue(PRODUCT_PATTERN.matcher(Build.PRODUCT).matches());
assertTrue(SERIAL_NUMBER_PATTERN.matcher(Build.SERIAL).matches());
assertTrue(SKU_PATTERN.matcher(Build.SKU).matches());
assertTrue(SKU_PATTERN.matcher(Build.ODM_SKU).matches());
assertTrue(TAGS_PATTERN.matcher(Build.TAGS).matches());
// No format requirements stated in CDD for Build.TIME
assertTrue(TYPE_PATTERN.matcher(Build.TYPE).matches());
assertNotEmpty(Build.USER);
}
/**
* Tests that check for valid values of codenames related constants.
*/
public void testBuildCodenameConstants() {
// CUR_DEVELOPMENT must be larger than any released version.
Field[] fields = Build.VERSION_CODES.class.getDeclaredFields();
List<String> activeCodenames = Arrays.asList(ACTIVE_CODENAMES);
// Make the codenames uppercase to match the field names.
activeCodenames.replaceAll(String::toUpperCase);
Set<String> knownCodenames = Build.VERSION.KNOWN_CODENAMES.stream()
.map(String::toUpperCase)
.collect(Collectors.toSet());
HashSet<String> declaredCodenames = new HashSet<>();
for (Field field : fields) {
if (field.getType().equals(int.class) && Modifier.isStatic(field.getModifiers())) {
String fieldName = field.getName();
final int fieldValue;
try {
fieldValue = field.getInt(null);
} catch (IllegalAccessException e) {
throw new AssertionError(e.getMessage());
}
declaredCodenames.add(fieldName);
if (fieldName.equals("CUR_DEVELOPMENT")) {
// It should be okay to change the value of this constant in future, but it
// should at least be a conscious decision.
assertEquals(10000, fieldValue);
} else {
if (activeCodenames.contains(fieldName)) {
// This is the current development version. Note that fieldName can
// become < CUR_DEVELOPMENT before CODENAME becomes "REL", so we
// can't assertEquals(CUR_DEVELOPMENT, fieldValue) here.
assertTrue("Expected " + fieldName + " value to be <= " + CUR_DEVELOPMENT
+ ", got " + fieldValue, fieldValue <= CUR_DEVELOPMENT);
} else {
assertTrue("Expected " + fieldName + " value to be < " + CUR_DEVELOPMENT
+ ", got " + fieldValue, fieldValue < CUR_DEVELOPMENT);
}
// KNOWN_CODENAMES only tracks Q+ codenames
if (fieldValue >= Build.VERSION_CODES.Q) {
// Remove all underscores to match build level codenames, e.g. S_V2 is Sv2.
String name = fieldName.replaceAll("_", "");
declaredCodenames.add(name);
assertTrue("Expected " + name
+ " to be declared in Build.VERSION.KNOWN_CODENAMES",
knownCodenames.contains(name));
}
}
}
}
HashSet<String> diff = new HashSet<>(knownCodenames);
diff.removeAll(declaredCodenames);
assertTrue(
"Expected all elements in Build.VERSION.KNOWN_CODENAMES to be declared in"
+ " Build.VERSION_CODES, found " + diff, diff.isEmpty());
if (!Build.VERSION.CODENAME.equals("REL")) {
assertTrue("In-development CODENAME must be declared in Build.VERSION.KNOWN_CODENAMES",
Build.VERSION.KNOWN_CODENAMES.contains(Build.VERSION.CODENAME));
}
}
/**
* Verify that SDK versions are bounded by both high and low expected
* values.
*/
public void testSdkInt() {
assertTrue(
"Current SDK version " + Build.VERSION.SDK_INT
+ " is invalid; must be at least VERSION_CODES.BASE",
Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE);
assertTrue(
"First SDK version " + Build.VERSION.DEVICE_INITIAL_SDK_INT
+ " is invalid; must be at least VERSION_CODES.BASE",
Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.BASE);
assertTrue(
"Current SDK version " + Build.VERSION.SDK_INT
+ " must be at least first SDK version "
+ Build.VERSION.DEVICE_INITIAL_SDK_INT,
Build.VERSION.SDK_INT >= Build.VERSION.DEVICE_INITIAL_SDK_INT);
}
/**
* Verify that MEDIA_PERFORMANCE_CLASS are bounded by both high and low expected values.
*/
public void testMediaPerformanceClass() {
// media performance class value of 0 is valid
if (Build.VERSION.MEDIA_PERFORMANCE_CLASS == 0) {
return;
}
assertTrue(
"Media Performance Class " + Build.VERSION.MEDIA_PERFORMANCE_CLASS
+ " is invalid; must be at least VERSION_CODES.R",
Build.VERSION.MEDIA_PERFORMANCE_CLASS >= Build.VERSION_CODES.R);
assertTrue(
"Media Performance Class " + Build.VERSION.MEDIA_PERFORMANCE_CLASS
+ " is invalid; must be at most VERSION.SDK_INT",
// we use RESOURCES_SDK_INT to account for active development versions
Build.VERSION.MEDIA_PERFORMANCE_CLASS <= Build.VERSION.RESOURCES_SDK_INT);
}
static final String RO_DEBUGGABLE = "ro.debuggable";
private static final String RO_SECURE = "ro.secure";
/**
* Assert that the device is a secure, not debuggable user build.
*
* Debuggable devices allow adb root and have the su command, allowing
* escalations to root and unauthorized access to application data.
*
* Note: This test will fail on userdebug / eng devices, but should pass
* on production (user) builds.
*/
@RestrictedBuildTest
@AppModeFull(reason = "Instant apps cannot access APIs")
public void testIsSecureUserBuild() throws IOException {
assertEquals("Must be a user build", "user", Build.TYPE);
assertProperty("Must be a non-debuggable build", RO_DEBUGGABLE, "0");
assertFalse("Must be a non-debuggable build", Build.isDebuggable());
assertProperty("Must be a secure build", RO_SECURE, "1");
}
private void assertNotEmpty(String value) {
assertNotNull(value);
assertFalse(value.isEmpty());
}
private int getVendorPartitionVersion() {
String version = SystemProperties.get("ro.vndk.version");
try {
return Integer.parseInt(version);
} catch (NumberFormatException ignore) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
}
}