Merge "Move `generateAllEnumerations` to testutils-common" into androidx-main
diff --git a/room/benchmark/build.gradle b/room/benchmark/build.gradle
index b985390..c2f2aa9 100644
--- a/room/benchmark/build.gradle
+++ b/room/benchmark/build.gradle
@@ -37,4 +37,5 @@
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.kotlinStdlib)
+    androidTestImplementation(project(":internal-testutils-common"))
 }
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
index 861da78..e3552e1 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
@@ -31,6 +31,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
+import androidx.testutils.generateAllEnumerations
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -103,19 +104,15 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "sampleSize={0}, mode={1}")
-        fun data(): List<Array<Any>> {
-            return mutableListOf<Array<Any>>().apply {
-                arrayOf(
+        fun data(): List<Array<Any>> =
+            generateAllEnumerations(
+                listOf(100, 1000, 5000, 10000),
+                listOf(
                     Mode.MEASURE_INSERT,
                     Mode.MEASURE_DELETE,
                     Mode.MEASURE_INSERT_AND_DELETE
-                ).forEach { mode ->
-                    arrayOf(100, 1000, 5000, 10000).forEach { sampleSize ->
-                        add(arrayOf(sampleSize, mode))
-                    }
-                }
-            }
-        }
+                )
+            )
 
         private const val DB_NAME = "invalidation-benchmark-test"
     }
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt
index 1000c14..d9ec441 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/RelationBenchmark.kt
@@ -32,6 +32,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
+import androidx.testutils.generateAllEnumerations
 import org.junit.Assert
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -89,11 +90,7 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "parentSampleSize={0}, childSampleSize={1}")
-        fun data() = arrayOf(100, 500, 1000).flatMap { parentSampleSize ->
-            arrayOf(10).map { childSampleSize ->
-                arrayOf(parentSampleSize, childSampleSize)
-            }
-        }
+        fun data() = generateAllEnumerations(listOf(100, 500, 1000), listOf(10))
 
         private const val DB_NAME = "relation-benchmark-test"
     }
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index d63f0af..95225c5 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -42,6 +42,7 @@
     testImplementation(libs.jsr250)
     testImplementation(libs.ksp)
     testImplementation(project(":room:room-compiler-processing-testing"))
+    testImplementation(project(":internal-testutils-common"))
 }
 
 tasks.withType(KotlinCompile).configureEach {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 4cc1736..f9e64b5 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -21,7 +21,7 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.generateAllEnumerations
+import androidx.testutils.generateAllEnumerations
 import androidx.room.compiler.processing.util.javaTypeUtils
 import androidx.room.compiler.processing.util.runKaptTest
 import androidx.room.compiler.processing.util.runProcessorTest
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/ParameterizedHelper.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/ParameterizedHelper.kt
deleted file mode 100644
index 35161e7..0000000
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/ParameterizedHelper.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2021 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.room.compiler.processing.util
-
-/**
- * Used to generate all argument enumerations for Parameterized tests.
- * See [ParameterizedHelperTest] for usage.
- */
-fun generateAllEnumerations(vararg args: List<Any?>): List<Array<Any?>> =
-    when (args.size) {
-        0 -> emptyList()
-        1 -> args[0].map {
-            arrayOf(it)
-        }
-        else -> generateAllEnumerations(
-            *args.dropLast(1).toTypedArray()
-        ).flatMap { prev ->
-            args.last().map { arg ->
-                prev + arg
-            }
-        }
-    }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/ParameterizedHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/ParameterizedHelperTest.kt
deleted file mode 100644
index a534f52..0000000
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/ParameterizedHelperTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2021 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.room.compiler.processing.util
-
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-class ParameterizedHelperTest {
-    @Test
-    fun testEnumerations() {
-        assertThat(generateAllEnumerations()).isEmpty()
-
-        // Comparing List of Arrays doesn't work(https://github.com/google/truth/issues/928), so
-        // we're mapping it to List of Lists
-        assertThat(
-            generateAllEnumerations(
-                listOf(false, true)
-            ).map { it.toList() }).isEqualTo(
-            listOf(
-                listOf<Any>(false), listOf<Any>(true)
-            )
-        )
-        assertThat(generateAllEnumerations(listOf(false, true), listOf())).isEmpty()
-        assertThat(
-            generateAllEnumerations(
-                listOf(false, true),
-                listOf(false, true)
-            ).map { it.toList() }
-        ).isEqualTo(
-            listOf(
-                listOf(false, false), listOf(false, true),
-                listOf(true, false), listOf(true, true)
-            )
-        )
-        assertThat(
-            generateAllEnumerations(
-                listOf(false, true),
-                (0..2).toList(),
-                listOf("low", "hi")
-            ).map { it.toList() }
-        ).isEqualTo(
-            listOf(
-                listOf(false, 0, "low"),
-                listOf(false, 0, "hi"),
-                listOf(false, 1, "low"),
-                listOf(false, 1, "hi"),
-                listOf(false, 2, "low"),
-                listOf(false, 2, "hi"),
-                listOf(true, 0, "low"),
-                listOf(true, 0, "hi"),
-                listOf(true, 1, "low"),
-                listOf(true, 1, "hi"),
-                listOf(true, 2, "low"),
-                listOf(true, 2, "hi")
-            )
-        )
-    }
-}
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index 0da469d6..0d266f3 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -122,6 +122,7 @@
             dir: "${new File(project(":sqlite:sqlite").buildDir, "libJar")}",
             include : "*.jar"
     ))
+    testImplementation(project(":internal-testutils-common"))
 }
 
 def generateAntlrTask = task("generateAntlrGrammar", type: GenerateAntlrGrammar) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
index 20772ab..a8b459a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
@@ -22,6 +22,7 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.testing.asTestInvocationHandler
+import androidx.testutils.generateAllEnumerations
 import loadTestSource
 import org.junit.Test
 import org.junit.experimental.runners.Enclosed
@@ -123,15 +124,10 @@
         companion object {
             @Parameterized.Parameters(name = "(maxStatementCount, valuesPerEntity)={0}")
             @JvmStatic
-            fun getParams(): List<Pair<Int, Int>> {
-                val result = arrayListOf<Pair<Int, Int>>()
-                arrayListOf(500, 1000, 3000).forEach { maxStatementCount ->
-                    arrayListOf(50, 100, 200).forEach { valuesPerEntity ->
-                        result.add(maxStatementCount to valuesPerEntity)
-                    }
+            fun getParams(): List<Pair<Int, Int>> =
+                generateAllEnumerations(listOf(500, 1000, 3000), listOf(50, 100, 200)).map {
+                    it[0] as Int to it[1] as Int
                 }
-                return result
-            }
         }
     }
 }
diff --git a/testutils/testutils-common/build.gradle b/testutils/testutils-common/build.gradle
index 19480c5..6090eaf 100644
--- a/testutils/testutils-common/build.gradle
+++ b/testutils/testutils-common/build.gradle
@@ -26,6 +26,9 @@
 dependencies {
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesAndroid)
+
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
 }
 
 // Allow usage of Kotlin's @OptIn.
diff --git a/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt b/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt
new file mode 100644
index 0000000..270b30c
--- /dev/null
+++ b/testutils/testutils-common/src/main/java/androidx/testutils/ParameterizedHelper.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2021 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.testutils
+
+/**
+ * Generate all argument enumerations for Parameterized tests. For example,
+ * `generateAllEnumerations(listOf(false, true), listOf(1, 2, 3))` would return:
+ *
+ * ```
+ * [
+ *   [false, 1],
+ *   [false, 2],
+ *   [false, 3],
+ *   [true, 1],
+ *   [true, 2],
+ *   [true, 3]
+ * ]
+ * ```
+ *
+ * See [ParameterizedHelperTest] for more examples.
+ */
+fun generateAllEnumerations(vararg args: List<Any>): List<Array<Any>> =
+    generateAllEnumerationsIteratively(args.toList()).map { it.toTypedArray() }
+
+internal fun generateAllEnumerationsIteratively(elements: List<List<Any>>): List<List<Any>> {
+    if (elements.isEmpty()) return emptyList()
+    var number = elements.map { RadixDigit(it.size, 0) }
+    val total = elements.map { it.size }.product()
+    val result = mutableListOf<List<Any>>()
+    for (i in 0 until total) {
+        result.add(elements.mapIndexed { index, element -> element[number[index].digit] })
+        number = increment(number)
+    }
+    return result
+}
+
+internal fun increment(number: List<RadixDigit>): List<RadixDigit> {
+    var index = number.size - 1
+    var carry = 1
+    val result = mutableListOf<RadixDigit>()
+    while (index >= 0) {
+        val rd = number[index]
+        if (carry > 0) {
+            if (rd.digit < rd.radix - 1) {
+                result.add(rd.copy(digit = rd.digit + 1))
+                carry = 0
+            } else {
+                result.add(rd.copy(digit = 0))
+            }
+        } else {
+            result.add(rd)
+        }
+        index--
+    }
+    return result.reversed()
+}
+
+internal fun List<Int>.product() = this.fold(1) { acc, elem -> acc * elem }
+
+internal data class RadixDigit(val radix: Int, val digit: Int)
\ No newline at end of file
diff --git a/testutils/testutils-common/src/test/java/androidx/testutils/ParameterizedHelperTest.kt b/testutils/testutils-common/src/test/java/androidx/testutils/ParameterizedHelperTest.kt
new file mode 100644
index 0000000..43fc951e
--- /dev/null
+++ b/testutils/testutils-common/src/test/java/androidx/testutils/ParameterizedHelperTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2021 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.testutils
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ParameterizedHelperTest {
+    @Test
+    fun testIncrement() {
+        val number = listOf(RadixDigit(2, 0), RadixDigit(3, 0))
+
+        assertThat(::increment.invoke(number, 1)).isEqualTo(
+            listOf(RadixDigit(2, 0), RadixDigit(3, 1))
+        )
+        assertThat(::increment.invoke(number, 2)).isEqualTo(
+            listOf(RadixDigit(2, 0), RadixDigit(3, 2))
+        )
+        assertThat(::increment.invoke(number, 3)).isEqualTo(
+            listOf(RadixDigit(2, 1), RadixDigit(3, 0))
+        )
+        assertThat(::increment.invoke(number, 4)).isEqualTo(
+            listOf(RadixDigit(2, 1), RadixDigit(3, 1))
+        )
+        assertThat(::increment.invoke(number, 5)).isEqualTo(
+            listOf(RadixDigit(2, 1), RadixDigit(3, 2))
+        )
+        assertThat(::increment.invoke(number, 6)).isEqualTo(
+            listOf(RadixDigit(2, 0), RadixDigit(3, 0))
+        )
+        assertThat(::increment.invoke(number, 7)).isEqualTo(
+            listOf(RadixDigit(2, 0), RadixDigit(3, 1))
+        )
+    }
+
+    @Test
+    fun testProduct() {
+        assertThat(listOf<Int>().product()).isEqualTo(1)
+        assertThat(listOf(0).product()).isEqualTo(0)
+        assertThat(listOf(2).product()).isEqualTo(2)
+        assertThat(listOf(2, 3).product()).isEqualTo(6)
+    }
+
+    @Test
+    fun testEnumerations() {
+        assertThat(generateAllEnumerations()).isEmpty()
+
+        // Comparing List of Arrays doesn't work(https://github.com/google/truth/issues/928), so
+        // we're mapping it to List of Lists
+        assertThat(
+            generateAllEnumerations(listOf(false)).map { it.toList() }).isEqualTo(
+            listOf(
+                listOf<Any>(false)
+            )
+        )
+        assertThat(
+            generateAllEnumerations(listOf(false, true)).map { it.toList() }).isEqualTo(
+            listOf(
+                listOf<Any>(false),
+                listOf<Any>(true)
+            )
+        )
+        assertThat(generateAllEnumerations(listOf(false, true), listOf())).isEmpty()
+        assertThat(
+            generateAllEnumerations(
+                listOf(false, true),
+                listOf(false, true)
+            ).map { it.toList() }
+        ).isEqualTo(
+            listOf(
+                listOf(false, false),
+                listOf(false, true),
+                listOf(true, false),
+                listOf(true, true)
+            )
+        )
+        assertThat(
+            generateAllEnumerations(
+                listOf(false, true),
+                (0..2).toList(),
+                listOf("low", "hi")
+            ).map { it.toList() }
+        ).isEqualTo(
+            listOf(
+                listOf(false, 0, "low"),
+                listOf(false, 0, "hi"),
+                listOf(false, 1, "low"),
+                listOf(false, 1, "hi"),
+                listOf(false, 2, "low"),
+                listOf(false, 2, "hi"),
+                listOf(true, 0, "low"),
+                listOf(true, 0, "hi"),
+                listOf(true, 1, "low"),
+                listOf(true, 1, "hi"),
+                listOf(true, 2, "low"),
+                listOf(true, 2, "hi")
+            )
+        )
+    }
+
+    // `::f.invoke(0, 3)` is equivalent to `f(f(f(0)))`
+    private fun <T> ((T) -> T).invoke(argument: T, repeat: Int): T {
+        var result = argument
+        for (i in 0 until repeat) {
+            result = this(result)
+        }
+        return result
+    }
+
+    @Test
+    fun testInvoke() {
+        val addOne = { i: Int -> i + 1 }
+        assertThat(addOne.invoke(42, 0)).isEqualTo(42)
+        assertThat(addOne.invoke(42, 1)).isEqualTo(addOne(42))
+        assertThat(addOne.invoke(42, 2)).isEqualTo(addOne(addOne(42)))
+
+        val appendA = { str: String -> str + "a" }
+        assertThat(appendA.invoke("a", 2)).isEqualTo(appendA(appendA("a")))
+    }
+}