Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed multiple jvm targets #62

Merged
merged 8 commits into from
Oct 4, 2021
34 changes: 34 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,48 @@ tasks.register<Test>("functionalTest") {
}
tasks.check { dependsOn(tasks["functionalTest"]) }

// While gradle testkit supports injection of the plugin classpath it doesn't allow using dependency notation
// to determine the actual runtime classpath for the plugin. It uses isolation, so plugins applied by the build
// script are not visible in the plugin classloader. This means optional dependencies (dependent on applied plugins -
// for example kotlin multiplatform) are not visible even if they are in regular gradle use. This hack will allow
// extending the classpath. It is based upon: https://docs.gradle.org/6.0/userguide/test_kit.html#sub:test-kit-classpath-injection

// Create a configuration to register the dependencies against
val testPluginRuntimeConfiguration = configurations.register("testPluginRuntime")

// The task that will create a file that stores the classpath needed for the plugin to have additional runtime dependencies
// This file is then used in to tell TestKit which classpath to use.
val createClasspathManifest = tasks.register("createClasspathManifest") {
val outputDir = buildDir.resolve("cpManifests")
inputs.files(testPluginRuntimeConfiguration)
.withPropertyName("runtimeClasspath")
.withNormalizer(ClasspathNormalizer::class)

outputs.dir(outputDir)
.withPropertyName("outputDir")

doLast {
outputDir.mkdirs()
file(outputDir.resolve("plugin-classpath.txt")).writeText(testPluginRuntimeConfiguration.get().joinToString("\n"))
}
}

val kotlinVersion: String by project

dependencies {
implementation(gradleApi())
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0")
implementation("org.ow2.asm:asm:9.0")
implementation("org.ow2.asm:asm-tree:9.0")
implementation("com.googlecode.java-diff-utils:diffutils:1.3.0")
compileOnly("org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:1.3.61")

// The test needs the full kotlin multiplatform plugin loaded as it has no visibility of previously loaded plugins,
// unlike the regular way gradle loads plugins.
add(testPluginRuntimeConfiguration.name, "org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:$kotlinVersion")

testImplementation(kotlin("test-junit"))
"functionalTestImplementation"(files(createClasspathManifest))

"functionalTestImplementation"("org.assertj:assertj-core:3.18.1")
"functionalTestImplementation"(gradleTestKit())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2016-2021 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

package kotlinx.validation.api

import org.gradle.testkit.runner.GradleRunner
import java.io.File
import java.io.InputStreamReader


fun GradleRunner.addPluginTestRuntimeClasspath() = apply {

val cpResource = javaClass.classLoader.getResourceAsStream("plugin-classpath.txt")
?.let { InputStreamReader(it) }
?: throw IllegalStateException("Could not find classpath resource")

val pluginClasspath = pluginClasspath + cpResource.readLines().map { File(it) }
withPluginClasspath(pluginClasspath)

}
6 changes: 3 additions & 3 deletions src/functionalTest/kotlin/kotlinx/validation/api/testDsl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ internal fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRu
}

/**
* same as [file][FileContainer.file], but prepends "src/main/java" before given `classFileName`
* same as [file][FileContainer.file], but prepends "src/${sourceSet}/kotlin" before given `classFileName`
*/
internal fun FileContainer.kotlin(classFileName: String, fn: AppendableScope.() -> Unit) {
internal fun FileContainer.kotlin(classFileName: String, sourceSet:String = "main", fn: AppendableScope.() -> Unit) {
require(classFileName.endsWith(".kt")) {
"ClassFileName must end with '.kt'"
}

val fileName = "src/main/java/$classFileName"
val fileName = "src/${sourceSet}/kotlin/$classFileName"
file(fileName, fn)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2016-2021 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

package kotlinx.validation.test

import kotlinx.validation.api.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.io.File

internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() {
private fun BaseKotlinScope.createProjectHierarchyWithPluginOnRoot() {
settingsGradleKts {
resolve("examples/gradle/settings/settings-name-testproject.gradle.kts")
}
buildGradleKts {
resolve("examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts")
}
}

@Test
fun testApiCheckPasses() {
val runner = test {
createProjectHierarchyWithPluginOnRoot()
runner {
arguments.add(":apiCheck")
arguments.add("--stacktrace")
}

dir("api/") {
file("testproject.api") {
resolve("examples/classes/Subsub1Class.dump")
resolve("examples/classes/Subsub2Class.dump")
}
}

dir("src/jvmMain/kotlin") {}
kotlin("Subsub1Class.kt", "commonMain") {
resolve("examples/classes/Subsub1Class.kt")
}
kotlin("Subsub2Class.kt", "jvmMain") {
resolve("examples/classes/Subsub2Class.kt")
}

}.addPluginTestRuntimeClasspath()

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun testApiCheckFails() {
val runner = test {
createProjectHierarchyWithPluginOnRoot()
runner {
arguments.add("--continue")
arguments.add(":check")
arguments.add("--stacktrace")
}

dir("api/") {
file("testproject.api") {
resolve("examples/classes/Subsub2Class.dump")
resolve("examples/classes/Subsub1Class.dump")
}
}

dir("src/jvmMain/kotlin") {}
kotlin("Subsub1Class.kt", "commonMain") {
resolve("examples/classes/Subsub1Class.kt")
}
kotlin("Subsub2Class.kt", "jvmMain") {
resolve("examples/classes/Subsub2Class.kt")
}

}.addPluginTestRuntimeClasspath()

runner.buildAndFail().apply {
assertTaskFailure(":jvmApiCheck")
assertTaskNotRun(":apiCheck")
assertThat(output).contains("API check failed for project testproject")
assertTaskNotRun(":check")
}
}

@Test
fun testApiDumpPasses() {
val runner = test {
createProjectHierarchyWithPluginOnRoot()

runner {
arguments.add(":apiDump")
arguments.add("--stacktrace")
}

dir("src/jvmMain/kotlin") {}
kotlin("Subsub1Class.kt", "commonMain") {
resolve("examples/classes/Subsub1Class.kt")
}
kotlin("Subsub2Class.kt", "jvmMain") {
resolve("examples/classes/Subsub2Class.kt")
}

}.addPluginTestRuntimeClasspath()

runner.build().apply {
assertTaskSuccess(":apiDump")

val commonExpectedApi = readFileList("examples/classes/Subsub1Class.dump")

val mainExpectedApi = commonExpectedApi + "\n" + readFileList("examples/classes/Subsub2Class.dump")
assertThat(jvmApiDump.readText()).isEqualToIgnoringNewLines(mainExpectedApi)
}
}

private val jvmApiDump: File get() = rootProjectDir.resolve("api/testproject.api")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2016-2021 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

package kotlinx.validation.test

import kotlinx.validation.api.*
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.GradleRunner
import org.junit.Test
import java.io.File
import java.io.InputStreamReader

internal class MultipleJvmTargetsTest : BaseKotlinGradleTest() {
private fun BaseKotlinScope.createProjectHierarchyWithPluginOnRoot() {
settingsGradleKts {
resolve("examples/gradle/settings/settings-name-testproject.gradle.kts")
}
buildGradleKts {
resolve("examples/gradle/base/multiplatformWithJvmTargets.gradle.kts")
}
}

@Test
fun testApiCheckPasses() {
val runner = test {
createProjectHierarchyWithPluginOnRoot()
runner {
arguments.add(":apiCheck")
}

dir("api/jvm/") {
file("testproject.api") {
resolve("examples/classes/Subsub1Class.dump")
resolve("examples/classes/Subsub2Class.dump")
}
}

dir("api/anotherJvm/") {
file("testproject.api") {
resolve("examples/classes/Subsub1Class.dump")
}
}

dir("src/jvmMain/kotlin") {}
kotlin("Subsub1Class.kt", "commonMain") {
resolve("examples/classes/Subsub1Class.kt")
}
kotlin("Subsub2Class.kt", "jvmMain") {
resolve("examples/classes/Subsub2Class.kt")
}

}.addPluginTestRuntimeClasspath()

runner.build().apply {
assertTaskSuccess(":apiCheck")
assertTaskSuccess(":jvmApiCheck")
assertTaskSuccess(":anotherJvmApiCheck")
}
}

@Test
fun testApiCheckFails() {
val runner = test {
createProjectHierarchyWithPluginOnRoot()
runner {
arguments.add("--continue")
arguments.add(":check")
}

dir("api/jvm/") {
file("testproject.api") {
resolve("examples/classes/Subsub2Class.dump")
resolve("examples/classes/Subsub1Class.dump")
}
}

dir("api/anotherJvm/") {
file("testproject.api") {
resolve("examples/classes/Subsub2Class.dump")
}
}

dir("src/jvmMain/kotlin") {}
kotlin("Subsub1Class.kt", "commonMain") {
resolve("examples/classes/Subsub1Class.kt")
}
kotlin("Subsub2Class.kt", "jvmMain") {
resolve("examples/classes/Subsub2Class.kt")
}

}.addPluginTestRuntimeClasspath()

runner.buildAndFail().apply {
assertTaskNotRun(":apiCheck")
assertTaskFailure(":jvmApiCheck")
assertTaskFailure(":anotherJvmApiCheck")
assertThat(output).contains("API check failed for project testproject")
assertTaskNotRun(":check")
}
}

@Test
fun testApiDumpPasses() {
val runner = test {
createProjectHierarchyWithPluginOnRoot()

runner {
arguments.add(":apiDump")
}

dir("src/jvmMain/kotlin") {}
kotlin("Subsub1Class.kt", "commonMain") {
resolve("examples/classes/Subsub1Class.kt")
}
kotlin("Subsub2Class.kt", "jvmMain") {
resolve("examples/classes/Subsub2Class.kt")
}

}.addPluginTestRuntimeClasspath()
runner.build().apply {
assertTaskSuccess(":apiDump")
assertTaskSuccess(":jvmApiDump")
assertTaskSuccess(":anotherJvmApiDump")

System.err.println(output)

val anotherExpectedApi = readFileList("examples/classes/Subsub1Class.dump")
assertThat(anotherApiDump.readText()).isEqualToIgnoringNewLines(anotherExpectedApi)

val mainExpectedApi = anotherExpectedApi + "\n" + readFileList("examples/classes/Subsub2Class.dump")
assertThat(jvmApiDump.readText()).isEqualToIgnoringNewLines(mainExpectedApi)
}
}

private val jvmApiDump: File get() = rootProjectDir.resolve("api/jvm/testproject.api")
private val anotherApiDump: File get() = rootProjectDir.resolve("api/anotherJvm/testproject.api")

}
Loading