Thanks to visit codestin.com
Credit goes to github.com

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

Commit e27f714

Browse files
author
Emmanuel Garcia
authored
Add regression test for #98973 (#99187)
1 parent bf1ddbf commit e27f714

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

.ci.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,17 @@ targets:
23782378
benchmark: "true"
23792379
scheduler: luci
23802380

2381+
- name: Linux_android android_choreographer_do_frame_test
2382+
bringup: true
2383+
recipe: devicelab/devicelab_drone
2384+
presubmit: false
2385+
timeout: 60
2386+
properties:
2387+
tags: >
2388+
["devicelab","android","linux"]
2389+
task_name: android_choreographer_do_frame_test
2390+
scheduler: luci
2391+
23812392
- name: Mac build_aar_module_test
23822393
recipe: devicelab/devicelab_drone
23832394
timeout: 60

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
/dev/devicelab/bin/tasks/opacity_peephole_fade_transition_text_perf__e2e_summary.dart @flar @flutter/engine
7272
/dev/devicelab/bin/tasks/opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary.dart @flar @flutter/engine
7373
/dev/devicelab/bin/tasks/opacity_peephole_grid_of_alpha_savelayers_perf__e2e_summary.dart @flar @flutter/engine
74+
/dev/devicelab/bin/tasks/android_choreographer_do_frame_test.dart @blasten @flutter/engine
7475

7576
## Windows Android DeviceLab tests
7677
/dev/devicelab/bin/tasks/basic_material_app_win__compile.dart @zanderso @flutter/tool
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_devicelab/framework/framework.dart';
6+
import 'package:flutter_devicelab/tasks/android_choreographer_do_frame_test.dart';
7+
8+
Future<void> main() async {
9+
await task(androidChoreographerDoFrameTest());
10+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
import 'dart:io';
8+
9+
import 'package:path/path.dart' as path;
10+
11+
import '../framework/framework.dart';
12+
import '../framework/task_result.dart';
13+
import '../framework/utils.dart';
14+
15+
const List<String> kSentinelStr = <String>[
16+
'==== sentinel #1 ====',
17+
'==== sentinel #2 ====',
18+
'==== sentinel #3 ====',
19+
];
20+
21+
// Regression test for https://github.com/flutter/flutter/issues/98973
22+
// This test ensures that Choreographer#doFrame finishes during application startup.
23+
// This test fails if the application hangs during this period.
24+
// https://ui.perfetto.dev/#!/?s=da6628c3a92456ae8fa3f345d0186e781da77e90fc8a64d073e9fee11d1e65
25+
TaskFunction androidChoreographerDoFrameTest({
26+
String? deviceIdOverride,
27+
Map<String, String>? environment,
28+
}) {
29+
final Directory tempDir = Directory.systemTemp
30+
.createTempSync('flutter_devicelab_android_surface_recreation.');
31+
return () async {
32+
try {
33+
section('Create app');
34+
await inDirectory(tempDir, () async {
35+
await flutter(
36+
'create',
37+
options: <String>[
38+
'--platforms',
39+
'android',
40+
'app',
41+
],
42+
environment: environment,
43+
);
44+
});
45+
46+
final File mainDart = File(path.join(
47+
tempDir.absolute.path,
48+
'app',
49+
'lib',
50+
'main.dart',
51+
));
52+
if (!mainDart.existsSync()) {
53+
return TaskResult.failure('${mainDart.path} does not exist');
54+
}
55+
56+
section('Patch lib/main.dart');
57+
await mainDart.writeAsString('''
58+
import 'package:flutter/material.dart';
59+
import 'package:flutter/services.dart';
60+
61+
Future<void> main() async {
62+
WidgetsFlutterBinding.ensureInitialized();
63+
64+
print('${kSentinelStr[0]}');
65+
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
66+
67+
print('${kSentinelStr[1]}');
68+
// If the Android UI thread is blocked, then this Future won't resolve.
69+
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
70+
71+
print('${kSentinelStr[2]}');
72+
runApp(
73+
Container(
74+
decoration: BoxDecoration(
75+
color: const Color(0xff7c94b6),
76+
),
77+
),
78+
);
79+
}
80+
''', flush: true);
81+
82+
Future<TaskResult> runTestFor(String mode) async {
83+
int nextCompleterIdx = 0;
84+
final Map<String, Completer<void>> sentinelCompleters = <String, Completer<void>>{};
85+
for (final String sentinel in kSentinelStr) {
86+
sentinelCompleters[sentinel] = Completer<void>();
87+
}
88+
89+
section('Flutter run (mode: $mode)');
90+
await inDirectory(path.join(tempDir.path, 'app'), () async {
91+
final Process run = await startProcess(
92+
path.join(flutterDirectory.path, 'bin', 'flutter'),
93+
flutterCommandArgs('run', <String>['--$mode', '--verbose']),
94+
);
95+
96+
int currSentinelIdx = 0;
97+
final StreamSubscription<void> stdout = run.stdout
98+
.transform<String>(utf8.decoder)
99+
.transform<String>(const LineSplitter())
100+
.listen((String line) {
101+
102+
if (currSentinelIdx < sentinelCompleters.keys.length &&
103+
line.contains(sentinelCompleters.keys.elementAt(currSentinelIdx))) {
104+
sentinelCompleters.values.elementAt(currSentinelIdx).complete();
105+
currSentinelIdx++;
106+
print('stdout(MATCHED): $line');
107+
} else {
108+
print('stdout: $line');
109+
}
110+
111+
});
112+
113+
final StreamSubscription<void> stderr = run.stderr
114+
.transform<String>(utf8.decoder)
115+
.transform<String>(const LineSplitter())
116+
.listen((String line) {
117+
print('stderr: $line');
118+
});
119+
120+
final Completer<void> exitCompleter = Completer<void>();
121+
122+
unawaited(run.exitCode.then((int exitCode) {
123+
exitCompleter.complete();
124+
}));
125+
126+
section('Wait for sentinels (mode: $mode)');
127+
for (final Completer<void> completer in sentinelCompleters.values) {
128+
if (nextCompleterIdx == 0) {
129+
// Don't time out because we don't know how long it would take to get the first log.
130+
await Future.any<dynamic>(
131+
<Future<dynamic>>[
132+
completer.future,
133+
exitCompleter.future,
134+
],
135+
);
136+
} else {
137+
try {
138+
// Time out since this should not take 1s after the first log was received.
139+
await Future.any<dynamic>(
140+
<Future<dynamic>>[
141+
completer.future.timeout(const Duration(seconds: 1)),
142+
exitCompleter.future,
143+
],
144+
);
145+
} on TimeoutException {
146+
break;
147+
}
148+
}
149+
if (exitCompleter.isCompleted) {
150+
// The process exited.
151+
break;
152+
}
153+
nextCompleterIdx++;
154+
}
155+
156+
section('Quit app (mode: $mode)');
157+
run.stdin.write('q');
158+
await exitCompleter.future;
159+
160+
section('Stop listening to stdout and stderr (mode: $mode)');
161+
await stdout.cancel();
162+
await stderr.cancel();
163+
run.kill();
164+
});
165+
166+
if (nextCompleterIdx == sentinelCompleters.values.length) {
167+
return TaskResult.success(null);
168+
}
169+
final String nextSentinel = sentinelCompleters.keys.elementAt(nextCompleterIdx);
170+
return TaskResult.failure('Expected sentinel `$nextSentinel` in mode $mode');
171+
}
172+
173+
final TaskResult debugResult = await runTestFor('debug');
174+
if (debugResult.failed) {
175+
return debugResult;
176+
}
177+
178+
final TaskResult profileResult = await runTestFor('profile');
179+
if (profileResult.failed) {
180+
return profileResult;
181+
}
182+
183+
final TaskResult releaseResult = await runTestFor('release');
184+
if (releaseResult.failed) {
185+
return releaseResult;
186+
}
187+
188+
return TaskResult.success(null);
189+
} finally {
190+
rmTree(tempDir);
191+
}
192+
};
193+
}

0 commit comments

Comments
 (0)