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