| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904 |
- // Copyright 2020 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h"
- #import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker.h"
- #import <XCTest/XCTest.h>
- #import <objc/runtime.h>
- #import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
- #import <OCMock/OCMock.h>
- #import "FirebasePerformance/Tests/Unit/FPRTestCase.h"
- /** Registers and returns an instance of a custom subclass of UIViewController. */
- static UIViewController *FPRCustomViewController(NSString *className, BOOL isViewLoaded) {
- Class customClass = NSClassFromString(className);
- if (!customClass) {
- // Register the class if it does not already exist.
- customClass = objc_allocateClassPair([UIViewController class], className.UTF8String, 0);
- objc_registerClassPair(customClass);
- }
- UIViewController *customVC = [[customClass alloc] init];
- if (isViewLoaded) {
- [customVC view];
- }
- return customVC;
- }
- /** Test UINavigationController subclass. */
- @interface FPRTestNavigationViewController : UINavigationController
- @end
- @implementation FPRTestNavigationViewController
- @end
- /** Test UITabBarController subclass. */
- @interface FPRTestTabBarController : UITabBarController
- @end
- @implementation FPRTestTabBarController
- @end
- /** Test UISplitViewController subclass. */
- @interface FPRTestSplitViewController : UISplitViewController
- @end
- @implementation FPRTestSplitViewController
- @end
- /** Test UIPageViewController. */
- @interface FPRTestPageViewController : UIPageViewController
- @end
- @implementation FPRTestPageViewController
- @end
- @interface FPRScreenTraceTrackerTest : FPRTestCase
- /** The FPRScreenTraceTracker instance that's being used for a given test. */
- @property(nonatomic, nullable) FPRScreenTraceTracker *tracker;
- /** The dispatch group a test should wait for completion on before asserting behavior under test. */
- @property(nonatomic, nullable) dispatch_group_t dispatchGroupToWaitOn;
- @end
- @implementation FPRScreenTraceTrackerTest
- - (void)setUp {
- [super setUp];
- FIRPerformance *performance = [FIRPerformance sharedInstance];
- [performance setDataCollectionEnabled:YES];
- self.tracker = [[FPRScreenTraceTracker alloc] init];
- self.tracker.displayLink.paused = YES;
- self.dispatchGroupToWaitOn = self.tracker.screenTraceTrackerDispatchGroup;
- }
- - (void)tearDown {
- [super tearDown];
- FIRPerformance *performance = [FIRPerformance sharedInstance];
- [performance setDataCollectionEnabled:NO];
- self.tracker = nil;
- self.dispatchGroupToWaitOn = nil;
- }
- /** Tests that shared instance returns the same instance. */
- - (void)testSingleton {
- FPRScreenTraceTracker *trackerOne = [FPRScreenTraceTracker sharedInstance];
- FPRScreenTraceTracker *trackerTwo = [FPRScreenTraceTracker sharedInstance];
- XCTAssertEqual(trackerOne, trackerTwo); // Check that it's the same instance.
- }
- /** Tests that the atomic counters are initialized to zero during init. */
- - (void)testCountersInitToZero {
- FPRScreenTraceTracker *tracker = [[FPRScreenTraceTracker alloc] init];
- XCTAssertEqual(tracker.frozenFramesCount, 0);
- XCTAssertEqual(tracker.slowFramesCount, 0);
- XCTAssertEqual(tracker.totalFramesCount, 0);
- }
- /** Tests that viewControllerDidAppear starts a trace. */
- - (void)testViewControllerDidAppearStartsATraceForVCWithLoadedView {
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
- NSString *expectedTraceName =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
- XCTAssertFalse(createdTrace.isCompleteAndValid);
- }
- /** Tests that the trace is not created when data collection is disabled */
- - (void)testTraceCreationDisabledWhenDataCollectionDisabled {
- @autoreleasepool {
- BOOL dataCollectionEnabled = [FIRPerformance sharedInstance].dataCollectionEnabled;
- [[FIRPerformance sharedInstance] setDataCollectionEnabled:NO];
- UIViewController *newVCInstance =
- FPRCustomViewController(@"MyModule.UIFancyViewController", YES);
- [self.tracker viewControllerDidAppear:newVCInstance];
- // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
- // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
- // in a timely manner and this test becomes flaky.
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
- XCTAssertNil(createdTrace);
- // Clean up.
- [self.tracker viewControllerDidDisappear:newVCInstance];
- newVCInstance = nil;
- [[FIRPerformance sharedInstance] setDataCollectionEnabled:dataCollectionEnabled];
- }
- }
- /** Tests that the trace is named correctly in case of Swift classes which are of the format
- * ModuleName.ClassName.
- */
- - (void)testUnprefixedClassName {
- @autoreleasepool {
- UIViewController *newVCInstance =
- FPRCustomViewController(@"MyModule.UIFancyViewController", YES);
- [self.tracker viewControllerDidAppear:newVCInstance];
- NSString *expectedTraceName = @"_st_UIFancyViewController";
- // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
- // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
- // in a timely manner and this test becomes flaky.
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
- XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
- createdTrace = nil;
- // Clean up.
- [self.tracker viewControllerDidDisappear:newVCInstance];
- newVCInstance = nil;
- }
- }
- /** Tests that the module name length is not factored into truncating the screen trace name in case
- * of Swift classes.
- */
- - (void)testDoesNotTruncateClassNameExtraLongSwiftModuleName {
- NSUInteger valueGreaterThanMaxTraceLength = kFPRMaxNameLength + 10;
- NSMutableString *extraLongModuleName =
- [[NSMutableString alloc] initWithCapacity:valueGreaterThanMaxTraceLength];
- for (int i = 0; i < valueGreaterThanMaxTraceLength; ++i) {
- [extraLongModuleName appendString:@"a"];
- }
- XCTAssertEqual(extraLongModuleName.length, valueGreaterThanMaxTraceLength);
- NSString *swiftClassName =
- [NSString stringWithFormat:@"%@.%@", extraLongModuleName, @"MyViewController"];
- NSString *expectedTraceName = @"_st_MyViewController";
- @autoreleasepool {
- UIViewController *newVCInstance = FPRCustomViewController(swiftClassName, YES);
- [self.tracker viewControllerDidAppear:newVCInstance];
- // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
- // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
- // in a timely manner and this test becomes flaky.
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
- XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
- createdTrace = nil;
- // Clean up.
- [self.tracker viewControllerDidDisappear:newVCInstance];
- newVCInstance = nil;
- }
- }
- /** Tests that if a Swift class name pushes the screen trace name beyond the max trace name length,
- * the screen trace name is truncated.
- */
- - (void)testTruncatesExtraLongSwiftClassName {
- NSUInteger valueGreaterThanMaxTraceLength = kFPRMaxNameLength + 10;
- NSMutableString *extraLongClassName = [[NSMutableString alloc] init];
- for (int i = 0; i < valueGreaterThanMaxTraceLength; ++i) {
- [extraLongClassName appendString:@"a"];
- }
- XCTAssertEqual(extraLongClassName.length, valueGreaterThanMaxTraceLength);
- NSString *swiftClassName = [NSString stringWithFormat:@"%@.%@", @"MyModule", extraLongClassName];
- @autoreleasepool {
- UIViewController *newVCInstance = FPRCustomViewController(swiftClassName, YES);
- [self.tracker viewControllerDidAppear:newVCInstance];
- // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
- // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
- // in a timely manner and this test becomes flaky.
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
- XCTAssertEqual(createdTrace.name.length, kFPRMaxNameLength);
- createdTrace = nil;
- // Clean up.
- [self.tracker viewControllerDidDisappear:newVCInstance];
- newVCInstance = nil;
- }
- }
- /** Tests that if an ObjC class name pushes the screen trace name beyond the max trace name length,
- * the screen trace name is truncated.
- */
- - (void)testTruncatesExtraLongObjCClassName {
- NSUInteger valueGreaterThanMaxTraceLength = kFPRMaxNameLength + 10;
- NSMutableString *extraLongClassName = [[NSMutableString alloc] init];
- for (int i = 0; i < valueGreaterThanMaxTraceLength; ++i) {
- [extraLongClassName appendString:@"a"];
- }
- XCTAssertEqual(extraLongClassName.length, valueGreaterThanMaxTraceLength);
- @autoreleasepool {
- UIViewController *newVCInstance = FPRCustomViewController(extraLongClassName, YES);
- [self.tracker viewControllerDidAppear:newVCInstance];
- // objectForKey: is always executed on the FPRScreenTraceTracker serial queue, which has its own
- // autorelesepool. Without the autoreleasepool, the ViewController instance is not released
- // in a timely manner and this test becomes flaky.
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:newVCInstance];
- XCTAssertEqual(createdTrace.name.length, kFPRMaxNameLength);
- createdTrace = nil;
- // Clean up.
- [self.tracker viewControllerDidDisappear:newVCInstance];
- newVCInstance = nil;
- }
- }
- /** Tests that a viewController isn't retained by the ScreenTraceTracker. */
- - (void)testViewControllerIsHeldWeaklyByTheScreenTraceTracker {
- __block UIViewController *newVCInstance = nil;
- __weak UIViewController *weakVCReference = nil;
- @autoreleasepool {
- newVCInstance = [[UIViewController alloc] init];
- [newVCInstance view]; // Loads the view so that a screen trace is created for it.
- [self.tracker viewControllerDidAppear:newVCInstance];
- [self.tracker viewControllerDidDisappear:newVCInstance];
- weakVCReference = newVCInstance;
- newVCInstance = nil;
- }
- XCTAssertNil(weakVCReference);
- }
- /** Tests that viewControllerDidDisappear stops a trace. */
- - (void)testViewControllerDidDisappearStopsATrace {
- // First screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- NSString *expectedTraceName =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertNotNil(createdTrace);
- XCTAssertEqualObjects(expectedTraceName, createdTrace.name);
- // First screen disappears.
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertTrue(createdTrace.isCompleteAndValid);
- }
- /** Tests that viewControllerDidAppear starts multiple traces if multiple view controllers with the
- * same class appear one after the other.
- */
- - (void)testViewControllerDidAppearStartsMultipleScreenTracesForSameClassIfNeeded {
- // First screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- // Second screen appears, first screen is still visible.
- UIViewController *testViewController2 = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController2];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- NSString *expectedTraceNameOne =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
- FIRTrace *traceForScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertEqualObjects(traceForScreenOne.name, expectedTraceNameOne);
- NSString *expectedTraceNameTwo =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController2];
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
- FIRTrace *traceForScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
- XCTAssertEqualObjects(traceForScreenTwo.name, expectedTraceNameTwo);
- // Test that they're different instances.
- XCTAssertNotEqual(traceForScreenOne, traceForScreenTwo);
- XCTAssertEqualObjects(traceForScreenOne.name, traceForScreenTwo.name);
- }
- /** Tests that viewControllerDidAppear starts multiple traces if multiple view controllers with
- * different classes appear one after the other.
- */
- - (void)testViewControllerDidAppearStartsMultipleScreenTracesForDifferentClassIfNeeded {
- // First screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- // Second screen appears, first screen is still visible.
- UIViewController *testViewController2 = FPRCustomViewController(@"FPRTestViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController2];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- NSString *expectedTraceNameOne =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
- FIRTrace *traceForScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertEqualObjects(traceForScreenOne.name, expectedTraceNameOne);
- NSString *expectedTraceNameTwo =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController2];
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
- FIRTrace *traceForScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
- XCTAssertEqualObjects(traceForScreenTwo.name, expectedTraceNameTwo);
- XCTAssertNotEqual(traceForScreenOne,
- traceForScreenTwo); // Test that they're different instances.
- XCTAssertNotEqualObjects(traceForScreenOne.name, traceForScreenTwo.name);
- }
- /** Tests that viewControllerDidDisappear stops the correct trace when multiple traces are present.
- */
- - (void)testViewControllerDidDisappearStopsCorrectTraceWhenMultiplePresent {
- // First screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- // Second screen appears, first screen is still visible.
- UIViewController *testViewController2 = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController2];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 2);
- FIRTrace *traceForScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
- FIRTrace *traceForScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
- XCTAssertFalse(traceForScreenOne.isCompleteAndValid);
- XCTAssertFalse(traceForScreenTwo.isCompleteAndValid);
- [self.tracker viewControllerDidDisappear:testViewController2];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertFalse(traceForScreenOne.isCompleteAndValid);
- XCTAssertTrue(traceForScreenTwo.isCompleteAndValid);
- }
- /** Tests that viewControllerDidAppear doesn't start a duplicate trace. */
- - (void)testViewControllerDidAppearIgnoresDuplicateEvent {
- // First screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- NSString *expectedTraceName =
- [FPRScreenTraceTrackerTest expectedTraceNameForViewController:testViewController];
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertNotNil(createdTrace);
- XCTAssertEqualObjects(createdTrace.name, expectedTraceName);
- // Send the same event again.
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
- FIRTrace *activeTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertEqual(createdTrace, activeTrace); // Test that it is the same trace.
- }
- /** Tests that viewControllerDidAppear gracefully handles a nil viewController. */
- - (void)testViewControllerDidAppearGracefullyHandlesNilViewController {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wnonnull"
- [self.tracker viewControllerDidAppear:nil];
- #pragma clang diagnostic pop
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
- }
- /** Tests that viewControllerDidDisappear for a viewController that did not appear does nothing. */
- - (void)testViewControllerDidDisappearIgnoresViewControllerThatWasntScreenTraced {
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- UIViewController *testViewController2 = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
- [self.tracker viewControllerDidDisappear:testViewController2];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wnonnull"
- [self.tracker viewControllerDidDisappear:nil];
- #pragma clang diagnostic pop
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 1);
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
- }
- /** Tests that UIViewControllers are weakly retained in the map table that holds the mapping between
- * them.
- */
- - (void)testViewControllerIsWeaklyRetained {
- @autoreleasepool {
- UIViewController *testViewController = [[UIViewController alloc] init];
- id mockTrace = OCMClassMock([FIRTrace class]);
- [self.tracker.activeScreenTraces setObject:mockTrace forKey:testViewController];
- testViewController = nil;
- }
- XCTAssertEqual([self.tracker.activeScreenTraces dictionaryRepresentation].count, 0);
- }
- /** Tests that a FIRTrace is strongly retained in the map table that holds the mapping between a
- * view controller and its screen trace.
- */
- - (void)testFIRTraceIsStronglyRetained {
- UIViewController *testViewController = [[UIViewController alloc] init];
- NSString *traceName = @"screenTrace";
- FIRTrace *trace = [[FIRTrace alloc] initInternalTraceWithName:traceName];
- [self.tracker.activeScreenTraces setObject:trace forKey:testViewController];
- trace = nil;
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
- FIRTrace *returnedTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- XCTAssertEqualObjects(returnedTrace.name, traceName);
- }
- /** Tests that a screen trace that doesn't collect any data isn't sent. */
- - (void)testTraceWithNoCountersIsNotSent {
- id mockTrace = OCMClassMock([FIRTrace class]);
- UIViewController *testViewController = [[UIViewController alloc] init];
- [self.tracker.activeScreenTraces setObject:mockTrace forKey:testViewController];
- OCMExpect([mockTrace cancel]);
- [[mockTrace reject] stop];
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- OCMVerifyAll(mockTrace);
- }
- /** Test that all active traces are stopped when the app resigns active status. */
- - (void)testWillAppResignActiveStopsAllActiveTraces {
- // First screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- // Second screen appears.
- UIViewController *testViewController2 = FPRCustomViewController(@"FPRTestViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController2];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- FIRTrace *traceScreenOne = [self.tracker.activeScreenTraces objectForKey:testViewController];
- FIRTrace *traceScreenTwo = [self.tracker.activeScreenTraces objectForKey:testViewController2];
- XCTAssertNotNil(traceScreenOne);
- XCTAssertNotNil(traceScreenTwo);
- // App is backgrounded.
- NSNotification *appWillResignActiveNSNotification =
- [NSNotification notificationWithName:UIApplicationWillResignActiveNotification object:nil];
- [self.tracker appWillResignActiveNotification:appWillResignActiveNSNotification];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertTrue(traceScreenOne.isCompleteAndValid);
- XCTAssertTrue(traceScreenTwo.isCompleteAndValid);
- }
- /** Test that viewController refs are weakly saved for future use when the app resigns active
- * status.
- */
- - (void)disabled_testWillAppResignActiveWeaklySavesAllVisibleViewControllers {
- // Screen appears.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- // App is backgrounded.
- NSNotification *appWillResignActiveNSNotification =
- [NSNotification notificationWithName:UIApplicationWillResignActiveNotification object:nil];
- [self.tracker appWillResignActiveNotification:appWillResignActiveNSNotification];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.previouslyVisibleViewControllers.count, 1);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
- __weak id weakTestViewController = testViewController;
- testViewController = nil;
- // The blocks retain the view controllers and it sometimes takes some time to release them.
- // This is in place to prevent test flakiness. Autoreleasepools do not work in this case.
- while (weakTestViewController) {
- continue;
- }
- XCTAssertNil([self.tracker.previouslyVisibleViewControllers pointerAtIndex:0]);
- }
- /** Tests that new traces are started with the screens that are currently visible after the app
- * regains active status.
- */
- - (void)testAppDidBecomeActiveWillRestoreTracesOfVisibleScreens {
- // Simulate state where two screen traces were previously active.
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- UIViewController *testViewController2 = FPRCustomViewController(@"FPRTestViewController", YES);
- self.tracker.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
- [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController];
- [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController2];
- // App becomes active.
- NSNotification *appDidBecomeActiveNSNotification =
- [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification object:nil];
- [self.tracker appDidBecomeActiveNotification:appDidBecomeActiveNSNotification];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 2);
- XCTAssertNil(self.tracker.previouslyVisibleViewControllers);
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
- }
- /** Tests that if one of the previously visible ViewControllers is deallocated, a new trace isn't
- * started for it, and the app doesn't crash. */
- - (void)testAppDidBecomeActiveWillNotRestoreTracesOfNilledViewControllers {
- // Simulate state where two screen traces were previously active.
- UIViewController *testViewController = [[UIViewController alloc] init];
- [testViewController view]; // Loads the view so that a screen trace is created for it.
- UIViewController *testViewController2 = [[UIViewController alloc] init];
- [testViewController2 view]; // Loads the view so that a screen trace is created for it.
- self.tracker.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
- [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController];
- [self.tracker.previouslyVisibleViewControllers addPointer:(__bridge void *)testViewController2];
- // UIKit deallocates one of the ViewControllers that was previously visible.
- testViewController2 = nil;
- // App becomes active.
- NSNotification *appDidBecomeActiveNSNotification =
- [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification object:nil];
- [self.tracker appDidBecomeActiveNotification:appDidBecomeActiveNSNotification];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertNil(self.tracker.previouslyVisibleViewControllers);
- XCTAssertNotNil([self.tracker.activeScreenTraces objectForKey:testViewController]);
- XCTAssertNil([self.tracker.activeScreenTraces objectForKey:testViewController2]);
- }
- /** Tests that if consecutive frames take more time to render than the slow frames threshold, the
- * slow frame counter of the screen trace tracker is incremented.
- */
- - (void)testSlowFrameIsRecorded {
- CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
- CFAbsoluteTime secondFrameRenderTimestamp =
- firstFrameRenderTimestamp + kFPRSlowFrameThreshold + 0.005; // Buffer for float comparison.
- id displayLinkMock = OCMClassMock([CADisplayLink class]);
- [self.tracker.displayLink invalidate];
- self.tracker.displayLink = displayLinkMock;
- // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
- OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
- OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t newSlowFramesCount = self.tracker.slowFramesCount;
- XCTAssertEqual(newSlowFramesCount, initialSlowFramesCount + 1);
- }
- /** Tests that the slow and frozen frame counter is not incremented in the case of a good frame. */
- - (void)testSlowAndFrozenFrameIsNotRecordedInCaseOfGoodFrame {
- CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
- CFAbsoluteTime secondFrameRenderTimestamp =
- firstFrameRenderTimestamp + kFPRSlowFrameThreshold - 0.005; // Good frame.
- id displayLinkMock = OCMClassMock([CADisplayLink class]);
- [self.tracker.displayLink invalidate];
- self.tracker.displayLink = displayLinkMock;
- // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
- OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
- int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
- OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t newSlowFramesCount = self.tracker.slowFramesCount;
- int64_t newFrozenFramesCount = self.tracker.frozenFramesCount;
- XCTAssertEqual(newSlowFramesCount, initialSlowFramesCount);
- XCTAssertEqual(newFrozenFramesCount, initialFrozenFramesCount);
- }
- /* Tests that the frozen frame counter is not incremented in case of a slow frame. */
- - (void)testFrozenFrameIsNotRecordedInCaseOfSlowFrame {
- CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
- CFAbsoluteTime secondFrameRenderTimestamp =
- firstFrameRenderTimestamp + kFPRSlowFrameThreshold + 0.005; // Slow frame.
- id displayLinkMock = OCMClassMock([CADisplayLink class]);
- [self.tracker.displayLink invalidate];
- self.tracker.displayLink = displayLinkMock;
- // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
- OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
- OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t newFrozenFramesCount = self.tracker.frozenFramesCount;
- XCTAssertEqual(newFrozenFramesCount, initialFrozenFramesCount);
- }
- /** Tests that the total frames counter is incremented in the case of good, slow and frozen
- * frames.
- */
- - (void)testTotalFramesAreAlwaysRecorded {
- CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
- CFAbsoluteTime secondFrameRenderTimestamp =
- firstFrameRenderTimestamp + kFPRSlowFrameThreshold - 0.005; // Good frame.
- CFAbsoluteTime thirdFrameRenderTimestamp =
- secondFrameRenderTimestamp + kFPRSlowFrameThreshold + 0.005; // Slow frame.
- CFAbsoluteTime fourthFrameRenderTimestamp =
- thirdFrameRenderTimestamp + kFPRFrozenFrameThreshold + 0.005; // Frozen frame.
- id displayLinkMock = OCMClassMock([CADisplayLink class]);
- [self.tracker.displayLink invalidate];
- self.tracker.displayLink = displayLinkMock;
- // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
- OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
- OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t newTotalFramesCount = self.tracker.totalFramesCount;
- XCTAssertEqual(newTotalFramesCount, initialTotalFramesCount + 1);
- OCMExpect([displayLinkMock timestamp]).andReturn(thirdFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- newTotalFramesCount = self.tracker.totalFramesCount;
- XCTAssertEqual(newTotalFramesCount, initialTotalFramesCount + 2);
- OCMExpect([displayLinkMock timestamp]).andReturn(fourthFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- newTotalFramesCount = self.tracker.totalFramesCount;
- XCTAssertEqual(newTotalFramesCount, initialTotalFramesCount + 3);
- }
- /** Tests that if consecutive frames take more time to render than the frozen frames threshold, the
- * frozen frame counter and slow frame counter of the screen trace tracker is incremented.
- */
- - (void)testFrozenFrameAndSlowFrameIsRecorded {
- CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
- CFAbsoluteTime secondFrameRenderTimestamp =
- firstFrameRenderTimestamp + kFPRFrozenFrameThreshold + 0.005; // Buffer for float comparison.
- id displayLinkMock = OCMClassMock([CADisplayLink class]);
- [self.tracker.displayLink invalidate];
- self.tracker.displayLink = displayLinkMock;
- // Set/Reset the previousFrameTimestamp if it has been set by a previous test.
- OCMExpect([displayLinkMock timestamp]).andReturn(firstFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
- int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
- OCMExpect([displayLinkMock timestamp]).andReturn(secondFrameRenderTimestamp);
- [self.tracker displayLinkStep];
- int64_t newSlowFramesCount = self.tracker.slowFramesCount;
- int64_t newFrozenFramesCount = self.tracker.frozenFramesCount;
- XCTAssertEqual(newFrozenFramesCount, initialFrozenFramesCount + 1);
- XCTAssertEqual(newSlowFramesCount, initialSlowFramesCount + 1);
- }
- /** Tests that the correct number of slow, frozen and total frames are recorded when all 3 are
- * present.
- */
- - (void)testTraceHasCorrectFrozenSlowAndTotalFrameMetricsWhenThoseFramesAreRecorded {
- int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
- int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
- int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
- int64_t expectedTotalFramesOnTrace = 5;
- int64_t expectedSlowFramesOnTrace = 3;
- int64_t expectedFrozenFramesOnTrace = 1;
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- self.tracker.totalFramesCount = initialTotalFramesCount + expectedTotalFramesOnTrace;
- self.tracker.slowFramesCount = initialSlowFramesCount + expectedSlowFramesOnTrace;
- self.tracker.frozenFramesCount = initialFrozenFramesCount + expectedFrozenFramesOnTrace;
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual([createdTrace valueForIntMetric:kFPRSlowFrameCounterName],
- expectedSlowFramesOnTrace);
- XCTAssertEqual([createdTrace valueForIntMetric:kFPRFrozenFrameCounterName],
- expectedFrozenFramesOnTrace);
- XCTAssertEqual([createdTrace valueForIntMetric:kFPRTotalFramesCounterName],
- expectedTotalFramesOnTrace);
- }
- /** Tests that if just total and slow frame and no frozen frames are recorded, then the frozen
- * frames metric is not present on the trace. */
- - (void)testTraceHasJustSlowAndTotalFrameMetricsWhenNoFrozenFramesAreRecorded {
- int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
- int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
- int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
- int64_t expectedTotalFramesOnTrace = 5;
- int64_t expectedSlowFramesOnTrace = 3;
- int64_t expectedFrozenFramesOnTrace = 0;
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- self.tracker.totalFramesCount = initialTotalFramesCount + expectedTotalFramesOnTrace;
- self.tracker.slowFramesCount = initialSlowFramesCount + expectedSlowFramesOnTrace;
- self.tracker.frozenFramesCount = initialFrozenFramesCount + expectedFrozenFramesOnTrace;
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual([createdTrace valueForIntMetric:kFPRSlowFrameCounterName],
- expectedSlowFramesOnTrace);
- XCTAssertEqual([createdTrace valueForIntMetric:kFPRTotalFramesCounterName],
- expectedTotalFramesOnTrace);
- XCTAssertNil(createdTrace.counters[kFPRFrozenFrameCounterName]);
- XCTAssertEqual(createdTrace.counters.count, 2);
- }
- /** Tests that when no frozen or slow frames are recorded, the trace only has the total frames
- * counter.
- */
- - (void)testTraceHasJustTotalFrameMetricsWhenNoFrozenOrSlowFramesAreRecorded {
- int64_t initialTotalFramesCount = self.tracker.totalFramesCount;
- int64_t initialFrozenFramesCount = self.tracker.frozenFramesCount;
- int64_t initialSlowFramesCount = self.tracker.slowFramesCount;
- int64_t expectedTotalFramesOnTrace = 5;
- int64_t expectedSlowFramesOnTrace = 0;
- int64_t expectedFrozenFramesOnTrace = 0;
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- self.tracker.totalFramesCount = initialTotalFramesCount + expectedTotalFramesOnTrace;
- self.tracker.slowFramesCount = initialSlowFramesCount + expectedSlowFramesOnTrace;
- self.tracker.frozenFramesCount = initialFrozenFramesCount + expectedFrozenFramesOnTrace;
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual([createdTrace valueForIntMetric:kFPRTotalFramesCounterName],
- expectedTotalFramesOnTrace);
- XCTAssertNil(createdTrace.counters[kFPRSlowFrameCounterName]);
- XCTAssertNil(createdTrace.counters[kFPRFrozenFrameCounterName]);
- XCTAssertEqual(createdTrace.counters.count, 1);
- }
- /** Tests that if no frames are recorded between a trace being started and stopped, it doesn't have
- * any metrics associated with it.
- */
- - (void)testTraceHasNoMetricsWhenNoFramesAreRecorded {
- UIViewController *testViewController = FPRCustomViewController(@"UIViewController", YES);
- [self.tracker viewControllerDidAppear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- FIRTrace *createdTrace = [self.tracker.activeScreenTraces objectForKey:testViewController];
- [self.tracker viewControllerDidDisappear:testViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(createdTrace.counters.count, 0);
- }
- /** Tests that screen traces are NOT created for container view controllers. */
- - (void)testScreenTracesAreNotCreatedForContainerViewControllers {
- UINavigationController *testNavigationController =
- (UINavigationController *)FPRCustomViewController(@"UINavigationController", YES);
- UITabBarController *testTabBarController =
- (UITabBarController *)FPRCustomViewController(@"UITabBarController", YES);
- UISplitViewController *testSplitViewController =
- (UISplitViewController *)FPRCustomViewController(@"UISplitViewController", YES);
- UIPageViewController *testPageViewController =
- (UIPageViewController *)FPRCustomViewController(@"UIPageViewController", YES);
- [self.tracker viewControllerDidAppear:testNavigationController];
- [self.tracker viewControllerDidAppear:testTabBarController];
- [self.tracker viewControllerDidAppear:testSplitViewController];
- [self.tracker viewControllerDidAppear:testPageViewController];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 0);
- }
- /** Tests that screen traces are created for canonical container view controller subclasses. */
- - (void)testScreenTracesAreCreatedForContainerViewControllerSubclasses {
- FPRTestNavigationViewController *testNavigationControllerSubclass =
- (FPRTestNavigationViewController *)FPRCustomViewController(@"FPRTestNavigationViewController",
- YES);
- FPRTestTabBarController *testTabBarControllerSubclass =
- (FPRTestTabBarController *)FPRCustomViewController(@"FPRTestTabBarController", YES);
- FPRTestSplitViewController *testSplitViewControllerSubclass =
- (FPRTestSplitViewController *)FPRCustomViewController(@"FPRTestSplitViewController", YES);
- FPRTestPageViewController *testPageViewControllerSubclass =
- (FPRTestPageViewController *)FPRCustomViewController(@"FPRTestPageViewController", YES);
- [self.tracker viewControllerDidAppear:testNavigationControllerSubclass];
- [self.tracker viewControllerDidAppear:testTabBarControllerSubclass];
- [self.tracker viewControllerDidAppear:testSplitViewControllerSubclass];
- [self.tracker viewControllerDidAppear:testPageViewControllerSubclass];
- dispatch_group_wait(self.dispatchGroupToWaitOn, DISPATCH_TIME_FOREVER);
- XCTAssertEqual(self.tracker.activeScreenTraces.count, 4);
- }
- #pragma mark - Helper methods
- + (NSString *)expectedTraceNameForViewController:(UIViewController *)viewController {
- return [@"_st_" stringByAppendingString:NSStringFromClass([viewController class])];
- }
- @end
|