Add a test of desugaring java.util.BufferedReader.lines

This includes testing support to amend the current default desugar
configuration.

Bug: 147485959
Change-Id: I633790db4e1f9f15985708fa4af6504b44a4ed2f
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index d687d19..7586210 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 public class DesugaredLibraryConfigurationParser {
 
@@ -77,6 +78,12 @@
   }
 
   public DesugaredLibraryConfiguration parse(StringResource stringResource) {
+    return parse(stringResource, builder -> {});
+  }
+
+  public DesugaredLibraryConfiguration parse(
+      StringResource stringResource,
+      Consumer<DesugaredLibraryConfiguration.Builder> configurationAmender) {
     origin = stringResource.getOrigin();
     assert origin != null;
     configurationBuilder = DesugaredLibraryConfiguration.builder(dexItemFactory, reporter, origin);
@@ -142,6 +149,7 @@
       }
       configurationBuilder.setExtraKeepRules(extraKeepRules);
     }
+    configurationAmender.accept(configurationBuilder);
     DesugaredLibraryConfiguration config = configurationBuilder.build();
     configurationBuilder = null;
     origin = null;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
new file mode 100644
index 0000000..99b1316
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -0,0 +1,250 @@
+// Copyright (c) 2020, 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 com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.L8Command;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BufferedReaderTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withAllRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .withApiLevel(AndroidApiLevel.N)
+            .build());
+  }
+
+  public BufferedReaderTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private String expectedOutput() {
+    return StringUtils.lines(
+        "Hello",
+        "Larry",
+        "Page",
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+            ? "Caught java.io.UncheckedIOException"
+            : "Caught j$.io.UncheckedIOException");
+  }
+
+  DesugaredLibraryConfiguration configurationWithBufferedReader(
+      InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
+    // Parse the current configuration and amend the configuration for BufferedReader.lines. The
+    // configuration is the same for both program and library.
+    return new DesugaredLibraryConfigurationParser(
+            options.dexItemFactory(),
+            options.reporter,
+            libraryCompilation,
+            parameters.getApiLevel().getLevel())
+        .parse(
+            StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING),
+            builder -> {
+              if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+                builder.putRewritePrefix(
+                    "java.io.DesugarBufferedReader", "j$.io.DesugarBufferedReader");
+                builder.putRewritePrefix(
+                    "java.io.UncheckedIOException", "j$.io.UncheckedIOException");
+                builder.putRetargetCoreLibMember(
+                    "java.io.BufferedReader#lines", "java.io.DesugarBufferedReader");
+              }
+            });
+  }
+
+  private void configurationForProgramCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        configurationWithBufferedReader(options, false, parameters);
+  }
+
+  private void configurationForLibraryCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        configurationWithBufferedReader(options, true, parameters);
+  }
+
+  @Test
+  public void testBufferedReaderD8Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addOptionsModification(this::configurationForProgramCompilation)
+            .addInnerClasses(BufferedReaderTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            // .inspect(this::checkRewrittenInvokes)
+            .writeToZip();
+
+    if (parameters.getRuntime().isDex()) {
+      // Collection keep rules is only implemented in the DEX writer.
+      String desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        assertEquals(0, desugaredLibraryKeepRules.length());
+        desugaredLibraryKeepRules = "-keep class * { *; }";
+      }
+
+      // Convert to DEX without desugaring and run.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              (apiLevel, keepRules, shrink) ->
+                  buildDesugaredLibrary(
+                      apiLevel,
+                      keepRules,
+                      shrink,
+                      ImmutableList.of(),
+                      this::configurationForLibraryCompilation),
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput());
+    } else {
+      // Build the desugared library in class file format.
+      Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
+      L8Command.Builder l8Builder =
+          L8Command.builder()
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+              .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+              .setMode(CompilationMode.DEBUG)
+              .addDesugaredLibraryConfiguration(
+                  StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+              .setMinApiLevel(parameters.getApiLevel().getLevel())
+              .setOutput(desugaredLib, OutputMode.ClassFile);
+      ToolHelper.runL8(l8Builder.build(), this::configurationForLibraryCompilation);
+
+      // Run on the JVM with desuagred library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(desugaredLib)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput());
+    }
+  }
+
+  @Test
+  public void testBufferedReaderD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addOptionsModification(
+            options ->
+                options.desugaredLibraryConfiguration =
+                    configurationWithBufferedReader(options, false, parameters))
+        .addInnerClasses(BufferedReaderTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput());
+  }
+
+  @Test
+  public void testBufferedReaderR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addOptionsModification(
+            options ->
+                options.desugaredLibraryConfiguration =
+                    configurationWithBufferedReader(options, false, parameters))
+        .addInnerClasses(BufferedReaderTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .enableInliningAnnotations()
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput());
+  }
+
+  static class TestClass {
+
+    @NeverInline
+    public static void testBufferedReaderLines() throws Exception {
+      try (BufferedReader reader = new BufferedReader(new StringReader("Hello\nLarry\nPage"))) {
+        reader.lines().forEach(System.out::println);
+      }
+    }
+
+    @NeverInline
+    public static void testBufferedReaderLines_uncheckedIoException() throws Exception {
+      BufferedReader reader = new BufferedReader(new StringReader(""));
+      reader.close();
+      try {
+        reader.lines().count();
+        System.out.println("UncheckedIOException expected");
+      } catch (UncheckedIOException expected) {
+        System.out.println("Caught " + expected.getClass().getName());
+      } catch (Throwable t) {
+        System.out.println("Caught unexpected" + t.getClass().getName());
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      testBufferedReaderLines();
+      testBufferedReaderLines_uncheckedIoException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index afc19d9..e520016 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -29,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class DesugaredLibraryTestBase extends TestBase {
 
@@ -69,7 +71,7 @@
   }
 
   protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel, String keepRules, boolean shrink) {
-    return buildDesugaredLibrary(apiLevel, keepRules, shrink, ImmutableList.of());
+    return buildDesugaredLibrary(apiLevel, keepRules, shrink, ImmutableList.of(), options -> {});
   }
 
   protected Path buildDesugaredLibrary(
@@ -77,6 +79,16 @@
       String keepRules,
       boolean shrink,
       List<Path> additionalProgramFiles) {
+    return buildDesugaredLibrary(
+        apiLevel, keepRules, shrink, additionalProgramFiles, options -> {});
+  }
+
+  protected Path buildDesugaredLibrary(
+      AndroidApiLevel apiLevel,
+      String keepRules,
+      boolean shrink,
+      List<Path> additionalProgramFiles,
+      Consumer<InternalOptions> optionsModifier) {
     // We wrap exceptions in a RuntimeException to call this from a lambda.
     try {
       // If we compile extended library here, it means we use TestNG.
@@ -108,6 +120,7 @@
               options.testing.disableL8AnnotationRemoval = true;
               options.testing.forceLibBackportsInL8CfToCf = true;
             }
+            optionsModifier.accept(options);
           });
       if (!extraFiles) {
         assertTrue(