From a42b1c7a9912e65e1d4f3c6aa7150aa78933b31f Mon Sep 17 00:00:00 2001 From: Hsin-Yi Chen Date: Sat, 18 Feb 2017 17:58:36 +0800 Subject: Add test case for VNDK dependency Add VendorDependencyTest for the 2nd rule mentioned in the issue. Test: vts-tradefed run commandAndExit vts-vndk Bug: 35121072 Change-Id: I6a6f2985da695dde60d53f4b8c0937afe1e81374 --- __init__.py | 0 dependency/Android.mk | 27 +++++++ dependency/AndroidTest.xml | 27 +++++++ dependency/VtsVndkDependencyTest.py | 147 ++++++++++++++++++++++++++++++++++++ dependency/__init__.py | 0 dependency/elf_parser.py | 81 ++++++++++++++++++++ 6 files changed, 282 insertions(+) create mode 100644 __init__.py create mode 100644 dependency/Android.mk create mode 100644 dependency/AndroidTest.xml create mode 100644 dependency/VtsVndkDependencyTest.py create mode 100644 dependency/__init__.py create mode 100644 dependency/elf_parser.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dependency/Android.mk b/dependency/Android.mk new file mode 100644 index 0000000..15af37a --- /dev/null +++ b/dependency/Android.mk @@ -0,0 +1,27 @@ +# +# Copyright (C) 2017 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(call all-subdir-makefiles) + +include $(CLEAR_VARS) + +LOCAL_MODULE := VtsVndkDependencyTest + +VTS_CONFIG_SRC_DIR := testcases/vndk/dependency +include test/vts/tools/build/Android.host_config.mk + diff --git a/dependency/AndroidTest.xml b/dependency/AndroidTest.xml new file mode 100644 index 0000000..706e0ef --- /dev/null +++ b/dependency/AndroidTest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/dependency/VtsVndkDependencyTest.py b/dependency/VtsVndkDependencyTest.py new file mode 100644 index 0000000..36f5241 --- /dev/null +++ b/dependency/VtsVndkDependencyTest.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3.4 +# +# Copyright (C) 2017 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. +# + +import logging +import os +import re +import shutil +import tempfile + +from vts.runners.host import asserts +from vts.runners.host import base_test_with_webdb +from vts.runners.host import test_runner +from vts.runners.host import utils +from vts.utils.python.controllers import android_device +from vts.testcases.vndk.dependency import elf_parser + +class VtsVndkDependencyTest(base_test_with_webdb.BaseTestWithWebDbClass): + """A test case to verify vendor library dependency. + + Attributes: + _temp_dir: The temporary directory to which the vendor partition is + copied. + _vendor_libs: Collection of strings. The names of the shared libraries + on vendor partition. + """ + _SHELL_NAME = "vendor_dep_test_shell" + _VENDOR_PATH = "/vendor" + _LOW_LEVEL_NDK = [ + "libc.so", + "libm.so", + "libz.so", + "liblog.so", + "libdl.so", + "libstdc++.so" + ] + _SAME_PROCESS_NDK = [re.compile(p) for p in [ + "libEGL_.*\\.so$", + "libGLESv1_CM_.*\\.so$", + "libGLESv2_.*\\.so$", + "libGLESv3_.*\\.so$", + "vulkan.*\\.so$", + "libRSDriver.*\\.so$", + "libPVRRS\\.so$", + "gralloc-mapper@\\d+.\\d+-impl\\.so$", + ]] + + def setUpClass(self): + """Initializes device and temporary directory.""" + self.dut = self.registerController(android_device)[0] + self.dut.shell.InvokeTerminal(self._SHELL_NAME) + self._temp_dir = tempfile.mkdtemp() + self._vendor_libs = [] + + def tearDownClass(self): + """Deletes the temporary directory.""" + logging.info("Delete %s", self._temp_dir) + shutil.rmtree(self._temp_dir) + + def _isAllowedDependency(self, lib_name): + """Checks whether a library dependency is allowed. + + A vendor library/executable is only allowed to depend on + - Low-level NDK + - Same-process NDK + - Other libraries on vendor partition + + Args: + lib_name: String. The name of the depended library. + + Returns: + A boolean representing whether the library is allowed. + """ + if lib_name in self._vendor_libs or lib_name in self._LOW_LEVEL_NDK: + return True + for pattern in self._SAME_PROCESS_NDK: + if pattern.match(lib_name): + return True + return False + + def _listSharedLibraries(self, path): + """Finds all shared libraries under a directory. + + Args: + path: String. The path to search. + + Returns: + Set of strings. The names of the found libraries. + """ + results = set() + for root_dir, dir_names, file_names in os.walk(path): + for file_name in file_names: + if file_name.endswith(".so"): + results.add(file_name) + return results + + def testElfDependency(self): + """Scans library/executable dependency on vendor partition.""" + if not elf_parser.ElfParser.isSupported(): + asserts.fail("readelf is not available") + logging.info("adb pull %s %s", self._VENDOR_PATH, self._temp_dir) + pull_output = self.dut.adb.pull(self._VENDOR_PATH, self._temp_dir) + logging.debug(pull_output) + self._vendor_libs = self._listSharedLibraries(self._temp_dir) + logging.info("Vendor libraries: " + str(self._vendor_libs)) + error_count = 0 + for root_dir, dir_names, file_names in os.walk(self._temp_dir): + for file_name in file_names: + file_path = os.path.join(root_dir, file_name) + elf = elf_parser.ElfParser(file_path) + if not elf.isValid(): + logging.info("%s is not an ELF file", file_path) + continue + try: + dep_libs = elf.listDependencies() + except OSError as e: + error_count += 1 + logging.exception("Cannot read %s: %s", file_path, str(e)) + continue + logging.info("%s depends on: %s", file_path, str(dep_libs)) + disallowed_libs = filter( + lambda x: not self._isAllowedDependency(x), dep_libs) + if len(disallowed_libs) == 0: + continue + error_count += 1 + logging.error("%s depends on disallowed libs: %s", + file_path.replace(self._temp_dir, "", 1), + str(disallowed_libs)) + asserts.assertEqual(error_count, 0, + "Total number of errors: " + str(error_count)) + +if __name__ == "__main__": + test_runner.main() + diff --git a/dependency/__init__.py b/dependency/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dependency/elf_parser.py b/dependency/elf_parser.py new file mode 100644 index 0000000..50e2584 --- /dev/null +++ b/dependency/elf_parser.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3.4 +# +# Copyright (C) 2017 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. +# + +import os +import re + +from vts.runners.host import utils + +class ElfParser(object): + """This class reads an ELF file by parsing output of the command readelf. + + Attributes: + _file_path: The path to the ELF file. + """ + + def __init__(self, file_path): + self._file_path = file_path + + @staticmethod + def isSupported(): + """Checks whether readelf is available.""" + try: + utils.exe_cmd("readelf", "--version") + return True + except OSError: + return False + + def isValid(self): + """Checks size and first 4 bytes of the ELF file. + + Returns: + A boolean representing whether _file_path is a valid ELF. + """ + try: + size = os.path.getsize(self._file_path) + # must be larger than 32-bit file header + if size < 52: + return False + except OSError: + return False + try: + with open(self._file_path, "rb") as f: + magic = f.read(4) + if list(bytearray(magic)) != [0x7f, 0x45, 0x4c, 0x46]: + return False + except IOError: + return False + return True + + def listDependencies(self): + """Lists the shared libraries that the ELF depends on. + + Returns: + List of strings. The names of the depended libraries. + + Raises: + OSError if readelf fails. + """ + pattern = re.compile("\\(NEEDED\\)\\s*Shared library: \[(.+)\]") + output = utils.exe_cmd("readelf", "--dynamic", self._file_path) + results = [] + for line in output.split("\n"): + match = pattern.search(line) + if match: + results.append(match.group(1)) + return results + -- cgit v1.2.3