Unify analysis options (#108462)

This commit is contained in:
Michael Goderbauer 2022-07-28 09:07:49 -07:00 committed by GitHub
parent 8a8ed75d3e
commit 10a7c9ba22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
138 changed files with 1155 additions and 607 deletions

View File

@ -99,7 +99,7 @@ linter:
# - conditional_uri_does_not_exist # not yet tested # - conditional_uri_does_not_exist # not yet tested
# - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
- control_flow_in_finally - control_flow_in_finally
# - curly_braces_in_flow_control_structures # not required by flutter style, but developer-facing code should have this on - curly_braces_in_flow_control_structures
- depend_on_referenced_packages - depend_on_referenced_packages
- deprecated_consistency - deprecated_consistency
# - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib)

View File

@ -113,10 +113,11 @@ class ComplexLayoutState extends State<ComplexLayout> {
key: const Key('complex-scroll'), // this key is used by the driver test key: const Key('complex-scroll'), // this key is used by the driver test
controller: ScrollController(), // So that the scroll offset can be tracked controller: ScrollController(), // So that the scroll offset can be tracked
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
if (index.isEven) if (index.isEven) {
return FancyImageItem(index, key: PageStorageKey<int>(index)); return FancyImageItem(index, key: PageStorageKey<int>(index));
else } else {
return FancyGalleryItem(index, key: PageStorageKey<int>(index)); return FancyGalleryItem(index, key: PageStorageKey<int>(index));
}
}, },
), ),
), ),

View File

@ -239,8 +239,9 @@ Map<String, dynamic> scrollSummary(
// //
final double absJerk = (scrollOffset[i-1] + scrollOffset[i+1] - 2*scrollOffset[i]).abs(); final double absJerk = (scrollOffset[i-1] + scrollOffset[i+1] - 2*scrollOffset[i]).abs();
absJerkAvg += absJerk; absJerkAvg += absJerk;
if (absJerk > 0.5) if (absJerk > 0.5) {
jankyCount += 1; jankyCount += 1;
}
} }
// expect(lostFrame < 0.1 * frameTimestamp.length, true); // expect(lostFrame < 0.1 * frameTimestamp.length, true);
absJerkAvg /= frameTimestamp.length - lostFrame; absJerkAvg /= frameTimestamp.length - lostFrame;

View File

@ -18,8 +18,9 @@ void main() {
}); });
tearDownAll(() async { tearDownAll(() async {
if (driver != null) if (driver != null) {
driver.close(); driver.close();
}
}); });
Future<void> testScrollPerf(String listKey, String summaryName) async { Future<void> testScrollPerf(String listKey, String summaryName) async {

View File

@ -19,8 +19,9 @@ void main() {
}); });
tearDownAll(() async { tearDownAll(() async {
if (driver != null) if (driver != null) {
driver.close(); driver.close();
}
}); });
test('initial tree creation', () async { test('initial tree creation', () async {
@ -34,8 +35,9 @@ void main() {
}); });
final Iterable<TimelineEvent>? semanticsEvents = timeline.events?.where((TimelineEvent event) => event.name == 'SEMANTICS'); final Iterable<TimelineEvent>? semanticsEvents = timeline.events?.where((TimelineEvent event) => event.name == 'SEMANTICS');
if (semanticsEvents?.length != 2) if (semanticsEvents?.length != 2) {
fail('Expected exactly two "SEMANTICS" events, got ${semanticsEvents?.length}:\n$semanticsEvents'); fail('Expected exactly two "SEMANTICS" events, got ${semanticsEvents?.length}:\n$semanticsEvents');
}
final Duration semanticsTreeCreation = Duration(microseconds: semanticsEvents!.last.timestampMicros! - semanticsEvents.first.timestampMicros!); final Duration semanticsTreeCreation = Duration(microseconds: semanticsEvents!.last.timestampMicros! - semanticsEvents.first.timestampMicros!);
final String jsonEncoded = json.encode(<String, dynamic>{'initialSemanticsTreeCreation': semanticsTreeCreation.inMilliseconds}); final String jsonEncoded = json.encode(<String, dynamic>{'initialSemanticsTreeCreation': semanticsTreeCreation.inMilliseconds});

View File

@ -44,13 +44,16 @@ const int kExitCodeSuccess = 0;
final GetStackPointerCallback getStackPointer = () { final GetStackPointerCallback getStackPointer = () {
// Makes sure we are running on an Android arm64 device. // Makes sure we are running on an Android arm64 device.
if (!io.Platform.isAndroid) if (!io.Platform.isAndroid) {
throw 'This benchmark test can only be run on Android arm devices.'; throw 'This benchmark test can only be run on Android arm devices.';
}
final io.ProcessResult result = io.Process.runSync('getprop', <String>['ro.product.cpu.abi']); final io.ProcessResult result = io.Process.runSync('getprop', <String>['ro.product.cpu.abi']);
if (result.exitCode != 0) if (result.exitCode != 0) {
throw 'Failed to retrieve CPU information.'; throw 'Failed to retrieve CPU information.';
if (!result.stdout.toString().contains('armeabi')) }
if (!result.stdout.toString().contains('armeabi')) {
throw 'This benchmark test can only be run on Android arm devices.'; throw 'This benchmark test can only be run on Android arm devices.';
}
// Creates a block of memory to store the assembly code. // Creates a block of memory to store the assembly code.
final ffi.Pointer<ffi.Void> region = mmap(ffi.nullptr, kMemorySize, kProtRead | kProtWrite, final ffi.Pointer<ffi.Void> region = mmap(ffi.nullptr, kMemorySize, kProtRead | kProtWrite,

View File

@ -159,8 +159,9 @@ class _Tester {
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
await gesture.moveTo(location, timeStamp: currentTime); await gesture.moveTo(location, timeStamp: currentTime);
stopwatch.stop(); stopwatch.stop();
if (onDataPoint != null) if (onDataPoint != null) {
onDataPoint(stopwatch.elapsed); onDataPoint(stopwatch.elapsed);
}
await _UntilNextFrame.wait(); await _UntilNextFrame.wait();
} }

View File

@ -181,8 +181,9 @@ class _Tester {
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
await gesture.moveTo(location, timeStamp: currentTime); await gesture.moveTo(location, timeStamp: currentTime);
stopwatch.stop(); stopwatch.stop();
if (onDataPoint != null) if (onDataPoint != null) {
onDataPoint(stopwatch.elapsed); onDataPoint(stopwatch.elapsed);
}
await _UntilNextFrame.wait(); await _UntilNextFrame.wait();
} }

View File

@ -405,8 +405,9 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder {
if (shouldContinue()) { if (shouldContinue()) {
PlatformDispatcher.instance.scheduleFrame(); PlatformDispatcher.instance.scheduleFrame();
} else { } else {
for (final VoidCallback fn in _didStopCallbacks) for (final VoidCallback fn in _didStopCallbacks) {
fn(); fn();
}
_runCompleter!.complete(); _runCompleter!.complete();
} }
} }
@ -520,8 +521,9 @@ abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder {
showWidget = !showWidget; showWidget = !showWidget;
_hostState._setStateTrampoline(); _hostState._setStateTrampoline();
} else { } else {
for (final VoidCallback fn in _didStopCallbacks) for (final VoidCallback fn in _didStopCallbacks) {
fn(); fn();
}
_runCompleter!.complete(); _runCompleter!.complete();
} }
} }

View File

@ -39,13 +39,14 @@ void main() {
watch.stop(); watch.stop();
final int elapsed = watch.elapsedMicroseconds; final int elapsed = watch.elapsedMicroseconds;
final double averagePerIteration = elapsed / iteration; final double averagePerIteration = elapsed / iteration;
if (addResult) if (addResult) {
printer.addResult( printer.addResult(
description: '$name ($listenerCount listeners)', description: '$name ($listenerCount listeners)',
value: averagePerIteration * _kScale, value: averagePerIteration * _kScale,
unit: 'ns per iteration', unit: 'ns per iteration',
name: '$name$listenerCount', name: '$name$listenerCount',
); );
}
} }
} }
@ -65,13 +66,14 @@ void main() {
watch.stop(); watch.stop();
final int elapsed = watch.elapsedMicroseconds; final int elapsed = watch.elapsedMicroseconds;
final double averagePerIteration = elapsed / iteration; final double averagePerIteration = elapsed / iteration;
if (addResult) if (addResult) {
printer.addResult( printer.addResult(
description: '$name ($listenerCount listeners)', description: '$name ($listenerCount listeners)',
value: averagePerIteration * _kScale, value: averagePerIteration * _kScale,
unit: 'ns per iteration', unit: 'ns per iteration',
name: '$name$listenerCount', name: '$name$listenerCount',
); );
}
} }
} }
@ -107,13 +109,14 @@ void main() {
watch.stop(); watch.stop();
final int elapsed = watch.elapsedMicroseconds; final int elapsed = watch.elapsedMicroseconds;
final double averagePerIteration = elapsed / iteration; final double averagePerIteration = elapsed / iteration;
if (addResult) if (addResult) {
printer.addResult( printer.addResult(
description: '$name ($listenerCount listeners)', description: '$name ($listenerCount listeners)',
value: averagePerIteration * _kScale, value: averagePerIteration * _kScale,
unit: 'ns per iteration', unit: 'ns per iteration',
name: '$name$listenerCount', name: '$name$listenerCount',
); );
}
} }
} }
@ -156,13 +159,14 @@ void main() {
watch.stop(); watch.stop();
final int elapsed = watch.elapsedMicroseconds; final int elapsed = watch.elapsedMicroseconds;
final double averagePerIteration = elapsed / iteration; final double averagePerIteration = elapsed / iteration;
if (addResult) if (addResult) {
printer.addResult( printer.addResult(
description: '$name ($listenerCount listeners)', description: '$name ($listenerCount listeners)',
value: averagePerIteration * _kScale, value: averagePerIteration * _kScale,
unit: 'ns per iteration', unit: 'ns per iteration',
name: '$name$listenerCount', name: '$name$listenerCount',
); );
}
} }
} }

View File

@ -32,10 +32,12 @@ void main() {
watch.start(); watch.start();
for (int i = 0; i < _kNumIters; i += 1) { for (int i = 0; i < _kNumIters; i += 1) {
for (final PointerEvent event in velocityEventData) { for (final PointerEvent event in velocityEventData) {
if (event is PointerDownEvent || event is PointerMoveEvent) if (event is PointerDownEvent || event is PointerMoveEvent) {
tracker.addPosition(event.timeStamp, event.position); tracker.addPosition(event.timeStamp, event.position);
if (event is PointerUpEvent) }
if (event is PointerUpEvent) {
tracker.getVelocity(); tracker.getVelocity();
}
} }
} }
watch.stop(); watch.stop();

View File

@ -67,8 +67,9 @@ class StockArrow extends StatelessWidget {
} }
Color _colorForPercentChange(double percentChange) { Color _colorForPercentChange(double percentChange) {
if (percentChange > 0) if (percentChange > 0) {
return Colors.green[_colorIndexForPercentChange(percentChange)]!; return Colors.green[_colorIndexForPercentChange(percentChange)]!;
}
return Colors.red[_colorIndexForPercentChange(percentChange)]!; return Colors.red[_colorIndexForPercentChange(percentChange)]!;
} }

View File

@ -85,8 +85,9 @@ class StockHomeState extends State<StockHome> {
} }
void _handleStockModeChange(StockMode? value) { void _handleStockModeChange(StockMode? value) {
if (widget.updater != null) if (widget.updater != null) {
widget.updater(widget.configuration.copyWith(stockMode: value)); widget.updater(widget.configuration.copyWith(stockMode: value));
}
} }
void _handleStockMenu(BuildContext context, _StockMenuItem value) { void _handleStockMenu(BuildContext context, _StockMenuItem value) {
@ -239,8 +240,9 @@ class StockHomeState extends State<StockHome> {
} }
Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) { Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) {
if (_searchQuery.text.isEmpty) if (_searchQuery.text.isEmpty) {
return stocks; return stocks;
}
final RegExp regexp = RegExp(_searchQuery.text, caseSensitive: false); final RegExp regexp = RegExp(_searchQuery.text, caseSensitive: false);
return stocks.where((Stock stock) => stock.symbol.contains(regexp)); return stocks.where((Stock stock) => stock.symbol.contains(regexp));
} }

View File

@ -32,8 +32,9 @@ class StockRow extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}'; final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}';
String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%'; String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%';
if (stock.percentChange > 0) if (stock.percentChange > 0) {
changeInPrice = '+$changeInPrice'; changeInPrice = '+$changeInPrice';
}
return InkWell( return InkWell(
key: ValueKey<String>(stock.symbol), key: ValueKey<String>(stock.symbol),
onTap: _getHandler(onPressed), onTap: _getHandler(onPressed),

View File

@ -93,8 +93,9 @@ class StockSettingsState extends State<StockSettings> {
} }
void sendUpdates(StockConfiguration value) { void sendUpdates(StockConfiguration value) {
if (widget.updater != null) if (widget.updater != null) {
widget.updater(value); widget.updater(value);
}
} }
AppBar buildAppBar(BuildContext context) { AppBar buildAppBar(BuildContext context) {

View File

@ -21,8 +21,9 @@ class _StockSymbolView extends StatelessWidget {
assert(stock != null); assert(stock != null);
final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}'; final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}';
String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%'; String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%';
if (stock.percentChange > 0) if (stock.percentChange > 0) {
changeInPrice = '+$changeInPrice'; changeInPrice = '+$changeInPrice';
}
final TextStyle headings = Theme.of(context).textTheme.bodyText1!; final TextStyle headings = Theme.of(context).textTheme.bodyText1!;
return Container( return Container(

View File

@ -9,8 +9,9 @@ import 'package:stocks/stock_data.dart' as stock_data;
Element? findElementOfExactWidgetTypeGoingDown(Element node, Type targetType) { Element? findElementOfExactWidgetTypeGoingDown(Element node, Type targetType) {
void walker(Element child) { void walker(Element child) {
if (child.widget.runtimeType == targetType) if (child.widget.runtimeType == targetType) {
throw child; throw child;
}
child.visitChildElements(walker); child.visitChildElements(walker);
} }
try { try {

View File

@ -460,12 +460,14 @@ Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches =
for (int lineNumber in linesWithDeprecations) { for (int lineNumber in linesWithDeprecations) {
try { try {
final RegExpMatch? startMatch = _deprecationStartPattern.firstMatch(lines[lineNumber]); final RegExpMatch? startMatch = _deprecationStartPattern.firstMatch(lines[lineNumber]);
if (startMatch == null) if (startMatch == null) {
throw 'Deprecation notice does not match required pattern.'; throw 'Deprecation notice does not match required pattern.';
}
final String indent = startMatch.namedGroup('indent')!; final String indent = startMatch.namedGroup('indent')!;
lineNumber += 1; lineNumber += 1;
if (lineNumber >= lines.length) if (lineNumber >= lines.length) {
throw 'Incomplete deprecation notice.'; throw 'Incomplete deprecation notice.';
}
RegExpMatch? versionMatch; RegExpMatch? versionMatch;
String? message; String? message;
do { do {
@ -477,17 +479,20 @@ Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches =
} }
throw 'Deprecation notice does not match required pattern.$possibleReason'; throw 'Deprecation notice does not match required pattern.$possibleReason';
} }
if (!lines[lineNumber].startsWith("$indent '")) if (!lines[lineNumber].startsWith("$indent '")) {
throw 'Unexpected deprecation notice indent.'; throw 'Unexpected deprecation notice indent.';
}
if (message == null) { if (message == null) {
message = messageMatch.namedGroup('message'); message = messageMatch.namedGroup('message');
final String firstChar = String.fromCharCode(message!.runes.first); final String firstChar = String.fromCharCode(message!.runes.first);
if (firstChar.toUpperCase() != firstChar) if (firstChar.toUpperCase() != firstChar) {
throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo'; throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo';
}
} }
lineNumber += 1; lineNumber += 1;
if (lineNumber >= lines.length) if (lineNumber >= lines.length) {
throw 'Incomplete deprecation notice.'; throw 'Incomplete deprecation notice.';
}
versionMatch = _deprecationVersionPattern.firstMatch(lines[lineNumber]); versionMatch = _deprecationVersionPattern.firstMatch(lines[lineNumber]);
} while (versionMatch == null); } while (versionMatch == null);
final int major = int.parse(versionMatch.namedGroup('major')!); final int major = int.parse(versionMatch.namedGroup('major')!);
@ -497,20 +502,26 @@ Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches =
// There was a beta release that was mistakenly labeled 3.1.0 without a build. // There was a beta release that was mistakenly labeled 3.1.0 without a build.
final bool specialBeta = major == 3 && minor == 1 && patch == 0; final bool specialBeta = major == 3 && minor == 1 && patch == 0;
if (!specialBeta && (major > 1 || (major == 1 && minor >= 20))) { if (!specialBeta && (major > 1 || (major == 1 && minor >= 20))) {
if (!hasBuild) if (!hasBuild) {
throw 'Deprecation notice does not accurately indicate a beta branch version number; please see https://flutter.dev/docs/development/tools/sdk/releases to find the latest beta build version number.'; throw 'Deprecation notice does not accurately indicate a beta branch version number; please see https://flutter.dev/docs/development/tools/sdk/releases to find the latest beta build version number.';
}
} }
if (!message.endsWith('.') && !message.endsWith('!') && !message.endsWith('?')) if (!message.endsWith('.') && !message.endsWith('!') && !message.endsWith('?')) {
throw 'Deprecation notice should be a grammatically correct sentence and end with a period.'; throw 'Deprecation notice should be a grammatically correct sentence and end with a period.';
if (!lines[lineNumber].startsWith("$indent '")) }
if (!lines[lineNumber].startsWith("$indent '")) {
throw 'Unexpected deprecation notice indent.'; throw 'Unexpected deprecation notice indent.';
}
lineNumber += 1; lineNumber += 1;
if (lineNumber >= lines.length) if (lineNumber >= lines.length) {
throw 'Incomplete deprecation notice.'; throw 'Incomplete deprecation notice.';
if (!lines[lineNumber].contains(_deprecationEndPattern)) }
if (!lines[lineNumber].contains(_deprecationEndPattern)) {
throw 'End of deprecation notice does not match required pattern.'; throw 'End of deprecation notice does not match required pattern.';
if (!lines[lineNumber].startsWith('$indent)')) }
if (!lines[lineNumber].startsWith('$indent)')) {
throw 'Unexpected deprecation notice indent.'; throw 'Unexpected deprecation notice indent.';
}
} catch (error) { } catch (error) {
errors.add('${file.path}:${lineNumber + 1}: $error'); errors.add('${file.path}:${lineNumber + 1}: $error');
} }
@ -569,10 +580,12 @@ Future<int> _verifyNoMissingLicenseForExtension(
final List<String> errors = <String>[]; final List<String> errors = <String>[];
await for (final File file in _allFiles(workingDirectory, extension, minimumMatches: minimumMatches)) { await for (final File file in _allFiles(workingDirectory, extension, minimumMatches: minimumMatches)) {
final String contents = file.readAsStringSync().replaceAll('\r\n', '\n'); final String contents = file.readAsStringSync().replaceAll('\r\n', '\n');
if (contents.isEmpty) if (contents.isEmpty) {
continue; // let's not go down the /bin/true rabbit hole continue; // let's not go down the /bin/true rabbit hole
if (!contents.startsWith(RegExp(header + licensePattern))) }
if (!contents.startsWith(RegExp(header + licensePattern))) {
errors.add(file.path); errors.add(file.path);
}
} }
// Fail if any errors // Fail if any errors
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
@ -679,8 +692,9 @@ Future<void> verifyNoTestImports(String workingDirectory) async {
for (final File file in dartFiles) { for (final File file in dartFiles) {
for (final String line in file.readAsLinesSync()) { for (final String line in file.readAsLinesSync()) {
final Match? match = _testImportPattern.firstMatch(line); final Match? match = _testImportPattern.firstMatch(line);
if (match != null && !_exemptTestImports.contains(match.group(2))) if (match != null && !_exemptTestImports.contains(match.group(2))) {
errors.add(file.path); errors.add(file.path);
}
} }
} }
// Fail if any errors // Fail if any errors
@ -733,8 +747,9 @@ Future<void> verifyNoBadImportsInFlutter(String workingDirectory) async {
for (final String key in dependencyMap.keys) { for (final String key in dependencyMap.keys) {
for (final String dependency in dependencyMap[key]!) { for (final String dependency in dependencyMap[key]!) {
if (dependencyMap[dependency] != null) if (dependencyMap[dependency] != null) {
continue; continue;
}
// Sanity check before performing _deepSearch, to ensure there's no rogue // Sanity check before performing _deepSearch, to ensure there's no rogue
// dependencies. // dependencies.
final String validFilenames = dependencyMap.keys.map((String name) => '$name.dart').join(', '); final String validFilenames = dependencyMap.keys.map((String name) => '$name.dart').join(', ');
@ -931,8 +946,9 @@ Future<void> verifyNoRuntimeTypeInToString(String workingDirectory) async {
} }
} }
} }
if (problems.isNotEmpty) if (problems.isNotEmpty) {
exitWithError(problems); exitWithError(problems);
}
} }
Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatches = 4000 }) async { Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatches = 4000 }) async {
@ -956,11 +972,13 @@ Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatche
problems.add('${file.path}:${index + 1}: trailing U+0009 tab character'); problems.add('${file.path}:${index + 1}: trailing U+0009 tab character');
} }
} }
if (lines.isNotEmpty && lines.last == '') if (lines.isNotEmpty && lines.last == '') {
problems.add('${file.path}:${lines.length}: trailing blank line'); problems.add('${file.path}:${lines.length}: trailing blank line');
}
} }
if (problems.isNotEmpty) if (problems.isNotEmpty) {
exitWithError(problems); exitWithError(problems);
}
} }
String _bullets(String value) => ' * $value'; String _bullets(String value) => ' * $value';
@ -984,8 +1002,9 @@ Future<void> verifyIssueLinks(String workingDirectory) async {
final Set<String> suggestions = <String>{}; final Set<String> suggestions = <String>{};
final List<File> files = await _gitFiles(workingDirectory); final List<File> files = await _gitFiles(workingDirectory);
for (final File file in files) { for (final File file in files) {
if (path.basename(file.path).endsWith('_test.dart') || path.basename(file.path) == 'analyze.dart') if (path.basename(file.path).endsWith('_test.dart') || path.basename(file.path) == 'analyze.dart') {
continue; // Skip tests, they're not public-facing. continue; // Skip tests, they're not public-facing.
}
final Uint8List bytes = file.readAsBytesSync(); final Uint8List bytes = file.readAsBytesSync();
// We allow invalid UTF-8 here so that binaries don't trip us up. // We allow invalid UTF-8 here so that binaries don't trip us up.
// There's a separate test in this file that verifies that all text // There's a separate test in this file that verifies that all text
@ -1087,8 +1106,9 @@ class Hash256 {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is Hash256 return other is Hash256
&& other.a == a && other.a == a
&& other.b == b && other.b == b
@ -1481,8 +1501,9 @@ Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256>? legacyBin
utf8.decode(bytes); utf8.decode(bytes);
} on FormatException catch (error) { } on FormatException catch (error) {
final Digest digest = sha256.convert(bytes); final Digest digest = sha256.convert(bytes);
if (!legacyBinaries.contains(Hash256.fromDigest(digest))) if (!legacyBinaries.contains(Hash256.fromDigest(digest))) {
problems.add('${file.path}:${error.offset}: file is not valid UTF-8'); problems.add('${file.path}:${error.offset}: file is not valid UTF-8');
}
} }
} }
if (problems.isNotEmpty) { if (problems.isNotEmpty) {
@ -1506,11 +1527,13 @@ Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256>? legacyBin
bool _listEquals<T>(List<T> a, List<T> b) { bool _listEquals<T>(List<T> a, List<T> b) {
assert(a != null); assert(a != null);
assert(b != null); assert(b != null);
if (a.length != b.length) if (a.length != b.length) {
return false; return false;
}
for (int index = 0; index < a.length; index += 1) { for (int index = 0; index < a.length; index += 1) {
if (a[index] != b[index]) if (a[index] != b[index]) {
return false; return false;
}
} }
return true; return true;
} }
@ -1550,38 +1573,51 @@ Stream<File> _allFiles(String workingDirectory, String? extension, { required in
while (pending.isNotEmpty) { while (pending.isNotEmpty) {
final FileSystemEntity entity = pending.first; final FileSystemEntity entity = pending.first;
pending.remove(entity); pending.remove(entity);
if (path.extension(entity.path) == '.tmpl') if (path.extension(entity.path) == '.tmpl') {
continue; continue;
}
if (entity is File) { if (entity is File) {
if (!gitFileNamesSet.contains(path.canonicalize(entity.absolute.path))) if (!gitFileNamesSet.contains(path.canonicalize(entity.absolute.path))) {
continue; continue;
if (_isGeneratedPluginRegistrant(entity)) }
if (_isGeneratedPluginRegistrant(entity)) {
continue; continue;
if (path.basename(entity.path) == 'flutter_export_environment.sh') }
if (path.basename(entity.path) == 'flutter_export_environment.sh') {
continue; continue;
if (path.basename(entity.path) == 'gradlew.bat') }
if (path.basename(entity.path) == 'gradlew.bat') {
continue; continue;
if (path.basename(entity.path) == '.DS_Store') }
if (path.basename(entity.path) == '.DS_Store') {
continue; continue;
}
if (extension == null || path.extension(entity.path) == '.$extension') { if (extension == null || path.extension(entity.path) == '.$extension') {
matches += 1; matches += 1;
yield entity; yield entity;
} }
} else if (entity is Directory) { } else if (entity is Directory) {
if (File(path.join(entity.path, '.dartignore')).existsSync()) if (File(path.join(entity.path, '.dartignore')).existsSync()) {
continue; continue;
if (path.basename(entity.path) == '.git') }
if (path.basename(entity.path) == '.git') {
continue; continue;
if (path.basename(entity.path) == '.idea') }
if (path.basename(entity.path) == '.idea') {
continue; continue;
if (path.basename(entity.path) == '.gradle') }
if (path.basename(entity.path) == '.gradle') {
continue; continue;
if (path.basename(entity.path) == '.dart_tool') }
if (path.basename(entity.path) == '.dart_tool') {
continue; continue;
if (path.basename(entity.path) == '.idea') }
if (path.basename(entity.path) == '.idea') {
continue; continue;
if (path.basename(entity.path) == 'build') }
if (path.basename(entity.path) == 'build') {
continue; continue;
}
pending.addAll(entity.listSync()); pending.addAll(entity.listSync());
} }
} }
@ -1830,8 +1866,9 @@ Future<Set<String>> _findFlutterDependencies(String srcPath, List<String> errors
final Set<String> result = <String>{}; final Set<String> result = <String>{};
for (final String line in file.readAsLinesSync()) { for (final String line in file.readAsLinesSync()) {
Match? match = _importPattern.firstMatch(line); Match? match = _importPattern.firstMatch(line);
if (match != null) if (match != null) {
result.add(match.group(2)!); result.add(match.group(2)!);
}
if (checkForMeta) { if (checkForMeta) {
match = _importMetaPattern.firstMatch(line); match = _importMetaPattern.firstMatch(line);
if (match != null) { if (match != null) {
@ -1852,14 +1889,17 @@ Future<Set<String>> _findFlutterDependencies(String srcPath, List<String> errors
} }
List<T>? _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T>? seen ]) { List<T>? _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T>? seen ]) {
if (map[start] == null) if (map[start] == null) {
return null; // We catch these separately. return null; // We catch these separately.
}
for (final T key in map[start]!) { for (final T key in map[start]!) {
if (key == start) if (key == start) {
continue; // we catch these separately continue; // we catch these separately
if (seen != null && seen.contains(key)) }
if (seen != null && seen.contains(key)) {
return <T>[start, key]; return <T>[start, key];
}
final List<T>? result = _deepSearch<T>( final List<T>? result = _deepSearch<T>(
map, map,
key, key,
@ -1874,8 +1914,9 @@ List<T>? _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T>? seen ]) {
// For example a->b->a, rather than c->a->b->a. // For example a->b->a, rather than c->a->b->a.
// Since we visit every node, we know the shortest chains are those // Since we visit every node, we know the shortest chains are those
// that start and end on the loop. // that start and end on the loop.
if (result.first == result.last) if (result.first == result.last) {
return result; return result;
}
} }
} }
return null; return null;

View File

@ -396,12 +396,14 @@ class _SnippetChecker {
} }
} }
} }
if (!silent) if (!silent) {
print('Found ${sections.length} snippet code blocks'); print('Found ${sections.length} snippet code blocks');
}
for (final _Section section in sections) { for (final _Section section in sections) {
final String path = _writeSection(section).path; final String path = _writeSection(section).path;
if (sectionMap != null) if (sectionMap != null) {
sectionMap[path] = section; sectionMap[path] = section;
}
} }
} }
@ -448,8 +450,9 @@ class _SnippetChecker {
/// Invokes the analyzer on the given [directory] and returns the stdout. /// Invokes the analyzer on the given [directory] and returns the stdout.
int _runAnalyzer(Directory directory, {bool silent = true, required List<String> output}) { int _runAnalyzer(Directory directory, {bool silent = true, required List<String> output}) {
if (!silent) if (!silent) {
print('Starting analysis of code snippets.'); print('Starting analysis of code snippets.');
}
_createConfigurationFiles(directory); _createConfigurationFiles(directory);
final ProcessResult result = Process.runSync( final ProcessResult result = Process.runSync(
_flutter, _flutter,
@ -598,8 +601,9 @@ class _SnippetChecker {
exitCode = 0; exitCode = 0;
} }
if (exitCode == 0) { if (exitCode == 0) {
if (!silent) if (!silent) {
print('No analysis errors in snippets!'); print('No analysis errors in snippets!');
}
assert(analysisErrors.isEmpty); assert(analysisErrors.isEmpty);
} }
return _AnalysisResult(exitCode, analysisErrors); return _AnalysisResult(exitCode, analysisErrors);
@ -633,17 +637,19 @@ class _SnippetChecker {
// Each section of the dart code that is either split by a blank line, or with '// ...' is // Each section of the dart code that is either split by a blank line, or with '// ...' is
// treated as a separate code block. // treated as a separate code block.
if (block[index] == '' || block[index] == '// ...') { if (block[index] == '' || block[index] == '// ...') {
if (subline == null) if (subline == null) {
throw _SnippetCheckerException('${_Line(filename: line.filename, line: line.line + index, indent: line.indent)}: ' throw _SnippetCheckerException('${_Line(filename: line.filename, line: line.line + index, indent: line.indent)}: '
'Unexpected blank line or "// ..." line near start of subblock in snippet code.'); 'Unexpected blank line or "// ..." line near start of subblock in snippet code.');
}
subblocks += 1; subblocks += 1;
subsections.add(_processBlock(subline, buffer)); subsections.add(_processBlock(subline, buffer));
buffer.clear(); buffer.clear();
assert(buffer.isEmpty); assert(buffer.isEmpty);
subline = null; subline = null;
} else if (block[index].startsWith('// ')) { } else if (block[index].startsWith('// ')) {
if (buffer.length > 1) // don't include leading comments if (buffer.length > 1) {
buffer.add('/${block[index]}'); // so that it doesn't start with "// " and get caught in this again buffer.add('/${block[index]}'); // so that it doesn't start with "// " and get caught in this again
}
} else { } else {
subline ??= _Line( subline ??= _Line(
code: block[index], code: block[index],

View File

@ -133,10 +133,12 @@ class FlutterCompactFormatter {
originalResult.errorMessage = error; originalResult.errorMessage = error;
originalResult.stackTrace = stackTrace; originalResult.stackTrace = stackTrace;
} else { } else {
if (error != null) if (error != null) {
stderr.writeln(error); stderr.writeln(error);
if (stackTrace != null) }
if (stackTrace != null) {
stderr.writeln(stackTrace); stderr.writeln(stackTrace);
}
} }
break; break;
case 'print': case 'print':

View File

@ -39,12 +39,15 @@ final String engineVersionFile = path.join(flutterRoot, 'bin', 'internal', 'engi
final String flutterPluginsVersionFile = path.join(flutterRoot, 'bin', 'internal', 'flutter_plugins.version'); final String flutterPluginsVersionFile = path.join(flutterRoot, 'bin', 'internal', 'flutter_plugins.version');
String get platformFolderName { String get platformFolderName {
if (Platform.isWindows) if (Platform.isWindows) {
return 'windows-x64'; return 'windows-x64';
if (Platform.isMacOS) }
if (Platform.isMacOS) {
return 'darwin-x64'; return 'darwin-x64';
if (Platform.isLinux) }
if (Platform.isLinux) {
return 'linux-x64'; return 'linux-x64';
}
throw UnsupportedError('The platform ${Platform.operatingSystem} is not supported by this script.'); throw UnsupportedError('The platform ${Platform.operatingSystem} is not supported by this script.');
} }
final String flutterTester = path.join(flutterRoot, 'bin', 'cache', 'artifacts', 'engine', platformFolderName, 'flutter_tester$exe'); final String flutterTester = path.join(flutterRoot, 'bin', 'cache', 'artifacts', 'engine', platformFolderName, 'flutter_tester$exe');
@ -187,8 +190,9 @@ Future<void> main(List<String> args) async {
} }
} }
flutterTestArgs.removeWhere((String arg) => removeArgs.contains(arg)); flutterTestArgs.removeWhere((String arg) => removeArgs.contains(arg));
if (Platform.environment.containsKey(CIRRUS_TASK_NAME)) if (Platform.environment.containsKey(CIRRUS_TASK_NAME)) {
print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}'); print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}');
}
print('' * 80); print('' * 80);
await selectShard(<String, ShardRunner>{ await selectShard(<String, ShardRunner>{
'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests, 'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests,
@ -327,8 +331,9 @@ Future<void> _runTestHarnessTests() async {
// Verify that we correctly generated the version file. // Verify that we correctly generated the version file.
final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version'))); final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version')));
if (versionError != null) if (versionError != null) {
exitWithError(<String>[versionError]); exitWithError(<String>[versionError]);
}
} }
final String _toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools'); final String _toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools');
@ -1748,8 +1753,9 @@ Future<void> _runFlutterTest(String workingDirectory, {
]; ];
final bool shouldProcessOutput = useFlutterTestFormatter && !expectFailure && !options.contains('--coverage'); final bool shouldProcessOutput = useFlutterTestFormatter && !expectFailure && !options.contains('--coverage');
if (shouldProcessOutput) if (shouldProcessOutput) {
args.add('--machine'); args.add('--machine');
}
if (script != null) { if (script != null) {
final String fullScriptPath = path.join(workingDirectory, script); final String fullScriptPath = path.join(workingDirectory, script);
@ -1757,8 +1763,9 @@ Future<void> _runFlutterTest(String workingDirectory, {
print('${red}Could not find test$reset: $green$fullScriptPath$reset'); print('${red}Could not find test$reset: $green$fullScriptPath$reset');
print('Working directory: $cyan$workingDirectory$reset'); print('Working directory: $cyan$workingDirectory$reset');
print('Script: $green$script$reset'); print('Script: $green$script$reset');
if (!printOutput) if (!printOutput) {
print('This is one of the tests that does not normally print output.'); print('This is one of the tests that does not normally print output.');
}
exit(1); exit(1);
} }
args.add(script); args.add(script);
@ -1782,8 +1789,9 @@ Future<void> _runFlutterTest(String workingDirectory, {
if (outputChecker != null) { if (outputChecker != null) {
final String? message = outputChecker(result); final String? message = outputChecker(result);
if (message != null) if (message != null) {
exitWithError(<String>[message]); exitWithError(<String>[message]);
}
} }
return; return;
} }
@ -1862,12 +1870,15 @@ Future<String?> verifyVersion(File file) async {
final RegExp pattern = RegExp( final RegExp pattern = RegExp(
r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$'); r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$');
final String version = await file.readAsString(); final String version = await file.readAsString();
if (!file.existsSync()) if (!file.existsSync()) {
return 'The version logic failed to create the Flutter version file.'; return 'The version logic failed to create the Flutter version file.';
if (version == '0.0.0-unknown') }
if (version == '0.0.0-unknown') {
return 'The version logic failed to determine the Flutter version.'; return 'The version logic failed to determine the Flutter version.';
if (!version.contains(pattern)) }
if (!version.contains(pattern)) {
return 'The version logic generated an invalid version string: "$version".'; return 'The version logic generated an invalid version string: "$version".';
}
return null; return null;
} }

View File

@ -59,8 +59,9 @@ String get clock {
String prettyPrintDuration(Duration duration) { String prettyPrintDuration(Duration duration) {
String result = ''; String result = '';
final int minutes = duration.inMinutes; final int minutes = duration.inMinutes;
if (minutes > 0) if (minutes > 0) {
result += '${minutes}min '; result += '${minutes}min ';
}
final int seconds = duration.inSeconds - minutes * 60; final int seconds = duration.inSeconds - minutes * 60;
final int milliseconds = duration.inMilliseconds - (seconds * 1000 + minutes * 60 * 1000); final int milliseconds = duration.inMilliseconds - (seconds * 1000 + minutes * 60 * 1000);
result += '$seconds.${milliseconds.toString().padLeft(3, "0")}s'; result += '$seconds.${milliseconds.toString().padLeft(3, "0")}s';

View File

@ -8,6 +8,5 @@ analyzer:
linter: linter:
rules: rules:
avoid_catches_without_on_clauses: true avoid_catches_without_on_clauses: true
curly_braces_in_flow_control_structures: true
prefer_relative_imports: true prefer_relative_imports: true
unawaited_futures: true unawaited_futures: true

View File

@ -31,42 +31,54 @@ class CustomerTest {
test.add(line.substring(5)); test.add(line.substring(5));
} else if (line.startsWith('test.windows=')) { } else if (line.startsWith('test.windows=')) {
hasTests = true; hasTests = true;
if (Platform.isWindows) if (Platform.isWindows) {
test.add(line.substring(13)); test.add(line.substring(13));
}
} else if (line.startsWith('test.macos=')) { } else if (line.startsWith('test.macos=')) {
hasTests = true; hasTests = true;
if (Platform.isMacOS) if (Platform.isMacOS) {
test.add(line.substring(11)); test.add(line.substring(11));
}
} else if (line.startsWith('test.linux=')) { } else if (line.startsWith('test.linux=')) {
hasTests = true; hasTests = true;
if (Platform.isLinux) if (Platform.isLinux) {
test.add(line.substring(11)); test.add(line.substring(11));
}
} else if (line.startsWith('test.posix=')) { } else if (line.startsWith('test.posix=')) {
hasTests = true; hasTests = true;
if (Platform.isLinux || Platform.isMacOS) if (Platform.isLinux || Platform.isMacOS) {
test.add(line.substring(11)); test.add(line.substring(11));
}
} else { } else {
throw FormatException('${errorPrefix}Unexpected directive:\n$line'); throw FormatException('${errorPrefix}Unexpected directive:\n$line');
} }
} }
if (contacts.isEmpty) if (contacts.isEmpty) {
throw FormatException('${errorPrefix}No contacts specified. At least one contact e-mail address must be specified.'); throw FormatException('${errorPrefix}No contacts specified. At least one contact e-mail address must be specified.');
for (final String email in contacts) {
if (!email.contains(_email) || email.endsWith('@example.com'))
throw FormatException('${errorPrefix}The following e-mail address appears to be an invalid e-mail address: $email');
} }
if (fetch.isEmpty) for (final String email in contacts) {
if (!email.contains(_email) || email.endsWith('@example.com')) {
throw FormatException('${errorPrefix}The following e-mail address appears to be an invalid e-mail address: $email');
}
}
if (fetch.isEmpty) {
throw FormatException('${errorPrefix}No "fetch" directives specified. Two lines are expected: "git clone https://github.com/USERNAME/REPOSITORY.git tests" and "git -C tests checkout HASH".'); throw FormatException('${errorPrefix}No "fetch" directives specified. Two lines are expected: "git clone https://github.com/USERNAME/REPOSITORY.git tests" and "git -C tests checkout HASH".');
if (fetch.length < 2) }
if (fetch.length < 2) {
throw FormatException('${errorPrefix}Only one "fetch" directive specified. Two lines are expected: "git clone https://github.com/USERNAME/REPOSITORY.git tests" and "git -C tests checkout HASH".'); throw FormatException('${errorPrefix}Only one "fetch" directive specified. Two lines are expected: "git clone https://github.com/USERNAME/REPOSITORY.git tests" and "git -C tests checkout HASH".');
if (!fetch[0].contains(_fetch1)) }
if (!fetch[0].contains(_fetch1)) {
throw FormatException('${errorPrefix}First "fetch" directive does not match expected pattern (expected "git clone https://github.com/USERNAME/REPOSITORY.git tests").'); throw FormatException('${errorPrefix}First "fetch" directive does not match expected pattern (expected "git clone https://github.com/USERNAME/REPOSITORY.git tests").');
if (!fetch[1].contains(_fetch2)) }
if (!fetch[1].contains(_fetch2)) {
throw FormatException('${errorPrefix}Second "fetch" directive does not match expected pattern (expected "git -C tests checkout HASH").'); throw FormatException('${errorPrefix}Second "fetch" directive does not match expected pattern (expected "git -C tests checkout HASH").');
if (update.isEmpty) }
if (update.isEmpty) {
throw FormatException('${errorPrefix}No "update" directives specified. At least one directory must be specified. (It can be "." to just upgrade the root of the repository.)'); throw FormatException('${errorPrefix}No "update" directives specified. At least one directory must be specified. (It can be "." to just upgrade the root of the repository.)');
if (!hasTests) }
if (!hasTests) {
throw FormatException('${errorPrefix}No "test" directives specified. At least one command must be specified to run tests.'); throw FormatException('${errorPrefix}No "test" directives specified. At least one command must be specified to run tests.');
}
return CustomerTest._( return CustomerTest._(
List<String>.unmodifiable(contacts), List<String>.unmodifiable(contacts),
List<String>.unmodifiable(fetch), List<String>.unmodifiable(fetch),

View File

@ -17,8 +17,9 @@ Future<bool> runTests({
int shardIndex = 0, int shardIndex = 0,
required List<File> files, required List<File> files,
}) async { }) async {
if (verbose) if (verbose) {
print('Starting run_tests.dart...'); print('Starting run_tests.dart...');
}
// Best attempt at evenly splitting tests among the shards // Best attempt at evenly splitting tests among the shards
final List<File> shardedFiles = <File>[]; final List<File> shardedFiles = <File>[];
@ -46,18 +47,21 @@ Future<bool> runTests({
} else { } else {
print('Tests:'); print('Tests:');
} }
for (final File file in shardedFiles) for (final File file in shardedFiles) {
print(file.path); print(file.path);
}
} }
print(''); print('');
for (final File file in shardedFiles) { for (final File file in shardedFiles) {
if (verbose) if (verbose) {
print('Processing ${file.path}...'); print('Processing ${file.path}...');
}
void printHeader() { void printHeader() {
if (!verbose) if (!verbose) {
print('Processing ${file.path}...'); print('Processing ${file.path}...');
}
} }
void failure(String message) { void failure(String message) {
@ -82,8 +86,9 @@ Future<bool> runTests({
bool success = true; bool success = true;
final Directory checkout = Directory.systemTemp.createTempSync('flutter_customer_testing.${path.basenameWithoutExtension(file.path)}.'); final Directory checkout = Directory.systemTemp.createTempSync('flutter_customer_testing.${path.basenameWithoutExtension(file.path)}.');
if (verbose) if (verbose) {
print('Created temporary directory: ${checkout.path}'); print('Created temporary directory: ${checkout.path}');
}
try { try {
assert(instructions.fetch.isNotEmpty); assert(instructions.fetch.isNotEmpty);
for (final String fetchCommand in instructions.fetch) { for (final String fetchCommand in instructions.fetch) {
@ -105,8 +110,9 @@ Future<bool> runTests({
final Directory customerRepo = Directory(path.join(checkout.path, 'tests')); final Directory customerRepo = Directory(path.join(checkout.path, 'tests'));
for (final Directory updateDirectory in instructions.update) { for (final Directory updateDirectory in instructions.update) {
final Directory resolvedUpdateDirectory = Directory(path.join(customerRepo.path, updateDirectory.path)); final Directory resolvedUpdateDirectory = Directory(path.join(customerRepo.path, updateDirectory.path));
if (verbose) if (verbose) {
print('Updating code in ${resolvedUpdateDirectory.path}...'); print('Updating code in ${resolvedUpdateDirectory.path}...');
}
if (!File(path.join(resolvedUpdateDirectory.path, 'pubspec.yaml')).existsSync()) { if (!File(path.join(resolvedUpdateDirectory.path, 'pubspec.yaml')).existsSync()) {
failure('The directory ${updateDirectory.path}, which was specified as an update directory, does not contain a "pubspec.yaml" file.'); failure('The directory ${updateDirectory.path}, which was specified as an update directory, does not contain a "pubspec.yaml" file.');
success = false; success = false;
@ -124,11 +130,13 @@ Future<bool> runTests({
} }
} }
if (success) { if (success) {
if (verbose) if (verbose) {
print('Running tests...'); print('Running tests...');
}
for (int iteration = 0; iteration < repeat; iteration += 1) { for (int iteration = 0; iteration < repeat; iteration += 1) {
if (verbose && repeat > 1) if (verbose && repeat > 1) {
print('Round ${iteration + 1} of $repeat.'); print('Round ${iteration + 1} of $repeat.');
}
for (final String testCommand in instructions.tests) { for (final String testCommand in instructions.tests) {
testCount += 1; testCount += 1;
success = await shell(testCommand, customerRepo, verbose: verbose, failedCallback: printHeader); success = await shell(testCommand, customerRepo, verbose: verbose, failedCallback: printHeader);
@ -138,13 +146,15 @@ Future<bool> runTests({
} }
} }
} }
if (verbose && success) if (verbose && success) {
print('Tests finished.'); print('Tests finished.');
}
} }
} }
} finally { } finally {
if (verbose) if (verbose) {
print('Deleting temporary directory...'); print('Deleting temporary directory...');
}
try { try {
checkout.deleteSync(recursive: true); checkout.deleteSync(recursive: true);
} on FileSystemException { } on FileSystemException {
@ -155,8 +165,9 @@ Future<bool> runTests({
final String s = instructions.contacts.length == 1 ? '' : 's'; final String s = instructions.contacts.length == 1 ? '' : 's';
print('Contact$s: ${instructions.contacts.join(", ")}'); print('Contact$s: ${instructions.contacts.join(", ")}');
} }
if (verbose || !success) if (verbose || !success) {
print(''); print('');
}
} }
if (failures > 0) { if (failures > 0) {
final String s = failures == 1 ? '' : 's'; final String s = failures == 1 ? '' : 's';
@ -170,8 +181,9 @@ Future<bool> runTests({
final RegExp _spaces = RegExp(r' +'); final RegExp _spaces = RegExp(r' +');
Future<bool> shell(String command, Directory directory, { bool verbose = false, bool silentFailure = false, void Function()? failedCallback }) async { Future<bool> shell(String command, Directory directory, { bool verbose = false, bool silentFailure = false, void Function()? failedCallback }) async {
if (verbose) if (verbose) {
print('>> $command'); print('>> $command');
}
Process process; Process process;
if (Platform.isWindows) { if (Platform.isWindows) {
process = await Process.start('CMD.EXE', <String>['/S', '/C', command], workingDirectory: directory.path); process = await Process.start('CMD.EXE', <String>['/S', '/C', command], workingDirectory: directory.path);
@ -183,11 +195,13 @@ Future<bool> shell(String command, Directory directory, { bool verbose = false,
utf8.decoder.bind(process.stdout).transform(const LineSplitter()).listen(verbose ? printLog : output.add); utf8.decoder.bind(process.stdout).transform(const LineSplitter()).listen(verbose ? printLog : output.add);
utf8.decoder.bind(process.stderr).transform(const LineSplitter()).listen(verbose ? printLog : output.add); utf8.decoder.bind(process.stderr).transform(const LineSplitter()).listen(verbose ? printLog : output.add);
final bool success = await process.exitCode == 0; final bool success = await process.exitCode == 0;
if (success || silentFailure) if (success || silentFailure) {
return success; return success;
}
if (!verbose) { if (!verbose) {
if (failedCallback != null) if (failedCallback != null) {
failedCallback(); failedCallback();
}
print('>> $command'); print('>> $command');
output.forEach(printLog); output.forEach(printLog);
} }

View File

@ -92,8 +92,9 @@ Future<bool> run(List<String> arguments) async {
if (help || repeat == null || files.isEmpty || numberShards == null || numberShards <= 0 || shardIndex == null || shardIndex < 0) { if (help || repeat == null || files.isEmpty || numberShards == null || numberShards <= 0 || shardIndex == null || shardIndex < 0) {
printHelp(); printHelp();
if (verbose) { if (verbose) {
if (repeat == null) if (repeat == null) {
print('Error: Could not parse repeat count ("${parsedArguments['repeat']}")'); print('Error: Could not parse repeat count ("${parsedArguments['repeat']}")');
}
if (numberShards == null) { if (numberShards == null) {
print('Error: Could not parse shards count ("${parsedArguments['shards']}")'); print('Error: Could not parse shards count ("${parsedArguments['shards']}")');
} else if (numberShards < 1) { } else if (numberShards < 1) {
@ -121,8 +122,9 @@ Future<bool> run(List<String> arguments) async {
return false; return false;
} }
if (files.length < numberShards) if (files.length < numberShards) {
print('Warning: There are more shards than tests. Some shards will not run any tests.'); print('Warning: There are more shards than tests. Some shards will not run any tests.');
}
return runTests( return runTests(
repeat: repeat, repeat: repeat,

View File

@ -19,8 +19,9 @@ Future<void> main() async {
section('Find Java'); section('Find Java');
final String? javaHome = await findJavaHome(); final String? javaHome = await findJavaHome();
if (javaHome == null) if (javaHome == null) {
return TaskResult.failure('Could not find Java'); return TaskResult.failure('Could not find Java');
}
print('\nUsing JAVA_HOME=$javaHome'); print('\nUsing JAVA_HOME=$javaHome');
section('Create project'); section('Create project');

View File

@ -19,8 +19,9 @@ Future<void> main() async {
section('Find Java'); section('Find Java');
final String? javaHome = await findJavaHome(); final String? javaHome = await findJavaHome();
if (javaHome == null) if (javaHome == null) {
return TaskResult.failure('Could not find Java'); return TaskResult.failure('Could not find Java');
}
print('\nUsing JAVA_HOME=$javaHome'); print('\nUsing JAVA_HOME=$javaHome');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');

View File

@ -42,8 +42,9 @@ class BackButtonMemoryTest extends MemoryTest {
prepareForNextMessage('READY'); prepareForNextMessage('READY');
final String output = await device!.shellEval('am', <String>['start', '-n', '$packageName/$activityName']); final String output = await device!.shellEval('am', <String>['start', '-n', '$packageName/$activityName']);
print('adb shell am start: $output'); print('adb shell am start: $output');
if (output.contains('Error')) if (output.contains('Error')) {
fail('unable to launch activity'); fail('unable to launch activity');
}
await receivedNextMessage; await receivedNextMessage;
// Wait for the Flutter app to settle (e.g. run GCs). // Wait for the Flutter app to settle (e.g. run GCs).

View File

@ -90,12 +90,15 @@ Future<int> runTest({bool coverage = false, bool noPub = false}) async {
}); });
final int result = await analysis.exitCode; final int result = await analysis.exitCode;
clock.stop(); clock.stop();
if (result != 0) if (result != 0) {
throw Exception('flutter test failed with exit code $result'); throw Exception('flutter test failed with exit code $result');
if (badLines > 0) }
if (badLines > 0) {
throw Exception('flutter test rendered unexpected output ($badLines bad lines)'); throw Exception('flutter test rendered unexpected output ($badLines bad lines)');
if (step != TestStep.testPassed) }
if (step != TestStep.testPassed) {
throw Exception('flutter test did not finish (only reached step $step)'); throw Exception('flutter test did not finish (only reached step $step)');
}
print('elapsed time: ${clock.elapsedMilliseconds}ms'); print('elapsed time: ${clock.elapsedMilliseconds}ms');
return clock.elapsedMilliseconds; return clock.elapsedMilliseconds;
} }

View File

@ -258,19 +258,22 @@ Future<void> main() async {
]); ]);
}); });
if (result.exitCode == 0) if (result.exitCode == 0) {
throw failure( throw failure(
'Gradle did not exit with error as expected', result); 'Gradle did not exit with error as expected', result);
}
String output = '${result.stdout}\n${result.stderr}'; String output = '${result.stdout}\n${result.stderr}';
if (output.contains('GradleException') || if (output.contains('GradleException') ||
output.contains('Failed to notify') || output.contains('Failed to notify') ||
output.contains('at org.gradle')) output.contains('at org.gradle')) {
throw failure( throw failure(
'Gradle output should not contain stacktrace', result); 'Gradle output should not contain stacktrace', result);
if (!output.contains('Build failed')) }
if (!output.contains('Build failed')) {
throw failure( throw failure(
'Gradle output should contain a readable error message', 'Gradle output should contain a readable error message',
result); result);
}
section('flutter build apk on build script with error'); section('flutter build apk on build script with error');
await project.introduceError(); await project.introduceError();
@ -280,18 +283,21 @@ Future<void> main() async {
'--release', '--release',
]); ]);
}); });
if (result.exitCode == 0) if (result.exitCode == 0) {
throw failure( throw failure(
'flutter build apk should fail when Gradle does', result); 'flutter build apk should fail when Gradle does', result);
}
output = '${result.stdout}\n${result.stderr}'; output = '${result.stdout}\n${result.stderr}';
if (!output.contains('Build failed')) if (!output.contains('Build failed')) {
throw failure( throw failure(
'flutter build apk output should contain a readable Gradle error message', 'flutter build apk output should contain a readable Gradle error message',
result); result);
if (hasMultipleOccurrences(output, 'Build failed')) }
if (hasMultipleOccurrences(output, 'Build failed')) {
throw failure( throw failure(
'flutter build apk should not invoke Gradle repeatedly on error', 'flutter build apk should not invoke Gradle repeatedly on error',
result); result);
}
}); });
await runProjectTest((FlutterProject project) async { await runProjectTest((FlutterProject project) async {
@ -303,12 +309,14 @@ Future<void> main() async {
'--release', '--release',
]); ]);
}); });
if (result.exitCode == 0) if (result.exitCode == 0) {
throw failure( throw failure(
'Gradle did not exit with error as expected', result); 'Gradle did not exit with error as expected', result);
}
final String output = '${result.stdout}\n${result.stderr}'; final String output = '${result.stdout}\n${result.stderr}';
if (!output.contains('No file or variants found for asset: lib/gallery/example_code.dart.')) if (!output.contains('No file or variants found for asset: lib/gallery/example_code.dart.')) {
throw failure(output, result); throw failure(output, result);
}
}); });
return TaskResult.success(null); return TaskResult.success(null);

View File

@ -23,8 +23,9 @@ Future<void> main() async {
section('Find Java'); section('Find Java');
final String? javaHome = await findJavaHome(); final String? javaHome = await findJavaHome();
if (javaHome == null) if (javaHome == null) {
return TaskResult.failure('Could not find Java'); return TaskResult.failure('Could not find Java');
}
print('\nUsing JAVA_HOME=$javaHome'); print('\nUsing JAVA_HOME=$javaHome');
section('Create Flutter module project'); section('Create Flutter module project');

View File

@ -26,8 +26,9 @@ Future<void> main() async {
section('Find Java'); section('Find Java');
final String? javaHome = await findJavaHome(); final String? javaHome = await findJavaHome();
if (javaHome == null) if (javaHome == null) {
return TaskResult.failure('Could not find Java'); return TaskResult.failure('Could not find Java');
}
print('\nUsing JAVA_HOME=$javaHome'); print('\nUsing JAVA_HOME=$javaHome');
section('Create Flutter module project'); section('Create Flutter module project');

View File

@ -66,8 +66,9 @@ void main() {
}); });
unawaited(run.exitCode.then<void>((int exitCode) { ok = false; })); unawaited(run.exitCode.then<void>((int exitCode) { ok = false; }));
await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]); await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]);
if (!ok) if (!ok) {
throw 'Failed to run test app.'; throw 'Failed to run test app.';
}
print('drive: starting...'); print('drive: starting...');
final Process drive = await startProcess( final Process drive = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'), path.join(flutterDirectory.path, 'bin', 'flutter'),
@ -90,11 +91,13 @@ void main() {
await flutter('install', options: <String>[ await flutter('install', options: <String>[
'--uninstall-only', '--uninstall-only',
]); ]);
if (result != 0) if (result != 0) {
throw 'Failed to drive test app (exit code $result).'; throw 'Failed to drive test app (exit code $result).';
}
result = await run.exitCode; result = await run.exitCode;
if (result != 0) if (result != 0) {
throw 'Received unexpected exit code $result from run process.'; throw 'Received unexpected exit code $result from run process.';
}
}); });
return TaskResult.success(null); return TaskResult.success(null);
}); });

View File

@ -52,8 +52,9 @@ void main() {
}); });
unawaited(run.exitCode.then<void>((int exitCode) { ok = false; })); unawaited(run.exitCode.then<void>((int exitCode) { ok = false; }));
await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]); await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]);
if (!ok) if (!ok) {
throw 'Failed to run test app.'; throw 'Failed to run test app.';
}
final VmService client = await vmServiceConnectUri('ws://localhost:$vmServicePort/ws'); final VmService client = await vmServiceConnectUri('ws://localhost:$vmServicePort/ws');
final VM vm = await client.getVM(); final VM vm = await client.getVM();
@ -114,14 +115,16 @@ void main() {
run.stdin.write('q'); run.stdin.write('q');
final int result = await run.exitCode; final int result = await run.exitCode;
if (result != 0) if (result != 0) {
throw 'Received unexpected exit code $result from run process.'; throw 'Received unexpected exit code $result from run process.';
}
}); });
return TaskResult.success(null); return TaskResult.success(null);
}); });
} }
void expect(bool value) { void expect(bool value) {
if (!value) if (!value) {
throw 'failed assertion in service extensions test'; throw 'failed assertion in service extensions test';
}
} }

View File

@ -11,7 +11,7 @@ Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.fake; deviceOperatingSystem = DeviceOperatingSystem.fake;
await task(() async { await task(() async {
final Device device = await devices.workingDevice; final Device device = await devices.workingDevice;
if (device.deviceId == 'FAKE_SUCCESS') if (device.deviceId == 'FAKE_SUCCESS') {
return TaskResult.success(<String, dynamic>{ return TaskResult.success(<String, dynamic>{
'metric1': 42, 'metric1': 42,
'metric2': 123, 'metric2': 123,
@ -20,7 +20,8 @@ Future<void> main() async {
'metric1', 'metric1',
'metric2', 'metric2',
]); ]);
else } else {
return TaskResult.failure('Failed'); return TaskResult.failure('Failed');
}
}); });
} }

View File

@ -36,35 +36,46 @@ final RegExp dartVersionPattern = RegExp(r'// *@dart *= *(\d+).(\d+)');
final Version firstNullSafeDartVersion = Version(2, 12, 0); final Version firstNullSafeDartVersion = Version(2, 12, 0);
Future<double> findCostsForFile(File file) async { Future<double> findCostsForFile(File file) async {
if (path.extension(file.path) == '.py') if (path.extension(file.path) == '.py') {
return pythonCost; return pythonCost;
}
if (path.extension(file.path) != '.dart' && if (path.extension(file.path) != '.dart' &&
path.extension(file.path) != '.yaml' && path.extension(file.path) != '.yaml' &&
path.extension(file.path) != '.sh') path.extension(file.path) != '.sh') {
return 0.0; return 0.0;
}
final bool isTest = file.path.endsWith('_test.dart'); final bool isTest = file.path.endsWith('_test.dart');
final bool isDart = file.path.endsWith('.dart'); final bool isDart = file.path.endsWith('.dart');
double total = 0.0; double total = 0.0;
for (final String line in await file.readAsLines()) { for (final String line in await file.readAsLines()) {
if (line.contains(todoPattern)) if (line.contains(todoPattern)) {
total += todoCost; total += todoCost;
if (line.contains(ignorePattern)) }
if (line.contains(ignorePattern)) {
total += ignoreCost; total += ignoreCost;
if (line.contains(ignoreForFilePattern)) }
if (line.contains(ignoreForFilePattern)) {
total += ignoreForFileCost; total += ignoreForFileCost;
if (!isTest && line.contains(asDynamicPattern)) }
if (!isTest && line.contains(asDynamicPattern)) {
total += asDynamicCost; total += asDynamicCost;
if (line.contains(deprecationPattern)) }
if (line.contains(deprecationPattern)) {
total += deprecationCost; total += deprecationCost;
if (line.contains(legacyDeprecationPattern)) }
if (line.contains(legacyDeprecationPattern)) {
total += legacyDeprecationCost; total += legacyDeprecationCost;
if (isTest && line.contains('skip:') && !line.contains('[intended]')) }
if (isTest && line.contains('skip:') && !line.contains('[intended]')) {
total += skipCost; total += skipCost;
if (isDart && isOptingOutOfNullSafety(line)) }
if (isDart && isOptingOutOfNullSafety(line)) {
total += fileNullSafetyMigrationCost; total += fileNullSafetyMigrationCost;
}
} }
if (path.basename(file.path) == 'pubspec.yaml' && !packageIsNullSafe(file)) if (path.basename(file.path) == 'pubspec.yaml' && !packageIsNullSafe(file)) {
total += packageNullSafetyMigrationCost; total += packageNullSafetyMigrationCost;
}
return total; return total;
} }
@ -89,12 +100,14 @@ bool packageIsNullSafe(File file) {
} }
Future<int> findGlobalsForFile(File file) async { Future<int> findGlobalsForFile(File file) async {
if (path.extension(file.path) != '.dart') if (path.extension(file.path) != '.dart') {
return 0; return 0;
}
int total = 0; int total = 0;
for (final String line in await file.readAsLines()) { for (final String line in await file.readAsLines()) {
if (line.contains(globalsPattern)) if (line.contains(globalsPattern)) {
total += 1; total += 1;
}
} }
return total; return total;
} }
@ -106,11 +119,13 @@ Future<double> findCostsForRepo() async {
workingDirectory: flutterDirectory.path, workingDirectory: flutterDirectory.path,
); );
double total = 0.0; double total = 0.0;
await for (final String entry in git.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter())) await for (final String entry in git.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter())) {
total += await findCostsForFile(File(path.join(flutterDirectory.path, entry))); total += await findCostsForFile(File(path.join(flutterDirectory.path, entry)));
}
final int gitExitCode = await git.exitCode; final int gitExitCode = await git.exitCode;
if (gitExitCode != 0) if (gitExitCode != 0) {
throw Exception('git exit with unexpected error code $gitExitCode'); throw Exception('git exit with unexpected error code $gitExitCode');
}
return total; return total;
} }
@ -121,11 +136,13 @@ Future<int> findGlobalsForTool() async {
workingDirectory: flutterDirectory.path, workingDirectory: flutterDirectory.path,
); );
int total = 0; int total = 0;
await for (final String entry in git.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter())) await for (final String entry in git.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter())) {
total += await findGlobalsForFile(File(path.join(flutterDirectory.path, entry))); total += await findGlobalsForFile(File(path.join(flutterDirectory.path, entry)));
}
final int gitExitCode = await git.exitCode; final int gitExitCode = await git.exitCode;
if (gitExitCode != 0) if (gitExitCode != 0) {
throw Exception('git exit with unexpected error code $gitExitCode'); throw Exception('git exit with unexpected error code $gitExitCode');
}
return total; return total;
} }
@ -135,8 +152,9 @@ Future<int> countDependencies() async {
options: <String>['--transitive-closure'], options: <String>['--transitive-closure'],
)).split('\n'); )).split('\n');
final int count = lines.where((String line) => line.contains('->')).length; final int count = lines.where((String line) => line.contains('->')).length;
if (count < 2) // we'll always have flutter and flutter_test, at least... if (count < 2) {
throw Exception('"flutter update-packages --transitive-closure" returned bogus output:\n${lines.join("\n")}'); throw Exception('"flutter update-packages --transitive-closure" returned bogus output:\n${lines.join("\n")}');
}
return count; return count;
} }
@ -146,8 +164,9 @@ Future<int> countConsumerDependencies() async {
options: <String>['--transitive-closure', '--consumer-only'], options: <String>['--transitive-closure', '--consumer-only'],
)).split('\n'); )).split('\n');
final int count = lines.where((String line) => line.contains('->')).length; final int count = lines.where((String line) => line.contains('->')).length;
if (count < 2) // we'll always have flutter and flutter_test, at least... if (count < 2) {
throw Exception('"flutter update-packages --transitive-closure" returned bogus output:\n${lines.join("\n")}'); throw Exception('"flutter update-packages --transitive-closure" returned bogus output:\n${lines.join("\n")}');
}
return count; return count;
} }

View File

@ -410,8 +410,9 @@ Future<void> _runGradleTask({
print('stderr:'); print('stderr:');
print(result.stderr); print(result.stderr);
} }
if (result.exitCode != 0) if (result.exitCode != 0) {
throw 'Gradle exited with error'; throw 'Gradle exited with error';
}
} }
Future<ProcessResult> _resultOfGradleTask({ Future<ProcessResult> _resultOfGradleTask({
@ -422,8 +423,9 @@ Future<ProcessResult> _resultOfGradleTask({
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');
}
print('\nUsing JAVA_HOME=$javaHome'); print('\nUsing JAVA_HOME=$javaHome');

View File

@ -252,8 +252,9 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
.map<AndroidDevice>((String id) => AndroidDevice(deviceId: id)) .map<AndroidDevice>((String id) => AndroidDevice(deviceId: id))
.toList(); .toList();
if (allDevices.isEmpty) if (allDevices.isEmpty) {
throw const DeviceException('No Android devices detected'); throw const DeviceException('No Android devices detected');
}
if (cpu != null) { if (cpu != null) {
for (final AndroidDevice device in allDevices) { for (final AndroidDevice device in allDevices) {
@ -268,8 +269,9 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)]; _workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
} }
if (_workingDevice == null) if (_workingDevice == null) {
throw const DeviceException('Cannot find a suitable Android device'); throw const DeviceException('Cannot find a suitable Android device');
}
print('Device chosen: $_workingDevice'); print('Device chosen: $_workingDevice');
} }
@ -300,11 +302,13 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
final List<String> results = <String>[]; final List<String> results = <String>[];
for (final String line in output) { for (final String line in output) {
// Skip lines like: * daemon started successfully * // Skip lines like: * daemon started successfully *
if (line.startsWith('* daemon ')) if (line.startsWith('* daemon ')) {
continue; continue;
}
if (line.startsWith('List of devices')) if (line.startsWith('List of devices')) {
continue; continue;
}
if (_kDeviceRegex.hasMatch(line)) { if (_kDeviceRegex.hasMatch(line)) {
final Match match = _kDeviceRegex.firstMatch(line)!; final Match match = _kDeviceRegex.firstMatch(line)!;
@ -551,15 +555,17 @@ class AndroidDevice extends Device {
/// Wake up the device if it is not awake using [togglePower]. /// Wake up the device if it is not awake using [togglePower].
@override @override
Future<void> wakeUp() async { Future<void> wakeUp() async {
if (!(await isAwake())) if (!(await isAwake())) {
await togglePower(); await togglePower();
}
} }
/// Send the device to sleep mode if it is not asleep using [togglePower]. /// Send the device to sleep mode if it is not asleep using [togglePower].
@override @override
Future<void> sendToSleep() async { Future<void> sendToSleep() async {
if (!(await isAsleep())) if (!(await isAsleep())) {
await togglePower(); await togglePower();
}
} }
/// Sends `KEYCODE_HOME` (3), which causes the device to go to the home screen. /// Sends `KEYCODE_HOME` (3), which causes the device to go to the home screen.
@ -840,8 +846,9 @@ class IosDeviceDiscovery implements DeviceDiscovery {
.map<IosDevice>((String id) => IosDevice(deviceId: id)) .map<IosDevice>((String id) => IosDevice(deviceId: id))
.toList(); .toList();
if (allDevices.isEmpty) if (allDevices.isEmpty) {
throw const DeviceException('No iOS devices detected'); throw const DeviceException('No iOS devices detected');
}
// TODO(yjbanov): filter out and warn about those with low battery level // TODO(yjbanov): filter out and warn about those with low battery level
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)]; _workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
@ -1211,8 +1218,9 @@ String get adbPath {
final String adbPath = path.join(androidHome, 'platform-tools/adb'); final String adbPath = path.join(androidHome, 'platform-tools/adb');
if (!canRun(adbPath)) if (!canRun(adbPath)) {
throw DeviceException('adb not found at: $adbPath'); throw DeviceException('adb not found at: $adbPath');
}
return path.absolute(adbPath); return path.absolute(adbPath);
} }

View File

@ -46,8 +46,9 @@ bool _isTaskRegistered = false;
/// If no `processManager` is provided, a default [LocalProcessManager] is created /// If no `processManager` is provided, a default [LocalProcessManager] is created
/// for the task. /// for the task.
Future<TaskResult> task(TaskFunction task, { ProcessManager? processManager }) async { Future<TaskResult> task(TaskFunction task, { ProcessManager? processManager }) async {
if (_isTaskRegistered) if (_isTaskRegistered) {
throw StateError('A task is already registered'); throw StateError('A task is already registered');
}
_isTaskRegistered = true; _isTaskRegistered = true;
processManager ??= const LocalProcessManager(); processManager ??= const LocalProcessManager();
@ -163,8 +164,9 @@ class _TaskRunner {
} }
Future<TaskResult> futureResult = _performTask(); Future<TaskResult> futureResult = _performTask();
if (taskTimeout != null) if (taskTimeout != null) {
futureResult = futureResult.timeout(taskTimeout); futureResult = futureResult.timeout(taskTimeout);
}
result = await futureResult; result = await futureResult;
} finally { } finally {
@ -241,8 +243,9 @@ class _TaskRunner {
/// Causes the Dart VM to stay alive until a request to run the task is /// Causes the Dart VM to stay alive until a request to run the task is
/// received via the VM service protocol. /// received via the VM service protocol.
void keepVmAliveUntilTaskRunRequested() { void keepVmAliveUntilTaskRunRequested() {
if (_taskStarted) if (_taskStarted) {
throw StateError('Task already started.'); throw StateError('Task already started.');
}
// Merely creating this port object will cause the VM to stay alive and keep // Merely creating this port object will cause the VM to stay alive and keep
// the VM service server running until the port is disposed of. // the VM service server running until the port is disposed of.
@ -280,8 +283,9 @@ class _TaskRunner {
// are catching errors coming from arbitrary (and untrustworthy) task // are catching errors coming from arbitrary (and untrustworthy) task
// code. Our goal is to convert the failure into a readable message. // code. Our goal is to convert the failure into a readable message.
// Propagating it further is not useful. // Propagating it further is not useful.
if (!completer.isCompleted) if (!completer.isCompleted) {
completer.complete(TaskResult.failure(message)); completer.complete(TaskResult.failure(message));
}
}); });
return completer.future; return completer.future;
} }

View File

@ -156,8 +156,9 @@ Future<TaskResult> runTask(
}) async { }) async {
final String taskExecutable = 'bin/tasks/$taskName.dart'; final String taskExecutable = 'bin/tasks/$taskName.dart';
if (!file(taskExecutable).existsSync()) if (!file(taskExecutable).existsSync()) {
throw 'Executable Dart file not found: $taskExecutable'; throw 'Executable Dart file not found: $taskExecutable';
}
final Process runner = await startProcess( final Process runner = await startProcess(
dartBin, dartBin,
@ -190,8 +191,9 @@ Future<TaskResult> runTask(
.listen((String line) { .listen((String line) {
if (!uri.isCompleted) { if (!uri.isCompleted) {
final Uri? serviceUri = parseServiceUri(line, prefix: RegExp('(Observatory|The Dart VM service is) listening on ')); final Uri? serviceUri = parseServiceUri(line, prefix: RegExp('(Observatory|The Dart VM service is) listening on '));
if (serviceUri != null) if (serviceUri != null) {
uri.complete(serviceUri); uri.complete(serviceUri);
}
} }
if (!silent) { if (!silent) {
stdout.writeln('[$taskName] [STDOUT] $line'); stdout.writeln('[$taskName] [STDOUT] $line');
@ -255,12 +257,14 @@ Future<ConnectionResult> _connectToRunnerIsolate(Uri vmServiceUri) async {
} }
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) {
if (stopwatch.elapsed > const Duration(seconds: 10)) if (stopwatch.elapsed > const Duration(seconds: 10)) {
print('VM service still not ready after ${stopwatch.elapsed}: $error\nContinuing to retry...'); print('VM service still not ready after ${stopwatch.elapsed}: $error\nContinuing to retry...');
}
await Future<void>.delayed(const Duration(milliseconds: 50)); await Future<void>.delayed(const Duration(milliseconds: 50));
} }
} }

View File

@ -126,14 +126,15 @@ void copy(File sourceFile, Directory targetDirectory, {String? name}) {
} }
void recursiveCopy(Directory source, Directory target) { void recursiveCopy(Directory source, Directory target) {
if (!target.existsSync()) if (!target.existsSync()) {
target.createSync(); target.createSync();
}
for (final FileSystemEntity entity in source.listSync(followLinks: false)) { for (final FileSystemEntity entity in source.listSync(followLinks: false)) {
final String name = path.basename(entity.path); final String name = path.basename(entity.path);
if (entity is Directory && !entity.path.contains('.dart_tool')) if (entity is Directory && !entity.path.contains('.dart_tool')) {
recursiveCopy(entity, Directory(path.join(target.path, name))); recursiveCopy(entity, Directory(path.join(target.path, name)));
else if (entity is File) { } else if (entity is File) {
final File dest = File(path.join(target.path, name)); final File dest = File(path.join(target.path, name));
dest.writeAsBytesSync(entity.readAsBytesSync()); dest.writeAsBytesSync(entity.readAsBytesSync());
// Preserve executable bit // Preserve executable bit
@ -194,8 +195,9 @@ void section(String title) {
title = '╡ ••• $title ••• ╞'; title = '╡ ••• $title ••• ╞';
final String line = '' * math.max((80 - title.length) ~/ 2, 2); final String line = '' * math.max((80 - title.length) ~/ 2, 2);
output = '$line$title$line'; output = '$line$title$line';
if (output.length == 79) if (output.length == 79) {
output += ''; output += '';
}
} }
print('\n\n$output\n'); print('\n\n$output\n');
} }
@ -209,10 +211,12 @@ Future<String> getDartVersion() async {
// Dart VM version: 1.17.0-dev.2.0 (Tue May 3 12:14:52 2016) on "macos_x64" // Dart VM version: 1.17.0-dev.2.0 (Tue May 3 12:14:52 2016) on "macos_x64"
// to: // to:
// 1.17.0-dev.2.0 // 1.17.0-dev.2.0
if (version.contains('(')) if (version.contains('(')) {
version = version.substring(0, version.indexOf('(')).trim(); version = version.substring(0, version.indexOf('(')).trim();
if (version.contains(':')) }
if (version.contains(':')) {
version = version.substring(version.indexOf(':') + 1).trim(); version = version.substring(version.indexOf(':') + 1).trim();
}
return version.replaceAll('"', "'"); return version.replaceAll('"', "'");
} }
@ -295,8 +299,9 @@ Future<Process> startProcess(
} }
Future<void> forceQuitRunningProcesses() async { Future<void> forceQuitRunningProcesses() async {
if (_runningProcesses.isEmpty) if (_runningProcesses.isEmpty) {
return; return;
}
// Give normally quitting processes a chance to report their exit code. // Give normally quitting processes a chance to report their exit code.
await Future<void>.delayed(const Duration(seconds: 1)); await Future<void>.delayed(const Duration(seconds: 1));
@ -354,8 +359,9 @@ Future<int> _execute(
); );
final int exitCode = await process.exitCode; final int exitCode = await process.exitCode;
if (exitCode != 0 && !canFail) if (exitCode != 0 && !canFail) {
fail('Executable "$executable" failed with exit code $exitCode.'); fail('Executable "$executable" failed with exit code $exitCode.');
}
return exitCode; return exitCode;
} }
@ -545,8 +551,9 @@ Future<String?> findJavaHome() async {
'Java binary at: ', 'Java binary at: ',
from: await evalFlutter('doctor', options: <String>['-v']), from: await evalFlutter('doctor', options: <String>['-v']),
); );
if (hits.isEmpty) if (hits.isEmpty) {
return null; return null;
}
final String javaBinary = hits.first final String javaBinary = hits.first
.split(': ') .split(': ')
.last; .last;
@ -579,8 +586,9 @@ void cd(dynamic directory) {
throw FileSystemException('Unsupported directory type ${directory.runtimeType}', directory.toString()); throw FileSystemException('Unsupported directory type ${directory.runtimeType}', directory.toString());
} }
if (!d.existsSync()) if (!d.existsSync()) {
throw FileSystemException('Cannot cd into directory that does not exist', d.toString()); throw FileSystemException('Cannot cd into directory that does not exist', d.toString());
}
} }
Directory get flutterDirectory => Directory.current.parent.parent; Directory get flutterDirectory => Directory.current.parent.parent;
@ -590,15 +598,17 @@ Directory get openpayDirectory => Directory(requireEnvVar('OPENPAY_CHECKOUT_PATH
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) {
if (!map.containsKey(propertyName)) if (!map.containsKey(propertyName)) {
fail('Configuration property not found: $propertyName'); fail('Configuration property not found: $propertyName');
}
final T result = map[propertyName] as T; final T result = map[propertyName] as T;
return result; return result;
} }
@ -634,26 +644,36 @@ void checkNotNull(Object o1,
Object o8 = 1, Object o8 = 1,
Object o9 = 1, Object o9 = 1,
Object o10 = 1]) { Object o10 = 1]) {
if (o1 == null) if (o1 == null) {
throw 'o1 is null'; throw 'o1 is null';
if (o2 == null) }
if (o2 == null) {
throw 'o2 is null'; throw 'o2 is null';
if (o3 == null) }
if (o3 == null) {
throw 'o3 is null'; throw 'o3 is null';
if (o4 == null) }
if (o4 == null) {
throw 'o4 is null'; throw 'o4 is null';
if (o5 == null) }
if (o5 == null) {
throw 'o5 is null'; throw 'o5 is null';
if (o6 == null) }
if (o6 == null) {
throw 'o6 is null'; throw 'o6 is null';
if (o7 == null) }
if (o7 == null) {
throw 'o7 is null'; throw 'o7 is null';
if (o8 == null) }
if (o8 == null) {
throw 'o8 is null'; throw 'o8 is null';
if (o9 == null) }
if (o9 == null) {
throw 'o9 is null'; throw 'o9 is null';
if (o10 == null) }
if (o10 == null) {
throw 'o10 is null'; throw 'o10 is null';
}
} }
/// Splits [from] into lines and selects those that contain [pattern]. /// Splits [from] into lines and selects those that contain [pattern].

View File

@ -74,8 +74,9 @@ Future<Map<String, double>> readJsonResults(Process process) {
return; return;
} }
if (jsonStarted && line.contains(jsonPrefix)) if (jsonStarted && line.contains(jsonPrefix)) {
jsonBuf.writeln(line.substring(line.indexOf(jsonPrefix) + jsonPrefix.length)); jsonBuf.writeln(line.substring(line.indexOf(jsonPrefix) + jsonPrefix.length));
}
}); });
process.exitCode.then<void>((int code) async { process.exitCode.then<void>((int code) async {

View File

@ -288,8 +288,9 @@ TaskFunction createBasicMaterialCompileTest() {
await flutter('create', options: <String>['--template=app', sampleAppName]); await flutter('create', options: <String>['--template=app', sampleAppName]);
}); });
if (!sampleDir.existsSync()) if (!sampleDir.existsSync()) {
throw 'Failed to create default Flutter app in ${sampleDir.path}'; throw 'Failed to create default Flutter app in ${sampleDir.path}';
}
return CompileTest(sampleDir.path).run(); return CompileTest(sampleDir.path).run();
}; };
@ -754,8 +755,9 @@ class StartupTest {
final Map<String, dynamic> averageResults = _average(results, iterations); final Map<String, dynamic> averageResults = _average(results, iterations);
if (!reportMetrics) if (!reportMetrics) {
return TaskResult.success(averageResults); return TaskResult.success(averageResults);
}
return TaskResult.success(averageResults, benchmarkScoreKeys: <String>[ return TaskResult.success(averageResults, benchmarkScoreKeys: <String>[
'timeToFirstFrameMicros', 'timeToFirstFrameMicros',
@ -860,8 +862,9 @@ class DevtoolsStartupTest {
device.deviceId, device.deviceId,
]); ]);
if (sawLine) if (sawLine) {
return TaskResult.success(null, benchmarkScoreKeys: <String>[]); return TaskResult.success(null, benchmarkScoreKeys: <String>[]);
}
return TaskResult.failure('Did not see line "The Flutter DevTools debugger and profiler" in output'); return TaskResult.failure('Did not see line "The Flutter DevTools debugger and profiler" in output');
}); });
} }
@ -1401,8 +1404,9 @@ class CompileTest {
// IPAs are created manually, https://flutter.dev/ios-release/ // IPAs are created manually, https://flutter.dev/ios-release/
await exec('tar', <String>['-zcf', 'build/app.ipa', appPath]); await exec('tar', <String>['-zcf', 'build/app.ipa', appPath]);
releaseSizeInBytes = await file('$cwd/build/app.ipa').length(); releaseSizeInBytes = await file('$cwd/build/app.ipa').length();
if (reportPackageContentSizes) if (reportPackageContentSizes) {
metrics.addAll(await getSizesFromIosApp(appPath)); metrics.addAll(await getSizesFromIosApp(appPath));
}
break; break;
case DeviceOperatingSystem.android: case DeviceOperatingSystem.android:
case DeviceOperatingSystem.androidArm: case DeviceOperatingSystem.androidArm:
@ -1416,8 +1420,9 @@ class CompileTest {
final String apkPath = '$cwd/build/app/outputs/flutter-apk/app-release.apk'; final String apkPath = '$cwd/build/app/outputs/flutter-apk/app-release.apk';
final File apk = file(apkPath); final File apk = file(apkPath);
releaseSizeInBytes = apk.lengthSync(); releaseSizeInBytes = apk.lengthSync();
if (reportPackageContentSizes) if (reportPackageContentSizes) {
metrics.addAll(await getSizesFromApk(apkPath)); metrics.addAll(await getSizesFromApk(apkPath));
}
break; break;
case DeviceOperatingSystem.androidArm64: case DeviceOperatingSystem.androidArm64:
options.insert(0, 'apk'); options.insert(0, 'apk');
@ -1430,8 +1435,9 @@ class CompileTest {
final String apkPath = '$cwd/build/app/outputs/flutter-apk/app-release.apk'; final String apkPath = '$cwd/build/app/outputs/flutter-apk/app-release.apk';
final File apk = file(apkPath); final File apk = file(apkPath);
releaseSizeInBytes = apk.lengthSync(); releaseSizeInBytes = apk.lengthSync();
if (reportPackageContentSizes) if (reportPackageContentSizes) {
metrics.addAll(await getSizesFromApk(apkPath)); metrics.addAll(await getSizesFromApk(apkPath));
}
break; break;
case DeviceOperatingSystem.fake: case DeviceOperatingSystem.fake:
throw Exception('Unsupported option for fake devices'); throw Exception('Unsupported option for fake devices');
@ -1572,8 +1578,9 @@ class MemoryTest {
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();
}
}, },
); );
@ -1764,8 +1771,9 @@ class ReportedDurationTest {
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...');

View File

@ -158,8 +158,9 @@ class CommandArgs {
@override @override
bool operator==(Object other) { bool operator==(Object other) {
if (other.runtimeType != CommandArgs) if (other.runtimeType != CommandArgs) {
return false; return false;
}
return other is CommandArgs return other is CommandArgs
&& other.command == command && other.command == command
&& const ListEquality<String>().equals(other.arguments, arguments) && const ListEquality<String>().equals(other.arguments, arguments)

View File

@ -33,10 +33,11 @@ Future<String> dataHandler(String message) async {
}); });
completer.complete(json.encode(result)); completer.complete(json.encode(result));
} }
if (SchedulerBinding.instance.hasScheduledFrame) if (SchedulerBinding.instance.hasScheduledFrame) {
SchedulerBinding.instance.addPostFrameCallback(completeSemantics); SchedulerBinding.instance.addPostFrameCallback(completeSemantics);
else } else {
completeSemantics(); completeSemantics();
}
return completer.future; return completer.future;
} }
if (message.contains('setClipboard')) { if (message.contains('setClipboard')) {
@ -48,10 +49,11 @@ Future<String> dataHandler(String message) async {
}); });
completer.complete(''); completer.complete('');
} }
if (SchedulerBinding.instance.hasScheduledFrame) if (SchedulerBinding.instance.hasScheduledFrame) {
SchedulerBinding.instance.addPostFrameCallback(completeSetClipboard); SchedulerBinding.instance.addPostFrameCallback(completeSetClipboard);
else } else {
completeSetClipboard(); completeSetClipboard();
}
return completer.future; return completer.future;
} }
throw UnimplementedError(); throw UnimplementedError();

View File

@ -187,8 +187,9 @@ class Rect {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is Rect return other is Rect
&& other.top == top && other.top == top
&& other.left == left && other.left == left
@ -219,8 +220,9 @@ class Size {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is Size return other is Size
&& other.width == width && other.width == width
&& other.height == height; && other.height == height;

View File

@ -201,8 +201,9 @@ class AndroidSemanticsAction {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is AndroidSemanticsAction return other is AndroidSemanticsAction
&& other.id == id; && other.id == id;
} }

View File

@ -100,53 +100,74 @@ class _AndroidSemanticsMatcher extends Matcher {
@override @override
Description describe(Description description) { Description describe(Description description) {
description.add('AndroidSemanticsNode'); description.add('AndroidSemanticsNode');
if (text != null) if (text != null) {
description.add(' with text: $text'); description.add(' with text: $text');
if (contentDescription != null) }
if (contentDescription != null) {
description.add( 'with contentDescription $contentDescription'); description.add( 'with contentDescription $contentDescription');
if (className != null) }
if (className != null) {
description.add(' with className: $className'); description.add(' with className: $className');
if (id != null) }
if (id != null) {
description.add(' with id: $id'); description.add(' with id: $id');
if (actions != null) }
if (actions != null) {
description.add(' with actions: $actions'); description.add(' with actions: $actions');
if (rect != null) }
if (rect != null) {
description.add(' with rect: $rect'); description.add(' with rect: $rect');
if (size != null) }
if (size != null) {
description.add(' with size: $size'); description.add(' with size: $size');
if (isChecked != null) }
if (isChecked != null) {
description.add(' with flag isChecked: $isChecked'); description.add(' with flag isChecked: $isChecked');
if (isEditable != null) }
if (isEditable != null) {
description.add(' with flag isEditable: $isEditable'); description.add(' with flag isEditable: $isEditable');
if (isEnabled != null) }
if (isEnabled != null) {
description.add(' with flag isEnabled: $isEnabled'); description.add(' with flag isEnabled: $isEnabled');
if (isFocusable != null) }
if (isFocusable != null) {
description.add(' with flag isFocusable: $isFocusable'); description.add(' with flag isFocusable: $isFocusable');
if (isFocused != null) }
if (isFocused != null) {
description.add(' with flag isFocused: $isFocused'); description.add(' with flag isFocused: $isFocused');
if (isHeading != null) }
if (isHeading != null) {
description.add(' with flag isHeading: $isHeading'); description.add(' with flag isHeading: $isHeading');
if (isPassword != null) }
if (isPassword != null) {
description.add(' with flag isPassword: $isPassword'); description.add(' with flag isPassword: $isPassword');
if (isLongClickable != null) }
if (isLongClickable != null) {
description.add(' with flag isLongClickable: $isLongClickable'); description.add(' with flag isLongClickable: $isLongClickable');
}
return description; return description;
} }
@override @override
bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) { bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) {
if (text != null && text != item.text) if (text != null && text != item.text) {
return _failWithMessage('Expected text: $text', matchState); return _failWithMessage('Expected text: $text', matchState);
if (contentDescription != null && contentDescription != item.contentDescription) }
if (contentDescription != null && contentDescription != item.contentDescription) {
return _failWithMessage('Expected contentDescription: $contentDescription', matchState); return _failWithMessage('Expected contentDescription: $contentDescription', matchState);
if (className != null && className != item.className) }
if (className != null && className != item.className) {
return _failWithMessage('Expected className: $className', matchState); return _failWithMessage('Expected className: $className', matchState);
if (id != null && id != item.id) }
if (id != null && id != item.id) {
return _failWithMessage('Expected id: $id', matchState); return _failWithMessage('Expected id: $id', matchState);
if (rect != null && rect != item.getRect()) }
if (rect != null && rect != item.getRect()) {
return _failWithMessage('Expected rect: $rect', matchState); return _failWithMessage('Expected rect: $rect', matchState);
if (size != null && size != item.getSize()) }
if (size != null && size != item.getSize()) {
return _failWithMessage('Expected size: $size', matchState); return _failWithMessage('Expected size: $size', matchState);
}
if (actions != null) { if (actions != null) {
final List<AndroidSemanticsAction> itemActions = item.getActions(); final List<AndroidSemanticsAction> itemActions = item.getActions();
if (!unorderedEquals(actions).matches(itemActions, matchState)) { if (!unorderedEquals(actions).matches(itemActions, matchState)) {
@ -161,25 +182,34 @@ class _AndroidSemanticsMatcher extends Matcher {
return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState); return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState);
} }
} }
if (isChecked != null && isChecked != item.isChecked) if (isChecked != null && isChecked != item.isChecked) {
return _failWithMessage('Expected isChecked: $isChecked', matchState); return _failWithMessage('Expected isChecked: $isChecked', matchState);
if (isCheckable != null && isCheckable != item.isCheckable) }
if (isCheckable != null && isCheckable != item.isCheckable) {
return _failWithMessage('Expected isCheckable: $isCheckable', matchState); return _failWithMessage('Expected isCheckable: $isCheckable', matchState);
if (isEditable != null && isEditable != item.isEditable) }
if (isEditable != null && isEditable != item.isEditable) {
return _failWithMessage('Expected isEditable: $isEditable', matchState); return _failWithMessage('Expected isEditable: $isEditable', matchState);
if (isEnabled != null && isEnabled != item.isEnabled) }
if (isEnabled != null && isEnabled != item.isEnabled) {
return _failWithMessage('Expected isEnabled: $isEnabled', matchState); return _failWithMessage('Expected isEnabled: $isEnabled', matchState);
if (isFocusable != null && isFocusable != item.isFocusable) }
if (isFocusable != null && isFocusable != item.isFocusable) {
return _failWithMessage('Expected isFocusable: $isFocusable', matchState); return _failWithMessage('Expected isFocusable: $isFocusable', matchState);
if (isFocused != null && isFocused != item.isFocused) }
if (isFocused != null && isFocused != item.isFocused) {
return _failWithMessage('Expected isFocused: $isFocused', matchState); return _failWithMessage('Expected isFocused: $isFocused', matchState);
}
// Heading is not available in all Android versions, so match anything if it is not set by the platform // Heading is not available in all Android versions, so match anything if it is not set by the platform
if (isHeading != null && isHeading != item.isHeading && item.isHeading != null) if (isHeading != null && isHeading != item.isHeading && item.isHeading != null) {
return _failWithMessage('Expected isHeading: $isHeading', matchState); return _failWithMessage('Expected isHeading: $isHeading', matchState);
if (isPassword != null && isPassword != item.isPassword) }
if (isPassword != null && isPassword != item.isPassword) {
return _failWithMessage('Expected isPassword: $isPassword', matchState); return _failWithMessage('Expected isPassword: $isPassword', matchState);
if (isLongClickable != null && isLongClickable != item.isLongClickable) }
if (isLongClickable != null && isLongClickable != item.isLongClickable) {
return _failWithMessage('Expected longClickable: $isLongClickable', matchState); return _failWithMessage('Expected longClickable: $isLongClickable', matchState);
}
return true; return true;
} }

View File

@ -47,17 +47,19 @@ void diffActions(StringBuffer diffBuffer, Map<String, dynamic> originalEvent,
final String originalActionName = final String originalActionName =
getActionName(originalActionMasked, originalEvent['action'] as int); getActionName(originalActionMasked, originalEvent['action'] as int);
if (synthesizedActionMasked != originalActionMasked) if (synthesizedActionMasked != originalActionMasked) {
diffBuffer.write( diffBuffer.write(
'action (expected: $originalActionName actual: $synthesizedActionName) '); 'action (expected: $originalActionName actual: $synthesizedActionName) ');
}
if (kPointerActions.contains(originalActionMasked) && if (kPointerActions.contains(originalActionMasked) &&
originalActionMasked == synthesizedActionMasked) { originalActionMasked == synthesizedActionMasked) {
final int originalPointer = getPointerIdx(originalEvent['action'] as int); final int originalPointer = getPointerIdx(originalEvent['action'] as int);
final int synthesizedPointer = getPointerIdx(synthesizedEvent['action'] as int); final int synthesizedPointer = getPointerIdx(synthesizedEvent['action'] as int);
if (originalPointer != synthesizedPointer) if (originalPointer != synthesizedPointer) {
diffBuffer.write( diffBuffer.write(
'pointerIdx (expected: $originalPointer actual: $synthesizedPointer action: $originalActionName '); 'pointerIdx (expected: $originalPointer actual: $synthesizedPointer action: $originalActionName ');
}
} }
} }
@ -123,10 +125,12 @@ void diffMaps(
return; return;
} }
for (final String key in expected.keys) { for (final String key in expected.keys) {
if (excludeKeys.contains(key)) if (excludeKeys.contains(key)) {
continue; continue;
if (doublesApproximatelyMatch(expected[key], actual[key])) }
if (doublesApproximatelyMatch(expected[key], actual[key])) {
continue; continue;
}
if (expected[key] != actual[key]) { if (expected[key] != actual[key]) {
diffBuffer.write( diffBuffer.write(
@ -155,10 +159,11 @@ String getActionName(int actionMasked, int action) {
'BUTTON_PRESS', 'BUTTON_PRESS',
'BUTTON_RELEASE', 'BUTTON_RELEASE',
]; ];
if (actionMasked < actionNames.length) if (actionMasked < actionNames.length) {
return '${actionNames[actionMasked]}($action)'; return '${actionNames[actionMasked]}($action)';
else } else {
return 'ACTION_$actionMasked'; return 'ACTION_$actionMasked';
}
} }
bool doublesApproximatelyMatch(dynamic a, dynamic b) => bool doublesApproximatelyMatch(dynamic a, dynamic b) =>

View File

@ -146,16 +146,19 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
await channel.invokeMethod<void>('stopFlutterViewEvents'); await channel.invokeMethod<void>('stopFlutterViewEvents');
await viewChannel?.invokeMethod<void>('stopTouchEvents'); await viewChannel?.invokeMethod<void>('stopTouchEvents');
if (flutterViewEvents.length != embeddedViewEvents.length) if (flutterViewEvents.length != embeddedViewEvents.length) {
return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events'; return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events';
}
final StringBuffer diff = StringBuffer(); final StringBuffer diff = StringBuffer();
for (int i = 0; i < flutterViewEvents.length; ++i) { for (int i = 0; i < flutterViewEvents.length; ++i) {
final String currentDiff = diffMotionEvents(flutterViewEvents[i], embeddedViewEvents[i]); final String currentDiff = diffMotionEvents(flutterViewEvents[i], embeddedViewEvents[i]);
if (currentDiff.isEmpty) if (currentDiff.isEmpty) {
continue; continue;
if (diff.isNotEmpty) }
if (diff.isNotEmpty) {
diff.write(', '); diff.write(', ');
}
diff.write(currentDiff); diff.write(currentDiff);
} }
return diff.toString(); return diff.toString();
@ -229,8 +232,9 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
case 'onTouch': case 'onTouch':
final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>; final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>;
flutterViewEvents.insert(0, map.cast<String, dynamic>()); flutterViewEvents.insert(0, map.cast<String, dynamic>());
if (flutterViewEvents.length > kEventsBufferSize) if (flutterViewEvents.length > kEventsBufferSize) {
flutterViewEvents.removeLast(); flutterViewEvents.removeLast();
}
setState(() {}); setState(() {});
break; break;
} }
@ -242,8 +246,9 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
case 'onTouch': case 'onTouch':
final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>; final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>;
embeddedViewEvents.insert(0, map.cast<String, dynamic>()); embeddedViewEvents.insert(0, map.cast<String, dynamic>());
if (embeddedViewEvents.length > kEventsBufferSize) if (embeddedViewEvents.length > kEventsBufferSize) {
embeddedViewEvents.removeLast(); embeddedViewEvents.removeLast();
}
setState(() {}); setState(() {});
break; break;
} }
@ -251,9 +256,10 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
} }
Widget buildEventTile(BuildContext context, int index) { Widget buildEventTile(BuildContext context, int index) {
if (embeddedViewEvents.length > index) if (embeddedViewEvents.length > index) {
return TouchEventDiff( return TouchEventDiff(
flutterViewEvents[index], embeddedViewEvents[index]); flutterViewEvents[index], embeddedViewEvents[index]);
}
return Text( return Text(
'Unmatched event, action: ${flutterViewEvents[index]['action']}'); 'Unmatched event, action: ${flutterViewEvents[index]['action']}');
} }

View File

@ -172,10 +172,11 @@ class _TestAppState extends State<TestApp> {
void _executeNextStep() { void _executeNextStep() {
setState(() { setState(() {
if (_step < steps.length) if (_step < steps.length) {
_result = steps[_step++](); _result = steps[_step++]();
else } else {
_result = Future<TestStepResult>.value(TestStepResult.complete); _result = Future<TestStepResult>.value(TestStepResult.complete);
}
}); });
} }

View File

@ -169,10 +169,11 @@ Future<TestStepResult> _basicMessageToUnknownChannel<T>(
} }
String toString(dynamic message) { String toString(dynamic message) {
if (message is ByteData) if (message is ByteData) {
return message.buffer return message.buffer
.asUint8List(message.offsetInBytes, message.lengthInBytes) .asUint8List(message.offsetInBytes, message.lengthInBytes)
.toString(); .toString();
else } else {
return '$message'; return '$message';
}
} }

View File

@ -101,8 +101,9 @@ Future<TestStepResult> resultOfHandshake(
dynamic error, dynamic error,
) async { ) async {
assert(message != nothing); assert(message != nothing);
while (received.length < 2) while (received.length < 2) {
received.add(nothing); received.add(nothing);
}
TestStatus status; TestStatus status;
if (!_deepEquals(messageEcho, message) || if (!_deepEquals(messageEcho, message) ||
received.length != 2 || received.length != 2 ||
@ -127,27 +128,34 @@ Future<TestStepResult> resultOfHandshake(
} }
String _toString(dynamic message) { String _toString(dynamic message) {
if (message is ByteData) if (message is ByteData) {
return message.buffer return message.buffer
.asUint8List(message.offsetInBytes, message.lengthInBytes) .asUint8List(message.offsetInBytes, message.lengthInBytes)
.toString(); .toString();
else } else {
return '$message'; return '$message';
}
} }
bool _deepEquals(dynamic a, dynamic b) { bool _deepEquals(dynamic a, dynamic b) {
if (a == b) if (a == b) {
return true; return true;
if (a is double && a.isNaN) }
if (a is double && a.isNaN) {
return b is double && b.isNaN; return b is double && b.isNaN;
if (a is ByteData) }
if (a is ByteData) {
return b is ByteData && _deepEqualsByteData(a, b); return b is ByteData && _deepEqualsByteData(a, b);
if (a is List) }
if (a is List) {
return b is List && _deepEqualsList(a, b); return b is List && _deepEqualsList(a, b);
if (a is Map) }
if (a is Map) {
return b is Map && _deepEqualsMap(a, b); return b is Map && _deepEqualsMap(a, b);
if (a is Pair) }
if (a is Pair) {
return b is Pair && _deepEqualsPair(a, b); return b is Pair && _deepEqualsPair(a, b);
}
return false; return false;
} }
@ -159,21 +167,25 @@ bool _deepEqualsByteData(ByteData a, ByteData b) {
} }
bool _deepEqualsList(List<dynamic> a, List<dynamic> b) { bool _deepEqualsList(List<dynamic> a, List<dynamic> b) {
if (a.length != b.length) if (a.length != b.length) {
return false; return false;
}
for (int i = 0; i < a.length; i++) { for (int i = 0; i < a.length; i++) {
if (!_deepEquals(a[i], b[i])) if (!_deepEquals(a[i], b[i])) {
return false; return false;
}
} }
return true; return true;
} }
bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) { bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
if (a.length != b.length) if (a.length != b.length) {
return false; return false;
}
for (final dynamic key in a.keys) { for (final dynamic key in a.keys) {
if (!b.containsKey(key) || !_deepEquals(a[key], b[key])) if (!b.containsKey(key) || !_deepEquals(a[key], b[key])) {
return false; return false;
}
} }
return true; return true;
} }

View File

@ -115,8 +115,9 @@ Press play to produce texture frames.''';
_state = FrameState.initial; _state = FrameState.initial;
}); });
} else { } else {
if ((tickCount % (calibrationTickCount ~/ 20)) == 0) if ((tickCount % (calibrationTickCount ~/ 20)) == 0) {
debugPrint('Calibrating... ${(100.0 * tickCount / calibrationTickCount).floor()}%'); debugPrint('Calibrating... ${(100.0 * tickCount / calibrationTickCount).floor()}%');
}
} }
}); });
ticker.start(); ticker.start();

View File

@ -44,8 +44,9 @@ class _RenderStatusBarPaddingSliver extends RenderSliver {
double _maxHeight; double _maxHeight;
set maxHeight(double value) { set maxHeight(double value) {
assert(maxHeight >= 0.0); assert(maxHeight >= 0.0);
if (_maxHeight == value) if (_maxHeight == value) {
return; return;
}
_maxHeight = value; _maxHeight = value;
markNeedsLayout(); markNeedsLayout();
} }
@ -56,8 +57,9 @@ class _RenderStatusBarPaddingSliver extends RenderSliver {
double _scrollFactor; double _scrollFactor;
set scrollFactor(double value) { set scrollFactor(double value) {
assert(scrollFactor >= 1.0); assert(scrollFactor >= 1.0);
if (_scrollFactor == value) if (_scrollFactor == value) {
return; return;
}
_scrollFactor = value; _scrollFactor = value;
markNeedsLayout(); markNeedsLayout();
} }
@ -390,22 +392,27 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics {
// then snap it there. Similarly if the simulation is headed down past // then snap it there. Similarly if the simulation is headed down past
// midScrollOffset but will not reach zero, then snap it to zero. // midScrollOffset but will not reach zero, then snap it to zero.
final double simulationEnd = simulation.x(double.infinity); final double simulationEnd = simulation.x(double.infinity);
if (simulationEnd >= midScrollOffset) if (simulationEnd >= midScrollOffset) {
return simulation; return simulation;
if (dragVelocity > 0.0) }
if (dragVelocity > 0.0) {
return _toMidScrollOffsetSimulation(offset, dragVelocity); return _toMidScrollOffsetSimulation(offset, dragVelocity);
if (dragVelocity < 0.0) }
if (dragVelocity < 0.0) {
return _toZeroScrollOffsetSimulation(offset, dragVelocity); return _toZeroScrollOffsetSimulation(offset, dragVelocity);
}
} else { } else {
// The user ended the drag with little or no velocity. If they // The user ended the drag with little or no velocity. If they
// didn't leave the offset above midScrollOffset, then // didn't leave the offset above midScrollOffset, then
// snap to midScrollOffset if they're more than halfway there, // snap to midScrollOffset if they're more than halfway there,
// otherwise snap to zero. // otherwise snap to zero.
final double snapThreshold = midScrollOffset / 2.0; final double snapThreshold = midScrollOffset / 2.0;
if (offset >= snapThreshold && offset < midScrollOffset) if (offset >= snapThreshold && offset < midScrollOffset) {
return _toMidScrollOffsetSimulation(offset, dragVelocity); return _toMidScrollOffsetSimulation(offset, dragVelocity);
if (offset > 0.0 && offset < snapThreshold) }
if (offset > 0.0 && offset < snapThreshold) {
return _toZeroScrollOffsetSimulation(offset, dragVelocity); return _toZeroScrollOffsetSimulation(offset, dragVelocity);
}
} }
return simulation; return simulation;
} }
@ -439,10 +446,11 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
} }
void _handleBackButton(double midScrollOffset) { void _handleBackButton(double midScrollOffset) {
if (_scrollController.offset >= midScrollOffset) if (_scrollController.offset >= midScrollOffset) {
_scrollController.animateTo(0.0, curve: _kScrollCurve, duration: _kScrollDuration); _scrollController.animateTo(0.0, curve: _kScrollCurve, duration: _kScrollDuration);
else } else {
Navigator.maybePop(context); Navigator.maybePop(context);
}
} }
// Only enable paging for the heading when the user has scrolled to midScrollOffset. // Only enable paging for the heading when the user has scrolled to midScrollOffset.
@ -478,8 +486,9 @@ class _AnimationDemoHomeState extends State<AnimationDemoHome> {
bool _handlePageNotification(ScrollNotification notification, PageController leader, PageController follower) { bool _handlePageNotification(ScrollNotification notification, PageController leader, PageController follower) {
if (notification.depth == 0 && notification is ScrollUpdateNotification) { if (notification.depth == 0 && notification is ScrollUpdateNotification) {
selectedIndex.value = leader.page; selectedIndex.value = leader.page;
if (follower.page != leader.page) if (follower.page != leader.page) {
follower.position.jumpToWithoutSettling(leader.position.pixels); // ignore: deprecated_member_use follower.position.jumpToWithoutSettling(leader.position.pixels); // ignore: deprecated_member_use
}
} }
return false; return false;
} }

View File

@ -35,10 +35,12 @@ class FloatToken extends NumberToken {
static double _parse(String stringRep) { static double _parse(String stringRep) {
String toParse = stringRep; String toParse = stringRep;
if (toParse.startsWith('.')) if (toParse.startsWith('.')) {
toParse = '0$toParse'; toParse = '0$toParse';
if (toParse.endsWith('.')) }
if (toParse.endsWith('.')) {
toParse = '${toParse}0'; toParse = '${toParse}0';
}
return double.parse(toParse); return double.parse(toParse);
} }
} }
@ -51,8 +53,9 @@ class ResultToken extends NumberToken {
/// floating point number is guaranteed to have at least this many /// floating point number is guaranteed to have at least this many
/// decimal digits of precision. /// decimal digits of precision.
static num round(num number) { static num round(num number) {
if (number is int) if (number is int) {
return number; return number;
}
return double.parse(number.toStringAsPrecision(14)); return double.parse(number.toStringAsPrecision(14));
} }
} }
@ -330,10 +333,11 @@ class CalcExpression {
// Remove the next number token. // Remove the next number token.
final NumberToken nextNumToken = list.removeAt(0)! as NumberToken; final NumberToken nextNumToken = list.removeAt(0)! as NumberToken;
final num nextNumber = nextNumToken.number; final num nextNumber = nextNumToken.number;
if (isDivision) if (isDivision) {
currentValue /= nextNumber; currentValue /= nextNumber;
else } else {
currentValue *= nextNumber; currentValue *= nextNumber;
}
} }
return currentValue; return currentValue;
} }

View File

@ -294,23 +294,26 @@ class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderSt
// the user must either tap its heading or the backdrop's menu icon. // the user must either tap its heading or the backdrop's menu icon.
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
if (_controller.isAnimating || _controller.status == AnimationStatus.completed) if (_controller.isAnimating || _controller.status == AnimationStatus.completed) {
return; return;
}
_controller.value -= details.primaryDelta! / _backdropHeight; _controller.value -= details.primaryDelta! / _backdropHeight;
} }
void _handleDragEnd(DragEndDetails details) { void _handleDragEnd(DragEndDetails details) {
if (_controller.isAnimating || _controller.status == AnimationStatus.completed) if (_controller.isAnimating || _controller.status == AnimationStatus.completed) {
return; return;
}
final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight; final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight;
if (flingVelocity < 0.0) if (flingVelocity < 0.0) {
_controller.fling(velocity: math.max(2.0, -flingVelocity)); _controller.fling(velocity: math.max(2.0, -flingVelocity));
else if (flingVelocity > 0.0) } else if (flingVelocity > 0.0) {
_controller.fling(velocity: math.min(-2.0, -flingVelocity)); _controller.fling(velocity: math.min(-2.0, -flingVelocity));
else } else {
_controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0); _controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
}
} }
// Stacks a BackdropPanel, which displays the selected category, on top // Stacks a BackdropPanel, which displays the selected category, on top

View File

@ -204,12 +204,15 @@ class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
} }
NotchedShape? _selectNotch() { NotchedShape? _selectNotch() {
if (!_showNotch.value!) if (!_showNotch.value!) {
return null; return null;
if (_fabShape == kCircularFab) }
if (_fabShape == kCircularFab) {
return const CircularNotchedRectangle(); return const CircularNotchedRectangle();
if (_fabShape == kDiamondFab) }
if (_fabShape == kDiamondFab) {
return const _DiamondNotchedRectangle(); return const _DiamondNotchedRectangle();
}
return null; return null;
} }
} }
@ -452,8 +455,9 @@ class _DiamondNotchedRectangle implements NotchedShape {
@override @override
Path getOuterPath(Rect host, Rect? guest) { Path getOuterPath(Rect host, Rect? guest) {
if (!host.overlaps(guest!)) if (!host.overlaps(guest!)) {
return Path()..addRect(host); return Path()..addRect(host);
}
assert(guest.width > 0.0); assert(guest.width > 0.0);
final Rect intersection = guest.intersect(host); final Rect intersection = guest.intersect(host);

View File

@ -165,8 +165,9 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
@override @override
void dispose() { void dispose() {
for (final NavigationIconView view in _navigationViews) for (final NavigationIconView view in _navigationViews) {
view.controller.dispose(); view.controller.dispose();
}
super.dispose(); super.dispose();
} }

View File

@ -97,8 +97,9 @@ class DessertDataSource extends DataTableSource {
@override @override
DataRow? getRow(int index) { DataRow? getRow(int index) {
assert(index >= 0); assert(index >= 0);
if (index >= _desserts.length) if (index >= _desserts.length) {
return null; return null;
}
final Dessert dessert = _desserts[index]; final Dessert dessert = _desserts[index];
return DataRow.byIndex( return DataRow.byIndex(
index: index, index: index,
@ -134,8 +135,9 @@ class DessertDataSource extends DataTableSource {
int get selectedRowCount => _selectedCount; int get selectedRowCount => _selectedCount;
void _selectAll(bool? checked) { void _selectAll(bool? checked) {
for (final Dessert dessert in _desserts) for (final Dessert dessert in _desserts) {
dessert.selected = checked; dessert.selected = checked;
}
_selectedCount = checked! ? _desserts.length : 0; _selectedCount = checked! ? _desserts.length : 0;
notifyListeners(); notifyListeners();
} }

View File

@ -66,8 +66,9 @@ class _DateTimePicker extends StatelessWidget {
firstDate: DateTime(2015, 8), firstDate: DateTime(2015, 8),
lastDate: DateTime(2101), lastDate: DateTime(2101),
); );
if (picked != null && picked != selectedDate) if (picked != null && picked != selectedDate) {
selectDate!(picked); selectDate!(picked);
}
} }
Future<void> _selectTime(BuildContext context) async { Future<void> _selectTime(BuildContext context) async {
@ -75,8 +76,9 @@ class _DateTimePicker extends StatelessWidget {
context: context, context: context,
initialTime: selectedTime!, initialTime: selectedTime!,
); );
if (picked != null && picked != selectedTime) if (picked != null && picked != selectedTime) {
selectTime!(picked); selectTime!(picked);
}
} }
@override @override

View File

@ -144,10 +144,11 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
onDetailsPressed: () { onDetailsPressed: () {
_showDrawerContents = !_showDrawerContents; _showDrawerContents = !_showDrawerContents;
if (_showDrawerContents) if (_showDrawerContents) {
_controller.reverse(); _controller.reverse();
else } else {
_controller.forward(); _controller.forward();
}
}, },
), ),
MediaQuery.removePadding( MediaQuery.removePadding(

View File

@ -46,8 +46,9 @@ class DateTimeItem extends StatelessWidget {
lastDate: date.add(const Duration(days: 30)), lastDate: date.add(const Duration(days: 30)),
) )
.then((DateTime? value) { .then((DateTime? value) {
if (value != null) if (value != null) {
onChanged(DateTime(value.year, value.month, value.day, time.hour, time.minute)); onChanged(DateTime(value.year, value.month, value.day, time.hour, time.minute));
}
}); });
}, },
child: Row( child: Row(
@ -73,8 +74,9 @@ class DateTimeItem extends StatelessWidget {
initialTime: time, initialTime: time,
) )
.then((TimeOfDay? value) { .then((TimeOfDay? value) {
if (value != null) if (value != null) {
onChanged(DateTime(date.year, date.month, date.day, value.hour, value.minute)); onChanged(DateTime(date.year, date.month, date.day, value.hour, value.minute));
}
}); });
}, },
child: Row( child: Row(
@ -109,8 +111,9 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
Future<bool> _onWillPop() async { Future<bool> _onWillPop() async {
_saveNeeded = _hasLocation || _hasName || _saveNeeded; _saveNeeded = _hasLocation || _hasName || _saveNeeded;
if (!_saveNeeded) if (!_saveNeeded) {
return true; return true;
}
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final TextStyle dialogTextStyle = theme.textTheme.subtitle1!.copyWith(color: theme.textTheme.caption!.color); final TextStyle dialogTextStyle = theme.textTheme.subtitle1!.copyWith(color: theme.textTheme.caption!.color);

View File

@ -118,8 +118,9 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv
void _handleOnScaleEnd(ScaleEndDetails details) { void _handleOnScaleEnd(ScaleEndDetails details) {
final double magnitude = details.velocity.pixelsPerSecond.distance; final double magnitude = details.velocity.pixelsPerSecond.distance;
if (magnitude < _kMinFlingVelocity) if (magnitude < _kMinFlingVelocity) {
return; return;
}
final Offset direction = details.velocity.pixelsPerSecond / magnitude; final Offset direction = details.velocity.pixelsPerSecond / magnitude;
final double distance = (Offset.zero & context.size!).shortestSide; final double distance = (Offset.zero & context.size!).shortestSide;
_flingAnimation = _controller.drive(Tween<Offset>( _flingAnimation = _controller.drive(Tween<Offset>(

View File

@ -222,10 +222,11 @@ class _LeaveBehindListItem extends StatelessWidget {
key: ObjectKey(item), key: ObjectKey(item),
direction: dismissDirection, direction: dismissDirection,
onDismissed: (DismissDirection direction) { onDismissed: (DismissDirection direction) {
if (direction == DismissDirection.endToStart) if (direction == DismissDirection.endToStart) {
_handleArchive(); _handleArchive();
else } else {
_handleDelete(); _handleDelete();
}
}, },
confirmDismiss: !confirmDismiss ? null : (DismissDirection dismissDirection) async { confirmDismiss: !confirmDismiss ? null : (DismissDirection dismissDirection) async {
switch(dismissDirection) { switch(dismissDirection) {

View File

@ -224,8 +224,9 @@ class _ListDemoState extends State<ListDemo> {
} }
Iterable<Widget> listTiles = items.map<Widget>((String item) => buildListTile(context, item)); Iterable<Widget> listTiles = items.map<Widget>((String item) => buildListTile(context, item));
if (_showDividers != null) if (_showDividers != null) {
listTiles = ListTile.divideTiles(context: context, tiles: listTiles); listTiles = ListTile.divideTiles(context: context, tiles: listTiles);
}
return Scaffold( return Scaffold(
key: scaffoldKey, key: scaffoldKey,

View File

@ -42,16 +42,18 @@ class MenuDemoState extends State<MenuDemo> {
} }
void showMenuSelection(String value) { void showMenuSelection(String value) {
if (<String>[_simpleValue1, _simpleValue2, _simpleValue3].contains(value)) if (<String>[_simpleValue1, _simpleValue2, _simpleValue3].contains(value)) {
setState(() => _simpleValue = value); setState(() => _simpleValue = value);
}
showInSnackBar('You selected: $value'); showInSnackBar('You selected: $value');
} }
void showCheckedMenuSelections(String value) { void showCheckedMenuSelections(String value) {
if (_checkedValues.contains(value)) if (_checkedValues.contains(value)) {
_checkedValues.remove(value); _checkedValues.remove(value);
else } else {
_checkedValues.add(value); _checkedValues.add(value);
}
showInSnackBar('Checked $_checkedValues'); showInSnackBar('Checked $_checkedValues');
} }

View File

@ -29,8 +29,9 @@ class OverscrollDemoState extends State<OverscrollDemo> {
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
Timer(const Duration(seconds: 3), () => completer.complete()); Timer(const Duration(seconds: 3), () => completer.complete());
return completer.future.then((_) { return completer.future.then((_) {
if (!mounted) if (!mounted) {
return; return;
}
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Refresh complete'), content: const Text('Refresh complete'),
action: SnackBarAction( action: SnackBarAction(

View File

@ -13,8 +13,9 @@ class _PageSelector extends StatelessWidget {
void _handleArrowButtonPress(BuildContext context, int delta) { void _handleArrowButtonPress(BuildContext context, int delta) {
final TabController controller = DefaultTabController.of(context)!; final TabController controller = DefaultTabController.of(context)!;
if (!controller.indexIsChanging) if (!controller.indexIsChanging) {
controller.animateTo((controller.index + delta).clamp(0, icons!.length - 1)); controller.animateTo((controller.index + delta).clamp(0, icons!.length - 1));
}
} }
@override @override

View File

@ -33,10 +33,11 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo> with Sing
curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn), curve: const Interval(0.0, 0.9, curve: Curves.fastOutSlowIn),
reverseCurve: Curves.fastOutSlowIn, reverseCurve: Curves.fastOutSlowIn,
)..addStatusListener((AnimationStatus status) { )..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed) if (status == AnimationStatus.dismissed) {
_controller.forward(); _controller.forward();
else if (status == AnimationStatus.completed) } else if (status == AnimationStatus.completed) {
_controller.reverse(); _controller.reverse();
}
}); });
} }

View File

@ -68,8 +68,9 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo> with SingleTicke
} }
Decoration? getIndicator() { Decoration? getIndicator() {
if (!_customIndicator) if (!_customIndicator) {
return const UnderlineTabIndicator(); return const UnderlineTabIndicator();
}
switch(_demoStyle) { switch(_demoStyle) {
case TabsDemoStyle.iconsAndText: case TabsDemoStyle.iconsAndText:

View File

@ -107,8 +107,9 @@ class _TabsFabDemoState extends State<TabsFabDemo> with SingleTickerProviderStat
} }
Widget? buildFloatingActionButton(_Page page) { Widget? buildFloatingActionButton(_Page page) {
if (!page.fabDefined) if (!page.fabDefined) {
return null; return null;
}
if (_extendedButtons) { if (_extendedButtons) {
return FloatingActionButton.extended( return FloatingActionButton.extended(

View File

@ -112,36 +112,42 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
String? _validateName(String? value) { String? _validateName(String? value) {
_formWasEdited = true; _formWasEdited = true;
if (value!.isEmpty) if (value!.isEmpty) {
return 'Name is required.'; return 'Name is required.';
}
final RegExp nameExp = RegExp(r'^[A-Za-z ]+$'); final RegExp nameExp = RegExp(r'^[A-Za-z ]+$');
if (!nameExp.hasMatch(value)) if (!nameExp.hasMatch(value)) {
return 'Please enter only alphabetical characters.'; return 'Please enter only alphabetical characters.';
}
return null; return null;
} }
String? _validatePhoneNumber(String? value) { String? _validatePhoneNumber(String? value) {
_formWasEdited = true; _formWasEdited = true;
final RegExp phoneExp = RegExp(r'^\(\d\d\d\) \d\d\d\-\d\d\d\d$'); final RegExp phoneExp = RegExp(r'^\(\d\d\d\) \d\d\d\-\d\d\d\d$');
if (!phoneExp.hasMatch(value!)) if (!phoneExp.hasMatch(value!)) {
return '(###) ###-#### - Enter a US phone number.'; return '(###) ###-#### - Enter a US phone number.';
}
return null; return null;
} }
String? _validatePassword(String? value) { String? _validatePassword(String? value) {
_formWasEdited = true; _formWasEdited = true;
final FormFieldState<String> passwordField = _passwordFieldKey.currentState!; final FormFieldState<String> passwordField = _passwordFieldKey.currentState!;
if (passwordField.value == null || passwordField.value!.isEmpty) if (passwordField.value == null || passwordField.value!.isEmpty) {
return 'Please enter a password.'; return 'Please enter a password.';
if (passwordField.value != value) }
if (passwordField.value != value) {
return "The passwords don't match"; return "The passwords don't match";
}
return null; return null;
} }
Future<bool> _warnUserAboutInvalidData() async { Future<bool> _warnUserAboutInvalidData() async {
final FormState? form = _formKey.currentState; final FormState? form = _formKey.currentState;
if (form == null || !_formWasEdited || form.validate()) if (form == null || !_formWasEdited || form.validate()) {
return true; return true;
}
final bool? result = await showDialog<bool>( final bool? result = await showDialog<bool>(
context: context, context: context,
@ -313,30 +319,35 @@ class _UsNumberTextInputFormatter extends TextInputFormatter {
final StringBuffer newText = StringBuffer(); final StringBuffer newText = StringBuffer();
if (newTextLength >= 1) { if (newTextLength >= 1) {
newText.write('('); newText.write('(');
if (newValue.selection.end >= 1) if (newValue.selection.end >= 1) {
selectionIndex++; selectionIndex++;
}
} }
if (newTextLength >= 4) { if (newTextLength >= 4) {
final String value = newValue.text.substring(0, usedSubstringIndex = 3); final String value = newValue.text.substring(0, usedSubstringIndex = 3);
newText.write('$value) '); newText.write('$value) ');
if (newValue.selection.end >= 3) if (newValue.selection.end >= 3) {
selectionIndex += 2; selectionIndex += 2;
}
} }
if (newTextLength >= 7) { if (newTextLength >= 7) {
final String value = newValue.text.substring(3, usedSubstringIndex = 6); final String value = newValue.text.substring(3, usedSubstringIndex = 6);
newText.write('$value-'); newText.write('$value-');
if (newValue.selection.end >= 6) if (newValue.selection.end >= 6) {
selectionIndex++; selectionIndex++;
}
} }
if (newTextLength >= 11) { if (newTextLength >= 11) {
final String value = newValue.text.substring(6, usedSubstringIndex = 10); final String value = newValue.text.substring(6, usedSubstringIndex = 10);
newText.write('$value '); newText.write('$value ');
if (newValue.selection.end >= 10) if (newValue.selection.end >= 10) {
selectionIndex++; selectionIndex++;
}
} }
// Dump the rest. // Dump the rest.
if (newTextLength >= usedSubstringIndex) if (newTextLength >= usedSubstringIndex) {
newText.write(newValue.text.substring(usedSubstringIndex)); newText.write(newValue.text.substring(usedSubstringIndex));
}
return TextEditingValue( return TextEditingValue(
text: newText.toString(), text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex), selection: TextSelection.collapsed(offset: selectionIndex),

View File

@ -418,10 +418,11 @@ class _RecipePageState extends State<RecipePage> {
void _toggleFavorite() { void _toggleFavorite() {
setState(() { setState(() {
if (_favoriteRecipes.contains(widget.recipe)) if (_favoriteRecipes.contains(widget.recipe)) {
_favoriteRecipes.remove(widget.recipe); _favoriteRecipes.remove(widget.recipe);
else } else {
_favoriteRecipes.add(widget.recipe); _favoriteRecipes.add(widget.recipe);
}
}); });
} }
} }

View File

@ -533,8 +533,9 @@ class ExtraProductsNumber extends StatelessWidget {
} }
Widget _buildOverflow(AppStateModel model, BuildContext context) { Widget _buildOverflow(AppStateModel model, BuildContext context) {
if (model.productsInCart.length <= 3) if (model.productsInCart.length <= 3) {
return Container(); return Container();
}
final int numOverflowProducts = _calculateOverflow(model); final int numOverflowProducts = _calculateOverflow(model);
// Maximum of 99 so padding doesn't get messy. // Maximum of 99 so padding doesn't get messy.

View File

@ -148,8 +148,9 @@ class VideoPlayPause extends StatefulWidget {
class _VideoPlayPauseState extends State<VideoPlayPause> { class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() { _VideoPlayPauseState() {
listener = () { listener = () {
if (mounted) if (mounted) {
setState(() { }); setState(() { });
}
}; };
} }

View File

@ -241,16 +241,18 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
} }
void _handleDragEnd(DragEndDetails details) { void _handleDragEnd(DragEndDetails details) {
if (_controller!.isAnimating || _controller!.status == AnimationStatus.completed) if (_controller!.isAnimating || _controller!.status == AnimationStatus.completed) {
return; return;
}
final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight; final double flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight;
if (flingVelocity < 0.0) if (flingVelocity < 0.0) {
_controller!.fling(velocity: math.max(2.0, -flingVelocity)); _controller!.fling(velocity: math.max(2.0, -flingVelocity));
else if (flingVelocity > 0.0) } else if (flingVelocity > 0.0) {
_controller!.fling(velocity: math.min(-2.0, -flingVelocity)); _controller!.fling(velocity: math.min(-2.0, -flingVelocity));
else } else {
_controller!.fling(velocity: _controller!.value < 0.5 ? -2.0 : 2.0); _controller!.fling(velocity: _controller!.value < 0.5 ? -2.0 : 2.0);
}
} }
void _toggleFrontLayer() { void _toggleFrontLayer() {

View File

@ -28,8 +28,9 @@ class ComponentDemoTabData {
@override @override
bool operator==(Object other) { bool operator==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is ComponentDemoTabData return other is ComponentDemoTabData
&& other.tabName == tabName && other.tabName == tabName
&& other.description == description && other.description == description
@ -67,8 +68,9 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
Future<void> _showApiDocumentation(BuildContext context) async { Future<void> _showApiDocumentation(BuildContext context) async {
final String? url = demos![DefaultTabController.of(context)!.index].documentationUrl; final String? url = demos![DefaultTabController.of(context)!.index].documentationUrl;
if (url == null) if (url == null) {
return; return;
}
final Uri uri = Uri.parse(url); final Uri uri = Uri.parse(url);
if (await canLaunchUrl(uri)) { if (await canLaunchUrl(uri)) {

View File

@ -19,10 +19,12 @@ class GalleryDemoCategory {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) if (identical(this, other)) {
return true; return true;
if (other.runtimeType != runtimeType) }
if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is GalleryDemoCategory return other is GalleryDemoCategory
&& other.name == name && other.name == name
&& other.icon == icon; && other.icon == icon;

View File

@ -99,8 +99,9 @@ DropdownButton<String>(
// null indicates the user didn't select a // null indicates the user didn't select a
// new value. // new value.
setState(() { setState(() {
if (newValue != null) if (newValue != null) {
dropdownValue = newValue; dropdownValue = newValue;
}
}); });
}, },
items: <String>['One', 'Two', 'Free', 'Four'] items: <String>['One', 'Two', 'Free', 'Four']

View File

@ -10,8 +10,9 @@ const String _kEndTag = '// END';
Map<String?, String>? _exampleCode; Map<String?, String>? _exampleCode;
Future<String?> getExampleCode(String? tag, AssetBundle bundle) async { Future<String?> getExampleCode(String? tag, AssetBundle bundle) async {
if (_exampleCode == null) if (_exampleCode == null) {
await _parseExampleCode(bundle); await _parseExampleCode(bundle);
}
return _exampleCode![tag]; return _exampleCode![tag];
} }

View File

@ -57,8 +57,9 @@ class GalleryOptions {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is GalleryOptions return other is GalleryOptions
&& other.themeMode == themeMode && other.themeMode == themeMode
&& other.textScaleFactor == textScaleFactor && other.textScaleFactor == textScaleFactor
@ -480,8 +481,9 @@ class GalleryOptionsPage extends StatelessWidget {
List<Widget> _enabledDiagnosticItems() { List<Widget> _enabledDiagnosticItems() {
// Boolean showFoo options with a value of null: don't display // Boolean showFoo options with a value of null: don't display
// the showFoo option at all. // the showFoo option at all.
if (options == null) if (options == null) {
return const <Widget>[]; return const <Widget>[];
}
return <Widget>[ return <Widget>[
const Divider(), const Divider(),

View File

@ -13,8 +13,9 @@ class GalleryTextScaleValue {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is GalleryTextScaleValue return other is GalleryTextScaleValue
&& other.scale == scale && other.scale == scale
&& other.label == label; && other.label == label;
@ -47,8 +48,9 @@ class GalleryVisualDensityValue {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType) {
return false; return false;
}
return other is GalleryVisualDensityValue return other is GalleryVisualDensityValue
&& other.visualDensity == visualDensity && other.visualDensity == visualDensity
&& other.label == label; && other.label == label;

View File

@ -95,16 +95,18 @@ class DartSyntaxHighlighter extends SyntaxHighlighter {
int currentPosition = 0; int currentPosition = 0;
for (final _HighlightSpan span in _spans) { for (final _HighlightSpan span in _spans) {
if (currentPosition != span.start) if (currentPosition != span.start) {
formattedText.add(TextSpan(text: _src!.substring(currentPosition, span.start))); formattedText.add(TextSpan(text: _src!.substring(currentPosition, span.start)));
}
formattedText.add(TextSpan(style: span.textStyle(_style), text: span.textForSpan(_src!))); formattedText.add(TextSpan(style: span.textStyle(_style), text: span.textForSpan(_src!)));
currentPosition = span.end; currentPosition = span.end;
} }
if (currentPosition != _src!.length) if (currentPosition != _src!.length) {
formattedText.add(TextSpan(text: _src!.substring(currentPosition, _src!.length))); formattedText.add(TextSpan(text: _src!.substring(currentPosition, _src!.length)));
}
return TextSpan(style: _style!.baseStyle, children: formattedText); return TextSpan(style: _style!.baseStyle, children: formattedText);
} else { } else {
@ -149,8 +151,9 @@ class DartSyntaxHighlighter extends SyntaxHighlighter {
endComment, endComment,
)); ));
if (eof) if (eof) {
break; break;
}
continue; continue;
} }
@ -260,17 +263,19 @@ class DartSyntaxHighlighter extends SyntaxHighlighter {
_HighlightType? type; _HighlightType? type;
String word = _scanner.lastMatch![0]!; String word = _scanner.lastMatch![0]!;
if (word.startsWith('_')) if (word.startsWith('_')) {
word = word.substring(1); word = word.substring(1);
}
if (_keywords.contains(word)) if (_keywords.contains(word)) {
type = _HighlightType.keyword; type = _HighlightType.keyword;
else if (_builtInTypes.contains(word)) } else if (_builtInTypes.contains(word)) {
type = _HighlightType.keyword; type = _HighlightType.keyword;
else if (_firstLetterIsUpperCase(word)) } else if (_firstLetterIsUpperCase(word)) {
type = _HighlightType.klass; type = _HighlightType.klass;
else if (word.length >= 2 && word.startsWith('k') && _firstLetterIsUpperCase(word.substring(1))) } else if (word.length >= 2 && word.startsWith('k') && _firstLetterIsUpperCase(word.substring(1))) {
type = _HighlightType.constant; type = _HighlightType.constant;
}
if (type != null) { if (type != null) {
_spans.add(_HighlightSpan( _spans.add(_HighlightSpan(
@ -336,21 +341,22 @@ class _HighlightSpan {
} }
TextStyle? textStyle(SyntaxHighlighterStyle? style) { TextStyle? textStyle(SyntaxHighlighterStyle? style) {
if (type == _HighlightType.number) if (type == _HighlightType.number) {
return style!.numberStyle; return style!.numberStyle;
else if (type == _HighlightType.comment) } else if (type == _HighlightType.comment) {
return style!.commentStyle; return style!.commentStyle;
else if (type == _HighlightType.keyword) } else if (type == _HighlightType.keyword) {
return style!.keywordStyle; return style!.keywordStyle;
else if (type == _HighlightType.string) } else if (type == _HighlightType.string) {
return style!.stringStyle; return style!.stringStyle;
else if (type == _HighlightType.punctuation) } else if (type == _HighlightType.punctuation) {
return style!.punctuationStyle; return style!.punctuationStyle;
else if (type == _HighlightType.klass) } else if (type == _HighlightType.klass) {
return style!.classStyle; return style!.classStyle;
else if (type == _HighlightType.constant) } else if (type == _HighlightType.constant) {
return style!.constantStyle; return style!.constantStyle;
else } else {
return style!.baseStyle; return style!.baseStyle;
}
} }
} }

View File

@ -36,8 +36,9 @@ class UpdaterState extends State<Updater> {
final String? updateUrl = await widget.updateUrlFetcher(); final String? updateUrl = await widget.updateUrlFetcher();
final bool? wantsUpdate = await showDialog<bool>(context: context, builder: _buildDialog); final bool? wantsUpdate = await showDialog<bool>(context: context, builder: _buildDialog);
if (wantsUpdate != null && updateUrl != null && wantsUpdate) if (wantsUpdate != null && updateUrl != null && wantsUpdate) {
launchUrl(Uri.parse(updateUrl)); launchUrl(Uri.parse(updateUrl));
}
} }
Widget _buildDialog(BuildContext context) { Widget _buildDialog(BuildContext context) {

View File

@ -8,8 +8,9 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) if (binding is LiveTestWidgetsFlutterBinding) {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}
// We press the "1" and the "2" buttons and check that the display // We press the "1" and the "2" buttons and check that the display
// reads "12". // reads "12".

View File

@ -9,8 +9,9 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) if (binding is LiveTestWidgetsFlutterBinding) {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}
testWidgets('Flutter Gallery drawer item test', (WidgetTester tester) async { testWidgets('Flutter Gallery drawer item test', (WidgetTester tester) async {
bool hasFeedback = false; bool hasFeedback = false;

View File

@ -8,8 +8,9 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) if (binding is LiveTestWidgetsFlutterBinding) {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}
testWidgets('Flutter gallery button example code displays', (WidgetTester tester) async { testWidgets('Flutter gallery button example code displays', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/6147 // Regression test for https://github.com/flutter/flutter/issues/6147

View File

@ -45,8 +45,9 @@ class TestAssetBundle extends AssetBundle {
@override @override
Future<String> loadString(String key, { bool cache = true }) async { Future<String> loadString(String key, { bool cache = true }) async {
if (key == 'lib/gallery/example_code.dart') if (key == 'lib/gallery/example_code.dart') {
return testCodeFile; return testCodeFile;
}
return ''; return '';
} }

View File

@ -51,10 +51,12 @@ Future<void> main() async {
// Verify that _kUnsynchronizedDemos and _kSkippedDemos identify // Verify that _kUnsynchronizedDemos and _kSkippedDemos identify
// demos that actually exist. // demos that actually exist.
final List<String> allDemoTitles = kAllGalleryDemos.map((GalleryDemo demo) => demo.title).toList(); final List<String> allDemoTitles = kAllGalleryDemos.map((GalleryDemo demo) => demo.title).toList();
if (!Set<String>.from(allDemoTitles).containsAll(_kUnsynchronizedDemoTitles)) if (!Set<String>.from(allDemoTitles).containsAll(_kUnsynchronizedDemoTitles)) {
fail('Unrecognized demo titles in _kUnsynchronizedDemosTitles: $_kUnsynchronizedDemoTitles'); fail('Unrecognized demo titles in _kUnsynchronizedDemosTitles: $_kUnsynchronizedDemoTitles');
if (!Set<String>.from(allDemoTitles).containsAll(_kSkippedDemoTitles)) }
if (!Set<String>.from(allDemoTitles).containsAll(_kSkippedDemoTitles)) {
fail('Unrecognized demo names in _kSkippedDemoTitles: $_kSkippedDemoTitles'); fail('Unrecognized demo names in _kSkippedDemoTitles: $_kSkippedDemoTitles');
}
print('Starting app...'); print('Starting app...');
runApp(const GalleryApp(testMode: true)); runApp(const GalleryApp(testMode: true));
@ -66,8 +68,9 @@ Future<void> main() async {
final Finder demoItem = find.text(demo.title); final Finder demoItem = find.text(demo.title);
print('Scrolling to "${demo.title}"...'); print('Scrolling to "${demo.title}"...');
await controller.scrollIntoView(demoItem, alignment: 0.5); await controller.scrollIntoView(demoItem, alignment: 0.5);
if (_kSkippedDemoTitles.contains(demo.title)) if (_kSkippedDemoTitles.contains(demo.title)) {
continue; continue;
}
for (int i = 0; i < 2; i += 1) { for (int i = 0; i < 2; i += 1) {
print('Tapping "${demo.title}"...'); print('Tapping "${demo.title}"...');
await controller.tap(demoItem); // Launch the demo await controller.tap(demoItem); // Launch the demo
@ -91,10 +94,12 @@ Future<void> main() async {
final Finder backFinder = find.byElementPredicate( final Finder backFinder = find.byElementPredicate(
(Element element) { (Element element) {
final Widget widget = element.widget; final Widget widget = element.widget;
if (widget is Tooltip) if (widget is Tooltip) {
return widget.message == 'Back'; return widget.message == 'Back';
if (widget is CupertinoNavigationBarBackButton) }
if (widget is CupertinoNavigationBarBackButton) {
return true; return true;
}
return false; return false;
}, },
description: 'Material or Cupertino back button', description: 'Material or Cupertino back button',
@ -122,11 +127,13 @@ class _LiveWidgetController extends LiveWidgetController {
/// Runs `finder` repeatedly until it finds one or more [Element]s. /// Runs `finder` repeatedly until it finds one or more [Element]s.
Future<Finder> _waitForElement(Finder finder) async { Future<Finder> _waitForElement(Finder finder) async {
if (frameSync) if (frameSync) {
await _waitUntilFrame(() => binding.transientCallbackCount == 0); await _waitUntilFrame(() => binding.transientCallbackCount == 0);
}
await _waitUntilFrame(() => finder.precache()); await _waitUntilFrame(() => finder.precache());
if (frameSync) if (frameSync) {
await _waitUntilFrame(() => binding.transientCallbackCount == 0); await _waitUntilFrame(() => binding.transientCallbackCount == 0);
}
return finder; return finder;
} }

View File

@ -8,8 +8,9 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) if (binding is LiveTestWidgetsFlutterBinding) {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}
// Regression test for https://github.com/flutter/flutter/pull/5168 // Regression test for https://github.com/flutter/flutter/pull/5168
testWidgets('Pesto appbar heroics', (WidgetTester tester) async { testWidgets('Pesto appbar heroics', (WidgetTester tester) async {

View File

@ -8,8 +8,9 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) if (binding is LiveTestWidgetsFlutterBinding) {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}
testWidgets('Flutter Gallery app simple smoke test', (WidgetTester tester) async { testWidgets('Flutter Gallery app simple smoke test', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(

View File

@ -40,8 +40,9 @@ void reportToStringError(String name, String route, int lineNumber, List<String>
void verifyToStringOutput(String name, String route, String testString) { void verifyToStringOutput(String name, String route, String testString) {
int lineNumber = 0; int lineNumber = 0;
final List<String> lines = testString.split('\n'); final List<String> lines = testString.split('\n');
if (!testString.endsWith('\n')) if (!testString.endsWith('\n')) {
reportToStringError(name, route, lines.length, lines, 'does not end with a line feed'); reportToStringError(name, route, lines.length, lines, 'does not end with a line feed');
}
for (final String line in lines) { for (final String line in lines) {
lineNumber += 1; lineNumber += 1;
if (line == '' && lineNumber != lines.length) { if (line == '' && lineNumber != lines.length) {

View File

@ -12,8 +12,9 @@ Future<String> mockUpdateUrlFetcher() {
void main() { void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
if (binding is LiveTestWidgetsFlutterBinding) if (binding is LiveTestWidgetsFlutterBinding) {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}
// Regression test for https://github.com/flutter/flutter/pull/5168 // Regression test for https://github.com/flutter/flutter/pull/5168
testWidgets('update dialog', (WidgetTester tester) async { testWidgets('update dialog', (WidgetTester tester) async {

View File

@ -23,8 +23,9 @@ Future<void> runDemos(List<String> demos, WidgetController controller) async {
String? currentDemoCategory; String? currentDemoCategory;
for (final String demo in demos) { for (final String demo in demos) {
if (kSkippedDemos.contains(demo)) if (kSkippedDemos.contains(demo)) {
continue; continue;
}
final String demoName = demo.substring(0, demo.indexOf('@')); final String demoName = demo.substring(0, demo.indexOf('@'));
final String demoCategory = demo.substring(demo.indexOf('@') + 1); final String demoCategory = demo.substring(demo.indexOf('@') + 1);

View File

@ -60,8 +60,9 @@ Future<void> saveDurationsHistogram(List<Map<String, dynamic>> events, String ou
} }
// Verify that the durations data is valid. // Verify that the durations data is valid.
if (durations.keys.isEmpty) if (durations.keys.isEmpty) {
throw 'no "Start Transition" timeline events found'; throw 'no "Start Transition" timeline events found';
}
final Map<String, int> unexpectedValueCounts = <String, int>{}; final Map<String, int> unexpectedValueCounts = <String, int>{};
durations.forEach((String routeName, List<int> values) { durations.forEach((String routeName, List<int> values) {
if (values.length != 2) { if (values.length != 2) {
@ -83,8 +84,9 @@ Future<void> saveDurationsHistogram(List<Map<String, dynamic>> events, String ou
while (eventIter.moveNext()) { while (eventIter.moveNext()) {
final String eventName = eventIter.current['name'] as String; final String eventName = eventIter.current['name'] as String;
if (!<String>['Start Transition', 'Frame'].contains(eventName)) if (!<String>['Start Transition', 'Frame'].contains(eventName)) {
continue; continue;
}
final String routeName = eventName == 'Start Transition' final String routeName = eventName == 'Start Transition'
? (eventIter.current['args'] as Map<String, dynamic>)['to'] as String ? (eventIter.current['args'] as Map<String, dynamic>)['to'] as String
@ -114,8 +116,9 @@ Future<void> runDemos(List<String> demos, FlutterDriver driver) async {
String? currentDemoCategory; String? currentDemoCategory;
for (final String demo in demos) { for (final String demo in demos) {
if (kSkippedDemos.contains(demo)) if (kSkippedDemos.contains(demo)) {
continue; continue;
}
final String demoName = demo.substring(0, demo.indexOf('@')); final String demoName = demo.substring(0, demo.indexOf('@'));
final String demoCategory = demo.substring(demo.indexOf('@') + 1); final String demoCategory = demo.substring(demo.indexOf('@') + 1);
@ -176,8 +179,9 @@ void main([List<String> args = const <String>[]]) {
// See _handleMessages() in transitions_perf.dart. // See _handleMessages() in transitions_perf.dart.
_allDemos = List<String>.from(json.decode(await driver.requestData('demoNames')) as List<dynamic>); _allDemos = List<String>.from(json.decode(await driver.requestData('demoNames')) as List<dynamic>);
if (_allDemos.isEmpty) if (_allDemos.isEmpty) {
throw 'no demo names found'; throw 'no demo names found';
}
}); });
tearDownAll(() async { tearDownAll(() async {

View File

@ -46,17 +46,19 @@ void diffActions(StringBuffer diffBuffer, Map<String, dynamic> originalEvent,
final String originalActionName = final String originalActionName =
getActionName(originalActionMasked, originalEvent['action'] as int); getActionName(originalActionMasked, originalEvent['action'] as int);
if (synthesizedActionMasked != originalActionMasked) if (synthesizedActionMasked != originalActionMasked) {
diffBuffer.write( diffBuffer.write(
'action (expected: $originalActionName actual: $synthesizedActionName) '); 'action (expected: $originalActionName actual: $synthesizedActionName) ');
}
if (kPointerActions.contains(originalActionMasked) && if (kPointerActions.contains(originalActionMasked) &&
originalActionMasked == synthesizedActionMasked) { originalActionMasked == synthesizedActionMasked) {
final int originalPointer = getPointerIdx(originalEvent['action'] as int); final int originalPointer = getPointerIdx(originalEvent['action'] as int);
final int synthesizedPointer = getPointerIdx(synthesizedEvent['action'] as int); final int synthesizedPointer = getPointerIdx(synthesizedEvent['action'] as int);
if (originalPointer != synthesizedPointer) if (originalPointer != synthesizedPointer) {
diffBuffer.write( diffBuffer.write(
'pointerIdx (expected: $originalPointer actual: $synthesizedPointer action: $originalActionName '); 'pointerIdx (expected: $originalPointer actual: $synthesizedPointer action: $originalActionName ');
}
} }
} }
@ -122,10 +124,12 @@ void diffMaps(
return; return;
} }
for (final String key in expected.keys) { for (final String key in expected.keys) {
if (excludeKeys.contains(key)) if (excludeKeys.contains(key)) {
continue; continue;
if (doublesApproximatelyMatch(expected[key], actual[key])) }
if (doublesApproximatelyMatch(expected[key], actual[key])) {
continue; continue;
}
if (expected[key] != actual[key]) { if (expected[key] != actual[key]) {
diffBuffer.write( diffBuffer.write(
@ -154,10 +158,11 @@ String getActionName(int actionMasked, int action) {
'BUTTON_PRESS', 'BUTTON_PRESS',
'BUTTON_RELEASE', 'BUTTON_RELEASE',
]; ];
if (actionMasked < actionNames.length) if (actionMasked < actionNames.length) {
return '${actionNames[actionMasked]}($action)'; return '${actionNames[actionMasked]}($action)';
else } else {
return 'ACTION_$actionMasked'; return 'ACTION_$actionMasked';
}
} }
bool doublesApproximatelyMatch(dynamic a, dynamic b) => bool doublesApproximatelyMatch(dynamic a, dynamic b) =>

View File

@ -128,16 +128,19 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
await viewChannel!.invokeMethod<void>('stopTouchEvents'); await viewChannel!.invokeMethod<void>('stopTouchEvents');
if (flutterViewEvents.length != embeddedViewEvents.length) if (flutterViewEvents.length != embeddedViewEvents.length) {
return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events'; return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events';
}
final StringBuffer diff = StringBuffer(); final StringBuffer diff = StringBuffer();
for (int i = 0; i < flutterViewEvents.length; ++i) { for (int i = 0; i < flutterViewEvents.length; ++i) {
final String currentDiff = diffMotionEvents(flutterViewEvents[i], embeddedViewEvents[i]); final String currentDiff = diffMotionEvents(flutterViewEvents[i], embeddedViewEvents[i]);
if (currentDiff.isEmpty) if (currentDiff.isEmpty) {
continue; continue;
if (diff.isNotEmpty) }
if (diff.isNotEmpty) {
diff.write(', '); diff.write(', ');
}
diff.write(currentDiff); diff.write(currentDiff);
} }
return diff.toString(); return diff.toString();
@ -201,8 +204,9 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
case 'onTouch': case 'onTouch':
final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>; final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>;
flutterViewEvents.insert(0, map.cast<String, dynamic>()); flutterViewEvents.insert(0, map.cast<String, dynamic>());
if (flutterViewEvents.length > kEventsBufferSize) if (flutterViewEvents.length > kEventsBufferSize) {
flutterViewEvents.removeLast(); flutterViewEvents.removeLast();
}
setState(() {}); setState(() {});
break; break;
} }
@ -214,8 +218,9 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
case 'onTouch': case 'onTouch':
final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>; final Map<dynamic, dynamic> map = call.arguments as Map<dynamic, dynamic>;
embeddedViewEvents.insert(0, map.cast<String, dynamic>()); embeddedViewEvents.insert(0, map.cast<String, dynamic>());
if (embeddedViewEvents.length > kEventsBufferSize) if (embeddedViewEvents.length > kEventsBufferSize) {
embeddedViewEvents.removeLast(); embeddedViewEvents.removeLast();
}
setState(() {}); setState(() {});
break; break;
} }
@ -223,9 +228,10 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
} }
Widget buildEventTile(BuildContext context, int index) { Widget buildEventTile(BuildContext context, int index) {
if (embeddedViewEvents.length > index) if (embeddedViewEvents.length > index) {
return TouchEventDiff( return TouchEventDiff(
flutterViewEvents[index], embeddedViewEvents[index]); flutterViewEvents[index], embeddedViewEvents[index]);
}
return Text( return Text(
'Unmatched event, action: ${flutterViewEvents[index]['action']}'); 'Unmatched event, action: ${flutterViewEvents[index]['action']}');
} }

View File

@ -31,10 +31,11 @@ class _TestAppState extends State<TestApp> {
void _executeNextStep() { void _executeNextStep() {
setState(() { setState(() {
if (_step < steps.length) if (_step < steps.length) {
_result = steps[_step++](); _result = steps[_step++]();
else } else {
_result = Future<TestStepResult>.value(TestStepResult.complete); _result = Future<TestStepResult>.value(TestStepResult.complete);
}
}); });
} }

Some files were not shown because too many files have changed in this diff Show More