Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 257eba9

Browse files
authored
Fix a AppLifecycleTest present time race between the animation and the rest of the test (#21107)
1 parent 5602b7b commit 257eba9

File tree

3 files changed

+99
-87
lines changed

3 files changed

+99
-87
lines changed

testing/scenario_app/ios/Scenarios/Scenarios/ScreenBeforeFlutter.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
@interface ScreenBeforeFlutter : UIViewController
88

9-
- (id)initWithEngineRunCompletion:(void (^)(void))engineRunCompletion;
10-
- (FlutterViewController*)showFlutter;
9+
- (id)initWithEngineRunCompletion:(dispatch_block_t)engineRunCompletion;
10+
- (FlutterViewController*)showFlutter:(dispatch_block_t)showCompletion;
1111

1212
@property(nonatomic, readonly) FlutterEngine* engine;
1313

testing/scenario_app/ios/Scenarios/Scenarios/ScreenBeforeFlutter.m

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ @implementation ScreenBeforeFlutter
99

1010
@synthesize engine = _engine;
1111

12-
- (id)initWithEngineRunCompletion:(void (^)(void))engineRunCompletion {
12+
- (id)initWithEngineRunCompletion:(dispatch_block_t)engineRunCompletion {
1313
self = [super init];
1414
_engine = [[FlutterEngine alloc] initWithScenario:@"poppable_screen"
1515
withCompletion:engineRunCompletion];
@@ -27,7 +27,7 @@ - (void)viewDidLoad {
2727
showFlutterButton.tintColor = UIColor.whiteColor;
2828
showFlutterButton.clipsToBounds = YES;
2929
[showFlutterButton addTarget:self
30-
action:@selector(showFlutter)
30+
action:@selector(showFlutter:)
3131
forControlEvents:UIControlEventTouchUpInside];
3232

3333
[self.view addSubview:showFlutterButton];
@@ -39,11 +39,11 @@ - (void)viewDidLoad {
3939
[_engine runWithEntrypoint:nil];
4040
}
4141

42-
- (FlutterViewController*)showFlutter {
42+
- (FlutterViewController*)showFlutter:(dispatch_block_t)showCompletion {
4343
FlutterViewController* flutterVC = [[FlutterViewController alloc] initWithEngine:_engine
4444
nibName:nil
4545
bundle:nil];
46-
[self presentViewController:flutterVC animated:NO completion:nil];
46+
[self presentViewController:flutterVC animated:NO completion:showCompletion];
4747
return flutterVC;
4848
}
4949

testing/scenario_app/ios/Scenarios/ScenariosTests/AppLifecycleTests.m

+93-81
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,34 @@ - (void)setUp {
4141
self.continueAfterFailure = NO;
4242
}
4343

44-
// TODO(cbracken): re-enable this test by removing the skip_ prefix once the source of its flakiness
45-
// has been identified. https://github.com/flutter/flutter/issues/61620
46-
- (void)skip_testDismissedFlutterViewControllerNotRespondingToApplicationLifecycle {
47-
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
44+
- (NSArray*)initialPresentLifecycles {
45+
NSMutableArray* expectations =
46+
[NSMutableArray arrayWithObject:[[XCAppLifecycleTestExpectation alloc]
47+
initForLifecycle:@"AppLifecycleState.inactive"
48+
forStep:@"showing a FlutterViewController"]];
49+
50+
// If the test is the very first test to run in the test target, the UIApplication may
51+
// be in inactive state. Haven't found a good way to force it to go to active state.
52+
// So just account for it in the initial lifecycle events with an extra resumed since
53+
// the FlutterViewController tracks all view controller and application lifecycle events.
54+
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
55+
[expectations addObject:[[XCAppLifecycleTestExpectation alloc]
56+
initForLifecycle:@"AppLifecycleState.resumed"
57+
forStep:@"showing a FlutterViewController"]];
58+
}
59+
[expectations addObject:[[XCAppLifecycleTestExpectation alloc]
60+
initForLifecycle:@"AppLifecycleState.resumed"
61+
forStep:@"showing a FlutterViewController"]];
62+
return expectations;
63+
}
4864

65+
- (void)testDismissedFlutterViewControllerNotRespondingToApplicationLifecycle {
66+
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
4967
// Let the engine finish booting (at the end of which the channels are properly set-up) before
5068
// moving onto the next step of showing the next view controller.
5169
ScreenBeforeFlutter* rootVC = [[ScreenBeforeFlutter alloc] initWithEngineRunCompletion:^void() {
5270
[engineStartedExpectation fulfill];
5371
}];
54-
5572
[self waitForExpectationsWithTimeout:5 handler:nil];
5673

5774
UIApplication* application = UIApplication.sharedApplication;
@@ -61,36 +78,34 @@ - (void)skip_testDismissedFlutterViewControllerNotRespondingToApplicationLifecyc
6178
NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];
6279

6380
// Expected sequence from showing the FlutterViewController is inactive and resumed.
64-
[lifecycleExpectations addObjectsFromArray:@[
65-
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
66-
forStep:@"showing a FlutterViewController"],
67-
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
68-
forStep:@"showing a FlutterViewController"]
69-
]];
81+
[lifecycleExpectations addObjectsFromArray:[self initialPresentLifecycles]];
82+
83+
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
84+
if (lifecycleExpectations.count == 0) {
85+
XCTFail(@"Unexpected lifecycle transition: %@", message);
86+
return;
87+
}
88+
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
89+
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
90+
XCTFail(@"Expected lifecycle %@ but instead received %@", [nextExpectation expectedLifecycle],
91+
message);
92+
return;
93+
}
94+
95+
[nextExpectation fulfill];
96+
[lifecycleExpectations removeObjectAtIndex:0];
97+
}];
7098

7199
FlutterViewController* flutterVC;
72100
@autoreleasepool {
73101
// Holding onto this FlutterViewController is consequential here. Since a released
74102
// FlutterViewController wouldn't keep listening to the application lifecycle events and produce
75103
// false positives for the application lifecycle tests further below.
76-
flutterVC = [rootVC showFlutter];
77-
NSLog(@"FlutterViewController instance %@ created", flutterVC);
78-
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
79-
if (lifecycleExpectations.count == 0) {
80-
XCTFail(@"Unexpected lifecycle transition: %@", message);
81-
return;
82-
}
83-
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
84-
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
85-
XCTFail(@"Expected lifecycle %@ but instead received %@",
86-
[nextExpectation expectedLifecycle], message);
87-
return;
88-
}
89-
90-
[nextExpectation fulfill];
91-
[lifecycleExpectations removeObjectAtIndex:0];
104+
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
105+
flutterVC = [rootVC showFlutter:^{
106+
[vcShown fulfill];
92107
}];
93-
108+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
94109
// The expectations list isn't dequeued by the message handler yet.
95110
[self waitForExpectations:lifecycleExpectations timeout:5 enforceOrder:YES];
96111

@@ -150,7 +165,11 @@ - (void)skip_testDismissedFlutterViewControllerNotRespondingToApplicationLifecyc
150165
]];
151166

152167
@autoreleasepool {
153-
flutterVC = [rootVC showFlutter];
168+
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
169+
flutterVC = [rootVC showFlutter:^{
170+
[vcShown fulfill];
171+
}];
172+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
154173
NSLog(@"FlutterViewController instance %@ created", flutterVC);
155174
[self waitForExpectations:lifecycleExpectations timeout:5 enforceOrder:YES];
156175

@@ -178,9 +197,7 @@ - (void)skip_testDismissedFlutterViewControllerNotRespondingToApplicationLifecyc
178197
[engine destroyContext];
179198
}
180199

181-
// TODO(cbracken): re-enable this test by removing the skip_ prefix once the source of its flakiness
182-
// has been identified. https://github.com/flutter/flutter/issues/61620
183-
- (void)skip_testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
200+
- (void)testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
184201
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
185202

186203
// Let the engine finish booting (at the end of which the channels are properly set-up) before
@@ -198,33 +215,31 @@ - (void)skip_testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
198215
NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];
199216

200217
// Expected sequence from showing the FlutterViewController is inactive and resumed.
201-
[lifecycleExpectations addObjectsFromArray:@[
202-
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
203-
forStep:@"showing a FlutterViewController"],
204-
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
205-
forStep:@"showing a FlutterViewController"]
206-
]];
218+
[lifecycleExpectations addObjectsFromArray:[self initialPresentLifecycles]];
219+
220+
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
221+
if (lifecycleExpectations.count == 0) {
222+
XCTFail(@"Unexpected lifecycle transition: %@", message);
223+
return;
224+
}
225+
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
226+
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
227+
XCTFail(@"Expected lifecycle %@ but instead received %@", [nextExpectation expectedLifecycle],
228+
message);
229+
return;
230+
}
231+
232+
[nextExpectation fulfill];
233+
[lifecycleExpectations removeObjectAtIndex:0];
234+
}];
207235

208236
FlutterViewController* flutterVC;
209237
@autoreleasepool {
210-
flutterVC = [rootVC showFlutter];
211-
NSLog(@"FlutterViewController instance %@ created", flutterVC);
212-
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
213-
if (lifecycleExpectations.count == 0) {
214-
XCTFail(@"Unexpected lifecycle transition: %@", message);
215-
return;
216-
}
217-
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
218-
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
219-
XCTFail(@"Expected lifecycle %@ but instead received %@",
220-
[nextExpectation expectedLifecycle], message);
221-
return;
222-
}
223-
224-
[nextExpectation fulfill];
225-
[lifecycleExpectations removeObjectAtIndex:0];
238+
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
239+
flutterVC = [rootVC showFlutter:^{
240+
[vcShown fulfill];
226241
}];
227-
242+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
228243
[self waitForExpectations:lifecycleExpectations timeout:5];
229244

230245
// Now put the FlutterViewController into background.
@@ -285,9 +300,7 @@ - (void)skip_testVisibleFlutterViewControllerRespondsToApplicationLifecycle {
285300
[engine destroyContext];
286301
}
287302

288-
// TODO(cbracken): re-enable this test by removing the skip_ prefix once the source of its flakiness
289-
// has been identified. https://github.com/flutter/flutter/issues/61620
290-
- (void)skip_testFlutterViewControllerDetachingSendsApplicationLifecycle {
303+
- (void)testFlutterViewControllerDetachingSendsApplicationLifecycle {
291304
XCTestExpectation* engineStartedExpectation = [self expectationWithDescription:@"Engine started"];
292305

293306
// Let the engine finish booting (at the end of which the channels are properly set-up) before
@@ -305,34 +318,33 @@ - (void)skip_testFlutterViewControllerDetachingSendsApplicationLifecycle {
305318
NSMutableArray* lifecycleExpectations = [NSMutableArray arrayWithCapacity:10];
306319

307320
// Expected sequence from showing the FlutterViewController is inactive and resumed.
308-
[lifecycleExpectations addObjectsFromArray:@[
309-
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.inactive"
310-
forStep:@"showing a FlutterViewController"],
311-
[[XCAppLifecycleTestExpectation alloc] initForLifecycle:@"AppLifecycleState.resumed"
312-
forStep:@"showing a FlutterViewController"]
313-
]];
321+
[lifecycleExpectations addObjectsFromArray:[self initialPresentLifecycles]];
322+
323+
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
324+
if (lifecycleExpectations.count == 0) {
325+
XCTFail(@"Unexpected lifecycle transition: %@", message);
326+
return;
327+
}
328+
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
329+
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
330+
XCTFail(@"Expected lifecycle %@ but instead received %@", [nextExpectation expectedLifecycle],
331+
message);
332+
return;
333+
}
334+
335+
[nextExpectation fulfill];
336+
[lifecycleExpectations removeObjectAtIndex:0];
337+
}];
338+
314339
// At the end of Flutter VC, we want to make sure it deallocs and sends detached signal.
315340
// Using autoreleasepool will guarantee that.
316341
FlutterViewController* flutterVC;
317342
@autoreleasepool {
318-
flutterVC = [rootVC showFlutter];
319-
NSLog(@"FlutterViewController instance %@ created", flutterVC);
320-
[engine.lifecycleChannel setMessageHandler:^(id message, FlutterReply callback) {
321-
if (lifecycleExpectations.count == 0) {
322-
XCTFail(@"Unexpected lifecycle transition: %@", message);
323-
return;
324-
}
325-
XCAppLifecycleTestExpectation* nextExpectation = [lifecycleExpectations objectAtIndex:0];
326-
if (![[nextExpectation expectedLifecycle] isEqualToString:message]) {
327-
XCTFail(@"Expected lifecycle %@ but instead received %@",
328-
[nextExpectation expectedLifecycle], message);
329-
return;
330-
}
331-
332-
[nextExpectation fulfill];
333-
[lifecycleExpectations removeObjectAtIndex:0];
343+
XCTestExpectation* vcShown = [self expectationWithDescription:@"present"];
344+
flutterVC = [rootVC showFlutter:^{
345+
[vcShown fulfill];
334346
}];
335-
347+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
336348
[self waitForExpectations:lifecycleExpectations timeout:5];
337349

338350
// Starts dealloc flutter VC.

0 commit comments

Comments
 (0)