blob: 0ea412b55d81ad13072160ef8b50134dfc5869c1 [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.debug
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import org.junit.*
import org.junit.Test
import java.io.*
import kotlin.coroutines.*
import kotlin.test.*
class ToStringTest : TestBase() {
@Before
fun setUp() {
before()
DebugProbes.sanitizeStackTraces = false
DebugProbes.install()
}
@After
fun tearDown() {
try {
DebugProbes.uninstall()
} finally {
onCompletion()
}
}
private suspend fun CoroutineScope.launchNestedScopes(): Job {
return launch {
expect(1)
coroutineScope {
expect(2)
launchDelayed()
supervisorScope {
expect(3)
launchDelayed()
}
}
}
}
private fun CoroutineScope.launchDelayed(): Job {
return launch {
delay(Long.MAX_VALUE)
}
}
@Test
fun testPrintHierarchyWithScopes() = runBlocking {
val tab = '\t'
val expectedString = """
"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchNestedScopes$2$1.invokeSuspend(ToStringTest.kt)
$tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt)
$tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt)
""".trimIndent()
val job = launchNestedScopes()
try {
repeat(5) { yield() }
val expected = expectedString.trimStackTrace().trimPackage()
expect(4)
assertEquals(expected, DebugProbes.jobToString(job).trimEnd().trimStackTrace().trimPackage())
assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(job)).trimEnd().trimStackTrace().trimPackage())
} finally {
finish(5)
job.cancelAndJoin()
}
}
@Test
fun testCompletingHierarchy() = runBlocking {
val tab = '\t'
val expectedString = """
"coroutine#2":StandaloneCoroutine{Completing}
$tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30)
$tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40)
$tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37)
""".trimIndent()
checkHierarchy(isCompleting = true, expectedString = expectedString)
}
@Test
fun testActiveHierarchy() = runBlocking {
val tab = '\t'
val expectedString = """
"coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1.invokeSuspend(ToStringTest.kt:94)
$tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30)
$tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40)
$tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37)
""".trimIndent()
checkHierarchy(isCompleting = false, expectedString = expectedString)
}
private suspend fun CoroutineScope.checkHierarchy(isCompleting: Boolean, expectedString: String) {
val root = launchHierarchy(isCompleting)
repeat(4) { yield() }
val expected = expectedString.trimStackTrace().trimPackage()
expect(6)
assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage())
assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage())
assertEquals(expected, printToString { DebugProbes.printScope(CoroutineScope(root), it) }.trimEnd().trimStackTrace().trimPackage())
assertEquals(expected, printToString { DebugProbes.printJob(root, it) }.trimEnd().trimStackTrace().trimPackage())
root.cancelAndJoin()
finish(7)
}
private fun CoroutineScope.launchHierarchy(isCompleting: Boolean): Job {
return launch {
expect(1)
async(CoroutineName("foo")) {
expect(2)
delay(Long.MAX_VALUE)
}
actor<Int> {
expect(3)
val job = launch {
expect(4)
delay(Long.MAX_VALUE)
}
withContext(wrapperDispatcher(coroutineContext)) {
expect(5)
job.join()
}
}
if (!isCompleting) {
delay(Long.MAX_VALUE)
}
}
}
private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
return object : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatcher.dispatch(context, block)
}
}
}
private inline fun printToString(block: (PrintStream) -> Unit): String {
val baos = ByteArrayOutputStream()
val ps = PrintStream(baos)
block(ps)
ps.close()
return baos.toString()
}
}