blob: a976fad3386a833fa25962f3ee8caea383972ead [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.runtime.reflect
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composer
import androidx.compose.runtime.Composition
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.mock.EmptyApplier
import androidx.compose.runtime.withRunningRecomposer
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlinx.coroutines.runBlocking
@Composable
private fun composableFunction() {
}
private fun nonComposableFunction() {
}
@Suppress("UNUSED_PARAMETER")
private fun nonComposableFunctionWithComposerParam(unused: Composer) {
}
@Composable
private fun composableFunctionWithDefaults(
s1: String,
s2: String,
s3: String = "a",
s4: String = "a",
s5: String = "a"
): String { return s1 + s2 + s3 + s4 + s5 }
@Composable
private fun overloadedComposable() {
}
@Suppress("UNUSED_PARAMETER")
@Composable
private fun overloadedComposable(s: String) {
}
@Suppress("UNUSED_PARAMETER")
@Composable
private fun overloadedComposable(
v1: String,
v2: String,
v3: String,
v4: String,
v5: String,
v6: String,
v7: String,
v8: String,
v9: String,
v10: String
) { }
@Suppress("UNUSED_PARAMETER")
@Composable
private fun overloadedComposable(
v1: String,
v2: String,
v3: String,
v4: String,
v5: String,
v6: String,
v7: String,
v8: String,
v9: String,
v10: String,
v11: String
) { }
@Suppress("UNUSED_PARAMETER")
@Composable
private fun overloadedComposable(
v1: String,
v2: String,
v3: String,
v4: String,
v5: String,
v6: String,
v7: String,
v8: String,
v9: String,
v10: String,
v11: String,
v12: String
) { }
@Suppress("UNUSED_PARAMETER")
@Composable
private fun differentParametersTypes(
v1: String,
v2: Any,
v3: Int,
v4: Float,
v5: Double,
v6: Long
) { }
private class ComposablesWrapper {
@Composable
fun composableMethod() {
}
fun nonComposableMethod() {
}
@Suppress("UNUSED_PARAMETER")
fun nonComposableMethodWithComposerParam(unused: Composer) {
}
@Composable
fun composableMethodWithDefaults(
s1: String,
s2: String,
s3: String = "a",
s4: String = "a",
s5: String = "a"
): String { return s1 + s2 + s3 + s4 + s5 }
@Composable
fun overloadedComposableMethod() {
}
@Suppress("UNUSED_PARAMETER")
@Composable
fun overloadedComposableMethod(s: String) {
}
@Suppress("UNUSED_PARAMETER")
@Composable
fun overloadedComposableMethod(
v1: String,
v2: String,
v3: String,
v4: String,
v5: String,
v6: String,
v7: String,
v8: String,
v9: String,
v10: String
) { }
@Suppress("UNUSED_PARAMETER")
@Composable
fun overloadedComposableMethod(
v1: String,
v2: String,
v3: String,
v4: String,
v5: String,
v6: String,
v7: String,
v8: String,
v9: String,
v10: String,
v11: String
) { }
@Suppress("UNUSED_PARAMETER")
@Composable
fun overloadedComposableMethod(
v1: String,
v2: String,
v3: String,
v4: String,
v5: String,
v6: String,
v7: String,
v8: String,
v9: String,
v10: String,
v11: String,
v12: String
) { }
@Suppress("UNUSED_PARAMETER")
@Composable
fun differentParametersTypesMethod(
v1: String,
v2: Any,
v3: Int,
v4: Float,
v5: Double,
v6: Long
) { }
}
class ComposableMethodInvokerTest {
private val clazz =
Class.forName("androidx.compose.runtime.reflect.ComposableMethodInvokerTestKt")
private val wrapperClazz =
Class.forName("androidx.compose.runtime.reflect.ComposablesWrapper")
private val composable = clazz.declaredMethods.find { it.name == "composableFunction" }!!
private val nonComposable = clazz.declaredMethods.find { it.name == "nonComposableFunction" }!!
private val nonComposableWithComposer =
clazz.declaredMethods.find { it.name == "nonComposableFunctionWithComposerParam" }!!
private val composableMethod =
wrapperClazz.declaredMethods.find { it.name == "composableMethod" }!!
private val nonComposableMethod =
wrapperClazz.declaredMethods.find { it.name == "nonComposableMethod" }!!
private val nonComposableMethodWithComposer =
wrapperClazz.declaredMethods.find { it.name == "nonComposableMethodWithComposerParam" }!!
@Test
fun test_isComposable_correctly_checks_functions() {
assertTrue(composable.isComposable)
assertFalse(nonComposable.isComposable)
assertFalse(nonComposableWithComposer.isComposable)
assertTrue(composableMethod.isComposable)
assertFalse(nonComposableMethod.isComposable)
assertFalse(nonComposableMethodWithComposer.isComposable)
}
@Throws(NoSuchMethodException::class)
@Test
fun test_getDeclaredComposableMethod_differentiates_overloaded_functions() {
val method0 = clazz.getDeclaredComposableMethod("overloadedComposable")
val method1 = clazz.getDeclaredComposableMethod("overloadedComposable", String::class.java)
val method10 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(10) { String::class.java }
)
val method11 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(11) { String::class.java }
)
val method12 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(12) { String::class.java }
)
assertNotEquals(method0, method1)
assertNotEquals(method0, method10)
assertNotEquals(method0, method11)
assertNotEquals(method0, method12)
assertNotEquals(method1, method10)
assertNotEquals(method1, method11)
assertNotEquals(method1, method12)
assertNotEquals(method10, method11)
assertNotEquals(method10, method12)
assertNotEquals(method11, method12)
}
@Throws(NoSuchMethodException::class)
@Test
fun test_getDeclaredComposableMethod_differentiates_overloaded_methods() {
val method0 = wrapperClazz.getDeclaredComposableMethod("overloadedComposableMethod")
val method1 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
String::class.java
)
val method10 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(10) { String::class.java }
)
val method11 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(11) { String::class.java }
)
val method12 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(12) { String::class.java }
)
assertNotEquals(method0, method1)
assertNotEquals(method0, method10)
assertNotEquals(method0, method11)
assertNotEquals(method0, method12)
assertNotEquals(method1, method10)
assertNotEquals(method1, method11)
assertNotEquals(method1, method12)
assertNotEquals(method10, method11)
assertNotEquals(method10, method12)
assertNotEquals(method11, method12)
}
@Throws(NoSuchMethodException::class)
@Test
fun test_getDeclaredComposableMethod_works_with_default_params() {
clazz.getDeclaredComposableMethod(
"composableFunctionWithDefaults",
*Array(5) { String::class.java }
)
wrapperClazz.getDeclaredComposableMethod(
"composableMethodWithDefaults",
*Array(5) { String::class.java }
)
}
@Throws(NoSuchMethodException::class)
@Test
fun test_realParametersCount_returns_correct_number_of_parameters() {
val function0 = clazz.getDeclaredComposableMethod("overloadedComposable")
val function1 =
clazz.getDeclaredComposableMethod("overloadedComposable", String::class.java)
val function10 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(10) { String::class.java }
)
val function11 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(11) { String::class.java }
)
val function12 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(12) { String::class.java }
)
val method0 = wrapperClazz.getDeclaredComposableMethod("overloadedComposableMethod")
val method1 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod", String::class.java
)
val method10 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(10) { String::class.java }
)
val method11 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(11) { String::class.java }
)
val method12 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(12) { String::class.java }
)
assertEquals(0, function0.realParametersCount)
assertEquals(1, function1.realParametersCount)
assertEquals(10, function10.realParametersCount)
assertEquals(11, function11.realParametersCount)
assertEquals(12, function12.realParametersCount)
assertEquals(0, method0.realParametersCount)
assertEquals(1, method1.realParametersCount)
assertEquals(10, method10.realParametersCount)
assertEquals(11, method11.realParametersCount)
assertEquals(12, method12.realParametersCount)
assertEquals(0, nonComposable.realParametersCount)
assertEquals(1, nonComposableWithComposer.realParametersCount)
assertEquals(0, composableMethod.realParametersCount)
assertEquals(0, nonComposableMethod.realParametersCount)
assertEquals(1, nonComposableMethodWithComposer.realParametersCount)
}
@Suppress("ClassVerificationFailure", "NewApi")
@Throws(NoSuchMethodException::class)
@Test
fun test_realParameters_returns_correct_parameters() {
val function0 = clazz.getDeclaredComposableMethod("overloadedComposable")
val function1 =
clazz.getDeclaredComposableMethod("overloadedComposable", String::class.java)
val function10 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(10) { String::class.java }
)
val function11 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(11) { String::class.java }
)
val function12 =
clazz.getDeclaredComposableMethod(
"overloadedComposable",
*Array(12) { String::class.java }
)
val method0 = wrapperClazz.getDeclaredComposableMethod("overloadedComposableMethod")
val method1 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
String::class.java
)
val method10 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(10) { String::class.java }
)
val method11 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(11) { String::class.java }
)
val method12 =
wrapperClazz.getDeclaredComposableMethod(
"overloadedComposableMethod",
*Array(12) { String::class.java }
)
val diffParameters =
clazz.getDeclaredComposableMethod(
"differentParametersTypes",
String::class.java,
Any::class.java,
Int::class.java,
Float::class.java,
Double::class.java,
Long::class.java
)
val diffParametersMethod =
wrapperClazz.getDeclaredComposableMethod(
"differentParametersTypesMethod",
String::class.java,
Any::class.java,
Int::class.java,
Float::class.java,
Double::class.java,
Long::class.java
)
assertEquals(0, function0.realParameters.size)
assertEquals(1, function1.realParameters.size)
assertEquals(10, function10.realParameters.size)
assertEquals(11, function11.realParameters.size)
assertEquals(12, function12.realParameters.size)
assertEquals(12, function12.realParameters.size)
assertEquals(0, method0.realParameters.size)
assertEquals(1, method1.realParameters.size)
assertEquals(10, method10.realParameters.size)
assertEquals(11, method11.realParameters.size)
assertEquals(12, method12.realParameters.size)
assertEquals(0, nonComposable.realParameters.size)
assertEquals(1, nonComposableWithComposer.realParameters.size)
assertEquals(0, composableMethod.realParameters.size)
assertEquals(0, nonComposableMethod.realParameters.size)
assertEquals(1, nonComposableMethodWithComposer.realParameters.size)
assertEquals(6, diffParameters.realParameters.size)
assertEquals(
listOf(String::class.java, Any::class.java, Int::class.java, Float::class.java,
Double::class.java, Long::class.java),
diffParameters.realParameters.map { it.type })
assertEquals(6, diffParametersMethod.realParameters.size)
assertEquals(
listOf(String::class.java, Any::class.java, Int::class.java, Float::class.java,
Double::class.java, Long::class.java),
diffParametersMethod.realParameters.map { it.type })
}
private class TestFrameClock : MonotonicFrameClock {
override suspend fun <R> withFrameNanos(onFrame: (Long) -> R): R = onFrame(0L)
}
private fun <T> executeWithComposer(block: (composer: Composer) -> T): T =
runBlocking(TestFrameClock()) {
fun compose(
recomposer: Recomposer,
block: @Composable () -> Unit
): Composition {
return Composition(
EmptyApplier(),
recomposer
).apply {
setContent(block)
}
}
var res: T? = null
withRunningRecomposer { r ->
compose(r) {
res = block(currentComposer)
}
}
res!!
}
@Test
fun testInvokeComposableFunctions() {
val composableWithDefaults =
clazz.declaredMethods.find { it.name == "composableFunctionWithDefaults" }!!
composableWithDefaults.isAccessible = true
val resABAAA = executeWithComposer {
composableWithDefaults.invokeComposable(it, null, "a", "b") as String
}
val resABCAA = executeWithComposer {
composableWithDefaults.invokeComposable(it, null, "a", "b", "c") as String
}
val resABCDA = executeWithComposer {
composableWithDefaults.invokeComposable(it, null, "a", "b", "c", "d") as String
}
val resABCDE = executeWithComposer {
composableWithDefaults.invokeComposable(it, null, "a", "b", "c", "d", "e") as String
}
val resABADA = executeWithComposer {
composableWithDefaults.invokeComposable(it, null, "a", "b", null, "d") as String
}
assertEquals("abaaa", resABAAA)
assertEquals("abcaa", resABCAA)
assertEquals("abcda", resABCDA)
assertEquals("abcde", resABCDE)
assertEquals("abada", resABADA)
}
@Test
fun testInvokeComposableMethods() {
val composableWithDefaults =
wrapperClazz.declaredMethods.find { it.name == "composableMethodWithDefaults" }!!
composableWithDefaults.isAccessible = true
val instance = ComposablesWrapper()
val resABAAA = executeWithComposer {
composableWithDefaults.invokeComposable(it, instance, "a", "b") as String
}
val resABCAA = executeWithComposer {
composableWithDefaults.invokeComposable(it, instance, "a", "b", "c") as String
}
val resABCDA = executeWithComposer {
composableWithDefaults.invokeComposable(it, instance, "a", "b", "c", "d") as String
}
val resABCDE = executeWithComposer {
composableWithDefaults
.invokeComposable(it, instance, "a", "b", "c", "d", "e") as String
}
val resABADA = executeWithComposer {
composableWithDefaults.invokeComposable(it, instance, "a", "b", null, "d") as String
}
assertEquals("abaaa", resABAAA)
assertEquals("abcaa", resABCAA)
assertEquals("abcda", resABCDA)
assertEquals("abcde", resABCDE)
assertEquals("abada", resABADA)
}
}