/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.dump import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpHandler.Companion.dump import com.android.systemui.log.LogBuffer import com.android.systemui.util.io.FakeBasicFileAttributes import com.android.systemui.util.io.Files import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import java.io.BufferedWriter import java.io.ByteArrayOutputStream import java.io.IOException import java.io.OutputStreamWriter import java.io.PrintWriter import java.nio.file.LinkOption import java.nio.file.OpenOption import java.nio.file.Paths import java.nio.file.attribute.BasicFileAttributes import java.util.Arrays import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest class LogEulogizerTest : SysuiTestCase() { lateinit var eulogizer: LogBufferEulogizer @Mock lateinit var dumpManager: DumpManager @Mock lateinit var logBuffer1: LogBuffer lateinit var logBufferEntry1: DumpsysEntry.LogBufferEntry @Mock lateinit var logBuffer2: LogBuffer lateinit var logBufferEntry2: DumpsysEntry.LogBufferEntry @Mock lateinit var files: Files private val clock = FakeSystemClock() private val path = Paths.get("/foo/bar/baz.txt") private val fileAttrs = FakeBasicFileAttributes() private val fileStream = ByteArrayOutputStream() private val fileWriter = BufferedWriter(OutputStreamWriter(fileStream)) private val dumpStream = ByteArrayOutputStream() private val dumpWriter = PrintWriter(OutputStreamWriter(dumpStream)) @Before fun setUp() { MockitoAnnotations.initMocks(this) logBufferEntry1 = DumpsysEntry.LogBufferEntry(logBuffer1, "logbuffer1") logBufferEntry2 = DumpsysEntry.LogBufferEntry(logBuffer2, "logbuffer2") eulogizer = LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE) Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java))) .thenReturn(fileWriter) Mockito.`when`( files.readAttributes( eq(path), eq(BasicFileAttributes::class.java), any(LinkOption::class.java) ) ) .thenReturn(fileAttrs) Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES)) whenever(dumpManager.getLogBuffers()).thenReturn(listOf(logBufferEntry1, logBufferEntry2)) } @Test fun testFileIsCreated() { // GIVEN that the log file doesn't already exist Mockito.`when`( files.readAttributes( eq(path), eq(BasicFileAttributes::class.java), any(LinkOption::class.java) ) ) .thenThrow(IOException("File not found")) // WHEN .record() is called val exception = RuntimeException("Something bad happened") assertEquals(exception, eulogizer.record(exception)) // THEN the buffers are dumped to the file verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt()) verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt()) assertTrue(fileStream.toString().isNotEmpty()) } @Test fun testExistingFileIsOverwritten() { // GIVEN that the log file already exists but hasn't been modified in a while fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MIN_WRITE_GAP - 20) // WHEN .record() is called val exception = RuntimeException("Something bad happened") assertEquals(exception, eulogizer.record(exception)) // THEN the buffers are dumped to the file verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt()) verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt()) assertTrue(fileStream.toString().isNotEmpty()) } @Test fun testYoungFileIsNotOverwritten() { // GIVEN that the log file has been modified recently fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MIN_WRITE_GAP + 7) // WHEN .record() is called val exception = RuntimeException("Something bad happened") assertEquals(exception, eulogizer.record(exception)) // THEN the file isn't written to verify(logBuffer1, never()).dump(any(PrintWriter::class.java), anyInt()) verify(logBuffer2, never()).dump(any(PrintWriter::class.java), anyInt()) assertTrue(fileStream.toString().isEmpty()) } @Test fun testRecentFileIsDumped() { // GIVEN that the log file was written to "recently" fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MAX_READ_AGE + 7) // WHEN we're asked to eulogize the log eulogizer.readEulogyIfPresent(dumpWriter) dumpWriter.close() // THEN the log file is written to the output stream verify(files).lines(eq(path)) assertTrue(dumpStream.toString().isNotBlank()) } @Test fun testOldFileIsNotDumped() { // GIVEN that the log file was written to a long time ago fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MAX_READ_AGE - 7) // WHEN we're asked to eulogize the log eulogizer.readEulogyIfPresent(dumpWriter) dumpWriter.close() // THEN the log file is NOT written to the output stream verify(files, never()).lines(eq(path)) assertTrue(dumpStream.toString().isEmpty()) } } private const val MIN_WRITE_GAP = 10L private const val MAX_READ_AGE = 100L private val FAKE_LINES = arrayOf("First line", "Second line", "Third line")