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.SysuiTestCase 21 import com.android.systemui.dump.DumpHandler.Companion.dump 22 import com.android.systemui.log.LogBuffer 23 import com.android.systemui.util.io.FakeBasicFileAttributes 24 import com.android.systemui.util.io.Files 25 import com.android.systemui.util.mockito.any 26 import com.android.systemui.util.mockito.eq 27 import com.android.systemui.util.mockito.whenever 28 import com.android.systemui.util.time.FakeSystemClock 29 import java.io.BufferedWriter 30 import java.io.ByteArrayOutputStream 31 import java.io.IOException 32 import java.io.OutputStreamWriter 33 import java.io.PrintWriter 34 import java.nio.file.LinkOption 35 import java.nio.file.OpenOption 36 import java.nio.file.Paths 37 import java.nio.file.attribute.BasicFileAttributes 38 import java.util.Arrays 39 import org.junit.Assert.assertEquals 40 import org.junit.Assert.assertTrue 41 import org.junit.Before 42 import org.junit.Test 43 import org.mockito.ArgumentMatchers.anyInt 44 import org.mockito.Mock 45 import org.mockito.Mockito 46 import org.mockito.Mockito.never 47 import org.mockito.Mockito.verify 48 import org.mockito.MockitoAnnotations 49 50 @SmallTest 51 class LogEulogizerTest : SysuiTestCase() { 52 53 lateinit var eulogizer: LogBufferEulogizer 54 55 @Mock lateinit var dumpManager: DumpManager 56 @Mock lateinit var logBuffer1: LogBuffer 57 lateinit var logBufferEntry1: DumpsysEntry.LogBufferEntry 58 @Mock lateinit var logBuffer2: LogBuffer 59 lateinit var logBufferEntry2: DumpsysEntry.LogBufferEntry 60 61 @Mock lateinit var files: Files 62 63 private val clock = FakeSystemClock() 64 65 private val path = Paths.get("/foo/bar/baz.txt") 66 private val fileAttrs = FakeBasicFileAttributes() 67 private val fileStream = ByteArrayOutputStream() 68 private val fileWriter = BufferedWriter(OutputStreamWriter(fileStream)) 69 70 private val dumpStream = ByteArrayOutputStream() 71 private val dumpWriter = PrintWriter(OutputStreamWriter(dumpStream)) 72 73 @Before 74 fun setUp() { 75 MockitoAnnotations.initMocks(this) 76 logBufferEntry1 = DumpsysEntry.LogBufferEntry(logBuffer1, "logbuffer1") 77 logBufferEntry2 = DumpsysEntry.LogBufferEntry(logBuffer2, "logbuffer2") 78 79 eulogizer = LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE) 80 81 Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java))) 82 .thenReturn(fileWriter) 83 84 Mockito.`when`( 85 files.readAttributes( 86 eq(path), 87 eq(BasicFileAttributes::class.java), 88 any(LinkOption::class.java) 89 ) 90 ) 91 .thenReturn(fileAttrs) 92 93 Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES)) 94 95 whenever(dumpManager.getLogBuffers()).thenReturn(listOf(logBufferEntry1, logBufferEntry2)) 96 } 97 98 @Test 99 fun testFileIsCreated() { 100 // GIVEN that the log file doesn't already exist 101 Mockito.`when`( 102 files.readAttributes( 103 eq(path), 104 eq(BasicFileAttributes::class.java), 105 any(LinkOption::class.java) 106 ) 107 ) 108 .thenThrow(IOException("File not found")) 109 110 // WHEN .record() is called 111 val exception = RuntimeException("Something bad happened") 112 assertEquals(exception, eulogizer.record(exception)) 113 114 // THEN the buffers are dumped to the file 115 verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt()) 116 verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt()) 117 assertTrue(fileStream.toString().isNotEmpty()) 118 } 119 120 @Test 121 fun testExistingFileIsOverwritten() { 122 // GIVEN that the log file already exists but hasn't been modified in a while 123 fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MIN_WRITE_GAP - 20) 124 125 // WHEN .record() is called 126 val exception = RuntimeException("Something bad happened") 127 assertEquals(exception, eulogizer.record(exception)) 128 129 // THEN the buffers are dumped to the file 130 verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt()) 131 verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt()) 132 assertTrue(fileStream.toString().isNotEmpty()) 133 } 134 135 @Test 136 fun testYoungFileIsNotOverwritten() { 137 // GIVEN that the log file has been modified recently 138 fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MIN_WRITE_GAP + 7) 139 140 // WHEN .record() is called 141 val exception = RuntimeException("Something bad happened") 142 assertEquals(exception, eulogizer.record(exception)) 143 144 // THEN the file isn't written to 145 verify(logBuffer1, never()).dump(any(PrintWriter::class.java), anyInt()) 146 verify(logBuffer2, never()).dump(any(PrintWriter::class.java), anyInt()) 147 assertTrue(fileStream.toString().isEmpty()) 148 } 149 150 @Test 151 fun testRecentFileIsDumped() { 152 // GIVEN that the log file was written to "recently" 153 fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MAX_READ_AGE + 7) 154 155 // WHEN we're asked to eulogize the log 156 eulogizer.readEulogyIfPresent(dumpWriter) 157 dumpWriter.close() 158 159 // THEN the log file is written to the output stream 160 verify(files).lines(eq(path)) 161 assertTrue(dumpStream.toString().isNotBlank()) 162 } 163 164 @Test 165 fun testOldFileIsNotDumped() { 166 // GIVEN that the log file was written to a long time ago 167 fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MAX_READ_AGE - 7) 168 169 // WHEN we're asked to eulogize the log 170 eulogizer.readEulogyIfPresent(dumpWriter) 171 dumpWriter.close() 172 173 // THEN the log file is NOT written to the output stream 174 verify(files, never()).lines(eq(path)) 175 assertTrue(dumpStream.toString().isEmpty()) 176 } 177 } 178 179 private const val MIN_WRITE_GAP = 10L 180 private const val MAX_READ_AGE = 100L 181 182 private val FAKE_LINES = arrayOf("First line", "Second line", "Third line") 183