Skip to content

Commit

Permalink
Integration test for the LoggingApi using the OTel classes (#751)
Browse files Browse the repository at this point in the history
  • Loading branch information
leandro-godon committed Apr 17, 2024
1 parent c2d3baf commit 247ff28
Show file tree
Hide file tree
Showing 3 changed files with 379 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.embrace.android.embracesdk

import android.app.Activity
import io.embrace.android.embracesdk.internal.payload.Envelope
import io.embrace.android.embracesdk.internal.payload.Log
import io.embrace.android.embracesdk.internal.payload.LogPayload
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.logging.InternalErrorService
Expand Down Expand Up @@ -45,6 +46,27 @@ internal fun IntegrationTestRule.Harness.getSentLogPayloads(minSize: Int? = null
}
}

/**
* Returns a list of [Log]s that were sent by the SDK since the last logs flush.
*/
internal fun IntegrationTestRule.Harness.getSentLogs(expectedSize: Int? = null): List<Log>? {
val logPayloads = overriddenDeliveryModule.deliveryService.lastSentLogPayloads
val logs = logPayloads.last().data.logs
return when (expectedSize) {
null -> logs
else -> returnIfConditionMet({ logs }) {
logs?.size == expectedSize
}
}
}

/**
* Returns the last [Log] that was sent to the delivery service.
*/
internal fun IntegrationTestRule.Harness.getLastSentLog(expectedSize: Int? = null): Log? {
return getSentLogs(expectedSize)?.last()
}

/**
* Returns the last [EventMessage] log that was sent by the SDK. If [expectedSize] is specified, it will wait up to 1 second to validate
* the number of sent log message equal that size. If a second passes that the size requirement is not met, a [TimeoutException] will
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.embrace.android.embracesdk.assertions

import io.embrace.android.embracesdk.IntegrationTestRule
import io.embrace.android.embracesdk.internal.payload.Log
import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer
import io.embrace.android.embracesdk.opentelemetry.embExceptionHandling
import io.embrace.android.embracesdk.opentelemetry.exceptionMessage
import io.embrace.android.embracesdk.opentelemetry.exceptionStacktrace
import io.embrace.android.embracesdk.opentelemetry.exceptionType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull

internal fun assertOtelLogReceived(
logReceived: Log?,
message: String,
severityNumber: Int,
severityText: String,
timeMs: Long = IntegrationTestRule.DEFAULT_SDK_START_TIME_MS,
type: String? = null,
exception: Throwable? = null,
stack: List<StackTraceElement>? = null,
properties: Map<String, Any>? = null
) {
assertNotNull(logReceived)
logReceived?.let { log ->
assertEquals(message, log.body)
assertEquals(severityNumber, log.severityNumber)
assertEquals(severityText, log.severityText)
assertEquals(timeMs * 1000000, log.timeUnixNano)
type?.let { assertAttribute(log, embExceptionHandling.name, it) }
exception?.let {
assertAttribute(log, exceptionType.key, it.javaClass.simpleName)
assertAttribute(log, exceptionMessage.key, it.message ?: "")
}
stack?.let {
val stackString = it.map(StackTraceElement::toString).take(200).toList()
val serializedStack = EmbraceSerializer().toJson(stackString, List::class.java)
assertAttribute(log, exceptionStacktrace.key, serializedStack)
}
properties?.forEach { (key, value) ->
assertAttribute(log, key, value.toString())
}
}
}

private fun assertAttribute(log: Log, name: String, expectedValue: String) {
val attribute = log.attributes?.find { it.key == name }
assertNotNull(attribute)
assertEquals(expectedValue, attribute?.data)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
package io.embrace.android.embracesdk.testcases

import androidx.test.ext.junit.runners.AndroidJUnit4
import io.embrace.android.embracesdk.IntegrationTestRule
import io.embrace.android.embracesdk.LogExceptionType
import io.embrace.android.embracesdk.assertions.assertOtelLogReceived
import io.embrace.android.embracesdk.config.remote.OTelRemoteConfig
import io.embrace.android.embracesdk.config.remote.RemoteConfig
import io.embrace.android.embracesdk.fakes.FakeClock
import io.embrace.android.embracesdk.fakes.fakeOTelBehavior
import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule
import io.embrace.android.embracesdk.getLastSentLog
import io.embrace.android.embracesdk.internal.utils.getSafeStackTrace
import io.embrace.android.embracesdk.worker.WorkerName
import io.opentelemetry.api.logs.Severity
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.lang.IllegalArgumentException

@RunWith(AndroidJUnit4::class)
internal class OTelLoggingApiTest {
@Rule
@JvmField
val testRule: IntegrationTestRule = IntegrationTestRule {
val clock = FakeClock(IntegrationTestRule.DEFAULT_SDK_START_TIME_MS)
val fakeInitModule = FakeInitModule(clock = clock)
IntegrationTestRule.Harness(
overriddenClock = clock,
overriddenInitModule = fakeInitModule,
overriddenWorkerThreadModule = FakeWorkerThreadModule(fakeInitModule = fakeInitModule, name = WorkerName.REMOTE_LOGGING)
)
}

@Before
fun setup() {
testRule.harness.overriddenConfigService.oTelBehavior = fakeOTelBehavior(
remoteCfg = {
RemoteConfig(oTelConfig = OTelRemoteConfig(isBetaEnabled = true))
}
)
}

@Test
fun `log info message sent`() {
with(testRule) {
embrace.logInfo("test message")
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = "test message",
severityNumber = getOtelSeverity(io.embrace.android.embracesdk.Severity.INFO).severityNumber,
severityText = io.embrace.android.embracesdk.Severity.INFO.name
)
}
}

@Test
fun `log warning message sent`() {
with(testRule) {
embrace.logWarning("test message")
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = "test message",
severityNumber = getOtelSeverity(io.embrace.android.embracesdk.Severity.WARNING).severityNumber,
severityText = io.embrace.android.embracesdk.Severity.WARNING.name
)
}
}

@Test
fun `log error message sent`() {
with(testRule) {
embrace.logError("test message")
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = "test message",
severityNumber = getOtelSeverity(io.embrace.android.embracesdk.Severity.ERROR).severityNumber,
severityText = io.embrace.android.embracesdk.Severity.ERROR.name
)
}
}

@Test
fun `log messages with different severities sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
val expectedMessage = "test message ${severity.name}"
embrace.logMessage(expectedMessage, severity)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = expectedMessage,
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name
)
}
}
}

@Test
fun `log messages with different severities and properties sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
val expectedMessage = "test message ${severity.name}"
embrace.logMessage(expectedMessage, severity, customProperties)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = expectedMessage,
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name,
properties = customProperties
)
}
}
}

@Test
fun `log exception message sent`() {
with(testRule) {
embrace.logException(testException)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = checkNotNull(testException.message),
severityNumber = Severity.ERROR.severityNumber,
severityText = io.embrace.android.embracesdk.Severity.ERROR.name,
type = LogExceptionType.HANDLED.value,
exception = testException,
stack = testException.getSafeStackTrace()?.toList()
)
}
}

@Test
fun `log exception with different severities sent`() {
with(testRule) {
embrace.logException(testException, io.embrace.android.embracesdk.Severity.INFO)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = checkNotNull(testException.message),
severityNumber = Severity.INFO.severityNumber,
severityText = io.embrace.android.embracesdk.Severity.INFO.name,
type = LogExceptionType.HANDLED.value,
exception = testException,
stack = testException.getSafeStackTrace()?.toList()
)
}
}

@Test
fun `log exception with different severities and properties sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
embrace.logException(
testException, severity,
customProperties
)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = checkNotNull(testException.message),
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name,
type = LogExceptionType.HANDLED.value,
exception = testException,
stack = testException.getSafeStackTrace()?.toList(),
properties = customProperties
)
}
}
}

@Test
fun `log exception with different severities, properties, and custom message sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
val expectedMessage = "test message ${severity.name}"
embrace.logException(testException, severity, customProperties, expectedMessage)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = expectedMessage,
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name,
type = LogExceptionType.HANDLED.value,
exception = testException,
stack = testException.getSafeStackTrace()?.toList(),
properties = customProperties
)
}
}
}

@Test
fun `log custom stacktrace message sent`() {
with(testRule) {
embrace.logCustomStacktrace(stacktrace)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = "",
severityNumber = getOtelSeverity(io.embrace.android.embracesdk.Severity.ERROR).severityNumber,
severityText = io.embrace.android.embracesdk.Severity.ERROR.name,
type = LogExceptionType.HANDLED.value,
stack = stacktrace.toList()
)
}
}

@Test
fun `log custom stacktrace with different severities sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
embrace.logCustomStacktrace(stacktrace, severity)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = "",
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name,
type = LogExceptionType.HANDLED.value,
stack = stacktrace.toList()
)
}
}
}

@Test
fun `log custom stacktrace with different severities and properties sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
embrace.logCustomStacktrace(stacktrace, severity, customProperties)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = "",
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name,
type = LogExceptionType.HANDLED.value,
stack = stacktrace.toList(),
properties = customProperties
)
}
}
}

@Test
fun `log custom stacktrace with different severities, properties, and custom message sent`() {
with(testRule) {
io.embrace.android.embracesdk.Severity.values().forEach { severity ->
val expectedMessage = "test message ${severity.name}"
embrace.logCustomStacktrace(stacktrace, severity, customProperties, expectedMessage)
flushLogs()
val log = harness.getLastSentLog()
assertOtelLogReceived(
log,
message = expectedMessage,
severityNumber = getOtelSeverity(severity).severityNumber,
severityText = severity.name,
type = LogExceptionType.HANDLED.value,
stack = stacktrace.toList(),
properties = customProperties
)
}
}
}

private fun flushLogs() {
val executor = (testRule.harness.overriddenWorkerThreadModule as FakeWorkerThreadModule).executor
executor.runCurrentlyBlocked()
val logOrchestrator = testRule.bootstrapper.customerLogModule.logOrchestrator
logOrchestrator.flush(false)
}

private fun getOtelSeverity(severity: io.embrace.android.embracesdk.Severity): Severity {
return when (severity) {
io.embrace.android.embracesdk.Severity.INFO -> Severity.INFO
io.embrace.android.embracesdk.Severity.WARNING -> Severity.WARN
io.embrace.android.embracesdk.Severity.ERROR -> Severity.ERROR
}
}

companion object {
private val testException = IllegalArgumentException("nooooooo")
private val customProperties: Map<String, Any> = linkedMapOf(Pair("first", 1), Pair("second", "two"), Pair("third", true))
private val stacktrace = Thread.currentThread().stackTrace
}
}

0 comments on commit 247ff28

Please sign in to comment.