blob: d417d594aa7aef2d0ed9f69071883dee0bf3fdbb [file] [log] [blame]
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.experimental
import com.devexperts.dxlab.lincheck.Actor
import com.devexperts.dxlab.lincheck.Result
import com.devexperts.dxlab.lincheck.verifier.Verifier
import java.lang.reflect.Method
import java.util.*
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.experimental.EmptyCoroutineContext
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn
data class OpResult(val name: String, val value: Any?) {
override fun toString(): String = "$name=$value"
}
private const val CS_STR = "COROUTINE_SUSPENDED"
class LinTesting {
private val resumed = object : ThreadLocal<ArrayList<OpResult>>() {
override fun initialValue() = arrayListOf<OpResult>()
}
private inline fun wrap(block: () -> Any?): Any? =
try { repr(block()) }
catch(e: Throwable) { repr(e) }
private fun repr(e: Any?): Any? =
when {
e === COROUTINE_SUSPENDED -> CS_STR
e is Throwable -> e.toString()
else -> e
}
fun <T> run(name: String, block: suspend () -> T): List<OpResult> {
val list = resumed.get()
list.clear()
val result = arrayListOf(OpResult(name, wrap {
block.startCoroutineUninterceptedOrReturn(completion = object : Continuation<Any?> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resume(value: Any?) {
resumed.get() += OpResult(name, repr(value))
}
override fun resumeWithException(exception: Throwable) {
resumed.get() += OpResult(name, repr(exception))
}
}
)
}))
result.addAll(list)
return result
}
}
class LinVerifier(
actorsPerThread: List<List<Actor>>, testInstance: Any, resetMethod: Method?
) : Verifier(actorsPerThread, testInstance, resetMethod) {
private val possibleResultsSet: Set<List<List<Result>>> =
generateAllLinearizableExecutions(actorsPerThread)
.map { linEx: List<Actor> ->
val res: List<Result> = executeActors(testInstance, linEx)
val actorIds = linEx.withIndex().associateBy({ it.value}, { it.index })
actorsPerThread.map { actors -> actors.map { actor -> res[actorIds[actor]!!] } }
}.toSet()
override fun verifyResults(results: List<List<Result>>) {
if (!valid(results)) {
println("\nNon-linearizable execution:")
printResults(results)
println("\nPossible linearizable executions:")
possibleResultsSet.forEach { possibleResults ->
printResults(possibleResults)
println()
}
throw AssertionError("Non-linearizable execution detected, see log for details")
}
}
private fun printResults(results: List<List<Result>>) {
results.forEachIndexed { index, res ->
println("Thread $index: $res")
}
println("Op map: ${results.toOpMap()}")
}
private fun valid(results: List<List<Result>>): Boolean =
(results in possibleResultsSet) || possibleResultsSet.any { matches(results, it) }
private fun matches(results: List<List<Result>>, possible: List<List<Result>>): Boolean =
results.toOpMap() == possible.toOpMap()
private fun List<List<Result>>.toOpMap(): Map<String, List<Any?>> {
val filtered = flatMap { it }.flatMap { it.resultValue }.filter { it.value != CS_STR }
return filtered.groupBy({ it.name }, { it.value })
}
private fun generateAllLinearizableExecutions(actorsPerThread: List<List<Actor>>): List<List<Actor>> {
val executions = ArrayList<List<Actor>>()
generateLinearizableExecutions0(
executions, actorsPerThread, ArrayList<Actor>(), IntArray(actorsPerThread.size),
actorsPerThread.sumBy { it.size })
return executions
}
@Suppress("UNCHECKED_CAST")
private fun generateLinearizableExecutions0(executions: MutableList<List<Actor>>, actorsPerThread: List<List<Actor>>,
currentExecution: ArrayList<Actor>, indexes: IntArray, length: Int) {
if (currentExecution.size == length) {
executions.add(currentExecution.clone() as List<Actor>)
return
}
for (i in indexes.indices) {
val actors = actorsPerThread[i]
if (indexes[i] == actors.size)
continue
currentExecution.add(actors[indexes[i]])
indexes[i]++
generateLinearizableExecutions0(executions, actorsPerThread, currentExecution, indexes, length)
indexes[i]--
currentExecution.removeAt(currentExecution.size - 1)
}
}
}
private val VALUE = Result::class.java.getDeclaredField("value").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
private val Result.resultValue: List<OpResult>
get() = VALUE.get(this) as List<OpResult>