1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.dump
18 
19 import androidx.test.filters.SmallTest
20 import com.android.systemui.CoreStartable
21 import com.android.systemui.Dumpable
22 import com.android.systemui.ProtoDumpable
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.log.LogBuffer
25 import com.android.systemui.log.table.TableLogBuffer
26 import com.android.systemui.util.mockito.any
27 import com.android.systemui.util.mockito.eq
28 import com.google.common.truth.Truth.assertThat
29 import org.junit.Before
30 import org.junit.Test
31 import org.mockito.Mock
32 import org.mockito.Mockito.anyInt
33 import org.mockito.Mockito.never
34 import org.mockito.Mockito.verify
35 import org.mockito.MockitoAnnotations
36 import java.io.FileDescriptor
37 import java.io.PrintWriter
38 import java.io.StringWriter
39 import javax.inject.Provider
40 
41 @SmallTest
42 class DumpHandlerTest : SysuiTestCase() {
43 
44     private lateinit var dumpHandler: DumpHandler
45 
46     @Mock
47     private lateinit var logBufferEulogizer: LogBufferEulogizer
48 
49     @Mock
50     private lateinit var pw: PrintWriter
51     @Mock
52     private lateinit var fd: FileDescriptor
53 
54     @Mock
55     private lateinit var dumpable1: Dumpable
56     @Mock
57     private lateinit var dumpable2: Dumpable
58     @Mock
59     private lateinit var dumpable3: Dumpable
60 
61     @Mock
62     private lateinit var protoDumpable1: ProtoDumpable
63     @Mock
64     private lateinit var protoDumpable2: ProtoDumpable
65 
66     @Mock
67     private lateinit var buffer1: LogBuffer
68     @Mock
69     private lateinit var buffer2: LogBuffer
70 
71     @Mock
72     private lateinit var table1: TableLogBuffer
73     @Mock
74     private lateinit var table2: TableLogBuffer
75 
76     private val dumpManager = DumpManager()
77 
78     @Before
79     fun setUp() {
80         MockitoAnnotations.initMocks(this)
81 
82         val config = SystemUIConfigDumpable(
83                 dumpManager,
84                 mContext,
85                 mutableMapOf(EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }),
86         )
87         dumpHandler = DumpHandler(dumpManager, logBufferEulogizer, config)
88     }
89 
90     @Test
91     fun testDumpablesCanBeDumpedSelectively() {
92         // GIVEN a variety of registered dumpables and buffers
93         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
94         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
95         dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
96         dumpManager.registerBuffer("buffer1", buffer1)
97         dumpManager.registerBuffer("buffer2", buffer2)
98         dumpManager.registerTableLogBuffer("table1", table1)
99         dumpManager.registerTableLogBuffer("table2", table2)
100 
101         // WHEN some of them are dumped explicitly
102         val args = arrayOf("dumpable1", "dumpable3", "buffer2", "table2")
103         dumpHandler.dump(fd, pw, args)
104 
105         // THEN only the requested ones have their dump() method called
106         verify(dumpable1).dump(pw, args)
107         verify(dumpable2, never()).dump(
108             any(PrintWriter::class.java),
109             any(Array<String>::class.java))
110         verify(dumpable3).dump(pw, args)
111         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
112         verify(buffer2).dump(pw, 0)
113         verify(table1, never()).dump(any(), any())
114         verify(table2).dump(pw, args)
115     }
116 
117     @Test
118     fun testDumpableMatchingIsBasedOnEndOfTag() {
119         // GIVEN a dumpable registered to the manager
120         dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
121 
122         // WHEN that module is dumped
123         val args = arrayOf("dumpable1")
124         dumpHandler.dump(fd, pw, args)
125 
126         // THEN its dump() method is called
127         verify(dumpable1).dump(pw, args)
128     }
129 
130     @Test
131     fun testCriticalDump() {
132         // GIVEN a variety of registered dumpables and buffers
133         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
134         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
135         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
136         dumpManager.registerBuffer("buffer1", buffer1)
137         dumpManager.registerBuffer("buffer2", buffer2)
138         dumpManager.registerTableLogBuffer("table1", table1)
139         dumpManager.registerTableLogBuffer("table2", table2)
140 
141         // WHEN a critical dump is requested
142         val args = arrayOf("--dump-priority", "CRITICAL")
143         dumpHandler.dump(fd, pw, args)
144 
145         // THEN only critical modules are dumped (and no buffers)
146         verify(dumpable1).dump(pw, args)
147         verify(dumpable2).dump(pw, args)
148         verify(dumpable3, never()).dump(
149             any(PrintWriter::class.java),
150             any(Array<String>::class.java))
151         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
152         verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
153         verify(table1, never()).dump(any(), any())
154         verify(table2, never()).dump(any(), any())
155     }
156 
157     @Test
158     fun testNormalDump() {
159         // GIVEN a variety of registered dumpables and buffers
160         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
161         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
162         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
163         dumpManager.registerBuffer("buffer1", buffer1)
164         dumpManager.registerBuffer("buffer2", buffer2)
165         dumpManager.registerTableLogBuffer("table1", table1)
166         dumpManager.registerTableLogBuffer("table2", table2)
167 
168         // WHEN a normal dump is requested
169         val args = arrayOf("--dump-priority", "NORMAL")
170         dumpHandler.dump(fd, pw, args)
171 
172         // THEN the normal module and all buffers are dumped
173         verify(dumpable1, never()).dump(
174                 any(PrintWriter::class.java),
175                 any(Array<String>::class.java))
176         verify(dumpable2, never()).dump(
177                 any(PrintWriter::class.java),
178                 any(Array<String>::class.java))
179         verify(dumpable3).dump(pw, args)
180         verify(buffer1).dump(pw, 0)
181         verify(buffer2).dump(pw, 0)
182         verify(table1).dump(pw, args)
183         verify(table2).dump(pw, args)
184     }
185 
186     @Test
187     fun testConfigDump() {
188         // GIVEN a StringPrintWriter
189         val stringWriter = StringWriter()
190         val spw = PrintWriter(stringWriter)
191 
192         // When a config dump is requested
193         dumpHandler.dump(fd, spw, arrayOf("config"))
194 
195         assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName)
196     }
197 
198     @Test
199     fun testDumpBuffers() {
200         // GIVEN a variety of registered dumpables and buffers and tables
201         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
202         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
203         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
204         dumpManager.registerBuffer("buffer1", buffer1)
205         dumpManager.registerBuffer("buffer2", buffer2)
206         dumpManager.registerTableLogBuffer("table1", table1)
207         dumpManager.registerTableLogBuffer("table2", table2)
208 
209         // WHEN a buffer dump is requested
210         val args = arrayOf("buffers", "--tail", "1")
211         dumpHandler.dump(fd, pw, args)
212 
213         // THEN all buffers are dumped (and no dumpables or tables)
214         verify(dumpable1, never()).dump(any(), any())
215         verify(dumpable2, never()).dump(any(), any())
216         verify(dumpable3, never()).dump(any(), any())
217         verify(buffer1).dump(pw, tailLength = 1)
218         verify(buffer2).dump(pw, tailLength = 1)
219         verify(table1, never()).dump(any(), any())
220         verify(table2, never()).dump(any(), any())
221     }
222 
223     @Test
224     fun testDumpDumpables() {
225         // GIVEN a variety of registered dumpables and buffers and tables
226         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
227         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
228         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
229         dumpManager.registerBuffer("buffer1", buffer1)
230         dumpManager.registerBuffer("buffer2", buffer2)
231         dumpManager.registerTableLogBuffer("table1", table1)
232         dumpManager.registerTableLogBuffer("table2", table2)
233 
234         // WHEN a dumpable dump is requested
235         val args = arrayOf("dumpables")
236         dumpHandler.dump(fd, pw, args)
237 
238         // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
239         verify(dumpable1).dump(pw, args)
240         verify(dumpable2).dump(pw, args)
241         verify(dumpable3).dump(pw, args)
242         verify(buffer1, never()).dump(any(), anyInt())
243         verify(buffer2, never()).dump(any(), anyInt())
244         verify(table1, never()).dump(any(), any())
245         verify(table2, never()).dump(any(), any())
246     }
247 
248     @Test
249     fun testDumpTables() {
250         // GIVEN a variety of registered dumpables and buffers and tables
251         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
252         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
253         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
254         dumpManager.registerBuffer("buffer1", buffer1)
255         dumpManager.registerBuffer("buffer2", buffer2)
256         dumpManager.registerTableLogBuffer("table1", table1)
257         dumpManager.registerTableLogBuffer("table2", table2)
258 
259         // WHEN a dumpable dump is requested
260         val args = arrayOf("tables")
261         dumpHandler.dump(fd, pw, args)
262 
263         // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
264         verify(dumpable1, never()).dump(any(), any())
265         verify(dumpable2, never()).dump(any(), any())
266         verify(dumpable3, never()).dump(any(), any())
267         verify(buffer1, never()).dump(any(), anyInt())
268         verify(buffer2, never()).dump(any(), anyInt())
269         verify(table1).dump(pw, args)
270         verify(table2).dump(pw, args)
271     }
272 
273     @Test
274     fun testDumpAllProtoDumpables() {
275         dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
276         dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
277 
278         val args = arrayOf(DumpHandler.PROTO)
279         dumpHandler.dump(fd, pw, args)
280 
281         verify(protoDumpable1).dumpProto(any(), eq(args))
282         verify(protoDumpable2).dumpProto(any(), eq(args))
283     }
284 
285     @Test
286     fun testDumpSingleProtoDumpable() {
287         dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
288         dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
289 
290         val args = arrayOf(DumpHandler.PROTO, "protoDumpable1")
291         dumpHandler.dump(fd, pw, args)
292 
293         verify(protoDumpable1).dumpProto(any(), eq(args))
294         verify(protoDumpable2, never()).dumpProto(any(), any())
295     }
296 
297     @Test
298     fun testDumpTarget_selectsShortestNamedDumpable() {
299         // GIVEN a variety of registered dumpables and buffers
300         dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
301         dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
302         dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
303 
304         // WHEN a dumpable is dumped by a suffix that matches multiple options
305         val args = arrayOf("dumpable")
306         dumpHandler.dump(fd, pw, args)
307 
308         // THEN the matching dumpable with the shorter name is dumped
309         verify(dumpable1, never()).dump(any(), any())
310         verify(dumpable2).dump(pw, args)
311         verify(dumpable3, never()).dump(any(), any())
312     }
313 
314     @Test
315     fun testDumpTarget_selectsShortestNamedBuffer() {
316         // GIVEN a variety of registered dumpables and buffers
317         dumpManager.registerBuffer("first-buffer", buffer1)
318         dumpManager.registerBuffer("scnd-buffer", buffer2)
319 
320         // WHEN a dumpable is dumped by a suffix that matches multiple options
321         val args = arrayOf("buffer", "--tail", "14")
322         dumpHandler.dump(fd, pw, args)
323 
324         // THEN the matching buffer with the shorter name is dumped
325         verify(buffer1, never()).dump(any(), anyInt())
326         verify(buffer2).dump(pw, tailLength = 14)
327     }
328 
329     @Test
330     fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
331         // GIVEN a variety of registered dumpables and buffers
332         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
333         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
334         dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
335         dumpManager.registerBuffer("big-buffer1", buffer1)
336         dumpManager.registerBuffer("big-buffer2", buffer2)
337 
338         // WHEN a dumpable is dumped by a suffix that matches multiple options
339         val args = arrayOf("2")
340         dumpHandler.dump(fd, pw, args)
341 
342         // THEN the matching dumpable with the shorter name is dumped
343         verify(dumpable1, never()).dump(any(), any())
344         verify(dumpable2).dump(pw, args)
345         verify(dumpable3, never()).dump(any(), any())
346         verify(buffer1, never()).dump(any(), anyInt())
347         verify(buffer2, never()).dump(any(), anyInt())
348     }
349 
350     @Test
351     fun testDumpTarget_selectsShortestNamedMatch_buffer() {
352         // GIVEN a variety of registered dumpables and buffers
353         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
354         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
355         dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
356         dumpManager.registerBuffer("buffer1", buffer1)
357         dumpManager.registerBuffer("buffer2", buffer2)
358 
359         // WHEN a dumpable is dumped by a suffix that matches multiple options
360         val args = arrayOf("2", "--tail", "14")
361         dumpHandler.dump(fd, pw, args)
362 
363         // THEN the matching buffer with the shorter name is dumped
364         verify(dumpable1, never()).dump(any(), any())
365         verify(dumpable2, never()).dump(any(), any())
366         verify(dumpable3, never()).dump(any(), any())
367         verify(buffer1, never()).dump(any(), anyInt())
368         verify(buffer2).dump(pw, tailLength = 14)
369     }
370 
371     @Test
372     fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
373         // GIVEN a variety of registered dumpables and buffers
374         dumpManager.registerCriticalDumpable("d1x", dumpable1)
375         dumpManager.registerCriticalDumpable("d2x", dumpable2)
376         dumpManager.registerCriticalDumpable("a3x", dumpable3)
377         dumpManager.registerBuffer("ab1x", buffer1)
378         dumpManager.registerBuffer("b2x", buffer2)
379 
380         // WHEN a dumpable is dumped by a suffix that matches multiple options
381         val args = arrayOf("x")
382         dumpHandler.dump(fd, pw, args)
383 
384         // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
385         verify(dumpable1, never()).dump(any(), any())
386         verify(dumpable2, never()).dump(any(), any())
387         verify(dumpable3).dump(pw, args)
388         verify(buffer1, never()).dump(any(), anyInt())
389         verify(buffer2, never()).dump(any(), anyInt())
390     }
391 
392     @Test
393     fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
394         // GIVEN a variety of registered dumpables and buffers
395         dumpManager.registerCriticalDumpable("d1x", dumpable1)
396         dumpManager.registerCriticalDumpable("d2x", dumpable2)
397         dumpManager.registerCriticalDumpable("az1x", dumpable3)
398         dumpManager.registerBuffer("b1x", buffer1)
399         dumpManager.registerBuffer("b2x", buffer2)
400 
401         // WHEN a dumpable is dumped by a suffix that matches multiple options
402         val args = arrayOf("x", "--tail", "14")
403         dumpHandler.dump(fd, pw, args)
404 
405         // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
406         verify(dumpable1, never()).dump(any(), any())
407         verify(dumpable2, never()).dump(any(), any())
408         verify(dumpable3, never()).dump(any(), any())
409         verify(buffer1).dump(pw, tailLength = 14)
410         verify(buffer2, never()).dump(any(), anyInt())
411     }
412 
413 
414     private class EmptyCoreStartable : CoreStartable {
415         override fun start() {}
416     }
417 }
418