Android tests: add reflect flavor
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
+80
-39
@@ -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");
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -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()
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-4
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user