Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
12
core/android/logging/build.gradle.kts
Normal file
12
core/android/logging/build.gradle.kts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.thunderbird.core.android.logging"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.timber)
|
||||
implementation(libs.commons.io)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package net.thunderbird.core.android.logging
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val loggingModule = module {
|
||||
factory<ProcessExecutor> { RealProcessExecutor() }
|
||||
factory<LogFileWriter> {
|
||||
LogcatLogFileWriter(
|
||||
contentResolver = get(),
|
||||
processExecutor = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package net.thunderbird.core.android.logging
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
interface LogFileWriter {
|
||||
suspend fun writeLogTo(contentUri: Uri)
|
||||
}
|
||||
|
||||
class LogcatLogFileWriter(
|
||||
private val contentResolver: ContentResolver,
|
||||
private val processExecutor: ProcessExecutor,
|
||||
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) : LogFileWriter {
|
||||
override suspend fun writeLogTo(contentUri: Uri) {
|
||||
return withContext(coroutineDispatcher) {
|
||||
val outputStream = contentResolver.openOutputStream(contentUri, "wt")
|
||||
?: error("Error opening contentUri for writing")
|
||||
|
||||
outputStream.use {
|
||||
processExecutor.exec("logcat -d").use { inputStream ->
|
||||
IOUtils.copy(inputStream, outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package net.thunderbird.core.android.logging
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
interface ProcessExecutor {
|
||||
fun exec(command: String): InputStream
|
||||
}
|
||||
|
||||
class RealProcessExecutor : ProcessExecutor {
|
||||
override fun exec(command: String): InputStream {
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
return process.inputStream
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package net.thunderbird.core.android.logging
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
class LogcatLogFileWriterTest {
|
||||
private val contentUri = mock<Uri>()
|
||||
private val outputStream = ByteArrayOutputStream()
|
||||
|
||||
@Test
|
||||
fun `write log to contentUri`() = runBlocking {
|
||||
val logData = "a".repeat(10_000)
|
||||
val logFileWriter = LogcatLogFileWriter(
|
||||
contentResolver = createContentResolver(),
|
||||
processExecutor = createProcessExecutor(logData),
|
||||
coroutineDispatcher = Dispatchers.Unconfined,
|
||||
)
|
||||
|
||||
logFileWriter.writeLogTo(contentUri)
|
||||
|
||||
assertThat(outputStream.toByteArray().decodeToString()).isEqualTo(logData)
|
||||
}
|
||||
|
||||
@Test(expected = FileNotFoundException::class)
|
||||
fun `contentResolver throws`() = runBlocking {
|
||||
val logFileWriter = LogcatLogFileWriter(
|
||||
contentResolver = createThrowingContentResolver(FileNotFoundException()),
|
||||
processExecutor = createProcessExecutor("irrelevant"),
|
||||
coroutineDispatcher = Dispatchers.Unconfined,
|
||||
)
|
||||
|
||||
logFileWriter.writeLogTo(contentUri)
|
||||
}
|
||||
|
||||
@Test(expected = IOException::class)
|
||||
fun `processExecutor throws`() = runBlocking {
|
||||
val logFileWriter = LogcatLogFileWriter(
|
||||
contentResolver = createContentResolver(),
|
||||
processExecutor = ThrowingProcessExecutor(IOException()),
|
||||
coroutineDispatcher = Dispatchers.Unconfined,
|
||||
)
|
||||
|
||||
logFileWriter.writeLogTo(contentUri)
|
||||
}
|
||||
|
||||
private fun createContentResolver(): ContentResolver {
|
||||
return mock {
|
||||
on { openOutputStream(contentUri, "wt") } doReturn outputStream
|
||||
}
|
||||
}
|
||||
|
||||
private fun createThrowingContentResolver(exception: Exception): ContentResolver {
|
||||
return mock {
|
||||
on { openOutputStream(contentUri, "wt") } doAnswer { throw exception }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProcessExecutor(logData: String): DataProcessExecutor {
|
||||
return DataProcessExecutor(logData.toByteArray(charset = Charsets.US_ASCII))
|
||||
}
|
||||
}
|
||||
|
||||
private class DataProcessExecutor(val data: ByteArray) : ProcessExecutor {
|
||||
override fun exec(command: String): InputStream {
|
||||
return ByteArrayInputStream(data)
|
||||
}
|
||||
}
|
||||
|
||||
private class ThrowingProcessExecutor(val exception: Exception) : ProcessExecutor {
|
||||
override fun exec(command: String): InputStream {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue