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 kotlin.coroutines.* 12 import kotlin.test.* 13 14 class ToStringTest : TestBase() { 15 16 @Before setUpnull17 fun setUp() { 18 before() 19 DebugProbes.sanitizeStackTraces = false 20 DebugProbes.install() 21 } 22 23 @After tearDownnull24 fun tearDown() { 25 try { 26 DebugProbes.uninstall() 27 } finally { 28 onCompletion() 29 } 30 } 31 32 launchNestedScopesnull33 private suspend fun CoroutineScope.launchNestedScopes(): Job { 34 return launch { 35 expect(1) 36 coroutineScope { 37 expect(2) 38 launchDelayed() 39 40 supervisorScope { 41 expect(3) 42 launchDelayed() 43 } 44 } 45 } 46 } 47 launchDelayednull48 private fun CoroutineScope.launchDelayed(): Job { 49 return launch { 50 delay(Long.MAX_VALUE) 51 } 52 } 53 54 @Test <lambda>null55 fun testPrintHierarchyWithScopes() = runBlocking { 56 val tab = '\t' 57 val expectedString = """ 58 "coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchNestedScopes$2$1.invokeSuspend(ToStringTest.kt) 59 $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt) 60 $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt) 61 """.trimIndent() 62 63 val job = launchNestedScopes() 64 try { 65 repeat(5) { yield() } 66 val expected = expectedString.trimStackTrace().trimPackage() 67 expect(4) 68 assertEquals(expected, DebugProbes.jobToString(job).trimEnd().trimStackTrace().trimPackage()) 69 assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(job)).trimEnd().trimStackTrace().trimPackage()) 70 } finally { 71 finish(5) 72 job.cancelAndJoin() 73 } 74 } 75 76 @Test <lambda>null77 fun testCompletingHierarchy() = runBlocking { 78 val tab = '\t' 79 val expectedString = """ 80 "coroutine#2":StandaloneCoroutine{Completing} 81 $tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30) 82 $tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40) 83 $tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37) 84 """.trimIndent() 85 86 checkHierarchy(isCompleting = true, expectedString = expectedString) 87 } 88 89 @Test <lambda>null90 fun testActiveHierarchy() = runBlocking { 91 val tab = '\t' 92 val expectedString = """ 93 "coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1.invokeSuspend(ToStringTest.kt:94) 94 $tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30) 95 $tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40) 96 $tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37) 97 """.trimIndent() 98 checkHierarchy(isCompleting = false, expectedString = expectedString) 99 } 100 checkHierarchynull101 private suspend fun CoroutineScope.checkHierarchy(isCompleting: Boolean, expectedString: String) { 102 val root = launchHierarchy(isCompleting) 103 repeat(4) { yield() } 104 val expected = expectedString.trimStackTrace().trimPackage() 105 expect(6) 106 assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage()) 107 assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).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 } 149