1 package kotlinx.coroutines.debug 2 3 import kotlinx.coroutines.testing.* 4 import kotlinx.coroutines.* 5 import kotlinx.coroutines.channels.* 6 import org.junit.* 7 import org.junit.Test 8 import java.io.* 9 import kotlin.coroutines.* 10 import kotlin.test.* 11 12 class ToStringTest : TestBase() { 13 14 @Before setUpnull15 fun setUp() { 16 before() 17 DebugProbes.sanitizeStackTraces = false 18 DebugProbes.install() 19 } 20 21 @After tearDownnull22 fun tearDown() { 23 try { 24 DebugProbes.uninstall() 25 } finally { 26 onCompletion() 27 } 28 } 29 30 launchNestedScopesnull31 private suspend fun CoroutineScope.launchNestedScopes(): Job { 32 return launch { 33 expect(1) 34 coroutineScope { 35 expect(2) 36 launchDelayed() 37 38 supervisorScope { 39 expect(3) 40 launchDelayed() 41 } 42 } 43 } 44 } 45 launchDelayednull46 private fun CoroutineScope.launchDelayed(): Job { 47 return launch { 48 delay(Long.MAX_VALUE) 49 } 50 } 51 52 @Test <lambda>null53 fun testPrintHierarchyWithScopes() = runBlocking { 54 val tab = '\t' 55 val expectedString = """ 56 "coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchNestedScopes$2$1.invokeSuspend(ToStringTest.kt) 57 $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt) 58 $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt) 59 """.trimIndent() 60 61 val job = launchNestedScopes() 62 try { 63 repeat(5) { yield() } 64 val expected = expectedString.trimStackTrace().trimPackage() 65 expect(4) 66 assertEquals(expected, DebugProbes.jobToString(job).trimEnd().trimStackTrace().trimPackage()) 67 assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(job)).trimEnd().trimStackTrace().trimPackage()) 68 } finally { 69 finish(5) 70 job.cancelAndJoin() 71 } 72 } 73 74 @Test <lambda>null75 fun testCompletingHierarchy() = runBlocking { 76 val tab = '\t' 77 val expectedString = """ 78 "coroutine#2":StandaloneCoroutine{Completing} 79 $tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30) 80 $tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40) 81 $tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37) 82 """.trimIndent() 83 84 checkHierarchy(isCompleting = true, expectedString = expectedString) 85 } 86 87 @Test <lambda>null88 fun testActiveHierarchy() = runBlocking { 89 val tab = '\t' 90 val expectedString = """ 91 "coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1.invokeSuspend(ToStringTest.kt:94) 92 $tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30) 93 $tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40) 94 $tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37) 95 """.trimIndent() 96 checkHierarchy(isCompleting = false, expectedString = expectedString) 97 } 98 checkHierarchynull99 private suspend fun CoroutineScope.checkHierarchy(isCompleting: Boolean, expectedString: String) { 100 val root = launchHierarchy(isCompleting) 101 repeat(4) { yield() } 102 val expected = expectedString.trimStackTrace().trimPackage() 103 expect(6) 104 assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage()) 105 assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage()) 106 assertEquals(expected, printToString { DebugProbes.printScope(CoroutineScope(root), it) }.trimEnd().trimStackTrace().trimPackage()) 107 assertEquals(expected, printToString { DebugProbes.printJob(root, it) }.trimEnd().trimStackTrace().trimPackage()) 108 109 root.cancelAndJoin() 110 finish(7) 111 } 112 CoroutineScopenull113 private fun CoroutineScope.launchHierarchy(isCompleting: Boolean): Job { 114 return launch { 115 expect(1) 116 async(CoroutineName("foo")) { 117 expect(2) 118 delay(Long.MAX_VALUE) 119 } 120 121 actor<Int> { 122 expect(3) 123 val job = launch { 124 expect(4) 125 delay(Long.MAX_VALUE) 126 } 127 128 withContext(wrapperDispatcher(coroutineContext)) { 129 expect(5) 130 job.join() 131 } 132 } 133 134 if (!isCompleting) { 135 delay(Long.MAX_VALUE) 136 } 137 } 138 } 139 wrapperDispatchernull140 private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { 141 val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher 142 return object : CoroutineDispatcher() { 143 override fun dispatch(context: CoroutineContext, block: Runnable) { 144 dispatcher.dispatch(context, block) 145 } 146 } 147 } 148 printToStringnull149 private inline fun printToString(block: (PrintStream) -> Unit): String { 150 val baos = ByteArrayOutputStream() 151 val ps = PrintStream(baos) 152 block(ps) 153 ps.close() 154 return baos.toString() 155 } 156 } 157