[atomicfu-K/N] Tests for K/N atomicfu-compiler-plugin

* `nativeTest` task now allows to provide compiler plugins that may be enabled during test compilation
* test sets for JVM and K/N backends are equal

KT-60800 describes all the issues with native tests that were solved in this commit.

Co-authored-by: Dmitriy Dolovov <Dmitriy.Dolovov@jetbrains.com>

Merge-request: KT-MR-11401
Merged-by: Maria Sokolova <maria.sokolova@jetbrains.com>
This commit is contained in:
mvicsokolova
2023-08-16 09:41:29 +00:00
committed by Space Team
parent 8f03eb9314
commit 9ff0e0b046
38 changed files with 2644 additions and 33 deletions
@@ -0,0 +1,130 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class IntArithmetic {
private val _x = atomic(0)
val x get() = _x.value
private val local = atomic(0)
private fun testGetValue() {
_x.value = 5
assertEquals(5, _x.value)
var aValue = _x.value
assertEquals(5, aValue)
assertEquals(5, x)
local.value = 555
aValue = local.value
assertEquals(aValue, local.value)
}
private fun testAtomicCallPlaces() {
_x.value = 5
_x.compareAndSet(5, 42)
val res = _x.compareAndSet(42, 45)
assertTrue(res)
assertTrue(_x.compareAndSet(45, 77))
assertFalse(_x.compareAndSet(95, 77))
assertTrue(_x.compareAndSet(77, 88))
}
private fun testInt() {
_x.value = 0
assertEquals(0, x)
val update = 3
assertEquals(0, _x.getAndSet(update))
assertTrue(_x.compareAndSet(update, 8))
_x.lazySet(1)
assertEquals(1, x)
assertEquals(1, _x.getAndSet(2))
assertEquals(2, x)
assertEquals(2, _x.getAndIncrement())
assertEquals(3, x)
assertEquals(3, _x.getAndDecrement())
assertEquals(2, x)
assertEquals(2, _x.getAndAdd(2))
assertEquals(4, x)
assertEquals(7, _x.addAndGet(3))
assertEquals(7, x)
assertEquals(8, _x.incrementAndGet())
assertEquals(8, x)
assertEquals(7, _x.decrementAndGet())
assertEquals(7, x)
assertTrue(_x.compareAndSet(7, 10))
}
fun test() {
testGetValue()
testAtomicCallPlaces()
testInt()
}
}
class LongArithmetic {
private val _x = atomic(4294967296)
val x get() = _x.value
private val y = atomic(5000000000)
private val z = atomic(2424920024888888848)
private val max = atomic(9223372036854775807)
fun testLong() {
assertEquals(2424920024888888848, z.value)
z.lazySet(8424920024888888848)
assertEquals(8424920024888888848, z.value)
assertEquals(8424920024888888848, z.getAndSet(8924920024888888848))
assertEquals(8924920024888888848, z.value)
assertEquals(8924920024888888849, z.incrementAndGet())
assertEquals(8924920024888888849, z.value)
assertEquals(8924920024888888849, z.getAndDecrement())
assertEquals(8924920024888888848, z.value)
assertEquals(8924920024888888848, z.getAndAdd(100000000000000000))
assertEquals(9024920024888888848, z.value)
assertEquals(-198452011965886959, z.addAndGet(-9223372036854775807))
assertEquals(-198452011965886959, z.value)
assertEquals(-198452011965886958, z.incrementAndGet())
assertEquals(-198452011965886958, z.value)
assertEquals(-198452011965886959, z.decrementAndGet())
assertEquals(-198452011965886959, z.value)
}
}
class BooleanArithmetic {
private val _x = atomic(false)
val x get() = _x.value
fun testBoolean() {
assertEquals(false, _x.value)
assertFalse(x)
_x.lazySet(true)
assertTrue(x)
assertTrue(_x.getAndSet(true))
assertTrue(_x.compareAndSet(true, false))
assertFalse(x)
}
}
class ReferenceArithmetic {
private val _x = atomic<String?>(null)
fun testReference() {
_x.value = "aaa"
assertEquals("aaa", _x.value)
_x.lazySet("bb")
assertEquals("bb", _x.value)
assertEquals("bb", _x.getAndSet("ccc"))
assertEquals("ccc", _x.value)
}
}
@Test
fun test() {
val intClass = IntArithmetic()
intClass.test()
val longClass = LongArithmetic()
longClass.testLong()
val booleanClass = BooleanArithmetic()
booleanClass.testBoolean()
val refClass = ReferenceArithmetic()
refClass.testReference()
}
@@ -0,0 +1,109 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class AtomicArrayTest {
fun testIntArray() {
val A = AtomicArrayClass()
assertTrue(A.intArr[0].compareAndSet(0, 3))
assertEquals(0, A.intArr[1].value)
A.intArr[0].lazySet(5)
assertEquals(5, A.intArr[0].value + A.intArr[1].value + A.intArr[2].value)
assertTrue(A.intArr[0].compareAndSet(5, 10))
assertEquals(10, A.intArr[0].getAndDecrement())
assertEquals(9, A.intArr[0].value)
A.intArr[2].value = 2
assertEquals(2, A.intArr[2].value)
assertTrue(A.intArr[2].compareAndSet(2, 34))
assertEquals(34, A.intArr[2].value)
}
fun testLongArray() {
val A = AtomicArrayClass()
A.longArr[0].value = 2424920024888888848
assertEquals(2424920024888888848, A.longArr[0].value)
A.longArr[0].lazySet(8424920024888888848)
assertEquals(8424920024888888848, A.longArr[0].value)
val ac = A.longArr[0].value
A.longArr[3].value = ac
assertEquals(8424920024888888848, A.longArr[3].getAndSet(8924920024888888848))
assertEquals(8924920024888888848, A.longArr[3].value)
val ac1 = A.longArr[3].value
A.longArr[4].value = ac1
assertEquals(8924920024888888849, A.longArr[4].incrementAndGet())
assertEquals(8924920024888888849, A.longArr[4].value)
assertEquals(8924920024888888849, A.longArr[4].getAndDecrement())
assertEquals(8924920024888888848, A.longArr[4].value)
A.longArr[4].value = 8924920024888888848
assertEquals(8924920024888888848, A.longArr[4].getAndAdd(100000000000000000))
val ac2 = A.longArr[4].value
A.longArr[1].value = ac2
assertEquals(9024920024888888848, A.longArr[1].value)
assertEquals(-198452011965886959, A.longArr[1].addAndGet(-9223372036854775807))
assertEquals(-198452011965886959, A.longArr[1].value)
assertEquals(-198452011965886958, A.longArr[1].incrementAndGet())
assertEquals(-198452011965886958, A.longArr[1].value)
assertEquals(-198452011965886959, A.longArr[1].decrementAndGet())
assertEquals(-198452011965886959, A.longArr[1].value)
}
fun testBooleanArray() {
val A = AtomicArrayClass()
assertFalse(A.booleanArr[1].value)
assertTrue(A.booleanArr[1].compareAndSet(false, true))
A.booleanArr[0].lazySet(true)
assertFalse(A.booleanArr[2].getAndSet(true))
assertTrue(A.booleanArr[0].value && A.booleanArr[1].value && A.booleanArr[2].value)
A.booleanArr[0].value = false
assertFalse(A.booleanArr[0].value)
}
fun testRefArray() {
val A = AtomicArrayClass()
val a2 = ARef(2)
val a3 = ARef(3)
A.refArr[0].value = a2
assertEquals(2, A.refArr[0].value!!.n)
assertTrue(A.refArr[0].compareAndSet(a2, a3))
assertEquals(3, A.refArr[0].value!!.n)
val r0 = A.refArr[0].value
A.refArr[3].value = r0
assertEquals(3, A.refArr[3].value!!.n)
val a = A.a.value
assertTrue(A.refArr[3].compareAndSet(a3, a))
}
fun testAnyArray() {
val A = AtomicArrayClass()
val s1 = "aaa"
val s2 = "bbb"
A.anyArr[0].value = s1
assertEquals("aaa", A.anyArr[0].value)
assertTrue(A.anyArr[0].compareAndSet(s1, s2))
assertEquals("bbb", A.anyArr[0].value)
val r0 = A.anyArr[0].value
A.anyArr[3].value = r0
assertEquals("bbb", A.anyArr[3].value)
}
}
private class AtomicArrayClass {
val intArr = AtomicIntArray(10)
val longArr = AtomicLongArray(10)
val booleanArr = AtomicBooleanArray(10)
val refArr = atomicArrayOfNulls<ARef>(10)
val anyArr = atomicArrayOfNulls<Any?>(10)
internal val a = atomic(ARef(8))
}
data class ARef(val n: Int)
@Test
fun test() {
val testClass = AtomicArrayTest()
testClass.testIntArray()
testClass.testLongArray()
testClass.testBooleanArray()
testClass.testRefArray()
testClass.testAnyArray()
}
@@ -0,0 +1,32 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class IndexArrayElementGetterTest {
private val clazz = AtomicArrayClass()
fun fib(a: Int): Int = if (a == 0 || a == 1) a else fib(a - 1) + fib(a - 2)
fun testIndexArrayElementGetting() {
clazz.intArr[8].value = 3
val i = fib(4)
val j = fib(5)
assertEquals(3, clazz.intArr[i + j].value)
assertEquals(3, clazz.intArr[fib(4) + fib(5)].value)
clazz.longArr[3].value = 100
assertEquals(100, clazz.longArr[fib(6) - fib(5)].value)
assertEquals(100, clazz.longArr[(fib(6) + fib(4)) % 8].value)
assertEquals(100, clazz.longArr[(fib(6) + fib(4)) % 8].value)
assertEquals(100, clazz.longArr[(fib(4) + fib(5)) % fib(5)].value)
}
private class AtomicArrayClass {
val intArr = AtomicIntArray(10)
val longArr = AtomicLongArray(10)
}
}
@Test
fun box() {
val testClass = IndexArrayElementGetterTest()
testClass.testIndexArrayElementGetting()
}
@@ -0,0 +1,33 @@
import kotlinx.atomicfu.*
import kotlin.test.*
private class AAA {
private val _counter = atomic(5L)
val counterValue: Long get() = _counter.value
val delegateCounterValue by _counter
val lateInitInt: AtomicInt
val intArr: AtomicIntArray
// test ensures that transformation does not change the order of initialization
init {
lateInitInt = atomic(10)
assertTrue(lateInitInt.compareAndSet(10, 100))
assertEquals(100, lateInitInt.value)
intArr = AtomicIntArray(10)
intArr[0].value = 10
assertTrue(intArr[0].compareAndSet(10, 100))
intArr[1].value = 20
}
init {
assertEquals(5L, _counter.value)
assertEquals(5L,counterValue)
assertEquals(5L, delegateCounterValue)
assertEquals(120, intArr[0].value + intArr[1].value)
}
}
@Test
fun box() {
val intClass = AAA()
}
@@ -0,0 +1,38 @@
import kotlinx.atomicfu.*
import kotlinx.atomicfu.locks.*
import kotlin.test.*
class LateinitPropertiesTest {
private val a: AtomicInt
private val head: AtomicRef<String>
private val dataRef: AtomicRef<Data>
private val lateIntArr: AtomicIntArray
private val lateRefArr: AtomicArray<String?>
private class Data(val n: Int)
init {
a = atomic(0)
head = atomic("AAA")
lateIntArr = AtomicIntArray(55)
val data = Data(77)
dataRef = atomic(data)
val size = 10
lateRefArr = atomicArrayOfNulls<String?>(size)
}
fun test() {
assertEquals(0, a.value)
assertTrue(head.compareAndSet("AAA", "BBB"))
assertEquals("BBB", head.value)
assertEquals(0, lateIntArr[35].value)
assertEquals(77, dataRef.value.n)
assertEquals(null, lateRefArr[5].value)
}
}
@Test
fun box() {
val testClass = LateinitPropertiesTest()
testClass.test()
}
@@ -0,0 +1,61 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LockFreeLongCounterTest {
private inline fun testWith(g: LockFreeLongCounter.() -> Long) {
val c = LockFreeLongCounter()
assertEquals(0L, c.g())
assertEquals(1L, c.increment())
assertEquals(1L, c.g())
assertEquals(2L, c.increment())
assertEquals(2L, c.g())
}
fun testBasic() = testWith { get() }
fun testGetInner() = testWith { getInner() }
fun testAdd2() {
val c = LockFreeLongCounter()
c.add2()
assertEquals(2L, c.get())
c.add2()
assertEquals(4L, c.get())
}
fun testSetM2() {
val c = LockFreeLongCounter()
c.setM2()
assertEquals(-2L, c.get())
}
}
class LockFreeLongCounter {
private val counter = atomic(0L)
fun get(): Long = counter.value
fun increment(): Long = counter.incrementAndGet()
fun add2() = counter.getAndAdd(2)
fun setM2() {
counter.value = -2L // LDC instruction here
}
fun getInner(): Long = Inner().getFromOuter()
// testing how an inner class can get access to it
private inner class Inner {
fun getFromOuter(): Long = counter.value
}
}
@Test
fun box() {
val testClass = LockFreeLongCounterTest()
testClass.testBasic()
testClass.testAdd2()
testClass.testSetM2()
testClass.testGetInner()
}
@@ -0,0 +1,55 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LockFreeQueueTest {
fun testBasic() {
val q = LockFreeQueue()
assertEquals(-1, q.dequeue())
q.enqueue(42)
assertEquals(42, q.dequeue())
assertEquals(-1, q.dequeue())
q.enqueue(1)
q.enqueue(2)
assertEquals(1, q.dequeue())
assertEquals(2, q.dequeue())
assertEquals(-1, q.dequeue())
}
}
// MS-queue
public class LockFreeQueue {
private val head = atomic(Node(0))
private val tail = atomic(head.value)
private class Node(val value: Int) {
val next = atomic<Node?>(null)
}
public fun enqueue(value: Int) {
val node = Node(value)
tail.loop { curTail ->
val curNext = curTail.next.value
if (curNext != null) {
tail.compareAndSet(curTail, curNext)
return@loop
}
if (curTail.next.compareAndSet(null, node)) {
tail.compareAndSet(curTail, node)
return
}
}
}
public fun dequeue(): Int {
head.loop { curHead ->
val next = curHead.next.value ?: return -1
if (head.compareAndSet(curHead, next)) return next.value
}
}
}
@Test
fun box() {
val testClass = LockFreeQueueTest()
testClass.testBasic()
}
@@ -0,0 +1,70 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LockFreeStackTest {
fun testClear() {
val s = LockFreeStack<String>()
assertTrue(s.isEmpty())
s.pushLoop("A")
assertTrue(!s.isEmpty())
s.clear()
assertTrue(s.isEmpty())
}
fun testPushPopLoop() {
val s = LockFreeStack<String>()
assertTrue(s.isEmpty())
s.pushLoop("A")
assertTrue(!s.isEmpty())
assertEquals("A", s.popLoop())
assertTrue(s.isEmpty())
}
fun testPushPopUpdate() {
val s = LockFreeStack<String>()
assertTrue(s.isEmpty())
s.pushUpdate("A")
assertTrue(!s.isEmpty())
assertEquals("A", s.popUpdate())
assertTrue(s.isEmpty())
}
}
class LockFreeStack<T> {
private val top = atomic<Node<T>?>(null)
private class Node<T>(val value: T, val next: Node<T>?)
fun isEmpty() = top.value == null
fun clear() { top.value = null }
fun pushLoop(value: T) {
top.loop { cur ->
val upd = Node(value, cur)
if (top.compareAndSet(cur, upd)) return
}
}
fun popLoop(): T? {
top.loop { cur ->
if (cur == null) return null
if (top.compareAndSet(cur, cur.next)) return cur.value
}
}
fun pushUpdate(value: T) {
top.update { cur -> Node(value, cur) }
}
fun popUpdate(): T? =
top.getAndUpdate { cur -> cur?.next } ?.value
}
@Test
fun box() {
val testClass = LockFreeStackTest()
testClass.testClear()
testClass.testPushPopLoop()
testClass.testPushPopUpdate()
}
@@ -0,0 +1,128 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LoopTest {
private val a = atomic(0)
private val a1 = atomic(1)
private val b = atomic(true)
private val l = atomic(5000000000L)
private val r = atomic<A>(A("aaaa"))
private val rs = atomic<String>("bbbb")
class A(val s: String)
fun atomicfuIntLoopTest() {
a.loop { value ->
if (a.compareAndSet(value, 777)) {
assertEquals(777, a.value)
return
}
}
}
fun atomicfuBooleanLoopTest() {
b.loop { value ->
assertTrue(value)
if (!b.value) return
if (b.compareAndSet(value, false)) {
return
}
}
}
fun atomicfuLongLoopTest() {
l.loop { cur ->
if (l.compareAndSet(5000000003, 9000000000)) {
return
} else {
l.incrementAndGet()
}
}
}
fun atomicfuRefLoopTest() {
r.loop { cur ->
assertEquals("aaaa", cur.s)
if (r.compareAndSet(cur, A("bbbb"))) {
return
}
}
}
fun atomicfuLoopTest() {
atomicfuIntLoopTest()
assertEquals(777, a.value)
atomicfuBooleanLoopTest()
assertFalse(b.value)
atomicfuLongLoopTest()
assertEquals(9000000000, l.value)
atomicfuRefLoopTest()
assertEquals("bbbb", r.value.s)
}
fun atomicfuUpdateTest() {
a.value = 0
a.update { value ->
val newValue = value + 1000
if (newValue >= 0) Int.MAX_VALUE else newValue
}
assertEquals(Int.MAX_VALUE, a.value)
b.update { true }
assertEquals(true, b.value)
assertTrue(b.value)
l.value = 0L
l.update { cur ->
val newValue = cur + 1000
if (newValue >= 0L) Long.MAX_VALUE else newValue
}
assertEquals(Long.MAX_VALUE, l.value)
r.lazySet(A("aaaa"))
r.update { cur ->
A("cccc${cur.s}")
}
assertEquals("ccccaaaa", r.value.s)
}
fun atomicfuUpdateAndGetTest() {
val res1 = a.updateAndGet { value ->
if (value >= 0) Int.MAX_VALUE else value
}
assertEquals(Int.MAX_VALUE, res1)
assertEquals(true, b.updateAndGet { true })
assertEquals(Long.MAX_VALUE, l.updateAndGet { cur ->
if (cur >= 0L) Long.MAX_VALUE else cur
})
r.lazySet(A("aaaa"))
val res3 = r.updateAndGet { cur ->
A("cccc${cur.s}")
}
assertEquals("ccccaaaa", res3.s)
}
fun atomicfuGetAndUpdateTest() {
a.getAndUpdate { value ->
if (value >= 0) Int.MAX_VALUE else value
}
assertEquals(Int.MAX_VALUE, a.value)
b.getAndUpdate { true }
assertTrue(b.value)
l.getAndUpdate { cur ->
if (cur >= 0L) Long.MAX_VALUE else cur
}
assertEquals(Long.MAX_VALUE, l.value)
r.lazySet(A("aaaa"))
r.getAndUpdate { cur ->
A("cccc${cur.s}")
}
assertEquals("ccccaaaa", r.value.s)
}
}
@Test
fun box() {
val testClass = LoopTest()
testClass.atomicfuLoopTest()
testClass.atomicfuUpdateTest()
testClass.atomicfuUpdateAndGetTest()
testClass.atomicfuGetAndUpdateTest()
}
@@ -0,0 +1,30 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class MultiInitTest {
fun testBasic() {
val t = MultiInit()
check(t.incA() == 1)
check(t.incA() == 2)
check(t.incB() == 1)
check(t.incB() == 2)
}
}
class MultiInit {
private val a = atomic(0)
private val b = atomic(0)
fun incA() = a.incrementAndGet()
fun incB() = b.incrementAndGet()
companion object {
fun foo() {} // just to force some clinit in outer file
}
}
@Test
fun box() {
val testClass = MultiInitTest()
testClass.testBasic()
}
@@ -0,0 +1,45 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class AA(val value: Int) {
val b = B(value + 1)
val c = C(D(E(value + 1)))
fun updateToB(affected: Any): Boolean {
(affected as AtomicState).state.compareAndSet(this, b)
return (affected.state.value is B && (affected.state.value as B).value == value + 1)
}
fun manyProperties(affected: Any): Boolean {
(affected as AtomicState).state.compareAndSet(this, c.d.e)
return (affected.state.value is E && (affected.state.value as E).x == value + 1)
}
}
class B (val value: Int)
class C (val d: D)
class D (val e: E)
class E (val x: Int)
private class AtomicState(value: Any) {
val state = atomic<Any?>(value)
}
class ScopeTest {
fun scopeTest() {
val a = AA(0)
val affected: Any = AtomicState(a)
check(a.updateToB(affected))
val a1 = AA(0)
val affected1: Any = AtomicState(a1)
check(a1.manyProperties(affected1))
}
}
@Test
fun box() {
val testClass = ScopeTest()
testClass.scopeTest()
}
@@ -0,0 +1,36 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class SimpleLockTest {
fun withLock() {
val lock = SimpleLock()
val result = lock.withLock {
"OK"
}
assertEquals("OK", result)
}
}
class SimpleLock {
private val _locked = atomic(0)
fun <T> withLock(block: () -> T): T {
// this contrieves construct triggers Kotlin compiler to reuse local variable slot #2 for
// the exception in `finally` clause
try {
_locked.loop { locked ->
check(locked == 0)
if (!_locked.compareAndSet(0, 1)) return@loop // continue
return block()
}
} finally {
_locked.value = 0
}
}
}
@Test
fun box() {
val testClass = SimpleLockTest()
testClass.withLock()
}
@@ -0,0 +1,54 @@
import kotlinx.atomicfu.*
import kotlin.test.*
private val topLevelS = atomic<Any>(arrayOf("A", "B"))
class UncheckedCastTest {
private val s = atomic<Any>("AAA")
private val bs = atomic<Any?>(null)
@Suppress("UNCHECKED_CAST")
fun testAtomicValUncheckedCast() {
assertEquals((s as AtomicRef<String>).value, "AAA")
bs.lazySet(arrayOf(arrayOf(Box(1), Box(2))))
assertEquals((bs as AtomicRef<Array<Array<Box>>>).value[0]!![0].b * 10, 10)
}
@Suppress("UNCHECKED_CAST")
fun testTopLevelValUnchekedCast() {
assertEquals((topLevelS as AtomicRef<Array<String>>).value[1], "B")
}
private data class Box(val b: Int)
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
private inline fun <T> AtomicRef<T>.getString(): String =
(this as AtomicRef<String>).value
fun testInlineFunc() {
assertEquals("AAA", s.getString())
}
private val a = atomicArrayOfNulls<Any?>(10)
fun testArrayValueUncheckedCast() {
a[0].value = "OK"
@Suppress("UNCHECKED_CAST")
assertEquals("OK", (a[0] as AtomicRef<String>).value)
}
fun testArrayValueUncheckedCastInlineFunc() {
a[0].value = "OK"
assertEquals("OK", a[0].getString())
}
}
@Test
fun box() {
val testClass = UncheckedCastTest()
testClass.testAtomicValUncheckedCast()
testClass.testTopLevelValUnchekedCast()
testClass.testArrayValueUncheckedCast()
testClass.testArrayValueUncheckedCastInlineFunc()
testClass.testInlineFunc()
}