Migrate core devicelab framework to null safety. (#85993)
Migrate core devicelab framework to null safety.
This commit is contained in:
parent
cc00e7e6bc
commit
2175e64e4f
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
|
|
||||||
import 'package:flutter_devicelab/framework/runner.dart';
|
import 'package:flutter_devicelab/framework/runner.dart';
|
||||||
@ -64,19 +62,19 @@ class TestCommand extends Command<void> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> run() async {
|
Future<void> run() async {
|
||||||
final List<String> taskArgsRaw = argResults['task-args'] as List<String>;
|
final List<String> taskArgsRaw = argResults!['task-args'] as List<String>;
|
||||||
// Prepend '--' to convert args to options when passed to task
|
// Prepend '--' to convert args to options when passed to task
|
||||||
final List<String> taskArgs = taskArgsRaw.map((String taskArg) => '--$taskArg').toList();
|
final List<String> taskArgs = taskArgsRaw.map((String taskArg) => '--$taskArg').toList();
|
||||||
print(taskArgs);
|
print(taskArgs);
|
||||||
await runTasks(
|
await runTasks(
|
||||||
<String>[argResults['task'] as String],
|
<String>[argResults!['task'] as String],
|
||||||
deviceId: argResults['device-id'] as String,
|
deviceId: argResults!['device-id'] as String?,
|
||||||
gitBranch: argResults['git-branch'] as String,
|
gitBranch: argResults!['git-branch'] as String?,
|
||||||
localEngine: argResults['local-engine'] as String,
|
localEngine: argResults!['local-engine'] as String?,
|
||||||
localEngineSrcPath: argResults['local-engine-src-path'] as String,
|
localEngineSrcPath: argResults!['local-engine-src-path'] as String?,
|
||||||
luciBuilder: argResults['luci-builder'] as String,
|
luciBuilder: argResults!['luci-builder'] as String?,
|
||||||
resultsPath: argResults['results-file'] as String,
|
resultsPath: argResults!['results-file'] as String?,
|
||||||
silent: argResults['silent'] as bool,
|
silent: (argResults!['silent'] as bool?) ?? false,
|
||||||
taskArgs: taskArgs,
|
taskArgs: taskArgs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
|
|
||||||
import '../framework/cocoon.dart';
|
import '../framework/cocoon.dart';
|
||||||
@ -25,8 +23,8 @@ class UploadMetricsCommand extends Command<void> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> run() async {
|
Future<void> run() async {
|
||||||
final String resultsPath = argResults['results-file'] as String;
|
final String resultsPath = argResults!['results-file'] as String;
|
||||||
final String serviceAccountTokenFile = argResults['service-account-token-file'] as String;
|
final String? serviceAccountTokenFile = argResults!['service-account-token-file'] as String?;
|
||||||
|
|
||||||
final Cocoon cocoon = Cocoon(serviceAccountTokenPath: serviceAccountTokenFile);
|
final Cocoon cocoon = Cocoon(serviceAccountTokenPath: serviceAccountTokenFile);
|
||||||
return cocoon.sendResultsPath(resultsPath);
|
return cocoon.sendResultsPath(resultsPath);
|
||||||
|
@ -2,10 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
|
|
||||||
import 'task_result.dart';
|
import 'task_result.dart';
|
||||||
|
|
||||||
@ -43,8 +40,8 @@ class ABTest {
|
|||||||
final String localEngine;
|
final String localEngine;
|
||||||
final String taskName;
|
final String taskName;
|
||||||
final DateTime runStart;
|
final DateTime runStart;
|
||||||
DateTime _runEnd;
|
DateTime? _runEnd;
|
||||||
DateTime get runEnd => _runEnd;
|
DateTime? get runEnd => _runEnd;
|
||||||
|
|
||||||
final Map<String, List<double>> _aResults;
|
final Map<String, List<double>> _aResults;
|
||||||
final Map<String, List<double>> _bResults;
|
final Map<String, List<double>> _bResults;
|
||||||
@ -91,15 +88,15 @@ class ABTest {
|
|||||||
kLocalEngineKeyName: localEngine,
|
kLocalEngineKeyName: localEngine,
|
||||||
kTaskNameKeyName: taskName,
|
kTaskNameKeyName: taskName,
|
||||||
kRunStartKeyName: runStart.toIso8601String(),
|
kRunStartKeyName: runStart.toIso8601String(),
|
||||||
kRunEndKeyName: runEnd.toIso8601String(),
|
kRunEndKeyName: runEnd!.toIso8601String(),
|
||||||
kAResultsKeyName: _aResults,
|
kAResultsKeyName: _aResults,
|
||||||
kBResultsKeyName: _bResults,
|
kBResultsKeyName: _bResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void updateColumnLengths(List<int> lengths, List<String> results) {
|
static void updateColumnLengths(List<int> lengths, List<String?> results) {
|
||||||
for (int column = 0; column < lengths.length; column++) {
|
for (int column = 0; column < lengths.length; column++) {
|
||||||
if (results[column] != null) {
|
if (results[column] != null) {
|
||||||
lengths[column] = math.max(lengths[column], results[column].length);
|
lengths[column] = math.max(lengths[column], results[column]?.length ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,10 +104,10 @@ class ABTest {
|
|||||||
static void formatResult(StringBuffer buffer,
|
static void formatResult(StringBuffer buffer,
|
||||||
List<int> lengths,
|
List<int> lengths,
|
||||||
List<FieldJustification> aligns,
|
List<FieldJustification> aligns,
|
||||||
List<String> values) {
|
List<String?> values) {
|
||||||
for (int column = 0; column < lengths.length; column++) {
|
for (int column = 0; column < lengths.length; column++) {
|
||||||
final int len = lengths[column];
|
final int len = lengths[column];
|
||||||
String value = values[column];
|
String? value = values[column];
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = ''.padRight(len);
|
value = ''.padRight(len);
|
||||||
} else {
|
} else {
|
||||||
@ -142,9 +139,9 @@ class ABTest {
|
|||||||
final Map<String, _ScoreSummary> summariesA = _summarize(_aResults);
|
final Map<String, _ScoreSummary> summariesA = _summarize(_aResults);
|
||||||
final Map<String, _ScoreSummary> summariesB = _summarize(_bResults);
|
final Map<String, _ScoreSummary> summariesB = _summarize(_bResults);
|
||||||
|
|
||||||
final List<List<String>> tableRows = <List<String>>[
|
final List<List<String?>> tableRows = <List<String?>>[
|
||||||
for (final String scoreKey in <String>{...summariesA.keys, ...summariesB.keys})
|
for (final String scoreKey in <String>{...summariesA.keys, ...summariesB.keys})
|
||||||
<String>[
|
<String?>[
|
||||||
scoreKey,
|
scoreKey,
|
||||||
summariesA[scoreKey]?.averageString, summariesA[scoreKey]?.noiseString,
|
summariesA[scoreKey]?.averageString, summariesA[scoreKey]?.noiseString,
|
||||||
summariesB[scoreKey]?.averageString, summariesB[scoreKey]?.noiseString,
|
summariesB[scoreKey]?.averageString, summariesB[scoreKey]?.noiseString,
|
||||||
@ -167,7 +164,7 @@ class ABTest {
|
|||||||
|
|
||||||
final List<int> lengths = List<int>.filled(6, 0);
|
final List<int> lengths = List<int>.filled(6, 0);
|
||||||
updateColumnLengths(lengths, titles);
|
updateColumnLengths(lengths, titles);
|
||||||
for (final List<String> row in tableRows) {
|
for (final List<String?> row in tableRows) {
|
||||||
updateColumnLengths(lengths, row);
|
updateColumnLengths(lengths, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +174,7 @@ class ABTest {
|
|||||||
FieldJustification.CENTER,
|
FieldJustification.CENTER,
|
||||||
...alignments.skip(1),
|
...alignments.skip(1),
|
||||||
], titles);
|
], titles);
|
||||||
for (final List<String> row in tableRows) {
|
for (final List<String?> row in tableRows) {
|
||||||
formatResult(buffer, lengths, alignments, row);
|
formatResult(buffer, lengths, alignments, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +189,7 @@ class ABTest {
|
|||||||
buffer.writeln('$scoreKey:');
|
buffer.writeln('$scoreKey:');
|
||||||
buffer.write(' A:\t');
|
buffer.write(' A:\t');
|
||||||
if (_aResults.containsKey(scoreKey)) {
|
if (_aResults.containsKey(scoreKey)) {
|
||||||
for (final double score in _aResults[scoreKey]) {
|
for (final double score in _aResults[scoreKey]!) {
|
||||||
buffer.write('${score.toStringAsFixed(2)}\t');
|
buffer.write('${score.toStringAsFixed(2)}\t');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -202,7 +199,7 @@ class ABTest {
|
|||||||
|
|
||||||
buffer.write(' B:\t');
|
buffer.write(' B:\t');
|
||||||
if (_bResults.containsKey(scoreKey)) {
|
if (_bResults.containsKey(scoreKey)) {
|
||||||
for (final double score in _bResults[scoreKey]) {
|
for (final double score in _bResults[scoreKey]!) {
|
||||||
buffer.write('${score.toStringAsFixed(2)}\t');
|
buffer.write('${score.toStringAsFixed(2)}\t');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -232,8 +229,8 @@ class ABTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (final String scoreKey in _allScoreKeys) {
|
for (final String scoreKey in _allScoreKeys) {
|
||||||
final _ScoreSummary summaryA = summariesA[scoreKey];
|
final _ScoreSummary? summaryA = summariesA[scoreKey];
|
||||||
final _ScoreSummary summaryB = summariesB[scoreKey];
|
final _ScoreSummary? summaryB = summariesB[scoreKey];
|
||||||
buffer.write('$scoreKey\t');
|
buffer.write('$scoreKey\t');
|
||||||
|
|
||||||
if (summaryA != null) {
|
if (summaryA != null) {
|
||||||
@ -261,8 +258,8 @@ class ABTest {
|
|||||||
|
|
||||||
class _ScoreSummary {
|
class _ScoreSummary {
|
||||||
_ScoreSummary({
|
_ScoreSummary({
|
||||||
@required this.average,
|
required this.average,
|
||||||
@required this.noise,
|
required this.noise,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Average (arithmetic mean) of a series of values collected by a benchmark.
|
/// Average (arithmetic mean) of a series of values collected by a benchmark.
|
||||||
@ -275,14 +272,14 @@ class _ScoreSummary {
|
|||||||
String get averageString => average.toStringAsFixed(2);
|
String get averageString => average.toStringAsFixed(2);
|
||||||
String get noiseString => '(${_ratioToPercent(noise)})';
|
String get noiseString => '(${_ratioToPercent(noise)})';
|
||||||
|
|
||||||
String improvementOver(_ScoreSummary other) {
|
String improvementOver(_ScoreSummary? other) {
|
||||||
return other == null ? '' : '${(average / other.average).toStringAsFixed(2)}x';
|
return other == null ? '' : '${(average / other.average).toStringAsFixed(2)}x';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addResult(TaskResult result, Map<String, List<double>> results) {
|
void _addResult(TaskResult result, Map<String, List<double>> results) {
|
||||||
for (final String scoreKey in result.benchmarkScoreKeys) {
|
for (final String scoreKey in result.benchmarkScoreKeys ?? <String>[]) {
|
||||||
final double score = (result.data[scoreKey] as num).toDouble();
|
final double score = (result.data![scoreKey] as num).toDouble();
|
||||||
results.putIfAbsent(scoreKey, () => <double>[]).add(score);
|
results.putIfAbsent(scoreKey, () => <double>[]).add(score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
@ -106,7 +104,7 @@ bool hasMultipleOccurrences(String text, Pattern pattern) {
|
|||||||
|
|
||||||
/// The Android home directory.
|
/// The Android home directory.
|
||||||
String get _androidHome {
|
String get _androidHome {
|
||||||
final String androidHome = Platform.environment['ANDROID_HOME'] ??
|
final String? androidHome = Platform.environment['ANDROID_HOME'] ??
|
||||||
Platform.environment['ANDROID_SDK_ROOT'];
|
Platform.environment['ANDROID_SDK_ROOT'];
|
||||||
if (androidHome == null || androidHome.isEmpty) {
|
if (androidHome == null || androidHome.isEmpty) {
|
||||||
throw Exception('Environment variable `ANDROID_SDK_ROOT` is not set.');
|
throw Exception('Environment variable `ANDROID_SDK_ROOT` is not set.');
|
||||||
@ -118,9 +116,9 @@ String get _androidHome {
|
|||||||
Future<String> _evalApkAnalyzer(
|
Future<String> _evalApkAnalyzer(
|
||||||
List<String> args, {
|
List<String> args, {
|
||||||
bool printStdout = false,
|
bool printStdout = false,
|
||||||
String workingDirectory,
|
String? workingDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
final String javaHome = await findJavaHome();
|
final String? javaHome = await findJavaHome();
|
||||||
if (javaHome == null || javaHome.isEmpty) {
|
if (javaHome == null || javaHome.isEmpty) {
|
||||||
throw Exception('No JAVA_HOME set.');
|
throw Exception('No JAVA_HOME set.');
|
||||||
}
|
}
|
||||||
@ -259,7 +257,7 @@ class FlutterProject {
|
|||||||
String get androidPath => path.join(rootPath, 'android');
|
String get androidPath => path.join(rootPath, 'android');
|
||||||
String get iosPath => path.join(rootPath, 'ios');
|
String get iosPath => path.join(rootPath, 'ios');
|
||||||
|
|
||||||
Future<void> addCustomBuildType(String name, {String initWith}) async {
|
Future<void> addCustomBuildType(String name, {required String initWith}) async {
|
||||||
final File buildScript = File(
|
final File buildScript = File(
|
||||||
path.join(androidPath, 'app', 'build.gradle'),
|
path.join(androidPath, 'app', 'build.gradle'),
|
||||||
);
|
);
|
||||||
@ -276,7 +274,7 @@ android {
|
|||||||
''');
|
''');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addGlobalBuildType(String name, {String initWith}) async {
|
Future<void> addGlobalBuildType(String name, {required String initWith}) async {
|
||||||
final File buildScript = File(
|
final File buildScript = File(
|
||||||
path.join(androidPath, 'build.gradle'),
|
path.join(androidPath, 'build.gradle'),
|
||||||
);
|
);
|
||||||
@ -360,11 +358,11 @@ flutter:
|
|||||||
pubspec.writeAsStringSync(newContents);
|
pubspec.writeAsStringSync(newContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> runGradleTask(String task, {List<String> options}) async {
|
Future<void> runGradleTask(String task, {List<String>? options}) async {
|
||||||
return _runGradleTask(workingDirectory: androidPath, task: task, options: options);
|
return _runGradleTask(workingDirectory: androidPath, task: task, options: options);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ProcessResult> resultOfGradleTask(String task, {List<String> options}) {
|
Future<ProcessResult> resultOfGradleTask(String task, {List<String>? options}) {
|
||||||
return _resultOfGradleTask(workingDirectory: androidPath, task: task, options: options);
|
return _resultOfGradleTask(workingDirectory: androidPath, task: task, options: options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,7 +414,11 @@ class FlutterModuleProject {
|
|||||||
String get rootPath => path.join(parent.path, name);
|
String get rootPath => path.join(parent.path, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runGradleTask({String workingDirectory, String task, List<String> options}) async {
|
Future<void> _runGradleTask({
|
||||||
|
required String workingDirectory,
|
||||||
|
required String task,
|
||||||
|
List<String>? options,
|
||||||
|
}) async {
|
||||||
final ProcessResult result = await _resultOfGradleTask(
|
final ProcessResult result = await _resultOfGradleTask(
|
||||||
workingDirectory: workingDirectory,
|
workingDirectory: workingDirectory,
|
||||||
task: task,
|
task: task,
|
||||||
@ -431,10 +433,13 @@ Future<void> _runGradleTask({String workingDirectory, String task, List<String>
|
|||||||
throw 'Gradle exited with error';
|
throw 'Gradle exited with error';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ProcessResult> _resultOfGradleTask({String workingDirectory, String task,
|
Future<ProcessResult> _resultOfGradleTask({
|
||||||
List<String> options}) async {
|
required String workingDirectory,
|
||||||
|
required String task,
|
||||||
|
List<String>? options,
|
||||||
|
}) async {
|
||||||
section('Find Java');
|
section('Find Java');
|
||||||
final String javaHome = await findJavaHome();
|
final String? javaHome = await findJavaHome();
|
||||||
|
|
||||||
if (javaHome == null)
|
if (javaHome == null)
|
||||||
throw TaskResult.failure('Could not find Java');
|
throw TaskResult.failure('Could not find Java');
|
||||||
@ -465,7 +470,7 @@ Future<ProcessResult> _resultOfGradleTask({String workingDirectory, String task,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns [null] if target matches [expectedTarget], otherwise returns an error message.
|
/// Returns [null] if target matches [expectedTarget], otherwise returns an error message.
|
||||||
String validateSnapshotDependency(FlutterProject project, String expectedTarget) {
|
String? validateSnapshotDependency(FlutterProject project, String expectedTarget) {
|
||||||
final File snapshotBlob = File(
|
final File snapshotBlob = File(
|
||||||
path.join(project.rootPath, 'build', 'app', 'intermediates',
|
path.join(project.rootPath, 'build', 'app', 'intermediates',
|
||||||
'flutter', 'debug', 'flutter_build.d'));
|
'flutter', 'debug', 'flutter_build.d'));
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert' show json, utf8, LineSplitter, JsonEncoder;
|
import 'dart:convert' show json, utf8, LineSplitter, JsonEncoder;
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter_devicelab/common.dart';
|
import 'package:flutter_devicelab/common.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
|
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
|
||||||
|
|
||||||
@ -32,10 +29,10 @@ class ChromeOptions {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// If not null passed as `--user-data-dir`.
|
/// If not null passed as `--user-data-dir`.
|
||||||
final String userDataDirectory;
|
final String? userDataDirectory;
|
||||||
|
|
||||||
/// If not null launches a Chrome tab at this URL.
|
/// If not null launches a Chrome tab at this URL.
|
||||||
final String url;
|
final String? url;
|
||||||
|
|
||||||
/// The width of the Chrome window.
|
/// The width of the Chrome window.
|
||||||
///
|
///
|
||||||
@ -49,14 +46,14 @@ class ChromeOptions {
|
|||||||
|
|
||||||
/// Launches code in "headless" mode, which allows running Chrome in
|
/// Launches code in "headless" mode, which allows running Chrome in
|
||||||
/// environments without a display, such as LUCI and Cirrus.
|
/// environments without a display, such as LUCI and Cirrus.
|
||||||
final bool headless;
|
final bool? headless;
|
||||||
|
|
||||||
/// The port Chrome will use for its debugging protocol.
|
/// The port Chrome will use for its debugging protocol.
|
||||||
///
|
///
|
||||||
/// If null, Chrome is launched without debugging. When running in headless
|
/// If null, Chrome is launched without debugging. When running in headless
|
||||||
/// mode without a debug port, Chrome quits immediately. For most tests it is
|
/// mode without a debug port, Chrome quits immediately. For most tests it is
|
||||||
/// typical to set [headless] to true and set a non-null debug port.
|
/// typical to set [headless] to true and set a non-null debug port.
|
||||||
final int debugPort;
|
final int? debugPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A function called when the Chrome process encounters an error.
|
/// A function called when the Chrome process encounters an error.
|
||||||
@ -79,7 +76,7 @@ class Chrome {
|
|||||||
/// The [onError] callback is called with an error message when the Chrome
|
/// The [onError] callback is called with an error message when the Chrome
|
||||||
/// process encounters an error. In particular, [onError] is called when the
|
/// process encounters an error. In particular, [onError] is called when the
|
||||||
/// Chrome process exits prematurely, i.e. before [stop] is called.
|
/// Chrome process exits prematurely, i.e. before [stop] is called.
|
||||||
static Future<Chrome> launch(ChromeOptions options, { String workingDirectory, @required ChromeErrorCallback onError }) async {
|
static Future<Chrome> launch(ChromeOptions options, { String? workingDirectory, required ChromeErrorCallback onError }) async {
|
||||||
if (!io.Platform.isWindows) {
|
if (!io.Platform.isWindows) {
|
||||||
final io.ProcessResult versionResult = io.Process.runSync(_findSystemChromeExecutable(), const <String>['--version']);
|
final io.ProcessResult versionResult = io.Process.runSync(_findSystemChromeExecutable(), const <String>['--version']);
|
||||||
print('Launching ${versionResult.stdout}');
|
print('Launching ${versionResult.stdout}');
|
||||||
@ -92,10 +89,10 @@ class Chrome {
|
|||||||
if (options.userDataDirectory != null)
|
if (options.userDataDirectory != null)
|
||||||
'--user-data-dir=${options.userDataDirectory}',
|
'--user-data-dir=${options.userDataDirectory}',
|
||||||
if (options.url != null)
|
if (options.url != null)
|
||||||
options.url,
|
options.url!,
|
||||||
if (io.Platform.environment['CHROME_NO_SANDBOX'] == 'true')
|
if (io.Platform.environment['CHROME_NO_SANDBOX'] == 'true')
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
if (options.headless)
|
if (options.headless == true)
|
||||||
'--headless',
|
'--headless',
|
||||||
if (withDebugging)
|
if (withDebugging)
|
||||||
'--remote-debugging-port=${options.debugPort}',
|
'--remote-debugging-port=${options.debugPort}',
|
||||||
@ -116,9 +113,9 @@ class Chrome {
|
|||||||
workingDirectory: workingDirectory,
|
workingDirectory: workingDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
WipConnection debugConnection;
|
WipConnection? debugConnection;
|
||||||
if (withDebugging) {
|
if (withDebugging) {
|
||||||
debugConnection = await _connectToChromeDebugPort(chromeProcess, options.debugPort);
|
debugConnection = await _connectToChromeDebugPort(chromeProcess, options.debugPort!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Chrome._(chromeProcess, onError, debugConnection);
|
return Chrome._(chromeProcess, onError, debugConnection);
|
||||||
@ -126,12 +123,12 @@ class Chrome {
|
|||||||
|
|
||||||
final io.Process _chromeProcess;
|
final io.Process _chromeProcess;
|
||||||
final ChromeErrorCallback _onError;
|
final ChromeErrorCallback _onError;
|
||||||
final WipConnection _debugConnection;
|
final WipConnection? _debugConnection;
|
||||||
bool _isStopped = false;
|
bool _isStopped = false;
|
||||||
|
|
||||||
Completer<void> _tracingCompleter;
|
Completer<void> ?_tracingCompleter;
|
||||||
StreamSubscription<WipEvent> _tracingSubscription;
|
StreamSubscription<WipEvent>? _tracingSubscription;
|
||||||
List<Map<String, dynamic>> _tracingData;
|
List<Map<String, dynamic>>? _tracingData;
|
||||||
|
|
||||||
/// Starts recording a performance trace.
|
/// Starts recording a performance trace.
|
||||||
///
|
///
|
||||||
@ -151,24 +148,24 @@ class Chrome {
|
|||||||
|
|
||||||
// Subscribe to tracing events prior to calling "Tracing.start". Otherwise,
|
// Subscribe to tracing events prior to calling "Tracing.start". Otherwise,
|
||||||
// we'll miss tracing data.
|
// we'll miss tracing data.
|
||||||
_tracingSubscription = _debugConnection.onNotification.listen((WipEvent event) {
|
_tracingSubscription = _debugConnection?.onNotification.listen((WipEvent event) {
|
||||||
// We receive data as a sequence of "Tracing.dataCollected" followed by
|
// We receive data as a sequence of "Tracing.dataCollected" followed by
|
||||||
// "Tracing.tracingComplete" at the end. Until "Tracing.tracingComplete"
|
// "Tracing.tracingComplete" at the end. Until "Tracing.tracingComplete"
|
||||||
// is received, the data may be incomplete.
|
// is received, the data may be incomplete.
|
||||||
if (event.method == 'Tracing.tracingComplete') {
|
if (event.method == 'Tracing.tracingComplete') {
|
||||||
_tracingCompleter.complete();
|
_tracingCompleter!.complete();
|
||||||
_tracingSubscription.cancel();
|
_tracingSubscription!.cancel();
|
||||||
_tracingSubscription = null;
|
_tracingSubscription = null;
|
||||||
} else if (event.method == 'Tracing.dataCollected') {
|
} else if (event.method == 'Tracing.dataCollected') {
|
||||||
final dynamic value = event.params['value'];
|
final dynamic value = event.params?['value'];
|
||||||
if (value is! List) {
|
if (value is! List) {
|
||||||
throw FormatException('"Tracing.dataCollected" returned malformed data. '
|
throw FormatException('"Tracing.dataCollected" returned malformed data. '
|
||||||
'Expected a List but got: ${value.runtimeType}');
|
'Expected a List but got: ${value.runtimeType}');
|
||||||
}
|
}
|
||||||
_tracingData.addAll((event.params['value'] as List<dynamic>).cast<Map<String, dynamic>>());
|
_tracingData?.addAll((event.params?['value'] as List<dynamic>).cast<Map<String, dynamic>>());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await _debugConnection.sendCommand('Tracing.start', <String, dynamic>{
|
await _debugConnection?.sendCommand('Tracing.start', <String, dynamic>{
|
||||||
// The choice of categories is as follows:
|
// The choice of categories is as follows:
|
||||||
//
|
//
|
||||||
// blink:
|
// blink:
|
||||||
@ -190,22 +187,23 @@ class Chrome {
|
|||||||
/// Stops a performance tracing session started by [beginRecordingPerformance].
|
/// Stops a performance tracing session started by [beginRecordingPerformance].
|
||||||
///
|
///
|
||||||
/// Returns all the collected tracing data unfiltered.
|
/// Returns all the collected tracing data unfiltered.
|
||||||
Future<List<Map<String, dynamic>>> endRecordingPerformance() async {
|
Future<List<Map<String, dynamic>>?> endRecordingPerformance() async {
|
||||||
await _debugConnection.sendCommand('Tracing.end');
|
await _debugConnection!.sendCommand('Tracing.end');
|
||||||
await _tracingCompleter.future;
|
await _tracingCompleter!.future;
|
||||||
final List<Map<String, dynamic>> data = _tracingData;
|
final List<Map<String, dynamic>>? data = _tracingData;
|
||||||
_tracingCompleter = null;
|
_tracingCompleter = null;
|
||||||
_tracingData = null;
|
_tracingData = null;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reloadPage({bool ignoreCache = false}) async {
|
Future<void> reloadPage({bool ignoreCache = false}) async {
|
||||||
await _debugConnection.page.reload(ignoreCache: ignoreCache);
|
await _debugConnection?.page.reload(ignoreCache: ignoreCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the Chrome process.
|
/// Stops the Chrome process.
|
||||||
void stop() {
|
void stop() {
|
||||||
_isStopped = true;
|
_isStopped = true;
|
||||||
|
_tracingSubscription?.cancel();
|
||||||
_chromeProcess.kill();
|
_chromeProcess.kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,7 +212,7 @@ String _findSystemChromeExecutable() {
|
|||||||
// On some environments, such as the Dart HHH tester, Chrome resides in a
|
// On some environments, such as the Dart HHH tester, Chrome resides in a
|
||||||
// non-standard location and is provided via the following environment
|
// non-standard location and is provided via the following environment
|
||||||
// variable.
|
// variable.
|
||||||
final String envExecutable = io.Platform.environment['CHROME_EXECUTABLE'];
|
final String? envExecutable = io.Platform.environment['CHROME_EXECUTABLE'];
|
||||||
if (envExecutable != null) {
|
if (envExecutable != null) {
|
||||||
return envExecutable;
|
return envExecutable;
|
||||||
}
|
}
|
||||||
@ -232,15 +230,12 @@ String _findSystemChromeExecutable() {
|
|||||||
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
||||||
} else if (io.Platform.isWindows) {
|
} else if (io.Platform.isWindows) {
|
||||||
const String kWindowsExecutable = r'Google\Chrome\Application\chrome.exe';
|
const String kWindowsExecutable = r'Google\Chrome\Application\chrome.exe';
|
||||||
final List<String> kWindowsPrefixes = <String>[
|
final List<String> kWindowsPrefixes = <String?>[
|
||||||
io.Platform.environment['LOCALAPPDATA'],
|
io.Platform.environment['LOCALAPPDATA'],
|
||||||
io.Platform.environment['PROGRAMFILES'],
|
io.Platform.environment['PROGRAMFILES'],
|
||||||
io.Platform.environment['PROGRAMFILES(X86)'],
|
io.Platform.environment['PROGRAMFILES(X86)'],
|
||||||
];
|
].whereType<String>().toList();
|
||||||
final String windowsPrefix = kWindowsPrefixes.firstWhere((String prefix) {
|
final String windowsPrefix = kWindowsPrefixes.firstWhere((String prefix) {
|
||||||
if (prefix == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final String expectedPath = path.join(prefix, kWindowsExecutable);
|
final String expectedPath = path.join(prefix, kWindowsExecutable);
|
||||||
return io.File(expectedPath).existsSync();
|
return io.File(expectedPath).existsSync();
|
||||||
}, orElse: () => '.');
|
}, orElse: () => '.');
|
||||||
@ -269,7 +264,7 @@ Future<Uri> _getRemoteDebuggerUrl(Uri base) async {
|
|||||||
final io.HttpClient client = io.HttpClient();
|
final io.HttpClient client = io.HttpClient();
|
||||||
final io.HttpClientRequest request = await client.getUrl(base.resolve('/json/list'));
|
final io.HttpClientRequest request = await client.getUrl(base.resolve('/json/list'));
|
||||||
final io.HttpClientResponse response = await request.close();
|
final io.HttpClientResponse response = await request.close();
|
||||||
final List<dynamic> jsonObject = await json.fuse(utf8).decoder.bind(response).single as List<dynamic>;
|
final List<dynamic>? jsonObject = await json.fuse(utf8).decoder.bind(response).single as List<dynamic>?;
|
||||||
if (jsonObject == null || jsonObject.isEmpty) {
|
if (jsonObject == null || jsonObject.isEmpty) {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
@ -279,17 +274,17 @@ Future<Uri> _getRemoteDebuggerUrl(Uri base) async {
|
|||||||
/// Summarizes a Blink trace down to a few interesting values.
|
/// Summarizes a Blink trace down to a few interesting values.
|
||||||
class BlinkTraceSummary {
|
class BlinkTraceSummary {
|
||||||
BlinkTraceSummary._({
|
BlinkTraceSummary._({
|
||||||
@required this.averageBeginFrameTime,
|
required this.averageBeginFrameTime,
|
||||||
@required this.averageUpdateLifecyclePhasesTime,
|
required this.averageUpdateLifecyclePhasesTime,
|
||||||
}) : averageTotalUIFrameTime = averageBeginFrameTime + averageUpdateLifecyclePhasesTime;
|
}) : averageTotalUIFrameTime = averageBeginFrameTime + averageUpdateLifecyclePhasesTime;
|
||||||
|
|
||||||
static BlinkTraceSummary fromJson(List<Map<String, dynamic>> traceJson) {
|
static BlinkTraceSummary? fromJson(List<Map<String, dynamic>> traceJson) {
|
||||||
try {
|
try {
|
||||||
// Convert raw JSON data to BlinkTraceEvent objects sorted by timestamp.
|
// Convert raw JSON data to BlinkTraceEvent objects sorted by timestamp.
|
||||||
List<BlinkTraceEvent> events = traceJson
|
List<BlinkTraceEvent> events = traceJson
|
||||||
.map<BlinkTraceEvent>(BlinkTraceEvent.fromJson)
|
.map<BlinkTraceEvent>(BlinkTraceEvent.fromJson)
|
||||||
.toList()
|
.toList()
|
||||||
..sort((BlinkTraceEvent a, BlinkTraceEvent b) => a.ts - b.ts);
|
..sort((BlinkTraceEvent a, BlinkTraceEvent b) => a.ts! - b.ts!);
|
||||||
|
|
||||||
Exception noMeasuredFramesFound() => Exception(
|
Exception noMeasuredFramesFound() => Exception(
|
||||||
'No measured frames found in benchmark tracing data. This likely '
|
'No measured frames found in benchmark tracing data. This likely '
|
||||||
@ -316,7 +311,7 @@ class BlinkTraceSummary {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int tabPid = firstMeasuredFrameEvent.pid;
|
final int tabPid = firstMeasuredFrameEvent.pid!;
|
||||||
|
|
||||||
// Filter out data from unrelated processes
|
// Filter out data from unrelated processes
|
||||||
events = events.where((BlinkTraceEvent element) => element.pid == tabPid).toList();
|
events = events.where((BlinkTraceEvent element) => element.pid == tabPid).toList();
|
||||||
@ -352,8 +347,8 @@ class BlinkTraceSummary {
|
|||||||
|
|
||||||
// Compute averages and summarize.
|
// Compute averages and summarize.
|
||||||
return BlinkTraceSummary._(
|
return BlinkTraceSummary._(
|
||||||
averageBeginFrameTime: _computeAverageDuration(frames.map((BlinkFrame frame) => frame.beginFrame).toList()),
|
averageBeginFrameTime: _computeAverageDuration(frames.map((BlinkFrame frame) => frame.beginFrame).whereType<BlinkTraceEvent>().toList()),
|
||||||
averageUpdateLifecyclePhasesTime: _computeAverageDuration(frames.map((BlinkFrame frame) => frame.updateAllLifecyclePhases).toList()),
|
averageUpdateLifecyclePhasesTime: _computeAverageDuration(frames.map((BlinkFrame frame) => frame.updateAllLifecyclePhases).whereType<BlinkTraceEvent>().toList()),
|
||||||
);
|
);
|
||||||
} catch (_, __) {
|
} catch (_, __) {
|
||||||
final io.File traceFile = io.File('./chrome-trace.json');
|
final io.File traceFile = io.File('./chrome-trace.json');
|
||||||
@ -392,16 +387,16 @@ class BlinkTraceSummary {
|
|||||||
/// Contains events pertaining to a single frame in the Blink trace data.
|
/// Contains events pertaining to a single frame in the Blink trace data.
|
||||||
class BlinkFrame {
|
class BlinkFrame {
|
||||||
/// Corresponds to 'WebViewImpl::beginFrame' event.
|
/// Corresponds to 'WebViewImpl::beginFrame' event.
|
||||||
BlinkTraceEvent beginFrame;
|
BlinkTraceEvent? beginFrame;
|
||||||
|
|
||||||
/// Corresponds to 'WebViewImpl::updateAllLifecyclePhases' event.
|
/// Corresponds to 'WebViewImpl::updateAllLifecyclePhases' event.
|
||||||
BlinkTraceEvent updateAllLifecyclePhases;
|
BlinkTraceEvent? updateAllLifecyclePhases;
|
||||||
|
|
||||||
/// Corresponds to 'measured_frame' begin event.
|
/// Corresponds to 'measured_frame' begin event.
|
||||||
BlinkTraceEvent beginMeasuredFrame;
|
BlinkTraceEvent? beginMeasuredFrame;
|
||||||
|
|
||||||
/// Corresponds to 'measured_frame' end event.
|
/// Corresponds to 'measured_frame' end event.
|
||||||
BlinkTraceEvent endMeasuredFrame;
|
BlinkTraceEvent? endMeasuredFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a list of events that have non-null [BlinkTraceEvent.tdur] computes
|
/// Takes a list of events that have non-null [BlinkTraceEvent.tdur] computes
|
||||||
@ -414,7 +409,7 @@ Duration _computeAverageDuration(List<BlinkTraceEvent> events) {
|
|||||||
if (event.tdur == null) {
|
if (event.tdur == null) {
|
||||||
throw FormatException('Trace event lacks "tdur" field: $event');
|
throw FormatException('Trace event lacks "tdur" field: $event');
|
||||||
}
|
}
|
||||||
return previousValue + event.tdur;
|
return previousValue + event.tdur!;
|
||||||
});
|
});
|
||||||
final int sampleCount = math.min(events.length, _kMeasuredSampleCount);
|
final int sampleCount = math.min(events.length, _kMeasuredSampleCount);
|
||||||
return Duration(microseconds: sum ~/ sampleCount);
|
return Duration(microseconds: sum ~/ sampleCount);
|
||||||
@ -426,15 +421,15 @@ Duration _computeAverageDuration(List<BlinkTraceEvent> events) {
|
|||||||
/// * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
|
/// * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
|
||||||
class BlinkTraceEvent {
|
class BlinkTraceEvent {
|
||||||
BlinkTraceEvent._({
|
BlinkTraceEvent._({
|
||||||
@required this.args,
|
required this.args,
|
||||||
@required this.cat,
|
required this.cat,
|
||||||
@required this.name,
|
required this.name,
|
||||||
@required this.ph,
|
required this.ph,
|
||||||
@required this.pid,
|
this.pid,
|
||||||
@required this.tid,
|
this.tid,
|
||||||
@required this.ts,
|
this.ts,
|
||||||
@required this.tts,
|
this.tts,
|
||||||
@required this.tdur,
|
this.tdur,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Parses an event from its JSON representation.
|
/// Parses an event from its JSON representation.
|
||||||
@ -488,19 +483,19 @@ class BlinkTraceEvent {
|
|||||||
final String ph;
|
final String ph;
|
||||||
|
|
||||||
/// Process ID of the process that emitted the event.
|
/// Process ID of the process that emitted the event.
|
||||||
final int pid;
|
final int? pid;
|
||||||
|
|
||||||
/// Thread ID of the thread that emitted the event.
|
/// Thread ID of the thread that emitted the event.
|
||||||
final int tid;
|
final int? tid;
|
||||||
|
|
||||||
/// Timestamp in microseconds using tracer clock.
|
/// Timestamp in microseconds using tracer clock.
|
||||||
final int ts;
|
final int? ts;
|
||||||
|
|
||||||
/// Timestamp in microseconds using thread clock.
|
/// Timestamp in microseconds using thread clock.
|
||||||
final int tts;
|
final int? tts;
|
||||||
|
|
||||||
/// Event duration in microseconds.
|
/// Event duration in microseconds.
|
||||||
final int tdur;
|
final int? tdur;
|
||||||
|
|
||||||
/// A "begin frame" event contains all of the scripting time of an animation
|
/// A "begin frame" event contains all of the scripting time of an animation
|
||||||
/// frame (JavaScript, WebAssembly), plus a negligible amount of internal
|
/// frame (JavaScript, WebAssembly), plus a negligible amount of internal
|
||||||
@ -556,7 +551,7 @@ class BlinkTraceEvent {
|
|||||||
/// validation and conversion is needed.
|
/// validation and conversion is needed.
|
||||||
///
|
///
|
||||||
/// Returns null if the value is null.
|
/// Returns null if the value is null.
|
||||||
int _readInt(Map<String, dynamic> json, String key) {
|
int? _readInt(Map<String, dynamic> json, String key) {
|
||||||
final num jsonValue = json[key] as num;
|
final num jsonValue = json[key] as num;
|
||||||
|
|
||||||
if (jsonValue == null) {
|
if (jsonValue == null) {
|
||||||
@ -578,10 +573,10 @@ int _readInt(Map<String, dynamic> json, String key) {
|
|||||||
/// Inconsistency detected by ld.so: ../elf/dl-tls.c: 493: _dl_allocate_tls_init: Assertion `listp->slotinfo[cnt].gen <= GL(dl_tls_generation)' failed!
|
/// Inconsistency detected by ld.so: ../elf/dl-tls.c: 493: _dl_allocate_tls_init: Assertion `listp->slotinfo[cnt].gen <= GL(dl_tls_generation)' failed!
|
||||||
const String _kGlibcError = 'Inconsistency detected by ld.so';
|
const String _kGlibcError = 'Inconsistency detected by ld.so';
|
||||||
|
|
||||||
Future<io.Process> _spawnChromiumProcess(String executable, List<String> args, { String workingDirectory }) async {
|
Future<io.Process> _spawnChromiumProcess(String executable, List<String> args, { String? workingDirectory }) async {
|
||||||
// Keep attempting to launch the browser until one of:
|
// Keep attempting to launch the browser until one of:
|
||||||
// - Chrome launched successfully, in which case we just return from the loop.
|
// - Chrome launched successfully, in which case we just return from the loop.
|
||||||
// - The tool detected an unretriable Chrome error, in which case we throw ToolExit.
|
// - The tool detected an unretryable Chrome error, in which case we throw ToolExit.
|
||||||
while (true) {
|
while (true) {
|
||||||
final io.Process process = await io.Process.start(executable, args, workingDirectory: workingDirectory);
|
final io.Process process = await io.Process.start(executable, args, workingDirectory: workingDirectory);
|
||||||
|
|
||||||
@ -611,7 +606,7 @@ Future<io.Process> _spawnChromiumProcess(String executable, List<String> args, {
|
|||||||
'Encountered glibc bug https://sourceware.org/bugzilla/show_bug.cgi?id=19329. '
|
'Encountered glibc bug https://sourceware.org/bugzilla/show_bug.cgi?id=19329. '
|
||||||
'Will try launching browser again.',
|
'Will try launching browser again.',
|
||||||
);
|
);
|
||||||
return null;
|
return '';
|
||||||
}
|
}
|
||||||
print('Failed to launch browser. Command used to launch it: ${args.join(' ')}');
|
print('Failed to launch browser. Command used to launch it: ${args.join(' ')}');
|
||||||
throw Exception(
|
throw Exception(
|
||||||
@ -630,7 +625,7 @@ Future<io.Process> _spawnChromiumProcess(String executable, List<String> args, {
|
|||||||
// launching more processes.
|
// launching more processes.
|
||||||
unawaited(process.exitCode.timeout(const Duration(seconds: 1), onTimeout: () {
|
unawaited(process.exitCode.timeout(const Duration(seconds: 1), onTimeout: () {
|
||||||
process.kill();
|
process.kill();
|
||||||
return null;
|
return 0;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert' show Encoding, json;
|
import 'dart:convert' show Encoding, json;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -20,12 +18,12 @@ import 'utils.dart';
|
|||||||
typedef ProcessRunSync = ProcessResult Function(
|
typedef ProcessRunSync = ProcessResult Function(
|
||||||
String,
|
String,
|
||||||
List<String>, {
|
List<String>, {
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
bool includeParentEnvironment,
|
bool includeParentEnvironment,
|
||||||
bool runInShell,
|
bool runInShell,
|
||||||
Encoding stderrEncoding,
|
Encoding? stderrEncoding,
|
||||||
Encoding stdoutEncoding,
|
Encoding? stdoutEncoding,
|
||||||
String workingDirectory,
|
String? workingDirectory,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Class for test runner to interact with Flutter's infrastructure service, Cocoon.
|
/// Class for test runner to interact with Flutter's infrastructure service, Cocoon.
|
||||||
@ -34,8 +32,8 @@ typedef ProcessRunSync = ProcessResult Function(
|
|||||||
/// To retrieve these results, the test runner needs to send results back so the database can be updated.
|
/// To retrieve these results, the test runner needs to send results back so the database can be updated.
|
||||||
class Cocoon {
|
class Cocoon {
|
||||||
Cocoon({
|
Cocoon({
|
||||||
String serviceAccountTokenPath,
|
String? serviceAccountTokenPath,
|
||||||
@visibleForTesting Client httpClient,
|
@visibleForTesting Client? httpClient,
|
||||||
@visibleForTesting this.fs = const LocalFileSystem(),
|
@visibleForTesting this.fs = const LocalFileSystem(),
|
||||||
@visibleForTesting this.processRunSync = Process.runSync,
|
@visibleForTesting this.processRunSync = Process.runSync,
|
||||||
@visibleForTesting this.requestRetryLimit = 5,
|
@visibleForTesting this.requestRetryLimit = 5,
|
||||||
@ -58,7 +56,7 @@ class Cocoon {
|
|||||||
final int requestRetryLimit;
|
final int requestRetryLimit;
|
||||||
|
|
||||||
String get commitSha => _commitSha ?? _readCommitSha();
|
String get commitSha => _commitSha ?? _readCommitSha();
|
||||||
String _commitSha;
|
String? _commitSha;
|
||||||
|
|
||||||
/// Parse the local repo for the current running commit.
|
/// Parse the local repo for the current running commit.
|
||||||
String _readCommitSha() {
|
String _readCommitSha() {
|
||||||
@ -85,9 +83,9 @@ class Cocoon {
|
|||||||
/// Send [TaskResult] to Cocoon.
|
/// Send [TaskResult] to Cocoon.
|
||||||
// TODO(chillers): Remove when sendResultsPath is used in prod. https://github.com/flutter/flutter/issues/72457
|
// TODO(chillers): Remove when sendResultsPath is used in prod. https://github.com/flutter/flutter/issues/72457
|
||||||
Future<void> sendTaskResult({
|
Future<void> sendTaskResult({
|
||||||
@required String builderName,
|
required String builderName,
|
||||||
@required TaskResult result,
|
required TaskResult result,
|
||||||
@required String gitBranch,
|
required String gitBranch,
|
||||||
}) async {
|
}) async {
|
||||||
assert(builderName != null);
|
assert(builderName != null);
|
||||||
assert(gitBranch != null);
|
assert(gitBranch != null);
|
||||||
@ -109,10 +107,10 @@ class Cocoon {
|
|||||||
|
|
||||||
/// Write the given parameters into an update task request and store the JSON in [resultsPath].
|
/// Write the given parameters into an update task request and store the JSON in [resultsPath].
|
||||||
Future<void> writeTaskResultToFile({
|
Future<void> writeTaskResultToFile({
|
||||||
@required String builderName,
|
required String builderName,
|
||||||
@required String gitBranch,
|
required String gitBranch,
|
||||||
@required TaskResult result,
|
required TaskResult result,
|
||||||
@required String resultsPath,
|
required String resultsPath,
|
||||||
}) async {
|
}) async {
|
||||||
assert(builderName != null);
|
assert(builderName != null);
|
||||||
assert(gitBranch != null);
|
assert(gitBranch != null);
|
||||||
@ -134,9 +132,9 @@ class Cocoon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _constructUpdateRequest({
|
Map<String, dynamic> _constructUpdateRequest({
|
||||||
@required String builderName,
|
String? builderName,
|
||||||
@required TaskResult result,
|
required TaskResult result,
|
||||||
@required String gitBranch,
|
required String gitBranch,
|
||||||
}) {
|
}) {
|
||||||
final Map<String, dynamic> updateRequest = <String, dynamic>{
|
final Map<String, dynamic> updateRequest = <String, dynamic>{
|
||||||
'CommitBranch': gitBranch,
|
'CommitBranch': gitBranch,
|
||||||
@ -151,12 +149,12 @@ class Cocoon {
|
|||||||
|
|
||||||
final List<String> validScoreKeys = <String>[];
|
final List<String> validScoreKeys = <String>[];
|
||||||
if (result.benchmarkScoreKeys != null) {
|
if (result.benchmarkScoreKeys != null) {
|
||||||
for (final String scoreKey in result.benchmarkScoreKeys) {
|
for (final String scoreKey in result.benchmarkScoreKeys!) {
|
||||||
final Object score = result.data[scoreKey];
|
final Object score = result.data![scoreKey] as Object;
|
||||||
if (score is num) {
|
if (score is num) {
|
||||||
// Convert all metrics to double, which provide plenty of precision
|
// Convert all metrics to double, which provide plenty of precision
|
||||||
// without having to add support for multiple numeric types in Cocoon.
|
// without having to add support for multiple numeric types in Cocoon.
|
||||||
result.data[scoreKey] = score.toDouble();
|
result.data![scoreKey] = score.toDouble();
|
||||||
validScoreKeys.add(scoreKey);
|
validScoreKeys.add(scoreKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,15 +193,15 @@ class Cocoon {
|
|||||||
class AuthenticatedCocoonClient extends BaseClient {
|
class AuthenticatedCocoonClient extends BaseClient {
|
||||||
AuthenticatedCocoonClient(
|
AuthenticatedCocoonClient(
|
||||||
this._serviceAccountTokenPath, {
|
this._serviceAccountTokenPath, {
|
||||||
@visibleForTesting Client httpClient,
|
@visibleForTesting Client? httpClient,
|
||||||
@visibleForTesting FileSystem filesystem,
|
@visibleForTesting FileSystem? filesystem,
|
||||||
}) : _delegate = httpClient ?? Client(),
|
}) : _delegate = httpClient ?? Client(),
|
||||||
_fs = filesystem ?? const LocalFileSystem();
|
_fs = filesystem ?? const LocalFileSystem();
|
||||||
|
|
||||||
/// Authentication token to have the ability to upload and record test results.
|
/// Authentication token to have the ability to upload and record test results.
|
||||||
///
|
///
|
||||||
/// This is intended to only be passed on automated runs on LUCI post-submit.
|
/// This is intended to only be passed on automated runs on LUCI post-submit.
|
||||||
final String _serviceAccountTokenPath;
|
final String? _serviceAccountTokenPath;
|
||||||
|
|
||||||
/// Underlying [HttpClient] to send requests to.
|
/// Underlying [HttpClient] to send requests to.
|
||||||
final Client _delegate;
|
final Client _delegate;
|
||||||
@ -213,7 +211,7 @@ class AuthenticatedCocoonClient extends BaseClient {
|
|||||||
|
|
||||||
/// Value contained in the service account token file that can be used in http requests.
|
/// Value contained in the service account token file that can be used in http requests.
|
||||||
String get serviceAccountToken => _serviceAccountToken ?? _readServiceAccountTokenFile();
|
String get serviceAccountToken => _serviceAccountToken ?? _readServiceAccountTokenFile();
|
||||||
String _serviceAccountToken;
|
String? _serviceAccountToken;
|
||||||
|
|
||||||
/// Get [serviceAccountToken] from the given service account file.
|
/// Get [serviceAccountToken] from the given service account file.
|
||||||
String _readServiceAccountTokenFile() {
|
String _readServiceAccountTokenFile() {
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter_devicelab/common.dart';
|
import 'package:flutter_devicelab/common.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
@ -37,8 +34,8 @@ String getArtifactPath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the item is in idList if find a match, otherwise return null
|
/// Return the item is in idList if find a match, otherwise return null
|
||||||
String _findMatchId(List<String> idList, String idPattern) {
|
String? _findMatchId(List<String> idList, String idPattern) {
|
||||||
String candidate;
|
String? candidate;
|
||||||
idPattern = idPattern.toLowerCase();
|
idPattern = idPattern.toLowerCase();
|
||||||
for(final String id in idList) {
|
for(final String id in idList) {
|
||||||
if (id.toLowerCase() == idPattern) {
|
if (id.toLowerCase() == idPattern) {
|
||||||
@ -184,47 +181,46 @@ enum AndroidCPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AndroidDeviceDiscovery implements DeviceDiscovery {
|
class AndroidDeviceDiscovery implements DeviceDiscovery {
|
||||||
factory AndroidDeviceDiscovery({AndroidCPU cpu}) {
|
factory AndroidDeviceDiscovery({AndroidCPU? cpu}) {
|
||||||
return _instance ??= AndroidDeviceDiscovery._(cpu);
|
return _instance ??= AndroidDeviceDiscovery._(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidDeviceDiscovery._(this.cpu);
|
AndroidDeviceDiscovery._(this.cpu);
|
||||||
|
|
||||||
final AndroidCPU cpu;
|
final AndroidCPU? cpu;
|
||||||
|
|
||||||
// Parses information about a device. Example:
|
// Parses information about a device. Example:
|
||||||
//
|
//
|
||||||
// 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper
|
// 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper
|
||||||
static final RegExp _kDeviceRegex = RegExp(r'^(\S+)\s+(\S+)(.*)');
|
static final RegExp _kDeviceRegex = RegExp(r'^(\S+)\s+(\S+)(.*)');
|
||||||
|
|
||||||
static AndroidDeviceDiscovery _instance;
|
static AndroidDeviceDiscovery? _instance;
|
||||||
|
|
||||||
AndroidDevice _workingDevice;
|
AndroidDevice? _workingDevice;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AndroidDevice> get workingDevice async {
|
Future<AndroidDevice> get workingDevice async {
|
||||||
if (_workingDevice == null) {
|
if (_workingDevice == null) {
|
||||||
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
||||||
final String deviceId = Platform.environment[DeviceIdEnvName];
|
final String deviceId = Platform.environment[DeviceIdEnvName]!;
|
||||||
await chooseWorkingDeviceById(deviceId);
|
await chooseWorkingDeviceById(deviceId);
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
await chooseWorkingDevice();
|
await chooseWorkingDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _matchesCPURequirement(AndroidDevice device) async {
|
Future<bool> _matchesCPURequirement(AndroidDevice device) async {
|
||||||
if (cpu == null)
|
|
||||||
return true;
|
|
||||||
switch (cpu) {
|
switch (cpu) {
|
||||||
|
case null:
|
||||||
|
return true;
|
||||||
case AndroidCPU.arm64:
|
case AndroidCPU.arm64:
|
||||||
return device.isArm64();
|
return device.isArm64();
|
||||||
case AndroidCPU.arm:
|
case AndroidCPU.arm:
|
||||||
return device.isArm();
|
return device.isArm();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Picks a random Android device out of connected devices and sets it as
|
/// Picks a random Android device out of connected devices and sets it as
|
||||||
@ -259,11 +255,11 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
||||||
final String matchedId = _findMatchId(await discoverDevices(), deviceId);
|
final String? matchedId = _findMatchId(await discoverDevices(), deviceId);
|
||||||
if (matchedId != null) {
|
if (matchedId != null) {
|
||||||
_workingDevice = AndroidDevice(deviceId: matchedId);
|
_workingDevice = AndroidDevice(deviceId: matchedId);
|
||||||
if (cpu != null) {
|
if (cpu != null) {
|
||||||
if (!await _matchesCPURequirement(_workingDevice)) {
|
if (!await _matchesCPURequirement(_workingDevice!)) {
|
||||||
throw DeviceException('The selected device $matchedId does not match the cpu requirement');
|
throw DeviceException('The selected device $matchedId does not match the cpu requirement');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,10 +286,10 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (_kDeviceRegex.hasMatch(line)) {
|
if (_kDeviceRegex.hasMatch(line)) {
|
||||||
final Match match = _kDeviceRegex.firstMatch(line);
|
final Match match = _kDeviceRegex.firstMatch(line)!;
|
||||||
|
|
||||||
final String deviceID = match[1];
|
final String deviceID = match[1]!;
|
||||||
final String deviceState = match[2];
|
final String deviceState = match[2]!;
|
||||||
|
|
||||||
if (!const <String>['unauthorized', 'offline'].contains(deviceState)) {
|
if (!const <String>['unauthorized', 'offline'].contains(deviceState)) {
|
||||||
results.add(deviceID);
|
results.add(deviceID);
|
||||||
@ -342,9 +338,9 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
FuchsiaDeviceDiscovery._();
|
FuchsiaDeviceDiscovery._();
|
||||||
|
|
||||||
static FuchsiaDeviceDiscovery _instance;
|
static FuchsiaDeviceDiscovery? _instance;
|
||||||
|
|
||||||
FuchsiaDevice _workingDevice;
|
FuchsiaDevice? _workingDevice;
|
||||||
|
|
||||||
String get _ffx {
|
String get _ffx {
|
||||||
final String ffx = path.join(getArtifactPath(), 'fuchsia', 'tools','x64', 'ffx');
|
final String ffx = path.join(getArtifactPath(), 'fuchsia', 'tools','x64', 'ffx');
|
||||||
@ -358,13 +354,13 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
|
|||||||
Future<FuchsiaDevice> get workingDevice async {
|
Future<FuchsiaDevice> get workingDevice async {
|
||||||
if (_workingDevice == null) {
|
if (_workingDevice == null) {
|
||||||
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
||||||
final String deviceId = Platform.environment[DeviceIdEnvName];
|
final String deviceId = Platform.environment[DeviceIdEnvName]!;
|
||||||
await chooseWorkingDeviceById(deviceId);
|
await chooseWorkingDeviceById(deviceId);
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
await chooseWorkingDevice();
|
await chooseWorkingDevice();
|
||||||
}
|
}
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Picks the first connected Fuchsia device.
|
/// Picks the first connected Fuchsia device.
|
||||||
@ -383,8 +379,8 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
||||||
final String matchedId = _findMatchId(await discoverDevices(), deviceId);
|
final String? matchedId = _findMatchId(await discoverDevices(), deviceId);
|
||||||
if (deviceId != null) {
|
if (matchedId != null) {
|
||||||
_workingDevice = FuchsiaDevice(deviceId: matchedId);
|
_workingDevice = FuchsiaDevice(deviceId: matchedId);
|
||||||
print('Choose device by ID: $matchedId');
|
print('Choose device by ID: $matchedId');
|
||||||
return;
|
return;
|
||||||
@ -442,7 +438,7 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AndroidDevice extends Device {
|
class AndroidDevice extends Device {
|
||||||
AndroidDevice({@required this.deviceId}) {
|
AndroidDevice({required this.deviceId}) {
|
||||||
_updateDeviceInfo();
|
_updateDeviceInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,19 +536,19 @@ class AndroidDevice extends Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes [command] on `adb shell` and returns its exit code.
|
/// Executes [command] on `adb shell` and returns its exit code.
|
||||||
Future<void> shellExec(String command, List<String> arguments, { Map<String, String> environment, bool silent = false }) async {
|
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);
|
await adb(<String>['shell', command, ...arguments], environment: environment, silent: silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes [command] on `adb shell` and returns its standard output as a [String].
|
/// Executes [command] on `adb shell` and returns its standard output as a [String].
|
||||||
Future<String> shellEval(String command, List<String> arguments, { Map<String, String> environment, bool silent = false }) {
|
Future<String> shellEval(String command, List<String> arguments, { Map<String, String>? environment, bool silent = false }) {
|
||||||
return adb(<String>['shell', command, ...arguments], environment: environment, silent: silent);
|
return adb(<String>['shell', command, ...arguments], environment: environment, silent: silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs `adb` with the given [arguments], selecting this device.
|
/// Runs `adb` with the given [arguments], selecting this device.
|
||||||
Future<String> adb(
|
Future<String> adb(
|
||||||
List<String> arguments, {
|
List<String> arguments, {
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
bool silent = false,
|
bool silent = false,
|
||||||
}) {
|
}) {
|
||||||
return eval(
|
return eval(
|
||||||
@ -568,10 +564,10 @@ class AndroidDevice extends Device {
|
|||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> getMemoryStats(String packageName) async {
|
Future<Map<String, dynamic>> getMemoryStats(String packageName) async {
|
||||||
final String meminfo = await shellEval('dumpsys', <String>['meminfo', packageName]);
|
final String meminfo = await shellEval('dumpsys', <String>['meminfo', packageName]);
|
||||||
final Match match = RegExp(r'TOTAL\s+(\d+)').firstMatch(meminfo);
|
final Match? match = RegExp(r'TOTAL\s+(\d+)').firstMatch(meminfo);
|
||||||
assert(match != null, 'could not parse dumpsys meminfo output');
|
assert(match != null, 'could not parse dumpsys meminfo output');
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'total_kb': int.parse(match.group(1)),
|
'total_kb': int.parse(match!.group(1)!),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +575,7 @@ class AndroidDevice extends Device {
|
|||||||
bool get canStreamLogs => true;
|
bool get canStreamLogs => true;
|
||||||
|
|
||||||
bool _abortedLogging/*!*/ = false;
|
bool _abortedLogging/*!*/ = false;
|
||||||
Process/*?*/ _loggingProcess;
|
Process? _loggingProcess;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> startLoggingToSink(IOSink sink, {bool clear = true}) async {
|
Future<void> startLoggingToSink(IOSink sink, {bool clear = true}) async {
|
||||||
@ -596,17 +592,17 @@ class AndroidDevice extends Device {
|
|||||||
// to view the whole log, or just run logcat alongside this.
|
// to view the whole log, or just run logcat alongside this.
|
||||||
<String>['-s', deviceId, 'logcat', 'ActivityManager:I', 'flutter:V', '*:F'],
|
<String>['-s', deviceId, 'logcat', 'ActivityManager:I', 'flutter:V', '*:F'],
|
||||||
);
|
);
|
||||||
_loggingProcess.stdout
|
_loggingProcess!.stdout
|
||||||
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
||||||
.listen((String line) {
|
.listen((String line) {
|
||||||
sink.write(line);
|
sink.write(line);
|
||||||
});
|
});
|
||||||
_loggingProcess.stderr
|
_loggingProcess!.stderr
|
||||||
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
||||||
.listen((String line) {
|
.listen((String line) {
|
||||||
sink.write(line);
|
sink.write(line);
|
||||||
});
|
});
|
||||||
unawaited(_loggingProcess.exitCode.then<void>((int exitCode) {
|
unawaited(_loggingProcess!.exitCode.then<void>((int exitCode) {
|
||||||
if (!_abortedLogging) {
|
if (!_abortedLogging) {
|
||||||
sink.writeln('adb logcat failed with exit code $exitCode.\n');
|
sink.writeln('adb logcat failed with exit code $exitCode.\n');
|
||||||
}
|
}
|
||||||
@ -617,8 +613,8 @@ class AndroidDevice extends Device {
|
|||||||
Future<void> stopLoggingToSink() async {
|
Future<void> stopLoggingToSink() async {
|
||||||
if (_loggingProcess != null) {
|
if (_loggingProcess != null) {
|
||||||
_abortedLogging = true;
|
_abortedLogging = true;
|
||||||
_loggingProcess.kill();
|
_loggingProcess!.kill();
|
||||||
await _loggingProcess.exitCode;
|
await _loggingProcess!.exitCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,7 +625,7 @@ class AndroidDevice extends Device {
|
|||||||
final Completer<void> processDone = Completer<void>();
|
final Completer<void> processDone = Completer<void>();
|
||||||
final Completer<void> abort = Completer<void>();
|
final Completer<void> abort = Completer<void>();
|
||||||
bool aborted = false;
|
bool aborted = false;
|
||||||
StreamController<String> stream;
|
late final StreamController<String> stream;
|
||||||
stream = StreamController<String>(
|
stream = StreamController<String>(
|
||||||
onListen: () async {
|
onListen: () async {
|
||||||
await adb(<String>['logcat', '--clear']);
|
await adb(<String>['logcat', '--clear']);
|
||||||
@ -713,22 +709,22 @@ class IosDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
IosDeviceDiscovery._();
|
IosDeviceDiscovery._();
|
||||||
|
|
||||||
static IosDeviceDiscovery _instance;
|
static IosDeviceDiscovery? _instance;
|
||||||
|
|
||||||
IosDevice _workingDevice;
|
IosDevice? _workingDevice;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<IosDevice> get workingDevice async {
|
Future<IosDevice> get workingDevice async {
|
||||||
if (_workingDevice == null) {
|
if (_workingDevice == null) {
|
||||||
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
||||||
final String deviceId = Platform.environment[DeviceIdEnvName];
|
final String deviceId = Platform.environment[DeviceIdEnvName]!;
|
||||||
await chooseWorkingDeviceById(deviceId);
|
await chooseWorkingDeviceById(deviceId);
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
await chooseWorkingDevice();
|
await chooseWorkingDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Picks a random iOS device out of connected devices and sets it as
|
/// Picks a random iOS device out of connected devices and sets it as
|
||||||
@ -749,7 +745,7 @@ class IosDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
||||||
final String matchedId = _findMatchId(await discoverDevices(), deviceId);
|
final String? matchedId = _findMatchId(await discoverDevices(), deviceId);
|
||||||
if (matchedId != null) {
|
if (matchedId != null) {
|
||||||
_workingDevice = IosDevice(deviceId: matchedId);
|
_workingDevice = IosDevice(deviceId: matchedId);
|
||||||
print('Choose device by ID: $matchedId');
|
print('Choose device by ID: $matchedId');
|
||||||
@ -824,7 +820,7 @@ class IosDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
/// iOS device.
|
/// iOS device.
|
||||||
class IosDevice extends Device {
|
class IosDevice extends Device {
|
||||||
IosDevice({ @required this.deviceId });
|
IosDevice({ required this.deviceId });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
@ -846,8 +842,8 @@ class IosDevice extends Device {
|
|||||||
@override
|
@override
|
||||||
bool get canStreamLogs => true;
|
bool get canStreamLogs => true;
|
||||||
|
|
||||||
bool _abortedLogging/*!*/ = false;
|
bool _abortedLogging = false;
|
||||||
Process/*?*/ _loggingProcess;
|
Process? _loggingProcess;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> startLoggingToSink(IOSink sink, {bool clear = true}) async {
|
Future<void> startLoggingToSink(IOSink sink, {bool clear = true}) async {
|
||||||
@ -859,17 +855,17 @@ class IosDevice extends Device {
|
|||||||
'DYLD_LIBRARY_PATH': dyldLibraryPath,
|
'DYLD_LIBRARY_PATH': dyldLibraryPath,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
_loggingProcess.stdout
|
_loggingProcess!.stdout
|
||||||
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
||||||
.listen((String line) {
|
.listen((String line) {
|
||||||
sink.write(line);
|
sink.write(line);
|
||||||
});
|
});
|
||||||
_loggingProcess.stderr
|
_loggingProcess!.stderr
|
||||||
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
.transform<String>(const Utf8Decoder(allowMalformed: true))
|
||||||
.listen((String line) {
|
.listen((String line) {
|
||||||
sink.write(line);
|
sink.write(line);
|
||||||
});
|
});
|
||||||
unawaited(_loggingProcess.exitCode.then<void>((int exitCode) {
|
unawaited(_loggingProcess!.exitCode.then<void>((int exitCode) {
|
||||||
if (!_abortedLogging) {
|
if (!_abortedLogging) {
|
||||||
sink.writeln('idevicesyslog failed with exit code $exitCode.\n');
|
sink.writeln('idevicesyslog failed with exit code $exitCode.\n');
|
||||||
}
|
}
|
||||||
@ -880,8 +876,8 @@ class IosDevice extends Device {
|
|||||||
Future<void> stopLoggingToSink() async {
|
Future<void> stopLoggingToSink() async {
|
||||||
if (_loggingProcess != null) {
|
if (_loggingProcess != null) {
|
||||||
_abortedLogging = true;
|
_abortedLogging = true;
|
||||||
_loggingProcess.kill();
|
_loggingProcess!.kill();
|
||||||
await _loggingProcess.exitCode;
|
await _loggingProcess!.exitCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -934,7 +930,7 @@ class IosDevice extends Device {
|
|||||||
|
|
||||||
/// Fuchsia device.
|
/// Fuchsia device.
|
||||||
class FuchsiaDevice extends Device {
|
class FuchsiaDevice extends Device {
|
||||||
const FuchsiaDevice({ @required this.deviceId });
|
const FuchsiaDevice({ required this.deviceId });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
@ -982,7 +978,7 @@ class FuchsiaDevice extends Device {
|
|||||||
|
|
||||||
/// Path to the `adb` executable.
|
/// Path to the `adb` executable.
|
||||||
String get adbPath {
|
String get adbPath {
|
||||||
final String androidHome = Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT'];
|
final String? androidHome = Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT'];
|
||||||
|
|
||||||
if (androidHome == null) {
|
if (androidHome == null) {
|
||||||
throw const DeviceException(
|
throw const DeviceException(
|
||||||
@ -1001,7 +997,7 @@ String get adbPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FakeDevice extends Device {
|
class FakeDevice extends Device {
|
||||||
const FakeDevice({ @required this.deviceId });
|
const FakeDevice({ required this.deviceId });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
@ -1055,22 +1051,22 @@ class FakeDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
FakeDeviceDiscovery._();
|
FakeDeviceDiscovery._();
|
||||||
|
|
||||||
static FakeDeviceDiscovery _instance;
|
static FakeDeviceDiscovery? _instance;
|
||||||
|
|
||||||
FakeDevice _workingDevice;
|
FakeDevice? _workingDevice;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FakeDevice> get workingDevice async {
|
Future<FakeDevice> get workingDevice async {
|
||||||
if (_workingDevice == null) {
|
if (_workingDevice == null) {
|
||||||
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
if (Platform.environment.containsKey(DeviceIdEnvName)) {
|
||||||
final String deviceId = Platform.environment[DeviceIdEnvName];
|
final String deviceId = Platform.environment[DeviceIdEnvName]!;
|
||||||
await chooseWorkingDeviceById(deviceId);
|
await chooseWorkingDeviceById(deviceId);
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
await chooseWorkingDevice();
|
await chooseWorkingDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _workingDevice;
|
return _workingDevice!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Fake is only available for by ID device discovery.
|
/// The Fake is only available for by ID device discovery.
|
||||||
@ -1081,7 +1077,7 @@ class FakeDeviceDiscovery implements DeviceDiscovery {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
Future<void> chooseWorkingDeviceById(String deviceId) async {
|
||||||
final String matchedId = _findMatchId(await discoverDevices(), deviceId);
|
final String? matchedId = _findMatchId(await discoverDevices(), deviceId);
|
||||||
if (matchedId != null) {
|
if (matchedId != null) {
|
||||||
_workingDevice = FakeDevice(deviceId: matchedId);
|
_workingDevice = FakeDevice(deviceId: matchedId);
|
||||||
print('Choose device by ID: $matchedId');
|
print('Choose device by ID: $matchedId');
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
@ -64,8 +62,8 @@ class _TaskRunner {
|
|||||||
_TaskRunner(this.task) {
|
_TaskRunner(this.task) {
|
||||||
registerExtension('ext.cocoonRunTask',
|
registerExtension('ext.cocoonRunTask',
|
||||||
(String method, Map<String, String> parameters) async {
|
(String method, Map<String, String> parameters) async {
|
||||||
final Duration taskTimeout = parameters.containsKey('timeoutInMinutes')
|
final Duration? taskTimeout = parameters.containsKey('timeoutInMinutes')
|
||||||
? Duration(minutes: int.parse(parameters['timeoutInMinutes']))
|
? Duration(minutes: int.parse(parameters['timeoutInMinutes']!))
|
||||||
: null;
|
: null;
|
||||||
// This is only expected to be passed in unit test runs so they do not
|
// This is only expected to be passed in unit test runs so they do not
|
||||||
// kill the Dart process that is running them and waste time running config.
|
// kill the Dart process that is running them and waste time running config.
|
||||||
@ -82,7 +80,7 @@ class _TaskRunner {
|
|||||||
|
|
||||||
final TaskFunction task;
|
final TaskFunction task;
|
||||||
|
|
||||||
Future<Device/*?*/> _getWorkingDeviceIfAvailable() async {
|
Future<Device?> _getWorkingDeviceIfAvailable() async {
|
||||||
try {
|
try {
|
||||||
return await devices.workingDevice;
|
return await devices.workingDevice;
|
||||||
} on DeviceException {
|
} on DeviceException {
|
||||||
@ -91,8 +89,8 @@ class _TaskRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ianh): workaround for https://github.com/dart-lang/sdk/issues/23797
|
// TODO(ianh): workaround for https://github.com/dart-lang/sdk/issues/23797
|
||||||
RawReceivePort _keepAlivePort;
|
RawReceivePort? _keepAlivePort;
|
||||||
Timer _startTaskTimeout;
|
Timer? _startTaskTimeout;
|
||||||
bool _taskStarted = false;
|
bool _taskStarted = false;
|
||||||
|
|
||||||
final Completer<TaskResult> _completer = Completer<TaskResult>();
|
final Completer<TaskResult> _completer = Completer<TaskResult>();
|
||||||
@ -102,7 +100,7 @@ class _TaskRunner {
|
|||||||
/// Signals that this task runner finished running the task.
|
/// Signals that this task runner finished running the task.
|
||||||
Future<TaskResult> get whenDone => _completer.future;
|
Future<TaskResult> get whenDone => _completer.future;
|
||||||
|
|
||||||
Future<TaskResult> run(Duration taskTimeout, {
|
Future<TaskResult> run(Duration? taskTimeout, {
|
||||||
bool runFlutterConfig = true,
|
bool runFlutterConfig = true,
|
||||||
bool runProcessCleanup = true,
|
bool runProcessCleanup = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -110,7 +108,7 @@ class _TaskRunner {
|
|||||||
_taskStarted = true;
|
_taskStarted = true;
|
||||||
print('Running task with a timeout of $taskTimeout.');
|
print('Running task with a timeout of $taskTimeout.');
|
||||||
final String exe = Platform.isWindows ? '.exe' : '';
|
final String exe = Platform.isWindows ? '.exe' : '';
|
||||||
Set<RunningProcessInfo> beforeRunningDartInstances;
|
late Set<RunningProcessInfo> beforeRunningDartInstances;
|
||||||
if (runProcessCleanup) {
|
if (runProcessCleanup) {
|
||||||
section('Checking running Dart$exe processes');
|
section('Checking running Dart$exe processes');
|
||||||
beforeRunningDartInstances = await getRunningProcesses(
|
beforeRunningDartInstances = await getRunningProcesses(
|
||||||
@ -136,7 +134,7 @@ class _TaskRunner {
|
|||||||
'--enable-windows-desktop',
|
'--enable-windows-desktop',
|
||||||
'--enable-linux-desktop',
|
'--enable-linux-desktop',
|
||||||
'--enable-web',
|
'--enable-web',
|
||||||
if (localEngine != null) ...<String>['--local-engine', localEngine],
|
if (localEngine != null) ...<String>['--local-engine', localEngine!],
|
||||||
], canFail: true);
|
], canFail: true);
|
||||||
if (configResult != 0) {
|
if (configResult != 0) {
|
||||||
print('Failed to enable configuration, tasks may not run.');
|
print('Failed to enable configuration, tasks may not run.');
|
||||||
@ -145,12 +143,12 @@ class _TaskRunner {
|
|||||||
print('Skipping enabling configs for macOS, Linux, Windows, and Web');
|
print('Skipping enabling configs for macOS, Linux, Windows, and Web');
|
||||||
}
|
}
|
||||||
|
|
||||||
final Device/*?*/ device = await _getWorkingDeviceIfAvailable();
|
final Device? device = await _getWorkingDeviceIfAvailable();
|
||||||
/*late*/ TaskResult result;
|
late TaskResult result;
|
||||||
IOSink/*?*/ sink;
|
IOSink? sink;
|
||||||
try {
|
try {
|
||||||
if (device != null && device.canStreamLogs && hostAgent.dumpDirectory != null) {
|
if (device != null && device.canStreamLogs && hostAgent.dumpDirectory != null) {
|
||||||
sink = File(path.join(hostAgent.dumpDirectory.path, '${device.deviceId}.log')).openWrite();
|
sink = File(path.join(hostAgent.dumpDirectory!.path, '${device.deviceId}.log')).openWrite();
|
||||||
await device.startLoggingToSink(sink);
|
await device.startLoggingToSink(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +210,7 @@ class _TaskRunner {
|
|||||||
final File rebootFile = _rebootFile();
|
final File rebootFile = _rebootFile();
|
||||||
int runCount;
|
int runCount;
|
||||||
if (rebootFile.existsSync()) {
|
if (rebootFile.existsSync()) {
|
||||||
runCount = int.tryParse(rebootFile.readAsStringSync().trim());
|
runCount = int.tryParse(rebootFile.readAsStringSync().trim()) ?? 0;
|
||||||
} else {
|
} else {
|
||||||
runCount = 0;
|
runCount = 0;
|
||||||
}
|
}
|
||||||
@ -283,10 +281,10 @@ class _TaskRunner {
|
|||||||
|
|
||||||
File _rebootFile() {
|
File _rebootFile() {
|
||||||
if (Platform.isLinux || Platform.isMacOS) {
|
if (Platform.isLinux || Platform.isMacOS) {
|
||||||
return File(path.join(Platform.environment['HOME'], '.reboot-count'));
|
return File(path.join(Platform.environment['HOME']!, '.reboot-count'));
|
||||||
}
|
}
|
||||||
if (!Platform.isWindows) {
|
if (!Platform.isWindows) {
|
||||||
throw StateError('Unexpected platform ${Platform.operatingSystem}');
|
throw StateError('Unexpected platform ${Platform.operatingSystem}');
|
||||||
}
|
}
|
||||||
return File(path.join(Platform.environment['USERPROFILE'], '.reboot-count'));
|
return File(path.join(Platform.environment['USERPROFILE']!, '.reboot-count'));
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
@ -42,17 +40,10 @@ Future<bool> containsBitcode(String pathToBinary) async {
|
|||||||
final List<String> lines = LineSplitter.split(loadCommands).toList();
|
final List<String> lines = LineSplitter.split(loadCommands).toList();
|
||||||
lines.asMap().forEach((int index, String line) {
|
lines.asMap().forEach((int index, String line) {
|
||||||
if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
|
if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
|
||||||
final String emptyBitcodeMarker = lines
|
emptyBitcodeMarkerFound |= lines
|
||||||
.skip(index - 1)
|
.skip(index - 1)
|
||||||
.take(4)
|
.take(4)
|
||||||
.firstWhere(
|
.any((String line) => line.contains(' size 0x0000000000000001'));
|
||||||
(String line) => line.contains(' size 0x0000000000000001'),
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (emptyBitcodeMarker != null) {
|
|
||||||
emptyBitcodeMarkerFound = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return !emptyBitcodeMarkerFound;
|
return !emptyBitcodeMarkerFound;
|
||||||
@ -79,16 +70,16 @@ Future<void> testWithNewIOSSimulator(
|
|||||||
workingDirectory: flutterDirectory.path,
|
workingDirectory: flutterDirectory.path,
|
||||||
);
|
);
|
||||||
|
|
||||||
String iOSSimRuntime;
|
String? iOSSimRuntime;
|
||||||
|
|
||||||
final RegExp iOSRuntimePattern = RegExp(r'iOS .*\) - (.*)');
|
final RegExp iOSRuntimePattern = RegExp(r'iOS .*\) - (.*)');
|
||||||
|
|
||||||
for (final String runtime in LineSplitter.split(availableRuntimes)) {
|
for (final String runtime in LineSplitter.split(availableRuntimes)) {
|
||||||
// These seem to be in order, so allow matching multiple lines so it grabs
|
// These seem to be in order, so allow matching multiple lines so it grabs
|
||||||
// the last (hopefully latest) one.
|
// the last (hopefully latest) one.
|
||||||
final RegExpMatch iOSRuntimeMatch = iOSRuntimePattern.firstMatch(runtime);
|
final RegExpMatch? iOSRuntimeMatch = iOSRuntimePattern.firstMatch(runtime);
|
||||||
if (iOSRuntimeMatch != null) {
|
if (iOSRuntimeMatch != null) {
|
||||||
iOSSimRuntime = iOSRuntimeMatch.group(1).trim();
|
iOSSimRuntime = iOSRuntimeMatch.group(1)!.trim();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,14 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
|
|
||||||
/// Loads manifest data from `manifest.yaml` file or from [yaml], if present.
|
/// Loads manifest data from `manifest.yaml` file or from [yaml], if present.
|
||||||
Manifest loadTaskManifest([ String yaml ]) {
|
Manifest loadTaskManifest([ String? yaml ]) {
|
||||||
final dynamic manifestYaml = yaml == null
|
final dynamic manifestYaml = yaml == null
|
||||||
? loadYaml(file('manifest.yaml').readAsStringSync())
|
? loadYaml(file('manifest.yaml').readAsStringSync())
|
||||||
: loadYamlNode(yaml);
|
: loadYamlNode(yaml);
|
||||||
@ -32,13 +29,13 @@ class Manifest {
|
|||||||
/// A CI task.
|
/// A CI task.
|
||||||
class ManifestTask {
|
class ManifestTask {
|
||||||
ManifestTask._({
|
ManifestTask._({
|
||||||
@required this.name,
|
required this.name,
|
||||||
@required this.description,
|
required this.description,
|
||||||
@required this.stage,
|
required this.stage,
|
||||||
@required this.requiredAgentCapabilities,
|
required this.requiredAgentCapabilities,
|
||||||
@required this.isFlaky,
|
required this.isFlaky,
|
||||||
@required this.timeoutInMinutes,
|
required this.timeoutInMinutes,
|
||||||
@required this.onLuci,
|
required this.onLuci,
|
||||||
}) {
|
}) {
|
||||||
final String taskName = 'task "$name"';
|
final String taskName = 'task "$name"';
|
||||||
_checkIsNotBlank(name, 'Task name', taskName);
|
_checkIsNotBlank(name, 'Task name', taskName);
|
||||||
@ -148,9 +145,9 @@ ManifestTask _validateAndParseTask(String taskName, dynamic taskYaml) {
|
|||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
stage: taskYaml['stage'] as String,
|
stage: taskYaml['stage'] as String,
|
||||||
requiredAgentCapabilities: capabilities as List<String>,
|
requiredAgentCapabilities: capabilities as List<String>,
|
||||||
isFlaky: isFlaky as bool ?? false,
|
isFlaky: isFlaky as bool,
|
||||||
timeoutInMinutes: timeoutInMinutes as int,
|
timeoutInMinutes: timeoutInMinutes as int,
|
||||||
onLuci: onLuci as bool ?? false,
|
onLuci: onLuci as bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +158,7 @@ List<String> _validateAndParseCapabilities(String taskName, dynamic capabilities
|
|||||||
final dynamic capability = capabilities[i];
|
final dynamic capability = capabilities[i];
|
||||||
_checkType(capability is String, capability, 'required_agent_capabilities[$i]', 'string');
|
_checkType(capability is String, capability, 'required_agent_capabilities[$i]', 'string');
|
||||||
}
|
}
|
||||||
return (capabilitiesYaml as List<dynamic>).cast<String>();
|
return capabilitiesYaml.cast<String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkType(bool isValid, dynamic value, String variableName, String typeName) {
|
void _checkType(bool isValid, dynamic value, String variableName, String typeName) {
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -21,13 +19,13 @@ Future<void> runTasks(
|
|||||||
List<String> taskNames, {
|
List<String> taskNames, {
|
||||||
bool exitOnFirstTestFailure = false,
|
bool exitOnFirstTestFailure = false,
|
||||||
bool silent = false,
|
bool silent = false,
|
||||||
String deviceId,
|
String? deviceId,
|
||||||
String gitBranch,
|
String? gitBranch,
|
||||||
String localEngine,
|
String? localEngine,
|
||||||
String localEngineSrcPath,
|
String? localEngineSrcPath,
|
||||||
String luciBuilder,
|
String? luciBuilder,
|
||||||
String resultsPath,
|
String? resultsPath,
|
||||||
List<String> taskArgs,
|
List<String>? taskArgs,
|
||||||
}) async {
|
}) async {
|
||||||
for (final String taskName in taskNames) {
|
for (final String taskName in taskNames) {
|
||||||
section('Running task "$taskName"');
|
section('Running task "$taskName"');
|
||||||
@ -44,10 +42,10 @@ Future<void> runTasks(
|
|||||||
print(const JsonEncoder.withIndent(' ').convert(result));
|
print(const JsonEncoder.withIndent(' ').convert(result));
|
||||||
section('Finished task "$taskName"');
|
section('Finished task "$taskName"');
|
||||||
|
|
||||||
if (resultsPath != null) {
|
if (resultsPath != null && gitBranch != null) {
|
||||||
final Cocoon cocoon = Cocoon();
|
final Cocoon cocoon = Cocoon();
|
||||||
await cocoon.writeTaskResultToFile(
|
await cocoon.writeTaskResultToFile(
|
||||||
builderName: luciBuilder,
|
builderName: luciBuilder!,
|
||||||
gitBranch: gitBranch,
|
gitBranch: gitBranch,
|
||||||
result: result,
|
result: result,
|
||||||
resultsPath: resultsPath,
|
resultsPath: resultsPath,
|
||||||
@ -76,11 +74,11 @@ Future<void> runTasks(
|
|||||||
Future<TaskResult> runTask(
|
Future<TaskResult> runTask(
|
||||||
String taskName, {
|
String taskName, {
|
||||||
bool silent = false,
|
bool silent = false,
|
||||||
String localEngine,
|
String? localEngine,
|
||||||
String localEngineSrcPath,
|
String? localEngineSrcPath,
|
||||||
String deviceId,
|
String? deviceId,
|
||||||
List<String> taskArgs,
|
List<String> ?taskArgs,
|
||||||
@visibleForTesting Map<String, String> isolateParams,
|
@visibleForTesting Map<String, String>? isolateParams,
|
||||||
}) async {
|
}) async {
|
||||||
final String taskExecutable = 'bin/tasks/$taskName.dart';
|
final String taskExecutable = 'bin/tasks/$taskName.dart';
|
||||||
|
|
||||||
@ -117,7 +115,7 @@ Future<TaskResult> runTask(
|
|||||||
.transform<String>(const LineSplitter())
|
.transform<String>(const LineSplitter())
|
||||||
.listen((String line) {
|
.listen((String line) {
|
||||||
if (!uri.isCompleted) {
|
if (!uri.isCompleted) {
|
||||||
final Uri serviceUri = parseServiceUri(line, prefix: 'Observatory listening on ');
|
final Uri? serviceUri = parseServiceUri(line, prefix: 'Observatory listening on ');
|
||||||
if (serviceUri != null)
|
if (serviceUri != null)
|
||||||
uri.complete(serviceUri);
|
uri.complete(serviceUri);
|
||||||
}
|
}
|
||||||
@ -139,7 +137,7 @@ Future<TaskResult> runTask(
|
|||||||
'ext.cocoonRunTask',
|
'ext.cocoonRunTask',
|
||||||
args: isolateParams,
|
args: isolateParams,
|
||||||
isolateId: result.isolate.id,
|
isolateId: result.isolate.id,
|
||||||
)).json;
|
)).json!;
|
||||||
final TaskResult taskResult = TaskResult.fromJson(taskResultJson);
|
final TaskResult taskResult = TaskResult.fromJson(taskResultJson);
|
||||||
await runner.exitCode;
|
await runner.exitCode;
|
||||||
return taskResult;
|
return taskResult;
|
||||||
@ -168,13 +166,13 @@ Future<ConnectionResult> _connectToRunnerIsolate(Uri vmServiceUri) async {
|
|||||||
// Look up the isolate.
|
// Look up the isolate.
|
||||||
final VmService client = await vmServiceConnectUri(url);
|
final VmService client = await vmServiceConnectUri(url);
|
||||||
VM vm = await client.getVM();
|
VM vm = await client.getVM();
|
||||||
while (vm.isolates.isEmpty) {
|
while (vm.isolates!.isEmpty) {
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
vm = await client.getVM();
|
vm = await client.getVM();
|
||||||
}
|
}
|
||||||
final IsolateRef isolate = vm.isolates.first;
|
final IsolateRef isolate = vm.isolates!.first;
|
||||||
final Response response = await client.callServiceExtension('ext.cocoonRunnerReady', isolateId: isolate.id);
|
final Response response = await client.callServiceExtension('ext.cocoonRunnerReady', isolateId: isolate.id);
|
||||||
if (response.json['response'] != 'ready')
|
if (response.json!['response'] != 'ready')
|
||||||
throw 'not ready yet';
|
throw 'not ready yet';
|
||||||
return ConnectionResult(client, isolate);
|
return ConnectionResult(client, isolate);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -193,7 +191,7 @@ class ConnectionResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The cocoon client sends an invalid VM service response, we need to intercept it.
|
/// The cocoon client sends an invalid VM service response, we need to intercept it.
|
||||||
Future<VmService> vmServiceConnectUri(String wsUri, {Log log}) async {
|
Future<VmService> vmServiceConnectUri(String wsUri, {Log? log}) async {
|
||||||
final WebSocket socket = await WebSocket.connect(wsUri);
|
final WebSocket socket = await WebSocket.connect(wsUri);
|
||||||
final StreamController<dynamic> controller = StreamController<dynamic>();
|
final StreamController<dynamic> controller = StreamController<dynamic>();
|
||||||
final Completer<dynamic> streamClosedCompleter = Completer<dynamic>();
|
final Completer<dynamic> streamClosedCompleter = Completer<dynamic>();
|
||||||
@ -207,7 +205,7 @@ Future<VmService> vmServiceConnectUri(String wsUri, {Log log}) async {
|
|||||||
controller.add(data);
|
controller.add(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (dynamic err, StackTrace stackTrace) => controller.addError(err, stackTrace),
|
onError: (Object err, StackTrace stackTrace) => controller.addError(err, stackTrace),
|
||||||
onDone: () => streamClosedCompleter.complete(),
|
onDone: () => streamClosedCompleter.complete(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
@ -36,7 +34,7 @@ class RunningProcessInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> killProcess(String pid, {ProcessManager processManager}) async {
|
Future<bool> killProcess(String pid, {ProcessManager? processManager}) async {
|
||||||
assert(pid != null, 'Must specify a pid to kill');
|
assert(pid != null, 'Must specify a pid to kill');
|
||||||
processManager ??= const LocalProcessManager();
|
processManager ??= const LocalProcessManager();
|
||||||
ProcessResult result;
|
ProcessResult result;
|
||||||
@ -58,8 +56,8 @@ Future<bool> killProcess(String pid, {ProcessManager processManager}) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<RunningProcessInfo> getRunningProcesses({
|
Stream<RunningProcessInfo> getRunningProcesses({
|
||||||
String processName,
|
String? processName,
|
||||||
ProcessManager processManager,
|
ProcessManager? processManager,
|
||||||
}) {
|
}) {
|
||||||
processManager ??= const LocalProcessManager();
|
processManager ??= const LocalProcessManager();
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
@ -69,7 +67,7 @@ Stream<RunningProcessInfo> getRunningProcesses({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Stream<RunningProcessInfo> windowsRunningProcesses(String processName) async* {
|
Stream<RunningProcessInfo> windowsRunningProcesses(String? processName) async* {
|
||||||
// PowerShell script to get the command line arguments and create time of
|
// PowerShell script to get the command line arguments and create time of
|
||||||
// a process.
|
// a process.
|
||||||
// See: https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
|
// See: https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-process
|
||||||
@ -107,8 +105,8 @@ Iterable<RunningProcessInfo> processPowershellOutput(String output) sync* {
|
|||||||
|
|
||||||
const int processIdHeaderSize = 'ProcessId'.length;
|
const int processIdHeaderSize = 'ProcessId'.length;
|
||||||
const int creationDateHeaderStart = processIdHeaderSize + 1;
|
const int creationDateHeaderStart = processIdHeaderSize + 1;
|
||||||
int creationDateHeaderEnd;
|
late int creationDateHeaderEnd;
|
||||||
int commandLineHeaderStart;
|
late int commandLineHeaderStart;
|
||||||
bool inTableBody = false;
|
bool inTableBody = false;
|
||||||
for (final String line in output.split('\n')) {
|
for (final String line in output.split('\n')) {
|
||||||
if (line.startsWith('ProcessId')) {
|
if (line.startsWith('ProcessId')) {
|
||||||
@ -160,7 +158,7 @@ Iterable<RunningProcessInfo> processPowershellOutput(String output) sync* {
|
|||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Stream<RunningProcessInfo> posixRunningProcesses(
|
Stream<RunningProcessInfo> posixRunningProcesses(
|
||||||
String processName,
|
String? processName,
|
||||||
ProcessManager processManager,
|
ProcessManager processManager,
|
||||||
) async* {
|
) async* {
|
||||||
// Cirrus is missing this in Linux for some reason.
|
// Cirrus is missing this in Linux for some reason.
|
||||||
@ -194,7 +192,7 @@ Stream<RunningProcessInfo> posixRunningProcesses(
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Iterable<RunningProcessInfo> processPsOutput(
|
Iterable<RunningProcessInfo> processPsOutput(
|
||||||
String output,
|
String output,
|
||||||
String processName,
|
String? processName,
|
||||||
) sync* {
|
) sync* {
|
||||||
if (output == null) {
|
if (output == null) {
|
||||||
return;
|
return;
|
||||||
@ -235,7 +233,7 @@ Iterable<RunningProcessInfo> processPsOutput(
|
|||||||
final String rawTime = line.substring(0, 24);
|
final String rawTime = line.substring(0, 24);
|
||||||
|
|
||||||
final String year = rawTime.substring(20, 24);
|
final String year = rawTime.substring(20, 24);
|
||||||
final String month = months[rawTime.substring(4, 7)];
|
final String month = months[rawTime.substring(4, 7)]!;
|
||||||
final String day = rawTime.substring(8, 10).replaceFirst(' ', '0');
|
final String day = rawTime.substring(8, 10).replaceFirst(' ', '0');
|
||||||
final String time = rawTime.substring(11, 19);
|
final String time = rawTime.substring(11, 19);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class TaskResult {
|
|||||||
List<String> detailFiles = const <String>[],
|
List<String> detailFiles = const <String>[],
|
||||||
}) {
|
}) {
|
||||||
return TaskResult.success(
|
return TaskResult.success(
|
||||||
json.decode(file.readAsStringSync()) as Map<String, dynamic>,
|
json.decode(file.readAsStringSync()) as Map<String, dynamic>?,
|
||||||
benchmarkScoreKeys: benchmarkScoreKeys,
|
benchmarkScoreKeys: benchmarkScoreKeys,
|
||||||
detailFiles: detailFiles,
|
detailFiles: detailFiles,
|
||||||
);
|
);
|
||||||
@ -53,14 +53,14 @@ class TaskResult {
|
|||||||
if (success) {
|
if (success) {
|
||||||
final List<String> benchmarkScoreKeys = (json['benchmarkScoreKeys'] as List<dynamic>? ?? <String>[]).cast<String>();
|
final List<String> benchmarkScoreKeys = (json['benchmarkScoreKeys'] as List<dynamic>? ?? <String>[]).cast<String>();
|
||||||
final List<String> detailFiles = (json['detailFiles'] as List<dynamic>? ?? <String>[]).cast<String>();
|
final List<String> detailFiles = (json['detailFiles'] as List<dynamic>? ?? <String>[]).cast<String>();
|
||||||
return TaskResult.success(json['data'] as Map<String, dynamic>,
|
return TaskResult.success(json['data'] as Map<String, dynamic>?,
|
||||||
benchmarkScoreKeys: benchmarkScoreKeys,
|
benchmarkScoreKeys: benchmarkScoreKeys,
|
||||||
detailFiles: detailFiles,
|
detailFiles: detailFiles,
|
||||||
message: json['reason'] as String,
|
message: json['reason'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TaskResult.failure(json['reason'] as String);
|
return TaskResult.failure(json['reason'] as String?);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs an unsuccessful result.
|
/// Constructs an unsuccessful result.
|
||||||
@ -88,7 +88,7 @@ class TaskResult {
|
|||||||
bool get failed => !succeeded;
|
bool get failed => !succeeded;
|
||||||
|
|
||||||
/// Explains the result in a human-readable format.
|
/// Explains the result in a human-readable format.
|
||||||
final String message;
|
final String? message;
|
||||||
|
|
||||||
/// Serializes this task result to JSON format.
|
/// Serializes this task result to JSON format.
|
||||||
///
|
///
|
||||||
@ -124,7 +124,7 @@ class TaskResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => message;
|
String toString() => message ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
class TaskResultCheckProcesses extends TaskResult {
|
class TaskResultCheckProcesses extends TaskResult {
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -11,7 +9,6 @@ import 'dart:math' as math;
|
|||||||
|
|
||||||
import 'package:flutter_devicelab/common.dart';
|
import 'package:flutter_devicelab/common.dart';
|
||||||
import 'package:flutter_devicelab/framework/devices.dart';
|
import 'package:flutter_devicelab/framework/devices.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
import 'package:stack_trace/stack_trace.dart';
|
import 'package:stack_trace/stack_trace.dart';
|
||||||
@ -23,7 +20,7 @@ import 'task_result.dart';
|
|||||||
String cwd = Directory.current.path;
|
String cwd = Directory.current.path;
|
||||||
|
|
||||||
/// The local engine to use for [flutter] and [evalFlutter], if any.
|
/// The local engine to use for [flutter] and [evalFlutter], if any.
|
||||||
String get localEngine {
|
String? get localEngine {
|
||||||
// Use two distinct `defaultValue`s to determine whether a 'localEngine'
|
// Use two distinct `defaultValue`s to determine whether a 'localEngine'
|
||||||
// declaration exists in the environment.
|
// declaration exists in the environment.
|
||||||
const bool isDefined =
|
const bool isDefined =
|
||||||
@ -34,7 +31,7 @@ String get localEngine {
|
|||||||
|
|
||||||
/// The local engine source path to use if a local engine is used for [flutter]
|
/// The local engine source path to use if a local engine is used for [flutter]
|
||||||
/// and [evalFlutter].
|
/// and [evalFlutter].
|
||||||
String get localEngineSrcPath {
|
String? get localEngineSrcPath {
|
||||||
// Use two distinct `defaultValue`s to determine whether a
|
// Use two distinct `defaultValue`s to determine whether a
|
||||||
// 'localEngineSrcPath' declaration exists in the environment.
|
// 'localEngineSrcPath' declaration exists in the environment.
|
||||||
const bool isDefined =
|
const bool isDefined =
|
||||||
@ -70,18 +67,18 @@ class HealthCheckResult {
|
|||||||
HealthCheckResult.failure(this.details) : succeeded = false;
|
HealthCheckResult.failure(this.details) : succeeded = false;
|
||||||
HealthCheckResult.error(dynamic error, dynamic stackTrace)
|
HealthCheckResult.error(dynamic error, dynamic stackTrace)
|
||||||
: succeeded = false,
|
: succeeded = false,
|
||||||
details = 'ERROR: $error${'\n$stackTrace' ?? ''}';
|
details = 'ERROR: $error${stackTrace != null ? '\n$stackTrace' : ''}';
|
||||||
|
|
||||||
final bool succeeded;
|
final bool succeeded;
|
||||||
final String details;
|
final String? details;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final StringBuffer buf = StringBuffer(succeeded ? 'succeeded' : 'failed');
|
final StringBuffer buf = StringBuffer(succeeded ? 'succeeded' : 'failed');
|
||||||
if (details != null && details.trim().isNotEmpty) {
|
if (details != null && details!.trim().isNotEmpty) {
|
||||||
buf.writeln();
|
buf.writeln();
|
||||||
// Indent details by 4 spaces
|
// Indent details by 4 spaces
|
||||||
for (final String line in details.trim().split('\n')) {
|
for (final String line in details!.trim().split('\n')) {
|
||||||
buf.writeln(' $line');
|
buf.writeln(' $line');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +124,7 @@ Directory dir(String path) => Directory(path);
|
|||||||
|
|
||||||
File file(String path) => File(path);
|
File file(String path) => File(path);
|
||||||
|
|
||||||
void copy(File sourceFile, Directory targetDirectory, {String name}) {
|
void copy(File sourceFile, Directory targetDirectory, {String? name}) {
|
||||||
final File target = file(
|
final File target = file(
|
||||||
path.join(targetDirectory.path, name ?? path.basename(sourceFile.path)));
|
path.join(targetDirectory.path, name ?? path.basename(sourceFile.path)));
|
||||||
target.writeAsBytesSync(sourceFile.readAsBytesSync());
|
target.writeAsBytesSync(sourceFile.readAsBytesSync());
|
||||||
@ -154,7 +151,7 @@ void recursiveCopy(Directory source, Directory target) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FileSystemEntity move(FileSystemEntity whatToMove,
|
FileSystemEntity move(FileSystemEntity whatToMove,
|
||||||
{Directory to, String name}) {
|
{required Directory to, String? name}) {
|
||||||
return whatToMove
|
return whatToMove
|
||||||
.renameSync(path.join(to.path, name ?? path.basename(whatToMove.path)));
|
.renameSync(path.join(to.path, name ?? path.basename(whatToMove.path)));
|
||||||
}
|
}
|
||||||
@ -225,9 +222,9 @@ Future<String> getDartVersion() async {
|
|||||||
return version.replaceAll('"', "'");
|
return version.replaceAll('"', "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getCurrentFlutterRepoCommit() {
|
Future<String?> getCurrentFlutterRepoCommit() {
|
||||||
if (!dir('${flutterDirectory.path}/.git').existsSync()) {
|
if (!dir('${flutterDirectory.path}/.git').existsSync()) {
|
||||||
return Future<String>.value(null);
|
return Future<String?>.value(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return inDirectory<String>(flutterDirectory, () {
|
return inDirectory<String>(flutterDirectory, () {
|
||||||
@ -275,10 +272,10 @@ Future<DateTime> getFlutterRepoCommitTimestamp(String commit) {
|
|||||||
/// returned in the form of a [Future] that completes to a [Process] object.
|
/// returned in the form of a [Future] that completes to a [Process] object.
|
||||||
Future<Process> startProcess(
|
Future<Process> startProcess(
|
||||||
String executable,
|
String executable,
|
||||||
List<String> arguments, {
|
List<String>? arguments, {
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
bool isBot = true, // set to false to pretend not to be on a bot (e.g. to test user-facing outputs)
|
bool isBot = true, // set to false to pretend not to be on a bot (e.g. to test user-facing outputs)
|
||||||
String workingDirectory,
|
String? workingDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
assert(isBot != null);
|
assert(isBot != null);
|
||||||
final String command = '$executable ${arguments?.join(" ") ?? ""}';
|
final String command = '$executable ${arguments?.join(" ") ?? ""}';
|
||||||
@ -288,7 +285,7 @@ Future<Process> startProcess(
|
|||||||
newEnvironment['LANG'] = 'en_US.UTF-8';
|
newEnvironment['LANG'] = 'en_US.UTF-8';
|
||||||
print('\nExecuting: $command in $finalWorkingDirectory with environment $newEnvironment');
|
print('\nExecuting: $command in $finalWorkingDirectory with environment $newEnvironment');
|
||||||
final Process process = await _processManager.start(
|
final Process process = await _processManager.start(
|
||||||
<String>[executable, ...arguments],
|
<String>[executable, ...?arguments],
|
||||||
environment: newEnvironment,
|
environment: newEnvironment,
|
||||||
workingDirectory: finalWorkingDirectory,
|
workingDirectory: finalWorkingDirectory,
|
||||||
);
|
);
|
||||||
@ -324,9 +321,9 @@ Future<void> forceQuitRunningProcesses() async {
|
|||||||
Future<int> exec(
|
Future<int> exec(
|
||||||
String executable,
|
String executable,
|
||||||
List<String> arguments, {
|
List<String> arguments, {
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
||||||
String workingDirectory,
|
String? workingDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
return _execute(
|
return _execute(
|
||||||
executable,
|
executable,
|
||||||
@ -340,11 +337,11 @@ Future<int> exec(
|
|||||||
Future<int> _execute(
|
Future<int> _execute(
|
||||||
String executable,
|
String executable,
|
||||||
List<String> arguments, {
|
List<String> arguments, {
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
||||||
String workingDirectory,
|
String? workingDirectory,
|
||||||
StringBuffer output, // if not null, the stdout will be written here
|
StringBuffer? output, // if not null, the stdout will be written here
|
||||||
StringBuffer stderr, // if not null, the stderr will be written here
|
StringBuffer? stderr, // if not null, the stderr will be written here
|
||||||
bool printStdout = true,
|
bool printStdout = true,
|
||||||
bool printStderr = true,
|
bool printStderr = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -376,8 +373,8 @@ Future<int> _execute(
|
|||||||
/// Returns a future that completes when both out and error streams a closed.
|
/// Returns a future that completes when both out and error streams a closed.
|
||||||
Future<void> forwardStandardStreams(
|
Future<void> forwardStandardStreams(
|
||||||
Process process, {
|
Process process, {
|
||||||
StringBuffer output,
|
StringBuffer? output,
|
||||||
StringBuffer stderr,
|
StringBuffer? stderr,
|
||||||
bool printStdout = true,
|
bool printStdout = true,
|
||||||
bool printStderr = true,
|
bool printStderr = true,
|
||||||
}) {
|
}) {
|
||||||
@ -414,10 +411,10 @@ Future<void> forwardStandardStreams(
|
|||||||
Future<String> eval(
|
Future<String> eval(
|
||||||
String executable,
|
String executable,
|
||||||
List<String> arguments, {
|
List<String> arguments, {
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
||||||
String workingDirectory,
|
String? workingDirectory,
|
||||||
StringBuffer stderr, // if not null, the stderr will be written here
|
StringBuffer? stderr, // if not null, the stderr will be written here
|
||||||
bool printStdout = true,
|
bool printStdout = true,
|
||||||
bool printStderr = true,
|
bool printStderr = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -457,10 +454,10 @@ List<String> flutterCommandArgs(String command, List<String> options) {
|
|||||||
|
|
||||||
if (command == 'drive' && hostAgent.dumpDirectory != null) ...<String>[
|
if (command == 'drive' && hostAgent.dumpDirectory != null) ...<String>[
|
||||||
'--screenshot',
|
'--screenshot',
|
||||||
hostAgent.dumpDirectory.path,
|
hostAgent.dumpDirectory!.path,
|
||||||
],
|
],
|
||||||
if (localEngine != null) ...<String>['--local-engine', localEngine],
|
if (localEngine != null) ...<String>['--local-engine', localEngine!],
|
||||||
if (localEngineSrcPath != null) ...<String>['--local-engine-src-path', localEngineSrcPath],
|
if (localEngineSrcPath != null) ...<String>['--local-engine-src-path', localEngineSrcPath!],
|
||||||
...options,
|
...options,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -470,7 +467,7 @@ List<String> flutterCommandArgs(String command, List<String> options) {
|
|||||||
Future<int> flutter(String command, {
|
Future<int> flutter(String command, {
|
||||||
List<String> options = const <String>[],
|
List<String> options = const <String>[],
|
||||||
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
||||||
Map<String, String> environment = const <String, String>{},
|
Map<String, String>? environment,
|
||||||
}) {
|
}) {
|
||||||
final List<String> args = flutterCommandArgs(command, options);
|
final List<String> args = flutterCommandArgs(command, options);
|
||||||
return exec(path.join(flutterDirectory.path, 'bin', 'flutter'), args,
|
return exec(path.join(flutterDirectory.path, 'bin', 'flutter'), args,
|
||||||
@ -493,8 +490,8 @@ Future<Process> startFlutter(String command, {
|
|||||||
Future<String> evalFlutter(String command, {
|
Future<String> evalFlutter(String command, {
|
||||||
List<String> options = const <String>[],
|
List<String> options = const <String>[],
|
||||||
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
StringBuffer stderr, // if not null, the stderr will be written here.
|
StringBuffer? stderr, // if not null, the stderr will be written here.
|
||||||
}) {
|
}) {
|
||||||
final List<String> args = flutterCommandArgs(command, options);
|
final List<String> args = flutterCommandArgs(command, options);
|
||||||
return eval(path.join(flutterDirectory.path, 'bin', 'flutter'), args,
|
return eval(path.join(flutterDirectory.path, 'bin', 'flutter'), args,
|
||||||
@ -521,7 +518,7 @@ Future<int> dart(List<String> args) => exec(dartBin, <String>['--disable-dart-de
|
|||||||
|
|
||||||
/// Returns a future that completes with a path suitable for JAVA_HOME
|
/// Returns a future that completes with a path suitable for JAVA_HOME
|
||||||
/// or with null, if Java cannot be found.
|
/// or with null, if Java cannot be found.
|
||||||
Future<String> findJavaHome() async {
|
Future<String?> findJavaHome() async {
|
||||||
if (_javaHome == null) {
|
if (_javaHome == null) {
|
||||||
final Iterable<String> hits = grep(
|
final Iterable<String> hits = grep(
|
||||||
'Java binary at: ',
|
'Java binary at: ',
|
||||||
@ -537,7 +534,7 @@ Future<String> findJavaHome() async {
|
|||||||
}
|
}
|
||||||
return _javaHome;
|
return _javaHome;
|
||||||
}
|
}
|
||||||
String _javaHome;
|
String? _javaHome;
|
||||||
|
|
||||||
Future<T> inDirectory<T>(dynamic directory, Future<T> Function() action) async {
|
Future<T> inDirectory<T>(dynamic directory, Future<T> Function() action) async {
|
||||||
final String previousCwd = cwd;
|
final String previousCwd = cwd;
|
||||||
@ -568,12 +565,12 @@ void cd(dynamic directory) {
|
|||||||
Directory get flutterDirectory => Directory.current.parent.parent;
|
Directory get flutterDirectory => Directory.current.parent.parent;
|
||||||
|
|
||||||
String requireEnvVar(String name) {
|
String requireEnvVar(String name) {
|
||||||
final String value = Platform.environment[name];
|
final String? value = Platform.environment[name];
|
||||||
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
fail('$name environment variable is missing. Quitting.');
|
fail('$name environment variable is missing. Quitting.');
|
||||||
|
|
||||||
return value;
|
return value!;
|
||||||
}
|
}
|
||||||
|
|
||||||
T requireConfigProperty<T>(Map<String, dynamic> map, String propertyName) {
|
T requireConfigProperty<T>(Map<String, dynamic> map, String propertyName) {
|
||||||
@ -637,7 +634,7 @@ void checkNotNull(Object o1,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Splits [from] into lines and selects those that contain [pattern].
|
/// Splits [from] into lines and selects those that contain [pattern].
|
||||||
Iterable<String> grep(Pattern pattern, {@required String from}) {
|
Iterable<String> grep(Pattern pattern, {required String from}) {
|
||||||
return from.split('\n').where((String line) {
|
return from.split('\n').where((String line) {
|
||||||
return line.contains(pattern);
|
return line.contains(pattern);
|
||||||
});
|
});
|
||||||
@ -675,8 +672,8 @@ final RegExp _obsUriRegExp = RegExp(r'((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)');
|
|||||||
///
|
///
|
||||||
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
|
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
|
||||||
/// `prefix` defaults to the RegExp: `An Observatory debugger .* is available at: `.
|
/// `prefix` defaults to the RegExp: `An Observatory debugger .* is available at: `.
|
||||||
int parseServicePort(String line, {
|
int? parseServicePort(String line, {
|
||||||
Pattern prefix,
|
Pattern? prefix,
|
||||||
}) {
|
}) {
|
||||||
prefix ??= _obsRegExp;
|
prefix ??= _obsRegExp;
|
||||||
final Iterable<Match> matchesIter = prefix.allMatches(line);
|
final Iterable<Match> matchesIter = prefix.allMatches(line);
|
||||||
@ -686,15 +683,15 @@ int parseServicePort(String line, {
|
|||||||
final Match prefixMatch = matchesIter.first;
|
final Match prefixMatch = matchesIter.first;
|
||||||
final List<Match> matches =
|
final List<Match> matches =
|
||||||
_obsPortRegExp.allMatches(line, prefixMatch.end).toList();
|
_obsPortRegExp.allMatches(line, prefixMatch.end).toList();
|
||||||
return matches.isEmpty ? null : int.parse(matches[0].group(2));
|
return matches.isEmpty ? null : int.parse(matches[0].group(2)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to extract a URL from the string.
|
/// Tries to extract a URL from the string.
|
||||||
///
|
///
|
||||||
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
|
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
|
||||||
/// `prefix` defaults to the RegExp: `An Observatory debugger .* is available at: `.
|
/// `prefix` defaults to the RegExp: `An Observatory debugger .* is available at: `.
|
||||||
Uri parseServiceUri(String line, {
|
Uri? parseServiceUri(String line, {
|
||||||
Pattern prefix,
|
Pattern? prefix,
|
||||||
}) {
|
}) {
|
||||||
prefix ??= _obsRegExp;
|
prefix ??= _obsRegExp;
|
||||||
final Iterable<Match> matchesIter = prefix.allMatches(line);
|
final Iterable<Match> matchesIter = prefix.allMatches(line);
|
||||||
@ -704,7 +701,7 @@ Uri parseServiceUri(String line, {
|
|||||||
final Match prefixMatch = matchesIter.first;
|
final Match prefixMatch = matchesIter.first;
|
||||||
final List<Match> matches =
|
final List<Match> matches =
|
||||||
_obsUriRegExp.allMatches(line, prefixMatch.end).toList();
|
_obsUriRegExp.allMatches(line, prefixMatch.end).toList();
|
||||||
return matches.isEmpty ? null : Uri.parse(matches[0].group(0));
|
return matches.isEmpty ? null : Uri.parse(matches[0].group(0)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that the file exists, otherwise throws a [FileSystemException].
|
/// Checks that the file exists, otherwise throws a [FileSystemException].
|
||||||
@ -771,7 +768,7 @@ void checkFileContains(List<Pattern> patterns, String filePath) {
|
|||||||
///
|
///
|
||||||
/// Removes the directory [path], then clones the git repository
|
/// Removes the directory [path], then clones the git repository
|
||||||
/// specified by [repo] to the directory [path].
|
/// specified by [repo] to the directory [path].
|
||||||
Future<int> gitClone({String path, String repo}) async {
|
Future<int> gitClone({required String path, required String repo}) async {
|
||||||
rmTree(Directory(path));
|
rmTree(Directory(path));
|
||||||
|
|
||||||
await Directory(path).create(recursive: true);
|
await Directory(path).create(recursive: true);
|
||||||
@ -792,7 +789,7 @@ Future<int> gitClone({String path, String repo}) async {
|
|||||||
/// Waits a constant duration of [delayDuration] between every retry attempt.
|
/// Waits a constant duration of [delayDuration] between every retry attempt.
|
||||||
Future<T> retry<T>(
|
Future<T> retry<T>(
|
||||||
FutureOr<T> Function() fn, {
|
FutureOr<T> Function() fn, {
|
||||||
FutureOr<bool> Function(Exception) retryIf,
|
FutureOr<bool> Function(Exception)? retryIf,
|
||||||
int maxAttempts = 5,
|
int maxAttempts = 5,
|
||||||
Duration delayDuration = const Duration(seconds: 3),
|
Duration delayDuration = const Duration(seconds: 3),
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
@ -18,10 +16,9 @@ import '../framework/utils.dart';
|
|||||||
abstract class BuildTestTask {
|
abstract class BuildTestTask {
|
||||||
BuildTestTask(this.args, {this.workingDirectory, this.runFlutterClean = true,}) {
|
BuildTestTask(this.args, {this.workingDirectory, this.runFlutterClean = true,}) {
|
||||||
final ArgResults argResults = argParser.parse(args);
|
final ArgResults argResults = argParser.parse(args);
|
||||||
applicationBinaryPath = argResults[kApplicationBinaryPathOption] as String;
|
applicationBinaryPath = argResults[kApplicationBinaryPathOption] as String?;
|
||||||
buildOnly = argResults[kBuildOnlyFlag] as bool;
|
buildOnly = argResults[kBuildOnlyFlag] as bool;
|
||||||
testOnly = argResults[kTestOnlyFlag] as bool;
|
testOnly = argResults[kTestOnlyFlag] as bool;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const String kApplicationBinaryPathOption = 'application-binary-path';
|
static const String kApplicationBinaryPathOption = 'application-binary-path';
|
||||||
@ -48,10 +45,10 @@ abstract class BuildTestTask {
|
|||||||
/// Path to a built application to use in [test].
|
/// Path to a built application to use in [test].
|
||||||
///
|
///
|
||||||
/// If not given, will default to child's expected location.
|
/// If not given, will default to child's expected location.
|
||||||
String applicationBinaryPath;
|
String? applicationBinaryPath;
|
||||||
|
|
||||||
/// Where the test artifacts are stored, such as performance results.
|
/// Where the test artifacts are stored, such as performance results.
|
||||||
final Directory workingDirectory;
|
final Directory? workingDirectory;
|
||||||
|
|
||||||
/// Run Flutter build to create [applicationBinaryPath].
|
/// Run Flutter build to create [applicationBinaryPath].
|
||||||
Future<void> build() async {
|
Future<void> build() async {
|
||||||
@ -93,7 +90,7 @@ abstract class BuildTestTask {
|
|||||||
///
|
///
|
||||||
/// Tasks can override to support default values. Otherwise, it will default
|
/// Tasks can override to support default values. Otherwise, it will default
|
||||||
/// to needing to be passed as an argument in the test runner.
|
/// to needing to be passed as an argument in the test runner.
|
||||||
String getApplicationBinaryPath() => applicationBinaryPath;
|
String? getApplicationBinaryPath() => applicationBinaryPath;
|
||||||
|
|
||||||
/// Run this task.
|
/// Run this task.
|
||||||
///
|
///
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -15,8 +13,8 @@ import 'package:flutter_devicelab/framework/utils.dart';
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
TaskFunction dartPluginRegistryTest({
|
TaskFunction dartPluginRegistryTest({
|
||||||
String deviceIdOverride,
|
String? deviceIdOverride,
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
}) {
|
}) {
|
||||||
final Directory tempDir = Directory.systemTemp
|
final Directory tempDir = Directory.systemTemp
|
||||||
.createTempSync('flutter_devicelab_dart_plugin_test.');
|
.createTempSync('flutter_devicelab_dart_plugin_test.');
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
@ -55,9 +53,9 @@ class GalleryTransitionTest {
|
|||||||
final bool needFullTimeline;
|
final bool needFullTimeline;
|
||||||
final String testFile;
|
final String testFile;
|
||||||
final String timelineSummaryFile;
|
final String timelineSummaryFile;
|
||||||
final String timelineTraceFile;
|
final String? timelineTraceFile;
|
||||||
final String transitionDurationFile;
|
final String? transitionDurationFile;
|
||||||
final String driverFile;
|
final String? driverFile;
|
||||||
|
|
||||||
Future<TaskResult> call() async {
|
Future<TaskResult> call() async {
|
||||||
final Device device = await devices.workingDevice;
|
final Device device = await devices.workingDevice;
|
||||||
@ -65,7 +63,7 @@ class GalleryTransitionTest {
|
|||||||
final String deviceId = device.deviceId;
|
final String deviceId = device.deviceId;
|
||||||
final Directory galleryDirectory = dir('${flutterDirectory.path}/dev/integration_tests/flutter_gallery');
|
final Directory galleryDirectory = dir('${flutterDirectory.path}/dev/integration_tests/flutter_gallery');
|
||||||
await inDirectory<void>(galleryDirectory, () async {
|
await inDirectory<void>(galleryDirectory, () async {
|
||||||
String applicationBinaryPath;
|
String? applicationBinaryPath;
|
||||||
if (deviceOperatingSystem == DeviceOperatingSystem.android) {
|
if (deviceOperatingSystem == DeviceOperatingSystem.android) {
|
||||||
section('BUILDING APPLICATION');
|
section('BUILDING APPLICATION');
|
||||||
await flutter(
|
await flutter(
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -20,7 +18,7 @@ final Directory flutterGalleryDir = dir(path.join(flutterDirectory.path, 'dev/in
|
|||||||
const String kSourceLine = 'fontSize: (orientation == Orientation.portrait) ? 32.0 : 24.0';
|
const String kSourceLine = 'fontSize: (orientation == Orientation.portrait) ? 32.0 : 24.0';
|
||||||
const String kReplacementLine = 'fontSize: (orientation == Orientation.portrait) ? 34.0 : 24.0';
|
const String kReplacementLine = 'fontSize: (orientation == Orientation.portrait) ? 34.0 : 24.0';
|
||||||
|
|
||||||
TaskFunction createHotModeTest({String deviceIdOverride, Map<String, String> environment}) {
|
TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? environment}) {
|
||||||
// This file is modified during the test and needs to be restored at the end.
|
// This file is modified during the test and needs to be restored at the end.
|
||||||
final File flutterFrameworkSource = file(path.join(
|
final File flutterFrameworkSource = file(path.join(
|
||||||
flutterDirectory.path, 'packages/flutter/lib/src/widgets/framework.dart',
|
flutterDirectory.path, 'packages/flutter/lib/src/widgets/framework.dart',
|
||||||
@ -35,13 +33,13 @@ TaskFunction createHotModeTest({String deviceIdOverride, Map<String, String> env
|
|||||||
final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json'));
|
final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json'));
|
||||||
rm(benchmarkFile);
|
rm(benchmarkFile);
|
||||||
final List<String> options = <String>[
|
final List<String> options = <String>[
|
||||||
'--hot', '-d', deviceIdOverride, '--benchmark', '--resident', '--no-android-gradle-daemon', '--no-publish-port', '--verbose',
|
'--hot', '-d', deviceIdOverride!, '--benchmark', '--resident', '--no-android-gradle-daemon', '--no-publish-port', '--verbose',
|
||||||
];
|
];
|
||||||
int hotReloadCount = 0;
|
int hotReloadCount = 0;
|
||||||
Map<String, dynamic> smallReloadData;
|
late Map<String, dynamic> smallReloadData;
|
||||||
Map<String, dynamic> mediumReloadData;
|
late Map<String, dynamic> mediumReloadData;
|
||||||
Map<String, dynamic> largeReloadData;
|
late Map<String, dynamic> largeReloadData;
|
||||||
Map<String, dynamic> freshRestartReloadsData;
|
late Map<String, dynamic> freshRestartReloadsData;
|
||||||
|
|
||||||
|
|
||||||
await inDirectory<void>(flutterDirectory, () async {
|
await inDirectory<void>(flutterDirectory, () async {
|
||||||
@ -217,7 +215,7 @@ TaskFunction createHotModeTest({String deviceIdOverride, Map<String, String> env
|
|||||||
|
|
||||||
Future<Map<String, Object>> captureReloadData(
|
Future<Map<String, Object>> captureReloadData(
|
||||||
List<String> options,
|
List<String> options,
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
File benchmarkFile,
|
File benchmarkFile,
|
||||||
void Function(String, Process) onLine,
|
void Function(String, Process) onLine,
|
||||||
) async {
|
) async {
|
||||||
@ -247,7 +245,7 @@ Future<Map<String, Object>> captureReloadData(
|
|||||||
|
|
||||||
await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]);
|
await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]);
|
||||||
await process.exitCode;
|
await process.exitCode;
|
||||||
final Map<String, dynamic> result = json.decode(benchmarkFile.readAsStringSync()) as Map<String, dynamic>;
|
final Map<String, Object> result = json.decode(benchmarkFile.readAsStringSync()) as Map<String, Object>;
|
||||||
benchmarkFile.deleteSync();
|
benchmarkFile.deleteSync();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import '../framework/devices.dart';
|
import '../framework/devices.dart';
|
||||||
import '../framework/framework.dart';
|
import '../framework/framework.dart';
|
||||||
import '../framework/task_result.dart';
|
import '../framework/task_result.dart';
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_devicelab/tasks/perf_tests.dart';
|
import 'package:flutter_devicelab/tasks/perf_tests.dart';
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert' show LineSplitter, json, utf8;
|
import 'dart:convert' show LineSplitter, json, utf8;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@ -516,7 +514,7 @@ class StartupTest {
|
|||||||
final List<Map<String, dynamic>> results = <Map<String, dynamic>>[];
|
final List<Map<String, dynamic>> results = <Map<String, dynamic>>[];
|
||||||
|
|
||||||
section('Building application');
|
section('Building application');
|
||||||
String applicationBinaryPath;
|
String? applicationBinaryPath;
|
||||||
switch (deviceOperatingSystem) {
|
switch (deviceOperatingSystem) {
|
||||||
case DeviceOperatingSystem.android:
|
case DeviceOperatingSystem.android:
|
||||||
await flutter('build', options: <String>[
|
await flutter('build', options: <String>[
|
||||||
@ -586,7 +584,7 @@ class StartupTest {
|
|||||||
'-d',
|
'-d',
|
||||||
device.deviceId,
|
device.deviceId,
|
||||||
'--out',
|
'--out',
|
||||||
hostAgent.dumpDirectory
|
hostAgent.dumpDirectory!
|
||||||
.childFile('screenshot_startup_failure_$currentFailures.png')
|
.childFile('screenshot_startup_failure_$currentFailures.png')
|
||||||
.path,
|
.path,
|
||||||
],
|
],
|
||||||
@ -630,7 +628,7 @@ class DevtoolsStartupTest {
|
|||||||
final Device device = await devices.workingDevice;
|
final Device device = await devices.workingDevice;
|
||||||
|
|
||||||
section('Building application');
|
section('Building application');
|
||||||
String applicationBinaryPath;
|
String? applicationBinaryPath;
|
||||||
switch (deviceOperatingSystem) {
|
switch (deviceOperatingSystem) {
|
||||||
case DeviceOperatingSystem.android:
|
case DeviceOperatingSystem.android:
|
||||||
await flutter('build', options: <String>[
|
await flutter('build', options: <String>[
|
||||||
@ -733,7 +731,7 @@ class PerfTest {
|
|||||||
this.needsFullTimeline = true,
|
this.needsFullTimeline = true,
|
||||||
this.benchmarkScoreKeys,
|
this.benchmarkScoreKeys,
|
||||||
this.dartDefine = '',
|
this.dartDefine = '',
|
||||||
String resultFilename,
|
String? resultFilename,
|
||||||
}): _resultFilename = resultFilename;
|
}): _resultFilename = resultFilename;
|
||||||
|
|
||||||
const PerfTest.e2e(
|
const PerfTest.e2e(
|
||||||
@ -753,12 +751,12 @@ class PerfTest {
|
|||||||
/// The main entry-point file of the application, as run on the device.
|
/// The main entry-point file of the application, as run on the device.
|
||||||
final String testTarget;
|
final String testTarget;
|
||||||
// The prefix name of the filename such as `<timelineFileName>.timeline_summary.json`.
|
// The prefix name of the filename such as `<timelineFileName>.timeline_summary.json`.
|
||||||
final String timelineFileName;
|
final String? timelineFileName;
|
||||||
String get traceFilename => '$timelineFileName.timeline';
|
String get traceFilename => '$timelineFileName.timeline';
|
||||||
String get resultFilename => _resultFilename ?? '$timelineFileName.timeline_summary';
|
String get resultFilename => _resultFilename ?? '$timelineFileName.timeline_summary';
|
||||||
final String _resultFilename;
|
final String? _resultFilename;
|
||||||
/// The test file to run on the host.
|
/// The test file to run on the host.
|
||||||
final String testDriver;
|
final String? testDriver;
|
||||||
/// Whether to collect CPU and GPU metrics.
|
/// Whether to collect CPU and GPU metrics.
|
||||||
final bool measureCpuGpu;
|
final bool measureCpuGpu;
|
||||||
/// Whether to collect memory metrics.
|
/// Whether to collect memory metrics.
|
||||||
@ -788,7 +786,7 @@ class PerfTest {
|
|||||||
/// if (measureCpuGpu) 'average_gpu_usage',
|
/// if (measureCpuGpu) 'average_gpu_usage',
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
final List<String> benchmarkScoreKeys;
|
final List<String>? benchmarkScoreKeys;
|
||||||
|
|
||||||
/// Additional flags for `--dart-define` to control the test
|
/// Additional flags for `--dart-define` to control the test
|
||||||
final String dartDefine;
|
final String dartDefine;
|
||||||
@ -800,8 +798,8 @@ class PerfTest {
|
|||||||
@protected
|
@protected
|
||||||
Future<TaskResult> internalRun({
|
Future<TaskResult> internalRun({
|
||||||
bool cacheSkSL = false,
|
bool cacheSkSL = false,
|
||||||
String existingApp,
|
String? existingApp,
|
||||||
String writeSkslFileName,
|
String? writeSkslFileName,
|
||||||
}) {
|
}) {
|
||||||
return inDirectory<TaskResult>(testDirectory, () async {
|
return inDirectory<TaskResult>(testDirectory, () async {
|
||||||
final Device device = await devices.workingDevice;
|
final Device device = await devices.workingDevice;
|
||||||
@ -818,7 +816,7 @@ class PerfTest {
|
|||||||
'--trace-startup', // Enables "endless" timeline event buffering.
|
'--trace-startup', // Enables "endless" timeline event buffering.
|
||||||
'-t', testTarget,
|
'-t', testTarget,
|
||||||
if (testDriver != null)
|
if (testDriver != null)
|
||||||
...<String>['--driver', testDriver],
|
...<String>['--driver', testDriver!],
|
||||||
if (existingApp != null)
|
if (existingApp != null)
|
||||||
...<String>['--use-existing-app', existingApp],
|
...<String>['--use-existing-app', existingApp],
|
||||||
if (writeSkslFileName != null)
|
if (writeSkslFileName != null)
|
||||||
@ -890,9 +888,9 @@ class PerfTestWithSkSL extends PerfTest {
|
|||||||
String testTarget,
|
String testTarget,
|
||||||
String timelineFileName, {
|
String timelineFileName, {
|
||||||
bool measureCpuGpu = false,
|
bool measureCpuGpu = false,
|
||||||
String testDriver,
|
String? testDriver,
|
||||||
bool needsFullTimeline = true,
|
bool needsFullTimeline = true,
|
||||||
List<String> benchmarkScoreKeys,
|
List<String>? benchmarkScoreKeys,
|
||||||
}) : super(
|
}) : super(
|
||||||
testDirectory,
|
testDirectory,
|
||||||
testTarget,
|
testTarget,
|
||||||
@ -964,7 +962,7 @@ class PerfTestWithSkSL extends PerfTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _runApp({String appBinary, bool cacheSkSL = false, String skslPath}) async {
|
Future<String> _runApp({String? appBinary, bool cacheSkSL = false, String? skslPath}) async {
|
||||||
if (File(_vmserviceFileName).existsSync()) {
|
if (File(_vmserviceFileName).existsSync()) {
|
||||||
File(_vmserviceFileName).deleteSync();
|
File(_vmserviceFileName).deleteSync();
|
||||||
}
|
}
|
||||||
@ -1027,9 +1025,9 @@ class PerfTestWithSkSL extends PerfTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String _flutterPath;
|
late String _flutterPath;
|
||||||
Device _device;
|
late Device _device;
|
||||||
Process _runProcess;
|
late Process _runProcess;
|
||||||
|
|
||||||
static const String _kVmserviceOutFileName = 'vmservice.out';
|
static const String _kVmserviceOutFileName = 'vmservice.out';
|
||||||
}
|
}
|
||||||
@ -1073,12 +1071,16 @@ class WebCompileTest {
|
|||||||
///
|
///
|
||||||
/// Run a single web compile test for the app under [directory], and store
|
/// Run a single web compile test for the app under [directory], and store
|
||||||
/// its metrics with prefix [metric].
|
/// its metrics with prefix [metric].
|
||||||
static Future<Map<String, int>> runSingleBuildTest({String directory, String metric, bool measureBuildTime = false}) {
|
static Future<Map<String, int>> runSingleBuildTest({
|
||||||
|
required String directory,
|
||||||
|
required String metric,
|
||||||
|
bool measureBuildTime = false,
|
||||||
|
}) {
|
||||||
return inDirectory<Map<String, int>>(directory, () async {
|
return inDirectory<Map<String, int>>(directory, () async {
|
||||||
final Map<String, int> metrics = <String, int>{};
|
final Map<String, int> metrics = <String, int>{};
|
||||||
|
|
||||||
await flutter('packages', options: <String>['get']);
|
await flutter('packages', options: <String>['get']);
|
||||||
final Stopwatch watch = measureBuildTime ? Stopwatch() : null;
|
final Stopwatch? watch = measureBuildTime ? Stopwatch() : null;
|
||||||
watch?.start();
|
watch?.start();
|
||||||
await evalFlutter('build', options: <String>[
|
await evalFlutter('build', options: <String>[
|
||||||
'web',
|
'web',
|
||||||
@ -1091,7 +1093,7 @@ class WebCompileTest {
|
|||||||
metrics.addAll(await getSize(outputFileName, metric: metric));
|
metrics.addAll(await getSize(outputFileName, metric: metric));
|
||||||
|
|
||||||
if (measureBuildTime) {
|
if (measureBuildTime) {
|
||||||
metrics['${metric}_dart2js_millis'] = watch.elapsedMilliseconds;
|
metrics['${metric}_dart2js_millis'] = watch!.elapsedMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
return metrics;
|
return metrics;
|
||||||
@ -1099,7 +1101,7 @@ class WebCompileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Obtains the size and gzipped size of a file given by [fileName].
|
/// Obtains the size and gzipped size of a file given by [fileName].
|
||||||
static Future<Map<String, int>> getSize(String fileName, {String metric}) async {
|
static Future<Map<String, int>> getSize(String fileName, {required String metric}) async {
|
||||||
final Map<String, int> sizeMetrics = <String, int>{};
|
final Map<String, int> sizeMetrics = <String, int>{};
|
||||||
|
|
||||||
final ProcessResult result = await Process.run('du', <String>['-k', fileName]);
|
final ProcessResult result = await Process.run('du', <String>['-k', fileName]);
|
||||||
@ -1168,10 +1170,12 @@ class CompileTest {
|
|||||||
await flutter('build', options: options);
|
await flutter('build', options: options);
|
||||||
watch.stop();
|
watch.stop();
|
||||||
final Directory appBuildDirectory = dir(path.join(cwd, 'build/ios/Release-iphoneos'));
|
final Directory appBuildDirectory = dir(path.join(cwd, 'build/ios/Release-iphoneos'));
|
||||||
final Directory appBundle = appBuildDirectory
|
final Directory? appBundle = appBuildDirectory
|
||||||
.listSync()
|
.listSync()
|
||||||
.whereType<Directory>()
|
.whereType<Directory?>()
|
||||||
.singleWhere((Directory directory) => path.extension(directory.path) == '.app', orElse: () => null);
|
.singleWhere((Directory? directory) =>
|
||||||
|
directory != null && path.extension(directory.path) == '.app',
|
||||||
|
orElse: () => null);
|
||||||
if (appBundle == null) {
|
if (appBundle == null) {
|
||||||
throw 'Failed to find app bundle in ${appBuildDirectory.path}';
|
throw 'Failed to find app bundle in ${appBuildDirectory.path}';
|
||||||
}
|
}
|
||||||
@ -1226,8 +1230,8 @@ class CompileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> _compileDebug({
|
static Future<Map<String, dynamic>> _compileDebug({
|
||||||
@required bool clean,
|
required bool clean,
|
||||||
@required String metricKey,
|
required String metricKey,
|
||||||
}) async {
|
}) async {
|
||||||
if (clean) {
|
if (clean) {
|
||||||
await flutter('clean');
|
await flutter('clean');
|
||||||
@ -1290,9 +1294,9 @@ class CompileTest {
|
|||||||
fileToMetadata[entry.path] = entry;
|
fileToMetadata[entry.path] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
final _UnzipListEntry libflutter = fileToMetadata['lib/armeabi-v7a/libflutter.so'];
|
final _UnzipListEntry libflutter = fileToMetadata['lib/armeabi-v7a/libflutter.so']!;
|
||||||
final _UnzipListEntry libapp = fileToMetadata['lib/armeabi-v7a/libapp.so'];
|
final _UnzipListEntry libapp = fileToMetadata['lib/armeabi-v7a/libapp.so']!;
|
||||||
final _UnzipListEntry license = fileToMetadata['assets/flutter_assets/NOTICES.Z'];
|
final _UnzipListEntry license = fileToMetadata['assets/flutter_assets/NOTICES.Z']!;
|
||||||
|
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'libflutter_uncompressed_bytes': libflutter.uncompressedSize,
|
'libflutter_uncompressed_bytes': libflutter.uncompressedSize,
|
||||||
@ -1315,9 +1319,9 @@ class MemoryTest {
|
|||||||
|
|
||||||
/// Completes when the log line specified in the last call to
|
/// Completes when the log line specified in the last call to
|
||||||
/// [prepareForNextMessage] is seen by `adb logcat`.
|
/// [prepareForNextMessage] is seen by `adb logcat`.
|
||||||
Future<void> get receivedNextMessage => _receivedNextMessage?.future;
|
Future<void>? get receivedNextMessage => _receivedNextMessage?.future;
|
||||||
Completer<void> _receivedNextMessage;
|
Completer<void>? _receivedNextMessage;
|
||||||
String _nextMessage;
|
String? _nextMessage;
|
||||||
|
|
||||||
/// Prepares the [receivedNextMessage] future such that it will complete
|
/// Prepares the [receivedNextMessage] future such that it will complete
|
||||||
/// when `adb logcat` sees a log line with the given `message`.
|
/// when `adb logcat` sees a log line with the given `message`.
|
||||||
@ -1328,8 +1332,8 @@ class MemoryTest {
|
|||||||
|
|
||||||
int get iterationCount => 10;
|
int get iterationCount => 10;
|
||||||
|
|
||||||
Device get device => _device;
|
Device? get device => _device;
|
||||||
Device _device;
|
Device? _device;
|
||||||
|
|
||||||
Future<TaskResult> run() {
|
Future<TaskResult> run() {
|
||||||
return inDirectory<TaskResult>(project, () async {
|
return inDirectory<TaskResult>(project, () async {
|
||||||
@ -1337,13 +1341,13 @@ class MemoryTest {
|
|||||||
// device.getMemoryStats, etc, aren't implemented for iOS.
|
// device.getMemoryStats, etc, aren't implemented for iOS.
|
||||||
|
|
||||||
_device = await devices.workingDevice;
|
_device = await devices.workingDevice;
|
||||||
await device.unlock();
|
await device!.unlock();
|
||||||
await flutter('packages', options: <String>['get']);
|
await flutter('packages', options: <String>['get']);
|
||||||
|
|
||||||
final StreamSubscription<String> adb = device.logcat.listen(
|
final StreamSubscription<String> adb = device!.logcat.listen(
|
||||||
(String data) {
|
(String data) {
|
||||||
if (data.contains('==== MEMORY BENCHMARK ==== $_nextMessage ===='))
|
if (data.contains('==== MEMORY BENCHMARK ==== $_nextMessage ===='))
|
||||||
_receivedNextMessage.complete();
|
_receivedNextMessage?.complete();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1356,12 +1360,12 @@ class MemoryTest {
|
|||||||
assert(_endMemory.length == iteration + 1);
|
assert(_endMemory.length == iteration + 1);
|
||||||
assert(_diffMemory.length == iteration + 1);
|
assert(_diffMemory.length == iteration + 1);
|
||||||
print('terminating...');
|
print('terminating...');
|
||||||
await device.stop(package);
|
await device!.stop(package);
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
await adb.cancel();
|
await adb.cancel();
|
||||||
await flutter('install', options: <String>['--uninstall-only', '-d', device.deviceId]);
|
await flutter('install', options: <String>['--uninstall-only', '-d', device!.deviceId]);
|
||||||
|
|
||||||
final ListStatistics startMemoryStatistics = ListStatistics(_startMemory);
|
final ListStatistics startMemoryStatistics = ListStatistics(_startMemory);
|
||||||
final ListStatistics endMemoryStatistics = ListStatistics(_endMemory);
|
final ListStatistics endMemoryStatistics = ListStatistics(_endMemory);
|
||||||
@ -1392,7 +1396,7 @@ class MemoryTest {
|
|||||||
'--verbose',
|
'--verbose',
|
||||||
'--release',
|
'--release',
|
||||||
'--no-resident',
|
'--no-resident',
|
||||||
'-d', device.deviceId,
|
'-d', device!.deviceId,
|
||||||
test,
|
test,
|
||||||
]);
|
]);
|
||||||
print('awaiting "ready" message...');
|
print('awaiting "ready" message...');
|
||||||
@ -1411,7 +1415,7 @@ class MemoryTest {
|
|||||||
|
|
||||||
prepareForNextMessage('DONE');
|
prepareForNextMessage('DONE');
|
||||||
print('tapping device...');
|
print('tapping device...');
|
||||||
await device.tap(100, 100);
|
await device!.tap(100, 100);
|
||||||
print('awaiting "done" message...');
|
print('awaiting "done" message...');
|
||||||
await receivedNextMessage;
|
await receivedNextMessage;
|
||||||
|
|
||||||
@ -1422,23 +1426,23 @@ class MemoryTest {
|
|||||||
final List<int> _endMemory = <int>[];
|
final List<int> _endMemory = <int>[];
|
||||||
final List<int> _diffMemory = <int>[];
|
final List<int> _diffMemory = <int>[];
|
||||||
|
|
||||||
Map<String, dynamic> _startMemoryUsage;
|
Map<String, dynamic>? _startMemoryUsage;
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Future<void> recordStart() async {
|
Future<void> recordStart() async {
|
||||||
assert(_startMemoryUsage == null);
|
assert(_startMemoryUsage == null);
|
||||||
print('snapshotting memory usage...');
|
print('snapshotting memory usage...');
|
||||||
_startMemoryUsage = await device.getMemoryStats(package);
|
_startMemoryUsage = await device!.getMemoryStats(package);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Future<void> recordEnd() async {
|
Future<void> recordEnd() async {
|
||||||
assert(_startMemoryUsage != null);
|
assert(_startMemoryUsage != null);
|
||||||
print('snapshotting memory usage...');
|
print('snapshotting memory usage...');
|
||||||
final Map<String, dynamic> endMemoryUsage = await device.getMemoryStats(package);
|
final Map<String, dynamic> endMemoryUsage = await device!.getMemoryStats(package);
|
||||||
_startMemory.add(_startMemoryUsage['total_kb'] as int);
|
_startMemory.add(_startMemoryUsage!['total_kb'] as int);
|
||||||
_endMemory.add(endMemoryUsage['total_kb'] as int);
|
_endMemory.add(endMemoryUsage['total_kb'] as int);
|
||||||
_diffMemory.add((endMemoryUsage['total_kb'] as int) - (_startMemoryUsage['total_kb'] as int));
|
_diffMemory.add((endMemoryUsage['total_kb'] as int) - (_startMemoryUsage!['total_kb'] as int));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1487,7 +1491,7 @@ class DevToolsMemoryTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Device _device;
|
late Device _device;
|
||||||
|
|
||||||
static const String _kJsonFileName = 'devtools_memory.json';
|
static const String _kJsonFileName = 'devtools_memory.json';
|
||||||
}
|
}
|
||||||
@ -1505,7 +1509,6 @@ String _reportedDurationTestToString(ReportedDurationTestFlavor flavor) {
|
|||||||
case ReportedDurationTestFlavor.release:
|
case ReportedDurationTestFlavor.release:
|
||||||
return 'release';
|
return 'release';
|
||||||
}
|
}
|
||||||
throw ArgumentError('Unexpected value for enum $flavor');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReportedDurationTest {
|
class ReportedDurationTest {
|
||||||
@ -1521,8 +1524,8 @@ class ReportedDurationTest {
|
|||||||
|
|
||||||
int get iterationCount => 10;
|
int get iterationCount => 10;
|
||||||
|
|
||||||
Device get device => _device;
|
Device? get device => _device;
|
||||||
Device _device;
|
Device? _device;
|
||||||
|
|
||||||
Future<TaskResult> run() {
|
Future<TaskResult> run() {
|
||||||
return inDirectory<TaskResult>(project, () async {
|
return inDirectory<TaskResult>(project, () async {
|
||||||
@ -1530,13 +1533,13 @@ class ReportedDurationTest {
|
|||||||
// device.getMemoryStats, etc, aren't implemented for iOS.
|
// device.getMemoryStats, etc, aren't implemented for iOS.
|
||||||
|
|
||||||
_device = await devices.workingDevice;
|
_device = await devices.workingDevice;
|
||||||
await device.unlock();
|
await device!.unlock();
|
||||||
await flutter('packages', options: <String>['get']);
|
await flutter('packages', options: <String>['get']);
|
||||||
|
|
||||||
final StreamSubscription<String> adb = device.logcat.listen(
|
final StreamSubscription<String> adb = device!.logcat.listen(
|
||||||
(String data) {
|
(String data) {
|
||||||
if (durationPattern.hasMatch(data))
|
if (durationPattern.hasMatch(data))
|
||||||
durationCompleter.complete(int.parse(durationPattern.firstMatch(data).group(1)));
|
durationCompleter.complete(int.parse(durationPattern.firstMatch(data)!.group(1)!));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
print('launching $project$test on device...');
|
print('launching $project$test on device...');
|
||||||
@ -1546,13 +1549,13 @@ class ReportedDurationTest {
|
|||||||
'--no-fast-start',
|
'--no-fast-start',
|
||||||
'--${_reportedDurationTestToString(flavor)}',
|
'--${_reportedDurationTestToString(flavor)}',
|
||||||
'--no-resident',
|
'--no-resident',
|
||||||
'-d', device.deviceId,
|
'-d', device!.deviceId,
|
||||||
test,
|
test,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final int duration = await durationCompleter.future;
|
final int duration = await durationCompleter.future;
|
||||||
print('terminating...');
|
print('terminating...');
|
||||||
await device.stop(package);
|
await device!.stop(package);
|
||||||
await adb.cancel();
|
await adb.cancel();
|
||||||
|
|
||||||
_device = null;
|
_device = null;
|
||||||
@ -1607,9 +1610,9 @@ class _UnzipListEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_UnzipListEntry._({
|
_UnzipListEntry._({
|
||||||
@required this.uncompressedSize,
|
required this.uncompressedSize,
|
||||||
@required this.compressedSize,
|
required this.compressedSize,
|
||||||
@required this.path,
|
required this.path,
|
||||||
}) : assert(uncompressedSize != null),
|
}) : assert(uncompressedSize != null),
|
||||||
assert(compressedSize != null),
|
assert(compressedSize != null),
|
||||||
assert(compressedSize <= uncompressedSize),
|
assert(compressedSize <= uncompressedSize),
|
||||||
@ -1633,7 +1636,7 @@ Future<File> waitForFile(String path) async {
|
|||||||
throw StateError('Did not find vmservice out file after 400 seconds');
|
throw StateError('Did not find vmservice out file after 400 seconds');
|
||||||
}
|
}
|
||||||
|
|
||||||
String _findIosAppInBuildDirectory(String searchDirectory) {
|
String? _findIosAppInBuildDirectory(String searchDirectory) {
|
||||||
for (final FileSystemEntity entity in Directory(searchDirectory).listSync()) {
|
for (final FileSystemEntity entity in Directory(searchDirectory).listSync()) {
|
||||||
if (entity.path.endsWith('.app')) {
|
if (entity.path.endsWith('.app')) {
|
||||||
return entity.path;
|
return entity.path;
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io' show Process, Directory;
|
import 'dart:io' show Process, Directory;
|
||||||
|
|
||||||
import 'package:flutter_devicelab/framework/devices.dart' as adb;
|
import 'package:flutter_devicelab/framework/devices.dart' as adb;
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_devicelab/framework/framework.dart';
|
import 'package:flutter_devicelab/framework/framework.dart';
|
||||||
@ -31,8 +29,8 @@ class PluginTest {
|
|||||||
|
|
||||||
final String buildTarget;
|
final String buildTarget;
|
||||||
final List<String> options;
|
final List<String> options;
|
||||||
final Map<String, String> pluginCreateEnvironment;
|
final Map<String, String>? pluginCreateEnvironment;
|
||||||
final Map<String, String> appCreateEnvironment;
|
final Map<String, String>? appCreateEnvironment;
|
||||||
|
|
||||||
Future<TaskResult> call() async {
|
Future<TaskResult> call() async {
|
||||||
final Directory tempDir =
|
final Directory tempDir =
|
||||||
@ -77,7 +75,7 @@ class _FlutterProject {
|
|||||||
|
|
||||||
String get rootPath => path.join(parent.path, name);
|
String get rootPath => path.join(parent.path, name);
|
||||||
|
|
||||||
Future<void> addPlugin(String plugin, {String pluginPath}) async {
|
Future<void> addPlugin(String plugin, {String? pluginPath}) async {
|
||||||
final File pubspec = File(path.join(rootPath, 'pubspec.yaml'));
|
final File pubspec = File(path.join(rootPath, 'pubspec.yaml'));
|
||||||
String content = await pubspec.readAsString();
|
String content = await pubspec.readAsString();
|
||||||
final String dependency =
|
final String dependency =
|
||||||
@ -100,9 +98,9 @@ class _FlutterProject {
|
|||||||
List<String> options,
|
List<String> options,
|
||||||
String target,
|
String target,
|
||||||
{
|
{
|
||||||
String name,
|
required String name,
|
||||||
String template,
|
required String template,
|
||||||
Map<String, String> environment,
|
Map<String, String>? environment,
|
||||||
}) async {
|
}) async {
|
||||||
await inDirectory(directory, () async {
|
await inDirectory(directory, () async {
|
||||||
await flutter(
|
await flutter(
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert' show json;
|
import 'dart:convert' show json;
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
@ -13,7 +11,6 @@ import 'package:flutter_devicelab/framework/browser.dart';
|
|||||||
import 'package:flutter_devicelab/framework/task_result.dart';
|
import 'package:flutter_devicelab/framework/task_result.dart';
|
||||||
import 'package:flutter_devicelab/framework/utils.dart';
|
import 'package:flutter_devicelab/framework/utils.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:shelf/shelf.dart';
|
import 'package:shelf/shelf.dart';
|
||||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||||
@ -23,7 +20,7 @@ import 'package:shelf_static/shelf_static.dart';
|
|||||||
const int benchmarkServerPort = 9999;
|
const int benchmarkServerPort = 9999;
|
||||||
const int chromeDebugPort = 10000;
|
const int chromeDebugPort = 10000;
|
||||||
|
|
||||||
Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async {
|
||||||
// Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy.
|
// Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy.
|
||||||
Logger.root.level = Level.INFO;
|
Logger.root.level = Level.INFO;
|
||||||
final String macrobenchmarksDirectory = path.join(flutterDirectory.path, 'dev', 'benchmarks', 'macrobenchmarks');
|
final String macrobenchmarksDirectory = path.join(flutterDirectory.path, 'dev', 'benchmarks', 'macrobenchmarks');
|
||||||
@ -38,17 +35,17 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
]);
|
]);
|
||||||
final Completer<List<Map<String, dynamic>>> profileData = Completer<List<Map<String, dynamic>>>();
|
final Completer<List<Map<String, dynamic>>> profileData = Completer<List<Map<String, dynamic>>>();
|
||||||
final List<Map<String, dynamic>> collectedProfiles = <Map<String, dynamic>>[];
|
final List<Map<String, dynamic>> collectedProfiles = <Map<String, dynamic>>[];
|
||||||
List<String> benchmarks;
|
List<String>? benchmarks;
|
||||||
Iterator<String> benchmarkIterator;
|
late Iterator<String> benchmarkIterator;
|
||||||
|
|
||||||
// This future fixes a race condition between the web-page loading and
|
// This future fixes a race condition between the web-page loading and
|
||||||
// asking to run a benchmark, and us connecting to Chrome's DevTools port.
|
// asking to run a benchmark, and us connecting to Chrome's DevTools port.
|
||||||
// Sometime one wins. Other times, the other wins.
|
// Sometime one wins. Other times, the other wins.
|
||||||
Future<Chrome> whenChromeIsReady;
|
Future<Chrome>? whenChromeIsReady;
|
||||||
Chrome chrome;
|
Chrome? chrome;
|
||||||
io.HttpServer server;
|
late io.HttpServer server;
|
||||||
Cascade cascade = Cascade();
|
Cascade cascade = Cascade();
|
||||||
List<Map<String, dynamic>> latestPerformanceTrace;
|
List<Map<String, dynamic>>? latestPerformanceTrace;
|
||||||
cascade = cascade.add((Request request) async {
|
cascade = cascade.add((Request request) async {
|
||||||
try {
|
try {
|
||||||
chrome ??= await whenChromeIsReady;
|
chrome ??= await whenChromeIsReady;
|
||||||
@ -66,7 +63,7 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
|
|
||||||
// Trace data is null when the benchmark is not frame-based, such as RawRecorder.
|
// Trace data is null when the benchmark is not frame-based, such as RawRecorder.
|
||||||
if (latestPerformanceTrace != null) {
|
if (latestPerformanceTrace != null) {
|
||||||
final BlinkTraceSummary traceSummary = BlinkTraceSummary.fromJson(latestPerformanceTrace);
|
final BlinkTraceSummary traceSummary = BlinkTraceSummary.fromJson(latestPerformanceTrace!)!;
|
||||||
profile['totalUiFrame.average'] = traceSummary.averageTotalUIFrameTime.inMicroseconds;
|
profile['totalUiFrame.average'] = traceSummary.averageTotalUIFrameTime.inMicroseconds;
|
||||||
profile['scoreKeys'] ??= <dynamic>[]; // using dynamic for consistency with JSON
|
profile['scoreKeys'] ??= <dynamic>[]; // using dynamic for consistency with JSON
|
||||||
(profile['scoreKeys'] as List<dynamic>).add('totalUiFrame.average');
|
(profile['scoreKeys'] as List<dynamic>).add('totalUiFrame.average');
|
||||||
@ -76,10 +73,10 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
return Response.ok('Profile received');
|
return Response.ok('Profile received');
|
||||||
} else if (request.requestedUri.path.endsWith('/start-performance-tracing')) {
|
} else if (request.requestedUri.path.endsWith('/start-performance-tracing')) {
|
||||||
latestPerformanceTrace = null;
|
latestPerformanceTrace = null;
|
||||||
await chrome.beginRecordingPerformance(request.requestedUri.queryParameters['label']);
|
await chrome!.beginRecordingPerformance(request.requestedUri.queryParameters['label']!);
|
||||||
return Response.ok('Started performance tracing');
|
return Response.ok('Started performance tracing');
|
||||||
} else if (request.requestedUri.path.endsWith('/stop-performance-tracing')) {
|
} else if (request.requestedUri.path.endsWith('/stop-performance-tracing')) {
|
||||||
latestPerformanceTrace = await chrome.endRecordingPerformance();
|
latestPerformanceTrace = await chrome!.endRecordingPerformance();
|
||||||
return Response.ok('Stopped performance tracing');
|
return Response.ok('Stopped performance tracing');
|
||||||
} else if (request.requestedUri.path.endsWith('/on-error')) {
|
} else if (request.requestedUri.path.endsWith('/on-error')) {
|
||||||
final Map<String, dynamic> errorDetails = json.decode(await request.readAsString()) as Map<String, dynamic>;
|
final Map<String, dynamic> errorDetails = json.decode(await request.readAsString()) as Map<String, dynamic>;
|
||||||
@ -90,7 +87,7 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
} else if (request.requestedUri.path.endsWith('/next-benchmark')) {
|
} else if (request.requestedUri.path.endsWith('/next-benchmark')) {
|
||||||
if (benchmarks == null) {
|
if (benchmarks == null) {
|
||||||
benchmarks = (json.decode(await request.readAsString()) as List<dynamic>).cast<String>();
|
benchmarks = (json.decode(await request.readAsString()) as List<dynamic>).cast<String>();
|
||||||
benchmarkIterator = benchmarks.iterator;
|
benchmarkIterator = benchmarks!.iterator;
|
||||||
}
|
}
|
||||||
if (benchmarkIterator.moveNext()) {
|
if (benchmarkIterator.moveNext()) {
|
||||||
final String nextBenchmark = benchmarkIterator.current;
|
final String nextBenchmark = benchmarkIterator.current;
|
||||||
@ -186,7 +183,7 @@ Future<TaskResult> runWebBenchmark({ @required bool useCanvasKit }) async {
|
|||||||
}
|
}
|
||||||
return TaskResult.success(taskResult, benchmarkScoreKeys: benchmarkScoreKeys);
|
return TaskResult.success(taskResult, benchmarkScoreKeys: benchmarkScoreKeys);
|
||||||
} finally {
|
} finally {
|
||||||
unawaited(server?.close());
|
unawaited(server.close());
|
||||||
chrome?.stop();
|
chrome?.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// @dart = 2.8
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user