| #!/usr/bin/env python |
| # -*- coding: utf-8 -*- |
| |
| # Copyright (C) 2019 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 argparse |
| import fnmatch |
| import logging |
| import os |
| import os.path |
| import subprocess |
| import sys |
| import zipfile |
| |
| logging.basicConfig(format='%(message)s') |
| |
| # Architectures supported by APEX packages. |
| archs = ["arm", "arm64", "x86", "x86_64"] |
| # Directory containing ART tests within a Runtime APEX (if the package includes |
| # any). ART test executables are installed in `bin/art/<arch>`. Segregating |
| # tests by architecture is useful on devices supporting more than one |
| # architecture, as it permits testing all of them using a single Runtime APEX |
| # package. |
| art_test_dir = 'bin/art' |
| |
| class FSObject: |
| def __init__(self, name, is_dir, is_exec, is_symlink, size): |
| self.name = name |
| self.is_dir = is_dir |
| self.is_exec = is_exec |
| self.is_symlink = is_symlink |
| self.size = size |
| |
| def __str__(self): |
| return '%s(dir=%r,exec=%r,symlink=%r,size=%d)' \ |
| % (self.name, self.is_dir, self.is_exec, self.is_symlink, self.size) |
| |
| |
| class TargetApexProvider: |
| def __init__(self, apex, tmpdir, debugfs): |
| self._tmpdir = tmpdir |
| self._debugfs = debugfs |
| self._folder_cache = {} |
| self._payload = os.path.join(self._tmpdir, 'apex_payload.img') |
| # Extract payload to tmpdir. |
| apex_zip = zipfile.ZipFile(apex) |
| apex_zip.extract('apex_payload.img', tmpdir) |
| |
| def __del__(self): |
| # Delete temps. |
| if os.path.exists(self._payload): |
| os.remove(self._payload) |
| |
| def get(self, path): |
| apex_dir, name = os.path.split(path) |
| if not apex_dir: |
| apex_dir = '.' |
| apex_map = self.read_dir(apex_dir) |
| return apex_map[name] if name in apex_map else None |
| |
| def read_dir(self, apex_dir): |
| if apex_dir in self._folder_cache: |
| return self._folder_cache[apex_dir] |
| # Cannot use check_output as it will annoy with stderr. |
| process = subprocess.Popen([self._debugfs, '-R', 'ls -l -p %s' % apex_dir, self._payload], |
| stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| universal_newlines=True) |
| stdout, _ = process.communicate() |
| res = str(stdout) |
| apex_map = {} |
| # Debugfs output looks like this: |
| # debugfs 1.44.4 (18-Aug-2018) |
| # /12/040755/0/2000/.// |
| # /2/040755/1000/1000/..// |
| # /13/100755/0/2000/dalvikvm32/28456/ |
| # /14/100755/0/2000/dexoptanalyzer/20396/ |
| # /15/100755/0/2000/linker/1152724/ |
| # /16/100755/0/2000/dex2oat/563508/ |
| # /17/100755/0/2000/linker64/1605424/ |
| # /18/100755/0/2000/profman/85304/ |
| # /19/100755/0/2000/dalvikvm64/28576/ |
| # | | | | | | |
| # | | | #- gid #- name #- size |
| # | | #- uid |
| # | #- type and permission bits |
| # #- inode nr (?) |
| # |
| # Note: could break just on '/' to avoid names with newlines. |
| for line in res.split("\n"): |
| if not line: |
| continue |
| comps = line.split('/') |
| if len(comps) != 8: |
| logging.warning('Could not break and parse line \'%s\'', line) |
| continue |
| bits = comps[2] |
| name = comps[5] |
| size_str = comps[6] |
| # Use a negative value as an indicator of undefined/unknown size. |
| size = int(size_str) if size_str != '' else -1 |
| if len(bits) != 6: |
| logging.warning('Dont understand bits \'%s\'', bits) |
| continue |
| is_dir = bits[1] == '4' |
| |
| def is_exec_bit(ch): |
| return int(ch) & 1 == 1 |
| |
| is_exec = is_exec_bit(bits[3]) and is_exec_bit(bits[4]) and is_exec_bit(bits[5]) |
| is_symlink = bits[1] == '2' |
| apex_map[name] = FSObject(name, is_dir, is_exec, is_symlink, size) |
| self._folder_cache[apex_dir] = apex_map |
| return apex_map |
| |
| |
| class TargetFlattenedApexProvider: |
| def __init__(self, apex): |
| self._folder_cache = {} |
| self._apex = apex |
| |
| def get(self, path): |
| apex_dir, name = os.path.split(path) |
| if not apex_dir: |
| apex_dir = '.' |
| apex_map = self.read_dir(apex_dir) |
| return apex_map[name] if name in apex_map else None |
| |
| def read_dir(self, apex_dir): |
| if apex_dir in self._folder_cache: |
| return self._folder_cache[apex_dir] |
| apex_map = {} |
| dirname = os.path.join(self._apex, apex_dir) |
| if os.path.exists(dirname): |
| for basename in os.listdir(dirname): |
| filepath = os.path.join(dirname, basename) |
| is_dir = os.path.isdir(filepath) |
| is_exec = os.access(filepath, os.X_OK) |
| is_symlink = os.path.islink(filepath) |
| size = os.path.getsize(filepath) |
| apex_map[basename] = FSObject(basename, is_dir, is_exec, is_symlink, size) |
| self._folder_cache[apex_dir] = apex_map |
| return apex_map |
| |
| |
| class HostApexProvider: |
| def __init__(self, apex, tmpdir): |
| self._tmpdir = tmpdir |
| self._folder_cache = {} |
| self._payload = os.path.join(self._tmpdir, 'apex_payload.zip') |
| # Extract payload to tmpdir. |
| apex_zip = zipfile.ZipFile(apex) |
| apex_zip.extract('apex_payload.zip', tmpdir) |
| |
| def __del__(self): |
| # Delete temps. |
| if os.path.exists(self._payload): |
| os.remove(self._payload) |
| |
| def get(self, path): |
| apex_dir, name = os.path.split(path) |
| if not apex_dir: |
| apex_dir = '' |
| apex_map = self.read_dir(apex_dir) |
| return apex_map[name] if name in apex_map else None |
| |
| def read_dir(self, apex_dir): |
| if apex_dir in self._folder_cache: |
| return self._folder_cache[apex_dir] |
| if not self._folder_cache: |
| self.parse_zip() |
| if apex_dir in self._folder_cache: |
| return self._folder_cache[apex_dir] |
| return {} |
| |
| def parse_zip(self): |
| apex_zip = zipfile.ZipFile(self._payload) |
| infos = apex_zip.infolist() |
| for zipinfo in infos: |
| path = zipinfo.filename |
| |
| # Assume no empty file is stored. |
| assert path |
| |
| def get_octal(val, index): |
| return (val >> (index * 3)) & 0x7 |
| |
| def bits_is_exec(val): |
| # TODO: Enforce group/other, too? |
| return get_octal(val, 2) & 1 == 1 |
| |
| is_zipinfo = True |
| while path: |
| apex_dir, base = os.path.split(path) |
| # TODO: If directories are stored, base will be empty. |
| |
| if apex_dir not in self._folder_cache: |
| self._folder_cache[apex_dir] = {} |
| dir_map = self._folder_cache[apex_dir] |
| if base not in dir_map: |
| if is_zipinfo: |
| bits = (zipinfo.external_attr >> 16) & 0xFFFF |
| is_dir = get_octal(bits, 4) == 4 |
| is_symlink = get_octal(bits, 4) == 2 |
| is_exec = bits_is_exec(bits) |
| size = zipinfo.file_size |
| else: |
| is_exec = False # Seems we can't get this easily? |
| is_symlink = False |
| is_dir = True |
| # Use a negative value as an indicator of undefined/unknown size. |
| size = -1 |
| dir_map[base] = FSObject(base, is_dir, is_exec, is_symlink, size) |
| is_zipinfo = False |
| path = apex_dir |
| |
| |
| # DO NOT USE DIRECTLY! This is an "abstract" base class. |
| class Checker: |
| def __init__(self, provider): |
| self._provider = provider |
| self._errors = 0 |
| self._expected_file_globs = set() |
| |
| def fail(self, msg, *fail_args): |
| self._errors += 1 |
| logging.error(msg, *fail_args) |
| |
| def error_count(self): |
| return self._errors |
| |
| def reset_errors(self): |
| self._errors = 0 |
| |
| def is_file(self, path): |
| fs_object = self._provider.get(path) |
| if fs_object is None: |
| return False, 'Could not find %s' |
| if fs_object.is_dir: |
| return False, '%s is a directory' |
| return True, '' |
| |
| def check_file(self, path): |
| ok, msg = self.is_file(path) |
| if not ok: |
| self.fail(msg, path) |
| self._expected_file_globs.add(path) |
| return ok |
| |
| def check_executable(self, filename): |
| path = 'bin/%s' % filename |
| if not self.check_file(path): |
| return |
| if not self._provider.get(path).is_exec: |
| self.fail('%s is not executable', path) |
| |
| def check_executable_symlink(self, filename): |
| path = 'bin/%s' % filename |
| fs_object = self._provider.get(path) |
| if fs_object is None: |
| self.fail('Could not find %s', path) |
| return |
| if fs_object.is_dir: |
| self.fail('%s is a directory', path) |
| return |
| if not fs_object.is_symlink: |
| self.fail('%s is not a symlink', path) |
| self._expected_file_globs.add(path) |
| |
| def check_art_test_executable(self, filename): |
| # This is a simplistic implementation, as we declare victory as soon as the |
| # test binary is found for one of the supported (not built) architectures. |
| # Ideally we would propagate the built architectures from the build system |
| # to this script and require test binaries for all of them to be present. |
| # Note that this behavior is not specific to this method: there are other |
| # places in this script where we rely on this simplified strategy. |
| # |
| # TODO: Implement the suggestion above (here and in other places in this |
| # script). |
| test_found = False |
| for arch in archs: |
| test_path = '%s/%s/%s' % (art_test_dir, arch, filename) |
| test_is_file, _ = self.is_file(test_path) |
| if test_is_file: |
| test_found = True |
| self._expected_file_globs.add(test_path) |
| if not self._provider.get(test_path).is_exec: |
| self.fail('%s is not executable', test_path) |
| if not test_found: |
| self.fail('ART test binary missing: %s', filename) |
| |
| def check_single_library(self, filename): |
| lib_path = 'lib/%s' % filename |
| lib64_path = 'lib64/%s' % filename |
| lib_is_file, _ = self.is_file(lib_path) |
| if lib_is_file: |
| self._expected_file_globs.add(lib_path) |
| lib64_is_file, _ = self.is_file(lib64_path) |
| if lib64_is_file: |
| self._expected_file_globs.add(lib64_path) |
| if not lib_is_file and not lib64_is_file: |
| self.fail('Library missing: %s', filename) |
| |
| def check_java_library(self, basename): |
| return self.check_file('javalib/%s.jar' % basename) |
| |
| def ignore_path(self, path_glob): |
| self._expected_file_globs.add(path_glob) |
| |
| def check_optional_art_test_executable(self, filename): |
| for arch in archs: |
| self.ignore_path('%s/%s/%s' % (art_test_dir, arch, filename)) |
| |
| def check_no_superfluous_files(self, dir_path): |
| paths = [] |
| for name in sorted(self._provider.read_dir(dir_path).keys()): |
| if name not in ('.', '..'): |
| paths.append(os.path.join(dir_path, name)) |
| expected_paths = set() |
| dir_prefix = dir_path + '/' |
| for path_glob in self._expected_file_globs: |
| expected_paths |= set(fnmatch.filter(paths, path_glob)) |
| # If there are globs in subdirectories of dir_path we want to match their |
| # path segments at this directory level. |
| if path_glob.startswith(dir_prefix): |
| subpath = path_glob[len(dir_prefix):] |
| subpath_first_segment, _, _ = subpath.partition('/') |
| expected_paths |= set(fnmatch.filter(paths, dir_prefix + subpath_first_segment)) |
| for unexpected_path in set(paths) - expected_paths: |
| self.fail('Unexpected file \'%s\'', unexpected_path) |
| |
| # Just here for docs purposes, even if it isn't good Python style. |
| |
| def check_symlinked_multilib_executable(self, filename): |
| """Check bin/filename32, and/or bin/filename64, with symlink bin/filename.""" |
| raise NotImplementedError |
| |
| def check_multilib_executable(self, filename): |
| """Check bin/filename for 32 bit, and/or bin/filename64.""" |
| raise NotImplementedError |
| |
| def check_native_library(self, basename): |
| """Check lib/basename.so, and/or lib64/basename.so.""" |
| raise NotImplementedError |
| |
| def check_optional_native_library(self, basename_glob): |
| """Allow lib/basename.so and/or lib64/basename.so to exist.""" |
| raise NotImplementedError |
| |
| def check_prefer64_library(self, basename): |
| """Check lib64/basename.so, or lib/basename.so on 32 bit only.""" |
| raise NotImplementedError |
| |
| |
| class Arch32Checker(Checker): |
| def check_symlinked_multilib_executable(self, filename): |
| self.check_executable('%s32' % filename) |
| self.check_executable_symlink(filename) |
| |
| def check_multilib_executable(self, filename): |
| self.check_executable(filename) |
| |
| def check_native_library(self, basename): |
| # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve |
| # the precision of this test? |
| self.check_file('lib/%s.so' % basename) |
| |
| def check_optional_native_library(self, basename_glob): |
| self.ignore_path('lib/%s.so' % basename_glob) |
| |
| def check_prefer64_library(self, basename): |
| self.check_native_library(basename) |
| |
| |
| class Arch64Checker(Checker): |
| def check_symlinked_multilib_executable(self, filename): |
| self.check_executable('%s64' % filename) |
| self.check_executable_symlink(filename) |
| |
| def check_multilib_executable(self, filename): |
| self.check_executable('%s64' % filename) |
| |
| def check_native_library(self, basename): |
| # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve |
| # the precision of this test? |
| self.check_file('lib64/%s.so' % basename) |
| |
| def check_optional_native_library(self, basename_glob): |
| self.ignore_path('lib64/%s.so' % basename_glob) |
| |
| def check_prefer64_library(self, basename): |
| self.check_native_library(basename) |
| |
| |
| class MultilibChecker(Checker): |
| def check_symlinked_multilib_executable(self, filename): |
| self.check_executable('%s32' % filename) |
| self.check_executable('%s64' % filename) |
| self.check_executable_symlink(filename) |
| |
| def check_multilib_executable(self, filename): |
| self.check_executable('%s64' % filename) |
| self.check_executable(filename) |
| |
| def check_native_library(self, basename): |
| # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve |
| # the precision of this test? |
| self.check_file('lib/%s.so' % basename) |
| self.check_file('lib64/%s.so' % basename) |
| |
| def check_optional_native_library(self, basename_glob): |
| self.ignore_path('lib/%s.so' % basename_glob) |
| self.ignore_path('lib64/%s.so' % basename_glob) |
| |
| def check_prefer64_library(self, basename): |
| self.check_file('lib64/%s.so' % basename) |
| |
| |
| class ReleaseChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'Release Checker' |
| |
| def run(self): |
| # Check the APEX manifest. |
| self._checker.check_file('apex_manifest.json') |
| |
| # Check binaries for ART. |
| self._checker.check_executable('dex2oat') |
| self._checker.check_executable('dexdump') |
| self._checker.check_executable('dexlist') |
| self._checker.check_executable('dexoptanalyzer') |
| self._checker.check_executable('profman') |
| self._checker.check_symlinked_multilib_executable('dalvikvm') |
| |
| # Check exported libraries for ART. |
| self._checker.check_native_library('libdexfile_external') |
| self._checker.check_native_library('libnativebridge') |
| self._checker.check_native_library('libnativehelper') |
| self._checker.check_native_library('libnativeloader') |
| |
| # Check internal libraries for ART. |
| self._checker.check_native_library('libadbconnection') |
| self._checker.check_native_library('libart') |
| self._checker.check_native_library('libart-compiler') |
| self._checker.check_native_library('libart-dexlayout') |
| self._checker.check_native_library('libartbase') |
| self._checker.check_native_library('libartpalette') |
| self._checker.check_native_library('libdexfile') |
| self._checker.check_native_library('libdexfile_support') |
| self._checker.check_native_library('libopenjdkjvm') |
| self._checker.check_native_library('libopenjdkjvmti') |
| self._checker.check_native_library('libprofile') |
| self._checker.check_native_library('libsigchain') |
| |
| # Check java libraries for Managed Core Library. |
| self._checker.check_java_library('apache-xml') |
| self._checker.check_java_library('bouncycastle') |
| self._checker.check_java_library('core-icu4j') |
| self._checker.check_java_library('core-libart') |
| self._checker.check_java_library('core-oj') |
| self._checker.check_java_library('okhttp') |
| |
| # Check internal native libraries for Managed Core Library. |
| self._checker.check_native_library('libjavacore') |
| self._checker.check_native_library('libopenjdk') |
| |
| # Check internal native library dependencies. |
| # |
| # Any internal dependency not listed here will cause a failure in |
| # NoSuperfluousLibrariesChecker. Internal dependencies are generally just |
| # implementation details, but in the release package we want to track them |
| # because a) they add to the package size and the RAM usage (in particular |
| # if the library is also present in /system or another APEX and hence might |
| # get loaded twice through linker namespace separation), and b) we need to |
| # catch invalid dependencies on /system or other APEXes that should go |
| # through an exported library with stubs (b/128708192 tracks implementing a |
| # better approach for that). |
| self._checker.check_native_library('libbacktrace') |
| self._checker.check_native_library('libbase') |
| self._checker.check_native_library('libc++') |
| self._checker.check_native_library('libdt_fd_forward') |
| self._checker.check_native_library('libdt_socket') |
| self._checker.check_native_library('libjdwp') |
| self._checker.check_native_library('liblzma') |
| self._checker.check_native_library('libnpt') |
| self._checker.check_native_library('libunwindstack') |
| self._checker.check_native_library('libziparchive') |
| self._checker.check_optional_native_library('libvixl') # Only on ARM/ARM64 |
| |
| # Allow extra dependencies that appear in ASAN builds. |
| self._checker.check_optional_native_library('libclang_rt.asan*') |
| self._checker.check_optional_native_library('libclang_rt.hwasan*') |
| self._checker.check_optional_native_library('libclang_rt.ubsan*') |
| |
| |
| class ReleaseTargetChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'Release (Target) Checker' |
| |
| def run(self): |
| # Check the APEX package scripts. |
| self._checker.check_executable('art_postinstall_hook') |
| self._checker.check_executable('art_preinstall_hook') |
| self._checker.check_executable('art_preinstall_hook_boot') |
| self._checker.check_executable('art_preinstall_hook_system_server') |
| self._checker.check_executable('art_prepostinstall_utils') |
| |
| # Check binaries for ART. |
| self._checker.check_executable('oatdump') |
| |
| # Check internal libraries for ART. |
| self._checker.check_prefer64_library('libart-disassembler') |
| self._checker.check_native_library('libperfetto_hprof') |
| |
| # Check exported native libraries for Managed Core Library. |
| self._checker.check_native_library('libandroidicu') |
| self._checker.check_native_library('libandroidio') |
| |
| # Check internal native library dependencies. |
| self._checker.check_native_library('libcrypto') |
| self._checker.check_native_library('libexpat') |
| self._checker.check_native_library('libicui18n') |
| self._checker.check_native_library('libicuuc') |
| self._checker.check_native_library('libpac') |
| self._checker.check_native_library('libz') |
| |
| # TODO(b/139046641): Fix proper 2nd arch checks. For now, just ignore these |
| # directories. |
| self._checker.ignore_path('bin/arm') |
| self._checker.ignore_path('lib/arm') |
| self._checker.ignore_path('lib64/arm') |
| |
| |
| class ReleaseHostChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'Release (Host) Checker' |
| |
| def run(self): |
| # Check binaries for ART. |
| self._checker.check_executable('hprof-conv') |
| self._checker.check_symlinked_multilib_executable('dex2oatd') |
| |
| # Check exported native libraries for Managed Core Library. |
| self._checker.check_native_library('libandroidicu-host') |
| self._checker.check_native_library('libandroidio') |
| |
| # Check internal libraries for Managed Core Library. |
| self._checker.check_native_library('libexpat-host') |
| self._checker.check_native_library('libicui18n-host') |
| self._checker.check_native_library('libicuuc-host') |
| self._checker.check_native_library('libz-host') |
| |
| |
| class DebugChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'Debug Checker' |
| |
| def run(self): |
| # Check binaries for ART. |
| self._checker.check_executable('dexdiag') |
| self._checker.check_executable('dexanalyze') |
| self._checker.check_executable('dexlayout') |
| self._checker.check_symlinked_multilib_executable('imgdiag') |
| |
| # Check debug binaries for ART. |
| self._checker.check_executable('dexlayoutd') |
| self._checker.check_executable('dexoptanalyzerd') |
| self._checker.check_symlinked_multilib_executable('imgdiagd') |
| self._checker.check_executable('profmand') |
| |
| # Check internal libraries for ART. |
| self._checker.check_native_library('libadbconnectiond') |
| self._checker.check_native_library('libartbased') |
| self._checker.check_native_library('libartd') |
| self._checker.check_native_library('libartd-compiler') |
| self._checker.check_native_library('libartd-dexlayout') |
| self._checker.check_native_library('libdexfiled') |
| self._checker.check_native_library('libopenjdkjvmd') |
| self._checker.check_native_library('libopenjdkjvmtid') |
| self._checker.check_native_library('libprofiled') |
| |
| # Check internal libraries for Managed Core Library. |
| self._checker.check_native_library('libopenjdkd') |
| |
| |
| class DebugTargetChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'Debug (Target) Checker' |
| |
| def run(self): |
| # Check ART debug binaries. |
| self._checker.check_executable('dex2oatd') |
| self._checker.check_executable('oatdumpd') |
| |
| # Check ART internal libraries. |
| self._checker.check_native_library('libdexfiled_external') |
| self._checker.check_prefer64_library('libartd-disassembler') |
| self._checker.check_native_library('libperfetto_hprofd') |
| |
| # Check internal native library dependencies. |
| # |
| # Like in the release package, we check that we don't get other dependencies |
| # besides those listed here. In this case the concern is not bloat, but |
| # rather that we don't get behavioural differences between user (release) |
| # and userdebug/eng builds, which could happen if the debug package has |
| # duplicate library instances where releases don't. In other words, it's |
| # uncontroversial to add debug-only dependencies, as long as they don't make |
| # assumptions on having a single global state (ideally they should have |
| # double_loadable:true, cf. go/double_loadable). Also, like in the release |
| # package we need to look out for dependencies that should go through |
| # exported library stubs (until b/128708192 is fixed). |
| self._checker.check_optional_native_library('libvixld') # Only on ARM/ARM64 |
| self._checker.check_prefer64_library('libmeminfo') |
| self._checker.check_prefer64_library('libprocinfo') |
| |
| |
| class TestingTargetChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'Testing (Target) Checker' |
| |
| def run(self): |
| # Check cmdline tests. |
| self._checker.check_optional_art_test_executable('cmdline_parser_test') |
| |
| # Check compiler tests. |
| self._checker.check_art_test_executable('atomic_dex_ref_map_test') |
| self._checker.check_art_test_executable('bounds_check_elimination_test') |
| self._checker.check_art_test_executable('codegen_test') |
| self._checker.check_art_test_executable('compiled_method_storage_test') |
| self._checker.check_art_test_executable('data_type_test') |
| self._checker.check_art_test_executable('dedupe_set_test') |
| self._checker.check_art_test_executable('dominator_test') |
| self._checker.check_art_test_executable('dwarf_test') |
| self._checker.check_art_test_executable('exception_test') |
| self._checker.check_art_test_executable('find_loops_test') |
| self._checker.check_art_test_executable('graph_checker_test') |
| self._checker.check_art_test_executable('graph_test') |
| self._checker.check_art_test_executable('gvn_test') |
| self._checker.check_art_test_executable('induction_var_analysis_test') |
| self._checker.check_art_test_executable('induction_var_range_test') |
| self._checker.check_art_test_executable('jni_cfi_test') |
| self._checker.check_art_test_executable('jni_compiler_test') |
| self._checker.check_art_test_executable('licm_test') |
| self._checker.check_art_test_executable('linker_patch_test') |
| self._checker.check_art_test_executable('live_interval_test') |
| self._checker.check_art_test_executable('load_store_analysis_test') |
| self._checker.check_art_test_executable('loop_optimization_test') |
| self._checker.check_art_test_executable('nodes_test') |
| self._checker.check_art_test_executable('nodes_vector_test') |
| self._checker.check_art_test_executable('optimizing_cfi_test') |
| self._checker.check_art_test_executable('output_stream_test') |
| self._checker.check_art_test_executable('parallel_move_test') |
| self._checker.check_art_test_executable('pretty_printer_test') |
| self._checker.check_art_test_executable('reference_type_propagation_test') |
| self._checker.check_art_test_executable('scheduler_test') |
| self._checker.check_art_test_executable('select_generator_test') |
| self._checker.check_art_test_executable('side_effects_test') |
| self._checker.check_art_test_executable('src_map_elem_test') |
| self._checker.check_art_test_executable('ssa_liveness_analysis_test') |
| self._checker.check_art_test_executable('ssa_test') |
| self._checker.check_art_test_executable('stack_map_test') |
| self._checker.check_art_test_executable('superblock_cloner_test') |
| self._checker.check_art_test_executable('suspend_check_test') |
| self._checker.check_art_test_executable('swap_space_test') |
| # These tests depend on a specific code generator and are conditionally included. |
| self._checker.check_optional_art_test_executable('constant_folding_test') |
| self._checker.check_optional_art_test_executable('dead_code_elimination_test') |
| self._checker.check_optional_art_test_executable('linearize_test') |
| self._checker.check_optional_art_test_executable('live_ranges_test') |
| self._checker.check_optional_art_test_executable('liveness_test') |
| self._checker.check_optional_art_test_executable('managed_register_arm64_test') |
| self._checker.check_optional_art_test_executable('managed_register_arm_test') |
| self._checker.check_optional_art_test_executable('managed_register_mips64_test') |
| self._checker.check_optional_art_test_executable('managed_register_x86_64_test') |
| self._checker.check_optional_art_test_executable('managed_register_x86_test') |
| self._checker.check_optional_art_test_executable('register_allocator_test') |
| |
| # Check dex2oat tests. |
| self._checker.check_art_test_executable('compiler_driver_test') |
| self._checker.check_art_test_executable('dex2oat_image_test') |
| self._checker.check_art_test_executable('dex2oat_test') |
| self._checker.check_art_test_executable('dex_to_dex_decompiler_test') |
| self._checker.check_art_test_executable('elf_writer_test') |
| self._checker.check_art_test_executable('image_test') |
| self._checker.check_art_test_executable('image_write_read_test') |
| self._checker.check_art_test_executable('index_bss_mapping_encoder_test') |
| self._checker.check_art_test_executable('multi_oat_relative_patcher_test') |
| self._checker.check_art_test_executable('oat_writer_test') |
| self._checker.check_art_test_executable('verifier_deps_test') |
| # These tests depend on a specific code generator and are conditionally included. |
| self._checker.check_optional_art_test_executable('relative_patcher_arm64_test') |
| self._checker.check_optional_art_test_executable('relative_patcher_mips32r6_test') |
| self._checker.check_optional_art_test_executable('relative_patcher_mips64_test') |
| self._checker.check_optional_art_test_executable('relative_patcher_mips_test') |
| self._checker.check_optional_art_test_executable('relative_patcher_thumb2_test') |
| self._checker.check_optional_art_test_executable('relative_patcher_x86_64_test') |
| self._checker.check_optional_art_test_executable('relative_patcher_x86_test') |
| |
| # Check dexanalyze tests. |
| self._checker.check_optional_art_test_executable('dexanalyze_test') |
| |
| # Check dexdiag tests. |
| self._checker.check_optional_art_test_executable('dexdiag_test') |
| |
| # Check dexdump tests. |
| self._checker.check_art_test_executable('dexdump_test') |
| |
| # Check dexlayout tests. |
| self._checker.check_optional_art_test_executable('dexlayout_test') |
| |
| # Check dexlist tests. |
| self._checker.check_art_test_executable('dexlist_test') |
| |
| # Check dexoptanalyzer tests. |
| self._checker.check_art_test_executable('dexoptanalyzer_test') |
| |
| # Check imgdiag tests. |
| self._checker.check_art_test_executable('imgdiag_test') |
| |
| # Check libartbase tests. |
| self._checker.check_art_test_executable('arena_allocator_test') |
| self._checker.check_art_test_executable('bit_field_test') |
| self._checker.check_art_test_executable('bit_memory_region_test') |
| self._checker.check_art_test_executable('bit_string_test') |
| self._checker.check_art_test_executable('bit_struct_test') |
| self._checker.check_art_test_executable('bit_table_test') |
| self._checker.check_art_test_executable('bit_utils_test') |
| self._checker.check_art_test_executable('bit_vector_test') |
| self._checker.check_art_test_executable('fd_file_test') |
| self._checker.check_art_test_executable('file_utils_test') |
| self._checker.check_art_test_executable('hash_set_test') |
| self._checker.check_art_test_executable('hex_dump_test') |
| self._checker.check_art_test_executable('histogram_test') |
| self._checker.check_art_test_executable('indenter_test') |
| self._checker.check_art_test_executable('instruction_set_test') |
| self._checker.check_art_test_executable('intrusive_forward_list_test') |
| self._checker.check_art_test_executable('leb128_test') |
| self._checker.check_art_test_executable('logging_test') |
| self._checker.check_art_test_executable('mem_map_test') |
| self._checker.check_art_test_executable('membarrier_test') |
| self._checker.check_art_test_executable('memfd_test') |
| self._checker.check_art_test_executable('memory_region_test') |
| self._checker.check_art_test_executable('memory_type_table_test') |
| self._checker.check_art_test_executable('safe_copy_test') |
| self._checker.check_art_test_executable('scoped_flock_test') |
| self._checker.check_art_test_executable('time_utils_test') |
| self._checker.check_art_test_executable('transform_array_ref_test') |
| self._checker.check_art_test_executable('transform_iterator_test') |
| self._checker.check_art_test_executable('utils_test') |
| self._checker.check_art_test_executable('variant_map_test') |
| self._checker.check_art_test_executable('zip_archive_test') |
| |
| # Check libartpalette tests. |
| self._checker.check_art_test_executable('palette_test') |
| |
| # Check libdexfile tests. |
| self._checker.check_art_test_executable('art_dex_file_loader_test') |
| self._checker.check_art_test_executable('art_libdexfile_support_tests') |
| self._checker.check_art_test_executable('class_accessor_test') |
| self._checker.check_art_test_executable('code_item_accessors_test') |
| self._checker.check_art_test_executable('compact_dex_file_test') |
| self._checker.check_art_test_executable('compact_offset_table_test') |
| self._checker.check_art_test_executable('descriptors_names_test') |
| self._checker.check_art_test_executable('dex_file_loader_test') |
| self._checker.check_art_test_executable('dex_file_verifier_test') |
| self._checker.check_art_test_executable('dex_instruction_test') |
| self._checker.check_art_test_executable('primitive_test') |
| self._checker.check_art_test_executable('string_reference_test') |
| self._checker.check_art_test_executable('test_dex_file_builder_test') |
| self._checker.check_art_test_executable('type_lookup_table_test') |
| self._checker.check_art_test_executable('utf_test') |
| |
| # Check libprofile tests. |
| self._checker.check_optional_art_test_executable('profile_boot_info_test') |
| self._checker.check_optional_art_test_executable('profile_compilation_info_test') |
| |
| # Check oatdump tests. |
| self._checker.check_art_test_executable('oatdump_app_test') |
| self._checker.check_art_test_executable('oatdump_image_test') |
| self._checker.check_art_test_executable('oatdump_test') |
| |
| # Check profman tests. |
| self._checker.check_art_test_executable('profile_assistant_test') |
| |
| # Check runtime compiler tests. |
| self._checker.check_art_test_executable('module_exclusion_test') |
| self._checker.check_art_test_executable('reflection_test') |
| |
| # Check runtime tests. |
| self._checker.check_art_test_executable('arch_test') |
| self._checker.check_art_test_executable('barrier_test') |
| self._checker.check_art_test_executable('card_table_test') |
| self._checker.check_art_test_executable('cha_test') |
| self._checker.check_art_test_executable('class_linker_test') |
| self._checker.check_art_test_executable('class_loader_context_test') |
| self._checker.check_art_test_executable('class_table_test') |
| self._checker.check_art_test_executable('compiler_filter_test') |
| self._checker.check_art_test_executable('dex_cache_test') |
| self._checker.check_art_test_executable('dlmalloc_space_random_test') |
| self._checker.check_art_test_executable('dlmalloc_space_static_test') |
| self._checker.check_art_test_executable('entrypoints_order_test') |
| self._checker.check_art_test_executable('exec_utils_test') |
| self._checker.check_art_test_executable('gtest_test') |
| self._checker.check_art_test_executable('handle_scope_test') |
| self._checker.check_art_test_executable('heap_test') |
| self._checker.check_art_test_executable('heap_verification_test') |
| self._checker.check_art_test_executable('hidden_api_test') |
| self._checker.check_art_test_executable('image_space_test') |
| self._checker.check_art_test_executable('immune_spaces_test') |
| self._checker.check_art_test_executable('imtable_test') |
| self._checker.check_art_test_executable('indirect_reference_table_test') |
| self._checker.check_art_test_executable('instruction_set_features_arm64_test') |
| self._checker.check_art_test_executable('instruction_set_features_arm_test') |
| self._checker.check_art_test_executable('instruction_set_features_mips64_test') |
| self._checker.check_art_test_executable('instruction_set_features_mips_test') |
| self._checker.check_art_test_executable('instruction_set_features_test') |
| self._checker.check_art_test_executable('instruction_set_features_x86_64_test') |
| self._checker.check_art_test_executable('instruction_set_features_x86_test') |
| self._checker.check_art_test_executable('instrumentation_test') |
| self._checker.check_art_test_executable('intern_table_test') |
| self._checker.check_art_test_executable('java_vm_ext_test') |
| self._checker.check_art_test_executable('jdwp_options_test') |
| self._checker.check_art_test_executable('jit_memory_region_test') |
| self._checker.check_art_test_executable('jni_internal_test') |
| self._checker.check_art_test_executable('large_object_space_test') |
| self._checker.check_art_test_executable('math_entrypoints_test') |
| self._checker.check_art_test_executable('memcmp16_test') |
| self._checker.check_art_test_executable('method_handles_test') |
| self._checker.check_art_test_executable('method_type_test') |
| self._checker.check_art_test_executable('method_verifier_test') |
| self._checker.check_art_test_executable('mod_union_table_test') |
| self._checker.check_art_test_executable('monitor_pool_test') |
| self._checker.check_art_test_executable('monitor_test') |
| self._checker.check_art_test_executable('mutex_test') |
| self._checker.check_art_test_executable('oat_file_assistant_test') |
| self._checker.check_art_test_executable('oat_file_test') |
| self._checker.check_art_test_executable('object_test') |
| self._checker.check_art_test_executable('parsed_options_test') |
| self._checker.check_art_test_executable('prebuilt_tools_test') |
| self._checker.check_art_test_executable('profiling_info_test') |
| self._checker.check_art_test_executable('proxy_test') |
| self._checker.check_art_test_executable('quick_trampoline_entrypoints_test') |
| self._checker.check_art_test_executable('reference_queue_test') |
| self._checker.check_art_test_executable('reference_table_test') |
| self._checker.check_art_test_executable('reg_type_test') |
| self._checker.check_art_test_executable('rosalloc_space_random_test') |
| self._checker.check_art_test_executable('rosalloc_space_static_test') |
| self._checker.check_art_test_executable('runtime_callbacks_test') |
| self._checker.check_art_test_executable('runtime_test') |
| self._checker.check_art_test_executable('safe_math_test') |
| self._checker.check_art_test_executable('space_bitmap_test') |
| self._checker.check_art_test_executable('space_create_test') |
| self._checker.check_art_test_executable('stub_test') |
| self._checker.check_art_test_executable('subtype_check_info_test') |
| self._checker.check_art_test_executable('subtype_check_test') |
| self._checker.check_art_test_executable('system_weak_test') |
| self._checker.check_art_test_executable('task_processor_test') |
| self._checker.check_art_test_executable('thread_pool_test') |
| self._checker.check_art_test_executable('timing_logger_test') |
| self._checker.check_art_test_executable('transaction_test') |
| self._checker.check_art_test_executable('two_runtimes_test') |
| self._checker.check_art_test_executable('unstarted_runtime_test') |
| self._checker.check_art_test_executable('var_handle_test') |
| self._checker.check_art_test_executable('vdex_file_test') |
| |
| # Check sigchainlib tests. |
| self._checker.check_art_test_executable('sigchain_test') |
| |
| # Check ART test (internal) libraries. |
| self._checker.check_native_library('libart-gtest') |
| self._checker.check_native_library('libartd-disassembler') |
| self._checker.check_native_library('libartd-simulator-container') |
| |
| # Check ART test tools. |
| self._checker.check_executable('signal_dumper') |
| |
| |
| class NoSuperfluousBinariesChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'No superfluous binaries checker' |
| |
| def run(self): |
| self._checker.check_no_superfluous_files('bin') |
| |
| |
| class NoSuperfluousLibrariesChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'No superfluous libraries checker' |
| |
| def run(self): |
| self._checker.check_no_superfluous_files('javalib') |
| self._checker.check_no_superfluous_files('lib') |
| self._checker.check_no_superfluous_files('lib/bionic') |
| self._checker.check_no_superfluous_files('lib64') |
| self._checker.check_no_superfluous_files('lib64/bionic') |
| |
| |
| class NoSuperfluousArtTestsChecker: |
| def __init__(self, checker): |
| self._checker = checker |
| |
| def __str__(self): |
| return 'No superfluous ART tests checker' |
| |
| def run(self): |
| for arch in archs: |
| self._checker.check_no_superfluous_files('%s/%s' % (art_test_dir, arch)) |
| |
| |
| class List: |
| def __init__(self, provider, print_size=False): |
| self._provider = provider |
| self._print_size = print_size |
| |
| def print_list(self): |
| |
| def print_list_rec(path): |
| apex_map = self._provider.read_dir(path) |
| if apex_map is None: |
| return |
| apex_map = dict(apex_map) |
| if '.' in apex_map: |
| del apex_map['.'] |
| if '..' in apex_map: |
| del apex_map['..'] |
| for (_, val) in sorted(apex_map.items()): |
| val_path = os.path.join(path, val.name) |
| if self._print_size: |
| if val.size < 0: |
| print('[ n/a ] %s' % val_path) |
| else: |
| print('[%11d] %s' % (val.size, val_path)) |
| else: |
| print(val_path) |
| if val.is_dir: |
| print_list_rec(val_path) |
| |
| print_list_rec('') |
| |
| |
| class Tree: |
| def __init__(self, provider, title, print_size=False): |
| print('%s' % title) |
| self._provider = provider |
| self._has_next_list = [] |
| self._print_size = print_size |
| |
| @staticmethod |
| def get_vertical(has_next_list): |
| string = '' |
| for v in has_next_list: |
| string += '%s ' % ('│' if v else ' ') |
| return string |
| |
| @staticmethod |
| def get_last_vertical(last): |
| return '└── ' if last else '├── ' |
| |
| def print_tree(self): |
| |
| def print_tree_rec(path): |
| apex_map = self._provider.read_dir(path) |
| if apex_map is None: |
| return |
| apex_map = dict(apex_map) |
| if '.' in apex_map: |
| del apex_map['.'] |
| if '..' in apex_map: |
| del apex_map['..'] |
| key_list = list(sorted(apex_map.keys())) |
| for i, key in enumerate(key_list): |
| prev = self.get_vertical(self._has_next_list) |
| last = self.get_last_vertical(i == len(key_list) - 1) |
| val = apex_map[key] |
| if self._print_size: |
| if val.size < 0: |
| print('%s%s[ n/a ] %s' % (prev, last, val.name)) |
| else: |
| print('%s%s[%11d] %s' % (prev, last, val.size, val.name)) |
| else: |
| print('%s%s%s' % (prev, last, val.name)) |
| if val.is_dir: |
| self._has_next_list.append(i < len(key_list) - 1) |
| val_path = os.path.join(path, val.name) |
| print_tree_rec(val_path) |
| self._has_next_list.pop() |
| |
| print_tree_rec('') |
| |
| |
| # Note: do not sys.exit early, for __del__ cleanup. |
| def art_apex_test_main(test_args): |
| if test_args.host and test_args.flattened: |
| logging.error("Both of --host and --flattened set") |
| return 1 |
| if test_args.tree and test_args.debug: |
| logging.error("Both of --tree and --debug set") |
| return 1 |
| if test_args.tree and test_args.testing: |
| logging.error("Both of --tree and --testing set") |
| return 1 |
| if test_args.list and test_args.debug: |
| logging.error("Both of --list and --debug set") |
| return 1 |
| if test_args.list and test_args.testing: |
| logging.error("Both of --list and --testing set") |
| return 1 |
| if test_args.list and test_args.tree: |
| logging.error("Both of --list and --tree set") |
| return 1 |
| if test_args.size and not (test_args.list or test_args.tree): |
| logging.error("--size set but neither --list nor --tree set") |
| return 1 |
| if test_args.host and test_args.testing: |
| logging.error("Both of --host and --testing set") |
| return 1 |
| if not test_args.flattened and not test_args.tmpdir: |
| logging.error("Need a tmpdir.") |
| return 1 |
| if not test_args.flattened and not test_args.host and not test_args.debugfs: |
| logging.error("Need debugfs.") |
| return 1 |
| if test_args.bitness not in ['32', '64', 'multilib', 'auto']: |
| logging.error('--bitness needs to be one of 32|64|multilib|auto') |
| |
| try: |
| if test_args.host: |
| apex_provider = HostApexProvider(test_args.apex, test_args.tmpdir) |
| else: |
| if test_args.flattened: |
| apex_provider = TargetFlattenedApexProvider(test_args.apex) |
| else: |
| apex_provider = TargetApexProvider(test_args.apex, test_args.tmpdir, test_args.debugfs) |
| except (zipfile.BadZipFile, zipfile.LargeZipFile) as e: |
| logging.error('Failed to create provider: %s', e) |
| return 1 |
| |
| if test_args.tree: |
| Tree(apex_provider, test_args.apex, test_args.size).print_tree() |
| return 0 |
| if test_args.list: |
| List(apex_provider, test_args.size).print_list() |
| return 0 |
| |
| checkers = [] |
| if test_args.bitness == 'auto': |
| logging.warning('--bitness=auto, trying to autodetect. This may be incorrect!') |
| has_32 = apex_provider.get('lib') is not None |
| has_64 = apex_provider.get('lib64') is not None |
| if has_32 and has_64: |
| logging.warning(' Detected multilib') |
| test_args.bitness = 'multilib' |
| elif has_32: |
| logging.warning(' Detected 32-only') |
| test_args.bitness = '32' |
| elif has_64: |
| logging.warning(' Detected 64-only') |
| test_args.bitness = '64' |
| else: |
| logging.error(' Could not detect bitness, neither lib nor lib64 contained.') |
| List(apex_provider).print_list() |
| return 1 |
| |
| if test_args.bitness == '32': |
| base_checker = Arch32Checker(apex_provider) |
| elif test_args.bitness == '64': |
| base_checker = Arch64Checker(apex_provider) |
| else: |
| assert test_args.bitness == 'multilib' |
| base_checker = MultilibChecker(apex_provider) |
| |
| checkers.append(ReleaseChecker(base_checker)) |
| if test_args.host: |
| checkers.append(ReleaseHostChecker(base_checker)) |
| else: |
| checkers.append(ReleaseTargetChecker(base_checker)) |
| if test_args.debug or test_args.testing: |
| checkers.append(DebugChecker(base_checker)) |
| if not test_args.host: |
| checkers.append(DebugTargetChecker(base_checker)) |
| if test_args.testing: |
| checkers.append(TestingTargetChecker(base_checker)) |
| |
| # These checkers must be last. |
| checkers.append(NoSuperfluousBinariesChecker(base_checker)) |
| checkers.append(NoSuperfluousArtTestsChecker(base_checker)) |
| if not test_args.host: |
| # We only care about superfluous libraries on target, where their absence |
| # can be vital to ensure they get picked up from the right package. |
| checkers.append(NoSuperfluousLibrariesChecker(base_checker)) |
| |
| failed = False |
| for checker in checkers: |
| logging.info('%s...', checker) |
| checker.run() |
| if base_checker.error_count() > 0: |
| logging.error('%s FAILED', checker) |
| failed = True |
| else: |
| logging.info('%s SUCCEEDED', checker) |
| base_checker.reset_errors() |
| |
| return 1 if failed else 0 |
| |
| |
| def art_apex_test_default(test_parser): |
| if 'ANDROID_PRODUCT_OUT' not in os.environ: |
| logging.error('No-argument use requires ANDROID_PRODUCT_OUT') |
| sys.exit(1) |
| product_out = os.environ['ANDROID_PRODUCT_OUT'] |
| if 'ANDROID_HOST_OUT' not in os.environ: |
| logging.error('No-argument use requires ANDROID_HOST_OUT') |
| sys.exit(1) |
| host_out = os.environ['ANDROID_HOST_OUT'] |
| |
| test_args = test_parser.parse_args(['dummy']) # For consistency. |
| test_args.debugfs = '%s/bin/debugfs' % host_out |
| test_args.tmpdir = '.' |
| test_args.tree = False |
| test_args.list = False |
| test_args.bitness = 'auto' |
| failed = False |
| |
| if not os.path.exists(test_args.debugfs): |
| logging.error("Cannot find debugfs (default path %s). Please build it, e.g., m debugfs", |
| test_args.debugfs) |
| sys.exit(1) |
| |
| # TODO: Add host support |
| configs = [ |
| {'name': 'com.android.art.release', 'debug': False, 'testing': False, 'host': False}, |
| {'name': 'com.android.art.debug', 'debug': True, 'testing': False, 'host': False}, |
| {'name': 'com.android.art.testing', 'debug': False, 'testing': True, 'host': False}, |
| ] |
| |
| for config in configs: |
| logging.info(config['name']) |
| # TODO: Host will need different path. |
| test_args.apex = '%s/system/apex/%s.apex' % (product_out, config['name']) |
| if not os.path.exists(test_args.apex): |
| failed = True |
| logging.error("Cannot find APEX %s. Please build it first.", test_args.apex) |
| continue |
| test_args.debug = config['debug'] |
| test_args.testing = config['testing'] |
| test_args.host = config['host'] |
| failed = art_apex_test_main(test_args) != 0 |
| |
| if failed: |
| sys.exit(1) |
| |
| |
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser(description='Check integrity of a Runtime APEX.') |
| |
| parser.add_argument('apex', help='apex file input') |
| |
| parser.add_argument('--host', help='Check as host apex', action='store_true') |
| |
| parser.add_argument('--flattened', help='Check as flattened (target) apex', action='store_true') |
| |
| parser.add_argument('--debug', help='Check as debug apex', action='store_true') |
| parser.add_argument('--testing', help='Check as testing apex', action='store_true') |
| |
| parser.add_argument('--list', help='List all files', action='store_true') |
| parser.add_argument('--tree', help='Print directory tree', action='store_true') |
| parser.add_argument('--size', help='Print file sizes', action='store_true') |
| |
| parser.add_argument('--tmpdir', help='Directory for temp files') |
| parser.add_argument('--debugfs', help='Path to debugfs') |
| |
| parser.add_argument('--bitness', help='Bitness to check, 32|64|multilib|auto', default='auto') |
| |
| if len(sys.argv) == 1: |
| art_apex_test_default(parser) |
| else: |
| args = parser.parse_args() |
| |
| if args is None: |
| sys.exit(1) |
| |
| exit_code = art_apex_test_main(args) |
| sys.exit(exit_code) |