Only extract zipapex once for linux-bionic-zipapex tests

We were unzipping the zipapex for every test when running the
art-linux-bionic-x64-zipapex tests. This could take a long time. This
makes us only have to unzip it a single time.

Test: ./test/testrunner/run_build_test_target.py art-linux-bionic-x64-zipapex
Change-Id: Id7cf1b6445526a950c45b74fc98208e234d3069f
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index e9380d3..e993fda 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -39,6 +39,8 @@
 CREATE_ANDROID_ROOT="n"
 USE_ZIPAPEX="n"
 ZIPAPEX_LOC=""
+USE_EXTRACTED_ZIPAPEX="n"
+EXTRACTED_ZIPAPEX_LOC=""
 INTERPRETER="n"
 JIT="n"
 INVOKE_WITH=""
@@ -229,6 +231,11 @@
         # host ones which are in a different location.
         CREATE_ANDROID_ROOT="y"
         shift
+    elif [ "x$1" = "x--runtime-extracted-zipapex" ]; then
+        shift
+        USE_EXTRACTED_ZIPAPEX="y"
+        EXTRACTED_ZIPAPEX_LOC="$1"
+        shift
     elif [ "x$1" = "x--runtime-zipapex" ]; then
         shift
         USE_ZIPAPEX="y"
@@ -804,6 +811,15 @@
   setupapex_cmdline="unzip -o -u ${zip_options} ${ZIPAPEX_LOC} apex_payload.zip -d ${DEX_LOCATION}"
   installapex_cmdline="unzip -o -u ${zip_options} ${DEX_LOCATION}/apex_payload.zip -d ${DEX_LOCATION}/zipapex"
   BIN_DIR=$DEX_LOCATION/zipapex/bin
+elif [ "$USE_EXTRACTED_ZIPAPEX" = "y" ]; then
+  # Just symlink the zipapex binaries
+  BIN_DIR=$DEX_LOCATION/zipapex/bin
+  # Force since some tests manually run this file twice.
+  ln_options=""
+  if [ "$DEV_MODE" = "y" ]; then
+    ln_options="--verbose"
+  fi
+  installapex_cmdline="ln -s -f ${ln_options} ${EXTRACTED_ZIPAPEX_LOC} ${DEX_LOCATION}/zipapex"
 fi
 
 # PROFILE takes precedence over RANDOM_PROFILE, since PROFILE tests require a
@@ -1047,7 +1063,7 @@
     export ANDROID_ROOT="${ANDROID_ROOT}"
     export ANDROID_RUNTIME_ROOT="${ANDROID_RUNTIME_ROOT}"
     export ANDROID_TZDATA_ROOT="${ANDROID_TZDATA_ROOT}"
-    if [ "$USE_ZIPAPEX" = "y" ]; then
+    if [ "$USE_ZIPAPEX" = "y" ] || [ "$USE_EXRACTED_ZIPAPEX" = "y" ]; then
       # Put the zipapex files in front of the ld-library-path
       export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
       export DYLD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
diff --git a/test/run-test b/test/run-test
index 5f78d17..a2d180e 100755
--- a/test/run-test
+++ b/test/run-test
@@ -425,6 +425,15 @@
         DEX_LOCATION=$tmp_dir
         host_lib_root=$OUT_DIR/soong/host/linux_bionic-x86
         shift
+    elif [ "x$1" = "x--runtime-extracted-zipapex" ]; then
+        shift
+        # TODO Should we allow the java.library.path to search the zipapex too?
+        # Not needed at the moment and adding it will be complicated so for now
+        # we'll ignore this.
+        run_args="${run_args} --host --runtime-extracted-zipapex $1"
+        target_mode="no"
+        DEX_LOCATION=$tmp_dir
+        shift
     elif [ "x$1" = "x--runtime-zipapex" ]; then
         shift
         # TODO Should we allow the java.library.path to search the zipapex too?
@@ -771,6 +780,9 @@
         echo "    --bionic              Use the (host, 64-bit only) linux_bionic libc runtime"
         echo "    --runtime-zipapex [file]"
         echo "                          Use the given zipapex file to provide runtime binaries"
+        echo "    --runtime-extracted-zipapex [dir]"
+        echo "                          Use the given extracted zipapex directory to provide"
+        echo "                          runtime binaries"
         echo "    --trace               Run with method tracing"
         echo "    --strace              Run with syscall tracing from strace."
         echo "    --stream              Run method tracing in streaming mode (requires --trace)"
diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py
index bc22360..6e299bd 100644
--- a/test/testrunner/target_config.py
+++ b/test/testrunner/target_config.py
@@ -339,7 +339,7 @@
     'art-linux-bionic-x64-zipapex': {
         'build': '{ANDROID_BUILD_TOP}/art/tools/build_linux_bionic_tests.sh {MAKE_OPTIONS} com.android.runtime.host',
         'run-test': ['--run-test-option=--bionic',
-                     "--run-test-option='--runtime-zipapex {SOONG_OUT_DIR}/host/linux_bionic-x86/apex/com.android.runtime.host.zipapex'",
+                     "--runtime-zipapex={SOONG_OUT_DIR}/host/linux_bionic-x86/apex/com.android.runtime.host.zipapex",
                      '--host',
                      '--64',
                      '--no-build-dependencies'],
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 54701af..c36b701 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -46,6 +46,7 @@
 """
 import argparse
 import collections
+import contextlib
 import fnmatch
 import itertools
 import json
@@ -118,6 +119,7 @@
 gdb_arg = ''
 runtime_option = ''
 with_agent = []
+zipapex_loc = None
 run_test_option = []
 stop_testrunner = False
 dex2oat_jobs = -1   # -1 corresponds to default threads for dex2oat
@@ -360,7 +362,7 @@
       'debuggable': [''], 'jvmti': [''],
       'cdex_level': ['']})
 
-  def start_combination(config_tuple, address_size):
+  def start_combination(config_tuple, global_options, address_size):
       test, target, run, prebuild, compiler, relocate, trace, gc, \
       jni, image, debuggable, jvmti, cdex_level = config_tuple
 
@@ -393,7 +395,7 @@
       variant_set = {target, run, prebuild, compiler, relocate, trace, gc, jni,
                      image, debuggable, jvmti, cdex_level, address_size}
 
-      options_test = options_all
+      options_test = global_options
 
       if target == 'host':
         options_test += ' --host'
@@ -502,17 +504,36 @@
       worker.daemon = True
       worker.start()
 
-  for config_tuple in config:
-    target = config_tuple[1]
-    for address_size in _user_input_variants['address_sizes_target'][target]:
-      start_combination(config_tuple, address_size)
+  #  Use a context-manager to handle cleaning up the extracted zipapex if needed.
+  with handle_zipapex(zipapex_loc) as zipapex_opt:
+    options_all += zipapex_opt
+    for config_tuple in config:
+      target = config_tuple[1]
+      for address_size in _user_input_variants['address_sizes_target'][target]:
+        start_combination(config_tuple, options_all, address_size)
 
-  for config_tuple in uncombinated_config:
-      start_combination(config_tuple, "")  # no address size
+    for config_tuple in uncombinated_config:
+        start_combination(config_tuple, options_all, "")  # no address size
 
-  while threading.active_count() > 2:
-    time.sleep(0.1)
+    while threading.active_count() > 2:
+      time.sleep(0.1)
 
+@contextlib.contextmanager
+def handle_zipapex(ziploc):
+  """Extracts the zipapex (if present) and handles cleanup.
+
+  If we are running out of a zipapex we want to unzip it once and have all the tests use the same
+  extracted contents. This extracts the files and handles cleanup if needed. It returns the
+  required extra arguments to pass to the run-test.
+  """
+  if ziploc is not None:
+    with tempfile.TemporaryDirectory() as tmpdir:
+      subprocess.run(["unzip", "-qq", ziploc, "apex_payload.zip", "-d", tmpdir]).check_returncode()
+      subprocess.run(
+        ["unzip", "-qq", os.path.join(tmpdir, "apex_payload.zip"), "-d", tmpdir]).check_returncode()
+      yield " --runtime-extracted-zipapex " + tmpdir
+  else:
+    yield ""
 
 def run_test(command, test, test_variant, test_name):
   """Runs the test.
@@ -912,6 +933,7 @@
   global dex2oat_jobs
   global run_all_configs
   global with_agent
+  global zipapex_loc
 
   parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.")
   parser.add_argument('-t', '--test', action='append', dest='tests', help='name(s) of the test(s)')
@@ -952,6 +974,8 @@
                             example '--runtime-option=-Xjitthreshold:0'.""")
   global_group.add_argument('--dex2oat-jobs', type=int, dest='dex2oat_jobs',
                             help='Number of dex2oat jobs')
+  global_group.add_argument('--runtime-zipapex', dest='runtime_zipapex', default=None,
+                            help='Location for runtime zipapex.')
   global_group.add_argument('-a', '--all', action='store_true', dest='run_all',
                             help="Run all the possible configurations for the input test set")
   for variant_type, variant_set in VARIANT_TYPE_DICT.items():
@@ -996,6 +1020,7 @@
   runtime_option = options['runtime_option'];
   with_agent = options['with_agent'];
   run_test_option = sum(map(shlex.split, options['run_test_option']), [])
+  zipapex_loc = options['runtime_zipapex']
 
   timeout = options['timeout']
   if options['dex2oat_jobs']: