Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUM-1520 Single Feature Integration Tests: Trace #1786

Merged
merged 5 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DatadogContextForgeryFactory : ForgeryFactory<DatadogContext> {
service = forge.anAlphabeticalString(),
version = forge.aStringMatching("[0-9](\\.[0-9]{1,3}){2,3}"),
variant = forge.anAlphabeticalString(),
env = forge.anAlphabeticalString(),
env = forge.anAlphabeticalString().lowercase(Locale.US),
source = forge.anAlphabeticalString(),
sdkVersion = forge.aStringMatching("[0-9](\\.[0-9]{1,2}){1,3}"),
time = forge.getForgery(),
Expand Down
2 changes: 0 additions & 2 deletions detekt_test_pyramid.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,9 @@ datadog-test-pyramid:
active: true
ApiUsage:
active: true
apiPackageNamePrefix: "com.datadog"
includes: ['**/reliability/**']
ApiSurface:
active: true
apiPackageNamePrefix: "com.datadog"
includes: ['**/dd-sdk-android-*/**']
excludes: ['**/build/**', '**/test/**', '**/testDebug/**','**/testRelease/**', '**/androidTest/**', '**/testFixtures/**', '**/buildSrc/**', '**/*.kts', '**/instrumented/**', '**/sample/**', '**/tools/**']

Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ internal class AndroidSpanLogsHandler(
timestampMicroseconds: Long? = null
) {
val logsFeature = sdkCore.getFeature(Feature.LOGS_FEATURE_NAME)
if (logsFeature != null) {
if (logsFeature != null && fields.isNotEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change is needed?

First, with this change we can have a false positive warning in LogCat about missing Logs features, when it is actually registered.

Second, we can have then a possibility to not send anything.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially this is to avoid sending a useless log: if the fields map is empty, then we will send a log with a default message (that just says "span log") with no custom attributes, providing no information whatsoever.
But indeed we might get a false positive logcat message, I'll fix that.

val message = fields.remove(Fields.MESSAGE)?.toString() ?: DEFAULT_EVENT_MESSAGE
fields[LogAttributes.DD_TRACE_ID] = span.traceId.toString()
fields[LogAttributes.DD_SPAN_ID] = span.spanId.toString()
Expand Down

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions reliability/single-fit/trace/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
}
}
testImplementation(testFixtures(project(":dd-sdk-android-core")))
testImplementation(project(":reliability:stub-core"))
testImplementation(libs.bundles.jUnit5)
testImplementation(libs.bundles.testTools)
testImplementation(libs.okHttp)
Expand All @@ -57,6 +58,10 @@ dependencies {
}

unMock {
keep("android.util.Singleton")
keep("com.android.internal.util.FastPrintWriter")
keep("dalvik.system.BlockGuard")
keep("dalvik.system.CloseGuard")
keepStartingWith("android.os")
keepStartingWith("org.json")
}
Expand Down

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.trace.integration

import com.datadog.android.core.internal.utils.toHexString
import io.opentracing.Span
import io.opentracing.SpanContext

/**
* Returns the span's traceId in hex format.
* The [SpanContext.toTraceId] method returns a string in decimal format,
* which doesn't match what we send in our events
*/
fun Span.traceIdAsHexString(): String {
return context().toTraceId().toLong().toHexString()
}

/**
* Returns the span's spanId in hex format.
* The [SpanContext.toSpanId] method returns a string in decimal format,
* which doesn't match what we send in our events
*/
fun Span.spanIdAsHexString(): String {
return context().toSpanId().toLong().toHexString()
}

/**
* Returns the span's traceId in hex format.
* The [SpanContext.toTraceId] method returns a string in decimal format,
* which doesn't match what we send in our events
*/
fun Span.traceIdAsLong(): Long {
return context().toTraceId().toLong()
}

/**
* Returns the span's spanId in as Long.
* The [SpanContext.toSpanId] method returns a string in decimal format,
* which doesn't match what we send in our events
*/
fun Span.spanIdAsLong(): Long {
return context().toSpanId().toLong()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/*
* 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.trace.integration

import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.StorageBackedFeature
import com.datadog.android.api.storage.RawBatchEvent
import com.datadog.android.core.stub.StubSDKCore
import com.datadog.android.tests.ktx.getInt
import com.datadog.android.tests.ktx.getLong
import com.datadog.android.tests.ktx.getString
import com.datadog.android.trace.AndroidTracer
import com.datadog.android.trace.Trace
import com.datadog.android.trace.TraceConfiguration
import com.datadog.android.trace.event.SpanEventMapper
import com.datadog.android.trace.integration.tests.elmyr.TraceIntegrationForgeConfigurator
import com.datadog.android.trace.model.SpanEvent
import com.datadog.tools.unit.extensions.TestConfigurationExtension
import com.datadog.tools.unit.setStaticValue
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.Forgery
import fr.xgouchet.elmyr.annotation.StringForgery
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
import fr.xgouchet.elmyr.junit5.ForgeExtension
import io.opentracing.util.GlobalTracer
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.Extensions
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.junit.jupiter.MockitoSettings
import org.mockito.quality.Strictness
import java.util.concurrent.TimeUnit
import kotlin.system.measureNanoTime

@Extensions(
ExtendWith(MockitoExtension::class),
ExtendWith(ForgeExtension::class),
ExtendWith(TestConfigurationExtension::class)
)
@ForgeConfiguration(TraceIntegrationForgeConfigurator::class)
@MockitoSettings(strictness = Strictness.LENIENT)
class TraceConfigurationTest {

private lateinit var stubSdkCore: StubSDKCore

@BeforeEach
fun `set up`(forge: Forge) {
stubSdkCore = StubSDKCore(forge)

val fakeTraceConfiguration = TraceConfiguration.Builder().build()
Trace.enable(fakeTraceConfiguration, stubSdkCore)
}

@AfterEach
fun `tear down`() {
GlobalTracer::class.java.setStaticValue("isRegistered", false)
}

@RepeatedTest(16)
fun `M create request to core site W RequestFactory#create()`(
@Forgery fakeBatch: List<RawBatchEvent>,
@StringForgery fakeMetadata: String
) {
// Given
val expectedSite = stubSdkCore.getDatadogContext().site
val expectedClientToken = stubSdkCore.getDatadogContext().clientToken
val expectedSource = stubSdkCore.getDatadogContext().source
val expectedSdkVersion = stubSdkCore.getDatadogContext().sdkVersion

// When
val traceFeature = stubSdkCore.getFeature(Feature.TRACING_FEATURE_NAME)?.unwrap<StorageBackedFeature>()
val requestFactory = traceFeature?.requestFactory
val request = requestFactory?.create(stubSdkCore.getDatadogContext(), fakeBatch, fakeMetadata.toByteArray())

// Then
checkNotNull(request)
assertThat(request.url).isEqualTo("${expectedSite.intakeEndpoint}/api/v2/spans")
assertThat(request.headers).containsEntry("DD-API-KEY", expectedClientToken)
assertThat(request.headers).containsEntry("DD-EVP-ORIGIN", expectedSource)
assertThat(request.headers).containsEntry("DD-EVP-ORIGIN-VERSION", expectedSdkVersion)
assertThat(request.contentType).isEqualTo("text/plain;charset=UTF-8")
}

@RepeatedTest(16)
fun `M create request to custom endpoint W useCustomEndpoint() + RequestFactory#create`(
@StringForgery fakeEndpoint: String,
@Forgery fakeBatch: List<RawBatchEvent>,
@StringForgery fakeMetadata: String
) {
// Given
val expectedClientToken = stubSdkCore.getDatadogContext().clientToken
val expectedSource = stubSdkCore.getDatadogContext().source
val expectedSdkVersion = stubSdkCore.getDatadogContext().sdkVersion
val fakeTraceConfiguration = TraceConfiguration.Builder()
.useCustomEndpoint(fakeEndpoint)
.build()
Trace.enable(fakeTraceConfiguration, stubSdkCore)

// When
val traceFeature = stubSdkCore.getFeature(Feature.TRACING_FEATURE_NAME)?.unwrap<StorageBackedFeature>()
val requestFactory = traceFeature?.requestFactory
val request = requestFactory?.create(stubSdkCore.getDatadogContext(), fakeBatch, fakeMetadata.toByteArray())

// Then
checkNotNull(request)
assertThat(request.url).isEqualTo("$fakeEndpoint/api/v2/spans")
assertThat(request.headers).containsEntry("DD-API-KEY", expectedClientToken)
assertThat(request.headers).containsEntry("DD-EVP-ORIGIN", expectedSource)
assertThat(request.headers).containsEntry("DD-EVP-ORIGIN-VERSION", expectedSdkVersion)
assertThat(request.contentType).isEqualTo("text/plain;charset=UTF-8")
}

@RepeatedTest(16)
fun `M send span without network info W setNetworkInfoEnabled(false) + buildSpan() + start() + finish()`(
@StringForgery fakeOperation: String
) {
// Given
val fakeTraceConfiguration = TraceConfiguration.Builder()
.setNetworkInfoEnabled(false)
.build()
Trace.enable(fakeTraceConfiguration, stubSdkCore)
val testedTracer = AndroidTracer.Builder(stubSdkCore).build()

// When
var traceId: String
var spanId: String
val fullDuration = measureNanoTime {
val span = testedTracer.buildSpan(fakeOperation).start()
traceId = span.traceIdAsHexString()
spanId = span.spanIdAsHexString()
Thread.sleep(OP_DURATION_MS)
span.finish()
}

// Then
val eventsWritten = stubSdkCore.eventsWritten(Feature.TRACING_FEATURE_NAME)
assertThat(eventsWritten).hasSize(1)
val event0 = JsonParser.parseString(eventsWritten[0].eventData) as JsonObject
println(event0)
assertThat(event0.getString("env")).isEqualTo(stubSdkCore.getDatadogContext().env)
assertThat(event0.getString("spans[0].trace_id")).isEqualTo(traceId)
assertThat(event0.getString("spans[0].span_id")).isEqualTo(spanId)
assertThat(event0.getString("spans[0].service")).isEqualTo(stubSdkCore.getDatadogContext().service)
assertThat(event0.getString("spans[0].meta.version")).isEqualTo(stubSdkCore.getDatadogContext().version)
assertThat(event0.getString("spans[0].meta._dd.source")).isEqualTo(stubSdkCore.getDatadogContext().source)
assertThat(event0.getString("spans[0].meta.tracer.version"))
.isEqualTo(stubSdkCore.getDatadogContext().sdkVersion)
assertThat(event0.getInt("spans[0].error")).isEqualTo(0)
assertThat(event0.getString("spans[0].name")).isEqualTo(fakeOperation)
assertThat(event0.getString("spans[0].resource")).isEqualTo(fakeOperation)
assertThat(event0.getLong("spans[0].duration")).isBetween(OP_DURATION_NS, fullDuration)
assertThat(event0.getString("spans[0].meta.network.client.connectivity")).isNull()
assertThat(event0.getString("spans[0].meta.network.client.sim_carrier.name")).isNull()
assertThat(event0.getString("spans[0].meta.network.client.sim_carrier.id")).isNull()
}

@RepeatedTest(16)
fun `M send span with network info W setNetworkInfoEnabled(true) + buildSpan() + start() + finish()`(
@StringForgery fakeOperation: String
) {
// Given
val fakeTraceConfiguration = TraceConfiguration.Builder()
.setNetworkInfoEnabled(true)
.build()
Trace.enable(fakeTraceConfiguration, stubSdkCore)
val testedTracer = AndroidTracer.Builder(stubSdkCore).build()

// When
var traceId: String
var spanId: String
val fullDuration = measureNanoTime {
val span = testedTracer.buildSpan(fakeOperation).start()
traceId = span.traceIdAsHexString()
spanId = span.spanIdAsHexString()
Thread.sleep(OP_DURATION_MS)
span.finish()
}

// Then
val eventsWritten = stubSdkCore.eventsWritten(Feature.TRACING_FEATURE_NAME)
assertThat(eventsWritten).hasSize(1)
val event0 = JsonParser.parseString(eventsWritten[0].eventData) as JsonObject
println(event0)
assertThat(event0.getString("env")).isEqualTo(stubSdkCore.getDatadogContext().env)
assertThat(event0.getString("spans[0].trace_id")).isEqualTo(traceId)
assertThat(event0.getString("spans[0].span_id")).isEqualTo(spanId)
assertThat(event0.getString("spans[0].service")).isEqualTo(stubSdkCore.getDatadogContext().service)
assertThat(event0.getString("spans[0].meta.version")).isEqualTo(stubSdkCore.getDatadogContext().version)
assertThat(event0.getString("spans[0].meta._dd.source")).isEqualTo(stubSdkCore.getDatadogContext().source)
assertThat(event0.getString("spans[0].meta.tracer.version"))
.isEqualTo(stubSdkCore.getDatadogContext().sdkVersion)
assertThat(event0.getInt("spans[0].error")).isEqualTo(0)
assertThat(event0.getString("spans[0].name")).isEqualTo(fakeOperation)
assertThat(event0.getString("spans[0].resource")).isEqualTo(fakeOperation)
assertThat(event0.getLong("spans[0].duration")).isBetween(OP_DURATION_NS, fullDuration)
assertThat(event0.getString("spans[0].meta.network.client.connectivity"))
.isEqualTo(stubSdkCore.getDatadogContext().networkInfo.connectivity.name)
assertThat(event0.getString("spans[0].meta.network.client.sim_carrier.name"))
.isEqualTo(stubSdkCore.getDatadogContext().networkInfo.carrierName)
assertThat(event0.getLong("spans[0].meta.network.client.sim_carrier.id"))
.isEqualTo(stubSdkCore.getDatadogContext().networkInfo.carrierId)
}

@RepeatedTest(16)
fun `M send mapped span W setEventMapper() + buildSpan() + start() + finish()`(
@StringForgery fakeOperation: String,
@StringForgery fakeMappedOperation: String,
@StringForgery fakeMappedResource: String
) {
// Given
val stubMapper = object : SpanEventMapper {
override fun map(event: SpanEvent): SpanEvent {
event.name = fakeMappedOperation
event.resource = fakeMappedResource
return event
}
}
val fakeTraceConfiguration = TraceConfiguration.Builder()
.setEventMapper(stubMapper)
.build()
Trace.enable(fakeTraceConfiguration, stubSdkCore)
val testedTracer = AndroidTracer.Builder(stubSdkCore).build()

// When
val fullDuration = measureNanoTime {
val span = testedTracer.buildSpan(fakeOperation).start()
Thread.sleep(OP_DURATION_MS)
span.finish()
}

// Then
val eventsWritten = stubSdkCore.eventsWritten(Feature.TRACING_FEATURE_NAME)
assertThat(eventsWritten).hasSize(1)
val event0 = JsonParser.parseString(eventsWritten[0].eventData) as JsonObject
println(event0)
assertThat(event0.getString("env")).isEqualTo(stubSdkCore.getDatadogContext().env)
assertThat(event0.getString("spans[0].service")).isEqualTo(stubSdkCore.getDatadogContext().service)
assertThat(event0.getString("spans[0].meta.version")).isEqualTo(stubSdkCore.getDatadogContext().version)
assertThat(event0.getString("spans[0].meta._dd.source")).isEqualTo(stubSdkCore.getDatadogContext().source)
assertThat(event0.getString("spans[0].meta.tracer.version"))
.isEqualTo(stubSdkCore.getDatadogContext().sdkVersion)
assertThat(event0.getInt("spans[0].error")).isEqualTo(0)
assertThat(event0.getString("spans[0].name")).isEqualTo(fakeMappedOperation)
assertThat(event0.getString("spans[0].resource")).isEqualTo(fakeMappedResource)
assertThat(event0.getLong("spans[0].duration")).isBetween(OP_DURATION_NS, fullDuration)
}

companion object {
const val OP_DURATION_MS = 10L
val OP_DURATION_NS = TimeUnit.MILLISECONDS.toNanos(OP_DURATION_MS)
}
}
Loading
Loading