Merge "Only run MethodHandleTest on DexVm.ART_DEFAULT"
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index e46bef9..5b96201 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -18,11 +18,19 @@
  *
  * <p>Lines with a # prefix are ignored.
  *
- * <p>We will do most specific matching, i.e., com.google.foobar.*:feature2 com.google.*:base will
- * put everything in the com.google namespace into base, except classes in com.google.foobar that
- * will go to feature2. Class based mappings takes precedence over packages (since they are more
- * specific): com.google.A:feature2 com.google.*:base Puts A into feature2, and all other classes
- * from com.google into base.
+ * <p>We will do most specific matching, i.e.,
+ * <pre>
+ *   com.google.foobar.*:feature2
+ *   com.google.*:base
+ * </pre>
+ * will put everything in the com.google namespace into base, except classes in com.google.foobar
+ * that will go to feature2. Class based mappings takes precedence over packages (since they are
+ * more specific):
+ * <pre>
+ *   com.google.A:feature2
+ *   com.google.*:base
+ *  </pre>
+ * Puts A into feature2, and all other classes from com.google into base.
  *
  * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
  * placement tool.
@@ -32,10 +40,12 @@
   HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
 
   HashSet<FeaturePredicate> mappings = new HashSet<>();
+
   Path mappingFile;
 
   static final String COMMENT = "#";
   static final String SEPARATOR = ":";
+  static final String BASE_NAME = "base";
 
   public static FeatureClassMapping fromSpecification(Path file)
       throws FeatureMappingException, IOException {
@@ -69,7 +79,7 @@
 
   private FeatureClassMapping() {}
 
-  public void addMapping(String clazz, String feature) throws FeatureMappingException {
+  private void addMapping(String clazz, String feature) throws FeatureMappingException {
     addRule(clazz, feature, 0);
   }
 
@@ -91,7 +101,7 @@
       }
     }
     if (bestMatch == null) {
-      throw new FeatureMappingException("Class: " + clazz + " is not mapped to any feature");
+      return BASE_NAME;
     }
     return bestMatch.feature;
   }
diff --git a/src/test/examples/dexsplitsample/Class1.java b/src/test/examplesAndroidN/dexsplitsample/Class1.java
similarity index 100%
rename from src/test/examples/dexsplitsample/Class1.java
rename to src/test/examplesAndroidN/dexsplitsample/Class1.java
diff --git a/src/test/examples/dexsplitsample/Class2.java b/src/test/examplesAndroidN/dexsplitsample/Class2.java
similarity index 100%
rename from src/test/examples/dexsplitsample/Class2.java
rename to src/test/examplesAndroidN/dexsplitsample/Class2.java
diff --git a/src/test/examples/dexsplitsample/Class3.java b/src/test/examplesAndroidN/dexsplitsample/Class3.java
similarity index 100%
rename from src/test/examples/dexsplitsample/Class3.java
rename to src/test/examplesAndroidN/dexsplitsample/Class3.java
diff --git a/src/test/examplesAndroidN/dexsplitsample/Class4.java b/src/test/examplesAndroidN/dexsplitsample/Class4.java
new file mode 100644
index 0000000..5b1607f
--- /dev/null
+++ b/src/test/examplesAndroidN/dexsplitsample/Class4.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, 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.
+
+package dexsplitsample;
+
+public class Class4 {
+  public static void main(String[] args) {
+    new Class4().createLambda();
+    System.out.println("Class4");
+  }
+
+  private void useLambda(LambdaInterface toInvokeOn) {
+    toInvokeOn.foo(42);
+  }
+
+  private void createLambda() {
+    useLambda((a) -> { return a + 2;});
+  }
+
+  interface LambdaInterface {
+    int foo(int a);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index 43e9d03..19a5ee2 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -40,11 +41,15 @@
 
 public class DexSplitterTests {
 
-  private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexsplitsample";
+  private static final String CLASS_DIR =
+      ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample";
   private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
   private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
   private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
   private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
+  private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
+  private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
+
 
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -75,6 +80,8 @@
             .addProgramFiles(Paths.get(CLASS2_CLASS))
             .addProgramFiles(Paths.get(CLASS3_CLASS))
             .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .build());
 
     Path output = temp.getRoot().toPath().resolve("output");
@@ -135,6 +142,18 @@
     } catch (AssertionError assertionError) {
       // We expect this to throw since base is not in the path and Class3 depends on Class1
     }
+
+    className = "Class4";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class4 includes a lambda that
+      // would have been pushed to base.
+    }
   }
 
   private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
@@ -143,7 +162,8 @@
       out.write(
           "dexsplitsample.Class1:base\n"
               + "dexsplitsample.Class2:feature1\n"
-              + "dexsplitsample.Class3:feature1");
+              + "dexsplitsample.Class3:feature1\n"
+              + "dexsplitsample.Class4:feature1");
     }
     return splitSpec;
   }
@@ -159,11 +179,13 @@
   public void splitFilesFromJar()
       throws IOException, CompilationFailedException, FeatureMappingException, ResourceException,
       CompilationException, ExecutionException {
-    splitFromJars(true);
-    splitFromJars(false);
+    splitFromJars(true, true);
+    splitFromJars(false, true);
+    splitFromJars(true, false);
+    splitFromJars(false, false);
   }
 
-  private void splitFromJars(boolean useOptions)
+  private void splitFromJars(boolean useOptions, boolean explicitBase)
       throws IOException, CompilationFailedException, FeatureMappingException, ResourceException,
       ExecutionException, CompilationException {
     // Initial normal compile to create dex files.
@@ -175,6 +197,8 @@
             .addProgramFiles(Paths.get(CLASS2_CLASS))
             .addProgramFiles(Paths.get(CLASS3_CLASS))
             .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .build());
 
     Path output = temp.getRoot().toPath().resolve("output");
@@ -200,26 +224,38 @@
     featureStream.putNextEntry(new ZipEntry(name));
     featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
     featureStream.closeEntry();
+    name = "dexsplitsample/Class4";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class4$LambdaInterface";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
+    featureStream.closeEntry();
     featureStream.close();
     if (useOptions) {
       Options options = new Options();
       options.inputArchives.add(inputZip.toString());
       options.splitBaseName = output.toString();
-      options.featureJars.add(baseJar.toString());
+      if (explicitBase) {
+        options.featureJars.add(baseJar.toString());
+      }
       options.featureJars.add(featureJar.toString());
       DexSplitter.run(options);
     } else {
-      DexSplitter.main(
-          new String[]{
-              "--input",
-              inputZip.toString(),
-              "--output",
-              output.toString(),
-              "--feature-jar",
-              baseJar.toString(),
-              "--feature-jar",
-              featureJar.toString()
-          });
+      List<String> args = Lists.newArrayList(
+          "--input",
+          inputZip.toString(),
+          "--output",
+          output.toString(),
+          "--feature-jar",
+          featureJar.toString());
+      if (explicitBase) {
+        args.add("--feature-jar");
+        args.add(baseJar.toString());
+      }
+
+      DexSplitter.main(args.toArray(new String[0]));
     }
     Path base = output.getParent().resolve("output.base.zip");
     Path feature = output.getParent().resolve("output.feature1.zip");
@@ -241,6 +277,8 @@
             .addProgramFiles(Paths.get(CLASS2_CLASS))
             .addProgramFiles(Paths.get(CLASS3_CLASS))
             .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
             .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
             .setProguardMapOutputPath(proguardMap)
             .addProguardConfiguration(getProguardConf(), null)
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
index a8ead8a..eff632a 100644
--- a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
+++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -65,10 +65,30 @@
 
   @Test
   public void testCatchAllWildcards() throws Exception {
+    testBaseWildcard(true);
+    testBaseWildcard(false);
+    testNonBaseCatchAll();
+  }
+
+  private void testNonBaseCatchAll() throws FeatureMappingException {
     List<String> lines =
         ImmutableList.of(
             "com.google.Feature1:feature1",
-            "*:base",
+            "*:nonbase",
+            "com.strange.*:feature2");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase");
+    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
+    assertEquals(mapping.featureForClass("Feature1"), "nonbase");
+    assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase");
+  }
+
+  private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException {
+    List<String> lines =
+        ImmutableList.of(
+            "com.google.Feature1:feature1",
+            explicitBase ? "*:base" : "",
             "com.strange.*:feature2");
     FeatureClassMapping mapping = new FeatureClassMapping(lines);
     assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");