Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add test for Gradle plugin and fix issues running tests for native ta…
…rgets on Kotlin 1.7+ (#3107) * Add (currently failing) test for Gradle plugin, and improve warnings generated by plugin when invalid configuration is detected. * Fix issue where compilation for Kotlin/Native and Kotlin/JS projects would fail if the project uses Kotlin 1.7. * Fix issue running Kotest tests under Kotlin 1.5. The current version of Gradle uses Kotlin 1.5, so retaining backwards compatibility with Kotlin 1.5 allows us to use Kotest to write tests for Gradle plugins. Resolves #3059. * Fix issue where Gradle plugin tests could fail due to conflicting with other concurrent build tasks. * Remove Gradle plugin test for now. See #3075 (comment). * Revert "Remove Gradle plugin test for now." This reverts commit ec14c55. * Disable JS browser test for now. * Fix issue where no tests are run when targeting a native platform using Kotlin 1.7. Kotlin 1.7 now uses the embeddable compiler JAR and corresponding plugins (from "getPluginArtifact" rather than "getPluginArtifactForNative" in KotestMultiplatformCompilerGradlePlugin) by default for all platforms. This means that the "JS" compiler plugin is used by all targets by default in projects that use Kotlin 1.7+. As a first step, I've brought the existing native plugin code over to the "JS" plugin. Next steps are to rename the projects to make their intention clearer and merge the transformers used by each platform. * Rename JS compiler plugin project to better reflect its purpose. * Update package name to match project name. * Rename "native" compiler plugin project to better reflect its purpose. * Refactor transformers to share more logic. * Add tests to ensure Kotest behaves correctly with Kotlin/Native memory model. * Extract further common functionality. * Configure project config objects when running on Kotlin/Native. * Reorder method calls in native transformer to match JS transformer. * Further consolidate IR generation logic into base Transformer class. * Fix issues running compiler plugin on modules that do not contain any Kotest tests. * Add workaround for issues running Gradle plugin tests on GitHub Actions. * Revert "Add workaround for issues running Gradle plugin tests on GitHub Actions." This reverts commit 9df35f8. * Set target JVM versions for all libraries. * Add Yarn lock file. * Run Gradle plugin tests in a separate GitHub Actions job. Co-authored-by: Sam <sam@sksamuel.com>
- Loading branch information
1 parent
b4dc446
commit 8101fdd
Showing
33 changed files
with
2,592 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
12 changes: 12 additions & 0 deletions
12
...c/jvmMain/kotlin/io/kotest/framework/multiplatform/embeddablecompiler/IrFileExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package io.kotest.framework.multiplatform.embeddablecompiler | ||
|
||
import org.jetbrains.kotlin.ir.declarations.IrFile | ||
import java.io.File | ||
|
||
// These extension properties are available in org.jetbrains.kotlin.ir.declarations, but were moved from one file to | ||
// another in Kotlin 1.7. This breaks backwards compatibility with earlier versions of Kotlin. | ||
// So instead of using the provided implementations, we've copied them here, so we can work with both Kotlin 1.7+ and earlier | ||
// versions without issue. | ||
// See https://github.com/kotest/kotest/issues/3060 and https://youtrack.jetbrains.com/issue/KT-52888 for more information. | ||
internal val IrFile.path: String get() = fileEntry.name | ||
internal val IrFile.name: String get() = File(path).name |
36 changes: 36 additions & 0 deletions
36
.../src/jvmMain/kotlin/io/kotest/framework/multiplatform/embeddablecompiler/JsTransformer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package io.kotest.framework.multiplatform.embeddablecompiler | ||
|
||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext | ||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun | ||
import org.jetbrains.kotlin.ir.builders.irBlockBody | ||
import org.jetbrains.kotlin.ir.builders.irCall | ||
import org.jetbrains.kotlin.ir.declarations.IrClass | ||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration | ||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent | ||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction | ||
import org.jetbrains.kotlin.ir.util.getSimpleFunction | ||
import org.jetbrains.kotlin.name.Name | ||
|
||
class JsTransformer(messageCollector: MessageCollector, pluginContext: IrPluginContext) : Transformer(messageCollector, pluginContext) { | ||
override fun generateLauncher(specs: Iterable<IrClass>, configs: Iterable<IrClass>, declarationParent: IrDeclarationParent): IrDeclaration { | ||
val main = pluginContext.irFactory.buildFun { | ||
name = Name.identifier("main") | ||
returnType = pluginContext.irBuiltIns.unitType | ||
}.also { func: IrSimpleFunction -> | ||
func.body = DeclarationIrBuilder(pluginContext, func.symbol).irBlockBody { | ||
+callLauncher(promiseFn, specs, configs) { | ||
irCall(launcherConstructor) | ||
} | ||
} | ||
} | ||
|
||
return main | ||
} | ||
|
||
private val promiseFn by lazy { | ||
launcherClass.getSimpleFunction(EntryPoint.PromiseMethodName) | ||
?: error("Cannot find function ${EntryPoint.PromiseMethodName}") | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...platform/js/KotestJsComponentRegistrar.kt → ...blecompiler/KotestJsComponentRegistrar.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
.../jvmMain/kotlin/io/kotest/framework/multiplatform/embeddablecompiler/NativeTransformer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package io.kotest.framework.multiplatform.embeddablecompiler | ||
|
||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext | ||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET | ||
import org.jetbrains.kotlin.ir.builders.IrSingleStatementBuilder | ||
import org.jetbrains.kotlin.ir.builders.Scope | ||
import org.jetbrains.kotlin.ir.builders.declarations.addGetter | ||
import org.jetbrains.kotlin.ir.builders.declarations.buildField | ||
import org.jetbrains.kotlin.ir.builders.declarations.buildProperty | ||
import org.jetbrains.kotlin.ir.builders.irBlock | ||
import org.jetbrains.kotlin.ir.builders.irBlockBody | ||
import org.jetbrains.kotlin.ir.builders.irCall | ||
import org.jetbrains.kotlin.ir.declarations.IrClass | ||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration | ||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent | ||
import org.jetbrains.kotlin.ir.util.constructors | ||
import org.jetbrains.kotlin.ir.util.getSimpleFunction | ||
import org.jetbrains.kotlin.name.FqName | ||
import org.jetbrains.kotlin.name.Name | ||
|
||
class NativeTransformer(messageCollector: MessageCollector, pluginContext: IrPluginContext) : Transformer(messageCollector, pluginContext) { | ||
override fun generateLauncher(specs: Iterable<IrClass>, configs: Iterable<IrClass>, declarationParent: IrDeclarationParent): IrDeclaration { | ||
val launcher = pluginContext.irFactory.buildProperty { | ||
name = Name.identifier(EntryPoint.LauncherValName) | ||
}.apply { | ||
parent = declarationParent | ||
annotations += IrSingleStatementBuilder( | ||
pluginContext, | ||
Scope(this.symbol), | ||
UNDEFINED_OFFSET, | ||
UNDEFINED_OFFSET | ||
).build { irCall(eagerAnnotationConstructor) } | ||
|
||
backingField = pluginContext.irFactory.buildField { | ||
type = pluginContext.irBuiltIns.unitType | ||
isFinal = true | ||
isExternal = false | ||
isStatic = true // top level vals must be static | ||
name = Name.identifier(EntryPoint.LauncherValName) | ||
}.also { field -> | ||
field.correspondingPropertySymbol = this@apply.symbol | ||
field.initializer = pluginContext.irFactory.createExpressionBody(startOffset, endOffset) { | ||
this.expression = DeclarationIrBuilder(pluginContext, field.symbol).irBlock { | ||
+callLauncher(launchFn, specs, configs) { | ||
irCall(withTeamCityListenerMethodNameFn).also { withTeamCity -> | ||
withTeamCity.dispatchReceiver = irCall(launcherConstructor) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
addGetter { | ||
returnType = pluginContext.irBuiltIns.unitType | ||
}.also { func -> | ||
func.body = DeclarationIrBuilder(pluginContext, func.symbol).irBlockBody { | ||
} | ||
} | ||
} | ||
|
||
return launcher | ||
} | ||
|
||
private val launchFn by lazy { | ||
launcherClass.getSimpleFunction(EntryPoint.LaunchMethodName) | ||
?: error("Cannot find function ${EntryPoint.LaunchMethodName}") | ||
} | ||
|
||
private val withTeamCityListenerMethodNameFn by lazy { | ||
launcherClass.getSimpleFunction(EntryPoint.WithTeamCityListenerMethodName) | ||
?: error("Cannot find function ${EntryPoint.WithTeamCityListenerMethodName}") | ||
} | ||
|
||
private val eagerAnnotationConstructor by lazy { | ||
val annotationName = FqName("kotlin.native.EagerInitialization") | ||
|
||
val annotation = pluginContext.referenceClass(annotationName) | ||
?: error("Cannot find eager initialisation annotation class $annotationName") | ||
|
||
annotation.constructors.single() | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
.../kotlin/io/kotest/framework/multiplatform/embeddablecompiler/SpecIrGenerationExtension.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package io.kotest.framework.multiplatform.embeddablecompiler | ||
|
||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension | ||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment | ||
import org.jetbrains.kotlin.platform.js.isJs | ||
import org.jetbrains.kotlin.platform.konan.isNative | ||
|
||
class SpecIrGenerationExtension(private val messageCollector: MessageCollector) : IrGenerationExtension { | ||
|
||
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { | ||
val platform = pluginContext.platform | ||
|
||
val transformer = when { | ||
platform.isJs() -> JsTransformer(messageCollector, pluginContext) | ||
platform.isNative() -> NativeTransformer(messageCollector, pluginContext) | ||
else -> throw UnsupportedOperationException("Cannot use Kotest compiler plugin with platform: $platform") | ||
} | ||
|
||
moduleFragment.transform(transformer, null) | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
...er/src/jvmMain/kotlin/io/kotest/framework/multiplatform/embeddablecompiler/Transformer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package io.kotest.framework.multiplatform.embeddablecompiler | ||
|
||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext | ||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext | ||
import org.jetbrains.kotlin.backend.common.ir.addChild | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.cli.common.toLogger | ||
import org.jetbrains.kotlin.ir.IrStatement | ||
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope | ||
import org.jetbrains.kotlin.ir.builders.irCall | ||
import org.jetbrains.kotlin.ir.builders.irVararg | ||
import org.jetbrains.kotlin.ir.declarations.IrClass | ||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration | ||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent | ||
import org.jetbrains.kotlin.ir.declarations.IrFile | ||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment | ||
import org.jetbrains.kotlin.ir.expressions.IrCall | ||
import org.jetbrains.kotlin.ir.expressions.IrExpression | ||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol | ||
import org.jetbrains.kotlin.ir.util.constructors | ||
import org.jetbrains.kotlin.ir.util.getSimpleFunction | ||
import org.jetbrains.kotlin.ir.util.kotlinFqName | ||
import org.jetbrains.kotlin.name.FqName | ||
import java.util.concurrent.CopyOnWriteArrayList | ||
|
||
abstract class Transformer(protected val messageCollector: MessageCollector, protected val pluginContext: IrPluginContext) : IrElementTransformerVoidWithContext() { | ||
private val specs = CopyOnWriteArrayList<IrClass>() | ||
private var configs = CopyOnWriteArrayList<IrClass>() | ||
|
||
override fun visitClassNew(declaration: IrClass): IrStatement { | ||
super.visitClassNew(declaration) | ||
if (declaration.isProjectConfig()) configs.add(declaration) | ||
return declaration | ||
} | ||
|
||
override fun visitFileNew(declaration: IrFile): IrFile { | ||
super.visitFileNew(declaration) | ||
val specs = declaration.specs() | ||
messageCollector.toLogger().log("${declaration.name} contains ${specs.size} spec(s): ${specs.joinToString(", ") { it.kotlinFqName.asString() }}") | ||
this.specs.addAll(specs) | ||
return declaration | ||
} | ||
|
||
override fun visitModuleFragment(declaration: IrModuleFragment): IrModuleFragment { | ||
val fragment = super.visitModuleFragment(declaration) | ||
|
||
messageCollector.toLogger().log("Detected ${configs.size} configs:") | ||
configs.forEach { | ||
messageCollector.toLogger().log(it.kotlinFqName.asString()) | ||
} | ||
|
||
messageCollector.toLogger().log("Detected ${specs.size} JS specs:") | ||
specs.forEach { | ||
messageCollector.toLogger().log(it.kotlinFqName.asString()) | ||
} | ||
|
||
if (specs.isEmpty()) { | ||
return fragment | ||
} | ||
|
||
val file = declaration.files.first() | ||
val launcher = generateLauncher(specs, configs, file) | ||
file.addChild(launcher) | ||
|
||
return fragment | ||
} | ||
|
||
abstract fun generateLauncher(specs: Iterable<IrClass>, configs: Iterable<IrClass>, declarationParent: IrDeclarationParent): IrDeclaration | ||
|
||
protected fun IrBuilderWithScope.callLauncher( | ||
launchFunction: IrSimpleFunctionSymbol, | ||
specs: Iterable<IrClass>, | ||
configs: Iterable<IrClass>, | ||
constructorGenerator: IrBuilderWithScope.() -> IrExpression | ||
): IrCall { | ||
return irCall(launchFunction).also { promise: IrCall -> | ||
promise.dispatchReceiver = irCall(withSpecsFn).also { withSpecs -> | ||
withSpecs.putValueArgument( | ||
0, | ||
irVararg( | ||
pluginContext.irBuiltIns.stringType, | ||
specs.map { irCall(it.constructors.first()) } | ||
) | ||
) | ||
withSpecs.dispatchReceiver = irCall(withConfigFn).also { withConfig -> | ||
withConfig.putValueArgument( | ||
0, | ||
irVararg( | ||
pluginContext.irBuiltIns.stringType, | ||
configs.map { irCall(it.constructors.first()) } | ||
) | ||
) | ||
withConfig.dispatchReceiver = constructorGenerator() | ||
} | ||
} | ||
} | ||
} | ||
|
||
protected val launcherClass by lazy { | ||
pluginContext.referenceClass(FqName(EntryPoint.TestEngineClassName)) | ||
?: error("Cannot find ${EntryPoint.TestEngineClassName} class reference") | ||
} | ||
|
||
protected val launcherConstructor by lazy { launcherClass.constructors.first { it.owner.valueParameters.isEmpty() } } | ||
|
||
protected val withSpecsFn by lazy { | ||
launcherClass.getSimpleFunction(EntryPoint.WithSpecsMethodName) | ||
?: error("Cannot find function ${EntryPoint.WithSpecsMethodName}") | ||
} | ||
|
||
protected val withConfigFn by lazy { | ||
launcherClass.getSimpleFunction(EntryPoint.WithConfigMethodName) | ||
?: error("Cannot find function ${EntryPoint.WithConfigMethodName}") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.