Exclude internal dependencies ASAN might add.

This required extending the superfluous files check to handle glob patterns.

This also corrects a bug in check_no_superfluous_files that caused it to not
check directories correctly for the host apex (due to trailing '/' in the
call to HostApexProvider.read_dir), so a few missing entries are added
there.

Bug: 124293228
Test: art/build/apex/runtests.sh (with and without SANITIZE_TARGET/SANITIZE_HOST).
Change-Id: I83030b5b696fd80f42dd8722737b02fdf3a4089d
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 384eef5..07006cb 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -16,8 +16,10 @@
 #
 
 import argparse
+import fnmatch
 import logging
 import os
+import os.path
 import subprocess
 import sys
 import zipfile
@@ -184,7 +186,7 @@
   def __init__(self, provider):
     self._provider = provider
     self._errors = 0
-    self._expected_file_paths = set()
+    self._expected_file_globs = set()
 
   def fail(self, msg, *fail_args):
     self._errors += 1
@@ -208,7 +210,7 @@
     ok, msg = self.is_file(path)
     if not ok:
       self.fail(msg, path)
-    self._expected_file_paths.add(path)
+    self._expected_file_globs.add(path)
     return ok
 
   def check_executable(self, filename):
@@ -229,37 +231,43 @@
       return
     if not fs_object.is_symlink:
       self.fail('%s is not a symlink', path)
-    self._expected_file_paths.add(path)
+    self._expected_file_globs.add(path)
 
   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_paths.add(lib_path)
+      self._expected_file_globs.add(lib_path)
     lib64_is_file, _ = self.is_file(lib64_path)
     if lib64_is_file:
-      self._expected_file_paths.add(lib64_path)
+      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):
-    self._expected_file_paths.add(path)
+  def ignore_path(self, path_glob):
+    self._expected_file_globs.add(path_glob)
 
   def check_no_superfluous_files(self, dir_path):
-    dir_path += '/'
-    expected_filenames = set(['.', '..'])
-    for path in self._expected_file_paths:
-      if path.startswith(dir_path):
-        subpath = path[len(dir_path):]
-        subpath_first_segment, _, _ = subpath.partition('/')
-        expected_filenames.add(subpath_first_segment)
+    paths = []
     for name in sorted(self._provider.read_dir(dir_path).keys()):
-      if name not in expected_filenames:
-        self.fail('Unexpected file \'%s%s\'', dir_path, name)
+      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.
 
@@ -279,7 +287,7 @@
     """Check lib/basename.so, and/or lib64/basename.so."""
     raise NotImplementedError
 
-  def check_optional_native_library(self, basename):
+  def check_optional_native_library(self, basename_glob):
     """Allow lib/basename.so and/or lib64/basename.so to exist."""
     raise NotImplementedError
 
@@ -305,8 +313,8 @@
     # the precision of this test?
     self.check_file('lib/%s.so' % basename)
 
-  def check_optional_native_library(self, basename):
-    self.ignore_path('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)
@@ -329,8 +337,8 @@
     # the precision of this test?
     self.check_file('lib64/%s.so' % basename)
 
-  def check_optional_native_library(self, basename):
-    self.ignore_path('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)
@@ -356,9 +364,9 @@
     self.check_file('lib/%s.so' % basename)
     self.check_file('lib64/%s.so' % basename)
 
-  def check_optional_native_library(self, basename):
-    self.ignore_path('lib/%s.so' % basename)
-    self.ignore_path('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)
@@ -435,11 +443,13 @@
     self._checker.check_native_library('libziparchive')
     self._checker.check_optional_native_library('libvixl')  # Only on ARM/ARM64
 
-    # TODO(b/124293228): Figure out why we get these.
+    # 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*')
+
+    # TODO(b/124293228): Figure out why we get this.
     self._checker.check_native_library('libcutils')
-    # The following appears with ASAN (SANITIZE_TARGET=address).
-    self._checker.check_optional_native_library('libclang_rt.asan-i686-android')
-    self._checker.check_optional_native_library('libclang_rt.hwasan-aarch64-android')
 
 
 class ReleaseTargetChecker:
@@ -509,6 +519,10 @@
     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')