Skip to content

Commit 337bbfb

Browse files
authored
Potentially fixing the flakiness in win32 windowing tests, but it needs some running (flutter#178499)
Adding some more logging and retrying mechanisms to the win32 windowing tests to see if it fixes the flakiness. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
1 parent 1cee814 commit 337bbfb

File tree

2 files changed

+242
-156
lines changed

2 files changed

+242
-156
lines changed

dev/integration_tests/windowing_test/lib/main.dart

Lines changed: 132 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -32,138 +32,149 @@ void main() {
3232
final Completer<void> windowCreated = Completer();
3333
enableFlutterDriverExtension(
3434
handler: (String? message) async {
35-
await windowCreated.future;
36-
if (message == null) {
37-
return '';
38-
}
39-
40-
final jsonMap = jsonDecode(message);
41-
if (!jsonMap.containsKey('type')) {
42-
throw ArgumentError('Message must contain a "type" field.');
43-
}
35+
try {
36+
await windowCreated.future;
37+
if (message == null) {
38+
return '';
39+
}
4440

45-
/// This helper method registers a listener on the controller,
46-
/// calls [act] to perform some action on the controller, waits for
47-
/// the [predicate] to be satisified, and finally cleans up the listener.
48-
Future<void> awaitNotification(
49-
VoidCallback act,
50-
bool Function() predicate,
51-
) async {
52-
final StreamController<bool> streamController = StreamController();
53-
void notificationHandler() {
54-
streamController.add(true);
41+
final jsonMap = jsonDecode(message);
42+
if (!jsonMap.containsKey('type')) {
43+
throw ArgumentError('Message must contain a "type" field.');
5544
}
5645

57-
controller.addListener(notificationHandler);
46+
/// This helper method registers a listener on the controller,
47+
/// calls [act] to perform some action on the controller, waits for
48+
/// the [predicate] to be satisified, and finally cleans up the listener.
49+
Future<void> awaitNotification(
50+
VoidCallback act,
51+
bool Function() predicate,
52+
) async {
53+
final StreamController<bool> streamController = StreamController();
54+
void notificationHandler() {
55+
streamController.add(true);
56+
}
57+
58+
controller.addListener(notificationHandler);
5859

59-
act();
60+
try {
61+
act();
6062

61-
// On macOS, the predicate is immediately true, even though
62-
// the animation is in progress and next request for state change
63-
// will be ignored. Easiest way to handle this is to just wait.
64-
if (defaultTargetPlatform == TargetPlatform.macOS) {
65-
await Future.delayed(Duration(seconds: 1));
66-
}
63+
// On macOS, the predicate is immediately true, even though
64+
// the animation is in progress and next request for state change
65+
// will be ignored. Easiest way to handle this is to just wait.
66+
if (defaultTargetPlatform == TargetPlatform.macOS) {
67+
await Future.delayed(Duration(seconds: 1));
68+
}
6769

68-
await for (final _ in streamController.stream) {
69-
if (predicate()) {
70-
break;
70+
// Add a timeout to avoid hanging indefinitely
71+
await for (final _ in streamController.stream.timeout(
72+
Duration(seconds: 10),
73+
)) {
74+
if (predicate()) {
75+
break;
76+
}
77+
}
78+
} finally {
79+
controller.removeListener(notificationHandler);
80+
await streamController.close();
7181
}
7282
}
73-
controller.removeListener(notificationHandler);
74-
}
7583

76-
if (jsonMap['type'] == 'ping') {
77-
return jsonEncode({'type': 'pong'});
78-
} else if (jsonMap['type'] == 'get_size') {
79-
return jsonEncode({
80-
'width': controller.contentSize.width,
81-
'height': controller.contentSize.height,
82-
});
83-
} else if (jsonMap['type'] == 'set_size') {
84-
final Size size = Size(
85-
jsonMap['width'].toDouble(),
86-
jsonMap['height'].toDouble(),
87-
);
88-
await awaitNotification(() {
89-
controller.setSize(size);
90-
}, () => controller.contentSize == size);
91-
} else if (jsonMap['type'] == 'set_constraints') {
92-
final BoxConstraints constraints = BoxConstraints(
93-
minWidth: jsonMap['min_width'].toDouble(),
94-
minHeight: jsonMap['min_height'].toDouble(),
95-
maxWidth: jsonMap['max_width'].toDouble(),
96-
maxHeight: jsonMap['max_height'].toDouble(),
97-
);
98-
// We assume that this will cause a resize, which the current tests do.
99-
final initialSize = controller.contentSize;
100-
await awaitNotification(() {
101-
controller.setConstraints(constraints);
102-
}, () => controller.contentSize != initialSize);
103-
} else if (jsonMap['type'] == 'set_fullscreen') {
104-
await awaitNotification(() {
105-
controller.setFullscreen(true);
106-
}, () => controller.isFullscreen);
107-
} else if (jsonMap['type'] == 'unset_fullscreen') {
108-
await awaitNotification(() {
109-
controller.setFullscreen(false);
110-
}, () => !controller.isFullscreen);
111-
} else if (jsonMap['type'] == 'get_fullscreen') {
112-
return jsonEncode({'isFullscreen': controller.isFullscreen});
113-
} else if (jsonMap['type'] == 'set_maximized') {
114-
await awaitNotification(() {
115-
controller.setMaximized(true);
116-
}, () => controller.isMaximized);
117-
} else if (jsonMap['type'] == 'unset_maximized') {
118-
await awaitNotification(() {
119-
controller.setMaximized(false);
120-
}, () => !controller.isMaximized);
121-
} else if (jsonMap['type'] == 'get_maximized') {
122-
return jsonEncode({'isMaximized': controller.isMaximized});
123-
} else if (jsonMap['type'] == 'set_minimized') {
124-
await awaitNotification(() {
125-
controller.setMinimized(true);
126-
}, () => controller.isMinimized);
127-
} else if (jsonMap['type'] == 'unset_minimized') {
128-
await awaitNotification(() {
129-
controller.setMinimized(false);
130-
}, () => !controller.isMinimized);
131-
} else if (jsonMap['type'] == 'get_minimized') {
132-
return jsonEncode({'isMinimized': controller.isMinimized});
133-
} else if (jsonMap['type'] == 'set_title') {
134-
final String title = jsonMap['title'];
135-
await awaitNotification(() {
136-
controller.setTitle(title);
137-
}, () => controller.title == title);
138-
} else if (jsonMap['type'] == 'get_title') {
139-
return jsonEncode({'title': controller.title});
140-
} else if (jsonMap['type'] == 'set_activated') {
141-
await awaitNotification(() {
142-
controller.activate();
143-
}, () => controller.isActivated);
144-
} else if (jsonMap['type'] == 'get_activated') {
145-
return jsonEncode({'isActivated': controller.isActivated});
146-
} else if (jsonMap['type'] == 'open_dialog') {
147-
if (dialogController.value != null) {
148-
return jsonEncode({'result': false});
84+
if (jsonMap['type'] == 'ping') {
85+
return jsonEncode({'type': 'pong'});
86+
} else if (jsonMap['type'] == 'get_size') {
87+
return jsonEncode({
88+
'width': controller.contentSize.width,
89+
'height': controller.contentSize.height,
90+
});
91+
} else if (jsonMap['type'] == 'set_size') {
92+
final Size size = Size(
93+
jsonMap['width'].toDouble(),
94+
jsonMap['height'].toDouble(),
95+
);
96+
await awaitNotification(() {
97+
controller.setSize(size);
98+
}, () => controller.contentSize == size);
99+
} else if (jsonMap['type'] == 'set_constraints') {
100+
final BoxConstraints constraints = BoxConstraints(
101+
minWidth: jsonMap['min_width'].toDouble(),
102+
minHeight: jsonMap['min_height'].toDouble(),
103+
maxWidth: jsonMap['max_width'].toDouble(),
104+
maxHeight: jsonMap['max_height'].toDouble(),
105+
);
106+
// We assume that this will cause a resize, which the current tests do.
107+
final initialSize = controller.contentSize;
108+
await awaitNotification(() {
109+
controller.setConstraints(constraints);
110+
}, () => controller.contentSize != initialSize);
111+
} else if (jsonMap['type'] == 'set_fullscreen') {
112+
await awaitNotification(() {
113+
controller.setFullscreen(true);
114+
}, () => controller.isFullscreen);
115+
} else if (jsonMap['type'] == 'unset_fullscreen') {
116+
await awaitNotification(() {
117+
controller.setFullscreen(false);
118+
}, () => !controller.isFullscreen);
119+
} else if (jsonMap['type'] == 'get_fullscreen') {
120+
return jsonEncode({'isFullscreen': controller.isFullscreen});
121+
} else if (jsonMap['type'] == 'set_maximized') {
122+
await awaitNotification(() {
123+
controller.setMaximized(true);
124+
}, () => controller.isMaximized);
125+
} else if (jsonMap['type'] == 'unset_maximized') {
126+
await awaitNotification(() {
127+
controller.setMaximized(false);
128+
}, () => !controller.isMaximized);
129+
} else if (jsonMap['type'] == 'get_maximized') {
130+
return jsonEncode({'isMaximized': controller.isMaximized});
131+
} else if (jsonMap['type'] == 'set_minimized') {
132+
await awaitNotification(() {
133+
controller.setMinimized(true);
134+
}, () => controller.isMinimized);
135+
} else if (jsonMap['type'] == 'unset_minimized') {
136+
await awaitNotification(() {
137+
controller.setMinimized(false);
138+
}, () => !controller.isMinimized);
139+
} else if (jsonMap['type'] == 'get_minimized') {
140+
return jsonEncode({'isMinimized': controller.isMinimized});
141+
} else if (jsonMap['type'] == 'set_title') {
142+
final String title = jsonMap['title'];
143+
await awaitNotification(() {
144+
controller.setTitle(title);
145+
}, () => controller.title == title);
146+
} else if (jsonMap['type'] == 'get_title') {
147+
return jsonEncode({'title': controller.title});
148+
} else if (jsonMap['type'] == 'set_activated') {
149+
await awaitNotification(() {
150+
controller.activate();
151+
}, () => controller.isActivated);
152+
} else if (jsonMap['type'] == 'get_activated') {
153+
return jsonEncode({'isActivated': controller.isActivated});
154+
} else if (jsonMap['type'] == 'open_dialog') {
155+
if (dialogController.value != null) {
156+
return jsonEncode({'result': false});
157+
}
158+
dialogController.value = DialogWindowController(
159+
preferredSize: const Size(200, 200),
160+
parent: controller,
161+
delegate: MyDialogWindowControllerDelegate(
162+
onDestroyed: () {
163+
dialogController.value = null;
164+
},
165+
),
166+
);
167+
return jsonEncode({'result': true});
168+
} else if (jsonMap['type'] == 'close_dialog') {
169+
dialogController.value?.destroy();
170+
return jsonEncode({'result': true});
171+
} else {
172+
throw ArgumentError('Unknown message type: ${jsonMap['type']}');
149173
}
150-
dialogController.value = DialogWindowController(
151-
preferredSize: const Size(200, 200),
152-
parent: controller,
153-
delegate: MyDialogWindowControllerDelegate(
154-
onDestroyed: () {
155-
dialogController.value = null;
156-
},
157-
),
158-
);
159-
return jsonEncode({'result': true});
160-
} else if (jsonMap['type'] == 'close_dialog') {
161-
dialogController.value?.destroy();
162-
return jsonEncode({'result': true});
163-
} else {
164-
throw ArgumentError('Unknown message type: ${jsonMap['type']}');
174+
return '';
175+
} catch (e) {
176+
return jsonEncode({'error': e.toString()});
165177
}
166-
return '';
167178
},
168179
);
169180
controller = RegularWindowController(

0 commit comments

Comments
 (0)