[Native][tests] Dump the list of all available tests during compilation
^KT-50316
This commit is contained in:
@@ -386,6 +386,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
|
||||
putIfNotNull(RUNTIME_LOGS, arguments.runtimeLogs)
|
||||
putIfNotNull(BUNDLE_ID, parseBundleId(arguments, outputKind, configuration))
|
||||
put(MEANINGFUL_BRIDGE_NAMES, arguments.meaningfulBridgeNames)
|
||||
arguments.testDumpOutputPath?.let { put(TEST_DUMP_OUTPUT_PATH, it) }
|
||||
put(PARTIAL_LINKAGE, arguments.partialLinkage)
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -361,6 +361,13 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
|
||||
@Argument(value = "-Xmeaningful-bridge-names", description = "(Unstable) Produce meaningful names for reverse bridges. Useful only for compiler tests.")
|
||||
var meaningfulBridgeNames: Boolean = false
|
||||
|
||||
@Argument(
|
||||
value = "-Xdump-tests-to",
|
||||
valueDescription = "<path>",
|
||||
description = "Path to a file to dump the list of all available tests"
|
||||
)
|
||||
var testDumpOutputPath: String? = null
|
||||
|
||||
@Argument(value = "-Xlazy-ir-for-caches", valueDescription = "{disable|enable}", description = "Use lazy IR for cached libraries")
|
||||
var lazyIrForCaches: String? = null
|
||||
|
||||
|
||||
+4
@@ -57,6 +57,7 @@ import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.library.SerializedIrModule
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
|
||||
internal class InlineFunctionOriginInfo(val irFunction: IrFunction, val irFile: IrFile, val startOffset: Int, val endOffset: Int)
|
||||
|
||||
@@ -290,6 +291,9 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
|
||||
getLocalClassName(source)?.let { name -> putLocalClassName(destination, name) }
|
||||
}
|
||||
|
||||
/* test suite class -> test function names */
|
||||
val testCasesToDump = mutableMapOf<ClassId, MutableCollection<String>>()
|
||||
|
||||
val reflectionTypes: KonanReflectionTypes by lazy(PUBLICATION) {
|
||||
KonanReflectionTypes(moduleDescriptor)
|
||||
}
|
||||
|
||||
+2
@@ -327,6 +327,8 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
|
||||
internal val unitSuspendFunctionObjCExport: UnitSuspendFunctionObjCExport
|
||||
get() = configuration.get(BinaryOptions.unitSuspendFunctionObjCExport) ?: UnitSuspendFunctionObjCExport.LEGACY
|
||||
|
||||
internal val testDumpFile: File? = configuration[KonanConfigKeys.TEST_DUMP_OUTPUT_PATH]?.let(::File)
|
||||
}
|
||||
|
||||
fun CompilerConfiguration.report(priority: CompilerMessageSeverity, message: String)
|
||||
|
||||
+2
@@ -172,6 +172,8 @@ class KonanConfigKeys {
|
||||
val LAZY_IR_FOR_CACHES: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("use lazy IR for cached libraries")
|
||||
val MEANINGFUL_BRIDGE_NAMES: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("enable meaningful bridge names")
|
||||
val PARTIAL_LINKAGE: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("allows some symbols in klibs be missed")
|
||||
val TEST_DUMP_OUTPUT_PATH: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("path to a file to dump the list of all available tests")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
@@ -348,6 +348,30 @@ internal val dependenciesLowerPhase = NamedCompilerPhase(
|
||||
}
|
||||
})
|
||||
|
||||
internal val dumpTestsPhase = makeCustomPhase<Context, IrModuleFragment>(
|
||||
name = "dumpTestsPhase",
|
||||
description = "Dump the list of all available tests",
|
||||
op = { context, _ ->
|
||||
val testDumpFile = context.config.testDumpFile
|
||||
requireNotNull(testDumpFile)
|
||||
|
||||
if (context.testCasesToDump.isEmpty()) {
|
||||
testDumpFile.writeText("")
|
||||
return@makeCustomPhase
|
||||
}
|
||||
|
||||
testDumpFile.writeText(
|
||||
context.testCasesToDump.asSequence()
|
||||
.flatMap { (suiteClassId, functionNames) ->
|
||||
val suiteName = suiteClassId.asString()
|
||||
functionNames.asSequence().map { "$suiteName:$it" }
|
||||
}
|
||||
.sorted()
|
||||
.joinToString(separator = "\n")
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
internal val entryPointPhase = makeCustomPhase<Context, IrModuleFragment>(
|
||||
name = "addEntryPoint",
|
||||
description = "Add entry point for program",
|
||||
@@ -533,6 +557,7 @@ private val backendCodegen = namedUnitPhase(
|
||||
allLoweringsPhase then // Lower current module first.
|
||||
dependenciesLowerPhase then // Then lower all libraries in topological order.
|
||||
// With that we guarantee that inline functions are unlowered while being inlined.
|
||||
dumpTestsPhase then
|
||||
entryPointPhase then
|
||||
exportInternalAbiPhase then
|
||||
useInternalAbiPhase then
|
||||
@@ -602,6 +627,7 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
|
||||
disableUnless(objectFilesPhase, config.produce.involvesLinkStage)
|
||||
disableUnless(linkerPhase, config.produce.involvesLinkStage)
|
||||
disableIf(testProcessorPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE)
|
||||
disableIf(dumpTestsPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE || config.testDumpFile == null)
|
||||
disableUnless(buildDFGPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
|
||||
disableUnless(devirtualizationAnalysisPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
|
||||
disableUnless(devirtualizationPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
|
||||
|
||||
+54
-32
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.ir.util.addChild
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
@@ -71,13 +72,6 @@ internal class TestProcessor (val context: Context) {
|
||||
private val IrFile.fileName
|
||||
get() = name.substringAfterLast('/')
|
||||
|
||||
private val IrFile.topLevelSuiteName: String
|
||||
get() {
|
||||
val packageFqName = fqName
|
||||
val shortFileName = PackagePartClassUtils.getFilePartShortName(fileName)
|
||||
return if (packageFqName.isRoot) shortFileName else "$packageFqName.$shortFileName"
|
||||
}
|
||||
|
||||
private fun MutableList<TestFunction>.registerFunction(
|
||||
function: IrFunction,
|
||||
kinds: Collection<Pair<FunctionKind, /* ignored: */ Boolean>>) =
|
||||
@@ -101,7 +95,7 @@ internal class TestProcessor (val context: Context) {
|
||||
// Call registerTestCase(name: String, testFunction: () -> Unit) method.
|
||||
+irCall(registerTestCase).apply {
|
||||
dispatchReceiver = irGet(receiver)
|
||||
putValueArgument(0, irString(it.function.name.identifier))
|
||||
putValueArgument(0, irString(it.functionName))
|
||||
putValueArgument(1, IrFunctionReferenceImpl(
|
||||
it.function.startOffset,
|
||||
it.function.endOffset,
|
||||
@@ -159,19 +153,19 @@ internal class TestProcessor (val context: Context) {
|
||||
private fun IrType.isTestFunctionKind() = classifierOrNull == symbols.testFunctionKind
|
||||
|
||||
private data class TestFunction(
|
||||
val function: IrFunction,
|
||||
val kind: FunctionKind,
|
||||
val ignored: Boolean
|
||||
)
|
||||
val function: IrFunction,
|
||||
val kind: FunctionKind,
|
||||
val ignored: Boolean
|
||||
) {
|
||||
val functionName: String get() = function.name.identifier
|
||||
}
|
||||
|
||||
private inner class TestClass(val ownerClass: IrClass) {
|
||||
var companion: IrClass? = null
|
||||
val functions = mutableListOf<TestFunction>()
|
||||
|
||||
fun registerFunction(
|
||||
function: IrFunction,
|
||||
kinds: Collection<Pair<FunctionKind, /* ignored: */ Boolean>>
|
||||
) = functions.registerFunction(function, kinds)
|
||||
val suiteClassId: ClassId = ownerClass.classId ?: error(ownerClass.render())
|
||||
val suiteName: String get() = suiteClassId.asFqNameString()
|
||||
|
||||
fun registerFunction(function: IrFunction, kind: FunctionKind, ignored: Boolean) =
|
||||
functions.registerFunction(function, kind, ignored)
|
||||
@@ -179,7 +173,12 @@ internal class TestProcessor (val context: Context) {
|
||||
|
||||
private inner class AnnotationCollector(val irFile: IrFile) : IrElementVisitorVoid {
|
||||
val testClasses = mutableMapOf<IrClass, TestClass>()
|
||||
|
||||
val topLevelFunctions = mutableListOf<TestFunction>()
|
||||
val topLevelSuiteClassId: ClassId by lazy {
|
||||
ClassId(irFile.fqName, PackagePartClassUtils.getFilePartShortName(irFile.fileName).let(Name::identifier))
|
||||
}
|
||||
val topLevelSuiteName: String get() = topLevelSuiteClassId.asFqNameString()
|
||||
|
||||
private fun MutableMap<IrClass, TestClass>.getTestClass(key: IrClass) =
|
||||
getOrPut(key) { TestClass(key) }
|
||||
@@ -474,10 +473,12 @@ internal class TestProcessor (val context: Context) {
|
||||
* Builds a test suite class representing a test class (any class in the original IrFile with method(s)
|
||||
* annotated with @Test). The test suite class is a subclass of ClassTestSuite<T> where T is the test class.
|
||||
*/
|
||||
private fun buildClassSuite(testClass: IrClass,
|
||||
testCompanion: IrClass?,
|
||||
functions: Collection<TestFunction>,
|
||||
irFile: IrFile
|
||||
private fun buildClassSuite(
|
||||
suiteName: String,
|
||||
testClass: IrClass,
|
||||
testCompanion: IrClass?,
|
||||
functions: Collection<TestFunction>,
|
||||
irFile: IrFile
|
||||
): IrClass {
|
||||
return IrClassImpl(
|
||||
testClass.startOffset, testClass.endOffset,
|
||||
@@ -506,8 +507,7 @@ internal class TestProcessor (val context: Context) {
|
||||
}
|
||||
|
||||
val constructor = buildClassSuiteConstructor(
|
||||
testClass.fqNameForIrSerialization.toString(), testClassType, testCompanionType,
|
||||
symbol, this, functions, testClass.ignored
|
||||
suiteName, testClassType, testCompanionType, symbol, this, functions, testClass.ignored
|
||||
)
|
||||
|
||||
val instanceGetter: IrFunction
|
||||
@@ -535,19 +535,19 @@ internal class TestProcessor (val context: Context) {
|
||||
|
||||
// region IR generation methods
|
||||
private fun generateClassSuite(irFile: IrFile, testClass: TestClass) =
|
||||
with(buildClassSuite(testClass.ownerClass, testClass.companion, testClass.functions, irFile)) {
|
||||
with(buildClassSuite(testClass.suiteName, testClass.ownerClass, testClass.companion, testClass.functions, irFile)) {
|
||||
val irConstructor = constructors.single()
|
||||
val irBuilder = context.createIrBuilder(irFile.symbol, testClass.ownerClass.startOffset, testClass.ownerClass.endOffset)
|
||||
irBuilder.irCall(irConstructor)
|
||||
}
|
||||
|
||||
/** Check if this fqName already used or not. */
|
||||
private fun checkSuiteName(irFile: IrFile, name: String): Boolean {
|
||||
if (topLevelSuiteNames.contains(name)) {
|
||||
private fun checkTopLevelSuiteName(irFile: IrFile, topLevelSuiteName: String): Boolean {
|
||||
if (topLevelSuiteNames.contains(topLevelSuiteName)) {
|
||||
context.reportCompilationError("Package '${irFile.fqName}' has top-level test " +
|
||||
"functions in several files with the same name: '${irFile.fileName}'")
|
||||
}
|
||||
topLevelSuiteNames.add(name)
|
||||
topLevelSuiteNames.add(topLevelSuiteName)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -570,16 +570,15 @@ internal class TestProcessor (val context: Context) {
|
||||
&& it.valueParameters[2].type.isBoolean()
|
||||
}
|
||||
|
||||
private fun generateTopLevelSuite(irFile: IrFile, functions: Collection<TestFunction>): IrExpression? {
|
||||
private fun generateTopLevelSuite(irFile: IrFile, topLevelSuiteName: String, functions: Collection<TestFunction>): IrExpression? {
|
||||
val irBuilder = context.createIrBuilder(irFile.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
|
||||
val suiteName = irFile.topLevelSuiteName
|
||||
if (!checkSuiteName(irFile, suiteName)) {
|
||||
if (!checkTopLevelSuiteName(irFile, topLevelSuiteName)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return irBuilder.irBlock {
|
||||
val constructorCall = irCall(topLevelSuiteConstructor).apply {
|
||||
putValueArgument(0, irString(suiteName))
|
||||
putValueArgument(0, irString(topLevelSuiteName))
|
||||
}
|
||||
val testSuiteVal = irTemporary(constructorCall, "topLevelTestSuite")
|
||||
generateFunctionRegistration(testSuiteVal,
|
||||
@@ -591,14 +590,17 @@ internal class TestProcessor (val context: Context) {
|
||||
|
||||
private fun createTestSuites(irFile: IrFile, annotationCollector: AnnotationCollector) {
|
||||
val statements = mutableListOf<IrExpression>()
|
||||
|
||||
annotationCollector.testClasses.filter {
|
||||
it.value.functions.any { it.kind == FunctionKind.TEST }
|
||||
}.forEach { _, testClass ->
|
||||
}.forEach { (_, testClass) ->
|
||||
statements.add(generateClassSuite(irFile, testClass))
|
||||
}
|
||||
|
||||
if (annotationCollector.topLevelFunctions.isNotEmpty()) {
|
||||
generateTopLevelSuite(irFile, annotationCollector.topLevelFunctions)?.let { statements.add(it) }
|
||||
generateTopLevelSuite(irFile, annotationCollector.topLevelSuiteName, annotationCollector.topLevelFunctions)?.let { statements.add(it) }
|
||||
}
|
||||
|
||||
if (statements.isNotEmpty()) {
|
||||
context.irFactory.buildFun {
|
||||
startOffset = SYNTHETIC_OFFSET
|
||||
@@ -618,6 +620,25 @@ internal class TestProcessor (val context: Context) {
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region test functions to be dumped
|
||||
private fun recordTestFunctions(annotationCollector: AnnotationCollector) {
|
||||
if (context.config.testDumpFile == null) return
|
||||
|
||||
fun recordFunction(suiteClassId: ClassId, function: TestFunction) {
|
||||
if (function.kind == FunctionKind.TEST)
|
||||
context.testCasesToDump.computeIfAbsent(suiteClassId) { mutableListOf() } += function.functionName
|
||||
}
|
||||
|
||||
annotationCollector.topLevelFunctions.forEach { function ->
|
||||
recordFunction(annotationCollector.topLevelSuiteClassId, function)
|
||||
}
|
||||
|
||||
annotationCollector.testClasses.values.forEach { testClass ->
|
||||
testClass.functions.forEach { function -> recordFunction(testClass.suiteClassId, function) }
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
private fun shouldProcessFile(irFile: IrFile): Boolean = irFile.packageFragmentDescriptor.module.let {
|
||||
// Process test annotations in source libraries too.
|
||||
it == context.moduleDescriptor || it in context.getIncludedLibraryDescriptors()
|
||||
@@ -632,5 +653,6 @@ internal class TestProcessor (val context: Context) {
|
||||
val annotationCollector = AnnotationCollector(irFile)
|
||||
irFile.acceptChildrenVoid(annotationCollector)
|
||||
createTestSuites(irFile, annotationCollector)
|
||||
recordTestFunctions(annotationCollector)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user