@@ -35,7 +35,6 @@ import fr.xgouchet.elmyr.annotation.StringForgery
35
35
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
36
36
import fr.xgouchet.elmyr.junit5.ForgeExtension
37
37
import org.assertj.core.api.Assertions.assertThat
38
- import org.assertj.core.data.Offset
39
38
import org.junit.jupiter.api.AfterEach
40
39
import org.junit.jupiter.api.BeforeEach
41
40
import org.junit.jupiter.api.RepeatedTest
@@ -58,9 +57,7 @@ import org.mockito.kotlin.whenever
58
57
import org.mockito.quality.Strictness
59
58
import java.io.FileNotFoundException
60
59
import java.io.IOException
61
- import java.util.concurrent.CountDownLatch
62
60
import java.util.concurrent.ThreadPoolExecutor
63
- import java.util.concurrent.TimeUnit
64
61
65
62
@Extensions(
66
63
ExtendWith (MockitoExtension ::class ),
@@ -124,266 +121,33 @@ internal class DatadogExceptionHandlerTest {
124
121
Datadog .stopInstance()
125
122
}
126
123
127
- // region Forward to Logs
128
-
129
- @Test
130
- fun `M log dev info W caught exception { no Logs feature registered }` () {
131
- // Given
132
- whenever(mockSdkCore.getFeature(Feature .LOGS_FEATURE_NAME )) doReturn null
133
-
134
- Thread .setDefaultUncaughtExceptionHandler(null )
135
- testedHandler.register()
136
- val currentThread = Thread .currentThread()
137
-
138
- // When
139
- testedHandler.uncaughtException(currentThread, fakeThrowable)
140
-
141
- // Then
142
- mockInternalLogger.verifyLog(
143
- InternalLogger .Level .INFO ,
144
- InternalLogger .Target .USER ,
145
- DatadogExceptionHandler .MISSING_LOGS_FEATURE_INFO
146
- )
147
- }
148
-
149
124
@Test
150
- fun `M log exception W caught with no previous handler` () {
125
+ fun `M do not send exception to logs W uncaughtException() { no previous handler} ` () {
151
126
// Given
152
127
Thread .setDefaultUncaughtExceptionHandler(null )
153
128
testedHandler.register()
154
129
val currentThread = Thread .currentThread()
155
- val now = System .currentTimeMillis()
156
130
157
131
// When
158
132
testedHandler.uncaughtException(currentThread, fakeThrowable)
159
133
160
134
// Then
161
- argumentCaptor<Any > {
162
- verify(mockLogsFeatureScope).sendEvent(capture())
163
-
164
- assertThat(lastValue).isInstanceOf(JvmCrash .Logs ::class .java)
165
-
166
- val logEvent = lastValue as JvmCrash .Logs
167
-
168
- assertThat(logEvent.timestamp).isCloseTo(now, Offset .offset(250 ))
169
- assertThat(logEvent.threadName).isEqualTo(currentThread.name)
170
- assertThat(logEvent.throwable).isSameAs(fakeThrowable)
171
- assertThat(logEvent.message).isEqualTo(fakeThrowable.message)
172
- assertThat(logEvent.loggerName).isEqualTo(DatadogExceptionHandler .LOGGER_NAME )
173
- with (logEvent.threads) {
174
- assertThat(this ).isNotEmpty
175
- assertThat(filter { it.crashed }).hasSize(1 )
176
- val crashedThread = first { it.crashed }
177
- assertThat(crashedThread.name).isEqualTo(currentThread.name)
178
- assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
179
- val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
180
- assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
181
- }
182
- }
183
- verifyNoInteractions(mockPreviousHandler)
135
+ verifyNoInteractions(mockLogsFeatureScope)
184
136
}
185
137
186
138
@Test
187
- fun `M log exception W caught { exception with message }` () {
139
+ fun `M do not send exception to logs W uncaughtException() {previous handler }` () {
188
140
// Given
189
141
val currentThread = Thread .currentThread()
190
- val now = System .currentTimeMillis()
191
142
192
143
// When
193
144
testedHandler.uncaughtException(currentThread, fakeThrowable)
194
145
195
146
// Then
196
- argumentCaptor<Any > {
197
- verify(mockLogsFeatureScope).sendEvent(capture())
198
-
199
- assertThat(lastValue).isInstanceOf(JvmCrash .Logs ::class .java)
200
-
201
- val logEvent = lastValue as JvmCrash .Logs
202
-
203
- assertThat(logEvent.timestamp).isCloseTo(now, Offset .offset(250 ))
204
- assertThat(logEvent.threadName).isEqualTo(currentThread.name)
205
- assertThat(logEvent.throwable).isSameAs(fakeThrowable)
206
- assertThat(logEvent.message).isEqualTo(fakeThrowable.message)
207
- assertThat(logEvent.loggerName).isEqualTo(DatadogExceptionHandler .LOGGER_NAME )
208
- with (logEvent.threads) {
209
- assertThat(this ).isNotEmpty
210
- assertThat(filter { it.crashed }).hasSize(1 )
211
- val crashedThread = first { it.crashed }
212
- assertThat(crashedThread.name).isEqualTo(currentThread.name)
213
- assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
214
- val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
215
- assertThat(nonCrashedThreadNames).isNotEmpty
216
- assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
217
- }
218
- }
147
+ verifyNoInteractions(mockLogsFeatureScope)
219
148
verify(mockPreviousHandler).uncaughtException(currentThread, fakeThrowable)
220
149
}
221
150
222
- @RepeatedTest(2 )
223
- fun `M log exception W caught { exception without message }` (forge : Forge ) {
224
- // Given
225
- val currentThread = Thread .currentThread()
226
- val now = System .currentTimeMillis()
227
- val throwable = forge.aThrowableWithoutMessage()
228
-
229
- // When
230
- testedHandler.uncaughtException(currentThread, throwable)
231
-
232
- // Then
233
- argumentCaptor<Any > {
234
- verify(mockLogsFeatureScope).sendEvent(capture())
235
-
236
- assertThat(lastValue).isInstanceOf(JvmCrash .Logs ::class .java)
237
-
238
- val logEvent = lastValue as JvmCrash .Logs
239
-
240
- assertThat(logEvent.timestamp).isCloseTo(now, Offset .offset(250 ))
241
- assertThat(logEvent.threadName).isEqualTo(currentThread.name)
242
- assertThat(logEvent.throwable).isSameAs(throwable)
243
- assertThat(logEvent.message)
244
- .isEqualTo(" Application crash detected: ${throwable.javaClass.canonicalName} " )
245
- assertThat(logEvent.loggerName).isEqualTo(DatadogExceptionHandler .LOGGER_NAME )
246
- with (logEvent.threads) {
247
- assertThat(this ).isNotEmpty
248
- assertThat(filter { it.crashed }).hasSize(1 )
249
- val crashedThread = first { it.crashed }
250
- assertThat(crashedThread.name).isEqualTo(currentThread.name)
251
- assertThat(crashedThread.stack).isEqualTo(throwable.loggableStackTrace())
252
- val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
253
- assertThat(nonCrashedThreadNames).isNotEmpty
254
- assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
255
- }
256
- }
257
- verify(mockPreviousHandler).uncaughtException(currentThread, throwable)
258
- }
259
-
260
- @Test
261
- fun `M log exception W caught { exception without message or class }` () {
262
- // Given
263
- val currentThread = Thread .currentThread()
264
- val now = System .currentTimeMillis()
265
- val throwable = object : RuntimeException () {}
266
-
267
- // When
268
- testedHandler.uncaughtException(currentThread, throwable)
269
-
270
- // Then
271
- argumentCaptor<Any > {
272
- verify(mockLogsFeatureScope).sendEvent(capture())
273
-
274
- assertThat(lastValue).isInstanceOf(JvmCrash .Logs ::class .java)
275
-
276
- val logEvent = lastValue as JvmCrash .Logs
277
-
278
- assertThat(logEvent.timestamp).isCloseTo(now, Offset .offset(250 ))
279
- assertThat(logEvent.threadName).isEqualTo(currentThread.name)
280
- assertThat(logEvent.throwable).isSameAs(throwable)
281
- assertThat(logEvent.message)
282
- .isEqualTo(" Application crash detected: ${throwable.javaClass.simpleName} " )
283
- assertThat(logEvent.loggerName).isEqualTo(DatadogExceptionHandler .LOGGER_NAME )
284
- with (logEvent.threads) {
285
- assertThat(this ).isNotEmpty
286
- assertThat(filter { it.crashed }).hasSize(1 )
287
- val crashedThread = first { it.crashed }
288
- assertThat(crashedThread.name).isEqualTo(currentThread.name)
289
- assertThat(crashedThread.stack).isEqualTo(throwable.loggableStackTrace())
290
- val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
291
- assertThat(nonCrashedThreadNames).isNotEmpty
292
- assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
293
- }
294
- }
295
- verify(mockPreviousHandler).uncaughtException(currentThread, throwable)
296
- }
297
-
298
- @Test
299
- fun `M log exception W caught on background thread` (forge : Forge ) {
300
- // Given
301
- val latch = CountDownLatch (1 )
302
- val threadName = forge.anAlphabeticalString()
303
-
304
- // When
305
- val thread = Thread (
306
- {
307
- testedHandler.uncaughtException(Thread .currentThread(), fakeThrowable)
308
- latch.countDown()
309
- },
310
- threadName
311
- )
312
-
313
- val now = System .currentTimeMillis()
314
- thread.start()
315
- latch.await(1 , TimeUnit .SECONDS )
316
-
317
- // Then
318
- argumentCaptor<Any > {
319
- verify(mockLogsFeatureScope).sendEvent(capture())
320
-
321
- assertThat(lastValue).isInstanceOf(JvmCrash .Logs ::class .java)
322
-
323
- val logEvent = lastValue as JvmCrash .Logs
324
-
325
- assertThat(logEvent.timestamp).isCloseTo(now, Offset .offset(250 ))
326
- assertThat(logEvent.threadName).isEqualTo(thread.name)
327
- assertThat(logEvent.throwable).isSameAs(fakeThrowable)
328
- assertThat(logEvent.message).isEqualTo(fakeThrowable.message)
329
- assertThat(logEvent.loggerName).isEqualTo(DatadogExceptionHandler .LOGGER_NAME )
330
- with (logEvent.threads) {
331
- assertThat(this ).isNotEmpty
332
- assertThat(filter { it.crashed }).hasSize(1 )
333
- val crashedThread = first { it.crashed }
334
- assertThat(crashedThread.name).isEqualTo(thread.name)
335
- assertThat(crashedThread.stack).isEqualTo(fakeThrowable.loggableStackTrace())
336
- val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
337
- assertThat(nonCrashedThreadNames).isNotEmpty
338
- assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
339
- }
340
- }
341
- verify(mockPreviousHandler).uncaughtException(thread, fakeThrowable)
342
- }
343
-
344
- @Test
345
- fun `M log exception with a crashed thread provided W caught` () {
346
- // Given
347
- val externalLock = CountDownLatch (1 )
348
- val internalLock = CountDownLatch (1 )
349
- val crashedThread = Thread {
350
- externalLock.countDown()
351
- internalLock.await()
352
- }.apply { start() }
353
- // need to wait here because thread is not necessarily running yet after `start()` call
354
- externalLock.await()
355
- val now = System .currentTimeMillis()
356
-
357
- // When
358
- testedHandler.uncaughtException(crashedThread, fakeThrowable)
359
- internalLock.countDown()
360
-
361
- // Then
362
- argumentCaptor<Any > {
363
- verify(mockLogsFeatureScope).sendEvent(capture())
364
-
365
- assertThat(lastValue).isInstanceOf(JvmCrash .Logs ::class .java)
366
-
367
- val logEvent = lastValue as JvmCrash .Logs
368
-
369
- assertThat(logEvent.timestamp).isCloseTo(now, Offset .offset(250 ))
370
- assertThat(logEvent.threadName).isEqualTo(crashedThread.name)
371
- assertThat(logEvent.throwable).isSameAs(fakeThrowable)
372
- assertThat(logEvent.message).isEqualTo(fakeThrowable.message)
373
- assertThat(logEvent.loggerName).isEqualTo(DatadogExceptionHandler .LOGGER_NAME )
374
- with (logEvent.threads) {
375
- assertThat(this ).isNotEmpty
376
- assertThat(filter { it.crashed }).hasSize(1 )
377
- assertThat(first { it.crashed }.name).isEqualTo(crashedThread.name)
378
- assertThat(first { it.crashed }.stack).isEqualTo(fakeThrowable.loggableStackTrace())
379
- val nonCrashedThreadNames = filterNot { it.crashed }.map { it.name }
380
- assertThat(nonCrashedThreadNames).isNotEmpty
381
- assertThat(nonCrashedThreadNames).doesNotContain(crashedThread.name)
382
- }
383
- }
384
- verify(mockPreviousHandler).uncaughtException(crashedThread, fakeThrowable)
385
- }
386
-
387
151
// endregion
388
152
389
153
@Test
0 commit comments