11package io.sentry
22
33import io.sentry.exception.ExceptionMechanismException
4+ import io.sentry.hints.DiskFlushNotification
45import io.sentry.protocol.SentryId
5- import io.sentry.util.noFlushTimeout
6+ import io.sentry.util.HintUtils
67import org.mockito.kotlin.any
8+ import org.mockito.kotlin.argThat
79import org.mockito.kotlin.argWhere
810import org.mockito.kotlin.mock
911import org.mockito.kotlin.never
1012import org.mockito.kotlin.verify
1113import org.mockito.kotlin.whenever
1214import java.io.ByteArrayOutputStream
13- import java.io.File
1415import java.io.PrintStream
1516import java.nio.file.Files
16- import kotlin.test.AfterTest
17- import kotlin.test.BeforeTest
17+ import kotlin.concurrent.thread
1818import kotlin.test.Test
1919import kotlin.test.assertNotNull
2020import kotlin.test.assertTrue
2121
2222class UncaughtExceptionHandlerIntegrationTest {
2323
24- private lateinit var file: File
25-
26- @BeforeTest
27- fun `set up` () {
28- file = Files .createTempDirectory(" sentry-disk-cache-test" ).toAbsolutePath().toFile()
24+ class Fixture {
25+ val file = Files .createTempDirectory(" sentry-disk-cache-test" ).toAbsolutePath().toFile()
26+ internal val handler = mock<UncaughtExceptionHandler >()
27+ val defaultHandler = mock<Thread .UncaughtExceptionHandler >()
28+ val thread = mock<Thread >()
29+ val throwable = Throwable (" test" )
30+ val hub = mock<IHub >()
31+ val options = SentryOptions ()
32+ val logger = mock<ILogger >()
33+
34+ fun getSut (
35+ flushTimeoutMillis : Long = 0L,
36+ hasDefaultHandler : Boolean = false,
37+ enableUncaughtExceptionHandler : Boolean = true,
38+ isPrintUncaughtStackTrace : Boolean = false
39+ ): UncaughtExceptionHandlerIntegration {
40+ options.flushTimeoutMillis = flushTimeoutMillis
41+ options.isEnableUncaughtExceptionHandler = enableUncaughtExceptionHandler
42+ options.isPrintUncaughtStackTrace = isPrintUncaughtStackTrace
43+ options.setLogger(logger)
44+ options.isDebug = true
45+ whenever(handler.defaultUncaughtExceptionHandler).thenReturn(if (hasDefaultHandler) defaultHandler else null )
46+ return UncaughtExceptionHandlerIntegration (handler)
47+ }
2948 }
3049
31- @AfterTest
32- fun shutdown () {
33- Files .delete(file.toPath())
34- }
50+ private val fixture = Fixture ()
3551
3652 @Test
3753 fun `when UncaughtExceptionHandlerIntegration is initialized, uncaught handler is unchanged` () {
38- val handlerMock = mock< UncaughtExceptionHandler >( )
39- UncaughtExceptionHandlerIntegration (handlerMock)
40- verify(handlerMock , never()).defaultUncaughtExceptionHandler = any()
54+ fixture.getSut(isPrintUncaughtStackTrace = false )
55+
56+ verify(fixture.handler , never()).defaultUncaughtExceptionHandler = any()
4157 }
4258
4359 @Test
4460 fun `when uncaughtException is called, sentry captures exception` () {
45- val handlerMock = mock<UncaughtExceptionHandler >()
46- val threadMock = mock<Thread >()
47- val throwableMock = mock<Throwable >()
48- val hubMock = mock<IHub >()
49- val options = SentryOptions ().noFlushTimeout()
50- val sut = UncaughtExceptionHandlerIntegration (handlerMock)
51- sut.register(hubMock, options)
52- sut.uncaughtException(threadMock, throwableMock)
53- verify(hubMock).captureEvent(any(), any<Hint >())
61+ val sut = fixture.getSut(isPrintUncaughtStackTrace = false )
62+
63+ sut.register(fixture.hub, fixture.options)
64+ sut.uncaughtException(fixture.thread, fixture.throwable)
65+
66+ verify(fixture.hub).captureEvent(any(), any<Hint >())
5467 }
5568
5669 @Test
5770 fun `when register is called, current handler is not lost` () {
58- val handlerMock = mock<UncaughtExceptionHandler >()
59- val threadMock = mock<Thread >()
60- val throwableMock = mock<Throwable >()
61- val defaultHandlerMock = mock<Thread .UncaughtExceptionHandler >()
62- whenever(handlerMock.defaultUncaughtExceptionHandler).thenReturn(defaultHandlerMock)
63- val hubMock = mock<IHub >()
64- val options = SentryOptions ().noFlushTimeout()
65- val sut = UncaughtExceptionHandlerIntegration (handlerMock)
66- sut.register(hubMock, options)
67- sut.uncaughtException(threadMock, throwableMock)
68- verify(defaultHandlerMock).uncaughtException(threadMock, throwableMock)
71+ val sut = fixture.getSut(hasDefaultHandler = true , isPrintUncaughtStackTrace = false )
72+
73+ sut.register(fixture.hub, fixture.options)
74+ sut.uncaughtException(fixture.thread, fixture.throwable)
75+
76+ verify(fixture.defaultHandler).uncaughtException(fixture.thread, fixture.throwable)
6977 }
7078
7179 @Test
7280 fun `when uncaughtException is called, exception captured has handled=false` () {
73- val handlerMock = mock<UncaughtExceptionHandler >()
74- val threadMock = mock<Thread >()
75- val throwableMock = mock<Throwable >()
76- val hubMock = mock<IHub >()
77- whenever(hubMock.captureException(any())).thenAnswer { invocation ->
78- val e = (invocation.arguments[1 ] as ExceptionMechanismException )
81+ whenever(fixture.hub.captureException(any())).thenAnswer { invocation ->
82+ val e = invocation.getArgument<ExceptionMechanismException >(1 )
7983 assertNotNull(e)
8084 assertNotNull(e.exceptionMechanism)
8185 assertTrue(e.exceptionMechanism.isHandled!! )
8286 SentryId .EMPTY_ID
8387 }
84- val options = SentryOptions ().noFlushTimeout()
85- val sut = UncaughtExceptionHandlerIntegration (handlerMock)
86- sut.register(hubMock, options)
87- sut.uncaughtException(threadMock, throwableMock)
88- verify(hubMock).captureEvent(any(), any<Hint >())
88+
89+ val sut = fixture.getSut(isPrintUncaughtStackTrace = false )
90+
91+ sut.register(fixture.hub, fixture.options)
92+ sut.uncaughtException(fixture.thread, fixture.throwable)
93+
94+ verify(fixture.hub).captureEvent(any(), any<Hint >())
8995 }
9096
9197 @Test
@@ -94,63 +100,57 @@ class UncaughtExceptionHandlerIntegrationTest {
94100 val options = SentryOptions ()
95101 options.dsn
= " https://[email protected] /proj" 96102 options.addIntegration(integrationMock)
97- options.cacheDirPath = file.absolutePath
103+ options.cacheDirPath = fixture. file.absolutePath
98104 options.setSerializer(mock())
99- // val expected = HubAdapter.getInstance()
100105 val hub = Hub (options)
101- // verify(integrationMock).register(expected, options)
102106 hub.close()
103107 verify(integrationMock).close()
104108 }
105109
106110 @Test
107111 fun `When defaultUncaughtExceptionHandler is disabled, should not install Sentry UncaughtExceptionHandler` () {
108- val options = SentryOptions ()
109- options.isEnableUncaughtExceptionHandler = false
110- val hub = mock<IHub >()
111- val handlerMock = mock<UncaughtExceptionHandler >()
112- val integration = UncaughtExceptionHandlerIntegration (handlerMock)
113- integration.register(hub, options)
114- verify(handlerMock, never()).defaultUncaughtExceptionHandler = any()
112+ val sut = fixture.getSut(
113+ enableUncaughtExceptionHandler = false ,
114+ isPrintUncaughtStackTrace = false
115+ )
116+
117+ sut.register(fixture.hub, fixture.options)
118+
119+ verify(fixture.handler, never()).defaultUncaughtExceptionHandler = any()
115120 }
116121
117122 @Test
118123 fun `When defaultUncaughtExceptionHandler is enabled, should install Sentry UncaughtExceptionHandler` () {
119- val options = SentryOptions ( )
120- val hub = mock< IHub >()
121- val handlerMock = mock< UncaughtExceptionHandler >( )
122- val integration = UncaughtExceptionHandlerIntegration (handlerMock)
123- integration.register(hub, options)
124- verify(handlerMock).defaultUncaughtExceptionHandler = argWhere { it is UncaughtExceptionHandlerIntegration }
124+ val sut = fixture.getSut(isPrintUncaughtStackTrace = false )
125+
126+ sut.register(fixture.hub, fixture.options )
127+
128+ verify(fixture.handler).defaultUncaughtExceptionHandler =
129+ argWhere { it is UncaughtExceptionHandlerIntegration }
125130 }
126131
127132 @Test
128133 fun `When defaultUncaughtExceptionHandler is set and integration is closed, default uncaught exception handler is reset to previous handler` () {
129- val handlerMock = mock<UncaughtExceptionHandler >()
130- val integration = UncaughtExceptionHandlerIntegration (handlerMock)
131-
132- val defaultExceptionHandler = mock<Thread .UncaughtExceptionHandler >()
133- whenever(handlerMock.defaultUncaughtExceptionHandler)
134- .thenReturn(defaultExceptionHandler)
135- integration.register(mock(), SentryOptions ())
136- whenever(handlerMock.defaultUncaughtExceptionHandler)
137- .thenReturn(integration)
138- integration.close()
139- verify(handlerMock).defaultUncaughtExceptionHandler = defaultExceptionHandler
134+ val sut = fixture.getSut(hasDefaultHandler = true , isPrintUncaughtStackTrace = false )
135+
136+ sut.register(fixture.hub, fixture.options)
137+ whenever(fixture.handler.defaultUncaughtExceptionHandler)
138+ .thenReturn(sut)
139+ sut.close()
140+
141+ verify(fixture.handler).defaultUncaughtExceptionHandler = fixture.defaultHandler
140142 }
141143
142144 @Test
143145 fun `When defaultUncaughtExceptionHandler is not set and integration is closed, default uncaught exception handler is reset to null` () {
144- val handlerMock = mock<UncaughtExceptionHandler >()
145- val integration = UncaughtExceptionHandlerIntegration (handlerMock)
146-
147- whenever(handlerMock.defaultUncaughtExceptionHandler)
148- .thenReturn(null )
149- integration.register(mock(), SentryOptions ())
150- whenever(handlerMock.defaultUncaughtExceptionHandler)
151- .thenReturn(integration)
152- integration.close()
153- verify(handlerMock).defaultUncaughtExceptionHandler = null
146+ val sut = fixture.getSut(isPrintUncaughtStackTrace = false )
147+
148+ sut.register(fixture.hub, fixture.options)
149+ whenever(fixture.handler.defaultUncaughtExceptionHandler)
150+ .thenReturn(sut)
151+ sut.close()
152+
153+ verify(fixture.handler).defaultUncaughtExceptionHandler = null
154154 }
155155
156156 @Test
@@ -160,12 +160,10 @@ class UncaughtExceptionHandlerIntegrationTest {
160160 val outputStreamCaptor = ByteArrayOutputStream ()
161161 System .setErr(PrintStream (outputStreamCaptor))
162162
163- val handlerMock = mock<UncaughtExceptionHandler >()
164- val options = SentryOptions ().noFlushTimeout()
165- options.isPrintUncaughtStackTrace = true
166- val sut = UncaughtExceptionHandlerIntegration (handlerMock)
167- sut.register(mock<IHub >(), options)
168- sut.uncaughtException(mock<Thread >(), RuntimeException (" This should be printed!" ))
163+ val sut = fixture.getSut(isPrintUncaughtStackTrace = true )
164+
165+ sut.register(fixture.hub, fixture.options)
166+ sut.uncaughtException(fixture.thread, RuntimeException (" This should be printed!" ))
169167
170168 assertTrue(
171169 outputStreamCaptor.toString()
@@ -179,4 +177,51 @@ class UncaughtExceptionHandlerIntegrationTest {
179177 System .setErr(standardErr)
180178 }
181179 }
180+
181+ @Test
182+ fun `waits for event to flush on disk` () {
183+ val capturedEventId = SentryId ()
184+
185+ whenever(fixture.hub.captureEvent(any(), any<Hint >())).thenAnswer { invocation ->
186+ val hint = HintUtils .getSentrySdkHint(invocation.getArgument(1 ))
187+ as DiskFlushNotification
188+ thread {
189+ Thread .sleep(1000L )
190+ hint.markFlushed()
191+ }
192+ capturedEventId
193+ }
194+
195+ val sut = fixture.getSut(flushTimeoutMillis = 5000 )
196+
197+ sut.register(fixture.hub, fixture.options)
198+ sut.uncaughtException(fixture.thread, fixture.throwable)
199+
200+ verify(fixture.hub).captureEvent(any(), any<Hint >())
201+ // shouldn't fall into timed out state, because we marked event as flushed on another thread
202+ verify(fixture.logger, never()).log(
203+ any(),
204+ argThat { startsWith(" Timed out" ) },
205+ any<Any >()
206+ )
207+ }
208+
209+ @Test
210+ fun `does not block flushing when the event was dropped` () {
211+ whenever(fixture.hub.captureEvent(any(), any<Hint >())).thenReturn(SentryId .EMPTY_ID )
212+
213+ val sut = fixture.getSut()
214+
215+ sut.register(fixture.hub, fixture.options)
216+ sut.uncaughtException(fixture.thread, fixture.throwable)
217+
218+ verify(fixture.hub).captureEvent(any(), any<Hint >())
219+ // we do not call markFlushed, hence it should time out waiting for flush, but because
220+ // we drop the event, it should not even come to this if-check
221+ verify(fixture.logger, never()).log(
222+ any(),
223+ argThat { startsWith(" Timed out" ) },
224+ any<Any >()
225+ )
226+ }
182227}
0 commit comments