Skip to content

Commit

Permalink
Add a test for the safe events serialization produced by RumViewScope…
Browse files Browse the repository at this point in the history
… in multi-threaded environment
  • Loading branch information
0xnm committed Mar 22, 2024
1 parent 684f5b4 commit e11172e
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ internal open class RumViewScope(
internal val url = key.url.replace('.', '/')

internal val eventAttributes: MutableMap<String, Any?> = initialAttributes.toMutableMap()
private var globalAttributes: MutableMap<String, Any?> = resolveGlobalAttributes(sdkCore)
private var globalAttributes: Map<String, Any?> = resolveGlobalAttributes(sdkCore)

private var sessionId: String = parentScope.getRumContext().sessionId
internal var viewId: String = UUID.randomUUID().toString()
Expand Down Expand Up @@ -862,8 +862,8 @@ internal open class RumViewScope(
}
}

private fun resolveGlobalAttributes(sdkCore: InternalSdkCore): MutableMap<String, Any?> {
return GlobalRumMonitor.get(sdkCore).getAttributes().toMutableMap()
private fun resolveGlobalAttributes(sdkCore: InternalSdkCore): Map<String, Any?> {
return GlobalRumMonitor.get(sdkCore).getAttributes().toMap()
}

private fun resolveViewDuration(event: RumRawEvent): Long {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ internal fun Forge.sdkInitEvent(): RumRawEvent.SdkInit {
)
}

internal fun Forge.updatePerformanceMetricEvent(): RumRawEvent.UpdatePerformanceMetric {
val time = Time()
return RumRawEvent.UpdatePerformanceMetric(
metric = getForgery(),
value = aDouble(),
eventTime = time
)
}

internal fun Forge.addFeatureFlagEvaluationEvent(): RumRawEvent.AddFeatureFlagEvaluation {
val time = Time()
return RumRawEvent.AddFeatureFlagEvaluation(
name = anAlphabeticalString(),
value = anElementFrom(aString(), anInt(), Any()),
eventTime = time
)
}

internal fun Forge.addCustomTimingEvent(): RumRawEvent.AddCustomTiming {
val time = Time()
return RumRawEvent.AddCustomTiming(
name = anAlphabeticalString(),
eventTime = time
)
}

internal fun Forge.validBackgroundEvent(): RumRawEvent {
return this.anElementFrom(
listOf(
Expand Down Expand Up @@ -168,7 +194,10 @@ internal fun Forge.anyRumEvent(excluding: List<Type> = listOf()): RumRawEvent {
stopResourceWithErrorEvent(),
stopResourceWithStacktraceEvent(),
addErrorEvent(),
addLongTaskEvent()
addLongTaskEvent(),
addFeatureFlagEvaluationEvent(),
addCustomTimingEvent(),
updatePerformanceMetricEvent()
)
return this.anElementFrom(
allEvents.filter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.Extensions
import org.junit.jupiter.params.ParameterizedTest
Expand Down Expand Up @@ -92,6 +93,8 @@ import org.mockito.quality.Strictness
import java.util.Arrays
import java.util.Locale
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.min
Expand Down Expand Up @@ -8465,6 +8468,79 @@ internal class RumViewScopeTest {

// endregion

@Test
fun `𝕄 produce event safe for serialization 𝕎 handleEvent()`(
forge: Forge
) {
// Given
val writeWorker = Executors.newCachedThreadPool()
val tasks = mutableListOf<Future<*>>()
whenever(mockRumFeatureScope.withWriteContext(any(), any())) doAnswer {
val callback = it.getArgument<(DatadogContext, EventBatchWriter) -> Unit>(1)
tasks += writeWorker.submit {
callback.invoke(fakeDatadogContext, mockEventBatchWriter)
}
}
whenever(mockWriter.write(eq(mockEventBatchWriter), any())) doAnswer {
when (val event = it.getArgument<Any>(1)) {
is ViewEvent -> assertDoesNotThrow { event.toJson() }
is ErrorEvent -> assertDoesNotThrow { event.toJson() }
is ActionEvent -> assertDoesNotThrow { event.toJson() }
is LongTaskEvent -> assertDoesNotThrow { event.toJson() }
// error is on purpose here, because under the hood all the Exceptions are caught
else -> throw Error("unsupported event type ${event::class}")
}
true
}
whenever(rumMonitor.mockInstance.getAttributes()) doReturn forge.exhaustiveAttributes()

testedScope = RumViewScope(
mockParentScope,
rumMonitor.mockSdkCore,
fakeKey,
fakeEventTime,
fakeAttributes,
mockViewChangedListener,
mockResolver,
mockCpuVitalMonitor,
mockMemoryVitalMonitor,
mockFrameRateVitalMonitor,
featuresContextResolver = mockFeaturesContextResolver,
trackFrustrations = fakeTrackFrustrations,
sampleRate = fakeSampleRate
)

// When
repeat(1000) {
testedScope.handleEvent(forge.applicationStartedEvent(), mockWriter)
testedScope.handleEvent(
forge.anyRumEvent(
excluding = listOf(
RumRawEvent.StartView::class.java,
RumRawEvent.StopView::class.java,
RumRawEvent.StartAction::class.java,
RumRawEvent.StopAction::class.java,
RumRawEvent.StartResource::class.java,
RumRawEvent.StopResource::class.java,
RumRawEvent.StopResourceWithError::class.java,
RumRawEvent.StopResourceWithStackTrace::class.java
)
),
mockWriter
)
}
testedScope.handleEvent(forge.stopViewEvent(), mockWriter)

writeWorker.shutdown()
writeWorker.awaitTermination(5, TimeUnit.SECONDS)

// Then
tasks.forEach {
// if there is any assertion error, it will be re-thrown
it.get()
}
}

// region Internal

private fun mockEvent(): RumRawEvent {
Expand Down

0 comments on commit e11172e

Please sign in to comment.