LoggerTest.kt 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Chris Narkiewicz
  5. * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.nextcloud.client.logger
  21. import android.os.Handler
  22. import com.nextcloud.client.core.Clock
  23. import com.nextcloud.client.core.ClockImpl
  24. import com.nhaarman.mockitokotlin2.any
  25. import com.nhaarman.mockitokotlin2.argThat
  26. import com.nhaarman.mockitokotlin2.capture
  27. import com.nhaarman.mockitokotlin2.doAnswer
  28. import com.nhaarman.mockitokotlin2.inOrder
  29. import com.nhaarman.mockitokotlin2.mock
  30. import com.nhaarman.mockitokotlin2.spy
  31. import com.nhaarman.mockitokotlin2.times
  32. import com.nhaarman.mockitokotlin2.verify
  33. import com.nhaarman.mockitokotlin2.whenever
  34. import org.junit.Assert.assertEquals
  35. import org.junit.Assert.assertFalse
  36. import org.junit.Assert.assertNotEquals
  37. import org.junit.Assert.assertTrue
  38. import org.junit.Before
  39. import org.junit.Test
  40. import org.mockito.ArgumentCaptor
  41. import org.mockito.MockitoAnnotations
  42. import java.nio.file.Files
  43. import java.util.concurrent.CountDownLatch
  44. import java.util.concurrent.TimeUnit
  45. class LoggerTest {
  46. private companion object {
  47. const val QUEUE_CAPACITY = 100
  48. }
  49. private lateinit var clock: Clock
  50. private lateinit var logHandler: FileLogHandler
  51. private lateinit var osHandler: Handler
  52. private lateinit var logger: LoggerImpl
  53. @Before
  54. fun setUp() {
  55. MockitoAnnotations.initMocks(this)
  56. val tempDir = Files.createTempDirectory("log-test").toFile()
  57. clock = ClockImpl()
  58. logHandler = spy(FileLogHandler(tempDir, "log.txt", 1024))
  59. osHandler = mock()
  60. logger = LoggerImpl(clock, logHandler, osHandler, QUEUE_CAPACITY)
  61. }
  62. @Test
  63. fun `write is done on background thread`() {
  64. val callerThreadId = Thread.currentThread().id
  65. val writerThreadIds = mutableListOf<Long>()
  66. val latch = CountDownLatch(3)
  67. doAnswer {
  68. writerThreadIds.add(Thread.currentThread().id)
  69. it.callRealMethod()
  70. latch.countDown()
  71. }.whenever(logHandler).open()
  72. doAnswer {
  73. writerThreadIds.add(Thread.currentThread().id)
  74. it.callRealMethod()
  75. latch.countDown()
  76. }.whenever(logHandler).write(any())
  77. doAnswer {
  78. writerThreadIds.add(Thread.currentThread().id)
  79. it.callRealMethod()
  80. latch.countDown()
  81. }.whenever(logHandler).close()
  82. // GIVEN
  83. // logger event loop is running
  84. logger.start()
  85. // WHEN
  86. // message is logged
  87. logger.d("tag", "message")
  88. // THEN
  89. // message is processed on bg thread
  90. // all handler invocations happen on bg thread
  91. // all handler invocations happen on single thread
  92. assertTrue(latch.await(3, TimeUnit.SECONDS))
  93. writerThreadIds.forEach { writerThreadId ->
  94. assertNotEquals("All requests must be made on bg thread", callerThreadId, writerThreadId)
  95. }
  96. writerThreadIds.forEach {
  97. assertEquals("All requests must be made on single thread", writerThreadIds[0], it)
  98. }
  99. }
  100. @Test
  101. fun `message is written via log handler`() {
  102. val tag = "test tag"
  103. val message = "test log message"
  104. val latch = CountDownLatch(3)
  105. doAnswer { it.callRealMethod(); latch.countDown() }.whenever(logHandler).open()
  106. doAnswer { it.callRealMethod(); latch.countDown() }.whenever(logHandler).write(any())
  107. doAnswer { it.callRealMethod(); latch.countDown() }.whenever(logHandler).close()
  108. // GIVEN
  109. // logger event loop is running
  110. logger.start()
  111. // WHEN
  112. // log message is written
  113. logger.d(tag, message)
  114. // THEN
  115. // log handler opens log file
  116. // log handler writes entry
  117. // log handler closes log file
  118. // no lost messages
  119. val called = latch.await(3, TimeUnit.SECONDS)
  120. assertTrue("Expected open(), write() and close() calls on bg thread", called)
  121. val inOrder = inOrder(logHandler)
  122. inOrder.verify(logHandler).open()
  123. inOrder.verify(logHandler).write(argThat {
  124. tag in this && message in this
  125. })
  126. inOrder.verify(logHandler).close()
  127. assertFalse(logger.lostEntries)
  128. }
  129. @Test
  130. fun `logs are loaded in background thread and posted to main thread`() {
  131. val currentThreadId = Thread.currentThread().id
  132. var loggerThreadId: Long = -1
  133. val listener: OnLogsLoaded = mock()
  134. val latch = CountDownLatch(2)
  135. // log handler will be called on bg thread
  136. doAnswer {
  137. loggerThreadId = Thread.currentThread().id
  138. latch.countDown()
  139. it.callRealMethod()
  140. }.whenever(logHandler).loadLogFiles(any())
  141. // os handler will be called on bg thread
  142. whenever(osHandler.post(any())).thenAnswer {
  143. latch.countDown()
  144. true
  145. }
  146. // GIVEN
  147. // logger event loop is running
  148. logger.start()
  149. // WHEN
  150. // messages are logged
  151. // log contents are requested
  152. logger.d("tag", "message 1")
  153. logger.d("tag", "message 2")
  154. logger.d("tag", "message 3")
  155. logger.load(listener)
  156. val called = latch.await(3, TimeUnit.SECONDS)
  157. assertTrue("Response not posted", called)
  158. // THEN
  159. // log contents are loaded on background thread
  160. // logs are posted to main thread handler
  161. // contents contain logged messages
  162. // messages are in order of writes
  163. assertNotEquals(currentThreadId, loggerThreadId)
  164. val postedCaptor = ArgumentCaptor.forClass(Runnable::class.java)
  165. verify(osHandler).post(capture(postedCaptor))
  166. postedCaptor.value.run()
  167. val logsCaptor = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<LogEntry>>
  168. val sizeCaptor = ArgumentCaptor.forClass(Long::class.java)
  169. verify(listener).invoke(capture(logsCaptor), capture(sizeCaptor))
  170. assertEquals(3, logsCaptor.value.size)
  171. assertTrue("message 1" in logsCaptor.value[0].message)
  172. assertTrue("message 2" in logsCaptor.value[1].message)
  173. assertTrue("message 3" in logsCaptor.value[2].message)
  174. }
  175. @Test
  176. fun `log level can be decoded from tags`() {
  177. Level.values().forEach {
  178. val decodedLevel = Level.fromTag(it.tag)
  179. assertEquals(it, decodedLevel)
  180. }
  181. }
  182. @Test
  183. fun `queue limit is enforced`() {
  184. // GIVEN
  185. // logger event loop is no running
  186. // WHEN
  187. // queue is filled up to it's capacity
  188. for (i in 0 until QUEUE_CAPACITY + 1) {
  189. logger.d("tag", "Message $i")
  190. }
  191. // THEN
  192. // overflow flag is raised
  193. assertTrue(logger.lostEntries)
  194. }
  195. @Test
  196. fun `queue overflow warning is logged`() {
  197. // GIVEN
  198. // logger loop is overflown
  199. for (i in 0..QUEUE_CAPACITY + 1) {
  200. logger.d("tag", "Message $i")
  201. }
  202. // WHEN
  203. // logger event loop processes events
  204. //
  205. logger.start()
  206. // THEN
  207. // overflow occurence is logged
  208. val posted = CountDownLatch(1)
  209. whenever(osHandler.post(any())).thenAnswer {
  210. (it.arguments[0] as Runnable).run()
  211. posted.countDown()
  212. true
  213. }
  214. val listener: OnLogsLoaded = mock()
  215. logger.load(listener)
  216. assertTrue("Logs not loaded", posted.await(1, TimeUnit.SECONDS))
  217. verify(listener).invoke(argThat {
  218. "Logger queue overflow" in last().message
  219. }, any())
  220. }
  221. @Test
  222. fun `all log files are deleted`() {
  223. val latch = CountDownLatch(1)
  224. doAnswer {
  225. it.callRealMethod()
  226. latch.countDown()
  227. }.whenever(logHandler).deleteAll()
  228. // GIVEN
  229. // logger is started
  230. logger.start()
  231. // WHEN
  232. // logger has some writes
  233. // logs are deleted
  234. logger.d("tag", "message")
  235. logger.d("tag", "message")
  236. logger.d("tag", "message")
  237. logger.deleteAll()
  238. // THEN
  239. // handler writes files
  240. // handler deletes all files
  241. assertTrue(latch.await(3, TimeUnit.SECONDS))
  242. verify(logHandler, times(3)).write(any())
  243. verify(logHandler).deleteAll()
  244. val loaded = logHandler.loadLogFiles(logHandler.maxLogFilesCount)
  245. assertEquals(0, loaded.lines.size)
  246. assertEquals(0L, loaded.logSize)
  247. }
  248. }