Add --run-tests option to run_on_app_dump.py

Bug: 152155164
Change-Id: Ib3fbbe1bd3a79887307b319b5f62289bf9af2578
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 0a4ac92..b2404c2 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -40,6 +40,7 @@
       'name': None,
       'dump_app': None,
       'apk_app': None,
+      'apk_test': None,
       'skip': False,
       'url': None,  # url is not used but nice to have for updating apps
       'revision': None,
@@ -57,6 +58,9 @@
     'name': 'applymapping',
     'dump_app': 'dump_app.zip',
     'apk_app': 'app-release.apk',
+    'id_test': 'com.example.applymapping.test',
+    'dump_test': 'dump_test.zip',
+    'apk_test': 'app-release-androidTest.apk',
     'url': 'https://github.com/mkj-gram/applymapping',
     'revision': 'e3ae14b8c16fa4718e5dea8f7ad00937701b3c48',
     'folder': 'applymapping',
@@ -97,6 +101,10 @@
   return os.path.join(app_dir, app.dump_app)
 
 
+def dump_test_for_app(app_dir, app):
+  return os.path.join(app_dir, app.dump_test)
+
+
 def get_results_for_app(app, options, temp_dir):
   app_folder = app.folder if app.folder else app.name + "_" + app.revision
   app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
@@ -156,11 +164,11 @@
   for compilation_step in range(0, compilation_steps):
     if status != 'success':
       break
-    print('Compiling {} of {}'.format(compilation_step, compilation_steps))
+    print('Compiling {} of {}'.format(compilation_step + 1, compilation_steps))
     result = {}
     try:
       start = time.time()
-      (app_jar, new_recomp_jar) = \
+      (app_jar, mapping, new_recomp_jar) = \
         build_app_with_shrinker(
           app, options, temp_dir, app_dir, shrinker, compilation_step,
           compilation_steps, recomp_jar)
@@ -169,6 +177,7 @@
       result['build_status'] = 'success'
       result['recompilation_status'] = 'success'
       result['output_jar'] = app_jar
+      result['output_mapping'] = mapping
       result['dex_size'] = dex_size
       result['duration'] = int((end - start) * 1000)  # Wall time
       if (new_recomp_jar is None
@@ -184,22 +193,48 @@
       result['build_status'] = 'failed'
       status = 'failed'
 
+    original_app_apk = os.path.join(app_dir, app.apk_app)
+    app_apk_destination = os.path.join(
+      temp_dir,"{}_{}.apk".format(app.id, compilation_step))
+
     if result.get('build_status') == 'success' and options.monkey:
       # Make a copy of the given APK, move the newly generated dex files into the
       # copied APK, and then sign the APK.
-      original_app_apk = os.path.join(app_dir, app.apk_app)
-      app_apk = os.path.join(temp_dir,
-                             "{}_{}.apk".format(app.id, compilation_step))
       apk_masseur.masseur(
         original_app_apk, dex=app_jar, resources='META-INF/services/*',
-        out=app_apk,
+        out=app_apk_destination,
         quiet=options.quiet, logging=is_logging_enabled_for(app, options),
         keystore=options.keystore)
 
       result['monkey_status'] = 'success' if adb.run_monkey(
-        app.id, options.emulator_id, app_apk, options.monkey_events,
+        app.id, options.emulator_id, app_apk_destination, options.monkey_events,
         options.quiet, is_logging_enabled_for(app, options)) else 'failed'
 
+    if result.get('build_status') == 'success' and options.run_tests:
+      if not os.path.isfile(app_apk_destination):
+        apk_masseur.masseur(
+          original_app_apk, dex=app_jar, resources='META-INF/services/*',
+          out=app_apk_destination,
+          quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+          keystore=options.keystore)
+
+      # Compile the tests with the mapping file.
+      test_jar = build_test_with_shrinker(
+        app, options, temp_dir, app_dir,shrinker, compilation_step,
+        result['output_mapping'])
+      original_test_apk = os.path.join(app_dir, app.apk_test)
+      test_apk_destination = os.path.join(
+        temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
+      apk_masseur.masseur(
+        original_test_apk, dex=test_jar, resources='META-INF/services/*',
+        out=test_apk_destination,
+        quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+        keystore=options.keystore)
+      result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
+        app.id, app.id_test, options.emulator_id, app_apk_destination,
+        test_apk_destination, options.quiet,
+        is_logging_enabled_for(app, options)) else 'failed'
+
     results.append(result)
     if result.get('recompilation_status') != 'success':
       break
@@ -222,15 +257,21 @@
     'nolib': not is_minified_r8(shrinker)
   })
 
-  out_jar = os.path.join(temp_dir, "out.jar")
   compile_result = compiledump.run1(temp_dir, args, [])
+
+  out_jar = os.path.join(temp_dir, "out.jar")
+  out_mapping = os.path.join(temp_dir, "out.jar.map")
   app_jar = os.path.join(
     temp_dir, '{}_{}_{}_dex_out.jar'.format(
       app.name, shrinker, compilation_step_index))
+  app_mapping = os.path.join(
+    temp_dir, '{}_{}_{}_dex_out.jar.map'.format(
+      app.name, shrinker, compilation_step_index))
 
   if compile_result != 0 or not os.path.isfile(out_jar):
     assert False, "Compilation of app_jar failed"
   shutil.move(out_jar, app_jar)
+  shutil.move(out_mapping, app_mapping)
 
   recomp_jar = None
   if compilation_step_index < compilation_steps - 1:
@@ -243,7 +284,48 @@
           app.name, shrinker, compilation_step_index))
       shutil.move(out_jar, recomp_jar)
 
-  return (app_jar, recomp_jar)
+  return (app_jar, app_mapping, recomp_jar)
+
+
+def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
+                             compilation_step_index, mapping):
+  r8jar = os.path.join(
+    temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
+
+  def rewrite_file(file):
+    with open(file) as f:
+      lines = f.readlines()
+    with open(file, 'w') as f:
+      for line in lines:
+        if '-applymapping' not in line:
+          f.write(line + '\n')
+      f.write("-applymapping " + mapping + '\n')
+
+  args = AttrDict({
+    'dump': dump_test_for_app(app_dir, app),
+    'r8_jar': r8jar,
+    'ea': False if options.disable_assertions else True,
+    'version': 'master',
+    'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
+    'debug_agent': options.debug_agent,
+    'nolib': not is_minified_r8(shrinker),
+    # The config file will have an -applymapping reference to an old map.
+    # Update it to point to mapping file build in the compilation of the app.
+    'config_file_consumer': rewrite_file
+  })
+
+  compile_result = compiledump.run1(temp_dir, args, [])
+
+  out_jar = os.path.join(temp_dir, "out.jar")
+  test_jar = os.path.join(
+    temp_dir, '{}_{}_{}_test_out.jar'.format(
+      app.name, shrinker, compilation_step_index))
+
+  if compile_result != 0 or not os.path.isfile(out_jar):
+    assert False, "Compilation of test_jar failed"
+  shutil.move(out_jar, test_jar)
+
+  return test_jar
 
 
 def log_results_for_apps(result_per_shrinker_per_app, options):
@@ -323,6 +405,13 @@
         else:
           success('    monkey: {}'.format(monkey_status))
 
+      if options.run_tests and 'instrumentation_test_status' in result:
+        test_status = result.get('instrumentation_test_status')
+        if test_status != 'success':
+          warn('    instrumentation_tests: {}'.format(test_status))
+        else:
+          success('    instrumentation_tests: {}'.format(test_status))
+
       recompilation_status = result.get('recompilation_status', '')
       if recompilation_status == 'failed':
         app_error = True