Test shrinking of proto builders

Change-Id: I63a6dd75ee2b4df632ee1a413be2e0028867e981
diff --git a/src/test/examplesProto/proto2/BuilderTestClass.java b/src/test/examplesProto/proto2/BuilderTestClass.java
new file mode 100644
index 0000000..9712c22
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderTestClass.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2019, 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.NestedMessage;
+import com.android.tools.r8.proto2.TestProto.OuterMessage;
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderTestClass {
+
+  public static void main(String[] args) {
+    builderWithPrimitiveSetters();
+    builderWithReusedSetters();
+    builderWithProtoBuilderSetter();
+    builderWithProtoSetter();
+    builderWithOneofSetter();
+  }
+
+  public static void builderWithPrimitiveSetters() {
+    System.out.println("builderWithPrimitiveSetters");
+    Primitives primitives = Primitives.newBuilder().setFooInt32(17).build();
+    Primitives other = Primitives.newBuilder().setBarInt64(16).build();
+    System.out.println(primitives.getFooInt32());
+    System.out.println(other.getBarInt64());
+  }
+
+  public static void builderWithReusedSetters() {
+    System.out.println("builderWithReusedSetters");
+    Primitives.Builder builder = Primitives.newBuilder().setFooInt32(1);
+    Primitives primitives = builder.build();
+    // Reusing the builder after a build should force copyOnWrite to be kept.
+    Primitives other = builder.setQuxString("qux").build();
+    System.out.println(primitives.getFooInt32());
+    System.out.println(other.getQuxString());
+  }
+
+  public static void builderWithProtoBuilderSetter() {
+    System.out.println("builderWithProtoBuilderSetter");
+    OuterMessage outerMessage =
+        OuterMessage.newBuilder()
+            .setNestedField(NestedMessage.newBuilder().setFooInt64(42))
+            .build();
+    System.out.println(outerMessage.getNestedField().getFooInt64());
+  }
+
+  public static void builderWithProtoSetter() {
+    System.out.println("builderWithProtoSetter");
+    OuterMessage outerMessage =
+        OuterMessage.newBuilder()
+            .setNestedField(NestedMessage.newBuilder().setFooInt64(42).build())
+            .build();
+    System.out.println(outerMessage.getNestedField().getFooInt64());
+  }
+
+  public static void builderWithOneofSetter() {
+    System.out.println("builderWithOneofSetter");
+    Primitives primitives = Primitives.newBuilder().setOneofString("foo").build();
+    System.out.println(primitives.getOneofString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
new file mode 100644
index 0000000..272f60b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2019, 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.internal.proto;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Proto2BuilderShrinkingTest extends ProtoShrinkingTestBase {
+
+  private static final String LITE_BUILDER = "com.google.protobuf.GeneratedMessageLite$Builder";
+  private static final String TEST_CLASS = "proto2.BuilderTestClass";
+
+  private static List<Path> PROGRAM_FILES =
+      ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
+
+  private final boolean enableMinification;
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{1}, enable minification: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+  }
+
+  public Proto2BuilderShrinkingTest(boolean enableMinification, TestParameters parameters) {
+    this.enableMinification = enableMinification;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(PROGRAM_FILES)
+        .addKeepMainRule(TEST_CLASS)
+        .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+        .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
+        .addKeepRules("-neverinline class " + TEST_CLASS + " { <methods>; }")
+        .addOptionsModification(
+            options -> {
+              options.enableFieldBitAccessAnalysis = true;
+              options.enableGeneratedMessageLiteShrinking = true;
+              options.enableGeneratedExtensionRegistryShrinking = true;
+              options.enableStringSwitchConversion = true;
+            })
+        .allowAccessModification()
+        .allowUnusedProguardConfigurationRules()
+        .enableInliningAnnotations()
+        .minification(enableMinification)
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TEST_CLASS)
+        .assertSuccessWithOutputLines(
+            "builderWithPrimitiveSetters",
+            "17",
+            "16",
+            "builderWithReusedSetters",
+            "1",
+            "qux",
+            "builderWithProtoBuilderSetter",
+            "42",
+            "builderWithProtoSetter",
+            "42",
+            "builderWithOneofSetter",
+            "foo");
+  }
+
+  private void inspect(CodeInspector outputInspector) {
+    ClassSubject liteClassSubject = outputInspector.clazz(LITE_BUILDER);
+    assertThat(liteClassSubject, isPresent());
+
+    MethodSubject copyOnWriteMethodSubject = liteClassSubject.uniqueMethodWithName("copyOnWrite");
+    assertThat(copyOnWriteMethodSubject, isPresent());
+
+    ClassSubject testClassSubject = outputInspector.clazz(TEST_CLASS);
+    assertThat(testClassSubject, isPresent());
+
+    List<String> testNames =
+        ImmutableList.of(
+            "builderWithPrimitiveSetters",
+            "builderWithReusedSetters",
+            "builderWithProtoBuilderSetter",
+            "builderWithProtoSetter",
+            "builderWithOneofSetter");
+    for (String testName : testNames) {
+      MethodSubject methodSubject = testClassSubject.uniqueMethodWithName(testName);
+      assertThat(methodSubject, isPresent());
+      assertTrue(
+          methodSubject
+              .streamInstructions()
+              .filter(InstructionSubject::isInvoke)
+              .map(InstructionSubject::getMethod)
+              // TODO(b/112437944): Only builderWithReusedSetters() should invoke copyOnWrite().
+              .anyMatch(method -> method == copyOnWriteMethodSubject.getMethod().method));
+    }
+  }
+}