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())
+}