| #!/usr/bin/env python |
| # |
| # 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.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" |
| _SAME_PROCESS_DIR_32 = "/vendor/lib/sameprocess" |
| _SAME_PROCESS_DIR_64 = "/vendor/lib64/sameprocess" |
| _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$", |
| "android\\.hardware\\.graphics\\.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() |
| 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)) |
| |
| def tearDownClass(self): |
| """Deletes the temporary directory.""" |
| logging.info("Delete %s", self._temp_dir) |
| shutil.rmtree(self._temp_dir) |
| |
| def _isSameProcessLibrary(self, lib_name): |
| """Checks whether a library is same-process. |
| |
| Args: |
| lib_name: String. The name of the library. |
| |
| Returns: |
| A boolean representing whether the library is same-process. |
| """ |
| for pattern in self._SAME_PROCESS_NDK: |
| if pattern.match(lib_name): |
| return True |
| return False |
| |
| 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 |
| if self._isSameProcessLibrary(lib_name): |
| return True |
| return False |
| |
| @staticmethod |
| def _iterateFiles(dir_path): |
| """A generator yielding regular files in a directory recursively. |
| |
| Args: |
| dir_path: String. The path to search. |
| |
| Yields: |
| A tuple of strings (directory, file). The directory containing |
| the file and the file name. |
| """ |
| for root_dir, dir_names, file_names in os.walk(dir_path): |
| for file_name in file_names: |
| yield root_dir, file_name |
| |
| @staticmethod |
| def _listSharedLibraries(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, file_name in VtsVndkDependencyTest._iterateFiles(path): |
| if file_name.endswith(".so"): |
| results.add(file_name) |
| return results |
| |
| def testSameProcessLibrary(self): |
| """Checks if same-process directory contains only allowed libraries.""" |
| dev_sp_dirs = [self._SAME_PROCESS_DIR_32] |
| if self.dut.is64Bit: |
| dev_sp_dirs.append(self._SAME_PROCESS_DIR_64) |
| error_count = 0 |
| for dev_sp_dir in dev_sp_dirs: |
| sp_dir = os.path.join(self._temp_dir, dev_sp_dir) |
| if not os.path.isdir(sp_dir): |
| logging.warning("%s is not a directory", sp_dir) |
| continue |
| logging.info("Enter %s", sp_dir) |
| for root_dir, file_name in self._iterateFiles(sp_dir): |
| full_path = os.path.join(root_dir, file_name) |
| if self._isSameProcessLibrary(file_name): |
| logging.info("%s is a same-process lib", full_path) |
| continue |
| error_count += 1 |
| logging.error("%s is not a same-process lib", full_path) |
| asserts.assertEqual(error_count, 0, |
| "Total number of errors: " + str(error_count)) |
| |
| def testElfDependency(self): |
| """Scans library/executable dependency on vendor partition.""" |
| if not elf_parser.ElfParser.isSupported(): |
| asserts.fail("readelf is not available") |
| error_count = 0 |
| for root_dir, file_name in self._iterateFiles(self._temp_dir): |
| 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 not disallowed_libs: |
| 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() |
| |