Skip to content

Commit 4b1cbc1

Browse files
committed
remove write method, fix tests
1 parent e893ea9 commit 4b1cbc1

File tree

5 files changed

+51
-72
lines changed

5 files changed

+51
-72
lines changed

features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/EvaluationEventWriter.kt

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,7 @@ import com.datadog.tools.annotation.NoOpImplementation
2222
@NoOpImplementation
2323
internal interface EvaluationEventWriter {
2424
/**
25-
* Writes a single flag evaluation event to persistent storage.
26-
*
27-
* The event is serialized and stored as an individual record. Multiple events
28-
* will be collected and batched together at upload time.
29-
*
30-
* Thread Safety: This method must be thread-safe and handle concurrent calls correctly.
31-
*
32-
* @param event the evaluation event to write to storage
33-
*/
34-
fun write(event: BatchedFlagEvaluations.FlagEvaluation)
35-
36-
/**
37-
* Writes a list of flag evaluation events to persistent storage.
25+
* Writes flag evaluation events to persistent storage.
3826
*
3927
* The events are serialized and stored as individual records. Multiple events
4028
* will be collected and batched together at upload time.

features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/EvaluationEventsProcessor.kt

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ internal class EvaluationEventsProcessor(
4040
private val timeProvider: TimeProvider,
4141
private val scheduledExecutor: ScheduledExecutorService,
4242
private val internalLogger: InternalLogger,
43-
private val flushIntervalMs: Long = DEFAULT_FLUSH_INTERVAL_MS,
44-
private val maxAggregations: Int = MAX_AGGREGATIONS_BEFORE_FLUSH
43+
private val flushIntervalMs: Long,
44+
private val maxAggregations: Int
4545
) {
4646

4747
@Volatile
@@ -88,7 +88,7 @@ internal class EvaluationEventsProcessor(
8888
variantKey = variantKey,
8989
allocationKey = allocationKey,
9090
targetingKey = context.targetingKey,
91-
rumViewId = ddContext.viewId,
91+
viewName = ddContext.viewName,
9292
errorCode = errorCode
9393
)
9494

@@ -208,11 +208,4 @@ internal class EvaluationEventsProcessor(
208208
Thread.currentThread().interrupt()
209209
}
210210
}
211-
212-
companion object {
213-
const val DEFAULT_FLUSH_INTERVAL_MS = 10_000L // 10 seconds
214-
const val MIN_FLUSH_INTERVAL_MS = 1_000L
215-
const val MAX_FLUSH_INTERVAL_MS = 60_000L
216-
const val MAX_AGGREGATIONS_BEFORE_FLUSH = 1000
217-
}
218211
}

features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/aggregation/AggregationKey.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ package com.datadog.android.flags.internal.aggregation
1414
* - variant key (or null when no variant is assigned)
1515
* - allocation key (or null when no allocation is assigned)
1616
* - targeting key
17-
* - RUM view ID (or null when RUM is not active)
17+
* - RUM view name (or null when RUM is not active or no view available)
1818
* - error code (ErrorCode enum name for aggregation, e.g., "FLAG_NOT_FOUND")
1919
*
2020
* Note: Error messages are stored in AggregationStats but not used for aggregation
@@ -27,6 +27,6 @@ internal data class AggregationKey(
2727
val variantKey: String? = null,
2828
val allocationKey: String? = null,
2929
val targetingKey: String?,
30-
val rumViewId: String? = null,
30+
val viewName: String? = null,
3131
val errorCode: String? = null
3232
)

features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/aggregation/AggregationStats.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package com.datadog.android.flags.internal.aggregation
88

99
import com.datadog.android.flags.model.BatchedFlagEvaluations
1010
import com.datadog.android.flags.model.EvaluationContext
11+
import com.datadog.android.flags.model.ResolutionReason
1112
import com.datadog.android.flags.model.BatchedFlagEvaluations.Context1 as EvaluationEventContext
1213

1314
/**
@@ -37,16 +38,10 @@ internal class AggregationStats(
3738
private val reason: String?,
3839
errorMessage: String?
3940
) {
40-
@Volatile
41+
// All field access is synchronized - see recordEvaluation() and toEvaluationEvent()
4142
private var count: Int = 1
42-
43-
@Volatile
4443
private var firstEvaluation: Long = firstTimestamp
45-
46-
@Volatile
4744
private var lastEvaluation: Long = firstTimestamp
48-
49-
@Volatile
5045
private var lastErrorMessage: String? = errorMessage
5146

5247
/**
@@ -122,7 +117,9 @@ internal class AggregationStats(
122117
evaluationCount = snapshotCount.toLong(),
123118
firstEvaluation = snapshotFirst,
124119
lastEvaluation = snapshotLast,
125-
runtimeDefaultUsed = reason == null || reason == "DEFAULT" || reason == "ERROR"
120+
runtimeDefaultUsed = reason == null ||
121+
reason == ResolutionReason.DEFAULT.name ||
122+
reason == ResolutionReason.ERROR.name
126123
)
127124
}
128125
}

features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/EvaluationEventsProcessorTest.kt

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,31 @@ internal class EvaluationEventsProcessorTest {
9191
private lateinit var fakeData: PrecomputedFlag
9292
private lateinit var fakeDDContext: DDContext
9393

94+
fun createEvaluationEventsProcessor(
95+
writer: EvaluationEventWriter,
96+
timeProvider: TimeProvider,
97+
scheduledExecutor: ScheduledExecutorService,
98+
internalLogger: InternalLogger,
99+
flushIntervalMs: Long = TEST_FLUSH_INTERVAL_MS,
100+
maxAggregations: Int = TEST_MAX_AGGREGATIONS
101+
): EvaluationEventsProcessor {
102+
return EvaluationEventsProcessor(
103+
writer = writer,
104+
timeProvider = timeProvider,
105+
scheduledExecutor = scheduledExecutor,
106+
internalLogger = internalLogger,
107+
flushIntervalMs = flushIntervalMs,
108+
maxAggregations = maxAggregations
109+
)
110+
}
111+
94112
@BeforeEach
95113
fun `set up`(forge: Forge) {
96-
testedProcessor = EvaluationEventsProcessor(
114+
testedProcessor = createEvaluationEventsProcessor(
97115
writer = mockWriter,
98116
timeProvider = mockTimeProvider,
99117
scheduledExecutor = mockScheduledExecutor,
100-
internalLogger = mockInternalLogger,
101-
flushIntervalMs = TEST_FLUSH_INTERVAL_MS,
102-
maxAggregations = TEST_MAX_AGGREGATIONS
118+
internalLogger = mockInternalLogger
103119
)
104120

105121
fakeContext = EvaluationContext(targetingKey = fakeTargetingKey)
@@ -361,12 +377,12 @@ internal class EvaluationEventsProcessorTest {
361377
}
362378

363379
@Test
364-
fun `M create separate aggregations W processEvaluation() { different error codes }`() {
380+
fun `M create separate aggregations W processEvaluation() { different error codes }`(forge: Forge) {
365381
// Given
366382
val errorCode1 = ErrorCode.FLAG_NOT_FOUND.name
367383
val errorCode2 = ErrorCode.PROVIDER_NOT_READY.name
368-
val errorMessage1 = "Flag not found"
369-
val errorMessage2 = "Provider not ready"
384+
val errorMessage1 = forge.anAlphabeticalString()
385+
val errorMessage2 = forge.anAlphabeticalString()
370386

371387
// When
372388
testedProcessor.processEvaluation(
@@ -493,7 +509,7 @@ internal class EvaluationEventsProcessorTest {
493509
fun `M flush automatically W processEvaluation() { size limit reached }`() {
494510
// Given
495511
val maxAggregations = 5
496-
val testProcessor = EvaluationEventsProcessor(
512+
val testProcessor = createEvaluationEventsProcessor(
497513
writer = mockWriter,
498514
timeProvider = mockTimeProvider,
499515
scheduledExecutor = mockScheduledExecutor,
@@ -551,13 +567,14 @@ internal class EvaluationEventsProcessorTest {
551567
)
552568
testedProcessor.flush()
553569

554-
// Then
570+
// Then - should have called writeAll twice, once per flush
555571
val eventCaptor = argumentCaptor<List<BatchedFlagEvaluations.FlagEvaluation>>()
556-
verify(mockWriter).writeAll(eventCaptor.capture())
572+
verify(mockWriter, times(2)).writeAll(eventCaptor.capture())
557573

558-
val events = eventCaptor.firstValue
559-
assertThat(events).hasSize(2)
560-
assertThat(events.map { it.evaluationCount }).containsOnly(1L)
574+
// Flatten all events from both writeAll calls
575+
val allEvents = eventCaptor.allValues.flatten()
576+
assertThat(allEvents).hasSize(2)
577+
assertThat(allEvents.map { it.evaluationCount }).containsOnly(1L)
561578
}
562579

563580
@Test
@@ -676,7 +693,7 @@ internal class EvaluationEventsProcessorTest {
676693
testedProcessor.schedulePeriodicFlush() // Initial schedule
677694
assertThat(scheduleCount).isEqualTo(1)
678695

679-
taskRunnable?.run() // Execute the scheduled task
696+
checkNotNull(taskRunnable).run() // Execute the scheduled task
680697

681698
// Then - should have rescheduled itself
682699
assertThat(scheduleCount).isEqualTo(2)
@@ -698,7 +715,7 @@ internal class EvaluationEventsProcessorTest {
698715

699716
// When
700717
testedProcessor.schedulePeriodicFlush()
701-
taskRunnable?.run() // Execute with no evaluations to flush
718+
checkNotNull(taskRunnable).run() // Execute with no evaluations to flush
702719

703720
// Then - should not write but should reschedule
704721
verify(mockWriter, never()).writeAll(any())
@@ -825,7 +842,7 @@ internal class EvaluationEventsProcessorTest {
825842
stopThread.start()
826843

827844
// Execute the scheduled task (which will try to reschedule)
828-
taskRunnable?.run()
845+
checkNotNull(taskRunnable).run()
829846

830847
stopThread.join()
831848

@@ -1011,16 +1028,13 @@ internal class EvaluationEventsProcessorTest {
10111028
val slowWriteLatch = CountDownLatch(1)
10121029
val writeStartedLatch = CountDownLatch(1)
10131030
val slowWriter = object : EvaluationEventWriter {
1014-
override fun write(event: BatchedFlagEvaluations.FlagEvaluation) {
1015-
writeAll(listOf(event))
1016-
}
10171031
override fun writeAll(events: List<BatchedFlagEvaluations.FlagEvaluation>) {
10181032
writeStartedLatch.countDown()
10191033
slowWriteLatch.await() // Block until released
10201034
}
10211035
}
10221036

1023-
val slowProcessor = EvaluationEventsProcessor(
1037+
val slowProcessor = createEvaluationEventsProcessor(
10241038
writer = slowWriter,
10251039
timeProvider = mockTimeProvider,
10261040
scheduledExecutor = mockScheduledExecutor,
@@ -1122,40 +1136,27 @@ internal class EvaluationEventsProcessorTest {
11221136

11231137
@Test
11241138
fun `M continue accepting evaluations W flush() { during flush operation }`() {
1125-
// Given - populate initial evaluations
1126-
repeat(50) { index ->
1127-
val context = EvaluationContext(targetingKey = "initial-$index")
1128-
testedProcessor.processEvaluation(
1129-
fakeFlagKey,
1130-
context,
1131-
fakeDDContext,
1132-
fakeData.variationKey,
1133-
fakeData.allocationKey,
1134-
fakeData.reason,
1135-
null,
1136-
null
1137-
)
1138-
}
1139+
// Given - stub scheduler to avoid reschedule interactions
1140+
whenever(mockScheduledExecutor.schedule(any<Runnable>(), any(), any())) doReturn mockScheduledFuture
11391141

11401142
// Create a slow writer to simulate long flush
11411143
val flushInProgress = CountDownLatch(1)
11421144
val continueFlush = CountDownLatch(1)
11431145
val writeCount = java.util.concurrent.atomic.AtomicInteger(0)
1146+
val firstCallFlag = java.util.concurrent.atomic.AtomicBoolean(true)
11441147

11451148
val slowWriter = object : EvaluationEventWriter {
1146-
override fun write(event: BatchedFlagEvaluations.FlagEvaluation) {
1147-
writeAll(listOf(event))
1148-
}
11491149
override fun writeAll(events: List<BatchedFlagEvaluations.FlagEvaluation>) {
1150+
val isFirstCall = firstCallFlag.compareAndSet(true, false)
11501151
writeCount.addAndGet(events.size)
1151-
if (writeCount.get() == 1) {
1152+
if (isFirstCall) {
11521153
flushInProgress.countDown() // Signal first write started
11531154
continueFlush.await() // Block until signaled
11541155
}
11551156
}
11561157
}
11571158

1158-
val slowProcessor = EvaluationEventsProcessor(
1159+
val slowProcessor = createEvaluationEventsProcessor(
11591160
writer = slowWriter,
11601161
timeProvider = mockTimeProvider,
11611162
scheduledExecutor = mockScheduledExecutor,

0 commit comments

Comments
 (0)