diff --git a/SKIE/skie-gradle/plugin/src/main/kotlin/co/touchlab/skie/plugin/configuration/CreateSkieConfigurationTask.kt b/SKIE/skie-gradle/plugin/src/main/kotlin/co/touchlab/skie/plugin/configuration/CreateSkieConfigurationTask.kt index f00c166d5..f54753c79 100644 --- a/SKIE/skie-gradle/plugin/src/main/kotlin/co/touchlab/skie/plugin/configuration/CreateSkieConfigurationTask.kt +++ b/SKIE/skie-gradle/plugin/src/main/kotlin/co/touchlab/skie/plugin/configuration/CreateSkieConfigurationTask.kt @@ -2,7 +2,7 @@ package co.touchlab.skie.plugin.configuration import co.touchlab.skie.plugin.util.SkieTarget import co.touchlab.skie.plugin.configuration.SkieExtension.Companion.buildConfiguration -import co.touchlab.skie.plugin.configuration.util.GradleSkieConfiguration +import co.touchlab.skie.plugin.configuration.util.GradleSkieConfigurationData import co.touchlab.skie.plugin.directory.createSkieBuildDirectoryTask import co.touchlab.skie.plugin.util.registerSkieTargetBasedTask import co.touchlab.skie.plugin.util.skieBuildDirectory @@ -20,7 +20,7 @@ import java.io.File internal abstract class CreateSkieConfigurationTask : DefaultTask() { @get:Input - abstract val configuration: Property + abstract val configuration: Property @get:OutputFile abstract val configurationFile: Property diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/FilterMatrixWith.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/FilterMatrixWith.kt new file mode 100644 index 000000000..ab4bacd0d --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/FilterMatrixWith.kt @@ -0,0 +1,10 @@ +package co.touchlab.skie.test.annotation.filter + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class FilterMatrixWith( + val value: KClass +) diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/MatrixFilter.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/MatrixFilter.kt new file mode 100644 index 000000000..9436c0eed --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/MatrixFilter.kt @@ -0,0 +1,8 @@ +package co.touchlab.skie.test.annotation.filter + +import co.touchlab.skie.test.runner.SkieTestMatrixSource +import org.junit.jupiter.api.extension.ExtensionContext + +interface MatrixFilter { + fun apply(context: ExtensionContext, source: SkieTestMatrixSource) +} diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyConfigurationCache.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyConfigurationCache.kt new file mode 100644 index 000000000..b99d9006c --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyConfigurationCache.kt @@ -0,0 +1,9 @@ +package co.touchlab.skie.test.annotation.filter + +import co.touchlab.skie.test.filter.OnlyConfigurationCacheMatrixFilter + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +@FilterMatrixWith(OnlyConfigurationCacheMatrixFilter::class) +annotation class OnlyConfigurationCache diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyFor.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyFor.kt index 6c56620d0..1681d854c 100644 --- a/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyFor.kt +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/annotation/filter/OnlyFor.kt @@ -1,12 +1,15 @@ package co.touchlab.skie.test.annotation.filter +import co.touchlab.skie.test.filter.OnlyForMatrixFilter import co.touchlab.skie.test.runner.BuildConfiguration import co.touchlab.skie.test.util.LinkMode import co.touchlab.skie.test.util.RawKotlinTarget +import org.intellij.lang.annotations.Language @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @Repeatable +@FilterMatrixWith(OnlyForMatrixFilter::class) annotation class OnlyFor( val targets: Array = [], val configurations: Array = [], diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/base/BaseGradleTests.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/base/BaseGradleTests.kt index c20ab44e6..709610e6e 100644 --- a/test-runner/src/test/kotlin/co/touchlab/skie/test/base/BaseGradleTests.kt +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/base/BaseGradleTests.kt @@ -8,6 +8,7 @@ import co.touchlab.skie.test.trait.TestUtilsTrait import co.touchlab.skie.test.trait.gradle.GradleBuildFileBuilderTrait import co.touchlab.skie.test.util.CommandResult import co.touchlab.skie.test.util.KotlinTarget +import co.touchlab.skie.test.util.StringBuilderScope import co.touchlab.skie.test.util.execute import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner @@ -58,9 +59,10 @@ abstract class BaseGradleTests: TestUtilsTrait, GradleBuildFileBuilderTrait { @BeforeEach fun createGradlePropertiesFile() { - gradlePropertiesFile(""" - org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=1g -XX:+UseParallelGC - """.trimIndent()) + gradlePropertiesFile { + +"org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=1g -XX:+UseParallelGC\n" + appendAdditionalGradleProperties() + } } fun runGradle( @@ -144,4 +146,6 @@ abstract class BaseGradleTests: TestUtilsTrait, GradleBuildFileBuilderTrait { "build/bin/${target.id}/${configuration.name.lowercase()}Framework" } } + + protected open fun StringBuilderScope.appendAdditionalGradleProperties() { } } diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/filter/OnlyConfigurationCacheMatrixFilter.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/filter/OnlyConfigurationCacheMatrixFilter.kt new file mode 100644 index 000000000..2bcd03349 --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/filter/OnlyConfigurationCacheMatrixFilter.kt @@ -0,0 +1,15 @@ +package co.touchlab.skie.test.filter + +import co.touchlab.skie.test.annotation.filter.MatrixFilter +import co.touchlab.skie.test.runner.SkieTestMatrixSource +import org.junit.jupiter.api.extension.ExtensionContext + +object OnlyConfigurationCacheMatrixFilter : MatrixFilter { + override fun apply(context: ExtensionContext, source: SkieTestMatrixSource) { + source.kotlinVersions.removeAll { kotlinVersion -> + kotlinVersion.value.startsWith("1.8.") || + kotlinVersion.value.startsWith("1.9.0") || + kotlinVersion.value.startsWith("1.9.1") + } + } +} diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/filter/OnlyForMatrixFilter.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/filter/OnlyForMatrixFilter.kt new file mode 100644 index 000000000..9f9a3d231 --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/filter/OnlyForMatrixFilter.kt @@ -0,0 +1,38 @@ +package co.touchlab.skie.test.filter + +import co.touchlab.skie.test.annotation.filter.MatrixFilter +import co.touchlab.skie.test.annotation.filter.OnlyFor +import co.touchlab.skie.test.runner.SkieTestMatrixSource +import co.touchlab.skie.test.util.KotlinVersion +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.platform.commons.util.AnnotationUtils + +object OnlyForMatrixFilter: MatrixFilter { + + override fun apply(context: ExtensionContext, source: SkieTestMatrixSource) { + val parentClassFilterAnnotations = AnnotationUtils.findRepeatableAnnotations( + context.requiredTestClass, + OnlyFor::class.java, + ) + val methodFilterAnnotations = AnnotationUtils.findRepeatableAnnotations( + context.requiredTestMethod, + OnlyFor::class.java, + ) + + (parentClassFilterAnnotations + methodFilterAnnotations).forEach { filter -> + filter.targets.map { it.target }.applyTo(source.targets) + filter.linkModes.toList().applyTo(source.linkModes) + filter.configurations.toList().applyTo(source.configurations) + filter.kotlinVersions.map(::KotlinVersion).applyTo(source.kotlinVersions) + } + } + + private inline fun Collection.applyTo(source: MutableList) { + if (this.isNotEmpty()) { + val thisSet = this.toSet() + source.removeAll { + it !in thisSet + } + } + } +} diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/MatrixFilter.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/MatrixFilter.kt index 3aa76802e..9428ea03e 100644 --- a/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/MatrixFilter.kt +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/MatrixFilter.kt @@ -1,36 +1,13 @@ package co.touchlab.skie.test.runner -import co.touchlab.skie.test.annotation.filter.OnlyFor import co.touchlab.skie.test.util.KotlinTarget +import co.touchlab.skie.test.util.KotlinVersion import co.touchlab.skie.test.util.LinkMode -data class MatrixFilter( - val targets: Set, - val configurations: Set, - val linkModes: Set, - val kotlinVersions: Set, -) { - fun apply(onlyFor: OnlyFor): MatrixFilter = MatrixFilter( - targets = targets.intersectOrChoose(onlyFor.targets.map { it.target }), - configurations = configurations.intersectOrChoose(onlyFor.configurations.toList()), - linkModes = linkModes.intersectOrChoose(onlyFor.linkModes.toList()), - kotlinVersions = kotlinVersions.intersectOrChoose(onlyFor.kotlinVersions.toList()), - ) - - companion object { - val empty = MatrixFilter( - targets = emptySet(), - configurations = emptySet(), - linkModes = emptySet(), - kotlinVersions = emptySet(), - ) - - private inline fun Set.intersectOrChoose(other: Collection): Set { - return when { - this.isEmpty() -> other.toSet() - other.isEmpty() -> this - else -> intersect(other.toSet()) - } - } - } -} +data class SkieTestMatrixSource( + val targets: MutableList, + val presets: MutableList, + val configurations: MutableList, + val linkModes: MutableList, + val kotlinVersions: MutableList, +) diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunner.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunner.kt index b33ea017c..e059f5ec6 100644 --- a/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunner.kt +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunner.kt @@ -1,7 +1,8 @@ package co.touchlab.skie.test.runner import co.touchlab.skie.test.annotation.MatrixTest -import co.touchlab.skie.test.annotation.filter.OnlyFor +import co.touchlab.skie.test.annotation.filter.FilterMatrixWith +import io.kotest.mpp.newInstanceNoArgConstructorOrObjectInstance import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.TestTemplateInvocationContext import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider @@ -15,13 +16,27 @@ class SkieTestRunner: TestTemplateInvocationContextProvider { override fun provideTestTemplateInvocationContexts(context: ExtensionContext): Stream { val testMethod = context.requiredTestMethod - val parentClassFilterAnnotations = - AnnotationUtils.findRepeatableAnnotations(context.requiredTestClass, OnlyFor::class.java) - val methodFilterAnnotations = AnnotationUtils.findRepeatableAnnotations(testMethod, OnlyFor::class.java) - val matrixFilter = (parentClassFilterAnnotations + methodFilterAnnotations).fold(MatrixFilter.empty) { acc, onlyFor -> - acc.apply(onlyFor) + val parentClassFilterAnnotations = AnnotationUtils.findRepeatableAnnotations( + context.requiredTestClass, + FilterMatrixWith::class.java + ) + val methodFilterAnnotations = AnnotationUtils.findRepeatableAnnotations( + testMethod, + FilterMatrixWith::class.java + ) + val uniqueMatrixFilterClasses = (parentClassFilterAnnotations + methodFilterAnnotations).map { + it.value + }.toSet() + + val uniqueMatrixFilters = uniqueMatrixFilterClasses.map { + it.newInstanceNoArgConstructorOrObjectInstance() + } + + val matrixSource = SkieTestRunnerConfiguration.buildMatrixSource() + uniqueMatrixFilters.forEach { filter -> + filter.apply(context, matrixSource) } - val filteredAxes = SkieTestRunnerConfiguration.filteredMatrixAxes(matrixFilter) + val filteredAxes = SkieTestRunnerConfiguration.buildMatrixAxes(matrixSource) val matrixAxes = testMethod.parameterTypes.mapNotNull { requestedAxisType -> filteredAxes[requestedAxisType] diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunnerConfiguration.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunnerConfiguration.kt index f723a58ed..95c05b435 100644 --- a/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunnerConfiguration.kt +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/runner/SkieTestRunnerConfiguration.kt @@ -15,35 +15,34 @@ object SkieTestRunnerConfiguration { val kotlinVersions = list("matrix.kotlinVersions", ::KotlinVersion) ?: listOf("1.8.0", "1.8.20", "1.9.20").map(::KotlinVersion) val gradleVersions = list("matrix.gradleVersions", ::GradleVersion) ?: listOf("8.6").map(::GradleVersion) - fun filteredMatrixAxes(filter: MatrixFilter) = buildList> { - this += SkieTestMatrix.Axis( - "Configuration", - configurations.intersectOrKeepIfEmpty(filter.configurations.toList()) - ) - this += SkieTestMatrix.Axis("Linkage", linkModes.intersectOrKeepIfEmpty(filter.linkModes.toList())) - this += SkieTestMatrix.Axis( - "Kotlin", - kotlinVersions.intersectOrKeepIfEmpty(filter.kotlinVersions.map(::KotlinVersion)) + fun buildMatrixSource(): SkieTestMatrixSource { + val allTargets = targets.targets + return SkieTestMatrixSource( + targets = allTargets.toMutableList(), + presets = targets.presets.takeIf { it.isNotEmpty() }?.toMutableList() + ?: KotlinTarget.Preset.Root.children.presets.filter { preset -> + allTargets.toSet().intersect(preset.targets.toSet()).isNotEmpty() + }.toMutableList(), + configurations = configurations.toMutableList(), + linkModes = linkModes.toMutableList(), + kotlinVersions = kotlinVersions.toMutableList(), ) + } - val filteredTargets = if (filter.targets.isNotEmpty()) { - targets.targets.filter(filter.targets::contains) - } else { - targets.targets - } - this += SkieTestMatrix.Axis("Target", filteredTargets) - this += SkieTestMatrix.Axis("Target", filteredTargets.filterIsInstance()) - this += SkieTestMatrix.Axis("Target", filteredTargets.filterIsInstance()) - this += SkieTestMatrix.Axis("Target", filteredTargets.filterIsInstance()) - this += SkieTestMatrix.Axis("Target", filteredTargets.filterIsInstance()) - - // TODO: Add filtering - val presets = targets.presets.takeIf { it.isNotEmpty() } ?: KotlinTarget.Preset.Root.children.presets.filter { preset -> - targets.targets.toSet().intersect(preset.targets.toSet()).isNotEmpty() - } - this += SkieTestMatrix.Axis("Preset", presets) - this += SkieTestMatrix.Axis("Preset", presets.filterIsInstance()) - this += SkieTestMatrix.Axis("Preset", presets.filterIsInstance()) + fun buildMatrixAxes(source: SkieTestMatrixSource) = buildList> { + this += SkieTestMatrix.Axis("Configuration", source.configurations) + this += SkieTestMatrix.Axis("Linkage", source.linkModes) + this += SkieTestMatrix.Axis("Kotlin", source.kotlinVersions) + + this += SkieTestMatrix.Axis("Target", source.targets) + this += SkieTestMatrix.Axis("Target", source.targets.filterIsInstance()) + this += SkieTestMatrix.Axis("Target", source.targets.filterIsInstance()) + this += SkieTestMatrix.Axis("Target", source.targets.filterIsInstance()) + this += SkieTestMatrix.Axis("Target", source.targets.filterIsInstance()) + + this += SkieTestMatrix.Axis("Preset", source.presets) + this += SkieTestMatrix.Axis("Preset", source.presets.filterIsInstance()) + this += SkieTestMatrix.Axis("Preset", source.presets.filterIsInstance()) }.associateBy { it.type } private fun value(property: String, deserialize: (String) -> T?): T? { diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/suite/gradle/configurationcache/GradleConfigurationCacheTests.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/suite/gradle/configurationcache/GradleConfigurationCacheTests.kt new file mode 100644 index 000000000..777c2db0e --- /dev/null +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/suite/gradle/configurationcache/GradleConfigurationCacheTests.kt @@ -0,0 +1,129 @@ +package co.touchlab.skie.test.suite.gradle.configurationcache + +import co.touchlab.skie.test.annotation.MatrixTest +import co.touchlab.skie.test.annotation.filter.OnlyConfigurationCache +import co.touchlab.skie.test.annotation.filter.Smoke +import co.touchlab.skie.test.annotation.type.GradleTests +import co.touchlab.skie.test.base.BaseGradleTests +import co.touchlab.skie.test.runner.BuildConfiguration +import co.touchlab.skie.test.util.KotlinTarget +import co.touchlab.skie.test.util.KotlinVersion +import co.touchlab.skie.test.util.LinkMode +import co.touchlab.skie.test.util.StringBuilderScope +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.TaskOutcome +import kotlin.test.assertEquals + +@Suppress("ClassName") +@Smoke +@GradleTests +@OnlyConfigurationCache +class GradleConfigurationCacheTests: BaseGradleTests() { + + // We don't need to run the whole build to check configuration cache + private val gradleArguments = arrayOf("dependencies") + private val assertGradleResult = { result: BuildResult -> + assertEquals(TaskOutcome.SUCCESS, result.task(":dependencies")?.outcome) + } + + @MatrixTest + fun `no artifact`( + kotlinVersion: KotlinVersion, + ) { + rootBuildFile(kotlinVersion) { + kotlin { + targets(KotlinTarget.Native) + } + } + + runGradle( + arguments = gradleArguments, + assertResult = assertGradleResult, + ) + } + + @MatrixTest + fun `frameworks`( + kotlinVersion: KotlinVersion, + linkMode: LinkMode, + configuration: BuildConfiguration, + ) { + rootBuildFile(kotlinVersion) { + kotlin { + targets(KotlinTarget.Native) + + registerNativeFrameworks( + kotlinVersion = kotlinVersion, + buildConfiguration = configuration, + linkMode = linkMode, + ) + } + } + + runGradle( + arguments = gradleArguments, + assertResult = assertGradleResult, + ) + } + + @MatrixTest + fun `framework artifacts`( + kotlinVersion: KotlinVersion, + linkMode: LinkMode, + configuration: BuildConfiguration, + ) { + rootBuildFile(kotlinVersion) { + kotlin { + targets(KotlinTarget.Native) + } + + kotlinArtifacts { + KotlinTarget.Native.Darwin.targets.forEach { target -> + framework( + kotlinVersion = kotlinVersion, + target = target, + linkMode = linkMode, + buildConfiguration = configuration, + ) + } + } + } + + runGradle( + arguments = gradleArguments, + assertResult = assertGradleResult, + ) + } + + @MatrixTest + fun `xcframework artifact`( + kotlinVersion: KotlinVersion, + linkMode: LinkMode, + configuration: BuildConfiguration, + ) { + rootBuildFile(kotlinVersion) { + kotlin { + targets(KotlinTarget.Native) + } + + kotlinArtifacts { + xcframework( + kotlinVersion = kotlinVersion, + targets = KotlinTarget.Native.Darwin.targets, + linkMode = linkMode, + buildConfiguration = configuration, + ) + } + } + + runGradle( + arguments = gradleArguments, + assertResult = assertGradleResult, + ) + } + + override fun StringBuilderScope.appendAdditionalGradleProperties() { + +"org.gradle.configuration-cache=true\n" + } + +} diff --git a/test-runner/src/test/kotlin/co/touchlab/skie/test/util/StringBuilderScope.kt b/test-runner/src/test/kotlin/co/touchlab/skie/test/util/StringBuilderScope.kt index b8c8df7c7..27cb0b802 100644 --- a/test-runner/src/test/kotlin/co/touchlab/skie/test/util/StringBuilderScope.kt +++ b/test-runner/src/test/kotlin/co/touchlab/skie/test/util/StringBuilderScope.kt @@ -4,4 +4,10 @@ interface StringBuilderScope { operator fun String.unaryPlus() operator fun StringBuilder.unaryPlus() + + operator fun Iterable.unaryPlus() { + this.forEach { + +it + } + } }