-
-
Notifications
You must be signed in to change notification settings - Fork 472
Expand file tree
/
Copy pathSQLiteSpanInstrumentationTest.kt
More file actions
222 lines (184 loc) · 8.39 KB
/
Copy pathSQLiteSpanInstrumentationTest.kt
File metadata and controls
222 lines (184 loc) · 8.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package io.sentry.sqlite
import io.sentry.IScopes
import io.sentry.ISpan
import io.sentry.SentryDateProvider
import io.sentry.SentryLongDate
import io.sentry.SentryNanotimeDate
import io.sentry.SentryOptions
import io.sentry.SentryTracer
import io.sentry.SpanDataConvention
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.util.thread.IThreadChecker
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
class SQLiteSpanInstrumentationTest {
private class Fixture {
val scopes = mock<IScopes>()
lateinit var sentryTracer: SentryTracer
lateinit var options: SentryOptions
fun getSut(
isTransactionActive: Boolean = true,
fileName: String = ":memory:",
): SQLiteSpanInstrumentation {
options = SentryOptions().apply { dsn = "https://key@sentry.io/proj" }
whenever(scopes.options).thenReturn(options)
sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes)
if (isTransactionActive) {
whenever(scopes.span).thenReturn(sentryTracer)
}
return SQLiteSpanInstrumentation.fromFileName(fileName, scopes)
}
}
private val fixture = Fixture()
@Test
fun `startTimestamp is ns-precise and skips date provider when parent uses SentryNanotimeDate`() {
// Only the parent date is queued. If startTimestamp() were to call dateProvider.now(),
// the queue would underflow and the test would fail loudly — this is what verifies the
// optimization is in effect.
val parentDate = SentryNanotimeDate(1_000_000L, 100_000_000L)
val sut = setUpWithNanotimeDates(parentDate)
val start = sut.startTimestamp()
val durationNanos = 42_000_000L
sut.recordSpan("SELECT 1", start, durationNanos, SpanStatus.OK)
val span = fixture.sentryTracer.children.first()
// startTimestamp returns an already-ns-precise value, anchored to the parent's wall clock and
// offset by elapsed System.nanoTime(). The exact ns-math is unit-tested in
// ChildStartTimestampOrNullTest; here we verify the integration shape.
assertIs<SentryLongDate>(span.startDate)
assertEquals(start, span.startDate.nanoTimestamp())
assertEquals(start + durationNanos, span.finishDate!!.nanoTimestamp())
}
@Test
fun `startTimestamp falls back to date provider when parent does not use SentryNanotimeDate`() {
val providerDate = SentryNanotimeDate(2_000_000L, 200_000_000L)
val parentSpan = mock<ISpan>()
whenever(parentSpan.startDate).thenReturn(SentryLongDate(1_000_000_000_000_000L))
val options =
SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
dateProvider = SentryDateProvider { providerDate }
}
whenever(fixture.scopes.options).thenReturn(options)
whenever(fixture.scopes.span).thenReturn(parentSpan)
val sut = SQLiteSpanInstrumentation.fromFileName(":memory:", fixture.scopes)
assertEquals(providerDate.nanoTimestamp(), sut.startTimestamp())
}
@Test
fun `startTimestamp falls back to date provider when no transaction is active`() {
val providerDate = SentryNanotimeDate(2_000_000L, 200_000_000L)
val options =
SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
dateProvider = SentryDateProvider { providerDate }
}
whenever(fixture.scopes.options).thenReturn(options)
whenever(fixture.scopes.span).thenReturn(null)
val sut = SQLiteSpanInstrumentation.fromFileName(":memory:", fixture.scopes)
assertEquals(providerDate.nanoTimestamp(), sut.startTimestamp())
}
@Test
fun `recordSpan records a span if a transaction is active`() {
val sut = fixture.getSut(isTransactionActive = true)
sut.recordSpan("SELECT 1", sut.startTimestamp(), 1_000_000, SpanStatus.OK)
assertEquals(1, fixture.sentryTracer.children.size)
}
@Test
fun `recordSpan does not record a span if no transaction is active`() {
val sut = fixture.getSut(isTransactionActive = false)
val start = sut.startTimestamp()
sut.recordSpan("SELECT 1", start, 1_000_000, SpanStatus.OK)
assertEquals(0, fixture.sentryTracer.children.size)
}
@Test
fun `recordSpan creates a span with correct properties`() {
val sut = fixture.getSut()
val start = sut.startTimestamp()
sut.recordSpan("SELECT * FROM users", start, 1_000_000, SpanStatus.OK)
val span = fixture.sentryTracer.children.firstOrNull()
assertNotNull(span)
assertEquals("db.sql.query", span.operation)
assertEquals("SELECT * FROM users", span.description)
assertEquals("auto.db.sqlite", span.spanContext.origin)
assertEquals(SpanStatus.OK, span.status)
assertTrue(span.isFinished)
}
@Test
fun `recordSpan sets finishDate equal to startDate + durationNanos`() {
val sut = fixture.getSut()
val start = sut.startTimestamp()
val durationNanos = 42_000_000L
sut.recordSpan("SELECT 1", start, durationNanos, SpanStatus.OK)
val span = fixture.sentryTracer.children.first()
assertEquals(span.startDate.nanoTimestamp() + durationNanos, span.finishDate!!.nanoTimestamp())
}
@Test
fun `recordSpan attaches throwable when provided`() {
val sut = fixture.getSut()
val start = sut.startTimestamp()
val exception = RuntimeException("disk I/O error")
sut.recordSpan("INSERT INTO t VALUES(1)", start, 500_000, SpanStatus.INTERNAL_ERROR, exception)
val span = fixture.sentryTracer.children.first()
assertEquals(SpanStatus.INTERNAL_ERROR, span.status)
assertEquals(exception, span.throwable)
}
@Test
fun `recordSpan sets db system and db name when fileName is not the in-memory sentinel`() {
val sut = fixture.getSut(fileName = "/data/data/com.example/databases/tracks.db")
val start = sut.startTimestamp()
sut.recordSpan("SELECT 1", start, 1_000_000, SpanStatus.OK)
val span = fixture.sentryTracer.children.first()
assertEquals("sqlite", span.data[SpanDataConvention.DB_SYSTEM_KEY])
assertEquals("tracks.db", span.data[SpanDataConvention.DB_NAME_KEY])
}
@Test
fun `recordSpan sets db system only when fileName is the in-memory sentinel`() {
val sut = fixture.getSut(fileName = ":memory:")
val start = sut.startTimestamp()
sut.recordSpan("SELECT 1", start, 1_000_000, SpanStatus.OK)
val span = fixture.sentryTracer.children.first()
assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY])
assertNull(span.data[SpanDataConvention.DB_NAME_KEY])
}
@Test
fun `recordSpan sets blocked_main_thread to true and attaches call stack on main thread`() {
val sut = fixture.getSut()
fixture.options.threadChecker = mock<IThreadChecker>()
whenever(fixture.options.threadChecker.isMainThread).thenReturn(true)
whenever(fixture.options.threadChecker.currentThreadName).thenReturn("main")
sut.recordSpan("SELECT 1", sut.startTimestamp(), 1_000_000, SpanStatus.OK)
val span = fixture.sentryTracer.children.first()
assertTrue(span.getData(SpanDataConvention.BLOCKED_MAIN_THREAD_KEY) as Boolean)
assertNotNull(span.getData(SpanDataConvention.CALL_STACK_KEY))
}
@Test
fun `recordSpan sets blocked_main_thread to false and does not attach a call stack on background thread`() {
val sut = fixture.getSut()
fixture.options.threadChecker = mock<IThreadChecker>()
whenever(fixture.options.threadChecker.isMainThread).thenReturn(false)
whenever(fixture.options.threadChecker.currentThreadName).thenReturn("worker")
sut.recordSpan("SELECT 1", sut.startTimestamp(), 1_000_000, SpanStatus.OK)
val span = fixture.sentryTracer.children.first()
assertFalse(span.getData(SpanDataConvention.BLOCKED_MAIN_THREAD_KEY) as Boolean)
assertNull(span.getData(SpanDataConvention.CALL_STACK_KEY))
}
private fun setUpWithNanotimeDates(vararg dates: SentryNanotimeDate): SQLiteSpanInstrumentation {
val dateQueue = ArrayDeque(dates.toList())
val options =
SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
dateProvider = SentryDateProvider { dateQueue.removeFirst() }
}
whenever(fixture.scopes.options).thenReturn(options)
fixture.sentryTracer = SentryTracer(TransactionContext("name", "op"), fixture.scopes)
whenever(fixture.scopes.span).thenReturn(fixture.sentryTracer)
return SQLiteSpanInstrumentation.fromFileName(":memory:", fixture.scopes)
}
}