@@ -171,6 +171,122 @@ void main() {
171171 expect (span.setDataCalls, isEmpty);
172172 });
173173 });
174+
175+ group ('OnSpanFinish sync processing' , () {
176+ test ('sets blocked_main_thread when sync span finishes on main isolate' ,
177+ () async {
178+ fixture.mockHelper.setIsRootIsolate (true );
179+
180+ final hub = fixture.createHub ();
181+ final integration = fixture.getSut ();
182+ final span = fixture.createMockSpanWithData ({
183+ 'sync' : true ,
184+ SpanDataConvention .threadName: 'main' ,
185+ });
186+
187+ integration.call (hub, fixture.options);
188+ // ignore: invalid_use_of_internal_member
189+ await fixture.options.lifecycleRegistry
190+ .dispatchCallback (OnSpanFinish (span));
191+
192+ final setDataCalls = span.setDataCalls;
193+ expect (setDataCalls.length, equals (1 ));
194+
195+ final blockedMainThreadCall = setDataCalls.firstWhere (
196+ (call) => call.key == SpanDataConvention .blockedMainThread);
197+ expect (blockedMainThreadCall.value, equals (true ));
198+
199+ // Check that sync was removed
200+ expect (span.removeDataCalls.length, equals (1 ));
201+ expect (span.removeDataCalls.first.key, equals ('sync' ));
202+ });
203+
204+ test (
205+ 'does not set blocked_main_thread when sync span finishes on background isolate' ,
206+ () async {
207+ final hub = fixture.createHub ();
208+ final integration = fixture.getSut ();
209+ final span = fixture.createMockSpanWithData ({
210+ 'sync' : true ,
211+ SpanDataConvention .threadName: 'worker-thread' ,
212+ });
213+
214+ integration.call (hub, fixture.options);
215+ // ignore: invalid_use_of_internal_member
216+ await fixture.options.lifecycleRegistry
217+ .dispatchCallback (OnSpanFinish (span));
218+
219+ // Should not set blocked_main_thread
220+ final blockedMainThreadCalls = span.setDataCalls
221+ .where ((call) => call.key == SpanDataConvention .blockedMainThread);
222+ expect (blockedMainThreadCalls, isEmpty);
223+
224+ // But should still remove sync
225+ expect (span.removeDataCalls.length, equals (1 ));
226+ expect (span.removeDataCalls.first.key, equals ('sync' ));
227+ });
228+
229+ test ('does not process spans without sync data' , () async {
230+ final hub = fixture.createHub ();
231+ final integration = fixture.getSut ();
232+ final span = fixture.createMockSpanWithData ({
233+ SpanDataConvention .threadName: 'main' ,
234+ });
235+
236+ integration.call (hub, fixture.options);
237+ // ignore: invalid_use_of_internal_member
238+ await fixture.options.lifecycleRegistry
239+ .dispatchCallback (OnSpanFinish (span));
240+
241+ // Should not add any data or remove anything
242+ expect (span.setDataCalls, isEmpty);
243+ expect (span.removeDataCalls, isEmpty);
244+ });
245+
246+ test ('removes sync flag even when sync is false' , () async {
247+ final hub = fixture.createHub ();
248+ final integration = fixture.getSut ();
249+ final span = fixture.createMockSpanWithData ({
250+ 'sync' : false ,
251+ SpanDataConvention .threadName: 'main' ,
252+ });
253+
254+ integration.call (hub, fixture.options);
255+ // ignore: invalid_use_of_internal_member
256+ await fixture.options.lifecycleRegistry
257+ .dispatchCallback (OnSpanFinish (span));
258+
259+ // Should not set blocked_main_thread (sync is false)
260+ final blockedMainThreadCalls = span.setDataCalls
261+ .where ((call) => call.key == SpanDataConvention .blockedMainThread);
262+ expect (blockedMainThreadCalls, isEmpty);
263+
264+ // But should still remove sync
265+ expect (span.removeDataCalls.length, equals (1 ));
266+ expect (span.removeDataCalls.first.key, equals ('sync' ));
267+ });
268+
269+ test ('does not set blocked_main_thread when sync span has no thread name' ,
270+ () async {
271+ final hub = fixture.createHub ();
272+ final integration = fixture.getSut ();
273+ final span = fixture.createMockSpanWithData ({'sync' : true });
274+
275+ integration.call (hub, fixture.options);
276+ // ignore: invalid_use_of_internal_member
277+ await fixture.options.lifecycleRegistry
278+ .dispatchCallback (OnSpanFinish (span));
279+
280+ // Should not set blocked_main_thread (no thread name)
281+ final blockedMainThreadCalls = span.setDataCalls
282+ .where ((call) => call.key == SpanDataConvention .blockedMainThread);
283+ expect (blockedMainThreadCalls, isEmpty);
284+
285+ // But should still remove sync
286+ expect (span.removeDataCalls.length, equals (1 ));
287+ expect (span.removeDataCalls.first.key, equals ('sync' ));
288+ });
289+ });
174290}
175291
176292class _Fixture {
@@ -194,6 +310,10 @@ class _Fixture {
194310 return _MockSpan ();
195311 }
196312
313+ _MockSpan createMockSpanWithData (Map <String , dynamic > data) {
314+ return _MockSpan .withData (data);
315+ }
316+
197317 MockHub createHub () {
198318 final hub = MockHub ();
199319 when (hub.options).thenReturn (options);
@@ -218,13 +338,31 @@ class _MockIsolateHelper extends Mock implements IsolateHelper {
218338class _MockSpan extends Mock implements SentrySpan {
219339 final SentrySpanContext _context = SentrySpanContext (operation: 'test' );
220340 final List <_SetDataCall > setDataCalls = [];
341+ final List <_RemoveDataCall > removeDataCalls = [];
342+ final Map <String , dynamic > _data = {};
343+
344+ _MockSpan ();
345+
346+ _MockSpan .withData (Map <String , dynamic > data) {
347+ _data.addAll (data);
348+ }
221349
222350 @override
223351 SentrySpanContext get context => _context;
224352
353+ @override
354+ Map <String , dynamic > get data => _data;
355+
225356 @override
226357 void setData (String key, dynamic value) {
227358 setDataCalls.add (_SetDataCall (key, value));
359+ _data[key] = value;
360+ }
361+
362+ @override
363+ void removeData (String key) {
364+ removeDataCalls.add (_RemoveDataCall (key));
365+ _data.remove (key);
228366 }
229367}
230368
@@ -234,3 +372,9 @@ class _SetDataCall {
234372
235373 _SetDataCall (this .key, this .value);
236374}
375+
376+ class _RemoveDataCall {
377+ final String key;
378+
379+ _RemoveDataCall (this .key);
380+ }
0 commit comments