Android tests: add reflect flavor

This commit is contained in:
Mikhail Bogdanov
2020-04-10 10:17:26 +02:00
parent 115cf4666e
commit b0e72f90f1
6 changed files with 114 additions and 64 deletions
@@ -62,15 +62,19 @@ android {
flavorDimensions "box"
productFlavors {
ktest0 {
common0 {
dimension "box"
}
ktest1 {
common1 {
dimension "box"
}
ktest2 {
common2 {
dimension "box"
}
reflect0 {
dimension "box"
}
}
@@ -80,9 +84,9 @@ android {
task jarTestFolders() {
println "Jar folders..."
new File("${projectDir}/libs/").listFiles().each { File file ->
if (file.isDirectory() && !file.name.equals("test")) {
if (file.isDirectory()) {
println "Jar '${file.name}' folder..."
ant.jar(basedir: "libs/${file.name}/", destfile: "libs/test/" + file.name + ".jar")
ant.jar(basedir: "libs/${file.name}/", destfile: "libs/" + file.name + ".jar")
}
}
}
@@ -92,11 +96,18 @@ tasks.withType(JavaCompile) {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['kotlin-test.jar', 'kotlin-stdlib.jar'])
androidTestImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
ktest0Implementation fileTree(dir: 'libs/test', include: ['libtest0.jar'])
ktest1Implementation fileTree(dir: 'libs/test', include: ['libtest1.jar'])
ktest2Implementation fileTree(dir: 'libs/test', include: ['libtest2.jar'])
android.applicationVariants.all { variant ->
variant.productFlavors.each {
def configuration = configurations.getByName(it.name + 'Implementation').name
add(configuration, project.fileTree(dir: 'libs', include: [it.name + ".jar"]))
if (it.name.startsWith("reflect")) {
add(configuration, project.fileTree(dir: 'libs', include: ['kotlin-reflect.jar']))
}
}
}
}
@@ -52,8 +52,8 @@ public class PathManager {
return getAndroidSdkRoot() + "/emulator";
}
public String getOutputForCompiledFiles(int index) {
return tmpFolder + "/libs/libtest" + index;
public String getOutputForCompiledFiles(String flavor) {
return tmpFolder + "/libs/" + flavor;
}
public String getLibsFolderInAndroidTmpFolder() {
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.*
@@ -31,22 +32,43 @@ import kotlin.test.assertTrue
data class ConfigurationKey(val kind: ConfigurationKind, val jdkKind: TestJdkKind, val configuration: String)
class CodegenTestsOnAndroidGenerator private constructor(private val pathManager: PathManager) {
private var writtenFilesCount = 0
private var currentModuleIndex = 1
private val pathFilter: String? = System.getProperties().getProperty("kotlin.test.android.path.filter")
private val pendingUnitTestGenerators = hashMapOf<Int, UnitTestFileWriter>()
private val pendingUnitTestGenerators = hashMapOf<String, UnitTestFileWriter>()
//keep it globally to avoid test grouping on TC
private val generatedTestNames = hashSetOf<String>()
fun getFlavorUnitTestFilePath(index: Int): String {
return pathManager.srcFolderInAndroidTmpFolder + "/androidTestKtest$index/java/" + testClassPackage.replace(
".",
"/"
) + "/" + testClassName + "$index.java"
private val COMMON = FlavorConfig("common", 3);
private val REFLECT = FlavorConfig("reflect", 1);
private val JVM8 = FlavorConfig("jvm8", 1);
private val JVM8REFLECT = FlavorConfig("reflectjvm8", 1);
class FlavorConfig(private val prefix: String, val limit: Int) {
private var writtenFilesCount = 0
fun printStatistics() {
println("FlavorTestCompiler: $prefix, generated file count: $writtenFilesCount")
}
fun getFlavorForNewFiles(newFilesCount: Int): String {
writtenFilesCount += newFilesCount
//2500 files per folder that would be used by flavor to avoid multidex usage,
// each folder would be jared by build.gradle script
val index = writtenFilesCount / 2500
return getFlavorName(index, prefix).also {
assertTrue("Please Add new flavor in build.gradle for $it") { index < limit }
}
}
private fun getFlavorName(index: Int, prefix: String): String {
return prefix + index
}
}
private fun prepareAndroidModuleAndGenerateTests(skipSdkDirWriting: Boolean) {
@@ -126,9 +148,15 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
holders.values.forEach {
it.writeFilesOnDisk()
}
COMMON.printStatistics()
REFLECT.printStatistics()
JVM8.printStatistics()
JVM8REFLECT.printStatistics()
}
internal inner class FilesWriter(
private val flavorConfig: FlavorConfig,
private val configuration: CompilerConfiguration
) {
private val rawFiles = arrayListOf<TestClassInfo>()
@@ -143,42 +171,40 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
}
fun writeFilesOnDisk() {
//don't convert to sam (identity lose)
@Suppress("ObjectLiteralToLambda")
val disposable = object : Disposable {
override fun dispose() {}
}
val disposable = Disposer.newDisposable()
val environment = KotlinCoreEnvironment.createForTests(
disposable,
configuration.copy().apply { put(CommonConfigurationKeys.MODULE_NAME, "android-module-" + currentModuleIndex++) },
EnvironmentConfigFiles.JVM_CONFIG_FILES
)
writeFiles(
rawFiles.map {
try {
CodegenTestFiles.create(it.name, it.content, environment.project).psiFile
} catch (e: Throwable) {
throw RuntimeException("Error on processing ${it.name}:\n${it.content}", e)
}
}, environment
)
Disposer.dispose(disposable)
rawFiles.clear()
unitTestDescriptions.clear()
try {
writeFiles(
rawFiles.map {
try {
CodegenTestFiles.create(it.name, it.content, environment.project).psiFile
} catch (e: Throwable) {
throw RuntimeException("Error on processing ${it.name}:\n${it.content}", e)
}
}, environment, unitTestDescriptions
)
} finally {
rawFiles.clear()
unitTestDescriptions.clear()
Disposer.dispose(disposable)
}
}
private fun writeFiles(filesToCompile: List<KtFile>, environment: KotlinCoreEnvironment) {
private fun writeFiles(
filesToCompile: List<KtFile>,
environment: KotlinCoreEnvironment,
unitTestDescriptions: ArrayList<TestInfo>
) {
if (filesToCompile.isEmpty()) return
//2500 files per folder that would be used by flavor to avoid multidex usage,
// each folder would be jared by build.gradle script
writtenFilesCount += filesToCompile.size
val index = writtenFilesCount / 2500
val outputDir = File(pathManager.getOutputForCompiledFiles(index))
assertTrue("Add flavors for ktest$index") { index < 3 }
val flavorName = flavorConfig.getFlavorForNewFiles(filesToCompile.size)
val outputDir = File(pathManager.getOutputForCompiledFiles(flavorName))
println("Generating ${filesToCompile.size} files into ${outputDir.name}, configuration: '${environment.configuration}'...")
val outputFiles = GenerationUtils.compileFiles(filesToCompile, environment).run { destroy(); factory }
@@ -187,10 +213,10 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
outputDir.mkdirs()
}
Assert.assertTrue("Cannot create directory for compiled files", outputDir.exists())
val unitTestFileWriter = pendingUnitTestGenerators.getOrPut(index) {
val unitTestFileWriter = pendingUnitTestGenerators.getOrPut(flavorName) {
UnitTestFileWriter(
getFlavorUnitTestFilePath(index),
index,
getFlavorUnitTestFolder(flavorName),
flavorName,
generatedTestNames
)
}
@@ -198,6 +224,12 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
outputFiles.writeAllTo(outputDir)
}
private fun getFlavorUnitTestFolder(flavourName: String): String {
return pathManager.srcFolderInAndroidTmpFolder +
"/androidTest${flavourName.capitalize()}/java/" +
testClassPackage.replace(".", "/") + "/"
}
fun addTest(testFiles: List<TestClassInfo>, info: TestInfo) {
rawFiles.addAll(testFiles)
unitTestDescriptions.add(info)
@@ -245,8 +277,14 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
if (fullFileText.contains("@file:JvmPackageName(")) continue
// TODO: Support jvm assertions
if (fullFileText.contains("// KOTLIN_CONFIGURATION_FLAGS: ASSERTIONS_MODE=jvm")) continue
// TODO: support JVM 8 test with D8
if (fullFileText.contains("// JVM_TARGET")) continue
val targets = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fullFileText, "// JVM_TARGET:")
.also { it.remove(JvmTarget.JVM_1_6.description) }
val isJvm8Target =
if (targets.isEmpty()) false
else if (targets.contains(JvmTarget.JVM_1_8.description) && targets.size == 1) true
else continue //TODO: support other targets on Android
// TODO: support SKIP_JDK6 on new platforms
if (fullFileText.contains("// SKIP_JDK6")) continue
@@ -258,8 +296,11 @@ class CodegenTestsOnAndroidGenerator private constructor(private val pathManager
CodegenTestCase.updateConfigurationByDirectivesInTestFiles(testFiles, keyConfiguration)
val key = ConfigurationKey(kind, jdkKind, keyConfiguration.toString())
val compiler = if (isJvm8Target) {
if (kind.withReflection) JVM8REFLECT else JVM8
} else if (kind.withReflection) REFLECT else COMMON
val filesHolder = holders.getOrPut(key) {
FilesWriter(KotlinTestUtils.newConfiguration(kind, jdkKind, KotlinTestUtils.getAnnotationsJar()).apply {
FilesWriter(compiler, KotlinTestUtils.newConfiguration(kind, jdkKind, KotlinTestUtils.getAnnotationsJar()).apply {
println("Creating new configuration by $key")
CodegenTestCase.updateConfigurationByDirectivesInTestFiles(testFiles, this)
})
@@ -40,13 +40,6 @@ public class SpecialFiles {
excludedFiles.add("nativePropertyAccessors.kt");
excludedFiles.add("topLevel.kt");
//Test with no reflection at runtime
excludedFiles.add("noReflectAtRuntime");
excludedFiles.add("noReflect");
excludedFiles.add("functionNtoStringNoReflect.kt");
excludedFiles.add("getDelegateWithoutReflection.kt");
excludedFiles.add("toStringNoReflect.kt");
// "IOOBE: Invalid index 4, size is 4" for java.lang.reflect.ParameterizedType on Android
excludedFiles.add("innerGenericTypeArgument.kt");
@@ -15,7 +15,11 @@ import java.io.FileWriter
class TestInfo(val name: String, val fqName: FqName, val file: File)
class UnitTestFileWriter(private val fileName: String, private val index: Int, private val generatedTestNames: MutableSet<String>) {
class UnitTestFileWriter(
private val flavourFolder: String,
private val flavourName: String,
private val generatedTestNames: MutableSet<String>
) {
private val infos = arrayListOf<TestInfo>()
fun addTests(info: List<TestInfo>) {
@@ -23,7 +27,7 @@ class UnitTestFileWriter(private val fileName: String, private val index: Int, p
}
fun generate() {
FileWriter(File(fileName).also { it.parentFile.mkdirs() }).use { suite ->
FileWriter(File(flavourFolder, flavourName.capitalize() + ".java").also { it.parentFile.mkdirs() }).use { suite ->
val p = Printer(suite)
p.println(
"""package ${CodegenTestsOnAndroidGenerator.testClassPackage};
@@ -31,7 +35,7 @@ class UnitTestFileWriter(private val fileName: String, private val index: Int, p
|import ${CodegenTestsOnAndroidGenerator.baseTestClassPackage}.${CodegenTestsOnAndroidGenerator.baseTestClassName};
|
|/* This class is generated by ${CodegenTestsOnAndroidGenerator.generatorName}. DO NOT MODIFY MANUALLY */
|public class ${CodegenTestsOnAndroidGenerator.testClassName}$index extends ${CodegenTestsOnAndroidGenerator.baseTestClassName} {
|public class ${flavourName.capitalize()} extends ${CodegenTestsOnAndroidGenerator.baseTestClassName} {
|
""".trimMargin()
)
@@ -1,6 +1,7 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND_FIR: JVM_IR
// WITH_RUNTIME
package test
import kotlin.test.assertEquals
@@ -18,10 +19,10 @@ fun defaultAndVararg(f: (A) -> CharSequence): Any = f
fun allOfTheAbove(f: (A) -> Unit): Any = f
fun box(): String {
check("Function3<A, java.lang.String, long[], kotlin.Unit>", coercionToUnit(A::foo))
check("Function4<A, java.lang.String, java.lang.Long, java.lang.Long, java.lang.CharSequence>", varargToElement(A::foo))
check("Function1<A, java.lang.CharSequence>", defaultAndVararg(A::foo))
check("Function1<A, kotlin.Unit>", allOfTheAbove(A::foo))
check("Function3<test.A, java.lang.String, long[], kotlin.Unit>", coercionToUnit(A::foo))
check("Function4<test.A, java.lang.String, java.lang.Long, java.lang.Long, java.lang.CharSequence>", varargToElement(A::foo))
check("Function1<test.A, java.lang.CharSequence>", defaultAndVararg(A::foo))
check("Function1<test.A, kotlin.Unit>", allOfTheAbove(A::foo))
return "OK"
}