blob: 0ea412b55d81ad13072160ef8b50134dfc5869c1 [file] [log] [blame]
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +03001/*
2 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5package kotlinx.coroutines.debug
6
7import kotlinx.coroutines.*
8import kotlinx.coroutines.channels.*
9import org.junit.*
10import org.junit.Test
Steve Elliottca095be2022-07-25 14:26:10 +000011import java.io.*
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +030012import kotlin.coroutines.*
13import kotlin.test.*
14
Vsevolod Tolstopyatov115a1392018-12-18 12:47:50 +030015class ToStringTest : TestBase() {
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +030016
17 @Before
18 fun setUp() {
19 before()
20 DebugProbes.sanitizeStackTraces = false
21 DebugProbes.install()
22 }
23
24 @After
25 fun tearDown() {
26 try {
27 DebugProbes.uninstall()
28 } finally {
29 onCompletion()
30 }
31 }
32
Vsevolod Tolstopyatov8d506b32019-02-20 18:31:02 +030033
34 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
49 private fun CoroutineScope.launchDelayed(): Job {
50 return launch {
51 delay(Long.MAX_VALUE)
52 }
53 }
54
55 @Test
56 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
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +030077 @Test
Vsevolod Tolstopyatov8e2428c2018-12-10 19:12:08 +030078 fun testCompletingHierarchy() = runBlocking {
79 val tab = '\t'
80 val expectedString = """
81 "coroutine#2":StandaloneCoroutine{Completing}
Vsevolod Tolstopyatov115a1392018-12-18 12:47:50 +030082 $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)
Vsevolod Tolstopyatov8e2428c2018-12-10 19:12:08 +030085 """.trimIndent()
86
87 checkHierarchy(isCompleting = true, expectedString = expectedString)
88 }
89
90 @Test
91 fun testActiveHierarchy() = runBlocking {
92 val tab = '\t'
93 val expectedString = """
Vsevolod Tolstopyatov115a1392018-12-18 12:47:50 +030094 "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)
Vsevolod Tolstopyatov8e2428c2018-12-10 19:12:08 +030098 """.trimIndent()
99 checkHierarchy(isCompleting = false, expectedString = expectedString)
100 }
101
102 private suspend fun CoroutineScope.checkHierarchy(isCompleting: Boolean, expectedString: String) {
103 val root = launchHierarchy(isCompleting)
104 repeat(4) { yield() }
Vsevolod Tolstopyatov115a1392018-12-18 12:47:50 +0300105 val expected = expectedString.trimStackTrace().trimPackage()
Vsevolod Tolstopyatov8e2428c2018-12-10 19:12:08 +0300106 expect(6)
Vsevolod Tolstopyatov115a1392018-12-18 12:47:50 +0300107 assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage())
108 assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage())
Steve Elliottca095be2022-07-25 14:26:10 +0000109 assertEquals(expected, printToString { DebugProbes.printScope(CoroutineScope(root), it) }.trimEnd().trimStackTrace().trimPackage())
110 assertEquals(expected, printToString { DebugProbes.printJob(root, it) }.trimEnd().trimStackTrace().trimPackage())
Vsevolod Tolstopyatov115a1392018-12-18 12:47:50 +0300111
Vsevolod Tolstopyatov8d506b32019-02-20 18:31:02 +0300112 root.cancelAndJoin()
Vsevolod Tolstopyatov8e2428c2018-12-10 19:12:08 +0300113 finish(7)
114 }
115
116 private fun CoroutineScope.launchHierarchy(isCompleting: Boolean): Job {
117 return launch {
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +0300118 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 }
Vsevolod Tolstopyatov8e2428c2018-12-10 19:12:08 +0300136
137 if (!isCompleting) {
138 delay(Long.MAX_VALUE)
139 }
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +0300140 }
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +0300141 }
142
143 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 }
Steve Elliottca095be2022-07-25 14:26:10 +0000151
152 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 }
Vsevolod Tolstopyatovc7239ac2018-12-10 11:41:00 +0300159}