From 8d74fd4a62e6403e88a8a54b3cac1932edc7964c Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Wed, 24 Mar 2021 16:24:24 -0700 Subject: [PATCH 1/3] Started waiting for the notifications locally before asserting they were called for plugin lifecycle tests. --- .../Source/FlutterEnginePlatformViewTest.mm | 6 +++++ .../FlutterPluginAppLifeCycleDelegateTest.m | 25 ++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 53d8688077068..3f6b269cc738f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -87,15 +87,21 @@ - (void)testCallsNotifyLowMemory { OCMVerify([mockEngine notifyLowMemory]); OCMReject([mockEngine notifyLowMemory]); + XCTNSNotificationExpectation* memoryExpectation = [[XCTNSNotificationExpectation alloc] + initWithName:UIApplicationDidReceiveMemoryWarningNotification]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + [self waitForExpectations:@[ memoryExpectation ] timeout:5.0]; OCMVerify([mockEngine notifyLowMemory]); OCMReject([mockEngine notifyLowMemory]); + XCTNSNotificationExpectation* backgroundExpectation = [[XCTNSNotificationExpectation alloc] + initWithName:UIApplicationDidEnterBackgroundNotification]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + [self waitForExpectations:@[ backgroundExpectation ] timeout:5.0]; OCMVerify([mockEngine notifyLowMemory]); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m index 21501c7421f3f..9891f52cf8c0c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m @@ -11,7 +11,6 @@ FLUTTER_ASSERT_ARC @interface FlutterPluginAppLifeCycleDelegateTest : XCTestCase - @end @implementation FlutterPluginAppLifeCycleDelegateTest @@ -22,51 +21,71 @@ - (void)testCreate { } - (void)testDidEnterBackground { + XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc] + initWithName:UIApplicationDidEnterBackgroundNotification]; FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); [delegate addDelegate:plugin]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil]; + + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerify([plugin applicationDidEnterBackground:[UIApplication sharedApplication]]); } - (void)testWillEnterForeground { + XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc] + initWithName:UIApplicationWillEnterForegroundNotification]; + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); [delegate addDelegate:plugin]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerify([plugin applicationWillEnterForeground:[UIApplication sharedApplication]]); } -- (void)skip_testWillResignActive { +- (void)testWillResignActive { + XCTNSNotificationExpectation* expectation = + [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationWillResignActiveNotification]; + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); [delegate addDelegate:plugin]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillResignActiveNotification object:nil]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerify([plugin applicationWillResignActive:[UIApplication sharedApplication]]); } -- (void)skip_testDidBecomeActive { +- (void)testDidBecomeActive { + XCTNSNotificationExpectation* expectation = + [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationDidBecomeActiveNotification]; + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); [delegate addDelegate:plugin]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerify([plugin applicationDidBecomeActive:[UIApplication sharedApplication]]); } - (void)testWillTerminate { + XCTNSNotificationExpectation* expectation = + [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationWillTerminateNotification]; + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); [delegate addDelegate:plugin]; [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillTerminateNotification object:nil]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerify([plugin applicationWillTerminate:[UIApplication sharedApplication]]); } From 8eb4d2e8f5eb5da68d0600830ab3a87d53813e00 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Thu, 25 Mar 2021 10:13:20 -0700 Subject: [PATCH 2/3] added stop mocking calls for engines --- .../ios/framework/Source/FlutterEnginePlatformViewTest.mm | 1 + .../darwin/ios/framework/Source/FlutterTextInputPluginTest.m | 3 +-- .../darwin/ios/framework/Source/accessibility_bridge_test.mm | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 3f6b269cc738f..2af636a71c13e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -104,6 +104,7 @@ - (void)testCallsNotifyLowMemory { [self waitForExpectations:@[ backgroundExpectation ] timeout:5.0]; OCMVerify([mockEngine notifyLowMemory]); + [mockEngine stopMocking]; } @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 4e7a3b610819c..06f53c39ff7cb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -885,9 +885,8 @@ - (void)testFlutterTokenizerCanParseLines { - (void)testFlutterTextInputPluginRetainsFlutterTextInputView { FlutterTextInputPlugin* myInputPlugin; - id myEngine = OCMClassMock([FlutterEngine class]); myInputPlugin = [[FlutterTextInputPlugin alloc] init]; - myInputPlugin.textInputDelegate = myEngine; + myInputPlugin.textInputDelegate = engine; __weak UIView* activeView; @autoreleasepool { FlutterMethodCall* setClientCall = [FlutterMethodCall diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 41f8072afd858..260a7eb19a115 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -970,5 +970,6 @@ - (void)testAccessibilityMessageAfterDeletion { }); latch.Wait(); OCMVerify([messenger cleanupConnection:connection]); + [engine stopMocking]; } @end From 5f3bab1ce7bdb8a304579379f3493eb17a4c8179 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Thu, 25 Mar 2021 17:23:43 -0700 Subject: [PATCH 3/3] removed ocmock to remove retain cycle --- .../Source/FlutterViewControllerTest.mm | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index a70387182e185..83c62f0518a5c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -16,6 +16,22 @@ class PointerDataPacket {}; } +/// Sometimes we have to use a custom mock to avoid retain cycles in ocmock. +@interface FlutterEnginePartialMock : FlutterEngine +@property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel; +@property(nonatomic, weak) FlutterViewController* viewController; +@property(nonatomic, assign) BOOL didCallNotifyLowMemory; +@end + +@implementation FlutterEnginePartialMock +@synthesize viewController; +@synthesize lifecycleChannel; + +- (void)notifyLowMemory { + _didCallNotifyLowMemory = YES; +} +@end + @interface FlutterEngine () - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI @@ -87,14 +103,15 @@ - (void)tearDown { - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController { id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]); - OCMStub([self.mockEngine lifecycleChannel]).andReturn(lifecycleChannel); + FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init]; + mockEngine.lifecycleChannel = lifecycleChannel; FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; id viewControllerMock = OCMPartialMock(viewControllerA); OCMStub([viewControllerMock surfaceUpdated:NO]); - OCMStub([self.mockEngine viewController]).andReturn(viewControllerB); + mockEngine.viewController = viewControllerB; [viewControllerA viewDidDisappear:NO]; OCMReject([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]); OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]); @@ -102,15 +119,21 @@ - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController { - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController { id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]); - OCMStub([self.mockEngine lifecycleChannel]).andReturn(lifecycleChannel); - FlutterViewController* viewController = - [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; - id viewControllerMock = OCMPartialMock(viewController); - OCMStub([viewControllerMock surfaceUpdated:NO]); - OCMStub([self.mockEngine viewController]).andReturn(viewController); - [viewController viewDidDisappear:NO]; - OCMVerify([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]); - OCMVerify([viewControllerMock surfaceUpdated:NO]); + FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init]; + mockEngine.lifecycleChannel = lifecycleChannel; + __weak FlutterViewController* weakViewController; + @autoreleasepool { + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + weakViewController = viewController; + id viewControllerMock = OCMPartialMock(viewController); + OCMStub([viewControllerMock surfaceUpdated:NO]); + [viewController viewDidDisappear:NO]; + OCMVerify([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]); + OCMVerify([viewControllerMock surfaceUpdated:NO]); + } + XCTAssertNil(weakViewController); } - (void)testBinaryMessenger { @@ -528,15 +551,16 @@ - (void)testHideOverlay { } - (void)testNotifyLowMemory { - FlutterViewController* viewController = - [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; - OCMStub([self.mockEngine viewController]).andReturn(viewController); + FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; id viewControllerMock = OCMPartialMock(viewController); OCMStub([viewControllerMock surfaceUpdated:NO]); - [viewController beginAppearanceTransition:NO animated:NO]; [viewController endAppearanceTransition]; - OCMVerify([self.mockEngine notifyLowMemory]); + XCTAssertTrue(mockEngine.didCallNotifyLowMemory); + [viewControllerMock stopMocking]; } - (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) {