summaryrefslogtreecommitdiff
path: root/dependency/VtsVndkDependencyTest.py
blob: b39f3200e247e20c554dd02df0032ef3e8721906 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/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()