ART: Add host apex provider and move all testing

Add HostApexProvider to access zipapexes. Update some common
code.

Wire up host apex testing. Remove all testing from runtests.sh.

Test: art/build/apex/runtests.sh
Test: art/build/apex/runtests.sh -l -t
Change-Id: I6442fdf28d001e39e2ace2b9b6ce2e25ce98af78
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index e636a72..b686152 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -51,7 +51,7 @@
   def get(self, path):
     dir, name = os.path.split(path)
     if len(dir) == 0:
-      dir = '/'
+      dir = '.'
     map = self.read_dir(dir)
     return map[name] if name in map else None
 
@@ -104,6 +104,73 @@
     self._folder_cache[dir] = map
     return 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.
+    zip = zipfile.ZipFile(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):
+    dir, name = os.path.split(path)
+    if len(dir) == 0:
+      dir = ''
+    map = self.read_dir(dir)
+    return map[name] if name in map else None
+
+  def read_dir(self, dir):
+    if dir in self._folder_cache:
+      return self._folder_cache[dir]
+    if not self._folder_cache:
+      self.parse_zip()
+    if dir in self._folder_cache:
+      return self._folder_cache[dir]
+    return {}
+
+  def parse_zip(self):
+    zip = zipfile.ZipFile(self._payload)
+    infos = 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:
+        dir, base = os.path.split(path)
+        # TODO: If directories are stored, base will be empty.
+
+        if not dir in self._folder_cache:
+          self._folder_cache[dir] = {}
+        dir_map = self._folder_cache[dir]
+        if not base 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)
+          else:
+            is_exec = False  # Seems we can't get this easily?
+            is_symlink = False
+            is_dir = True
+          dir_map[base] = FSObject(base, is_dir, is_exec, is_symlink)
+        is_zipinfo = False
+        path = dir
+
 class Checker:
   def __init__(self, provider):
     self._provider = provider
@@ -177,6 +244,14 @@
       return False
     return True
 
+  def check_no_library(self, file):
+    res1 = self.is_file('lib/%s' % (file))
+    res2 = self.is_file('lib64/%s' % (file))
+    if res1[0] or res2[0]:
+      self.fail('Library exists: %s', file)
+      return False
+    return True
+
   def check_java_library(self, file):
     return self.check_file('javalib/%s' % (file))
 
@@ -205,15 +280,17 @@
     self.check_library('libart-dexlayout.so')
     self.check_library('libart.so')
     self.check_library('libartbase.so')
+    self.check_library('libartpalette.so')
+    self.check_no_library('libartpalette-system.so')
     self.check_library('libdexfile.so')
+    self.check_library('libdexfile_external.so')
     self.check_library('libopenjdkjvm.so')
     self.check_library('libopenjdkjvmti.so')
     self.check_library('libprofile.so')
     # Check that the mounted image contains Android Core libraries.
-    self.check_library('libexpat.so')
+    # Note: host vs target libs are checked elsewhere.
     self.check_library('libjavacore.so')
     self.check_library('libopenjdk.so')
-    self.check_library('libz.so')
     self.check_library('libziparchive.so')
     # Check that the mounted image contains additional required libraries.
     self.check_library('libadbconnection.so')
@@ -238,6 +315,28 @@
     self.check_java_library('bouncycastle.jar')
     self.check_java_library('apache-xml.jar')
 
+class ReleaseTargetChecker(Checker):
+  def __init__(self, provider):
+    super().__init__(provider)
+  def __str__(self):
+    return 'Release (Target) Checker'
+
+  def run(self):
+    # Check that the mounted image contains Android Core libraries.
+    self.check_library('libexpat.so')
+    self.check_library('libz.so')
+
+class ReleaseHostChecker(Checker):
+  def __init__(self, provider):
+    super().__init__(provider)
+  def __str__(self):
+    return 'Release (Host) Checker'
+
+  def run(self):
+    # Check that the mounted image contains Android Core libraries.
+    self.check_library('libexpat-host.so')
+    self.check_library('libz-host.so')
+
 class DebugChecker(Checker):
   def __init__(self, provider):
     super().__init__(provider)
@@ -296,7 +395,7 @@
         print(new_path)
         if val.is_dir:
           print_list_impl(provider, new_path)
-    print_list_impl(provider, '.')
+    print_list_impl(provider, '')
 
 def print_tree(provider, title):
     def get_vertical(has_next_list):
@@ -326,36 +425,31 @@
           print_tree_impl(provider, os.path.join(path, val.name), has_next_list)
           has_next_list.pop()
     print('%s' % (title))
-    print_tree_impl(provider, '.', [])
+    print_tree_impl(provider, '', [])
 
 # Note: do not sys.exit early, for __del__ cleanup.
 def artApexTestMain(args):
-  if not args.host and not args.target and not args.debug and not args.tree and not args.list:
-    logging.error("None of --host, --target, --debug, --tree nor --list set")
+  if args.tree and args.debug:
+    logging.error("Both of --tree and --debug set")
     return 1
-  if args.tree and (args.host or args.debug):
-    logging.error("Both of --tree and --host|--debug set")
-    return 1
-  if args.list and (args.host or args.debug):
-    logging.error("Both of --list and --host|--debug set")
+  if args.list and args.debug:
+    logging.error("Both of --list and --debug set")
     return 1
   if args.list and args.tree:
     logging.error("Both of --list and --tree set")
     return 1
-  if args.host and (args.target or args.debug):
-    logging.error("Both of --host and --target|--debug set")
-    return 1
-  if args.debug and not args.target:
-    args.target = True
-  if args.target and not args.tmpdir:
+  if not args.tmpdir:
     logging.error("Need a tmpdir.")
     return 1
-  if args.target and not args.debugfs:
+  if not args.host and not args.debugfs:
     logging.error("Need debugfs.")
     return 1
 
   try:
-    apex_provider = TargetApexProvider(args.apex, args.tmpdir, args.debugfs)
+    if args.host:
+      apex_provider = HostApexProvider(args.apex, args.tmpdir)
+    else:
+      apex_provider = TargetApexProvider(args.apex, args.tmpdir, args.debugfs)
   except Exception as e:
     logging.error('Failed to create provider: %s', e)
     return 1
@@ -368,14 +462,15 @@
     return 0
 
   checkers = []
-  if args.host:
-    logging.error('host checking not yet supported')
-    return 1
 
   checkers.append(ReleaseChecker(apex_provider))
+  if args.host:
+    checkers.append(ReleaseHostChecker(apex_provider))
+  else:
+    checkers.append(ReleaseTargetChecker(apex_provider))
   if args.debug:
     checkers.append(DebugChecker(apex_provider))
-  if args.debug and args.target:
+  if args.debug and not args.host:
     checkers.append(DebugTargetChecker(apex_provider))
 
   failed = False
@@ -414,8 +509,8 @@
 
   # TODO: Add host support
   configs= [
-    {'name': 'com.android.runtime.release', 'target': True, 'debug': False, 'host': False},
-    {'name': 'com.android.runtime.debug', 'target': True, 'debug': True, 'host': False},
+    {'name': 'com.android.runtime.release', 'debug': False, 'host': False},
+    {'name': 'com.android.runtime.debug', 'debug': True, 'host': False},
   ]
 
   for config in configs:
@@ -426,7 +521,6 @@
       failed = True
       logging.error("Cannot find APEX %s. Please build it first.", args.apex)
       continue
-    args.target = config['target']
     args.debug = config['debug']
     args.host = config['host']
     exit_code = artApexTestMain(args)
@@ -442,7 +536,7 @@
   parser.add_argument('apex', help='apex file input')
 
   parser.add_argument('--host', help='Check as host apex', action='store_true')
-  parser.add_argument('--target', help='Check as target apex', action='store_true')
+
   parser.add_argument('--debug', help='Check as debug apex', action='store_true')
 
   parser.add_argument('--list', help='List all files', action='store_true')
diff --git a/build/apex/runtests.sh b/build/apex/runtests.sh
index da73857..95c1de9 100755
--- a/build/apex/runtests.sh
+++ b/build/apex/runtests.sh
@@ -77,15 +77,6 @@
   shift
 done
 
-if $print_image_tree_p; then
-  which tree >/dev/null || die "This script requires the 'tree' tool.
-On Debian-based systems, this can be installed with:
-
-   sudo apt-get install tree
-"
-fi
-
-
 # build_apex APEX_MODULE
 # ----------------------
 # Build APEX package APEX_MODULE.
@@ -96,24 +87,6 @@
   fi
 }
 
-# maybe_list_apex_contents MOUNT_POINT
-# ------------------------------------
-# If any listing/printing option was used, honor them and display the contents
-# of the APEX payload at MOUNT_POINT.
-function maybe_list_apex_contents {
-  local mount_point=$1
-
-  # List the contents of the mounted image using `find` (optional).
-  if $list_image_files_p; then
-    say "Listing image files" && find "$mount_point"
-  fi
-
-  # List the contents of the mounted image using `tree` (optional).
-  if $print_image_tree_p; then
-    say "Printing image tree" && ls -ld "$mount_point" && tree -aph --du "$mount_point"
-  fi
-}
-
 # maybe_list_apex_contents_apex APEX TMPDIR [other]
 function maybe_list_apex_contents_apex {
   local apex=$1
@@ -139,134 +112,6 @@
   exit_status=1
 }
 
-function check_file {
-  [[ -f "$mount_point/$1" ]] || fail_check "Cannot find file '$1' in mounted image"
-}
-
-function check_binary {
-  [[ -x "$mount_point/bin/$1" ]] || fail_check "Cannot find binary '$1' in mounted image"
-}
-
-function check_multilib_binary {
-  # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
-  # the precision of this test?
-  if ! [[ -L "$mount_point/bin/${1}" ]]; then
-    fail_check "Cannot find symlink for multilib binary '$1' in mounted image"
-  fi
-  [[ -x "$mount_point/bin/${1}32" ]] || [[ -x "$mount_point/bin/${1}64" ]] \
-    || fail_check "Cannot find binary '$1' in mounted image"
-}
-
-function check_binary_symlink {
-  [[ -h "$mount_point/bin/$1" ]] || fail_check "Cannot find symbolic link '$1' in mounted image"
-}
-
-function check_library {
-  # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
-  # the precision of this test?
-  [[ -f "$mount_point/lib/$1" ]] || [[ -f "$mount_point/lib64/$1" ]] \
-    || fail_check "Cannot find library '$1' in mounted image"
-}
-
-function check_no_library {
-  # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve
-  # the precision of this test?
-  [[ ! -f "$mount_point/lib/$1" && ! -f "$mount_point/lib64/$1" ]] \
-    || die "Found unwanted library '$1' in mounted image"
-}
-
-function check_java_library {
-  [[ -f "$mount_point/javalib/$1" ]] || fail_check "Cannot find java library '$1' in mounted image"
-}
-
-# !!! NOTE: Please also update art_apex_test.py !!!
-
-# Check contents of APEX payload located in `$mount_point`.
-function check_release_contents {
-  # Check that the mounted image contains an APEX manifest.
-  check_file apex_manifest.json
-
-  # Check that the mounted image contains ART base binaries.
-  check_multilib_binary dalvikvm
-  # TODO: Does not work yet (b/119942078).
-  : check_binary_symlink dalvikvm
-  check_binary dex2oat
-  check_binary dexoptanalyzer
-  check_binary profman
-
-  # oatdump is only in device apex's due to build rules
-  # TODO: Check for it when it is also built for host.
-  : check_binary oatdump
-
-  # Check that the mounted image contains Android Runtime libraries.
-  check_library libart-compiler.so
-  check_library libart-dexlayout.so
-  check_library libart.so
-  check_library libartbase.so
-  check_library libartpalette.so
-  check_no_library libartpalette-system.so
-  check_library libdexfile.so
-  check_library libdexfile_external.so
-  check_library libopenjdkjvm.so
-  check_library libopenjdkjvmti.so
-  check_library libprofile.so
-  # Check that the mounted image contains Android Core libraries.
-  check_library "libexpat${host_suffix}.so"
-  check_library libjavacore.so
-  check_library libopenjdk.so
-  check_library "libz${host_suffix}.so"
-  check_library libziparchive.so
-  # Check that the mounted image contains additional required libraries.
-  check_library libadbconnection.so
-
-  # TODO: Should we check for other libraries, such as:
-  #
-  #   libbacktrace.so
-  #   libbase.so
-  #   liblog.so
-  #   libsigchain.so
-  #   libtombstoned_client.so
-  #   libunwindstack.so
-  #   libvixl.so
-  #   libvixld.so
-  #   ...
-  #
-  # ?
-
-  check_java_library core-oj.jar
-  check_java_library core-libart.jar
-  check_java_library okhttp.jar
-  check_java_library bouncycastle.jar
-  check_java_library apache-xml.jar
-}
-
-# Check debug contents of APEX payload located in `$mount_point`.
-function check_debug_contents {
-  # Check that the mounted image contains ART tools binaries.
-  check_binary dexdiag
-  check_binary dexdump
-  check_binary dexlist
-
-  # Check that the mounted image contains ART debug binaries.
-  check_binary dex2oatd
-  check_binary dexoptanalyzerd
-  check_binary profmand
-
-  # Check that the mounted image contains Android Runtime debug libraries.
-  check_library libartbased.so
-  check_library libartd-compiler.so
-  check_library libartd-dexlayout.so
-  check_library libartd.so
-  check_library libdexfiled.so
-  check_library libopenjdkjvmd.so
-  check_library libopenjdkjvmtid.so
-  check_library libprofiled.so
-  # Check that the mounted image contains Android Core debug libraries.
-  check_library libopenjdkd.so
-  # Check that the mounted image contains additional required debug libraries.
-  check_library libadbconnectiond.so
-}
-
 # Testing target (device) APEX packages.
 # ======================================
 
@@ -299,14 +144,13 @@
 apex_path="$ANDROID_PRODUCT_OUT/system/apex/${apex_module}.apex"
 
 # List the contents of the APEX image (optional).
-maybe_list_apex_contents_apex $apex_path $work_dir --target --debugfs $ANDROID_HOST_OUT/bin/debugfs
+maybe_list_apex_contents_apex $apex_path $work_dir --debugfs $ANDROID_HOST_OUT/bin/debugfs
 
 # Run tests on APEX package.
 say "Checking APEX package $apex_module"
 $SCRIPT_DIR/art_apex_test.py \
   --tmpdir $work_dir \
   --debugfs $ANDROID_HOST_OUT/bin/debugfs \
-  --target \
   $apex_path \
     || fail_check "Release checks failed"
 
@@ -334,14 +178,13 @@
 apex_path="$ANDROID_PRODUCT_OUT/system/apex/${apex_module}.apex"
 
 # List the contents of the APEX image (optional).
-maybe_list_apex_contents_apex $apex_path $work_dir --target --debugfs $ANDROID_HOST_OUT/bin/debugfs
+maybe_list_apex_contents_apex $apex_path $work_dir --debugfs $ANDROID_HOST_OUT/bin/debugfs
 
 # Run tests on APEX package.
 say "Checking APEX package $apex_module"
 $SCRIPT_DIR/art_apex_test.py \
   --tmpdir $work_dir \
   --debugfs $ANDROID_HOST_OUT/bin/debugfs \
-  --target \
   --debug \
   $apex_path \
     || fail_check "Debug checks failed"
@@ -369,51 +212,30 @@
   cleanup_host
 }
 
-# setup_host_apex APEX_MODULE MOUNT_POINT
-# ---------------------------------------
-# Extract Zip file from host APEX_MODULE and extract it in MOUNT_POINT.
-function setup_host_apex {
-  local apex_module=$1
-  local mount_point=$2
-  local system_apexdir="$ANDROID_HOST_OUT/apex"
-  local apex_package="$system_apexdir/$apex_module.zipapex"
-
-  say "Extracting payload"
-
-  # Extract the payload from the Android Runtime APEX.
-  local image_filename="apex_payload.zip"
-  unzip -q "$apex_package" "$image_filename" -d "$work_dir"
-  mkdir "$mount_point"
-  local image_file="$work_dir/$image_filename"
-
-  # Unzipping the payload
-  unzip -q "$image_file" -d "$mount_point"
-}
-
 apex_module="com.android.runtime.host"
 test_status=0
 
 say "Processing APEX package $apex_module"
 
 work_dir=$(mktemp -d)
-mount_point="$work_dir/zip"
-host_suffix="-host"
 
 trap finish_host EXIT
 
 # Build the APEX package (optional).
 build_apex "$apex_module"
-
-# Set up APEX package.
-setup_host_apex "$apex_module" "$mount_point"
+apex_path="$ANDROID_HOST_OUT/apex/${apex_module}.zipapex"
 
 # List the contents of the APEX image (optional).
-maybe_list_apex_contents "$mount_point"
+maybe_list_apex_contents_apex $apex_path $work_dir --host
 
 # Run tests on APEX package.
 say "Checking APEX package $apex_module"
-check_release_contents "$apex_module"
-check_debug_contents
+$SCRIPT_DIR/art_apex_test.py \
+  --tmpdir $work_dir \
+  --host \
+  --debug \
+  $apex_path \
+    || fail_check "Debug checks failed"
 
 # Clean up.
 trap - EXIT