Add tests of running R8 on Kotlin libraries with inline functions

Bug: b/453861119

Change-Id: Ie2cff456be28a8f05681704618798c39d3f17964
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineClassLevelLibraryTest.java b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineClassLevelLibraryTest.java
new file mode 100644
index 0000000..c17e506
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineClassLevelLibraryTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2025, 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.inline;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinInlineClassLevelLibraryTest extends KotlinInlineTestBase {
+
+  public KotlinInlineClassLevelLibraryTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters, "classlevel");
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected String getExpected() {
+    return StringUtils.lines("Hello, world!", "Hello again, world!", "Hello, default");
+  }
+
+  @Override
+  protected String getLibraryClass() {
+    return PKG + "." + subPackage + ".lib.A";
+  }
+
+  @Override
+  protected void configure(R8FullTestBuilder builder) {
+    builder.addKeepRules(ImmutableList.of("-keep public class * { public <methods>; }"));
+  }
+
+  @Override
+  protected boolean kotlinCompilationFails() {
+    return true;
+  }
+
+  @Override
+  protected void kotlinCompilationResult(ProcessResult result) {
+    if (kotlinParameters.isNewerThan(KotlinCompilerVersion.KOTLINC_1_4_20)) {
+      assertThat(result.stderr, containsString("Exception while generating code for:"));
+      assertThat(
+          result.stderr,
+          containsString(
+              "FUN name:main visibility:public modality:FINAL <> () returnType:kotlin.Unit"));
+    } else {
+      assertThat(
+          result.stderr,
+          containsString("Backend Internal error: Exception during file facade code generation"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineCompanionLibraryTest.java b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineCompanionLibraryTest.java
new file mode 100644
index 0000000..22672a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineCompanionLibraryTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2025, 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.inline;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinInlineCompanionLibraryTest extends KotlinInlineTestBase {
+
+  public KotlinInlineCompanionLibraryTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters, "companion");
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected String getExpected() {
+    return StringUtils.lines("Hello, world!", "Hello again, world!", "Hello, default");
+  }
+
+  @Override
+  protected String getLibraryClass() {
+    return PKG + "." + subPackage + ".lib.A";
+  }
+
+  @Override
+  protected void configure(R8FullTestBuilder builder) {
+    builder.addKeepRules(ImmutableList.of("-keep public class * { public <methods>; }"));
+  }
+
+  @Override
+  protected boolean kotlinCompilationFails() {
+    return true;
+  }
+
+  @Override
+  protected void kotlinCompilationResult(ProcessResult result) {
+    assertThat(
+        result.stderr,
+        containsString(
+            kotlinParameters
+                    .getCompilerVersion()
+                    .isGreaterThanOrEqualTo(KotlinCompilerVersion.KOTLINC_2_0_20)
+                ? "main.kt:8:5: error: unresolved reference 'f'"
+                : "main.kt:8:5: error: unresolved reference: f"));
+    assertThat(
+        result.stderr,
+        containsString(
+            kotlinParameters
+                    .getCompilerVersion()
+                    .isGreaterThanOrEqualTo(KotlinCompilerVersion.KOTLINC_2_0_20)
+                ? "main.kt:9:5: error: unresolved reference 'f'"
+                : "main.kt:9:5: error: unresolved reference: f"));
+    assertThat(
+        result.stderr,
+        containsString(
+            kotlinParameters
+                    .getCompilerVersion()
+                    .isGreaterThanOrEqualTo(KotlinCompilerVersion.KOTLINC_2_0_20)
+                ? "main.kt:10:5: error: unresolved reference 'g'"
+                : "main.kt:10:5: error: unresolved reference: g"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineFileLevelLibraryTest.java b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineFileLevelLibraryTest.java
new file mode 100644
index 0000000..819ec65
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineFileLevelLibraryTest.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2025, 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.inline;
+
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KotlinInlineFileLevelLibraryTest extends KotlinInlineTestBase {
+
+  public KotlinInlineFileLevelLibraryTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters, "filelevel");
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected String getExpected() {
+    return StringUtils.lines("Hello, world!", "Hello again, world!", "Hello, default");
+  }
+
+  @Override
+  protected String getLibraryClass() {
+    return PKG + "." + subPackage + ".lib.LibKt";
+  }
+
+  @Override
+  protected void configure(R8FullTestBuilder builder) {
+    builder.addKeepRules(ImmutableList.of("-keep public class * { public <methods>; }"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineTestBase.java b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineTestBase.java
new file mode 100644
index 0000000..39427f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/KotlinInlineTestBase.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2025, 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.inline;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.KotlinCompileMemoizer;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public abstract class KotlinInlineTestBase extends KotlinTestBase {
+
+  static final String PKG =
+      com.android.tools.r8.kotlin.inline.KotlinInlineTestBase.class.getPackage().getName();
+  static final String PKG_PREFIX = DescriptorUtils.getBinaryNameFromJavaType(PKG);
+
+  protected String subPackage;
+  protected KotlinCompileMemoizer jarMap;
+
+  protected TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        getKotlinTestParameters().withAllCompilersAndLambdaGenerations().build());
+  }
+
+  public KotlinInlineTestBase(KotlinTestParameters kotlinParameters, String subPackage) {
+    super(kotlinParameters);
+    this.subPackage = subPackage;
+    this.jarMap = getCompileMemoizer(getLibSourceFile());
+  }
+
+  protected Path getLibSourceFile() {
+    return getKotlinFileInTest(PKG_PREFIX + "/" + subPackage + "/lib", "lib");
+  }
+
+  protected Path getAppSourceFile() {
+    return getKotlinFileInTest(PKG_PREFIX + "/" + subPackage + "/app", "main");
+  }
+
+  protected String getMainClass() {
+    return PKG + "." + subPackage + ".app.MainKt";
+  }
+
+  protected abstract String getExpected();
+
+  protected abstract String getLibraryClass();
+
+  protected abstract void configure(R8FullTestBuilder builder);
+
+  protected boolean kotlinCompilationFails() {
+    return false;
+  }
+
+  protected void kotlinCompilationResult(ProcessResult result) {}
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = jarMap.getForConfiguration(kotlinParameters);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion, lambdaGeneration)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getAppSourceFile())
+            .compile();
+    testForRuntime(parameters)
+        .addProgramFiles(
+            libJar, output, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar())
+        .run(parameters.getRuntime(), getMainClass())
+        .assertSuccessWithOutput(getExpected());
+  }
+
+  @Test
+  public void testR8Library() throws Exception {
+    Path r8LibJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(kotlinc.getKotlinAnnotationJar())
+            .addProgramFiles(jarMap.getForConfiguration(kotlinParameters))
+            .addClasspathFiles(kotlinc.getKotlinStdlibJar())
+            .addKeepKotlinMetadata()
+            .apply(this::configure)
+            .compile()
+            .inspect(
+                inspector -> {
+                  ClassSubject libKtClass = inspector.clazz(getLibraryClass());
+                  assertThat(libKtClass, isPresentAndNotRenamed());
+                  libKtClass
+                      .allMethods()
+                      .forEach(method -> assertThat(method, isPresentAndNotRenamed()));
+                })
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), kotlinParameters)
+            .addClasspathFiles(r8LibJar)
+            .addSourceFiles(getAppSourceFile())
+            .setOutputPath(temp.newFolder().toPath())
+            .compile(kotlinCompilationFails(), this::kotlinCompilationResult);
+    if (!kotlinCompilationFails()) {
+      testForRuntime(parameters)
+          .addProgramFiles(
+              r8LibJar, output, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar())
+          .run(parameters.getRuntime(), getMainClass())
+          .assertSuccessWithOutput(getExpected());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/classlevel/app/main.kt b/src/test/java/com/android/tools/r8/kotlin/inline/classlevel/app/main.kt
new file mode 100644
index 0000000..56c6f86
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/classlevel/app/main.kt
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, 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.inline.classlevel.app
+
+import com.android.tools.r8.kotlin.inline.classlevel.lib.A
+
+fun main() {
+  A().f()
+  A().f { "Hello again, world!" }
+  A().g({ "Hello, " })
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/classlevel/lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/inline/classlevel/lib/lib.kt
new file mode 100644
index 0000000..e518075
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/classlevel/lib/lib.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.inline.classlevel.lib
+
+class A {
+  inline fun f() {
+    println("Hello, world!")
+  }
+
+  inline fun f(supplier: () -> String) {
+    println(supplier())
+  }
+
+  inline fun g(supplier1: () -> String, supplier2: () -> String = { "default" }) {
+    println(supplier1() + supplier2())
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/companion/app/main.kt b/src/test/java/com/android/tools/r8/kotlin/inline/companion/app/main.kt
new file mode 100644
index 0000000..53e49e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/companion/app/main.kt
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, 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.inline.companion.app
+
+import com.android.tools.r8.kotlin.inline.companion.lib.A
+
+fun main() {
+  A.f()
+  A.f { "Hello again, world!" }
+  A.g({ "Hello, " })
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/companion/lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/inline/companion/lib/lib.kt
new file mode 100644
index 0000000..c5b2445
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/companion/lib/lib.kt
@@ -0,0 +1,22 @@
+// Copyright (c) 2025, 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.inline.companion.lib
+
+class A {
+  fun f() {}
+
+  companion object {
+    inline fun f() {
+      println("Hello, world!")
+    }
+
+    inline fun f(supplier: () -> String) {
+      println(supplier())
+    }
+
+    inline fun g(supplier1: () -> String, supplier2: () -> String = { "default" }) {
+      println(supplier1() + supplier2())
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/filelevel/app/main.kt b/src/test/java/com/android/tools/r8/kotlin/inline/filelevel/app/main.kt
new file mode 100644
index 0000000..21b6391
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/filelevel/app/main.kt
@@ -0,0 +1,13 @@
+// Copyright (c) 2025, 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.inline.filelevel.app
+
+import com.android.tools.r8.kotlin.inline.filelevel.lib.f
+import com.android.tools.r8.kotlin.inline.filelevel.lib.g
+
+fun main() {
+  f()
+  f { "Hello again, world!" }
+  g({ "Hello, " })
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/inline/filelevel/lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/inline/filelevel/lib/lib.kt
new file mode 100644
index 0000000..c4d7767
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/inline/filelevel/lib/lib.kt
@@ -0,0 +1,16 @@
+// Copyright (c) 2025, 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.inline.filelevel.lib
+
+inline fun f() {
+  println("Hello, world!")
+}
+
+inline fun f(supplier: () -> String) {
+  println(supplier())
+}
+
+inline fun g(supplier1: () -> String, supplier2: () -> String = { "default" }) {
+  println(supplier1() + supplier2())
+}