/* * 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 { 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() } }