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