Add tests for @Metadata rewriting of sealedSubclasses.

Bug: 70169921
Change-Id: I32193c60a0522323e0ce61a9d839c6044b0efb26
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
index 949b0f2..a5c01a5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInCompanionTest.java
@@ -75,7 +75,6 @@
     final String companionClassName = pkg + ".companion_lib.B$Companion";
     compileResult.inspect(inspector -> {
       ClassSubject sup = inspector.clazz(superClassName);
-      assertThat(sup, isPresent());
       assertThat(sup, isRenamed());
 
       ClassSubject impl = inspector.clazz(bClassName);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
new file mode 100644
index 0000000..725576d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInSealedClassTest.java
@@ -0,0 +1,185 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameInSealedClassTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRenameInSealedClassTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Path sealedLibJar;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String sealedLibFolder = PKG_PREFIX + "/sealed_lib";
+    sealedLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(getKotlinFileInTest(sealedLibFolder, "lib"))
+            .compile();
+  }
+
+  @Test
+  public void testMetadataInSealedClass_valid() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(sealedLibJar)
+            // Keep the Expr class
+            .addKeepRules("-keep class **.Expr")
+            // Keep the extension function
+            .addKeepRules("-keep class **.LibKt { <methods>; }")
+            // Keep the factory object and utils
+            .addKeepRules("-keep class **.ExprFactory { *; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String numClassName = pkg + ".sealed_lib.Num";
+    final String exprClassName = pkg + ".sealed_lib.Expr";
+    final String libClassName = pkg + ".sealed_lib.LibKt";
+    compileResult.inspect(inspector -> {
+      ClassSubject num = inspector.clazz(numClassName);
+      assertThat(num, isRenamed());
+
+      ClassSubject expr = inspector.clazz(exprClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmClassSubject kmClass = expr.getKmClass();
+      assertThat(kmClass, isPresent());
+
+      kmClass.getSealedSubclassDescriptors().forEach(sealedSubclassDescriptor -> {
+        ClassSubject sealedSubclass =
+            inspector.clazz(descriptorToJavaType(sealedSubclassDescriptor));
+        // TODO(b/70169921): sealedSubclasses in KmClass should refer to live classes.
+        // assertThat(sealedSubclass, isRenamed());
+        // TODO(b/70169921): sealedSubclasses in KmClass should refer to renamed classes.
+        assertNotEquals(sealedSubclassDescriptor, sealedSubclass.getFinalDescriptor());
+      });
+
+      ClassSubject libKt = inspector.clazz(libClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmPackageSubject kmPackage = libKt.getKmPackage();
+      assertThat(kmPackage, isPresent());
+
+      KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+      assertThat(eval, isPresent());
+      assertThat(eval, isExtensionFunction());
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/sealed_app";
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "valid"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".sealed_app.ValidKt")
+        .assertSuccessWithOutputLines("6");
+  }
+
+  @Test
+  public void testMetadataInSealedClass_invalid() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(sealedLibJar)
+            // Keep the Expr class
+            .addKeepRules("-keep class **.Expr")
+            // Keep the extension function
+            .addKeepRules("-keep class **.LibKt { <methods>; }")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile();
+    String pkg = getClass().getPackage().getName();
+    final String exprClassName = pkg + ".sealed_lib.Expr";
+    final String numClassName = pkg + ".sealed_lib.Num";
+    final String libClassName = pkg + ".sealed_lib.LibKt";
+    compileResult.inspect(inspector -> {
+      // Without any specific keep rule and no instantiation point, it's not necessary to keep
+      // sub classes of Expr.
+      ClassSubject num = inspector.clazz(numClassName);
+      assertThat(num, not(isPresent()));
+
+      ClassSubject expr = inspector.clazz(exprClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmClassSubject kmClass = expr.getKmClass();
+      assertThat(kmClass, isPresent());
+
+      // TODO(b/70169921): Then, we can cleanup sealedSubclasses in KmClass
+      assertFalse(kmClass.getSealedSubclassDescriptors().isEmpty());
+
+      ClassSubject libKt = inspector.clazz(libClassName);
+      assertThat(expr, isPresent());
+      assertThat(expr, not(isRenamed()));
+
+      KmPackageSubject kmPackage = libKt.getKmPackage();
+      assertThat(kmPackage, isPresent());
+
+      KmFunctionSubject eval = kmPackage.kmFunctionExtensionWithUniqueName("eval");
+      assertThat(eval, isPresent());
+      assertThat(eval, isExtensionFunction());
+    });
+
+    Path libJar = compileResult.writeToZip();
+
+    String appFolder = PKG_PREFIX + "/sealed_app";
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(appFolder, "invalid"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
+
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(kotlinTestCompileResult.stderr, containsString("cannot access"));
+    assertThat(kotlinTestCompileResult.stderr, containsString("private in 'Expr'"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
new file mode 100644
index 0000000..88f401c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/invalid.kt
@@ -0,0 +1,14 @@
+// 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.kotlin.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.Expr
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+
+// A sealed class can only be extended within that file.
+class MyExpr : Expr()
+
+fun main() {
+  println(MyExpr().eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
new file mode 100644
index 0000000..379c17e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_app/valid.kt
@@ -0,0 +1,11 @@
+// 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.kotlin.metadata.sealed_app
+
+import com.android.tools.r8.kotlin.metadata.sealed_lib.eval
+import com.android.tools.r8.kotlin.metadata.sealed_lib.ExprFactory
+
+fun main() {
+  println(ExprFactory.createSum(ExprFactory.createNum(4), ExprFactory.createNum(2)).eval())
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
new file mode 100644
index 0000000..ea64d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/sealed_lib/lib.kt
@@ -0,0 +1,26 @@
+// 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.kotlin.metadata.sealed_lib
+
+sealed class Expr
+
+sealed class BinOp(val e1: Expr, val e2: Expr, val op: String) : Expr() {
+  override fun toString() = "$e1 $op $e2"
+}
+
+data class Num(val num: Int) : Expr()
+
+data class Sum(val left: Expr, val right: Expr) : BinOp(left, right, "+")
+
+object ExprFactory {
+  fun createNum(i: Int): Expr = Num(i)
+  fun createSum(e1: Expr, e2: Expr): Expr = Sum(e1, e2)
+}
+
+fun Expr.eval(): Int =
+  when(this) {
+    is Num -> num
+    is Sum -> e1.eval() + e2.eval()
+    else -> throw IllegalArgumentException("Unknown Expr: $this")
+  }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index 6a2f504..1a640ed 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -97,4 +97,14 @@
   public List<ClassSubject> getSuperTypes() {
     return null;
   }
+
+  @Override
+  public List<String> getSealedSubclassDescriptors() {
+    return null;
+  }
+
+  @Override
+  public List<ClassSubject> getSealedSubclasses() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index e60f9f3..6c65c2d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -69,4 +70,19 @@
         .filter(ClassSubject::isPresent)
         .collect(Collectors.toList());
   }
+
+  @Override
+  public List<String> getSealedSubclassDescriptors() {
+    return kmClass.getSealedSubclasses().stream()
+        .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public List<ClassSubject> getSealedSubclasses() {
+    return kmClass.getSealedSubclasses().stream()
+        .map(DescriptorUtils::getDescriptorFromKotlinClassifier)
+        .map(this::getClassSubjectFromDescriptor)
+        .collect(Collectors.toList());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 34bc771..9fece50 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -140,12 +140,16 @@
         .collect(Collectors.toList());
   }
 
+  default ClassSubject getClassSubjectFromDescriptor(String descriptor) {
+    return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+  }
+
   default ClassSubject getClassSubjectFromKmType(KmType kmType) {
     String descriptor = getDescriptorFromKmType(kmType);
     if (descriptor == null) {
       return new AbsentClassSubject();
     }
-    return codeInspector().clazz(Reference.classFromDescriptor(descriptor));
+    return getClassSubjectFromDescriptor(descriptor);
   }
 
   @Override
@@ -167,7 +171,7 @@
   }
 
   // TODO(b/145824437): This is a dup of KotlinMetadataJvmExtensionUtils$KmPropertyProcessor
-  static class KmPropertyProcessor {
+  class KmPropertyProcessor {
     JvmFieldSignature fieldSignature = null;
     // Custom getter via @get:JvmName("..."). Otherwise, null.
     JvmMethodSignature getterSignature = null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index d36f233..97b233d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -12,4 +12,8 @@
   public abstract List<String> getSuperTypeDescriptors();
 
   public abstract List<ClassSubject> getSuperTypes();
+
+  public abstract List<String> getSealedSubclassDescriptors();
+
+  public abstract List<ClassSubject> getSealedSubclasses();
 }