Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
6c818d77
Unverified
Commit
6c818d77
authored
Mar 01, 2022
by
Emmanuel Garcia
Committed by
GitHub
Mar 01, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add Android lifecycles test (#99319)
parent
64d9ea60
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
289 additions
and
57 deletions
+289
-57
.ci.yaml
.ci.yaml
+11
-0
TESTOWNERS
TESTOWNERS
+2
-1
android_lifecycles_test.dart
dev/devicelab/bin/tasks/android_lifecycles_test.dart
+10
-0
devices.dart
dev/devicelab/lib/framework/devices.dart
+2
-2
android_choreographer_do_frame_test.dart
...icelab/lib/tasks/android_choreographer_do_frame_test.dart
+52
-54
android_lifecycles_test.dart
dev/devicelab/lib/tasks/android_lifecycles_test.dart
+212
-0
No files found.
.ci.yaml
View file @
6c818d77
...
...
@@ -2388,6 +2388,17 @@ targets:
task_name
:
android_choreographer_do_frame_test
scheduler
:
luci
-
name
:
Linux_android android_lifecycles_test
bringup
:
true
recipe
:
devicelab/devicelab_drone
presubmit
:
false
timeout
:
60
properties
:
tags
:
>
["devicelab","android","linux"]
task_name
:
android_lifecycles_test
scheduler
:
luci
-
name
:
Mac build_aar_module_test
recipe
:
devicelab/devicelab_drone
timeout
:
60
...
...
TESTOWNERS
View file @
6c818d77
...
...
@@ -10,7 +10,9 @@
## Linux Android DeviceLab tests
/dev/devicelab/bin/tasks/analyzer_benchmark.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/android_choreographer_do_frame_test.dart @blasten @flutter/engine
/dev/devicelab/bin/tasks/android_defines_test.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/android_lifecycles_test.dart @blasten @flutter/engine
/dev/devicelab/bin/tasks/android_obfuscate_test.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/android_picture_cache_complexity_scoring_perf__timeline_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/android_stack_size_test.dart @zanderso @flutter/tool
...
...
@@ -71,7 +73,6 @@
/dev/devicelab/bin/tasks/opacity_peephole_fade_transition_text_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/opacity_peephole_grid_of_alpha_savelayers_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/android_choreographer_do_frame_test.dart @blasten @flutter/engine
## Windows Android DeviceLab tests
/dev/devicelab/bin/tasks/basic_material_app_win__compile.dart @zanderso @flutter/tool
...
...
dev/devicelab/bin/tasks/android_lifecycles_test.dart
0 → 100644
View file @
6c818d77
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter_devicelab/framework/framework.dart'
;
import
'package:flutter_devicelab/tasks/android_lifecycles_test.dart'
;
Future
<
void
>
main
()
async
{
await
task
(
androidLifecyclesTest
());
}
dev/devicelab/lib/framework/devices.dart
View file @
6c818d77
...
...
@@ -545,7 +545,7 @@ class AndroidDevice extends Device {
}
}
/// Executes [command] on `adb shell`
and returns its exit code
.
/// Executes [command] on `adb shell`.
Future
<
void
>
shellExec
(
String
command
,
List
<
String
>
arguments
,
{
Map
<
String
,
String
>?
environment
,
bool
silent
=
false
})
async
{
await
adb
(<
String
>[
'shell'
,
command
,
...
arguments
],
environment:
environment
,
silent:
silent
);
}
...
...
@@ -637,7 +637,7 @@ class AndroidDevice extends Device {
late
final
StreamController
<
String
>
stream
;
stream
=
StreamController
<
String
>(
onListen:
()
async
{
await
adb
(<
String
>[
'logcat'
,
'-
-clear
'
]);
await
adb
(<
String
>[
'logcat'
,
'-
c
'
]);
final
Process
process
=
await
startProcess
(
adbPath
,
// Make logcat less chatty by filtering down to just ActivityManager
...
...
dev/devicelab/lib/tasks/android_choreographer_do_frame_test.dart
View file @
6c818d77
...
...
@@ -18,12 +18,11 @@ const List<String> kSentinelStr = <String>[
'==== sentinel #3 ===='
,
];
//
Regression test for https://github.com/flutter/flutter/issues/98973
//
This test ensures that Choreographer#doFrame finishes during application startup
.
//
This test fails if the application hangs during this period.
//
https://ui.perfetto.dev/#!/?s=da6628c3a92456ae8fa3f345d0186e781da77e90fc8a64d073e9fee11d1e65
//
/ Tests that Choreographer#doFrame finishes during application startup.
//
/ This test fails if the application hangs during this period
.
//
/ https://ui.perfetto.dev/#!/?s=da6628c3a92456ae8fa3f345d0186e781da77e90fc8a64d073e9fee11d1e65
//
/ Regression test for https://github.com/flutter/flutter/issues/98973
TaskFunction
androidChoreographerDoFrameTest
(
{
String
?
deviceIdOverride
,
Map
<
String
,
String
>?
environment
,
})
{
final
Directory
tempDir
=
Directory
.
systemTemp
...
...
@@ -87,18 +86,19 @@ Future<void> main() async {
}
section('
Flutter
run
(
mode:
$mode
)
');
late Process run;
await inDirectory(path.join(tempDir.path, '
app
'), () async {
final Process
run = await startProcess(
run = await startProcess(
path.join(flutterDirectory.path, '
bin
', '
flutter
'),
flutterCommandArgs('
run
', <String>['
--
$mode
', '
--
verbose
']),
);
});
int currSentinelIdx = 0;
final StreamSubscription<void> stdout = run.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
int currSentinelIdx = 0;
final StreamSubscription<void> stdout = run.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
if (currSentinelIdx < sentinelCompleters.keys.length &&
line.contains(sentinelCompleters.keys.elementAt(currSentinelIdx))) {
sentinelCompleters.values.elementAt(currSentinelIdx).complete();
...
...
@@ -107,61 +107,59 @@ Future<void> main() async {
} else {
print('
stdout:
$line
');
}
});
final StreamSubscription<void> stderr = run.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('
stderr:
$line
');
});
final Completer<void> exitCompleter = Completer<void>();
unawaited(run.exitCode.then((int exitCode) {
exitCompleter.complete();
}));
final StreamSubscription<void> stderr = run.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
print('
stderr:
$line
');
});
section('
Wait
for
sentinels
(
mode:
$mode
)
');
for (final Completer<void> completer in sentinelCompleters.values) {
if (nextCompleterIdx == 0) {
// Don'
t
time
out
because
we
don
't know how long it would take to get the first log.
final Completer<void> exitCompleter = Completer<void>();
unawaited(run.exitCode.then((int exitCode) {
exitCompleter.complete();
}));
section('
Wait
for
sentinels
(
mode:
$mode
)
');
for (final Completer<void> completer in sentinelCompleters.values) {
if (nextCompleterIdx == 0) {
// Don'
t
time
out
because
we
don
't know how long it would take to get the first log.
await Future.any<dynamic>(
<Future<dynamic>>[
completer.future,
exitCompleter.future,
],
);
} else {
try {
// Time out since this should not take 1s after the first log was received.
await Future.any<dynamic>(
<Future<dynamic>>[
completer.future,
completer.future
.timeout(const Duration(seconds: 1))
,
exitCompleter.future,
],
);
} else {
try {
// Time out since this should not take 1s after the first log was received.
await Future.any<dynamic>(
<Future<dynamic>>[
completer.future.timeout(const Duration(seconds: 1)),
exitCompleter.future,
],
);
} on TimeoutException {
break;
}
}
if (exitCompleter.isCompleted) {
// The process exited.
} on TimeoutException {
break;
}
nextCompleterIdx++;
}
if (exitCompleter.isCompleted) {
// The process exited.
break;
}
nextCompleterIdx++;
}
section('
Quit
app
(
mode:
$mode
)
');
run.stdin.write('
q
');
await exitCompleter.future;
section('
Quit
app
(
mode:
$mode
)
');
run.stdin.write('
q
');
await exitCompleter.future;
section('
Stop
listening
to
stdout
and
stderr
(
mode:
$mode
)
');
await stdout.cancel();
await stderr.cancel();
run.kill();
});
section('
Stop
listening
to
stdout
and
stderr
(
mode:
$mode
)
');
await stdout.cancel();
await stderr.cancel();
run.kill();
if (nextCompleterIdx == sentinelCompleters.values.length) {
return TaskResult.success(null);
...
...
dev/devicelab/lib/tasks/android_lifecycles_test.dart
0 → 100644
View file @
6c818d77
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:convert'
;
import
'dart:io'
;
import
'package:path/path.dart'
as
path
;
import
'../framework/devices.dart'
;
import
'../framework/framework.dart'
;
import
'../framework/task_result.dart'
;
import
'../framework/utils.dart'
;
const
String
_kOrgName
=
'com.example.activitydestroy'
;
final
RegExp
_lifecycleSentinelRegExp
=
RegExp
(
r'==== lifecycle\: (.+) ===='
);
/// Tests the following Android lifecycles: Activity#onStop(), Activity#onResume(), Activity#onPause()
/// from Dart perspective in debug, profile, and release modes.
TaskFunction
androidLifecyclesTest
(
{
Map
<
String
,
String
>?
environment
,
})
{
final
Directory
tempDir
=
Directory
.
systemTemp
.
createTempSync
(
'flutter_devicelab_activity_destroy.'
);
return
()
async
{
try
{
section
(
'Create app'
);
await
inDirectory
(
tempDir
,
()
async
{
await
flutter
(
'create'
,
options:
<
String
>[
'--platforms'
,
'android'
,
'--org'
,
_kOrgName
,
'app'
,
],
environment:
environment
,
);
});
final
File
mainDart
=
File
(
path
.
join
(
tempDir
.
absolute
.
path
,
'app'
,
'lib'
,
'main.dart'
,
));
if
(!
mainDart
.
existsSync
())
{
return
TaskResult
.
failure
(
'
${mainDart.path}
does not exist'
);
}
section
(
'Patch lib/main.dart'
);
await
mainDart
.
writeAsString
(
r''
'
import '
package:
flutter
/
widgets
.
dart
';
class LifecycleObserver extends WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('
====
lifecycle:
$state
====
');
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding.instance.addObserver(LifecycleObserver());
runApp(Container());
}
'''
,
flush:
true
);
final
List
<
String
>
androidLifecycles
=
<
String
>[];
Future
<
TaskResult
>
runTestFor
(
String
mode
)
async
{
section
(
'Flutter run (mode:
$mode
)'
);
late
Process
run
;
await
inDirectory
(
path
.
join
(
tempDir
.
path
,
'app'
),
()
async
{
run
=
await
startProcess
(
path
.
join
(
flutterDirectory
.
path
,
'bin'
,
'flutter'
),
flutterCommandArgs
(
'run'
,
<
String
>[
'--
$mode
'
]),
);
});
final
AndroidDevice
device
=
await
devices
.
workingDevice
as
AndroidDevice
;
await
device
.
unlock
();
final
StreamController
<
String
>
lifecyles
=
StreamController
<
String
>();
final
StreamSubscription
<
void
>
stdout
=
run
.
stdout
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
listen
((
String
log
)
{
final
RegExpMatch
?
match
=
_lifecycleSentinelRegExp
.
firstMatch
(
log
);
print
(
'stdout:
$log
'
);
if
(
match
==
null
)
{
return
;
}
final
String
lifecycle
=
match
[
1
]!;
androidLifecycles
.
add
(
lifecycle
);
print
(
'stdout: Found app lifecycle:
$lifecycle
'
);
lifecyles
.
add
(
lifecycle
);
});
final
StreamSubscription
<
void
>
stderr
=
run
.
stderr
.
transform
<
String
>(
utf8
.
decoder
)
.
transform
<
String
>(
const
LineSplitter
())
.
listen
((
String
log
)
{
print
(
'stderr:
$log
'
);
});
final
StreamIterator
<
String
>
lifecycleItr
=
StreamIterator
<
String
>(
lifecyles
.
stream
);
{
const
String
expected
=
'AppLifecycleState.resumed'
;
await
lifecycleItr
.
moveNext
();
final
String
got
=
lifecycleItr
.
current
;
if
(
expected
!=
got
)
{
return
TaskResult
.
failure
(
'expected lifecycles: `
$expected
`, but got`
$got
`'
);
}
}
section
(
'Toggling app switch (mode:
$mode
)'
);
await
device
.
shellExec
(
'input'
,
<
String
>[
'keyevent'
,
'KEYCODE_APP_SWITCH'
]);
{
const
String
expected
=
'AppLifecycleState.inactive'
;
await
lifecycleItr
.
moveNext
();
final
String
got
=
lifecycleItr
.
current
;
if
(
expected
!=
got
)
{
return
TaskResult
.
failure
(
'expected lifecycles: `
$expected
`, but got`
$got
`'
);
}
}
section
(
'Bring activity to foreground (mode:
$mode
)'
);
await
device
.
shellExec
(
'am'
,
<
String
>[
'start'
,
'--activity-single-top'
,
'
$_kOrgName
.app/.MainActivity'
]);
{
const
String
expected
=
'AppLifecycleState.resumed'
;
await
lifecycleItr
.
moveNext
();
final
String
got
=
lifecycleItr
.
current
;
if
(
expected
!=
got
)
{
return
TaskResult
.
failure
(
'expected lifecycles: `
$expected
`, but got`
$got
`'
);
}
}
section
(
'Launch Settings app (mode:
$mode
)'
);
await
device
.
shellExec
(
'am'
,
<
String
>[
'start'
,
'-a'
,
'android.settings.SETTINGS'
]);
{
const
String
expected
=
'AppLifecycleState.inactive'
;
await
lifecycleItr
.
moveNext
();
final
String
got
=
lifecycleItr
.
current
;
if
(
expected
!=
got
)
{
return
TaskResult
.
failure
(
'expected lifecycles: `
$expected
`, but got`
$got
`'
);
}
}
{
const
String
expected
=
'AppLifecycleState.paused'
;
await
lifecycleItr
.
moveNext
();
final
String
got
=
lifecycleItr
.
current
;
if
(
expected
!=
got
)
{
return
TaskResult
.
failure
(
'expected lifecycles: `
$expected
`, but got`
$got
`'
);
}
}
section
(
'Bring activity to foreground (mode:
$mode
)'
);
await
device
.
shellExec
(
'am'
,
<
String
>[
'start'
,
'--activity-single-top'
,
'
$_kOrgName
.app/.MainActivity'
]);
{
const
String
expected
=
'AppLifecycleState.resumed'
;
await
lifecycleItr
.
moveNext
();
final
String
got
=
lifecycleItr
.
current
;
if
(
expected
!=
got
)
{
return
TaskResult
.
failure
(
'expected lifecycles: `
$expected
`, but got`
$got
`'
);
}
}
run
.
kill
();
section
(
'Stop subscriptions (mode:
$mode
)'
);
await
lifecycleItr
.
cancel
();
await
lifecyles
.
close
();
await
stdout
.
cancel
();
await
stderr
.
cancel
();
return
TaskResult
.
success
(
null
);
}
final
TaskResult
debugResult
=
await
runTestFor
(
'debug'
);
if
(
debugResult
.
failed
)
{
return
debugResult
;
}
final
TaskResult
profileResult
=
await
runTestFor
(
'profile'
);
if
(
profileResult
.
failed
)
{
return
profileResult
;
}
final
TaskResult
releaseResult
=
await
runTestFor
(
'release'
);
if
(
releaseResult
.
failed
)
{
return
releaseResult
;
}
return
TaskResult
.
success
(
null
);
}
finally
{
rmTree
(
tempDir
);
}
};
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment