xref: /aosp_15_r20/external/kotlinx.coroutines/kotlinx-coroutines-debug/test/ToStringTest.kt (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)
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