Merge "Make running test possible with CMD.exe"
diff --git a/.gitignore b/.gitignore
index a599785..bc26830 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,38 +22,24 @@
 third_party/android_jar/lib.tar.gz
 third_party/android_jar/lib-v[0-9][0-9]
 third_party/android_jar/lib-v[0-9][0-9].tar.gz
-third_party/gmscore/v4
-third_party/gmscore/v4.tar.gz
-third_party/gmscore/v5
-third_party/gmscore/v5.tar.gz
-third_party/gmscore/v6
-third_party/gmscore/v6.tar.gz
-third_party/gmscore/v7
-third_party/gmscore/v7.tar.gz
-third_party/gmscore/v8
-third_party/gmscore/v8.tar.gz
-third_party/gmscore/gmscore_v9
-third_party/gmscore/gmscore_v9.tar.gz
-third_party/gmscore/gmscore_v10
-third_party/gmscore/gmscore_v10.tar.gz
-third_party/gmscore/latest
-third_party/gmscore/latest.tar.gz
+third_party/gmail/*
+!third_party/gmail/*.sha1
+third_party/gmscore/*
+!third_party/gmscore/*.sha1
 third_party/gradle/gradle.tar.gz
 third_party/gradle/gradle
 third_party/jasmin.tar.gz
 third_party/jasmin
 third_party/jdwp-tests.tar.gz
 third_party/jdwp-tests
+third_party/photos/*
+!third_party/photos/*.sha1
 third_party/proguard/proguard5.2.1.tar.gz
 third_party/proguard/proguard5.2.1
 third_party/proguardsettings.tar.gz
 third_party/proguardsettings/
-third_party/youtube/youtube.android_12.10
-third_party/youtube/youtube.android_12.10.tar.gz
-third_party/youtube/youtube.android_12.17/
-third_party/youtube/youtube.android_12.17.tar.gz
-third_party/youtube/youtube.android_12.22/
-third_party/youtube/youtube.android_12.22.tar.gz
+third_party/youtube/*
+!third_party/youtube/*sha1
 third_party/jctf
 third_party/jctf.tar.gz
 third_party/android_cts_baseline
diff --git a/build.gradle b/build.gradle
index 6feb7a8..0a512f2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -151,6 +151,7 @@
 
 def x20Dependencies = [
     "third_party": [
+        "gmail/gmail_android_170604.16.tar.gz",
         "gmscore/v4.tar.gz",
         "gmscore/v5.tar.gz",
         "gmscore/v6.tar.gz",
@@ -160,9 +161,9 @@
         "gmscore/gmscore_v10.tar.gz",
         "gmscore/latest.tar.gz",
         "photos/2017-06-06.tar.gz",
-        "youtube/youtube.android_11.47.tar.gz",
         "youtube/youtube.android_12.10.tar.gz",
         "youtube/youtube.android_12.17.tar.gz",
+        "youtube/youtube.android_12.22.tar.gz",
         "proguardsettings.tar.gz",
     ],
 ]
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 0a0c976..5a8ff11 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -162,7 +163,7 @@
       app = optimize(app, appInfo, options);
 
       // If a method filter is present don't produce output since the application is likely partial.
-      if (!options.methodsFilter.isEmpty()) {
+      if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
         options.methodsFilter.forEach((m) -> System.out.println("  - " + m));
         return null;
@@ -178,13 +179,17 @@
       options.printWarnings();
       return output;
     } catch (ExecutionException e) {
-      throw new RuntimeException(e.getMessage(), e.getCause());
+      if (e.getCause() instanceof CompilationError) {
+        throw (CompilationError) e.getCause();
+      } else {
+        throw new RuntimeException(e.getMessage(), e.getCause());
+      }
     }
   }
 
   private static DexApplication optimize(
       DexApplication application, AppInfo appInfo, InternalOptions options)
-      throws IOException, ExecutionException {
+      throws IOException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
     IRConverter converter = new IRConverter(application, appInfo, options, printer);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index caf7f46..2116981 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -173,7 +173,7 @@
   }
 
   static CompilationResult runForTesting(AndroidApp app, InternalOptions options)
-      throws ProguardRuleParserException, ExecutionException, IOException {
+      throws ProguardRuleParserException, IOException {
     ExecutorService executor = ThreadUtils.getExecutorService(options);
     try {
       return runForTesting(app, options, executor);
@@ -186,12 +186,12 @@
       AndroidApp app,
       InternalOptions options,
       ExecutorService executor)
-      throws ProguardRuleParserException, ExecutionException, IOException {
+      throws ProguardRuleParserException, IOException {
     return new R8(options).run(app, executor);
   }
 
   private CompilationResult run(AndroidApp inputApp, ExecutorService executorService)
-      throws IOException, ExecutionException, ProguardRuleParserException {
+      throws IOException, ProguardRuleParserException {
     if (options.quiet) {
       System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
     }
@@ -324,7 +324,7 @@
       timing.end();
 
       // If a method filter is present don't produce output since the application is likely partial.
-      if (!options.methodsFilter.isEmpty()) {
+      if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
         options.methodsFilter.forEach((m) -> System.out.println("  - " + m));
         return null;
@@ -363,6 +363,12 @@
       }
       options.printWarnings();
       return new CompilationResult(androidApp, application, appInfo);
+    } catch (ExecutionException e) {
+      if (e.getCause() instanceof CompilationError) {
+        throw (CompilationError) e.getCause();
+      } else {
+        throw new RuntimeException(e.getMessage(), e.getCause());
+      }
     } finally {
       // Dump timings.
       if (options.printTimes) {
@@ -381,7 +387,7 @@
    * @return the compilation result.
    */
   public static AndroidApp run(R8Command command)
-      throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
+      throws IOException, CompilationException, ProguardRuleParserException {
     AndroidApp outputApp =
         runForTesting(command.getInputApp(), command.getInternalOptions()).androidApp;
     if (command.getOutputPath() != null) {
@@ -401,7 +407,7 @@
    * @return the compilation result.
    */
   public static AndroidApp run(R8Command command, ExecutorService executor)
-      throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
+      throws IOException, CompilationException, ProguardRuleParserException {
     AndroidApp outputApp =
         runForTesting(command.getInputApp(), command.getInternalOptions(), executor).androidApp;
     if (command.getOutputPath() != null) {
@@ -411,7 +417,7 @@
   }
 
   private static void run(String[] args)
-      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+      throws IOException, ProguardRuleParserException, CompilationException {
     R8Command.Builder builder = R8Command.parse(args);
     if (builder.getOutputPath() == null) {
       builder.setOutputPath(Paths.get("."));
@@ -442,7 +448,7 @@
     } catch (ProguardRuleParserException e) {
       System.err.println("Failed parsing proguard keep rules: " + e.getMessage());
       System.exit(1);
-    } catch (RuntimeException | ExecutionException e) {
+    } catch (RuntimeException e) {
       System.err.println("Compilation failed with an internal error.");
       Throwable cause = e.getCause() == null ? e : e.getCause();
       cause.printStackTrace();
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 208d0f8..e7bf9db 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -70,13 +70,6 @@
       readClassSources(builder, closer);
       initializeLazyClassCollection(builder);
       ThreadUtils.awaitFutures(futures);
-    } catch (ExecutionException e) {
-      // If the reading failed with a valid compilation error, rethrow the unwrapped exception.
-      Throwable cause = e.getCause();
-      if (cause != null && cause instanceof CompilationError) {
-        throw (CompilationError) cause;
-      }
-      throw e;
     } finally {
       timing.end();
     }
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 07fce3a..db8adab 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -575,6 +575,7 @@
       this.originalNames = originalNames;
     }
 
+    @Override
     public List<DexProgramClass> call() {
       String currentPrefix = null;
       int currentFileId = -1;
@@ -598,9 +599,9 @@
           inserted.add(clazz);
         }
         if (file.isFull()) {
-          throw new RuntimeException(
+          throw new CompilationError(
               "Cannot fit package " + currentPrefix
-                  + " in requested dex file, consider to remove mapping.");
+                  + " in requested dex file, consider removing mapping.");
         }
       }
       file.commitTransaction();
@@ -695,6 +696,7 @@
       return originalNames != null ? originalNames.get(clazz) : clazz.toString();
     }
 
+    @Override
     public Map<String, Integer> call() throws IOException {
       Iterator<VirtualFile> allFilesCyclic = Iterators.cycle(files.values());
       Iterator<VirtualFile> activeFiles = Iterators.limit(allFilesCyclic, files.size());
@@ -797,7 +799,7 @@
     }
 
     private void addNonPackageClasses(Iterator<VirtualFile> activeFiles,
-        List<DexProgramClass> nonPackageClasses) throws IOException {
+        List<DexProgramClass> nonPackageClasses) {
       VirtualFile current;
       current = activeFiles.next();
       for (DexProgramClass clazz : nonPackageClasses) {
@@ -820,7 +822,7 @@
       }
     }
 
-    private VirtualFile getVirtualFile(Iterator<VirtualFile> activeFiles) throws IOException {
+    private VirtualFile getVirtualFile(Iterator<VirtualFile> activeFiles) {
       VirtualFile current = null;
       while (activeFiles.hasNext() && (current = activeFiles.next()).isFilledEnough(options)) {}
       if (current == null || current.isFilledEnough(options)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 6ae0b16..af586db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -212,15 +212,38 @@
     ps.append(builder.toString());
   }
 
-  /** Write smali source for the application code on the provided PrintStream. */
+  private void writeClassFooter(DexClass clazz, PrintStream ps) {
+    StringBuilder builder = new StringBuilder();
+    builder.append("# End of class ");
+    builder.append(clazz.type.toSmaliString());
+    builder.append("\n");
+    ps.append(builder.toString());
+  }
+
+  /**
+   * Write smali source for the application code on the provided PrintStream.
+   */
   public void smali(InternalOptions options, PrintStream ps) {
     List<DexProgramClass> classes = (List<DexProgramClass>) classes();
     classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
+    boolean firstClass = true;
     for (DexClass clazz : classes) {
       boolean classHeaderWritten = false;
+      if (!options.hasMethodsFilter()) {
+        if (!firstClass) {
+          ps.append("\n");
+          firstClass = false;
+        }
+        writeClassHeader(clazz, ps);
+        classHeaderWritten = true;
+      }
       for (DexEncodedMethod method : clazz.virtualMethods()) {
         if (options.methodMatchesFilter(method)) {
           if (!classHeaderWritten) {
+            if (!firstClass) {
+              ps.append("\n");
+              firstClass = false;
+            }
             writeClassHeader(clazz, ps);
             classHeaderWritten = true;
           }
@@ -231,6 +254,10 @@
       for (DexEncodedMethod method : clazz.directMethods()) {
         if (options.methodMatchesFilter(method)) {
           if (!classHeaderWritten) {
+            if (!firstClass) {
+              ps.append("\n");
+              firstClass = false;
+            }
             writeClassHeader(clazz, ps);
             classHeaderWritten = true;
           }
@@ -238,6 +265,10 @@
           ps.append(method.toSmaliString(getProguardMap()));
         }
       }
+      if (classHeaderWritten) {
+        ps.append("\n");
+        writeClassFooter(clazz, ps);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 860fc45..d760b93 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -41,7 +41,7 @@
           "optimizationpasses",
           "target");
   private static final List<String> ignoredOptionalSingleArgOptions = ImmutableList
-      .of("keepdirectories");
+      .of("keepdirectories", "runtype", "laststageoutput");
   private static final List<String> ignoredFlagOptions = ImmutableList
       .of("forceprocessing", "dontusemixedcaseclassnames",
           "dontpreverify", "experimentalshrinkunusedprotofields",
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index bb8bc7c..15fc89c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -98,9 +98,13 @@
     return printed;
   }
 
+  public boolean hasMethodsFilter() {
+    return methodsFilter.size() > 0;
+  }
+
   public boolean methodMatchesFilter(DexEncodedMethod method) {
     // Not specifying a filter matches all methods.
-    if (methodsFilter.size() == 0) {
+    if (!hasMethodsFilter()) {
       return true;
     }
     // Currently the filter is simple string equality on the qualified name.
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index b1934dd..f7cbee8 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4686,8 +4686,7 @@
           // 1) t02
           // java.lang.AssertionError: java.lang.AssertionError: expected:<5> but was:<4>
 
-          .put("lang.Thread.interrupt.Thread_interrupt_A04",
-              match(D8_COMPILER, runtimes(DexVm.ART_6_0_1)))
+          .put("lang.Thread.interrupt.Thread_interrupt_A04", any())
           // Been running fine for a while then this happened on a bot:
           // 1) t01
           // java.lang.AssertionError: expected:<BLOCKED> but was:<RUNNABLE>
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 52bbda6..4923a27 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -379,4 +379,15 @@
     );
     parser.parse(proguardConfig);
   }
+
+  @Test
+  public void parseCustomFlags() throws Exception {
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    // Custom Proguard flags -runtype and -laststageoutput are ignored.
+    Path proguardConfig = writeTextToTempFile(
+        "-runtype FINAL                    ",
+        "-laststageoutput /some/file/name  "
+    );
+    parser.parse(proguardConfig);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index 0c1d13d..66d0304 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -68,7 +68,9 @@
             "    mul-int/2addr       v2, v1\n" +
             "    div-int/2addr       v3, v2\n" +
             "    return              v3\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -122,7 +124,9 @@
             "      0x00000001 -> :label_5  # 1\n" +
             "      0x00000002 -> :label_7  # 2\n" +
             "    .end sparse-switch\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -176,7 +180,9 @@
             "      :label_5\n" +
             "      :label_7\n" +
             "    .end packed-switch\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -217,7 +223,9 @@
             "      0x02  # 2\n" +
             "      0xff  # 255\n" +
             "    .end array-data\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -258,7 +266,9 @@
             "      0x0002  # 2\n" +
             "      0xffff  # 65535\n" +
             "    .end array-data\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -299,7 +309,9 @@
             "      0x00000002  # 2\n" +
             "      0xffffffff  # 4294967295\n" +
             "    .end array-data\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -340,7 +352,9 @@
             "      0x0000000000000002  # 2\n" +
             "      0xffffffffffffffff  # -1\n" +
             "    .end array-data\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -361,7 +375,9 @@
             ".super Ljava/lang/Object;\n" +
             "\n" +
             ".method public abstract test()I\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
@@ -383,7 +399,9 @@
             ".implements Ljava/util/List;\n" +
             "\n" +
             ".method public abstract test()I\n" +
-            ".end method\n";
+            ".end method\n" +
+            "\n" +
+            "# End of class LTest;\n";
 
     assertEquals(expected, application.smali(new InternalOptions()));
 
diff --git a/third_party/android_cts_baseline.tar.gz.sha1 b/third_party/android_cts_baseline.tar.gz.sha1
index 7126671..5efd8e5 100644
--- a/third_party/android_cts_baseline.tar.gz.sha1
+++ b/third_party/android_cts_baseline.tar.gz.sha1
@@ -1 +1 @@
-3914f251fc8b8669b2ec92766696267c1a9ac7ff
\ No newline at end of file
+499d86b9b9eba837261c2d10e5b76f3a3a9a9952
\ No newline at end of file
diff --git a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1 b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
new file mode 100644
index 0000000..f57ba90
--- /dev/null
+++ b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
@@ -0,0 +1 @@
+161c569821a5c9b4cb8e99de764f3449191af084
\ No newline at end of file
diff --git a/tools/compare_cts_results.py b/tools/compare_cts_results.py
index 95e29d1..00901ec 100755
--- a/tools/compare_cts_results.py
+++ b/tools/compare_cts_results.py
@@ -9,9 +9,10 @@
 from os.path import basename
 import argparse
 import os
-import re
 import sys
 
+import utils
+
 class Module:
   def __init__(self):
     self.test_cases = {}
@@ -90,35 +91,19 @@
 
 # Read CTS test_result.xml from file and merge into result_tree
 def add_to_result_tree(result_tree, file_xml, file_idx):
-  re_module = re.compile('<Module name="([^"]*)"')
-  re_test_case = re.compile('<TestCase name="([^"]*)"')
-  re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
   module = None
   test_case = None
-  with open(file_xml) as f:
-    for line in f:
-      m = re_module.search(line)
-      if m:
-        module_name = m.groups()[0]
-        module = result_tree.setdefault(module_name, Module())
-        module.set_file_index_present(file_idx)
-        continue
-
-      m = re_test_case.search(line)
-      if m:
-        test_case_name = m.groups()[0]
-        test_case = module.get_test_case_maybe_create(test_case_name)
-        test_case.set_file_index_present(file_idx)
-        continue
-
-      m = re_test.search(line)
-      if m:
-        outcome = m.groups()[0]
-        test_name = m.groups()[1]
-        assert outcome in ["fail", "pass"]
-
-        v = test_case.get_test_maybe_create(test_name)
-        v.set_file_index_outcome(outcome == 'pass', file_idx)
+  for x in utils.read_cts_test_result(file_xml):
+    if type(x) is utils.CtsModule:
+      module = result_tree.setdefault(x.name, Module())
+      module.set_file_index_present(file_idx)
+    elif type(x) is utils.CtsTestCase:
+      test_case = module.get_test_case_maybe_create(x.name)
+      test_case.set_file_index_present(file_idx)
+    else:
+      assert(type(x) is utils.CtsTest)
+      v = test_case.get_test_maybe_create(x.name)
+      v.set_file_index_outcome(x.outcome, file_idx)
 
 # main tree_report function
 def tree_report(result_tree, files, diff_only):
diff --git a/tools/gmail_data.py b/tools/gmail_data.py
new file mode 100644
index 0000000..d09a3e8
--- /dev/null
+++ b/tools/gmail_data.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import glob
+import os
+import utils
+
+THIRD_PARTY = os.path.join(utils.REPO_ROOT, 'third_party')
+BASE = os.path.join(THIRD_PARTY, 'gmail')
+
+V170604_16_BASE = os.path.join(BASE, 'gmail_android_170604.16')
+V170604_16_PREFIX = os.path.join(V170604_16_BASE, 'Gmail_release_unstripped')
+
+# NOTE: We always use android.jar for SDK v25 for now.
+ANDROID_JAR = os.path.join(THIRD_PARTY, 'android_jar', 'lib-v25', 'android.jar')
+
+VERSIONS = {
+  '170604.16': {
+    'dex' : {
+      'inputs': [os.path.join(V170604_16_BASE, 'Gmail_release_unsigned.apk')],
+      'pgmap': '%s_proguard.map' % V170604_16_PREFIX,
+      'libraries' : [ANDROID_JAR],
+      'r8-flags': '--ignore-missing-classes',
+    },
+    'deploy' : {
+      'inputs': ['%s_deploy.jar' % V170604_16_PREFIX],
+      'pgconf': ['%s_proguard.config' % V170604_16_PREFIX],
+    },
+    'proguarded' : {
+      'inputs': ['%s_proguard.jar' % V170604_16_PREFIX],
+      'pgmap': '%s_proguard.map' % V170604_16_PREFIX,
+    }
+  },
+}
diff --git a/tools/run-r8-on-gmscore.py b/tools/run-r8-on-gmscore.py
index c00a58d..6d09760 100755
--- a/tools/run-r8-on-gmscore.py
+++ b/tools/run-r8-on-gmscore.py
@@ -5,7 +5,8 @@
 
 import sys
 
-import run_r8_on_app
+import run_on_app
 
 if __name__ == '__main__':
-  sys.exit(run_r8_on_app.main())
+  # Default compiler is R8.
+  sys.exit(run_on_app.main())
diff --git a/tools/run_r8_on_app.py b/tools/run_on_app.py
similarity index 73%
rename from tools/run_r8_on_app.py
rename to tools/run_on_app.py
index 804a27e..966f786 100755
--- a/tools/run_r8_on_app.py
+++ b/tools/run_on_app.py
@@ -6,16 +6,22 @@
 import optparse
 import os
 import r8
+import d8
 import sys
 
 import gmscore_data
 import youtube_data
+import gmail_data
 
 TYPES = ['dex', 'deploy', 'proguarded']
-APPS = ['gmscore', 'youtube']
+APPS = ['gmscore', 'youtube', 'gmail']
 
 def ParseOptions():
   result = optparse.OptionParser()
+  result.add_option('--compiler',
+                    help='',
+                    default='r8',
+                    choices=['d8', 'r8'])
   result.add_option('--app',
                     help='',
                     default='gmscore',
@@ -69,6 +75,9 @@
   elif options.app == 'youtube':
     options.version = options.version or '12.22'
     data = youtube_data
+  elif options.app == 'gmail':
+    options.version = options.version or '170604.16'
+    data = gmail_data
   else:
     raise 'Unexpected'
 
@@ -85,18 +94,21 @@
     return 1
   values = version[options.type]
   inputs = None
-  # For 'deploy' the JAR is located using the Proguard configuration -injars option.
-  if 'inputs' in values and options.type != 'deploy':
+  # For R8 'deploy' the JAR is located using the Proguard configuration -injars option.
+  if 'inputs' in values and (options.compiler != 'r8' or options.type != 'deploy'):
     inputs = values['inputs']
 
   args.extend(['--output', outdir])
-  if 'pgmap' in values:
-    args.extend(['--pg-map', values['pgmap']])
-  if 'pgconf' in values and not options.k:
-    for pgconf in values['pgconf']:
-      args.extend(['--pg-conf', pgconf])
-  if options.k:
-    args.extend(['--pg-conf', options.k])
+
+  if options.compiler == 'r8':
+    if 'pgmap' in values:
+      args.extend(['--pg-map', values['pgmap']])
+    if 'pgconf' in values and not options.k:
+      for pgconf in values['pgconf']:
+        args.extend(['--pg-conf', pgconf])
+    if options.k:
+      args.extend(['--pg-conf', options.k])
+
   if not options.no_libraries and 'libraries' in values:
     for lib in values['libraries']:
       args.extend(['--lib', lib])
@@ -104,10 +116,11 @@
   if not outdir.endswith('.zip') and not outdir.endswith('.jar') and not os.path.exists(outdir):
     os.makedirs(outdir)
 
-  if 'r8-flags' in values:
-    args.extend(values['r8-flags'].split(' '))
-  if options.r8_flags:
-    args.extend(options.r8_flags.split(' '))
+  if options.compiler == 'r8':
+    if 'r8-flags' in values:
+      args.extend(values['r8-flags'].split(' '))
+    if options.r8_flags:
+      args.extend(options.r8_flags.split(' '))
 
   if inputs:
     args.extend(inputs)
@@ -116,8 +129,12 @@
     with open(options.dump_args_file, 'w') as args_file:
       args_file.writelines([arg + os.linesep for arg in args])
   else:
-    r8.run(args, not options.no_build, not options.no_debug, options.profile,
-           options.track_memory_to_file)
+    if options.compiler == 'd8':
+      d8.run(args, not options.no_build, not options.no_debug, options.profile,
+             options.track_memory_to_file)
+    else:
+      r8.run(args, not options.no_build, not options.no_debug, options.profile,
+             options.track_memory_to_file)
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py
index 384ce70..606611d 100755
--- a/tools/test_android_cts.py
+++ b/tools/test_android_cts.py
@@ -14,11 +14,9 @@
 #   cd build/aosp
 #   repo manifest -o ../../third_party/aosp_manifest.xml -r
 #
-# The baseline is the `test_result.xml` file which is created with an AOSP
-# build which uses the default (JACK) toolset.
-#
-# Use this script, with '--tool=jack' to reproduce the baseline results
-#
+# The baseline is a set of `test_result.xml` files in
+# third_party/android_cts_baseline/jack. The current test considered a success
+# if all tests pass that consistently pass in the baseline.
 
 from __future__ import print_function
 from glob import glob
@@ -30,12 +28,13 @@
 import os
 import re
 import sys
+import time
 
 import gradle
 import utils
 
-CTS_BASELINE = join(utils.REPO_ROOT,
-  'third_party/android_cts_baseline/test_result.xml')
+CTS_BASELINE_FILES_DIR = join(utils.REPO_ROOT,
+  'third_party/android_cts_baseline/jack')
 AOSP_MANIFEST_XML = join(utils.REPO_ROOT, 'third_party',
   'aosp_manifest.xml')
 AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh')
@@ -96,97 +95,33 @@
       return False
   return True
 
-# Read the xml test result file into an in-memory tree:
-# Extract only the Module/TestCase/Test names and outcome (True|False for
-# PASS|FAIL):
-#
-#     tree[module_name][testcase_name][test_name] = True|False
-#
-def read_test_result_into_tree(filename):
-  re_module = re.compile('<Module name="([^"]*)"')
-  re_testcase = re.compile('<TestCase name="([^"]*)"')
-  re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
+# Return list of fully qualified names of tests passing in
+# all the files.
+def consistently_passing_tests_from_test_results(filenames):
   tree = {}
   module = None
   testcase = None
-  with open(filename) as f:
-    for line in f:
-      m = re_module.search(line)
-      if m:
-        module_name = m.groups()[0]
-        tree[module_name] = {}
-        module = tree[module_name]
-        continue
+  # Build a tree with leaves True|False|None for passing, failing and flaky
+  # tests.
+  for f in filenames:
+    for x in utils.read_cts_test_result(f):
+      if type(x) is utils.CtsModule:
+        module = tree.setdefault(x.name, {})
+      elif type(x) is utils.CtsTestCase:
+        testcase = module.setdefault(x.name, {})
+      else:
+        outcome = testcase.setdefault(x.name, x.outcome)
+        if outcome is not None and outcome != x.outcome:
+          testcase[x.name] = None
 
-      m = re_testcase.search(line)
-      if m:
-        testcase_name = m.groups()[0]
-        module[testcase_name] = {}
-        testcase = module[testcase_name]
-        continue
+  result = []
+  for module_name, module in tree.iteritems():
+    for test_case_name, test_case in module.iteritems():
+      result.extend(['{}/{}/{}'.format(module_name, test_case_name, test_name)
+          for test_name, test in test_case.iteritems()
+              if test])
 
-      m = re_test.search(line)
-      if m:
-        outcome = m.groups()[0]
-        test_name = m.groups()[1]
-        assert outcome in ["fail", "pass"]
-        testcase[test_name] = outcome == "pass"
-  return tree
-
-# Report the items with the title
-def report_key_diff(title, items, prefix = ''):
-  if len(items) > 0:
-    print(title, ":")
-    for x in items:
-      print("- {}{}".format(prefix, x))
-    print()
-
-
-def diff_sets(base_minus_result_title, result_minus_base_title,
-    base_set, result_set, prefix = ''):
-  base_minus_result = base_set - result_set
-  result_minus_base = result_set - base_set
-  report_key_diff(base_minus_result_title, base_minus_result, prefix)
-  report_key_diff(result_minus_base_title, result_minus_base, prefix)
-  return len(base_minus_result) > 0 or len(result_minus_base) > 0
-
-def diff_tree_report(baseline_tree, result_tree):
-  baseline_modules = set(baseline_tree.keys())
-  result_modules = set(result_tree.keys())
-  differ = diff_sets('Modules missing from current result',
-      'New modules appeared in current result',
-      baseline_modules, result_modules)
-  for module in (result_modules & baseline_modules):
-    baseline_module = baseline_tree[module]
-    result_module = result_tree[module]
-    baseline_testcases = set(baseline_module.keys())
-    result_testcases = set(result_module.keys())
-    differ = diff_sets('Test cases missing from current result',
-        'New test cases appeared in current result',
-        baseline_testcases, result_testcases, module + '/') \
-        or differ
-    for testcase in (result_testcases & baseline_testcases):
-      baseline_testcase = baseline_module[testcase]
-      result_testcase = result_module[testcase]
-      baseline_tests = set(baseline_testcase.keys())
-      result_tests = set(result_testcase.keys())
-      differ = diff_sets('Tests missing from current result',
-          'New tests appeared in current result',
-          baseline_tests, result_tests, module + '/' + testcase + '/') \
-          or differ
-      need_newline_at_end = False
-      for test in (result_tests & baseline_tests):
-        baseline_outcome = baseline_testcase[test]
-        result_outcome = result_testcase[test]
-        if baseline_outcome != result_outcome:
-          differ = True
-          print('Test: {}/{}/{}, change: {}'.format(
-            module, testcase, test,
-            'PASS -> FAIL' if baseline_outcome else 'FAIL -> PASS'))
-          need_newline_at_end = True
-      if need_newline_at_end:
-        print()
-  return differ
+  return result
 
 def setup_and_clean(tool_is_d8, clean_dex):
   # Two output dirs, one for the android image and one for cts tests.
@@ -296,8 +231,6 @@
   re_summary = re.compile('<Summary ')
 
   summaries = [('Summary from current test results: ', results_xml)]
-  if not args.no_baseline:
-    summaries.append(('Summary from baseline: ', CTS_BASELINE))
 
   for (title, result_file) in summaries:
     print(title, result_file)
@@ -312,10 +245,31 @@
   else:
     print('Comparing test results to baseline:\n')
 
-    result_tree = read_test_result_into_tree(results_xml)
-    baseline_tree = read_test_result_into_tree(CTS_BASELINE)
+    passing_tests = consistently_passing_tests_from_test_results([results_xml])
+    baseline_results = \
+        [f for f in glob(join(CTS_BASELINE_FILES_DIR, '*.xml'))]
+    assert len(baseline_results) != 0
 
-    r = EXIT_FAILURE if diff_tree_report(baseline_tree, result_tree) else 0
+    passing_tests_in_baseline = \
+        consistently_passing_tests_from_test_results(baseline_results)
+
+    missing_or_failing_tests = \
+        set(passing_tests_in_baseline) - set(passing_tests)
+
+    num_tests = len(missing_or_failing_tests)
+    if num_tests != 0:
+      if num_tests > 1:
+        text = '{} tests that consistently pass in the baseline' \
+          ' are missing or failing in the current test:'.format(num_tests)
+      else:
+        text = '1 test that consistently passes in the baseline' \
+          ' is missing or failing in the current test:'
+      print(text)
+      for t in missing_or_failing_tests:
+        print(t)
+      r = EXIT_FAILURE
+    else:
+      r = 0
 
   if args.save_result:
     copy2(results_xml, args.save_result)
diff --git a/tools/utils.py b/tools/utils.py
index 67cac46..7379e16 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -6,6 +6,7 @@
 
 import hashlib
 import os
+import re
 import shutil
 import subprocess
 import sys
@@ -69,3 +70,40 @@
  def __exit__(self, *_):
    print "Enter directory = ", self._old_cwd
    os.chdir(self._old_cwd)
+
+# Reading Android CTS test_result.xml
+
+class CtsModule(object):
+  def __init__(self, module_name):
+    self.name = module_name
+
+class CtsTestCase(object):
+  def __init__(self, test_case_name):
+    self.name = test_case_name
+
+class CtsTest(object):
+  def __init__(self, test_name, outcome):
+    self.name = test_name
+    self.outcome = outcome
+
+# Generator yielding CtsModule, CtsTestCase or CtsTest from
+# reading through a CTS test_result.xml file.
+def read_cts_test_result(file_xml):
+  re_module = re.compile('<Module name="([^"]*)"')
+  re_test_case = re.compile('<TestCase name="([^"]*)"')
+  re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
+  with open(file_xml) as f:
+    for line in f:
+      m = re_module.search(line)
+      if m:
+        yield CtsModule(m.groups()[0])
+        continue
+      m = re_test_case.search(line)
+      if m:
+        yield CtsTestCase(m.groups()[0])
+        continue
+      m = re_test.search(line)
+      if m:
+        outcome = m.groups()[0]
+        assert outcome in ["fail", "pass"]
+        yield CtsTest(m.groups()[1], outcome == 'pass')