Skip to content

Commit

Permalink
Introduced integration tests for JVM and MPP projects
Browse files Browse the repository at this point in the history
  • Loading branch information
mvicsokolova committed Oct 12, 2023
1 parent 2b86fbf commit ae77f31
Show file tree
Hide file tree
Showing 20 changed files with 474 additions and 4 deletions.
10 changes: 7 additions & 3 deletions integration-testing/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

buildscript {

/*
Expand Down Expand Up @@ -59,8 +58,11 @@ kotlin {
}

dependencies {
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation gradleTestKit()
api "org.ow2.asm:asm:$asm_version"
api "org.ow2.asm:asm-commons:$asm_version"
}

sourceSets {
Expand All @@ -80,4 +82,6 @@ task mavenTest(type: Test) {
def sourceSet = sourceSets.mavenTest
testClassesDirs = sourceSet.output.classesDirs
classpath = sourceSet.runtimeClasspath
}
}

// todo: set atomicfu system property
28 changes: 28 additions & 0 deletions integration-testing/examples/jvm-sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
buildscript {
val atomicfu_version = rootProject.properties["atomicfu_version"]

repositories {
mavenLocal()
}

dependencies {
classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version")
}
}

plugins {
kotlin("jvm") version "1.9.10" // todo get kotlin_version from gradle.properties
}

apply(plugin = "kotlinx-atomicfu")

repositories {
mavenLocal()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
}

dependencies {
implementation(kotlin("stdlib"))
implementation(kotlin("test-junit"))
}
2 changes: 2 additions & 0 deletions integration-testing/examples/jvm-sample/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
kotlin_version=1.9.0
atomicfu_version=0.22.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "jvm-sample"
19 changes: 19 additions & 0 deletions integration-testing/examples/jvm-sample/src/main/kotlin/Sample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

import kotlinx.atomicfu.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class IntArithmetic {
private val _x = atomic(0)
val x get() = _x.value

fun doWork(finalValue: Int) {
assertEquals(0, x)
assertEquals(0, _x.getAndSet(3))
assertEquals(3, x)
assertTrue(_x.compareAndSet(3, finalValue))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

import kotlin.test.*

class ArithmeticTest {
@Test
fun testInt() {
val a = IntArithmetic()
a.doWork(1234)
assertEquals(1234, a.x)
}
}
41 changes: 41 additions & 0 deletions integration-testing/examples/mpp-sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

buildscript {
val atomicfu_version = rootProject.properties["atomicfu_version"]

repositories {
mavenLocal()
mavenCentral()
}

dependencies {
classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version")
}
}

plugins {
kotlin("multiplatform") version "1.9.10" // todo get kotlin_version from gradle.proeprties
}

apply(plugin = "kotlinx-atomicfu")

repositories {
mavenLocal()
mavenCentral()
}

kotlin {
jvm()
macosX64("native")

sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}
2 changes: 2 additions & 0 deletions integration-testing/examples/mpp-sample/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
kotlin_version=1.9.0
atomicfu_version=0.22.0-SNAPSHOT
9 changes: 9 additions & 0 deletions integration-testing/examples/mpp-sample/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pluginManagement {
repositories {
mavenLocal()
mavenCentral()
gradlePluginPortal()
}
}

rootProject.name = "mpp-sample"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import kotlinx.atomicfu.*

class IntArithmetic {
private val _x = atomic(0)
val x get() = _x.value

fun doWork(finalValue: Int) {
check(x == 0)
_x.getAndSet(3)
check(x == 3)
_x.compareAndSet(3, finalValue)
check(x == finalValue)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

import kotlin.test.*

class IntArithmeticTest {

@Test
fun testInt() {
val a = IntArithmetic()
a.doWork(1234)
assertEquals(1234, a.x)
}
}
2 changes: 1 addition & 1 deletion integration-testing/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
kotlin_version=1.9.0
atomicfu_version=0.22.0-SNAPSHOT

asm_version=9.3
kotlin.code.style=official
kotlin.mpp.stability.nowarn=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.atomicfu.gradle.plugin.test.cases

import kotlinx.atomicfu.gradle.plugin.test.framework.checker.*
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.*
import kotlin.test.Test

class JvmProjectTest {

private val jvmSample: GradleBuild = createGradleBuildFromSources("jvm-sample")

@Test
fun testJvmWithEnabledIrTransformation() {
jvmSample.enableJvmIrTransformation = true
jvmSample.checkJvmCompileOnlyDependencies()
jvmSample.buildAndCheckBytecode()
}

@Test
fun testJvmWithDisabledIrTransformation() {
jvmSample.enableJvmIrTransformation = false
jvmSample.checkJvmCompileOnlyDependencies()
jvmSample.buildAndCheckBytecode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package test

import kotlinx.atomicfu.gradle.plugin.test.framework.checker.*
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.*
import kotlin.test.Test

class MppProjectTest {
private val mppSample: GradleBuild = createGradleBuildFromSources("mpp-sample")

@Test
fun testMppJvm1() {
mppSample.enableJvmIrTransformation = true
mppSample.checkMppJvmCompileOnlyDependencies()
mppSample.buildAndCheckBytecode()
}

@Test
fun testMppJvm2() {
mppSample.enableJvmIrTransformation = false
mppSample.checkMppJvmCompileOnlyDependencies()
mppSample.buildAndCheckBytecode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.atomicfu.gradle.plugin.test.framework.checker

import kotlinx.atomicfu.gradle.plugin.test.framework.runner.GradleBuild
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.build
import org.objectweb.asm.*
import java.io.File
import kotlin.test.assertFalse

internal abstract class ArtifactChecker(val targetDir: File) {

private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
protected val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"

protected val projectName = targetDir.name.substringBeforeLast("-")

val buildDir
get() = targetDir.resolve("build").also {
require(it.exists() && it.isDirectory) { "Could not find `build/` directory in the target directory of the project $projectName: ${targetDir.path}" }
}

abstract fun checkReferences()

protected fun ByteArray.findAtomicfuRef(): Boolean {
loop@for (i in 0 until this.size - ATOMIC_FU_REF.size) {
for (j in ATOMIC_FU_REF.indices) {
if (this[i + j] != ATOMIC_FU_REF[j]) continue@loop
}
return true
}
return false
}
}

private class BytecodeChecker(targetDir: File) : ArtifactChecker(targetDir) {

override fun checkReferences() {
val atomicfuDir = buildDir.resolve("classes/atomicfu/")
(if (atomicfuDir.exists() && atomicfuDir.isDirectory) atomicfuDir else buildDir).let {
it.walkBottomUp().filter { it.isFile && it.name.endsWith(".class") }.forEach { clazz ->
assertFalse(clazz.readBytes().eraseMetadata().findAtomicfuRef(), "Found kotlinx/atomicfu in class file ${clazz.path}")
}
}
}

// The atomicfu compiler plugin does not remove atomic properties from metadata,
// so for now we check that there are no ATOMIC_FU_REF left in the class bytecode excluding metadata.
// This may be reverted after the fix in the compiler plugin transformer (See #254).
private fun ByteArray.eraseMetadata(): ByteArray {
val cw = ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
ClassReader(this).accept(object : ClassVisitor(Opcodes.ASM9, cw) {
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
return if (descriptor == KOTLIN_METADATA_DESC) null else super.visitAnnotation(descriptor, visible)
}
}, ClassReader.SKIP_FRAMES)
return cw.toByteArray()
}
}

internal fun GradleBuild.buildAndCheckBytecode() {
val buildResult = build()
require(buildResult.isSuccessful) { "Build of the project $projectName failed:\n ${buildResult.output}" }
BytecodeChecker(this.targetDir).checkReferences()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.atomicfu.gradle.plugin.test.framework.checker

import kotlinx.atomicfu.gradle.plugin.test.framework.runner.BuildResult
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.GradleBuild
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.atomicfuVersion
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.dependencies

private const val COMPILE_CLASSPATH = "compileClasspath"
private const val RUNTIME_CLASSPATH = "runtimeClasspath"
private val jvmAtomicfuDependency = "org.jetbrains.kotlinx:atomicfu-jvm:$atomicfuVersion"
private val commonAtomicfuDependency = "org.jetbrains.kotlinx:atomicfu:$atomicfuVersion"

private class DependenciesChecker(
private val buildResult: BuildResult,
private val dependencies: List<String>
) {
fun checkCompileOnly(compileConfigurations: List<String>, runtimeConfigurations: List<String>) {
val compileClasspath = buildResult.getDependencies(compileConfigurations)
val runtimeClasspath = buildResult.getDependencies(runtimeConfigurations)
for (dep in dependencies) {
check(compileClasspath.contains(dep)) { "Expected compileOnly dependency $dep was not found in the compileClasspath: $compileClasspath" }
check(!runtimeClasspath.contains(dep)) { "Dependency $dep should be compileOnly, but it was found in the runtimeClasspath: $runtimeClasspath" }
}
}

fun checkImplementation(runtimeConfigurations: List<String>) {
val runtimeClasspath = buildResult.getDependencies(runtimeConfigurations)
for (dep in dependencies) {
check(runtimeClasspath.contains(dep)) { "Expected implementation dependency $dep was not found in the runtimeClasspath: $runtimeClasspath" }
}
}
}

// Checks that a simple non-mpp JVM project does not have atomicfu-jvm dependency in runtime classpath
internal fun GradleBuild.checkJvmCompileOnlyDependencies() {
val checker = DependenciesChecker(dependencies(), listOf(jvmAtomicfuDependency))
checker.checkCompileOnly(listOf(COMPILE_CLASSPATH), listOf(RUNTIME_CLASSPATH))
}

// For MPP project with a JVM target and enabled atomicfu transformation checks that atomicfu-jvm is a compileOnly dependency
internal fun GradleBuild.checkMppJvmCompileOnlyDependencies() {
val checker = DependenciesChecker(dependencies(), listOf(jvmAtomicfuDependency))
checker.checkCompileOnly(
compileConfigurations = listOf("jvmCompileClasspath"),
runtimeConfigurations = listOf("jvmRuntimeClasspath")
)
}
Loading

0 comments on commit ae77f31

Please sign in to comment.