Skip to content

Commit b13018d

Browse files
ndkovalelizarov
andauthored
Add the model checking mode in Lincheck tests (#2326)
* Add the model checking mode to Lincheck tests and use a newer and faster version of it Co-authored-by: Roman Elizarov <[email protected]>
1 parent 7223897 commit b13018d

16 files changed

+197
-128
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
!/.idea/copyright
55
!/.idea/codeStyleSettings.xml
66
!/.idea/codeStyles
7+
!/.idea/dictionaries
78
*.iml
89
.gradle
910
.gradletasknamecache

.idea/dictionaries/shared.xml

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ junit_version=4.12
1212
atomicfu_version=0.14.4
1313
knit_version=0.2.2
1414
html_version=0.6.8
15-
lincheck_version=2.7.1
15+
lincheck_version=2.10
1616
dokka_version=0.9.16-rdev-2-mpp-hacks
1717
byte_buddy_version=1.10.9
1818
reactor_version=3.2.5.RELEASE

kotlinx-coroutines-core/build.gradle

+35-8
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,22 @@ jvmTest {
184184
minHeapSize = '1g'
185185
maxHeapSize = '1g'
186186
enableAssertions = true
187-
systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager'
187+
if (!Idea.active) {
188+
// We should not set this security manager when `jvmTest`
189+
// is invoked by IntelliJ IDEA since we need to pass
190+
// system properties for Lincheck and stress tests.
191+
// TODO Remove once IDEA is smart enough to select between `jvmTest`/`jvmStressTest`/`jvmLincheckTest` #KTIJ-599
192+
systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager'
193+
}
188194
// 'stress' is required to be able to run all subpackage tests like ":jvmTests --tests "*channels*" -Pstress=true"
189195
if (!Idea.active && rootProject.properties['stress'] == null) {
196+
exclude '**/*LincheckTest.*'
190197
exclude '**/*StressTest.*'
191198
}
192-
systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test
193-
199+
if (Idea.active) {
200+
// Configure the IDEA runner for Lincheck
201+
configureJvmForLincheck(jvmTest)
202+
}
194203
// TODO: JVM IR generates different stacktrace so temporary disable stacktrace tests
195204
if (rootProject.ext.jvm_ir_enabled) {
196205
filter {
@@ -219,23 +228,41 @@ task jvmStressTest(type: Test, dependsOn: compileTestKotlinJvm) {
219228
systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '10'
220229
}
221230

231+
task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) {
232+
classpath = files { jvmTest.classpath }
233+
testClassesDirs = files { jvmTest.testClassesDirs }
234+
include '**/*LincheckTest.*'
235+
enableAssertions = true
236+
testLogging.showStandardStreams = true
237+
configureJvmForLincheck(jvmLincheckTest)
238+
}
239+
240+
static void configureJvmForLincheck(task) {
241+
task.minHeapSize = '1g'
242+
task.maxHeapSize = '6g' // we may need more space for building an interleaving tree in the model checking mode
243+
task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', // required for transformation
244+
'--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
245+
task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2'
246+
task.systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '1' // better for the model checking mode
247+
}
248+
222249
task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) {
223250
classpath = files { jvmTest.classpath }
224251
testClassesDirs = files { jvmTest.testClassesDirs }
225252
executable = "$System.env.JDK_16/bin/java"
226253
exclude '**/*LFStressTest.*' // lock-freedom tests use LockFreedomTestEnvironment which needs JDK8
227-
exclude '**/*LCStressTest.*' // lin-check tests use LinChecker which needs JDK8
254+
exclude '**/*LincheckTest.*' // Lincheck tests use LinChecker which needs JDK8
228255
exclude '**/exceptions/**' // exceptions tests check suppressed exception which needs JDK8
229256
exclude '**/ExceptionsGuideTest.*'
230257
exclude '**/RunInterruptibleStressTest.*' // fails on JDK 1.6 due to JDK bug
231258
}
232259

233-
// Run these tests only during nightly stress test
260+
// Run jdk16Test test only during nightly stress test
234261
jdk16Test.onlyIf { project.properties['stressTest'] != null }
235262

236-
// Always run those tests
237-
task moreTest(dependsOn: [jvmStressTest, jdk16Test])
238-
build.dependsOn moreTest
263+
// Always check additional test sets
264+
task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jdk16Test])
265+
check.dependsOn moreTest
239266

240267
task testsJar(type: Jar, dependsOn: jvmTestClasses) {
241268
classifier = 'tests'

kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ internal open class CancellableContinuationImpl<in T>(
8585

8686
public override val isCancelled: Boolean get() = state is CancelledContinuation
8787

88+
// We cannot invoke `state.toString()` since it may cause a circular dependency
89+
private val stateDebugRepresentation get() = when(state) {
90+
is NotCompleted -> "Active"
91+
is CancelledContinuation -> "Cancelled"
92+
else -> "Completed"
93+
}
94+
8895
public override fun initCancellability() {
8996
setupCancellation()
9097
}
@@ -503,7 +510,7 @@ internal open class CancellableContinuationImpl<in T>(
503510

504511
// For nicer debugging
505512
public override fun toString(): String =
506-
"${nameString()}(${delegate.toDebugString()}){$state}@$hexAddress"
513+
"${nameString()}(${delegate.toDebugString()}){$stateDebugRepresentation}@$hexAddress"
507514

508515
protected open fun nameString(): String =
509516
"CancellableContinuation"

kotlinx-coroutines-core/common/src/internal/Symbol.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ package kotlinx.coroutines.internal
1010
* @suppress **This is unstable API and it is subject to change.**
1111
*/
1212
internal class Symbol(val symbol: String) {
13-
override fun toString(): String = symbol
13+
override fun toString(): String = "<$symbol>"
1414

1515
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
1616
inline fun <T> unbox(value: Any?): T = if (value === this) null as T else value as T
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package kotlinx.coroutines
5+
6+
import org.jetbrains.kotlinx.lincheck.*
7+
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
8+
import org.jetbrains.kotlinx.lincheck.strategy.stress.*
9+
import org.jetbrains.kotlinx.lincheck.verifier.*
10+
import org.junit.*
11+
12+
abstract class AbstractLincheckTest : VerifierState() {
13+
open fun <O: Options<O, *>> O.customize(isStressTest: Boolean): O = this
14+
open fun ModelCheckingOptions.customize(isStressTest: Boolean): ModelCheckingOptions = this
15+
open fun StressOptions.customize(isStressTest: Boolean): StressOptions = this
16+
17+
@Test
18+
fun modelCheckingTest() = ModelCheckingOptions()
19+
.iterations(if (isStressTest) 100 else 20)
20+
.invocationsPerIteration(if (isStressTest) 10_000 else 1_000)
21+
.commonConfiguration()
22+
.customize(isStressTest)
23+
.check(this::class)
24+
25+
@Test
26+
fun stressTest() = StressOptions()
27+
.iterations(if (isStressTest) 100 else 20)
28+
.invocationsPerIteration(if (isStressTest) 10_000 else 1_000)
29+
.commonConfiguration()
30+
.customize(isStressTest)
31+
.check(this::class)
32+
33+
private fun <O : Options<O, *>> O.commonConfiguration(): O = this
34+
.actorsBefore(if (isStressTest) 3 else 1)
35+
.threads(3)
36+
.actorsPerThread(if (isStressTest) 4 else 2)
37+
.actorsAfter(if (isStressTest) 3 else 0)
38+
.customize(isStressTest)
39+
40+
override fun extractState(): Any = error("Not implemented")
41+
}

kotlinx-coroutines-core/jvm/test/LCStressOptionsDefault.kt

-20
This file was deleted.

kotlinx-coroutines-core/jvm/test/linearizability/ChannelsLCStressTest.kt renamed to kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt

+16-16
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,45 @@
33
*/
44
@file:Suppress("unused")
55

6-
package kotlinx.coroutines.linearizability
6+
package kotlinx.coroutines.lincheck
77

88
import kotlinx.coroutines.*
99
import kotlinx.coroutines.channels.*
1010
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
1111
import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
1212
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
1313
import kotlinx.coroutines.selects.*
14+
import org.jetbrains.kotlinx.lincheck.*
1415
import org.jetbrains.kotlinx.lincheck.annotations.*
1516
import org.jetbrains.kotlinx.lincheck.annotations.Operation
1617
import org.jetbrains.kotlinx.lincheck.paramgen.*
1718
import org.jetbrains.kotlinx.lincheck.verifier.*
18-
import org.junit.*
1919

20-
class RendezvousChannelLCStressTest : ChannelLCStressTestBase(
20+
class RendezvousChannelLincheckTest : ChannelLincheckTestBase(
2121
c = Channel(RENDEZVOUS),
2222
sequentialSpecification = SequentialRendezvousChannel::class.java
2323
)
2424
class SequentialRendezvousChannel : SequentialIntChannelBase(RENDEZVOUS)
2525

26-
class Array1ChannelLCStressTest : ChannelLCStressTestBase(
26+
class Array1ChannelLincheckTest : ChannelLincheckTestBase(
2727
c = Channel(1),
2828
sequentialSpecification = SequentialArray1RendezvousChannel::class.java
2929
)
3030
class SequentialArray1RendezvousChannel : SequentialIntChannelBase(1)
3131

32-
class Array2ChannelLCStressTest : ChannelLCStressTestBase(
32+
class Array2ChannelLincheckTest : ChannelLincheckTestBase(
3333
c = Channel(2),
3434
sequentialSpecification = SequentialArray2RendezvousChannel::class.java
3535
)
3636
class SequentialArray2RendezvousChannel : SequentialIntChannelBase(2)
3737

38-
class UnlimitedChannelLCStressTest : ChannelLCStressTestBase(
38+
class UnlimitedChannelLincheckTest : ChannelLincheckTestBase(
3939
c = Channel(UNLIMITED),
4040
sequentialSpecification = SequentialUnlimitedChannel::class.java
4141
)
4242
class SequentialUnlimitedChannel : SequentialIntChannelBase(UNLIMITED)
4343

44-
class ConflatedChannelLCStressTest : ChannelLCStressTestBase(
44+
class ConflatedChannelLincheckTest : ChannelLincheckTestBase(
4545
c = Channel(CONFLATED),
4646
sequentialSpecification = SequentialConflatedChannel::class.java
4747
)
@@ -51,8 +51,11 @@ class SequentialConflatedChannel : SequentialIntChannelBase(CONFLATED)
5151
Param(name = "value", gen = IntGen::class, conf = "1:5"),
5252
Param(name = "closeToken", gen = IntGen::class, conf = "1:3")
5353
)
54-
abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val sequentialSpecification: Class<*>) {
55-
@Operation
54+
abstract class ChannelLincheckTestBase(
55+
private val c: Channel<Int>,
56+
private val sequentialSpecification: Class<*>
57+
) : AbstractLincheckTest() {
58+
@Operation(promptCancellation = true)
5659
suspend fun send(@Param(name = "value") value: Int): Any = try {
5760
c.send(value)
5861
} catch (e: NumberedCancellationException) {
@@ -74,7 +77,7 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
7477
e.testResult
7578
}
7679

77-
@Operation
80+
@Operation(promptCancellation = true)
7881
suspend fun receive(): Any = try {
7982
c.receive()
8083
} catch (e: NumberedCancellationException) {
@@ -96,7 +99,7 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
9699
e.testResult
97100
}
98101

99-
@Operation
102+
@Operation(causesBlocking = true)
100103
fun close(@Param(name = "closeToken") token: Int): Boolean = c.close(NumberedCancellationException(token))
101104

102105
// TODO: this operation should be (and can be!) linearizable, but is not
@@ -113,11 +116,8 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
113116
// @Operation
114117
fun isEmpty() = c.isEmpty
115118

116-
@Test
117-
fun test() = LCStressOptionsDefault()
118-
.actorsBefore(0)
119-
.sequentialSpecification(sequentialSpecification)
120-
.check(this::class)
119+
override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O =
120+
actorsBefore(0).sequentialSpecification(sequentialSpecification)
121121
}
122122

123123
private class NumberedCancellationException(number: Int) : CancellationException() {

kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt renamed to kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt

+6-7
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@
33
*/
44
@file:Suppress("unused")
55

6-
package kotlinx.coroutines.linearizability
6+
package kotlinx.coroutines.lincheck
77

88
import kotlinx.coroutines.*
99
import kotlinx.coroutines.internal.*
1010
import org.jetbrains.kotlinx.lincheck.annotations.*
1111
import org.jetbrains.kotlinx.lincheck.annotations.Operation
1212
import org.jetbrains.kotlinx.lincheck.paramgen.*
13-
import org.jetbrains.kotlinx.lincheck.verifier.*
14-
import kotlin.test.*
13+
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
1514

1615
@Param(name = "value", gen = IntGen::class, conf = "1:5")
17-
class LockFreeListLCStressTest : VerifierState() {
16+
class LockFreeListLincheckTest : AbstractLincheckTest() {
1817
class Node(val value: Int): LockFreeLinkedListNode()
1918

2019
private val q: LockFreeLinkedListHead = LockFreeLinkedListHead()
@@ -43,12 +42,12 @@ class LockFreeListLCStressTest : VerifierState() {
4342

4443
private fun Any.isSame(value: Int) = this is Node && this.value == value
4544

46-
@Test
47-
fun testAddRemoveLinearizability() = LCStressOptionsDefault().check(this::class)
48-
4945
override fun extractState(): Any {
5046
val elements = ArrayList<Int>()
5147
q.forEach<Node> { elements.add(it.value) }
5248
return elements
5349
}
50+
51+
override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
52+
checkObstructionFreedom()
5453
}

kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt renamed to kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt

+20-14
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@
33
*/
44
@file:Suppress("unused")
55

6-
package kotlinx.coroutines.linearizability
6+
package kotlinx.coroutines.lincheck
77

88
import kotlinx.coroutines.*
99
import kotlinx.coroutines.internal.*
10+
import org.jetbrains.kotlinx.lincheck.*
1011
import org.jetbrains.kotlinx.lincheck.annotations.*
1112
import org.jetbrains.kotlinx.lincheck.annotations.Operation
1213
import org.jetbrains.kotlinx.lincheck.paramgen.*
13-
import org.jetbrains.kotlinx.lincheck.verifier.*
14+
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
1415
import org.jetbrains.kotlinx.lincheck.verifier.quiescent.*
15-
import kotlin.test.*
1616

1717
@Param(name = "value", gen = IntGen::class, conf = "1:3")
18-
internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLCStressTest protected constructor(singleConsumer: Boolean) : VerifierState() {
18+
internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(
19+
val singleConsumer: Boolean
20+
) : AbstractLincheckTest() {
1921
@JvmField
2022
protected val q = LockFreeTaskQueue<Int>(singleConsumer = singleConsumer)
2123

@@ -25,20 +27,24 @@ internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLCStressTest prote
2527
@Operation
2628
fun addLast(@Param(name = "value") value: Int) = q.addLast(value)
2729

28-
@QuiescentConsistent
29-
@Operation(group = "consumer")
30-
fun removeFirstOrNull() = q.removeFirstOrNull()
30+
override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O =
31+
verifier(QuiescentConsistencyVerifier::class.java)
3132

3233
override fun extractState() = q.map { it } to q.isClosed()
3334

34-
@Test
35-
fun testWithRemoveForQuiescentConsistency() = LCStressOptionsDefault()
36-
.verifier(QuiescentConsistencyVerifier::class.java)
37-
.check(this::class)
35+
override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
36+
checkObstructionFreedom()
3837
}
3938

40-
@OpGroupConfig(name = "consumer", nonParallel = false)
41-
internal class MCLockFreeTaskQueueWithRemoveLCStressTest : AbstractLockFreeTaskQueueWithoutRemoveLCStressTest(singleConsumer = false)
39+
internal class MCLockFreeTaskQueueWithRemoveLincheckTest : AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(singleConsumer = false) {
40+
@QuiescentConsistent
41+
@Operation(blocking = true)
42+
fun removeFirstOrNull() = q.removeFirstOrNull()
43+
}
4244

4345
@OpGroupConfig(name = "consumer", nonParallel = true)
44-
internal class SCLockFreeTaskQueueWithRemoveLCStressTest : AbstractLockFreeTaskQueueWithoutRemoveLCStressTest(singleConsumer = true)
46+
internal class SCLockFreeTaskQueueWithRemoveLincheckTest : AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(singleConsumer = true) {
47+
@QuiescentConsistent
48+
@Operation(group = "consumer")
49+
fun removeFirstOrNull() = q.removeFirstOrNull()
50+
}

0 commit comments

Comments
 (0)