1 package leakcanary.internal.activity.screen
2 
3 import org.assertj.core.api.Assertions.assertThat
4 import org.junit.Test
5 
6 class LeakTraceWrapperTest {
7 
short string stays identicalnull8   @Test fun `short string stays identical`() {
9     val string = "12\n"
10 
11     val wrapped = LeakTraceWrapper.wrap(string, 4)
12 
13     assertThat(wrapped).isEqualTo(string)
14   }
15 
string at width stays identicalnull16   @Test fun `string at width stays identical`() {
17     val string = "1234\n"
18 
19     val wrapped = LeakTraceWrapper.wrap(string, 4)
20 
21     assertThat(wrapped).isEqualTo(string)
22   }
23 
string at width no newline stays identicalnull24   @Test fun `string at width no newline stays identical`() {
25     val string = "1234"
26 
27     val wrapped = LeakTraceWrapper.wrap(string, 4)
28 
29     assertThat(wrapped).isEqualTo(string)
30   }
31 
string wrapped without newline stays has no trailing newlinenull32   @Test fun `string wrapped without newline stays has no trailing newline`() {
33     val string = "12 34"
34 
35     val wrapped = LeakTraceWrapper.wrap(string, 4)
36 
37     assertThat(wrapped).isEqualTo("12\n34")
38   }
39 
wrap line at space removing spacenull40   @Test fun `wrap line at space removing space`() {
41     val string = "12 34\n"
42 
43     val wrapped = LeakTraceWrapper.wrap(string, 4)
44 
45     assertThat(wrapped).isEqualTo("12\n34\n")
46   }
47 
wrap line at space keeps prefixnull48   @Test fun `wrap line at space keeps prefix`() {
49     val prefix = "│   "
50     val string = "${prefix}12 34\n"
51 
52     val wrapped = LeakTraceWrapper.wrap(string, prefix.length + 4)
53 
54     assertThat(wrapped).isEqualTo("${prefix}12\n${prefix}34\n")
55   }
56 
wrap line at space keeps non breaking spacenull57   @Test fun `wrap line at space keeps non breaking space`() {
58     val string = "\u200B 12 34\n"
59     val wrapped = LeakTraceWrapper.wrap(string, 5)
60 
61     assertThat(wrapped).isEqualTo("\u200B 12\n\u200B 34\n")
62   }
63 
wrap line at period keeping periodnull64   @Test fun `wrap line at period keeping period`() {
65     val string = "12.34\n"
66 
67     val wrapped = LeakTraceWrapper.wrap(string, 4)
68 
69     assertThat(wrapped).isEqualTo("12.\n34\n")
70   }
71 
two periods wraps at last periodnull72   @Test fun `two periods wraps at last period`() {
73     val string = "1.2.34\n"
74 
75     val wrapped = LeakTraceWrapper.wrap(string, 5)
76 
77     assertThat(wrapped).isEqualTo("1.2.\n34\n")
78   }
79 
no space or period is wrapped at max widthnull80   @Test fun `no space or period is wrapped at max width`() {
81     val string = "1234\n"
82 
83     val wrapped = LeakTraceWrapper.wrap(string, 2)
84 
85     assertThat(wrapped).isEqualTo("12\n34\n")
86   }
87 
two consecutive periods wraps at last periodnull88   @Test fun `two consecutive periods wraps at last period`() {
89     val string = "12..34\n"
90 
91     val wrapped = LeakTraceWrapper.wrap(string, 5)
92 
93     assertThat(wrapped).isEqualTo("12..\n34\n")
94   }
95 
period and space wraps at lastnull96   @Test fun `period and space wraps at last`() {
97     val string = "12. 34\n"
98 
99     val wrapped = LeakTraceWrapper.wrap(string, 5)
100 
101     assertThat(wrapped).isEqualTo("12.\n34\n")
102   }
103 
space and period wraps at lastnull104   @Test fun `space and period wraps at last`() {
105     val string = "12 .34\n"
106 
107     val wrapped = LeakTraceWrapper.wrap(string, 5)
108 
109     assertThat(wrapped).isEqualTo("12 .\n34\n")
110   }
111 
period and separated space wraps at lastnull112   @Test fun `period and separated space wraps at last`() {
113     val string = "1.2 34\n"
114 
115     val wrapped = LeakTraceWrapper.wrap(string, 5)
116 
117     assertThat(wrapped).isEqualTo("1.2\n34\n")
118   }
119 
several spaces are all removednull120   @Test fun `several spaces are all removed`() {
121     val string = "12  34\n"
122 
123     val wrapped = LeakTraceWrapper.wrap(string, 5)
124 
125     assertThat(wrapped).isEqualTo("12\n34\n")
126   }
127 
several periods all keeping periodnull128   @Test fun `several periods all keeping period`() {
129     val string = "12...34\n"
130     val wrapped = LeakTraceWrapper.wrap(string, 4)
131     assertThat(wrapped).isEqualTo("12..\n.34\n")
132   }
133 
prefix applied to all linesnull134   @Test fun `prefix applied to all lines`() {
135     val prefix = "│  "
136     val part1 = "A word"
137     val part2 = "and a"
138     val part3 = "pk.g"
139     val string = "\n${prefix}$part1 $part2 $part3"
140     val wrappedString = LeakTraceWrapper.wrap(string, prefix.length + part1.length + 1)
141 
142     assertThat(wrappedString).isEqualTo(
143       """
144 ${prefix}$part1
145 ${prefix}$part2
146 ${prefix}$part3"""
147     )
148   }
149 
underline is positioned under a word on a line that will not be wrappednull150   @Test fun `underline is positioned under a word on a line that will not be wrapped`() {
151     val string = """
152 │  A word and a pk.g
153 │         ~~~
154         """
155     val wrappedString = LeakTraceWrapper.wrap(string, 10)
156 
157     assertThat(wrappedString).isEqualTo(
158       """
159 │  A word
160 │  and a
161 │  ~~~
162 │  pk.g
163 """
164     )
165   }
166 
underline is positioned under a word on last linenull167   @Test fun `underline is positioned under a word on last line`() {
168     val string = """
169 │  A word and a pk.g
170 │                  ~
171         """
172     val wrappedString = LeakTraceWrapper.wrap(string, 10)
173 
174     assertThat(wrappedString).isEqualTo(
175       """
176 │  A word
177 │  and a
178 │  pk.g
179 │     ~
180 """
181     )
182   }
183 
underline within multiline stringnull184   @Test fun `underline within multiline string`() {
185     val string = """
186 ├─ com.example.FooFooFooFooFooFooFoo instance
187 │    Leaking: UNKNOWN
188 │    ↓ FooFooFooFooFooFooFoo.barbarbarbarbarbarbarbar
189 │                            ~~~~~~~~~~~~~~~~~~~~~~~~
190 """
191 
192     val wrappedString = LeakTraceWrapper.wrap(string, 30)
193 
194     assertThat(wrappedString).isEqualTo(
195       """
196 ├─ com.example.
197 │  FooFooFooFooFooFooFoo
198 │  instance
199 │    Leaking: UNKNOWN
200 │    ↓ FooFooFooFooFooFooFoo.
201 │    barbarbarbarbarbarbarbar
202 │    ~~~~~~~~~~~~~~~~~~~~~~~~
203 """
204     )
205   }
206 
a real leak trace is correctly wrappednull207   @Test fun `a real leak trace is correctly wrapped`() {
208     val string = """
209 ┬───
210 │ GC Root: System class
211 
212 ├─ leakcanary.internal.InternalAppWatcher class
213 │    Leaking: NO (ExampleApplication↓ is not leaking and a class is never leaking)
214 │    ↓ static InternalAppWatcher.application
215 ├─ com.example.leakcanary.ExampleApplication instance
216 │    Leaking: NO (Application is a singleton)
217 │    ExampleApplication does not wrap an activity context
218 │    ↓ ExampleApplication.leakedViews
219 │                         ~~~~~~~~~~~
220 ├─ java.util.ArrayList instance
221 │    Leaking: UNKNOWN
222 │    ↓ ArrayList.array
223 │                ~~~~~
224 ├─ java.lang.Object[] array
225 │    Leaking: UNKNOWN
226 │    ↓ Object[].[0]
227 │               ~~~
228 ├─ android.widget.TextView instance
229 │    Leaking: YES (View.mContext references a destroyed activity)
230 │    mContext instance of com.example.leakcanary.MainActivity with mDestroyed = true
231 │    View#mParent is set
232 │    View#mAttachInfo is null (view detached)
233 │    View.mWindowAttachCount = 1
234 │    ↓ TextView.mContext
235 ╰→ com.example.leakcanary.MainActivity instance
236 ​     Leaking: YES (ObjectWatcher was watching this because com.example.leakcanary.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
237 ​     key = b3dd6589-560d-48dc-9fbb-ab8300e5752b
238 ​     watchDurationMillis = 5117
239 ​     retainedDurationMillis = 110
240 """
241 
242     val wrappedString = LeakTraceWrapper.wrap(string, 80)
243 
244     assertThat(wrappedString).isEqualTo(
245       """
246 ┬───
247 │ GC Root: System class
248 
249 ├─ leakcanary.internal.InternalAppWatcher class
250 │    Leaking: NO (ExampleApplication↓ is not leaking and a class is never
251 │    leaking)
252 │    ↓ static InternalAppWatcher.application
253 ├─ com.example.leakcanary.ExampleApplication instance
254 │    Leaking: NO (Application is a singleton)
255 │    ExampleApplication does not wrap an activity context
256 │    ↓ ExampleApplication.leakedViews
257 │                         ~~~~~~~~~~~
258 ├─ java.util.ArrayList instance
259 │    Leaking: UNKNOWN
260 │    ↓ ArrayList.array
261 │                ~~~~~
262 ├─ java.lang.Object[] array
263 │    Leaking: UNKNOWN
264 │    ↓ Object[].[0]
265 │               ~~~
266 ├─ android.widget.TextView instance
267 │    Leaking: YES (View.mContext references a destroyed activity)
268 │    mContext instance of com.example.leakcanary.MainActivity with mDestroyed =
269 │    true
270 │    View#mParent is set
271 │    View#mAttachInfo is null (view detached)
272 │    View.mWindowAttachCount = 1
273 │    ↓ TextView.mContext
274 ╰→ com.example.leakcanary.MainActivity instance
275 ​     Leaking: YES (ObjectWatcher was watching this because com.example.
276 ​     leakcanary.MainActivity received Activity#onDestroy() callback and
277 ​     Activity#mDestroyed is true)
278 ​     key = b3dd6589-560d-48dc-9fbb-ab8300e5752b
279 ​     watchDurationMillis = 5117
280 ​     retainedDurationMillis = 110
281 """
282     )
283   }
284 }