Skip to content

Commit

Permalink
Merge pull request #1734 from DataDog/xgouchet/RUM-1517/stub_sdk_core
Browse files Browse the repository at this point in the history
RUM-1517 Create SDKCore stub classes
  • Loading branch information
xgouchet authored Nov 21, 2023
2 parents 1b499cf + 714bbec commit 764129c
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.api

/**
* Holds the data and metadata of an event written via the [StubSDKCore].
* @param eventData the event data
* @param eventMetadata the event metadata
* @param batchMetadata the batch metadata
*/
data class StubEvent(
val eventData: String,
val eventMetadata: String,
val batchMetadata: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.api

import com.datadog.android.api.context.DatadogContext
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.storage.EventBatchWriter
import com.datadog.android.api.storage.RawBatchEvent
import org.mockito.Mockito.mock
import org.mockito.Mockito.mockingDetails

internal class StubFeatureScope(
private val feature: Feature,
private val datadogContextProvider: () -> DatadogContext,
private val mockFeatureScope: FeatureScope = mock()
) : FeatureScope by mockFeatureScope {

private val eventBatchWriter: EventBatchWriter = mock()

// region Stub

fun eventsWritten(): List<StubEvent> {
val details = mockingDetails(eventBatchWriter)
return details.invocations
.filter { it.method.name == "write" }
.map { invocation ->
val event = invocation.arguments[0] as? RawBatchEvent
val batchMetadata = invocation.arguments[1] as? ByteArray ?: ByteArray(0)

check(event != null) { "Unexpected null event, arguments were ${invocation.arguments.joinToString()}" }
val eventContent = String(event.data)
val eventMetadata = String(event.metadata)
StubEvent(eventContent, eventMetadata, String(batchMetadata))
}
}

// endregion

// region FeatureScope

override fun withWriteContext(
forceNewBatch: Boolean,
callback: (DatadogContext, EventBatchWriter) -> Unit
) {
callback(datadogContextProvider(), eventBatchWriter)
}

override fun <T : Feature> unwrap(): T {
@Suppress("UNCHECKED_CAST")
return feature as T
}

// endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.api

import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.trace.TracingHeaderType
import okhttp3.HttpUrl

class StubFirstPartyHostHeaderTypeResolver :
FirstPartyHostHeaderTypeResolver {

// region FirstPartyHostHeaderTypeResolver

override fun isEmpty(): Boolean = false

override fun isFirstPartyUrl(url: HttpUrl): Boolean = true

override fun isFirstPartyUrl(url: String): Boolean = true

override fun headerTypesForUrl(url: String): Set<TracingHeaderType> = setOf(TracingHeaderType.TRACECONTEXT)

override fun headerTypesForUrl(url: HttpUrl): Set<TracingHeaderType> = setOf(TracingHeaderType.TRACECONTEXT)

override fun getAllHeaderTypes(): Set<TracingHeaderType> = setOf(TracingHeaderType.TRACECONTEXT)

// endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.api

internal class StubInternalLogger : InternalLogger {
override fun log(
level: InternalLogger.Level,
target: InternalLogger.Target,
messageBuilder: () -> String,
throwable: Throwable?,
onlyOnce: Boolean,
additionalProperties: Map<String, Any?>?
) {
println("${level.name.first()} [${target.name.first()}]: ${messageBuilder()}")
additionalProperties?.log()
throwable?.printStackTrace()
}

override fun log(
level: InternalLogger.Level,
targets: List<InternalLogger.Target>,
messageBuilder: () -> String,
throwable: Throwable?,
onlyOnce: Boolean,
additionalProperties: Map<String, Any?>?
) {
println("${level.name.first()} [${targets.joinToString { it.name.first().toString() }}]: ${messageBuilder()}")
additionalProperties?.log()
throwable?.printStackTrace()
}

override fun logMetric(
messageBuilder: () -> String,
additionalProperties: Map<String, Any?>
) {
println("M [T]: ${messageBuilder()}")
additionalProperties.log()
}

private fun <K, T> Map<K, T>.log() {
forEach {
println(" ${it.key}: ${it.value}")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.api

import android.content.Context
import com.datadog.android.api.context.DatadogContext
import com.datadog.android.api.context.NetworkInfo
import com.datadog.android.api.context.TimeInfo
import com.datadog.android.api.context.UserInfo
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import fr.xgouchet.elmyr.Forge
import org.mockito.Mockito.mock
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.whenever

/**
* A stub implementation of [InternalSdkCore].
*
* It adds several functions to get info about internal state and usage:
* [eventsWritten], …
*/
class StubSDKCore(
private val forge: Forge,
val mockContext: Context = mock(),
private val mockSdkCore: InternalSdkCore = mock()
) : InternalSdkCore by mockSdkCore {

private val featureScopes = mutableMapOf<String, StubFeatureScope>()

private var datadogContext = forge.getForgery<DatadogContext>().copy(source = "android")

init {
whenever(mockContext.packageName) doReturn forge.anAlphabeticalString()
}

// region Stub

/**
* Lists all the events written by a given feature.
* @param featureName the name of the feature
* @return a list of pairs, each pair holds
*/
fun eventsWritten(featureName: String): List<StubEvent> {
return featureScopes[featureName]?.eventsWritten() ?: emptyList()
}

fun stubNetworkInfo(networkInfo: NetworkInfo) {
datadogContext = datadogContext.copy(networkInfo = networkInfo)
}

fun stubUserInfo(userInfo: UserInfo) {
networkInfo
datadogContext = datadogContext.copy(userInfo = userInfo)
}

// endregion

// region InternalSdkCore

override val firstPartyHostResolver: FirstPartyHostHeaderTypeResolver =
StubFirstPartyHostHeaderTypeResolver()

override fun getDatadogContext(): DatadogContext {
return datadogContext
}

override val networkInfo: NetworkInfo
get() = datadogContext.networkInfo

// endregion

// region FeatureSdkCore

override val internalLogger: InternalLogger = StubInternalLogger()

override fun registerFeature(feature: Feature) {
featureScopes[feature.name] = StubFeatureScope(feature, { datadogContext })
feature.onInitialize(mockContext)
mockSdkCore.registerFeature(feature)
}

override fun getFeature(featureName: String): FeatureScope? {
mockSdkCore.getFeature(featureName)
return featureScopes[featureName]
}

override fun updateFeatureContext(
featureName: String,
updateCallback: (context: MutableMap<String, Any?>) -> Unit
) {
val featureContext = datadogContext.featuresContext[featureName]?.toMutableMap() ?: mutableMapOf()
updateCallback(featureContext)
datadogContext = datadogContext.copy(
featuresContext = datadogContext.featuresContext.toMutableMap().apply {
put(featureName, featureContext)
}
)
}

// endregion

// region SdkCore

override val time: TimeInfo
get() {
val nanos = System.nanoTime()
return TimeInfo(
deviceTimeNs = nanos,
serverTimeNs = nanos,
serverTimeOffsetMs = 0L,
serverTimeOffsetNs = 0L
)
}

override fun setUserInfo(
id: String?,
name: String?,
email: String?,
extraInfo: Map<String, Any?>
) {
datadogContext = datadogContext.copy(userInfo = UserInfo(id, name, email, extraInfo))
}

// endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.tests.ktx

import com.google.gson.JsonObject

/**
* A utility method to retrieve a nested attribute using the dotted notation.
* E.g.: assuming the `this` JsonObject represents the json below, calling
* `getString("foo.bar.spam")` will return the String `"42"`.
*
* {
* "foo": {
* "bar" : {
* "spam": "42"
* }
* }
* }
*/
fun JsonObject.getString(path: String): String? {
return if (has(path)) {
getAsJsonPrimitive(path)?.asString
} else if (path.contains('.')) {
val head = path.substringBefore('.')
val tail = path.substringAfter('.')
getAsJsonObject(head)?.getString(tail)
} else {
getAsJsonPrimitive(path)?.asString
}
}
1 change: 1 addition & 0 deletions features/dd-sdk-android-logs/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dependencies {
testImplementation(testFixtures(project(":dd-sdk-android-core")))
testImplementation(libs.bundles.jUnit5)
testImplementation(libs.bundles.testTools)
testImplementation(libs.okHttp)
unmock(libs.robolectric)
}

Expand Down
2 changes: 1 addition & 1 deletion features/dd-sdk-android-rum/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ dependencies {
}
}
testImplementation(testFixtures(project(":dd-sdk-android-core")))
testImplementation(libs.okHttp)
testImplementation(libs.bundles.jUnit5)
testImplementation(libs.bundles.testTools)
testImplementation(libs.okHttp)
testImplementation(libs.okHttpMock)
testImplementation(libs.bundles.openTracing)
unmock(libs.robolectric)
Expand Down

0 comments on commit 764129c

Please sign in to comment.