Skip to content

Commit f535386

Browse files
committed
add ttid tracker tests
1 parent 96b9f83 commit f535386

File tree

4 files changed

+224
-11
lines changed

4 files changed

+224
-11
lines changed

flutter/lib/src/navigation/time_to_initial_display_tracker.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class TimeToInitialDisplayTracker {
4141
ttidSpan.origin = SentryTraceOrigins.autoUiTimeToDisplay;
4242
}
4343

44+
// Reset after completion
4445
_isManual = false;
4546

4647
final ttidMeasurement = SentryFlutterMeasurement.timeToInitialDisplay(

flutter/test/mocks.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:sentry/src/sentry_tracer.dart';
99

1010
import 'package:meta/meta.dart';
1111
import 'package:sentry_flutter/sentry_flutter.dart';
12+
import 'package:sentry_flutter/src/frame_callback_handler.dart';
1213
import 'package:sentry_flutter/src/renderer/renderer.dart';
1314
import 'package:sentry_flutter/src/native/sentry_native.dart';
1415
import 'package:sentry_flutter/src/native/sentry_native_binding.dart';
@@ -47,6 +48,7 @@ ISentrySpan startTransactionShim(
4748
SentryTracer,
4849
SentryTransaction,
4950
MethodChannel,
51+
IFrameCallbackHandler,
5052
], customMocks: [
5153
MockSpec<Hub>(fallbackGenerators: {#startTransaction: startTransactionShim})
5254
])

flutter/test/mocks.mocks.dart

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
1-
// Mocks generated by Mockito 5.4.2 from annotations
1+
// Mocks generated by Mockito 5.4.4 from annotations
22
// in sentry_flutter/test/mocks.dart.
33
// Do not manually edit this file.
44

55
// ignore_for_file: no_leading_underscores_for_library_prefixes
66
import 'dart:async' as _i7;
77

8+
import 'package:flutter/scheduler.dart' as _i13;
89
import 'package:flutter/src/services/binary_messenger.dart' as _i6;
910
import 'package:flutter/src/services/message_codec.dart' as _i5;
10-
import 'package:flutter/src/services/platform_channel.dart' as _i10;
11+
import 'package:flutter/src/services/platform_channel.dart' as _i11;
1112
import 'package:mockito/mockito.dart' as _i1;
13+
import 'package:mockito/src/dummies.dart' as _i9;
1214
import 'package:sentry/sentry.dart' as _i2;
13-
import 'package:sentry/src/profiling.dart' as _i9;
15+
import 'package:sentry/src/profiling.dart' as _i10;
1416
import 'package:sentry/src/protocol.dart' as _i3;
1517
import 'package:sentry/src/sentry_envelope.dart' as _i8;
1618
import 'package:sentry/src/sentry_tracer.dart' as _i4;
19+
import 'package:sentry_flutter/src/frame_callback_handler.dart' as _i12;
1720

18-
import 'mocks.dart' as _i11;
21+
import 'mocks.dart' as _i14;
1922

2023
// ignore_for_file: type=lint
2124
// ignore_for_file: avoid_redundant_argument_values
2225
// ignore_for_file: avoid_setters_without_getters
2326
// ignore_for_file: comment_references
27+
// ignore_for_file: deprecated_member_use
28+
// ignore_for_file: deprecated_member_use_from_same_package
2429
// ignore_for_file: implementation_imports
2530
// ignore_for_file: invalid_use_of_visible_for_testing_member
2631
// ignore_for_file: prefer_const_constructors
@@ -192,7 +197,10 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer {
192197
@override
193198
String get name => (super.noSuchMethod(
194199
Invocation.getter(#name),
195-
returnValue: '',
200+
returnValue: _i9.dummyValue<String>(
201+
this,
202+
Invocation.getter(#name),
203+
),
196204
) as String);
197205

198206
@override
@@ -223,7 +231,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer {
223231
);
224232

225233
@override
226-
set profiler(_i9.SentryProfiler? _profiler) => super.noSuchMethod(
234+
set profiler(_i10.SentryProfiler? _profiler) => super.noSuchMethod(
227235
Invocation.setter(
228236
#profiler,
229237
_profiler,
@@ -232,7 +240,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer {
232240
);
233241

234242
@override
235-
set profileInfo(_i9.SentryProfileInfo? _profileInfo) => super.noSuchMethod(
243+
set profileInfo(_i10.SentryProfileInfo? _profileInfo) => super.noSuchMethod(
236244
Invocation.setter(
237245
#profileInfo,
238246
_profileInfo,
@@ -714,15 +722,18 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction {
714722
/// A class which mocks [MethodChannel].
715723
///
716724
/// See the documentation for Mockito's code generation for more information.
717-
class MockMethodChannel extends _i1.Mock implements _i10.MethodChannel {
725+
class MockMethodChannel extends _i1.Mock implements _i11.MethodChannel {
718726
MockMethodChannel() {
719727
_i1.throwOnMissingStub(this);
720728
}
721729

722730
@override
723731
String get name => (super.noSuchMethod(
724732
Invocation.getter(#name),
725-
returnValue: '',
733+
returnValue: _i9.dummyValue<String>(
734+
this,
735+
Invocation.getter(#name),
736+
),
726737
) as String);
727738

728739
@override
@@ -803,6 +814,30 @@ class MockMethodChannel extends _i1.Mock implements _i10.MethodChannel {
803814
);
804815
}
805816

817+
/// A class which mocks [IFrameCallbackHandler].
818+
///
819+
/// See the documentation for Mockito's code generation for more information.
820+
class MockIFrameCallbackHandler extends _i1.Mock
821+
implements _i12.IFrameCallbackHandler {
822+
MockIFrameCallbackHandler() {
823+
_i1.throwOnMissingStub(this);
824+
}
825+
826+
@override
827+
void addPostFrameCallback(
828+
_i13.FrameCallback? callback, {
829+
String? debugLabel,
830+
}) =>
831+
super.noSuchMethod(
832+
Invocation.method(
833+
#addPostFrameCallback,
834+
[callback],
835+
{#debugLabel: debugLabel},
836+
),
837+
returnValueForMissingStub: null,
838+
);
839+
}
840+
806841
/// A class which mocks [Hub].
807842
///
808843
/// See the documentation for Mockito's code generation for more information.
@@ -845,7 +880,7 @@ class MockHub extends _i1.Mock implements _i2.Hub {
845880
) as _i2.Scope);
846881

847882
@override
848-
set profilerFactory(_i9.SentryProfilerFactory? value) => super.noSuchMethod(
883+
set profilerFactory(_i10.SentryProfilerFactory? value) => super.noSuchMethod(
849884
Invocation.setter(
850885
#profilerFactory,
851886
value,
@@ -1050,7 +1085,7 @@ class MockHub extends _i1.Mock implements _i2.Hub {
10501085
#customSamplingContext: customSamplingContext,
10511086
},
10521087
),
1053-
returnValue: _i11.startTransactionShim(
1088+
returnValue: _i14.startTransactionShim(
10541089
name,
10551090
operation,
10561091
description: description,
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import 'dart:math';
2+
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:sentry_flutter/sentry_flutter.dart';
5+
import 'package:sentry_flutter/src/integrations/app_start/app_start_tracker.dart';
6+
import 'package:sentry_flutter/src/navigation/time_to_initial_display_tracker.dart';
7+
8+
import '../fake_frame_callback_handler.dart';
9+
import '../mocks.dart';
10+
import 'package:sentry/src/sentry_tracer.dart';
11+
12+
void main() {
13+
late Fixture fixture;
14+
15+
setUp(() {
16+
fixture = Fixture();
17+
});
18+
19+
group('app start', () {
20+
test('tracking creates and finishes ttid span with correct measurements', () async {
21+
final sut = fixture.getSut();
22+
final transaction = fixture.hub.startTransaction('fake', 'fake')
23+
as SentryTracer;
24+
final startTimestamp = DateTime.now();
25+
final appStartInfo = AppStartInfo(
26+
startTimestamp,
27+
startTimestamp.add(Duration(milliseconds: 10)),
28+
SentryMeasurement.coldAppStart(Duration(milliseconds: 10)));
29+
30+
await sut.trackAppStart(transaction, appStartInfo, 'route ("/")');
31+
32+
final children = transaction.children;
33+
expect(children, hasLength(1));
34+
35+
final ttidSpan = children.first;
36+
expect(ttidSpan.context.operation, SentrySpanOperations.uiTimeToInitialDisplay);
37+
expect(ttidSpan.finished, isTrue);
38+
expect(ttidSpan.context.description, 'route ("/") initial display');
39+
expect(ttidSpan.origin, SentryTraceOrigins.autoUiTimeToDisplay);
40+
41+
final ttidMeasurement = transaction.measurements['time_to_initial_display'];
42+
expect(ttidMeasurement, isNotNull);
43+
expect(ttidMeasurement?.unit, DurationSentryMeasurementUnit.milliSecond);
44+
expect(ttidMeasurement?.value, 10);
45+
46+
final appStartMeasurement = transaction.measurements['app_start_cold'];
47+
expect(appStartMeasurement, isNotNull);
48+
expect(appStartMeasurement?.unit, DurationSentryMeasurementUnit.milliSecond);
49+
expect(appStartMeasurement?.value, 10);
50+
});
51+
});
52+
53+
group('regular route', () {
54+
test('approximation tracking creates and finishes ttid span with correct measurements', () async {
55+
final sut = fixture.getSut();
56+
final transaction = fixture.hub.startTransaction('fake', 'fake')
57+
as SentryTracer;
58+
final startTimestamp = DateTime.now();
59+
60+
await sut.trackRegularRoute(transaction, startTimestamp, 'regular route');
61+
62+
final children = transaction.children;
63+
expect(children, hasLength(1));
64+
65+
final ttidSpan = children.first;
66+
expect(ttidSpan.context.operation, SentrySpanOperations.uiTimeToInitialDisplay);
67+
expect(ttidSpan.finished, isTrue);
68+
expect(ttidSpan.context.description, 'regular route initial display');
69+
expect(ttidSpan.origin, SentryTraceOrigins.autoUiTimeToDisplay);
70+
71+
final ttidMeasurement = transaction.measurements['time_to_initial_display'];
72+
expect(ttidMeasurement, isNotNull);
73+
expect(ttidMeasurement?.unit, DurationSentryMeasurementUnit.milliSecond);
74+
expect(ttidMeasurement?.value, greaterThan(fixture.finishAfterDuration.inMilliseconds));
75+
expect(ttidMeasurement?.value, lessThan(fixture.finishAfterDuration.inMilliseconds + 10));
76+
});
77+
78+
test('manual tracking creates and finishes ttid span with correct measurements', () async {
79+
final sut = fixture.getSut();
80+
final transaction = fixture.hub.startTransaction('fake', 'fake')
81+
as SentryTracer;
82+
final startTimestamp = DateTime.now();
83+
84+
sut.markAsManual();
85+
Future.delayed(fixture.finishAfterDuration, () {
86+
sut.completeTracking();
87+
});
88+
await sut.trackRegularRoute(transaction, startTimestamp, 'regular route');
89+
90+
final children = transaction.children;
91+
expect(children, hasLength(1));
92+
93+
final ttidSpan = children.first;
94+
expect(ttidSpan.context.operation, SentrySpanOperations.uiTimeToInitialDisplay);
95+
expect(ttidSpan.finished, isTrue);
96+
expect(ttidSpan.context.description, 'regular route initial display');
97+
expect(ttidSpan.origin, SentryTraceOrigins.manualUiTimeToDisplay);
98+
99+
final ttidMeasurement = transaction.measurements['time_to_initial_display'];
100+
expect(ttidMeasurement, isNotNull);
101+
expect(ttidMeasurement?.unit, DurationSentryMeasurementUnit.milliSecond);
102+
expect(ttidMeasurement?.value, greaterThan(fixture.finishAfterDuration.inMilliseconds));
103+
expect(ttidMeasurement?.value, lessThan(fixture.finishAfterDuration.inMilliseconds + 10));
104+
});
105+
});
106+
107+
group('determineEndtime', () {
108+
test('can complete automatically in approximation mode', () async {
109+
final sut = fixture.getSut();
110+
111+
final futureEndTime = sut.determineEndTime();
112+
113+
expect(futureEndTime, completes);
114+
});
115+
116+
test('prevents automatic completion in manual mode', () async {
117+
final sut = fixture.getSut();
118+
119+
sut.markAsManual();
120+
final futureEndTime = sut.determineEndTime();
121+
122+
expect(futureEndTime, doesNotComplete);
123+
});
124+
125+
test('can complete manually in manual mode', () async {
126+
final sut = fixture.getSut();
127+
128+
sut.markAsManual();
129+
final futureEndTime = sut.determineEndTime();
130+
131+
sut.completeTracking();
132+
expect(futureEndTime, completes);
133+
});
134+
135+
test('returns the correct approximation end time', () async {
136+
final startTime = DateTime.now();
137+
final sut = fixture.getSut();
138+
139+
final futureEndTime = sut.determineEndTime();
140+
141+
final endTime = await futureEndTime;
142+
expect(endTime?.difference(startTime).inSeconds,
143+
fixture.finishAfterDuration.inSeconds);
144+
});
145+
146+
test('returns the correct manual end time', () async {
147+
final startTime = DateTime.now();
148+
final sut = fixture.getSut();
149+
150+
sut.markAsManual();
151+
final futureEndTime = sut.determineEndTime();
152+
153+
Future.delayed(fixture.finishAfterDuration, () {
154+
sut.completeTracking();
155+
});
156+
157+
final endTime = await futureEndTime;
158+
expect(endTime?.difference(startTime).inSeconds,
159+
fixture.finishAfterDuration.inSeconds);
160+
});
161+
});
162+
}
163+
164+
class Fixture {
165+
final hub = Hub(SentryFlutterOptions(dsn: fakeDsn)..tracesSampleRate = 1.0);
166+
final finishAfterDuration = Duration(milliseconds: 100);
167+
late final fakeFrameCallbackHandler =
168+
FakeFrameCallbackHandler(finishAfterDuration: finishAfterDuration);
169+
170+
TimeToInitialDisplayTracker getSut() {
171+
final sut = TimeToInitialDisplayTracker();
172+
sut.frameCallbackHandler = fakeFrameCallbackHandler;
173+
return sut;
174+
}
175+
}

0 commit comments

Comments
 (0)