From f4f34abd4204952d3aa046e5f597adcfddbf0121 Mon Sep 17 00:00:00 2001 From: Hsin-Yi Chen Date: Tue, 4 Feb 2020 15:29:51 +0800 Subject: Add vts_vndk_dependency_test This commit adds vts_vndk_dependency_test.py which is copied from VtsVndkDependencyTest.py. The new file can be packaged into an executable file running on device without whole VTS framework. Bug: 147454897 Test: adb shell /data/local/tmp/vts_vndk_dependency_test Change-Id: Idc0fbf6e1d48fafffe537a73f7be93ab5c71d62e --- Android.bp | 60 +++++ dependency/VtsVndkDependencyTest.py | 2 + dependency/vts_vndk_dependency_test.py | 441 +++++++++++++++++++++++++++++++++ golden/vndk_data.py | 81 ++++-- utils.py | 165 ++++++++++++ 5 files changed, 733 insertions(+), 16 deletions(-) create mode 100644 Android.bp create mode 100644 dependency/vts_vndk_dependency_test.py create mode 100644 utils.py diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..18a2b91 --- /dev/null +++ b/Android.bp @@ -0,0 +1,60 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// 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. + +python_library { + name: "vts_vndk_utils", + pkg_path: "vts/testcases/vndk", + srcs: [ + "utils.py", + "golden/vndk_data.py", + ], + data: [ + ":vndk_lib_lists", + ":vndk_lib_extra_lists" + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: true, + }, + }, +} + +python_defaults { + name: "vts_vndk_default", + libs: [ + "vndk_utils", + "vts_vndk_utils", + ], + version: { + py2: { + enabled: false, + }, + py3: { + enabled: true, + embedded_launcher: true, + } + } +} + +python_test { + name: "vts_vndk_dependency_test", + defaults: ["vts_vndk_default"], + main: "dependency/vts_vndk_dependency_test.py", + srcs: [ + "dependency/vts_vndk_dependency_test.py", + ] +} diff --git a/dependency/VtsVndkDependencyTest.py b/dependency/VtsVndkDependencyTest.py index 49ac575..f99e670 100644 --- a/dependency/VtsVndkDependencyTest.py +++ b/dependency/VtsVndkDependencyTest.py @@ -15,6 +15,8 @@ # limitations under the License. # +# TODO(b/147454897): Keep the test logic in sync with vts_vndk_dependency.py +# until this file is removed. import collections import logging import os diff --git a/dependency/vts_vndk_dependency_test.py b/dependency/vts_vndk_dependency_test.py new file mode 100644 index 0000000..6541a80 --- /dev/null +++ b/dependency/vts_vndk_dependency_test.py @@ -0,0 +1,441 @@ +#!/usr/bin/env python +# +# Copyright (C) 2020 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. +# + +# TODO(b/147454897): Keep the test logic in sync with VtsVndkDependency.py +# until it is removed. +import collections +import logging +import os +import re +import unittest + +from vts.testcases.vndk import utils +from vts.testcases.vndk.golden import vndk_data +from vts.utils.python.library import elf_parser +from vts.utils.python.vndk import vndk_utils + + +class VtsVndkDependencyTest(unittest.TestCase): + """A test case to verify vendor library dependency. + + Attributes: + _dut: The AndroidDevice under test. + _ll_ndk: Set of strings. The names of low-level NDK libraries in + /system/lib[64]. + _sp_hal: List of patterns. The names of the same-process HAL libraries + expected to be in /vendor/lib[64]. + _vndk: Set of strings. The names of VNDK-core libraries. + _vndk_sp: Set of strings. The names of VNDK-SP libraries. + _SP_HAL_LINK_PATHS: Format strings of same-process HAL's link paths. + _VENDOR_LINK_PATHS: Format strings of vendor processes' link paths. + """ + _TARGET_DIR_SEP = "/" + _TARGET_ODM_DIR = "/odm" + _TARGET_VENDOR_DIR = "/vendor" + + _SP_HAL_LINK_PATHS = [ + "/odm/{LIB}/egl", "/odm/{LIB}/hw", "/odm/{LIB}", + "/vendor/{LIB}/egl", "/vendor/{LIB}/hw", "/vendor/{LIB}" + ] + _VENDOR_LINK_PATHS = [ + "/odm/{LIB}/hw", "/odm/{LIB}/egl", "/odm/{LIB}", + "/vendor/{LIB}/hw", "/vendor/{LIB}/egl", "/vendor/{LIB}" + ] + _DEFAULT_PROGRAM_INTERPRETERS = [ + "/system/bin/linker", "/system/bin/linker64" + ] + + class ElfObject(object): + """Contains dependencies of an ELF file on target device. + + Attributes: + target_path: String. The path to the ELF file on target. + name: String. File name of the ELF. + target_dir: String. The directory containing the ELF file on + target. + bitness: Integer. Bitness of the ELF. + deps: List of strings. The names of the depended libraries. + runpaths: List of strings. The library search paths. + """ + + def __init__(self, target_path, bitness, deps, runpaths): + self.target_path = target_path + self.name = os.path.basename(target_path) + self.target_dir = os.path.dirname(target_path) + self.bitness = bitness + self.deps = deps + # Format runpaths + self.runpaths = [] + lib_dir_name = "lib64" if bitness == 64 else "lib" + for runpath in runpaths: + path = runpath.replace("${LIB}", lib_dir_name) + path = path.replace("$LIB", lib_dir_name) + path = path.replace("${ORIGIN}", self.target_dir) + path = path.replace("$ORIGIN", self.target_dir) + self.runpaths.append(path) + + def setUp(self): + """Initializes device, temporary directory, and VNDK lists.""" + self._dut = utils.AndroidDevice() + self.assertTrue(self._dut.IsRoot(), "This test requires adb root.") + + vndk_lists = vndk_data.LoadVndkLibraryListsFromResources( + self._dut.GetVndkVersion(), + vndk_data.SP_HAL, + vndk_data.LL_NDK, + vndk_data.VNDK, + vndk_data.VNDK_SP) + self.assertTrue(vndk_lists, "Cannot load VNDK library lists.") + + sp_hal_strings = vndk_lists[0] + self._sp_hal = [re.compile(x) for x in sp_hal_strings] + (self._ll_ndk, self._vndk, self._vndk_sp) = vndk_lists[1:] + + logging.debug("LL_NDK: %s", self._ll_ndk) + logging.debug("SP_HAL: %s", sp_hal_strings) + logging.debug("VNDK: %s", self._vndk) + logging.debug("VNDK_SP: %s", self._vndk_sp) + + def _IsElfObjectForAp(self, elf, target_path, abi_list): + """Checks whether an ELF object is for application processor. + + Args: + elf: The object of elf_parser.ElfParser. + target_path: The path to the ELF file on target. + abi_list: A list of strings, the ABIs of the application processor. + + Returns: + A boolean, whether the ELF object is for application processor. + """ + if not any(elf.MatchCpuAbi(x) for x in abi_list): + logging.debug("%s does not match the ABI", target_path) + return False + + # b/115567177 Skip an ELF file if it meets the following 3 conditions: + # The ELF type is executable. + if not elf.IsExecutable(): + return True + + # It requires special program interpreter. + interp = elf.GetProgramInterpreter() + if not interp or interp in self._DEFAULT_PROGRAM_INTERPRETERS: + return True + + # It does not have execute permission in the file system. + if self._dut.IsExecutable(target_path): + return True + + return False + + def _IsElfObjectBuiltForAndroid(self, elf, target_path): + """Checks whether an ELF object is built for Android. + + Some ELF objects in vendor partition require special program + interpreters. Such executable files have .interp sections, but shared + libraries don't. As there is no reliable way to identify those + libraries. This method checks .note.android.ident section which is + created by Android build system. + + Args: + elf: The object of elf_parser.ElfParser. + target_path: The path to the ELF file on target. + + Returns: + A boolean, whether the ELF object is built for Android. + """ + # b/133399940 Skip an ELF file if it does not have .note.android.ident + # section and meets one of the following conditions: + if elf.HasAndroidIdent(): + return True + + # It's in the specific directory and is a shared library. + if (target_path.startswith("/vendor/arib/lib/") and + ".so" in target_path and + elf.IsSharedObject()): + return False + + # It's in the specific directory, requires special program interpreter, + # and is executable. + if target_path.startswith("/vendor/arib/bin/"): + interp = elf.GetProgramInterpreter() + if interp and interp not in self._DEFAULT_PROGRAM_INTERPRETERS: + if elf.IsExecutable() or self._dut.IsExecutable(target_path): + return False + + return True + + @staticmethod + def _IterateFiles(host_dir): + """Iterates files in a host directory. + + Args: + host_dir: The host directory. + + Yields: + The file paths under the directory. + """ + for root_dir, dir_names, file_names in os.walk(host_dir): + for file_name in file_names: + yield os.path.join(root_dir, file_name) + + def _LoadElfObjects(self, target_dir, abi_list, elf_error_handler): + """Scans a host directory recursively and loads all ELF files in it. + + Args: + target_dir: The host directory to scan. + abi_list: A list of strings, the ABIs of the ELF files to load. + elf_error_handler: A function that takes 2 arguments + (path, exception). It is called when + the parser fails to read an ELF file. + + Returns: + List of ElfObject. + """ + objs = [] + for target_path in self._IterateFiles(target_dir): + try: + elf = elf_parser.ElfParser(target_path) + except elf_parser.ElfError: + logging.debug("%s is not an ELF file", target_path) + continue + try: + if not self._IsElfObjectForAp(elf, target_path, abi_list): + logging.info("%s is not for application processor", + target_path) + continue + if not self._IsElfObjectBuiltForAndroid(elf, target_path): + logging.warning("%s is not built for Android, which is no " + "longer exempted.", target_path) + + deps, runpaths = elf.ListDependencies() + except elf_parser.ElfError as e: + elf_error_handler(target_path, e) + continue + finally: + elf.Close() + + logging.info("%s depends on: %s", target_path, ", ".join(deps)) + if runpaths: + logging.info("%s has runpaths: %s", + target_path, ":".join(runpaths)) + objs.append(self.ElfObject(target_path, elf.bitness, deps, + runpaths)) + return objs + + def _FindLibsInLinkPaths(self, bitness, link_paths, objs): + """Finds libraries in link paths. + + Args: + bitness: 32 or 64, the bitness of the returned libraries. + link_paths: List of strings, the default link paths. + objs: List of ElfObject, the libraries/executables to be filtered + by bitness and path. + + Returns: + A defaultdict, {dir: {name: obj}} where obj is an ElfObject, dir + is obj.target_dir, and name is obj.name. + """ + namespace = collections.defaultdict(dict) + for obj in objs: + if (obj.bitness == bitness and + any(obj.target_path.startswith(link_path + + self._TARGET_DIR_SEP) + for link_path in link_paths)): + namespace[obj.target_dir][obj.name] = obj + return namespace + + def _DfsDependencies(self, lib, searched, namespace, link_paths): + """Depth-first-search for library dependencies. + + Args: + lib: ElfObject, the library to search for dependencies. + searched: The set of searched libraries. + namespace: Defaultdict, {dir: {name: obj}} containing all + searchable libraries. + link_paths: List of strings, the default link paths. + """ + if lib in searched: + return + searched.add(lib) + for dep_name in lib.deps: + for link_path in lib.runpaths + link_paths: + if dep_name in namespace[link_path]: + self._DfsDependencies(namespace[link_path][dep_name], + searched, namespace, link_paths) + break + + def _FindDisallowedDependencies(self, objs, namespace, link_paths, + *vndk_lists): + """Tests if libraries/executables have disallowed dependencies. + + Args: + objs: Collection of ElfObject, the libraries/executables under + test. + namespace: Defaultdict, {dir: {name: obj}} containing all libraries + in the linker namespace. + link_paths: List of strings, the default link paths. + vndk_lists: Collections of library names in VNDK, VNDK-SP, etc. + + Returns: + List of tuples (path, disallowed_dependencies). + """ + dep_errors = [] + for obj in objs: + disallowed_libs = [] + for dep_name in obj.deps: + if any((dep_name in vndk_list) for vndk_list in vndk_lists): + continue + if any((dep_name in namespace[link_path]) for link_path in + obj.runpaths + link_paths): + continue + disallowed_libs.append(dep_name) + + if disallowed_libs: + dep_errors.append((obj.target_path, disallowed_libs)) + return dep_errors + + def _TestElfDependency(self, bitness, objs): + """Tests vendor libraries/executables and SP-HAL dependencies. + + Args: + bitness: 32 or 64, the bitness of the vendor libraries. + objs: List of ElfObject. The libraries/executables in odm and + vendor partitions. + + Returns: + List of tuples (path, disallowed_dependencies). + """ + vndk_sp_ext_dirs = vndk_utils.GetVndkSpExtDirectories(bitness) + + vendor_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for + x in self._VENDOR_LINK_PATHS] + vendor_namespace = self._FindLibsInLinkPaths(bitness, + vendor_link_paths, objs) + # Exclude VNDK and VNDK-SP extensions from vendor libraries. + for vndk_ext_dir in (vndk_utils.GetVndkExtDirectories(bitness) + + vndk_utils.GetVndkSpExtDirectories(bitness)): + vendor_namespace.pop(vndk_ext_dir, None) + logging.info("%d-bit odm, vendor, and SP-HAL libraries:", bitness) + for dir_path, libs in vendor_namespace.items(): + logging.info("%s: %s", dir_path, ",".join(libs.keys())) + + sp_hal_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for + x in self._SP_HAL_LINK_PATHS] + sp_hal_namespace = self._FindLibsInLinkPaths(bitness, + sp_hal_link_paths, objs) + + # Find same-process HAL and dependencies + sp_hal_libs = set() + for link_path in sp_hal_link_paths: + for obj in sp_hal_namespace[link_path].values(): + if any(x.match(obj.target_path) for x in self._sp_hal): + self._DfsDependencies(obj, sp_hal_libs, sp_hal_namespace, + sp_hal_link_paths) + logging.info("%d-bit SP-HAL libraries: %s", + bitness, ", ".join(x.name for x in sp_hal_libs)) + + # Find VNDK-SP extension libraries and their dependencies. + vndk_sp_ext_libs = set(obj for obj in objs if + obj.bitness == bitness and + obj.target_dir in vndk_sp_ext_dirs) + vndk_sp_ext_deps = set() + for lib in vndk_sp_ext_libs: + self._DfsDependencies(lib, vndk_sp_ext_deps, sp_hal_namespace, + sp_hal_link_paths) + logging.info("%d-bit VNDK-SP extension libraries and dependencies: %s", + bitness, ", ".join(x.name for x in vndk_sp_ext_deps)) + + # A vendor library/executable is allowed to depend on + # LL-NDK + # VNDK + # VNDK-SP + # Other libraries in vendor link paths + vendor_objs = {obj for obj in objs if + obj.bitness == bitness and + obj not in sp_hal_libs and + obj not in vndk_sp_ext_deps} + dep_errors = self._FindDisallowedDependencies( + vendor_objs, vendor_namespace, vendor_link_paths, + self._ll_ndk, self._vndk, self._vndk_sp) + + # A VNDK-SP extension library/dependency is allowed to depend on + # LL-NDK + # VNDK-SP + # Libraries in vendor link paths + # Other VNDK-SP extension libraries, which is a subset of VNDK-SP + # + # However, it is not allowed to indirectly depend on VNDK. i.e., the + # depended vendor libraries must not depend on VNDK. + # + # vndk_sp_ext_deps and sp_hal_libs may overlap. Their dependency + # restrictions are the same. + dep_errors.extend(self._FindDisallowedDependencies( + vndk_sp_ext_deps - sp_hal_libs, vendor_namespace, + vendor_link_paths, self._ll_ndk, self._vndk_sp)) + + if not vndk_utils.IsVndkRuntimeEnforced(self._dut): + logging.warning("Ignore dependency errors: %s", dep_errors) + dep_errors = [] + + # A same-process HAL library is allowed to depend on + # LL-NDK + # VNDK-SP + # Other same-process HAL libraries and dependencies + dep_errors.extend(self._FindDisallowedDependencies( + sp_hal_libs, sp_hal_namespace, sp_hal_link_paths, + self._ll_ndk, self._vndk_sp)) + return dep_errors + + def testElfDependency(self): + """Tests vendor libraries/executables and SP-HAL dependencies.""" + read_errors = [] + abi_list = self._dut.GetCpuAbiList() + objs = [] + for target_dir in (self._TARGET_ODM_DIR, self._TARGET_VENDOR_DIR): + if self._dut.IsDirectory(target_dir): + objs.extend(self._LoadElfObjects( + target_dir, abi_list, + lambda p, e: read_errors.append((p, str(e))))) + + dep_errors = self._TestElfDependency(32, objs) + if self._dut.GetCpuAbiList(64): + dep_errors.extend(self._TestElfDependency(64, objs)) + + assert_lines = [] + if read_errors: + error_lines = ["%s: %s" % (x[0], x[1]) for x in read_errors] + logging.error("%d read errors:\n%s", + len(read_errors), "\n".join(error_lines)) + assert_lines.extend(error_lines[:20]) + + if dep_errors: + error_lines = ["%s: %s" % (x[0], ", ".join(x[1])) + for x in dep_errors] + logging.error("%d disallowed dependencies:\n%s", + len(dep_errors), "\n".join(error_lines)) + assert_lines.extend(error_lines[:20]) + + error_count = len(read_errors) + len(dep_errors) + if error_count: + if error_count > len(assert_lines): + assert_lines.append("...") + assert_lines.append("Total number of errors: " + str(error_count)) + self.fail("\n".join(assert_lines)) + + +if __name__ == "__main__": + unittest.main() diff --git a/golden/vndk_data.py b/golden/vndk_data.py index e215a91..4eb127d 100644 --- a/golden/vndk_data.py +++ b/golden/vndk_data.py @@ -17,6 +17,12 @@ import collections import logging import os +import re + +try: + from importlib import resources +except ImportError: + resources = None # The tags in VNDK list: # Low-level NDK libraries that can be used by framework and vendor modules. @@ -48,6 +54,9 @@ _ABI_NAMES = ("arm64", "arm", "mips64", "mips", "x86_64", "x86") # The data directory. _GOLDEN_DIR = os.path.join("vts", "testcases", "vndk", "golden") +# The data package. +_RESOURCE_PACKAGE = "vts.testcases.vndk"; + # Regular expression prefix for library name patterns. _REGEX_PREFIX = "[regex]" @@ -108,32 +117,31 @@ def GetAbiDumpDirectory(data_file_path, version, binder_bitness, abi_name, return dump_dir -def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_path): +def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file): """Load VNDK libraries from the file to the specified tuple. Args: vndk_lists: The output tuple of lists containing library names. tags: Strings, the tags of the libraries to find. - vndk_lib_list_path: The path to load the VNDK library list. + vndk_lib_list_file: The file object containing the VNDK library list. """ lib_sets = collections.defaultdict(set) # Load VNDK tags from the list. - with open(vndk_lib_list_path) as vndk_lib_list_file: - for line in vndk_lib_list_file: - # Ignore comments. - if line.startswith('#'): - continue + for line in vndk_lib_list_file: + # Ignore comments. + if line.startswith('#'): + continue - # Split columns. - cells = line.split(': ', 1) - if len(cells) < 2: - continue - tag = cells[0] - lib_name = cells[1].strip() + # Split columns. + cells = line.split(': ', 1) + if len(cells) < 2: + continue + tag = cells[0] + lib_name = cells[1].strip() - lib_sets[tag].add(lib_name) + lib_sets[tag].add(lib_name) # Compute VNDK-core-private and VNDK-SP-private. private = lib_sets.get('VNDK-private', set()) @@ -187,6 +195,47 @@ def LoadVndkLibraryLists(data_file_path, version, *tags): vndk_lists = tuple([] for x in tags) - _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_path) - _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_extra_list_path) + with open(vndk_lib_list_path, "r") as f: + _LoadVndkLibraryListsFile(vndk_lists, tags, f) + with open(vndk_lib_extra_list_path, "r") as f: + _LoadVndkLibraryListsFile(vndk_lists, tags, f) + return vndk_lists + + +def LoadVndkLibraryListsFromResources(version, *tags): + """Find the VNDK libraries with specific tags in resources. + + Args: + version: A string, the VNDK version. + *tags: Strings, the tags of the libraries to find. + + Returns: + A tuple of lists containing library names. Each list corresponds to + one tag in the argument. For SP-HAL, the returned names are regular + expressions. + None if the VNDK list for the version is not found. + """ + if not resources: + logging.error("Could not import resources module.") + return None + + version_str = (version if version and re.match("\\d+", version) else + "current") + vndk_lib_list_name = version_str + ".txt" + vndk_lib_extra_list_name = "vndk-lib-extra-list-" + version_str + ".txt" + + if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_list_name): + logging.warning("Cannot load %s.", vndk_lib_list_name) + return None + + if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_extra_list_name): + logging.warning("Cannot load %s.", vndk_lib_extra_list_name) + return None + + vndk_lists = tuple([] for x in tags) + + with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_list_name) as f: + _LoadVndkLibraryListsFile(vndk_lists, tags, f) + with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_extra_list_name) as f: + _LoadVndkLibraryListsFile(vndk_lists, tags, f) return vndk_lists diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..8e6cc52 --- /dev/null +++ b/utils.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# +# Copyright (C) 2020 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. +# + +# TODO(b/147454897): Keep the logic in sync with +# test/vts/utils/python/controllers/android_device.py until +# it is removed. +import logging +import subprocess + +class AndroidDevice(object): + """This class controls the device via adb commands.""" + + def _ExecuteCommand(self, *cmd): + """Executes a command. + + Args: + args: Strings, the arguments. + + Returns: + Stdout as a string, stderr as a string, and return code as an + integer. + """ + proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + # Compatible with python2 and python3 + if not isinstance(out, str): + out = out.decode("utf-8") + if not isinstance(err, str): + err = err.decode("utf-8") + return out, err, proc.returncode + + def _GetProp(self, name): + """Gets an Android system property. + + Args: + name: A string, the property name. + + Returns: + A string, the value of the property. + + Raises: + IOError if the command fails. + """ + out, err, return_code = self._ExecuteCommand("getprop", name) + if err.strip() or return_code != 0: + raise IOError("`getprop %s` stdout: %s\nstderr: %s" % + (name, out, err)) + return out.strip() + + def GetCpuAbiList(self, bitness=""): + """Gets the list of supported ABIs from property. + + Args: + bitness: 32 or 64. If the argument is not specified, this method + returns both 32 and 64-bit ABIs. + + Returns: + A list of strings, the supported ABIs. + """ + out = self._GetProp("ro.product.cpu.abilist" + str(bitness)) + return out.lower().split(",") if out else [] + + def GetLaunchApiLevel(self): + """Gets the API level that the device was initially launched with. + + This method reads ro.product.first_api_level from the device. If the + value is 0, it then reads ro.build.version.sdk. + + Returns: + An integer, the API level. + """ + level_str = self._GetProp("ro.product.first_api_level") + level = int(level_str) + if level != 0: + return level + + level_str = self._GetProp("ro.build.version.sdk") + return int(level_str) + + def getLaunchApiLevel(self, strict=True): + """Gets the API level that the device was initially launched with. + + This method is compatible with vndk_utils in vts package. + + Args: + strict: A boolean, whether to raise an error if the property is + not an integer or not defined. + + Returns: + An integer, the API level. + 0 if the value is undefined and strict is False. + + Raises: + ValueError: if the value is undefined and strict is True. + """ + try: + return self.GetLaunchApiLevel() + except ValueError as e: + if strict: + raise + logging.exception(e) + return 0 + + @property + def vndk_lite(self): + """Checks whether the vendor partition requests lite VNDK enforcement. + + This method is compatible with vndk_utils in vts package. + + Returns: + A boolean, True for lite vndk enforcement. + """ + return self._GetProp("ro.vndk.lite").lower() == "true" + + def GetVndkVersion(self): + """Gets the VNDK version that the vendor partition requests.""" + return self._GetProp("ro.vndk.version") + + def IsRoot(self): + """Returns whether adb has root privilege on the device.""" + out, err, return_code = self._ExecuteCommand("id") + if err.strip() or return_code != 0: + raise IOError("`id` stdout: %s\nstderr: %s \n" % (out, err)) + return "uid=0(root)" in out.strip() + + def _Test(self, *args): + """Tests file types and status.""" + out, err, return_code = self._ExecuteCommand("sh", "-c", + "test " + " ".join(args)) + if out.strip() or err.strip(): + raise IOError("`test` args: %s\nstdout: %s\nstderr: %s" % + (args, out, err)) + return return_code == 0 + + def IsDirectory(self, path): + """Returns whether a path on the device is a directory.""" + return self._Test("-d", path) + + def _Stat(self, fmt, path): + """Executes stat command.""" + out, err, return_code = self._ExecuteCommand("stat", "--format", fmt, + path) + if return_code != 0 or err.strip(): + raise IOError("`stat --format %s %s` stdout: %s\nstderr: %s" % + (fmt, path, out, err)) + return out.strip() + + def IsExecutable(self, path): + """Returns if execute permission is granted to a path on the device.""" + return "x" in self._Stat("%A", path) -- cgit v1.2.3