普通文本  |  394行  |  15.83 KB

#!/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
from vts.runners.host import keys
from vts.runners.host import test_runner
from vts.runners.host import utils
from vts.testcases.vndk.golden import vndk_data
from vts.utils.python.controllers import android_device
from vts.utils.python.file import target_file_utils
from vts.utils.python.library import elf_parser
from vts.utils.python.os import path_utils
from vts.utils.python.vndk import vndk_utils


class VtsVndkDependencyTest(base_test.BaseTestClass):
    """A test case to verify vendor library dependency.

    Attributes:
        data_file_path: The path to VTS data directory.
        _dut: The AndroidDevice under test.
        _temp_dir: The temporary directory to which the odm and vendor
                   partitions are copied.
        _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 in
               /system/lib[64]/vndk-${VER}.
        _vndk_sp: Set of strings. The names of VNDK-SP libraries in
                  /system/lib[64]/vndk-sp-${VER}.
        _SP_HAL_LINK_PATHS: Format strings of same-process HAL's link paths.
        _VENDOR_LINK_PATHS: Format strings of vendor processes' link paths.
    """
    _TARGET_ROOT_DIR = "/"
    _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}"
    ]

    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.
        """

        def __init__(self, target_path, bitness, deps):
            self.target_path = target_path
            self.name = path_utils.TargetBaseName(target_path)
            self.target_dir = path_utils.TargetDirName(target_path)
            self.bitness = bitness
            self.deps = deps

    def setUpClass(self):
        """Initializes device, temporary directory, and VNDK lists."""
        required_params = [keys.ConfigKeys.IKEY_DATA_FILE_PATH]
        self.getUserParams(required_params)
        self._dut = self.android_devices[0]
        self._temp_dir = tempfile.mkdtemp()
        for target_dir in (self._TARGET_ODM_DIR, self._TARGET_VENDOR_DIR):
            if target_file_utils.IsDirectory(target_dir, self._dut.shell):
                logging.info("adb pull %s %s", target_dir, self._temp_dir)
                self._dut.adb.pull(target_dir, self._temp_dir)
            else:
                logging.info("Skip adb pull %s", target_dir)

        vndk_lists = vndk_data.LoadVndkLibraryLists(
            self.data_file_path,
            self._dut.vndk_version,
            vndk_data.SP_HAL,
            vndk_data.LL_NDK,
            vndk_data.VNDK,
            vndk_data.VNDK_SP)
        asserts.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) = (
            set(path_utils.TargetBaseName(path) for path in vndk_list)
            for vndk_list in 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 tearDownClass(self):
        """Deletes the temporary directory."""
        logging.info("Delete %s", self._temp_dir)
        shutil.rmtree(self._temp_dir)

    def _LoadElfObjects(self, host_dir, target_dir, abi_list,
                        elf_error_handler):
        """Scans a host directory recursively and loads all ELF files in it.

        Args:
            host_dir: The host directory to scan.
            target_dir: The path from which host_dir is copied.
            abi_list: A list of strings, the ABIs of the ELF files to load.
            elf_error_handler: A function that takes 2 arguments
                               (target_path, exception). It is called when
                               the parser fails to read an ELF file.

        Returns:
            List of ElfObject.
        """
        objs = []
        for root_dir, file_name in utils.iterate_files(host_dir):
            full_path = os.path.join(root_dir, file_name)
            rel_path = os.path.relpath(full_path, host_dir)
            target_path = path_utils.JoinTargetPath(
                target_dir, *rel_path.split(os.path.sep))
            try:
                elf = elf_parser.ElfParser(full_path)
            except elf_parser.ElfError:
                logging.debug("%s is not an ELF file", target_path)
                continue
            if not any(elf.MatchCpuAbi(x) for x in abi_list):
                logging.debug("%s does not match the ABI", target_path)
                elf.Close()
                continue
            try:
                deps = 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))
            objs.append(self.ElfObject(target_path, elf.bitness, deps))
        return objs

    def _DfsDependencies(self, lib, searched, searchable):
        """Depth-first-search for library dependencies.

        Args:
            lib: ElfObject. The library to search dependencies.
            searched: The set of searched libraries.
            searchable: The dictionary that maps file names to libraries.
        """
        if lib in searched:
            return
        searched.add(lib)
        for dep_name in lib.deps:
            if dep_name in searchable:
                self._DfsDependencies(searchable[dep_name], searched,
                                      searchable)

    def _FindLibsInSpHalNamespace(self, bitness, objs):
        """Finds libraries in SP-HAL link paths.

        Args:
            bitness: 32 or 64, the bitness of the returned libraries.
            objs: List of ElfObject, the libraries/executables in odm and
                  vendor partitions.

        Returns:
            Dict of {string: ElfObject}, the library name and the first library
            in SP-HAL link paths.
        """
        sp_hal_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for
                             x in self._SP_HAL_LINK_PATHS]
        vendor_libs = [obj for obj in objs if
                       obj.bitness == bitness and
                       obj.target_dir in sp_hal_link_paths]
        linkable_libs = dict()
        for obj in vendor_libs:
            if obj.name not in linkable_libs:
                linkable_libs[obj.name] = obj
            else:
                linkable_libs[obj.name] = min(
                    linkable_libs[obj.name], obj,
                    key=lambda x: sp_hal_link_paths.index(x.target_dir))
        return linkable_libs

    def _FilterDisallowedDependencies(self, objs, is_allowed_dependency):
        """Returns libraries with disallowed dependencies.

        Args:
            objs: A collection of ElfObject, the libraries/executables.
            is_allowed_dependency: A function that takes the library name as the
                                   argument and returns whether objs can depend
                                   on the library.

        Returns:
            List of tuples (path, disallowed_dependencies). The library with
            disallowed dependencies and list of the dependencies.
        """
        dep_errors = []
        for obj in objs:
            disallowed_libs = [
                x for x in obj.deps if not is_allowed_dependency(x)]
            if disallowed_libs:
                dep_errors.append((obj.target_path, disallowed_libs))
        return dep_errors

    def _TestVendorDependency(self, vendor_objs, vendor_libs):
        """Tests if vendor libraries/executables have disallowed dependencies.

        A vendor library/executable is allowed to depend on
        - LL-NDK
        - VNDK
        - VNDK-SP
        - Other libraries in vendor link paths.

        Args:
            vendor_objs: Collection of ElfObject, the libraries/executables in
                         odm and vendor partitions, excluding VNDK-SP extension
                         and SP-HAL.
            vendor_libs: Set of ElfObject, the libraries in vendor link paths,
                         including SP-HAL.

        Returns:
            List of tuples (path, disallowed_dependencies).
        """
        vendor_lib_names = set(x.name for x in vendor_libs)
        is_allowed_dep = lambda x: (x in self._ll_ndk or
                                    x in self._vndk or
                                    x in self._vndk_sp or
                                    x in vendor_lib_names)
        return self._FilterDisallowedDependencies(vendor_objs, is_allowed_dep)

    def _TestVndkSpExtDependency(self, vndk_sp_ext_deps, vendor_libs):
        """Tests if VNDK-SP extension libraries have disallowed dependencies.

        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.

        Args:
            vndk_sp_ext_deps: Collection of ElfObject, the VNDK-SP extension
                              libraries and dependencies.
            vendor_libs: Set of ElfObject, the libraries in vendor link paths.

        Returns:
            List of tuples (path, disallowed_dependencies).
        """
        vendor_lib_names = set(x.name for x in vendor_libs)
        is_allowed_dep = lambda x: (x in self._ll_ndk or
                                    x in self._vndk_sp or
                                    x in vendor_lib_names)
        return self._FilterDisallowedDependencies(
            vndk_sp_ext_deps, is_allowed_dep)

    def _TestSpHalDependency(self, sp_hal_libs):
        """Tests if SP-HAL libraries have disallowed dependencies.

        A same-process HAL library is allowed to depend on
        - LL-NDK
        - VNDK-SP
        - Other same-process HAL libraries and dependencies

        Args:
            sp_hal_libs: Set of ElfObject, the Same-process HAL libraries and
                         the dependencies.

        Returns:
            List of tuples (path, disallowed_dependencies).
        """
        sp_hal_lib_names = set(x.name for x in sp_hal_libs)
        is_allowed_dep = lambda x: (x in self._ll_ndk or
                                    x in self._vndk_sp or
                                    x in sp_hal_lib_names)
        return self._FilterDisallowedDependencies(sp_hal_libs, is_allowed_dep)

    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_libs = set(obj for obj in objs if
                          obj.bitness == bitness and
                          obj.target_dir in vendor_link_paths)
        logging.info("%d-bit odm and vendor libraries including SP-HAL: %s",
                     bitness, ", ".join(x.name for x in vendor_libs))

        sp_hal_namespace = self._FindLibsInSpHalNamespace(bitness, objs)

        # Find same-process HAL and dependencies
        sp_hal_libs = set()
        for obj in sp_hal_namespace.itervalues():
            if any(x.match(obj.target_path) for x in self._sp_hal):
                self._DfsDependencies(obj, sp_hal_libs, sp_hal_namespace)
        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)
        logging.info("%d-bit VNDK-SP extension libraries and dependencies: %s",
                     bitness, ", ".join(x.name for x in vndk_sp_ext_deps))

        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._TestVendorDependency(vendor_objs, vendor_libs)

        # vndk_sp_ext_deps and sp_hal_libs may overlap. Their dependency
        # restrictions are the same.
        dep_errors.extend(self._TestVndkSpExtDependency(
            vndk_sp_ext_deps - sp_hal_libs, vendor_libs))

        if not vndk_utils.IsVndkRuntimeEnforced(self._dut):
            logging.warning("Ignore dependency errors: %s", dep_errors)
            dep_errors = []

        dep_errors.extend(self._TestSpHalDependency(sp_hal_libs))
        return dep_errors

    def testElfDependency(self):
        """Tests vendor libraries/executables and SP-HAL dependencies."""
        read_errors = []
        abi_list = self._dut.getCpuAbiList()
        objs = self._LoadElfObjects(
            self._temp_dir, self._TARGET_ROOT_DIR, abi_list,
            lambda p, e: read_errors.append((p, str(e))))

        dep_errors = self._TestElfDependency(32, objs)
        if self._dut.is64Bit:
            dep_errors.extend(self._TestElfDependency(64, objs))

        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))
        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))
        error_count = len(read_errors) + len(dep_errors)
        asserts.assertEqual(error_count, 0,
                            "Total number of errors: " + str(error_count))


if __name__ == "__main__":
    test_runner.main()