FPRScreenTraceTrackerTest.m 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h"
  15. #import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker.h"
  16. #import <XCTest/XCTest.h>
  17. #import <objc/runtime.h>
  18. #import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
  19. #import <OCMock/OCMock.h>
  20. #import "FirebasePerformance/Tests/Unit/FPRTestCase.h"
  21. /** Registers and returns an instance of a custom subclass of UIViewController. */
  22. static UIViewController *FPRCustomViewController(NSString *className, BOOL isViewLoaded) {
  23. Class customClass = NSClassFromString(className);
  24. if (!customClass) {
  25. // Register the class if it does not already exist.
  26. customClass = objc_allocateClassPair([UIViewController class], className.UTF8String, 0);
  27. objc_registerClassPair(customClass);
  28. }
  29. UIViewController *customVC = [[customClass alloc] init];
  30. if (isViewLoaded) {
  31. [customVC view];
  32. }
  33. return customVC;
  34. }
  35. /** Test UINavigationController subclass. */
  36. @interface FPRTestNavigationViewController : UINavigationController
  37. @end
  38. @implementation FPRTestNavigationViewController
  39. @end
  40. /** Test UITabBarController subclass. */
  41. @interface FPRTestTabBarController : UITabBarController
  42. @end
  43. @implementation FPRTestTabBarController
  44. @end
  45. /** Test UISplitViewController subclass. */
  46. @interface FPRTestSplitViewController : UISplitViewController
  47. @end
  48. @implementation FPRTestSplitViewController
  49. @end
  50. /** Test UIPageViewController. */
  51. @interface FPRTestPageViewController : UIPageViewController
  52. @end
  53. @implementation FPRTestPageViewController
  54. @end
  55. @interface FPRScreenTraceTrackerTest : FPRTestCase
  56. /** The FPRScreenTraceTracker instance that's being used for a given test. */
  57. @property(nonatomic, nullable) FPRScreenTraceTracker *tracker;
  58. /** The dispatch group a test should wait for completion on before asserting behavior under test. */
  59. @property(nonatomic, nullable) dispatch_group_t dispatchGroupToWaitOn;
  60. @end
  61. @implementation FPRScreenTraceTrackerTest
  62. - (void)setUp {
  63. [super setUp];
  64. FIRPerformance *performance = [FIRPerformance sharedInstance];
  65. [performance setDataCollectionEnabled:YES];
  66. self.tracker = [[FPRScreenTraceTracker alloc] init];
  67. self.tracker.displayLink.paused = YES;
  68. self.dispatchGroupToWaitOn = self.tracker.screenTraceTrackerDispatchGroup;
  69. }
  70. - (void)tearDown {
  71. [super tearDown];
  72. FIRPerformance *performance = [FIRPerformance sharedInstance];
  73. [performance setDataCollectionEnabled:NO];
  74. self.tracker = nil;
  75. self.dispatchGroupToWaitOn = nil;
  76. }
  77. /** Tests that shared instance returns the same instance. */
  78. - (void)testSingleton {
  79. FPRScreenTraceTracker *trackerOne = [FPRScreenTraceTracker sharedInstance];
  80. FPRScreenTraceTracker *trackerTwo = [FPRScreenTraceTracker sharedInstance];
  81. XCTAssertEqual(trackerOne, trackerTwo); // Check that it's the same instance.
  82. }
  83. /** Tests that the atomic counters are initialized to zero during init. */
  84. - (void)testCountersInitToZero {
  85. FPRScreenTraceTracker *tracker = [[FPRScreenTraceTracker alloc] init];
  86. XCTAssertEqual(tracker.frozenFramesCount, 0);
  87. XCTAssertEqual(tracker.slowFramesCount, 0);
  88. XCTAssertEqual(tracker.totalFramesCount, 0);
  89. }
  90. /** Tests that viewControllerDidAppear starts a trace. */
  91. - (void)testViewControllerDidAppearStartsATraceForVCWithLoadedView {
  92. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  93. [self.tracker viewControllerDidAppear:testViewController];
  94. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  95. XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
  96. NSString *expectedTraceName =
  97. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
  98. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
  99. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  100. XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
  101. XCTAssertFalse(createdTrace.isCompleteAndValid);
  102. }
  103. /** Tests that the trace is not created when data collection is disabled */
  104. - (void)testTraceCreationDisabledWhenDataCollectionDisabled {
  105. @autoreleasepool {
  106. BOOL dataCollectionEnabled = [FIRPerformance sharedInstance].dataCollectionEnabled;
  107. [[FIRPerformance sharedInstance] setDataCollectionEnabled:NO];
  108. UIViewController *newVCInstance =
  109. FPRCustomViewController(@"MyModule.UIFancyViewController", YES);
  110. [self.tracker viewControllerDidAppear:newVCInstance];
  111. // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
  112. // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
  113. // in a timely manner and this test becomes flaky.
  114. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
  115. XCTAssertNil(createdTrace);
  116. // Clean up.
  117. [self.tracker viewControllerDidDisappear:newVCInstance];
  118. newVCInstance = nil;
  119. [[FIRPerformance sharedInstance] setDataCollectionEnabled:dataCollectionEnabled];
  120. }
  121. }
  122. /** Tests that the trace is named correctly in case of Swift classes which are of the format
  123. * ModuleName.ClassName.
  124. */
  125. - (void)testUnprefixedClassName {
  126. @autoreleasepool {
  127. UIViewController *newVCInstance =
  128. FPRCustomViewController(@"MyModule.UIFancyViewController", YES);
  129. [self.tracker viewControllerDidAppear:newVCInstance];
  130. NSString *expectedTraceName = @"_st_UIFancyViewController";
  131. // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
  132. // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
  133. // in a timely manner and this test becomes flaky.
  134. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
  135. XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
  136. createdTrace = nil;
  137. // Clean up.
  138. [self.tracker viewControllerDidDisappear:newVCInstance];
  139. newVCInstance = nil;
  140. }
  141. }
  142. /** Tests that the module name length is not factored into truncating the screen trace name in case
  143. * of Swift classes.
  144. */
  145. - (void)testDoesNotTruncateClassNameExtraLongSwiftModuleName {
  146. NSUInteger valueGreaterThanMaxTraceLength = kFPRMaxNameLength + 10;
  147. NSMutableString *extraLongModuleName =
  148. [[NSMutableString alloc] initWithCapacity:valueGreaterThanMaxTraceLength];
  149. for (int i = 0; i < valueGreaterThanMaxTraceLength; ++i) {
  150. [extraLongModuleName appendString:@"a"];
  151. }
  152. XCTAssertEqual(extraLongModuleName.length, valueGreaterThanMaxTraceLength);
  153. NSString *swiftClassName =
  154. [NSString stringWithFormat:@"%@.%@", extraLongModuleName, @"MyViewController"];
  155. NSString *expectedTraceName = @"_st_MyViewController";
  156. @autoreleasepool {
  157. UIViewController *newVCInstance = FPRCustomViewController(swiftClassName, YES);
  158. [self.tracker viewControllerDidAppear:newVCInstance];
  159. // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
  160. // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
  161. // in a timely manner and this test becomes flaky.
  162. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
  163. XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
  164. createdTrace = nil;
  165. // Clean up.
  166. [self.tracker viewControllerDidDisappear:newVCInstance];
  167. newVCInstance = nil;
  168. }
  169. }
  170. /** Tests that if a Swift class name pushes the screen trace name beyond the max trace name length,
  171. * the screen trace name is truncated.
  172. */
  173. - (void)testTruncatesExtraLongSwiftClassName {
  174. NSUInteger valueGreaterThanMaxTraceLength = kFPRMaxNameLength + 10;
  175. NSMutableString *extraLongClassName = [[NSMutableString alloc] init];
  176. for (int i = 0; i < valueGreaterThanMaxTraceLength; ++i) {
  177. [extraLongClassName appendString:@"a"];
  178. }
  179. XCTAssertEqual(extraLongClassName.length, valueGreaterThanMaxTraceLength);
  180. NSString *swiftClassName = [NSString stringWithFormat:@"%@.%@", @"MyModule", extraLongClassName];
  181. @autoreleasepool {
  182. UIViewController *newVCInstance = FPRCustomViewController(swiftClassName, YES);
  183. [self.tracker viewControllerDidAppear:newVCInstance];
  184. // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
  185. // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
  186. // in a timely manner and this test becomes flaky.
  187. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
  188. XCTAssertEqual(createdTrace.name.length, kFPRMaxNameLength);
  189. createdTrace = nil;
  190. // Clean up.
  191. [self.tracker viewControllerDidDisappear:newVCInstance];
  192. newVCInstance = nil;
  193. }
  194. }
  195. /** Tests that if an ObjC class name pushes the screen trace name beyond the max trace name length,
  196. * the screen trace name is truncated.
  197. */
  198. - (void)testTruncatesExtraLongObjCClassName {
  199. NSUInteger valueGreaterThanMaxTraceLength = kFPRMaxNameLength + 10;
  200. NSMutableString *extraLongClassName = [[NSMutableString alloc] init];
  201. for (int i = 0; i < valueGreaterThanMaxTraceLength; ++i) {
  202. [extraLongClassName appendString:@"a"];
  203. }
  204. XCTAssertEqual(extraLongClassName.length, valueGreaterThanMaxTraceLength);
  205. @autoreleasepool {
  206. UIViewController *newVCInstance = FPRCustomViewController(extraLongClassName, YES);
  207. [self.tracker viewControllerDidAppear:newVCInstance];
  208. // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
  209. // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
  210. // in a timely manner and this test becomes flaky.
  211. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
  212. XCTAssertEqual(createdTrace.name.length, kFPRMaxNameLength);
  213. createdTrace = nil;
  214. // Clean up.
  215. [self.tracker viewControllerDidDisappear:newVCInstance];
  216. newVCInstance = nil;
  217. }
  218. }
  219. /** Tests that a viewController isn't retained by the ScreenTraceTracker. */
  220. - (void)testViewControllerIsHeldWeaklyByTheScreenTraceTracker {
  221. __block UIViewController *newVCInstance = nil;
  222. __weak UIViewController *weakVCReference = nil;
  223. @autoreleasepool {
  224. newVCInstance = [[UIViewController alloc] init];
  225. [newVCInstance view]; // Loads the view so that a screen trace is created for it.
  226. [self.tracker viewControllerDidAppear:newVCInstance];
  227. [self.tracker viewControllerDidDisappear:newVCInstance];
  228. weakVCReference = newVCInstance;
  229. newVCInstance = nil;
  230. }
  231. XCTAssertNil(weakVCReference);
  232. }
  233. /** Tests that viewControllerDidDisappear stops a trace. */
  234. - (void)testViewControllerDidDisappearStopsATrace {
  235. // First screen appears.
  236. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  237. [self.tracker viewControllerDidAppear:testViewController];
  238. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  239. NSString *expectedTraceName =
  240. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
  241. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  242. XCTAssertNotNil(createdTrace);
  243. XCTAssertEqualObjects(expectedTraceName, createdTrace.name);
  244. // First screen disappears.
  245. [self.tracker viewControllerDidDisappear:testViewController];
  246. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  247. XCTAssertTrue(createdTrace.isCompleteAndValid);
  248. }
  249. /** Tests that viewControllerDidAppear starts multiple traces if multiple view controllers with the
  250. * same class appear one after the other.
  251. */
  252. - (void)testViewControllerDidAppearStartsMultipleScreenTracesForSameClassIfNeeded {
  253. // First screen appears.
  254. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  255. [self.tracker viewControllerDidAppear:testViewController];
  256. // Second screen appears, first screen is still visible.
  257. UIViewController *testViewController2 = FPRCustomViewController(@"UIViewController", YES);
  258. [self.tracker viewControllerDidAppear:testViewController2];
  259. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  260. NSString *expectedTraceNameOne =
  261. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
  262. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
  263. FIRTrace *traceForScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
  264. XCTAssertEqualObjects(traceForScreenOne.name, expectedTraceNameOne);
  265. NSString *expectedTraceNameTwo =
  266. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController2];
  267. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
  268. FIRTrace *traceForScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
  269. XCTAssertEqualObjects(traceForScreenTwo.name, expectedTraceNameTwo);
  270. // Test that they're different instances.
  271. XCTAssertNotEqual(traceForScreenOne, traceForScreenTwo);
  272. XCTAssertEqualObjects(traceForScreenOne.name, traceForScreenTwo.name);
  273. }
  274. /** Tests that viewControllerDidAppear starts multiple traces if multiple view controllers with
  275. * different classes appear one after the other.
  276. */
  277. - (void)testViewControllerDidAppearStartsMultipleScreenTracesForDifferentClassIfNeeded {
  278. // First screen appears.
  279. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  280. [self.tracker viewControllerDidAppear:testViewController];
  281. // Second screen appears, first screen is still visible.
  282. UIViewController *testViewController2 = FPRCustomViewController(@"FPRTestViewController", YES);
  283. [self.tracker viewControllerDidAppear:testViewController2];
  284. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  285. NSString *expectedTraceNameOne =
  286. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
  287. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
  288. FIRTrace *traceForScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
  289. XCTAssertEqualObjects(traceForScreenOne.name, expectedTraceNameOne);
  290. NSString *expectedTraceNameTwo =
  291. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController2];
  292. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
  293. FIRTrace *traceForScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
  294. XCTAssertEqualObjects(traceForScreenTwo.name, expectedTraceNameTwo);
  295. XCTAssertNotEqual(traceForScreenOne,
  296. traceForScreenTwo); // Test that they're different instances.
  297. XCTAssertNotEqualObjects(traceForScreenOne.name, traceForScreenTwo.name);
  298. }
  299. /** Tests that viewControllerDidDisappear stops the correct trace when multiple traces are present.
  300. */
  301. - (void)testViewControllerDidDisappearStopsCorrectTraceWhenMultiplePresent {
  302. // First screen appears.
  303. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  304. [self.tracker viewControllerDidAppear:testViewController];
  305. // Second screen appears, first screen is still visible.
  306. UIViewController *testViewController2 = FPRCustomViewController(@"UIViewController", YES);
  307. [self.tracker viewControllerDidAppear:testViewController2];
  308. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  309. XCTAssertEqual(self.tracker.activeScreenTraces.count, 2);
  310. FIRTrace *traceForScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
  311. FIRTrace *traceForScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
  312. XCTAssertFalse(traceForScreenOne.isCompleteAndValid);
  313. XCTAssertFalse(traceForScreenTwo.isCompleteAndValid);
  314. [self.tracker viewControllerDidDisappear:testViewController2];
  315. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  316. XCTAssertFalse(traceForScreenOne.isCompleteAndValid);
  317. XCTAssertTrue(traceForScreenTwo.isCompleteAndValid);
  318. }
  319. /** Tests that viewControllerDidAppear doesn't start a duplicate trace. */
  320. - (void)testViewControllerDidAppearIgnoresDuplicateEvent {
  321. // First screen appears.
  322. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  323. [self.tracker viewControllerDidAppear:testViewController];
  324. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  325. NSString *expectedTraceName =
  326. [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
  327. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  328. XCTAssertNotNil(createdTrace);
  329. XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
  330. // Send the same event again.
  331. [self.tracker viewControllerDidAppear:testViewController];
  332. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  333. XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
  334. FIRTrace *activeTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  335. XCTAssertEqual(createdTrace, activeTrace); // Test that it is the same trace.
  336. }
  337. /** Tests that viewControllerDidAppear gracefully handles a nil viewController. */
  338. - (void)testViewControllerDidAppearGracefullyHandlesNilViewController {
  339. #pragma clang diagnostic push
  340. #pragma clang diagnostic ignored "-Wnonnull"
  341. [self.tracker viewControllerDidAppear:nil];
  342. #pragma clang diagnostic pop
  343. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  344. XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
  345. }
  346. /** Tests that viewControllerDidDisappear for a viewController that did not appear does nothing. */
  347. - (void)testViewControllerDidDisappearIgnoresViewControllerThatWasntScreenTraced {
  348. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  349. UIViewController *testViewController2 = FPRCustomViewController(@"UIViewController", YES);
  350. [self.tracker viewControllerDidAppear:testViewController];
  351. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  352. XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
  353. [self.tracker viewControllerDidDisappear:testViewController2];
  354. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  355. XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
  356. #pragma clang diagnostic push
  357. #pragma clang diagnostic ignored "-Wnonnull"
  358. [self.tracker viewControllerDidDisappear:nil];
  359. #pragma clang diagnostic pop
  360. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  361. XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
  362. [self.tracker viewControllerDidDisappear:testViewController];
  363. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  364. XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
  365. }
  366. /** Tests that UIViewControllers are weakly retained in the map table that holds the mapping between
  367. * them.
  368. */
  369. - (void)testViewControllerIsWeaklyRetained {
  370. @autoreleasepool {
  371. UIViewController *testViewController = [[UIViewController alloc] init];
  372. id mockTrace = OCMClassMock([FIRTrace class]);
  373. [self.tracker.activeScreenTraces setObject:mockTrace forKey:testViewController];
  374. testViewController = nil;
  375. }
  376. XCTAssertEqual([self.tracker.activeScreenTraces dictionaryRepresentation].count, 0);
  377. }
  378. /** Tests that a FIRTrace is strongly retained in the map table that holds the mapping between a
  379. * view controller and its screen trace.
  380. */
  381. - (void)testFIRTraceIsStronglyRetained {
  382. UIViewController *testViewController = [[UIViewController alloc] init];
  383. NSString *traceName = @"screenTrace";
  384. FIRTrace *trace = [[FIRTrace alloc] initInternalTraceWithName:traceName];
  385. [self.tracker.activeScreenTraces setObject:trace forKey:testViewController];
  386. trace = nil;
  387. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
  388. FIRTrace *returnedTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  389. XCTAssertEqualObjects(returnedTrace.name, traceName);
  390. }
  391. /** Tests that a screen trace that doesn't collect any data isn't sent. */
  392. - (void)testTraceWithNoCountersIsNotSent {
  393. id mockTrace = OCMClassMock([FIRTrace class]);
  394. UIViewController *testViewController = [[UIViewController alloc] init];
  395. [self.tracker.activeScreenTraces setObject:mockTrace forKey:testViewController];
  396. OCMExpect([mockTrace cancel]);
  397. [[mockTrace reject] stop];
  398. [self.tracker viewControllerDidDisappear:testViewController];
  399. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  400. OCMVerifyAll(mockTrace);
  401. }
  402. /** Test that all active traces are stopped when the app resigns active status. */
  403. - (void)testWillAppResignActiveStopsAllActiveTraces {
  404. // First screen appears.
  405. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  406. [self.tracker viewControllerDidAppear:testViewController];
  407. // Second screen appears.
  408. UIViewController *testViewController2 = FPRCustomViewController(@"FPRTestViewController", YES);
  409. [self.tracker viewControllerDidAppear:testViewController2];
  410. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  411. FIRTrace *traceScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
  412. FIRTrace *traceScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
  413. XCTAssertNotNil(traceScreenOne);
  414. XCTAssertNotNil(traceScreenTwo);
  415. // App is backgrounded.
  416. NSNotification *appWillResignActiveNSNotification =
  417. [NSNotification notificationWithName:UIApplicationWillResignActiveNotification object:nil];
  418. [self.tracker appWillResignActiveNotification:appWillResignActiveNSNotification];
  419. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  420. XCTAssertTrue(traceScreenOne.isCompleteAndValid);
  421. XCTAssertTrue(traceScreenTwo.isCompleteAndValid);
  422. }
  423. /** Test that viewController refs are weakly saved for future use when the app resigns active
  424. * status.
  425. */
  426. - (void)disabled_testWillAppResignActiveWeaklySavesAllVisibleViewControllers {
  427. // Screen appears.
  428. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  429. [self.tracker viewControllerDidAppear:testViewController];
  430. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  431. // App is backgrounded.
  432. NSNotification *appWillResignActiveNSNotification =
  433. [NSNotification notificationWithName:UIApplicationWillResignActiveNotification object:nil];
  434. [self.tracker appWillResignActiveNotification:appWillResignActiveNSNotification];
  435. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  436. XCTAssertEqual(self.tracker.previouslyVisibleViewControllers.count, 1);
  437. XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
  438. __weak id weakTestViewController = testViewController;
  439. testViewController = nil;
  440. // The blocks retain the view controllers and it sometimes takes some time to release them.
  441. // This is in place to prevent test flakiness. Autoreleasepools do not work in this case.
  442. while (weakTestViewController) {
  443. continue;
  444. }
  445. XCTAssertNil([self.tracker.previouslyVisibleViewControllers pointerAtIndex:0]);
  446. }
  447. /** Tests that new traces are started with the screens that are currently visible after the app
  448. * regains active status.
  449. */
  450. - (void)testAppDidBecomeActiveWillRestoreTracesOfVisibleScreens {
  451. // Simulate state where two screen traces were previously active.
  452. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  453. UIViewController *testViewController2 = FPRCustomViewController(@"FPRTestViewController", YES);
  454. self.tracker.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
  455. [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController];
  456. [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController2];
  457. // App becomes active.
  458. NSNotification *appDidBecomeActiveNSNotification =
  459. [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification object:nil];
  460. [self.tracker appDidBecomeActiveNotification:appDidBecomeActiveNSNotification];
  461. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  462. XCTAssertEqual(self.tracker.activeScreenTraces.count, 2);
  463. XCTAssertNil(self.tracker.previouslyVisibleViewControllers);
  464. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
  465. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
  466. }
  467. /** Tests that if one of the previously visible ViewControllers is deallocated, a new trace isn't
  468. * started for it, and the app doesn't crash. */
  469. - (void)testAppDidBecomeActiveWillNotRestoreTracesOfNilledViewControllers {
  470. // Simulate state where two screen traces were previously active.
  471. UIViewController *testViewController = [[UIViewController alloc] init];
  472. [testViewController view]; // Loads the view so that a screen trace is created for it.
  473. UIViewController *testViewController2 = [[UIViewController alloc] init];
  474. [testViewController2 view]; // Loads the view so that a screen trace is created for it.
  475. self.tracker.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
  476. [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController];
  477. [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController2];
  478. // UIKit deallocates one of the ViewControllers that was previously visible.
  479. testViewController2 = nil;
  480. // App becomes active.
  481. NSNotification *appDidBecomeActiveNSNotification =
  482. [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification object:nil];
  483. [self.tracker appDidBecomeActiveNotification:appDidBecomeActiveNSNotification];
  484. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  485. XCTAssertNil(self.tracker.previouslyVisibleViewControllers);
  486. XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
  487. XCTAssertNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
  488. }
  489. /** Tests that if consecutive frames take more time to render than the slow frames threshold, the
  490. * slow frame counter of the screen trace tracker is incremented.
  491. */
  492. - (void)testSlowFrameIsRecorded {
  493. CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
  494. CFAbsoluteTime secondFrameRenderTimestamp =
  495. firstFrameRenderTimestamp + kFPRSlowFrameThreshold + 0.005; // Buffer for float comparison.
  496. id displayLinkMock = OCMClassMock([CADisplayLink class]);
  497. [self.tracker.displayLink invalidate];
  498. self.tracker.displayLink = displayLinkMock;
  499. // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
  500. OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
  501. [self.tracker displayLinkStep];
  502. int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
  503. OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
  504. [self.tracker displayLinkStep];
  505. int64_t newSlowFramesCount = self.tracker.slowFramesCount;
  506. XCTAssertEqual(newSlowFramesCount, initialSlowFramesCount + 1);
  507. }
  508. /** Tests that the slow and frozen frame counter is not incremented in the case of a good frame. */
  509. - (void)testSlowAndFrozenFrameIsNotRecordedInCaseOfGoodFrame {
  510. CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
  511. CFAbsoluteTime secondFrameRenderTimestamp =
  512. firstFrameRenderTimestamp + kFPRSlowFrameThreshold - 0.005; // Good frame.
  513. id displayLinkMock = OCMClassMock([CADisplayLink class]);
  514. [self.tracker.displayLink invalidate];
  515. self.tracker.displayLink = displayLinkMock;
  516. // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
  517. OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
  518. [self.tracker displayLinkStep];
  519. int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
  520. int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
  521. OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
  522. [self.tracker displayLinkStep];
  523. int64_t newSlowFramesCount = self.tracker.slowFramesCount;
  524. int64_t newFrozenFramesCount = self.tracker.frozenFramesCount;
  525. XCTAssertEqual(newSlowFramesCount, initialSlowFramesCount);
  526. XCTAssertEqual(newFrozenFramesCount, initialFrozenFramesCount);
  527. }
  528. /* Tests that the frozen frame counter is not incremented in case of a slow frame. */
  529. - (void)testFrozenFrameIsNotRecordedInCaseOfSlowFrame {
  530. CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
  531. CFAbsoluteTime secondFrameRenderTimestamp =
  532. firstFrameRenderTimestamp + kFPRSlowFrameThreshold + 0.005; // Slow frame.
  533. id displayLinkMock = OCMClassMock([CADisplayLink class]);
  534. [self.tracker.displayLink invalidate];
  535. self.tracker.displayLink = displayLinkMock;
  536. // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
  537. OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
  538. [self.tracker displayLinkStep];
  539. int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
  540. OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
  541. [self.tracker displayLinkStep];
  542. int64_t newFrozenFramesCount = self.tracker.frozenFramesCount;
  543. XCTAssertEqual(newFrozenFramesCount, initialFrozenFramesCount);
  544. }
  545. /** Tests that the total frames counter is incremented in the case of good, slow and frozen
  546. * frames.
  547. */
  548. - (void)testTotalFramesAreAlwaysRecorded {
  549. CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
  550. CFAbsoluteTime secondFrameRenderTimestamp =
  551. firstFrameRenderTimestamp + kFPRSlowFrameThreshold - 0.005; // Good frame.
  552. CFAbsoluteTime thirdFrameRenderTimestamp =
  553. secondFrameRenderTimestamp + kFPRSlowFrameThreshold + 0.005; // Slow frame.
  554. CFAbsoluteTime fourthFrameRenderTimestamp =
  555. thirdFrameRenderTimestamp + kFPRFrozenFrameThreshold + 0.005; // Frozen frame.
  556. id displayLinkMock = OCMClassMock([CADisplayLink class]);
  557. [self.tracker.displayLink invalidate];
  558. self.tracker.displayLink = displayLinkMock;
  559. // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
  560. OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
  561. [self.tracker displayLinkStep];
  562. int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
  563. OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
  564. [self.tracker displayLinkStep];
  565. int64_t newTotalFramesCount = self.tracker.totalFramesCount;
  566. XCTAssertEqual(newTotalFramesCount, initialTotalFramesCount + 1);
  567. OCMExpect([displayLinkMock timestamp]).andReturn(thirdFrameRenderTimestamp);
  568. [self.tracker displayLinkStep];
  569. newTotalFramesCount = self.tracker.totalFramesCount;
  570. XCTAssertEqual(newTotalFramesCount, initialTotalFramesCount + 2);
  571. OCMExpect([displayLinkMock timestamp]).andReturn(fourthFrameRenderTimestamp);
  572. [self.tracker displayLinkStep];
  573. newTotalFramesCount = self.tracker.totalFramesCount;
  574. XCTAssertEqual(newTotalFramesCount, initialTotalFramesCount + 3);
  575. }
  576. /** Tests that if consecutive frames take more time to render than the frozen frames threshold, the
  577. * frozen frame counter and slow frame counter of the screen trace tracker is incremented.
  578. */
  579. - (void)testFrozenFrameAndSlowFrameIsRecorded {
  580. CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
  581. CFAbsoluteTime secondFrameRenderTimestamp =
  582. firstFrameRenderTimestamp + kFPRFrozenFrameThreshold + 0.005; // Buffer for float comparison.
  583. id displayLinkMock = OCMClassMock([CADisplayLink class]);
  584. [self.tracker.displayLink invalidate];
  585. self.tracker.displayLink = displayLinkMock;
  586. // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
  587. OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
  588. [self.tracker displayLinkStep];
  589. int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
  590. int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
  591. OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
  592. [self.tracker displayLinkStep];
  593. int64_t newSlowFramesCount = self.tracker.slowFramesCount;
  594. int64_t newFrozenFramesCount = self.tracker.frozenFramesCount;
  595. XCTAssertEqual(newFrozenFramesCount, initialFrozenFramesCount + 1);
  596. XCTAssertEqual(newSlowFramesCount, initialSlowFramesCount + 1);
  597. }
  598. /** Tests that the correct number of slow, frozen and total frames are recorded when all 3 are
  599. * present.
  600. */
  601. - (void)testTraceHasCorrectFrozenSlowAndTotalFrameMetricsWhenThoseFramesAreRecorded {
  602. int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
  603. int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
  604. int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
  605. int64_t expectedTotalFramesOnTrace = 5;
  606. int64_t expectedSlowFramesOnTrace = 3;
  607. int64_t expectedFrozenFramesOnTrace = 1;
  608. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  609. [self.tracker viewControllerDidAppear:testViewController];
  610. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  611. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  612. self.tracker.totalFramesCount = initialTotalFramesCount + expectedTotalFramesOnTrace;
  613. self.tracker.slowFramesCount = initialSlowFramesCount + expectedSlowFramesOnTrace;
  614. self.tracker.frozenFramesCount = initialFrozenFramesCount + expectedFrozenFramesOnTrace;
  615. [self.tracker viewControllerDidDisappear:testViewController];
  616. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  617. XCTAssertEqual([createdTrace valueForIntMetric:kFPRSlowFrameCounterName],
  618. expectedSlowFramesOnTrace);
  619. XCTAssertEqual([createdTrace valueForIntMetric:kFPRFrozenFrameCounterName],
  620. expectedFrozenFramesOnTrace);
  621. XCTAssertEqual([createdTrace valueForIntMetric:kFPRTotalFramesCounterName],
  622. expectedTotalFramesOnTrace);
  623. }
  624. /** Tests that if just total and slow frame and no frozen frames are recorded, then the frozen
  625. * frames metric is not present on the trace. */
  626. - (void)testTraceHasJustSlowAndTotalFrameMetricsWhenNoFrozenFramesAreRecorded {
  627. int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
  628. int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
  629. int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
  630. int64_t expectedTotalFramesOnTrace = 5;
  631. int64_t expectedSlowFramesOnTrace = 3;
  632. int64_t expectedFrozenFramesOnTrace = 0;
  633. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  634. [self.tracker viewControllerDidAppear:testViewController];
  635. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  636. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  637. self.tracker.totalFramesCount = initialTotalFramesCount + expectedTotalFramesOnTrace;
  638. self.tracker.slowFramesCount = initialSlowFramesCount + expectedSlowFramesOnTrace;
  639. self.tracker.frozenFramesCount = initialFrozenFramesCount + expectedFrozenFramesOnTrace;
  640. [self.tracker viewControllerDidDisappear:testViewController];
  641. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  642. XCTAssertEqual([createdTrace valueForIntMetric:kFPRSlowFrameCounterName],
  643. expectedSlowFramesOnTrace);
  644. XCTAssertEqual([createdTrace valueForIntMetric:kFPRTotalFramesCounterName],
  645. expectedTotalFramesOnTrace);
  646. XCTAssertNil(createdTrace.counters[kFPRFrozenFrameCounterName]);
  647. XCTAssertEqual(createdTrace.counters.count, 2);
  648. }
  649. /** Tests that when no frozen or slow frames are recorded, the trace only has the total frames
  650. * counter.
  651. */
  652. - (void)testTraceHasJustTotalFrameMetricsWhenNoFrozenOrSlowFramesAreRecorded {
  653. int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
  654. int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
  655. int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
  656. int64_t expectedTotalFramesOnTrace = 5;
  657. int64_t expectedSlowFramesOnTrace = 0;
  658. int64_t expectedFrozenFramesOnTrace = 0;
  659. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  660. [self.tracker viewControllerDidAppear:testViewController];
  661. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  662. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  663. self.tracker.totalFramesCount = initialTotalFramesCount + expectedTotalFramesOnTrace;
  664. self.tracker.slowFramesCount = initialSlowFramesCount + expectedSlowFramesOnTrace;
  665. self.tracker.frozenFramesCount = initialFrozenFramesCount + expectedFrozenFramesOnTrace;
  666. [self.tracker viewControllerDidDisappear:testViewController];
  667. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  668. XCTAssertEqual([createdTrace valueForIntMetric:kFPRTotalFramesCounterName],
  669. expectedTotalFramesOnTrace);
  670. XCTAssertNil(createdTrace.counters[kFPRSlowFrameCounterName]);
  671. XCTAssertNil(createdTrace.counters[kFPRFrozenFrameCounterName]);
  672. XCTAssertEqual(createdTrace.counters.count, 1);
  673. }
  674. /** Tests that if no frames are recorded between a trace being started and stopped, it doesn't have
  675. * any metrics associated with it.
  676. */
  677. - (void)testTraceHasNoMetricsWhenNoFramesAreRecorded {
  678. UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
  679. [self.tracker viewControllerDidAppear:testViewController];
  680. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  681. FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
  682. [self.tracker viewControllerDidDisappear:testViewController];
  683. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  684. XCTAssertEqual(createdTrace.counters.count, 0);
  685. }
  686. /** Tests that screen traces are NOT created for container view controllers. */
  687. - (void)testScreenTracesAreNotCreatedForContainerViewControllers {
  688. UINavigationController *testNavigationController =
  689. (UINavigationController *)FPRCustomViewController(@"UINavigationController", YES);
  690. UITabBarController *testTabBarController =
  691. (UITabBarController *)FPRCustomViewController(@"UITabBarController", YES);
  692. UISplitViewController *testSplitViewController =
  693. (UISplitViewController *)FPRCustomViewController(@"UISplitViewController", YES);
  694. UIPageViewController *testPageViewController =
  695. (UIPageViewController *)FPRCustomViewController(@"UIPageViewController", YES);
  696. [self.tracker viewControllerDidAppear:testNavigationController];
  697. [self.tracker viewControllerDidAppear:testTabBarController];
  698. [self.tracker viewControllerDidAppear:testSplitViewController];
  699. [self.tracker viewControllerDidAppear:testPageViewController];
  700. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  701. XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
  702. }
  703. /** Tests that screen traces are created for canonical container view controller subclasses. */
  704. - (void)testScreenTracesAreCreatedForContainerViewControllerSubclasses {
  705. FPRTestNavigationViewController *testNavigationControllerSubclass =
  706. (FPRTestNavigationViewController *)FPRCustomViewController(@"FPRTestNavigationViewController",
  707. YES);
  708. FPRTestTabBarController *testTabBarControllerSubclass =
  709. (FPRTestTabBarController *)FPRCustomViewController(@"FPRTestTabBarController", YES);
  710. FPRTestSplitViewController *testSplitViewControllerSubclass =
  711. (FPRTestSplitViewController *)FPRCustomViewController(@"FPRTestSplitViewController", YES);
  712. FPRTestPageViewController *testPageViewControllerSubclass =
  713. (FPRTestPageViewController *)FPRCustomViewController(@"FPRTestPageViewController", YES);
  714. [self.tracker viewControllerDidAppear:testNavigationControllerSubclass];
  715. [self.tracker viewControllerDidAppear:testTabBarControllerSubclass];
  716. [self.tracker viewControllerDidAppear:testSplitViewControllerSubclass];
  717. [self.tracker viewControllerDidAppear:testPageViewControllerSubclass];
  718. dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
  719. XCTAssertEqual(self.tracker.activeScreenTraces.count, 4);
  720. }
  721. #pragma mark - Helper methods
  722. + (NSString *)expectedTraceNameForViewController:(UIViewController *)viewController {
  723. return [@"_st_" stringByAppendingString:NSStringFromClass([viewController class])];
  724. }
  725. @end