-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathStartingRUMSessionTests.swift
332 lines (278 loc) · 14.9 KB
/
StartingRUMSessionTests.swift
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/
import XCTest
@testable import DatadogRUM
import DatadogInternal
import TestUtilities
/// Test case covering scenarios of starting RUM session.
class StartingRUMSessionTests: XCTestCase {
private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional
private var rumConfig: RUM.Configuration! // swiftlint:disable:this implicitly_unwrapped_optional
/// The date of app process start.
private let processStartTime = Date()
/// The time it took for app process to start.
private let processStartDuration: TimeInterval = 2.5
/// The date of SDK init.
private lazy var sdkInitTime = processStartTime.addingTimeInterval(processStartDuration)
/// The date of first activity in RUM.
private lazy var firstRUMTime = sdkInitTime.addingTimeInterval(3)
override func setUp() {
super.setUp()
core = DatadogCoreProxy()
rumConfig = RUM.Configuration(applicationID: .mockAny())
}
override func tearDown() {
core.flushAndTearDown()
core = nil
rumConfig = nil
super.tearDown()
}
func testGivenAppLaunchInForegroundAndNoPrewarming_whenRUMisEnabled_itStartsAppLaunchViewAndSendsAppStartAction() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: false
),
applicationStateHistory: .mockAppInForeground(
since: processStartTime
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = .mockRandom() // no matter BET state
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
// Then
let session = try RUMSessionMatcher
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
.takeSingle()
XCTAssertEqual(session.views.count, 1)
XCTAssertTrue(try session.has(sessionPrecondition: .userAppLaunch), "Session must be marked as 'user app launch'")
let initView = try XCTUnwrap(session.views.first)
XCTAssertTrue(initView.isApplicationLaunchView(), "Session should begin with 'app launch' view")
let initViewEvent = try XCTUnwrap(initView.viewEvents.last)
XCTAssertEqual(initViewEvent.date, processStartTime.timeIntervalSince1970.toInt64Milliseconds, "The 'app launch' view should start at process launch")
XCTAssertEqual(initViewEvent.view.timeSpent, (sdkInitTime.timeIntervalSince(processStartTime)).toInt64Nanoseconds, "The 'app launch' view should span from app launch to 'sdk init'")
let actionEvent = try XCTUnwrap(initView.actionEvents.first)
XCTAssertEqual(actionEvent.action.type, .applicationStart, "The 'app launch' view should send 'app start' action")
XCTAssertEqual(actionEvent.date, initViewEvent.date, "The 'app start' action must occur at the beginning of 'app launch' view")
XCTAssertEqual(actionEvent.action.loadingTime, processStartDuration.toInt64Nanoseconds, "The duration of 'app start' action must equal to the duration of process launch")
}
func testGivenAppLaunchInForegroundAndNoPrewarming_whenRUMisEnabledAndViewIsTracked_itStartsWithAppLaunchViewAndSendsAppStartAction() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: false
),
applicationStateHistory: .mockAppInForeground(
since: processStartTime
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = .mockRandom() // no matter BET state
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
rumTime.now = firstRUMTime
RUMMonitor.shared(in: core).startView(key: "key", name: "FirstView")
// Then
let session = try RUMSessionMatcher
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
.takeSingle()
XCTAssertEqual(session.views.count, 2)
XCTAssertTrue(try session.has(sessionPrecondition: .userAppLaunch), "Session must be marked as 'user app launch'")
let initView = try XCTUnwrap(session.views.first)
XCTAssertTrue(initView.isApplicationLaunchView(), "Session should begin with 'app launch' view")
let initViewEvent = try XCTUnwrap(initView.viewEvents.last)
XCTAssertEqual(initViewEvent.date, processStartTime.timeIntervalSince1970.toInt64Milliseconds, "The 'app launch' view should start at process launch")
XCTAssertEqual(initViewEvent.view.timeSpent, (firstRUMTime.timeIntervalSince(processStartTime)).toInt64Nanoseconds, "The 'app launch' view should span from app launch to first view")
let actionEvent = try XCTUnwrap(initView.actionEvents.first)
XCTAssertEqual(actionEvent.action.type, .applicationStart, "The 'app launch' view should send 'app start' action")
XCTAssertEqual(actionEvent.date, initViewEvent.date, "The 'app start' action must occur at the beginning of 'app launch' view")
XCTAssertEqual(actionEvent.action.loadingTime, processStartDuration.toInt64Nanoseconds, "The duration of 'app start' action must equal to the duration of process launch")
let firstView = try XCTUnwrap(session.views.last)
XCTAssertEqual(firstView.name, "FirstView")
XCTAssertEqual(firstView.viewEvents.last?.date, firstRUMTime.timeIntervalSince1970.toInt64Milliseconds)
}
func testGivenAppLaunchInBackgroundAndNoPrewarming_whenRUMisEnabledAndViewIsTracked_itStartsWithTrackedView() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: false
),
applicationStateHistory: .mockAppInBackground(
since: processStartTime
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = .mockRandom() // no matter BET state
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
rumTime.now = firstRUMTime
RUMMonitor.shared(in: core).startView(key: "key", name: "FirstView")
// Then
let session = try RUMSessionMatcher
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
.takeSingle()
XCTAssertEqual(session.views.count, 1)
XCTAssertTrue(try session.has(sessionPrecondition: .backgroundLaunch), "Session must be marked as 'background launch'")
let firstView = try XCTUnwrap(session.views.first)
XCTAssertFalse(firstView.isApplicationLaunchView(), "Session should not begin with 'app launch' view")
XCTAssertEqual(firstView.name, "FirstView")
XCTAssertEqual(firstView.viewEvents.last?.date, firstRUMTime.timeIntervalSince1970.toInt64Milliseconds)
XCTAssertTrue(firstView.actionEvents.isEmpty, "The 'app start' action should not be sent")
}
func testGivenAppLaunchWithPrewarming_whenRUMisEnabledAndViewIsTrackedInBackground_itStartsWithTrackedView() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: true
),
applicationStateHistory: .mockAppInBackground( // active prewarm implies background
since: processStartTime
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = .mockRandom() // no matter BET state
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
rumTime.now = firstRUMTime
RUMMonitor.shared(in: core).startView(key: "key", name: "FirstView")
// Then
let session = try RUMSessionMatcher
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
.takeSingle()
XCTAssertEqual(session.views.count, 1)
XCTAssertTrue(try session.has(sessionPrecondition: .prewarm), "Session must be marked as 'prewarm'")
let firstView = try XCTUnwrap(session.views.first)
XCTAssertFalse(firstView.isApplicationLaunchView(), "Session should not begin with 'app launch' view")
XCTAssertEqual(firstView.name, "FirstView")
XCTAssertEqual(firstView.viewEvents.last?.date, firstRUMTime.timeIntervalSince1970.toInt64Milliseconds)
XCTAssertTrue(firstView.actionEvents.isEmpty, "The 'app start' action should not be sent")
}
func testGivenAppLaunchWithPrewarming_whenRUMisEnabledAndViewIsTrackedInForeground_itStartsWithAppLaunchViewAndSendsAppStartAction() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: true
),
applicationStateHistory: AppStateHistory(
initialSnapshot: .init(state: .background, date: processStartTime), // active prewarm implies background
recentDate: firstRUMTime,
snapshots: [
.init(state: .active, date: firstRUMTime.addingTimeInterval(-0.5)) // become active shortly before view is started
]
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = .mockRandom() // no matter BET state
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
rumTime.now = firstRUMTime
RUMMonitor.shared(in: core).startView(key: "key", name: "FirstView")
// Then
let session = try RUMSessionMatcher
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
.takeSingle()
XCTAssertEqual(session.views.count, 2)
XCTAssertTrue(try session.has(sessionPrecondition: .prewarm), "Session must be marked as 'prewarm'")
let initView = try XCTUnwrap(session.views.first)
XCTAssertTrue(initView.isApplicationLaunchView(), "Session should begin with 'app launch' view")
let initViewEvent = try XCTUnwrap(initView.viewEvents.last)
XCTAssertEqual(initViewEvent.date, sdkInitTime.timeIntervalSince1970.toInt64Milliseconds, "The 'app launch' view should start at sdk init")
XCTAssertEqual(initViewEvent.view.timeSpent, (firstRUMTime.timeIntervalSince(sdkInitTime)).toInt64Nanoseconds, "The 'app launch' view should span from sdk init to first view")
let actionEvent = try XCTUnwrap(initView.actionEvents.first)
XCTAssertEqual(actionEvent.action.type, .applicationStart, "The 'app launch' view should send 'app start' action")
XCTAssertEqual(actionEvent.date, initViewEvent.date, "The 'app start' action must occur at the beginning of 'app launch' view")
XCTAssertNil(actionEvent.action.loadingTime, "The 'app start' action must have no 'loading time' set as we can't know it for prewarmed session")
let firstView = try XCTUnwrap(session.views.last)
XCTAssertEqual(firstView.name, "FirstView")
XCTAssertEqual(firstView.viewEvents.last?.date, firstRUMTime.timeIntervalSince1970.toInt64Milliseconds)
}
// MARK: - Test Background Events Tracking
func testGivenAppLaunchWithPrewarmingAndBETEnabled_whenRUMisEnabledAndInteractionEventIsTracked_itStartsWithBackgroundView() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: true
),
applicationStateHistory: .mockAppInBackground( // active prewarm implies background
since: processStartTime
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = true
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
rumTime.now = firstRUMTime
RUMMonitor.shared(in: core).addAction(type: .custom, name: "CustomAction")
// Then
let session = try RUMSessionMatcher
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
.takeSingle()
XCTAssertEqual(session.views.count, 1)
XCTAssertTrue(try session.has(sessionPrecondition: .prewarm), "Session must be marked as 'prewarm'")
let initView = try XCTUnwrap(session.views.first)
XCTAssertTrue(initView.isBackgroundView(), "Session should begin with 'background' view")
XCTAssertFalse(initView.actionEvents.contains(where: { $0.action.type == .applicationStart }), "The 'app start' action should not be sent")
let actionEvent = try XCTUnwrap(initView.actionEvents.first)
XCTAssertEqual(actionEvent.date, firstRUMTime.timeIntervalSince1970.toInt64Milliseconds)
XCTAssertEqual(actionEvent.action.target?.name, "CustomAction")
}
func testGivenAppLaunchWithPrewarmingAndBETDisabled_whenRUMisEnabledAndInteractionEventIsTracked_itIgnoresTheEvent() throws {
// Given
core.context = .mockWith(
sdkInitDate: sdkInitTime,
launchTime: .mockWith(
launchTime: processStartDuration,
launchDate: processStartTime,
isActivePrewarm: true
),
applicationStateHistory: .mockAppInBackground( // active prewarm implies background
since: processStartTime
)
)
let rumTime = DateProviderMock()
rumConfig.dateProvider = rumTime
rumConfig.trackBackgroundEvents = false
// When
rumTime.now = sdkInitTime
RUM.enable(with: rumConfig, in: core)
rumTime.now = firstRUMTime
RUMMonitor.shared(in: core).addAction(type: .custom, name: "CustomAction")
// Then
let events = core.waitAndReturnEventsData(ofFeature: RUMFeature.name, timeout: .now() + 1)
XCTAssertTrue(events.isEmpty, "The session should not be started (no events must be sent)")
}
}