Fix a AppLifecycleTest present time race between the animation and the rest of the test (flutter/engine#21107)

This commit is contained in:
xster 2020-09-14 08:50:16 -07:00 committed by GitHub
parent f223bcae3a
commit e1cfc1ac6b
3 changed files with 99 additions and 87 deletions

View File

@ -6,8 +6,8 @@
@interface ScreenBeforeFlutter : UIViewController
- (id)initWithEngineRunCompletion:(void (^)(void))engineRunCompletion;
- (FlutterViewController*)showFlutter;
- (id)initWithEngineRunCompletion:(dispatch_block_t)engineRunCompletion;
- (FlutterViewController*)showFlutter:(dispatch_block_t)showCompletion;
@property(nonatomic, readonly) FlutterEngine* engine;

View File

@ -9,7 +9,7 @@
@synthesize engine = _engine;
- (id)initWithEngineRunCompletion:(void (^)(void))engineRunCompletion {
- (id)initWithEngineRunCompletion:(dispatch_block_t)engineRunCompletion {
self = [super init];
_engine = [[FlutterEngine alloc] initWithScenario:@"poppable_screen"
withCompletion:engineRunCompletion];
@ -27,7 +27,7 @@
showFlutterButton.tintColor = UIColor.whiteColor;
showFlutterButton.clipsToBounds = YES;
[showFlutterButton addTarget:self
action:@selector(showFlutter)
action:@selector(showFlutter:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:showFlutterButton];
@ -39,11 +39,11 @@
[_engine runWithEntrypoint:nil];
}
- (FlutterViewController*)showFlutter {
- (FlutterViewController*)showFlutter:(dispatch_block_t)showCompletion {
FlutterViewController* flutterVC = [[FlutterViewController alloc] initWithEngine:_engine
nibName:nil
bundle:nil];
[self presentViewController:flutterVC animated:NO completion:nil];
[self presentViewController:flutterVC animated:NO completion:showCompletion];
return flutterVC;
}

View File

@ -41,17 +41,34 @@ FLUTTER_ASSERT_ARC
self.continueAfterFailure = NO;
}
// TODO(cbracken): re-enable this test by removing the skip_ prefix once the source of its flakiness
// has been identified. https://github.com/flutter/flutter/issues/61620
- (void)skip_testDismissedFlutterViewControllerNotRespondingToApplicationLifecycle {
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
- (NSArray*)initialPresentLifecycles {
NSMutableArray* expectations =
[NSMutableArray arrayWithObject:[[XCAppLifecycleTestExpectation alloc]
initForLifecycle:@"AppLifecycleState.inactive"
forStep:@"showing a FlutterViewController"]];
// If the test is the very first test to run in the test target, the UIApplication may
// be in inactive state. Haven't found a good way to force it to go to active state.
// So just account for it in the initial lifecycle events with an extra resumed since
// the FlutterViewController tracks all view controller and application lifecycle events.
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
[expectations addObject:[[XCAppLifecycleTestExpectation alloc]
initForLifecycle:@"AppLifecycleState.resumed"
forStep:@"showing a FlutterViewController"]];
}
[expectations addObject:[[XCAppLifecycleTestExpectation alloc]
initForLifecycle:@"AppLifecycleState.resumed"
forStep:@"showing a FlutterViewController"]];
return expectations;
}
- (void)testDismissedFlutterViewControllerNotRespondingToApplicationLifecycle {
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
// Let the engine finish booting (at the end of which the channels are properly set-up) before
// moving onto the next step of showing the next view controller.
ScreenBeforeFlutter* rootVC = [[ScreenBeforeFlutter alloc] initWithEngineRunCompletion:^void() {
[engineStartedExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:nil];
UIApplication* application = UIApplication.sharedApplication;
@ -61,36 +78,34 @@ FLUTTER_ASSERT_ARC
NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];
// Expected sequence from showing the FlutterViewController is inactive and resumed.
[lifecycleExpectations addObjectsFromArray:@[
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
forStep:@"showing a FlutterViewController"],
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
forStep:@"showing a FlutterViewController"]
]];
[lifecycleExpectations addObjectsFromArray:[self initialPresentLifecycles]];
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@", [nextExpectation expectedLifecycle],
message);
return;
}
[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
}];
FlutterViewController* flutterVC;
@autoreleasepool {
// Holding onto this FlutterViewController is consequential here. Since a released
// FlutterViewController wouldn't keep listening to the application lifecycle events and produce
// false positives for the application lifecycle tests further below.
flutterVC = [rootVC showFlutter];
NSLog(@"FlutterViewController instance %@ created", flutterVC);
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@",
[nextExpectation expectedLifecycle], message);
return;
}
[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
flutterVC = [rootVC showFlutter:^{
[vcShown fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
// The expectations list isn't dequeued by the message handler yet.
[self waitForExpectations:lifecycleExpectations timeout:5 enforceOrder:YES];
@ -150,7 +165,11 @@ FLUTTER_ASSERT_ARC
]];
@autoreleasepool {
flutterVC = [rootVC showFlutter];
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
flutterVC = [rootVC showFlutter:^{
[vcShown fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
NSLog(@"FlutterViewController instance %@ created", flutterVC);
[self waitForExpectations:lifecycleExpectations timeout:5 enforceOrder:YES];
@ -178,9 +197,7 @@ FLUTTER_ASSERT_ARC
[engine destroyContext];
}
// TODO(cbracken): re-enable this test by removing the skip_ prefix once the source of its flakiness
// has been identified. https://github.com/flutter/flutter/issues/61620
- (void)skip_testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
- (void)testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
// Let the engine finish booting (at the end of which the channels are properly set-up) before
@ -198,33 +215,31 @@ FLUTTER_ASSERT_ARC
NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];
// Expected sequence from showing the FlutterViewController is inactive and resumed.
[lifecycleExpectations addObjectsFromArray:@[
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
forStep:@"showing a FlutterViewController"],
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
forStep:@"showing a FlutterViewController"]
]];
[lifecycleExpectations addObjectsFromArray:[self initialPresentLifecycles]];
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@", [nextExpectation expectedLifecycle],
message);
return;
}
[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
}];
FlutterViewController* flutterVC;
@autoreleasepool {
flutterVC = [rootVC showFlutter];
NSLog(@"FlutterViewController instance %@ created", flutterVC);
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@",
[nextExpectation expectedLifecycle], message);
return;
}
[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
flutterVC = [rootVC showFlutter:^{
[vcShown fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[self waitForExpectations:lifecycleExpectations timeout:5];
// Now put the FlutterViewController into background.
@ -285,9 +300,7 @@ FLUTTER_ASSERT_ARC
[engine destroyContext];
}
// TODO(cbracken): re-enable this test by removing the skip_ prefix once the source of its flakiness
// has been identified. https://github.com/flutter/flutter/issues/61620
- (void)skip_testFlutterViewControllerDetachingSendsApplicationLifecycle {
- (void)testFlutterViewControllerDetachingSendsApplicationLifecycle {
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
// Let the engine finish booting (at the end of which the channels are properly set-up) before
@ -305,34 +318,33 @@ FLUTTER_ASSERT_ARC
NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];
// Expected sequence from showing the FlutterViewController is inactive and resumed.
[lifecycleExpectations addObjectsFromArray:@[
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
forStep:@"showing a FlutterViewController"],
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
forStep:@"showing a FlutterViewController"]
]];
[lifecycleExpectations addObjectsFromArray:[self initialPresentLifecycles]];
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@", [nextExpectation expectedLifecycle],
message);
return;
}
[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
}];
// At the end of Flutter VC, we want to make sure it deallocs and sends detached signal.
// Using autoreleasepool will guarantee that.
FlutterViewController* flutterVC;
@autoreleasepool {
flutterVC = [rootVC showFlutter];
NSLog(@"FlutterViewController instance %@ created", flutterVC);
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
if (lifecycleExpectations.count == 0) {
XCTFail(@"Unexpected lifecycle transition: %@", message);
return;
}
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
XCTFail(@"Expected lifecycle %@ but instead received %@",
[nextExpectation expectedLifecycle], message);
return;
}
[nextExpectation fulfill];
[lifecycleExpectations removeObjectAtIndex:0];
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
flutterVC = [rootVC showFlutter:^{
[vcShown fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
[self waitForExpectations:lifecycleExpectations timeout:5];
// Starts dealloc flutter VC.