Auto-format Framework (#160545)

This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.

**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
This commit is contained in:
Michael Goderbauer
2024-12-19 12:06:21 -08:00
committed by GitHub
parent 8e0993eda8
commit 5491c8c146
4411 changed files with 455108 additions and 415991 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -78,12 +78,27 @@ import 'package:watcher/watcher.dart';
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
final String _packageFlutter = path.join(_flutterRoot, 'packages', 'flutter', 'lib');
final String _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui');
final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
final String _defaultDartUiLocation = path.join(
_flutterRoot,
'bin',
'cache',
'pkg',
'sky_engine',
'lib',
'ui',
);
final String _flutter = path.join(
_flutterRoot,
'bin',
Platform.isWindows ? 'flutter.bat' : 'flutter',
);
Future<void> main(List<String> arguments) async {
bool asserts = false;
assert(() { asserts = true; return true; }());
assert(() {
asserts = true;
return true;
}());
if (!asserts) {
print('You must run this script with asserts enabled.');
exit(1);
@@ -98,9 +113,10 @@ Future<void> main(List<String> arguments) async {
argParser.addOption(
'temp',
valueHelp: 'path',
help: 'A location where temporary files may be written. Defaults to a '
'directory in the system temp folder. If specified, will not be '
'automatically removed at the end of execution.',
help:
'A location where temporary files may be written. Defaults to a '
'directory in the system temp folder. If specified, will not be '
'automatically removed at the end of execution.',
);
argParser.addFlag(
'verbose',
@@ -111,21 +127,18 @@ Future<void> main(List<String> arguments) async {
'dart-ui-location',
defaultsTo: _defaultDartUiLocation,
valueHelp: 'path',
help: 'A location where the dart:ui dart files are to be found. Defaults to '
'the sky_engine directory installed in this flutter repo. This '
'is typically the engine/src/flutter/lib/ui directory in an engine dev setup. '
'Implies --include-dart-ui.',
help:
'A location where the dart:ui dart files are to be found. Defaults to '
'the sky_engine directory installed in this flutter repo. This '
'is typically the engine/src/flutter/lib/ui directory in an engine dev setup. '
'Implies --include-dart-ui.',
);
argParser.addFlag(
'include-dart-ui',
defaultsTo: true,
help: 'Includes the dart:ui code supplied by the engine in the analysis.',
);
argParser.addFlag(
'help',
negatable: false,
help: 'Print help for this command.',
);
argParser.addFlag('help', negatable: false, help: 'Print help for this command.');
argParser.addOption(
'interactive',
abbr: 'i',
@@ -173,11 +186,11 @@ Future<void> main(List<String> arguments) async {
assert(flutterPackages.length >= 4);
}
final bool includeDartUi = parsedArguments.wasParsed('dart-ui-location') || parsedArguments['include-dart-ui'] as bool;
final bool includeDartUi =
parsedArguments.wasParsed('dart-ui-location') || parsedArguments['include-dart-ui'] as bool;
late Directory dartUiLocation;
if (((parsedArguments['dart-ui-location'] ?? '') as String).isNotEmpty) {
dartUiLocation = Directory(
path.absolute(parsedArguments['dart-ui-location'] as String));
dartUiLocation = Directory(path.absolute(parsedArguments['dart-ui-location'] as String));
} else {
dartUiLocation = Directory(_defaultDartUiLocation);
}
@@ -195,12 +208,14 @@ Future<void> main(List<String> arguments) async {
);
} else {
if (await _SnippetChecker(
flutterPackages,
tempDirectory: parsedArguments['temp'] as String?,
verbose: parsedArguments['verbose'] as bool,
dartUiLocation: includeDartUi ? dartUiLocation : null,
).checkSnippets()) {
stderr.writeln('See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.');
flutterPackages,
tempDirectory: parsedArguments['temp'] as String?,
verbose: parsedArguments['verbose'] as bool,
dartUiLocation: includeDartUi ? dartUiLocation : null,
).checkSnippets()) {
stderr.writeln(
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
);
exit(1);
}
}
@@ -209,12 +224,8 @@ Future<void> main(List<String> arguments) async {
/// A class to represent a line of input code.
@immutable
class _Line {
const _Line({this.code = '', this.line = -1, this.indent = 0})
: generated = false;
const _Line.generated({this.code = ''})
: line = -1,
indent = 0,
generated = true;
const _Line({this.code = '', this.line = -1, this.indent = 0}) : generated = false;
const _Line.generated({this.code = ''}) : line = -1, indent = 0, generated = true;
final int line;
final int indent;
@@ -233,11 +244,11 @@ class _Line {
if (other.runtimeType != runtimeType) {
return false;
}
return other is _Line
&& other.line == line
&& other.indent == indent
&& other.code == code
&& other.generated == generated;
return other is _Line &&
other.line == line &&
other.indent == indent &&
other.code == code &&
other.generated == generated;
}
@override
@@ -307,10 +318,10 @@ class _SnippetCheckerException extends _ErrorBase implements Exception {
if (other.runtimeType != runtimeType) {
return false;
}
return other is _SnippetCheckerException
&& other.message == message
&& other.file == file
&& other.line == line;
return other is _SnippetCheckerException &&
other.message == message &&
other.file == file &&
other.line == line;
}
@override
@@ -322,14 +333,8 @@ class _SnippetCheckerException extends _ErrorBase implements Exception {
/// Changes how it converts to a string based on the source of the error.
@immutable
class _AnalysisError extends _ErrorBase {
const _AnalysisError(
String file,
int line,
int column,
this.message,
this.errorCode,
this.source,
) : super(file: file, line: line, column: column);
const _AnalysisError(String file, int line, int column, this.message, this.errorCode, this.source)
: super(file: file, line: line, column: column);
final String message;
final String errorCode;
@@ -345,13 +350,13 @@ class _AnalysisError extends _ErrorBase {
if (other.runtimeType != runtimeType) {
return false;
}
return other is _AnalysisError
&& other.file == file
&& other.line == line
&& other.column == column
&& other.message == message
&& other.errorCode == errorCode
&& other.source == source;
return other is _AnalysisError &&
other.file == file &&
other.line == line &&
other.column == column &&
other.message == message &&
other.errorCode == errorCode &&
other.source == source;
}
@override
@@ -419,16 +424,22 @@ class _SnippetChecker {
static final RegExp _nonCodeRegExp = RegExp(r'^ *(//|@)');
/// A RegExp that matches things that look like a function declaration.
static final RegExp _maybeFunctionDeclarationRegExp = RegExp(r'^([A-Z][A-Za-z0-9_<>, ?]*|int|double|num|bool|void)\?? (_?[a-z][A-Za-z0-9_<>]*)\(.*');
static final RegExp _maybeFunctionDeclarationRegExp = RegExp(
r'^([A-Z][A-Za-z0-9_<>, ?]*|int|double|num|bool|void)\?? (_?[a-z][A-Za-z0-9_<>]*)\(.*',
);
/// A RegExp that matches things that look like a getter.
static final RegExp _maybeGetterDeclarationRegExp = RegExp(r'^([A-Z][A-Za-z0-9_<>?]*|int|double|num|bool)\?? get (_?[a-z][A-Za-z0-9_<>]*) (?:=>|{).*');
static final RegExp _maybeGetterDeclarationRegExp = RegExp(
r'^([A-Z][A-Za-z0-9_<>?]*|int|double|num|bool)\?? get (_?[a-z][A-Za-z0-9_<>]*) (?:=>|{).*',
);
/// A RegExp that matches an identifier followed by a colon, potentially with two spaces of indent.
static final RegExp _namedArgumentRegExp = RegExp(r'^(?: )?([a-zA-Z0-9_]+): ');
/// A RegExp that matches things that look unambiguously like top-level declarations.
static final RegExp _topLevelDeclarationRegExp = RegExp(r'^(abstract|class|mixin|enum|typedef|final|extension) ');
static final RegExp _topLevelDeclarationRegExp = RegExp(
r'^(abstract|class|mixin|enum|typedef|final|extension) ',
);
/// A RegExp that matches things that look unambiguously like statements.
static final RegExp _statementRegExp = RegExp(r'^(if|while|for|try) ');
@@ -472,7 +483,11 @@ class _SnippetChecker {
final Directory? _dartUiLocation;
static List<File> _listDartFiles(Directory directory, {bool recursive = false}) {
return directory.listSync(recursive: recursive, followLinks: false).whereType<File>().where((File file) => path.extension(file.path) == '.dart').toList();
return directory
.listSync(recursive: recursive, followLinks: false)
.whereType<File>()
.where((File file) => path.extension(file.path) == '.dart')
.toList();
}
static const List<String> ignoresDirectives = <String>[
@@ -489,26 +504,30 @@ class _SnippetChecker {
/// Computes the headers needed for each snippet file.
List<_Line> get headersWithoutImports {
return _headersWithoutImports ??= ignoresDirectives.map<_Line>((String code) => _Line.generated(code: code)).toList();
return _headersWithoutImports ??=
ignoresDirectives.map<_Line>((String code) => _Line.generated(code: code)).toList();
}
List<_Line>? _headersWithoutImports;
/// Computes the headers needed for each snippet file.
List<_Line> get headersWithImports {
return _headersWithImports ??= <String>[
...ignoresDirectives,
'// ignore_for_file: unused_import',
"import 'dart:async';",
"import 'dart:convert';",
"import 'dart:io';",
"import 'dart:math' as math;",
"import 'dart:typed_data';",
"import 'dart:ui' as ui;",
"import 'package:flutter_test/flutter_test.dart';",
for (final File file in _listDartFiles(Directory(_packageFlutter)))
"import 'package:flutter/${path.basename(file.path)}';",
].map<_Line>((String code) => _Line.generated(code: code)).toList();
return _headersWithImports ??=
<String>[
...ignoresDirectives,
'// ignore_for_file: unused_import',
"import 'dart:async';",
"import 'dart:convert';",
"import 'dart:io';",
"import 'dart:math' as math;",
"import 'dart:typed_data';",
"import 'dart:ui' as ui;",
"import 'package:flutter_test/flutter_test.dart';",
for (final File file in _listDartFiles(Directory(_packageFlutter)))
"import 'package:flutter/${path.basename(file.path)}';",
].map<_Line>((String code) => _Line.generated(code: code)).toList();
}
List<_Line>? _headersWithImports;
/// Checks all the snippets in the Dart files in [_flutterPackage] for errors.
@@ -535,9 +554,13 @@ class _SnippetChecker {
static Directory _createTempDirectory(String? tempArg) {
if (tempArg != null) {
final Directory tempDirectory = Directory(path.join(Directory.systemTemp.absolute.path, path.basename(tempArg)));
final Directory tempDirectory = Directory(
path.join(Directory.systemTemp.absolute.path, path.basename(tempArg)),
);
if (path.basename(tempArg) != tempArg) {
stderr.writeln('Supplied temporary directory name should be a name, not a path. Using ${tempDirectory.absolute.path} instead.');
stderr.writeln(
'Supplied temporary directory name should be a name, not a path. Using ${tempDirectory.absolute.path} instead.',
);
}
print('Leaving temporary output in ${tempDirectory.absolute.path}.');
// Make sure that any directory left around from a previous run is cleared out.
@@ -591,7 +614,8 @@ class _SnippetChecker {
final List<_Line> ignorePreambleLinesOnly = <_Line>[];
final List<_Line> preambleLines = <_Line>[];
final List<_Line> customImports = <_Line>[];
bool inExamplesCanAssumePreamble = false; // Whether or not we're in the file-wide preamble section ("Examples can assume").
bool inExamplesCanAssumePreamble =
false; // Whether or not we're in the file-wide preamble section ("Examples can assume").
bool inToolSection = false; // Whether or not we're in a code snippet
bool inDartSection = false; // Whether or not we're in a '```dart' segment.
bool inOtherBlock = false; // Whether we're in some other '```' segment.
@@ -606,11 +630,15 @@ class _SnippetChecker {
// end of preamble
inExamplesCanAssumePreamble = false;
} else if (!line.startsWith('// ')) {
throw _SnippetCheckerException('Unexpected content in snippet code preamble.', file: relativeFilePath, line: lineNumber);
throw _SnippetCheckerException(
'Unexpected content in snippet code preamble.',
file: relativeFilePath,
line: lineNumber,
);
} else {
final _Line newLine = _Line(line: lineNumber, indent: 3, code: line.substring(3));
if (newLine.code.startsWith('import ')) {
customImports.add(newLine);
customImports.add(newLine);
} else {
preambleLines.add(newLine);
}
@@ -620,20 +648,40 @@ class _SnippetChecker {
}
} else if (trimmedLine.startsWith(_dartDocSnippetEndRegex)) {
if (!inToolSection) {
throw _SnippetCheckerException('{@tool-end} marker detected without matching {@tool}.', file: relativeFilePath, line: lineNumber);
throw _SnippetCheckerException(
'{@tool-end} marker detected without matching {@tool}.',
file: relativeFilePath,
line: lineNumber,
);
}
if (inDartSection) {
throw _SnippetCheckerException("Dart section didn't terminate before end of snippet", file: relativeFilePath, line: lineNumber);
throw _SnippetCheckerException(
"Dart section didn't terminate before end of snippet",
file: relativeFilePath,
line: lineNumber,
);
}
inToolSection = false;
} else if (inDartSection) {
final RegExpMatch? snippetMatch = _dartDocSnippetBeginRegex.firstMatch(trimmedLine);
if (snippetMatch != null) {
throw _SnippetCheckerException('{@tool} found inside Dart section', file: relativeFilePath, line: lineNumber);
throw _SnippetCheckerException(
'{@tool} found inside Dart section',
file: relativeFilePath,
line: lineNumber,
);
}
if (trimmedLine.startsWith(_codeBlockEndRegex)) {
inDartSection = false;
final _SnippetFile snippet = _processBlock(startLine, block, preambleLines, ignorePreambleLinesOnly, relativeFilePath, lastExample, customImports);
final _SnippetFile snippet = _processBlock(
startLine,
block,
preambleLines,
ignorePreambleLinesOnly,
relativeFilePath,
lastExample,
customImports,
);
final String path = _writeSnippetFile(snippet).path;
assert(!snippetMap.containsKey(path));
snippetMap[path] = snippet;
@@ -670,15 +718,15 @@ class _SnippetChecker {
if (inOtherBlock) {
inOtherBlock = false;
} else if (line.contains('```yaml') ||
line.contains('```ascii') ||
line.contains('```java') ||
line.contains('```objectivec') ||
line.contains('```kotlin') ||
line.contains('```swift') ||
line.contains('```glsl') ||
line.contains('```json') ||
line.contains('```csv') ||
line.contains('```sh')) {
line.contains('```ascii') ||
line.contains('```java') ||
line.contains('```objectivec') ||
line.contains('```kotlin') ||
line.contains('```swift') ||
line.contains('```glsl') ||
line.contains('```json') ||
line.contains('```csv') ||
line.contains('```sh')) {
inOtherBlock = true;
} else if (line.startsWith(_uncheckedCodeBlockStartRegex)) {
// this is an intentionally-unchecked block that doesn't appear in the API docs.
@@ -717,15 +765,26 @@ class _SnippetChecker {
/// a primitive heuristic to make snippet blocks into valid Dart code.
///
/// `block` argument will get mutated, but is copied before this function returns.
_SnippetFile _processBlock(_Line startingLine, List<String> block, List<_Line> assumptions, List<_Line> ignoreAssumptionsOnly, String filename, _SnippetFile? lastExample, List<_Line> customImports) {
_SnippetFile _processBlock(
_Line startingLine,
List<String> block,
List<_Line> assumptions,
List<_Line> ignoreAssumptionsOnly,
String filename,
_SnippetFile? lastExample,
List<_Line> customImports,
) {
if (block.isEmpty) {
throw _SnippetCheckerException('${startingLine.asLocation(filename, 0)}: Empty ```dart block in snippet code.');
throw _SnippetCheckerException(
'${startingLine.asLocation(filename, 0)}: Empty ```dart block in snippet code.',
);
}
bool hasEllipsis = false;
for (int index = 0; index < block.length; index += 1) {
final Match? match = _ellipsisRegExp.matchAsPrefix(block[index]);
if (match != null) {
hasEllipsis = true; // in case the "..." is implying some overridden members, add an ignore to silence relevant warnings
hasEllipsis =
true; // in case the "..." is implying some overridden members, add an ignore to silence relevant warnings
break;
}
}
@@ -735,15 +794,27 @@ class _SnippetChecker {
for (final String line in block) {
if (line == '// (e.g. in a stateful widget)') {
if (hasStatefulWidgetComment) {
throw _SnippetCheckerException('Example says it is in a stateful widget twice.', file: filename, line: index);
throw _SnippetCheckerException(
'Example says it is in a stateful widget twice.',
file: filename,
line: index,
);
}
hasStatefulWidgetComment = true;
} else if (line == '// continuing from previous example...') {
if (importPreviousExample) {
throw _SnippetCheckerException('Example says it continues from the previous example twice.', file: filename, line: index);
throw _SnippetCheckerException(
'Example says it continues from the previous example twice.',
file: filename,
line: index,
);
}
if (lastExample == null) {
throw _SnippetCheckerException('Example says it continues from the previous example but it is the first example in the file.', file: filename, line: index);
throw _SnippetCheckerException(
'Example says it continues from the previous example but it is the first example in the file.',
file: filename,
line: index,
);
}
importPreviousExample = true;
} else {
@@ -756,24 +827,38 @@ class _SnippetChecker {
preamble = <_Line>[
...lastExample!.code, // includes assumptions
if (hasEllipsis || hasStatefulWidgetComment)
const _Line.generated(code: '// ignore_for_file: non_abstract_class_inherits_abstract_member'),
const _Line.generated(
code: '// ignore_for_file: non_abstract_class_inherits_abstract_member',
),
];
} else {
preamble = <_Line>[
if (hasEllipsis || hasStatefulWidgetComment)
const _Line.generated(code: '// ignore_for_file: non_abstract_class_inherits_abstract_member'),
const _Line.generated(
code: '// ignore_for_file: non_abstract_class_inherits_abstract_member',
),
...assumptions,
];
}
final String firstCodeLine = block.firstWhere((String line) => !line.startsWith(_nonCodeRegExp)).trim();
final String lastCodeLine = block.lastWhere((String line) => !line.startsWith(_nonCodeRegExp)).trim();
final String firstCodeLine =
block.firstWhere((String line) => !line.startsWith(_nonCodeRegExp)).trim();
final String lastCodeLine =
block.lastWhere((String line) => !line.startsWith(_nonCodeRegExp)).trim();
if (firstCodeLine.startsWith('import ')) {
// probably an entire program
if (importPreviousExample) {
throw _SnippetCheckerException('An example cannot both be self-contained (with its own imports) and say it wants to import the previous example.', file: filename, line: startingLine.line);
throw _SnippetCheckerException(
'An example cannot both be self-contained (with its own imports) and say it wants to import the previous example.',
file: filename,
line: startingLine.line,
);
}
if (hasStatefulWidgetComment) {
throw _SnippetCheckerException('An example cannot both be self-contained (with its own imports) and say it is in a stateful widget.', file: filename, line: startingLine.line);
throw _SnippetCheckerException(
'An example cannot both be self-contained (with its own imports) and say it is in a stateful widget.',
file: filename,
line: startingLine.line,
);
}
return _SnippetFile.fromStrings(
startingLine,
@@ -782,7 +867,9 @@ class _SnippetChecker {
<_Line>[
...ignoreAssumptionsOnly,
if (hasEllipsis)
const _Line.generated(code: '// ignore_for_file: non_abstract_class_inherits_abstract_member'),
const _Line.generated(
code: '// ignore_for_file: non_abstract_class_inherits_abstract_member',
),
],
'self-contained program',
filename,
@@ -796,7 +883,7 @@ class _SnippetChecker {
...headersWithoutImports,
const _Line.generated(code: '// ignore_for_file: unused_import'),
...customImports,
]
],
};
if (hasStatefulWidgetComment) {
return _SnippetFile.fromStrings(
@@ -810,8 +897,9 @@ class _SnippetChecker {
filename,
);
} else if (firstCodeLine.startsWith(_maybeGetterDeclarationRegExp) ||
(firstCodeLine.startsWith(_maybeFunctionDeclarationRegExp) && lastCodeLine.startsWith(_trailingCloseBraceRegExp)) ||
block.any((String line) => line.startsWith(_topLevelDeclarationRegExp))) {
(firstCodeLine.startsWith(_maybeFunctionDeclarationRegExp) &&
lastCodeLine.startsWith(_trailingCloseBraceRegExp)) ||
block.any((String line) => line.startsWith(_topLevelDeclarationRegExp))) {
// probably a top-level declaration
return _SnippetFile.fromStrings(
startingLine,
@@ -822,7 +910,7 @@ class _SnippetChecker {
filename,
);
} else if (lastCodeLine.startsWith(_trailingSemicolonRegExp) ||
block.any((String line) => line.startsWith(_statementRegExp))) {
block.any((String line) => line.startsWith(_statementRegExp))) {
// probably a statement
return _SnippetFile.fromStrings(
startingLine,
@@ -903,10 +991,14 @@ class _SnippetChecker {
}
sourcePubSpec.copySync(targetPubSpec.path);
}
final File targetAnalysisOptions = File(path.join(_tempDirectory.path, 'analysis_options.yaml'));
final File targetAnalysisOptions = File(
path.join(_tempDirectory.path, 'analysis_options.yaml'),
);
if (!targetAnalysisOptions.existsSync()) {
// Use the same analysis_options.yaml configuration that's used for examples/api.
final File sourceAnalysisOptions = File(path.join(_flutterRoot, 'examples', 'api', 'analysis_options.yaml'));
final File sourceAnalysisOptions = File(
path.join(_flutterRoot, 'examples', 'api', 'analysis_options.yaml'),
);
if (!sourceAnalysisOptions.existsSync()) {
throw 'Cannot find analysis_options.yaml at ${sourceAnalysisOptions.path}, which is also used to analyze code snippets.';
}
@@ -918,9 +1010,15 @@ class _SnippetChecker {
/// Writes out a snippet section to the disk and returns the file.
File _writeSnippetFile(_SnippetFile snippetFile) {
final String snippetFileId = _createNameFromSource('snippet', snippetFile.filename, snippetFile.indexLine);
final File outputFile = File(path.join(_tempDirectory.path, '$snippetFileId.dart'))..createSync(recursive: true);
final String contents = snippetFile.code.map<String>((_Line line) => line.code).join('\n').trimRight();
final String snippetFileId = _createNameFromSource(
'snippet',
snippetFile.filename,
snippetFile.indexLine,
);
final File outputFile = File(path.join(_tempDirectory.path, '$snippetFileId.dart'))
..createSync(recursive: true);
final String contents =
snippetFile.code.map<String>((_Line line) => line.code).join('\n').trimRight();
outputFile.writeAsStringSync('$contents\n');
return outputFile;
}
@@ -956,33 +1054,45 @@ class _SnippetChecker {
final int columnNumber = int.parse(columnString, radix: 10);
if (lineNumber < 1 || lineNumber > fileContents.length + 1) {
errors.add(_AnalysisError(
file.path,
lineNumber,
columnNumber,
message,
errorCode,
_Line(line: lineNumber),
));
errors.add(_SnippetCheckerException('Error message points to non-existent line number: $error', file: file.path, line: lineNumber));
errors.add(
_AnalysisError(
file.path,
lineNumber,
columnNumber,
message,
errorCode,
_Line(line: lineNumber),
),
);
errors.add(
_SnippetCheckerException(
'Error message points to non-existent line number: $error',
file: file.path,
line: lineNumber,
),
);
continue;
}
final _SnippetFile? snippet = snippets[file.path];
if (snippet == null) {
errors.add(_SnippetCheckerException(
"Unknown section for ${file.path}. Maybe the temporary directory wasn't empty?",
file: file.path,
line: lineNumber,
));
errors.add(
_SnippetCheckerException(
"Unknown section for ${file.path}. Maybe the temporary directory wasn't empty?",
file: file.path,
line: lineNumber,
),
);
continue;
}
if (fileContents.length != snippet.code.length) {
errors.add(_SnippetCheckerException(
'Unexpected file contents for ${file.path}. File has ${fileContents.length} lines but we generated ${snippet.code.length} lines:\n${snippet.code.join("\n")}',
file: file.path,
line: lineNumber,
));
errors.add(
_SnippetCheckerException(
'Unexpected file contents for ${file.path}. File has ${fileContents.length} lines but we generated ${snippet.code.length} lines:\n${snippet.code.join("\n")}',
file: file.path,
line: lineNumber,
),
);
continue;
}
@@ -993,14 +1103,18 @@ class _SnippetChecker {
int delta = 0;
while (true) {
// find the nearest non-generated line to the error
if ((lineNumber - delta > 0) && (lineNumber - delta <= snippet.code.length) && !snippet.code[lineNumber - delta - 1].generated) {
if ((lineNumber - delta > 0) &&
(lineNumber - delta <= snippet.code.length) &&
!snippet.code[lineNumber - delta - 1].generated) {
actualSource = snippet.code[lineNumber - delta - 1];
actualLine = actualSource.line;
actualColumn = delta == 0 ? columnNumber : actualSource.code.length + 1;
actualMessage = delta == 0 ? message : '$message -- in later generated code';
break;
}
if ((lineNumber + delta < snippet.code.length) && (lineNumber + delta >= 0) && !snippet.code[lineNumber + delta].generated) {
if ((lineNumber + delta < snippet.code.length) &&
(lineNumber + delta >= 0) &&
!snippet.code[lineNumber + delta].generated) {
actualSource = snippet.code[lineNumber + delta];
actualLine = actualSource.line;
actualColumn = 1;
@@ -1010,14 +1124,16 @@ class _SnippetChecker {
delta += 1;
assert((lineNumber - delta > 0) || (lineNumber + delta < snippet.code.length));
}
errors.add(_AnalysisError(
snippet.filename,
actualLine,
actualColumn,
'$actualMessage (${snippet.generatorComment})',
errorCode,
actualSource,
));
errors.add(
_AnalysisError(
snippet.filename,
actualLine,
actualColumn,
'$actualMessage (${snippet.generatorComment})',
errorCode,
actualSource,
),
);
}
return errors;
}
@@ -1027,23 +1143,24 @@ class _SnippetChecker {
_createConfigurationFiles();
// Run pub get to avoid output from getting dependencies in the analyzer
// output.
Process.runSync(
_flutter,
<String>['pub', 'get'],
workingDirectory: _tempDirectory.absolute.path,
);
final ProcessResult result = Process.runSync(
_flutter,
<String>['--no-wrap', 'analyze', '--no-preamble', '--no-congratulate', '.'],
workingDirectory: _tempDirectory.absolute.path,
);
Process.runSync(_flutter, <String>[
'pub',
'get',
], workingDirectory: _tempDirectory.absolute.path);
final ProcessResult result = Process.runSync(_flutter, <String>[
'--no-wrap',
'analyze',
'--no-preamble',
'--no-congratulate',
'.',
], workingDirectory: _tempDirectory.absolute.path);
final List<String> stderr = result.stderr.toString().trim().split('\n');
final List<String> stdout = result.stdout.toString().trim().split('\n');
// Remove output from building the flutter tool.
stderr.removeWhere((String line) {
return line.startsWith('Building flutter tool...')
|| line.startsWith('Waiting for another flutter command to release the startup lock...')
|| line.startsWith('Flutter assets will be downloaded from ');
return line.startsWith('Building flutter tool...') ||
line.startsWith('Waiting for another flutter command to release the startup lock...') ||
line.startsWith('Flutter assets will be downloaded from ');
});
// Check out the stderr to see if the analyzer had it's own issues.
if (stderr.isNotEmpty && stderr.first.contains(RegExp(r' issues? found\. \(ran in '))) {
@@ -1086,10 +1203,8 @@ class _SnippetFile {
<_Line>[
...headers,
const _Line.generated(), // blank line
if (preamble.isNotEmpty)
...preamble,
if (preamble.isNotEmpty)
const _Line.generated(), // blank line
if (preamble.isNotEmpty) ...preamble,
if (preamble.isNotEmpty) const _Line.generated(), // blank line
_Line.generated(code: '// From: $filename:${firstLine.line}'),
...code,
],
@@ -1106,15 +1221,14 @@ class _SnippetFile {
List<_Line> preamble,
String generatorComment,
String filename, {
String? prefix, String? postfix,
String? prefix,
String? postfix,
}) {
final List<_Line> codeLines = <_Line>[
if (prefix != null)
_Line.generated(code: prefix),
if (prefix != null) _Line.generated(code: prefix),
for (int i = 0; i < code.length; i += 1)
_Line(code: code[i], line: firstLine.line + i, indent: firstLine.indent),
if (postfix != null)
_Line.generated(code: postfix),
if (postfix != null) _Line.generated(code: postfix),
];
return _SnippetFile.fromLines(codeLines, headers, preamble, generatorComment, filename);
}
@@ -1141,7 +1255,7 @@ Future<void> _runInteractive({
(dartUiLocation == null || !path.isWithin(dartUiLocation.path, file.absolute.path))) {
stderr.writeln(
'Specified file ${file.absolute.path} is not within the flutter root: '
"$_flutterRoot${dartUiLocation != null ? ' or the dart:ui location: $dartUiLocation' : ''}"
"$_flutterRoot${dartUiLocation != null ? ' or the dart:ui location: $dartUiLocation' : ''}",
);
exit(1);
}
@@ -1179,6 +1293,7 @@ Future<void> _runInteractive({
busy = false;
}
}
await rerun();
stdin.lineMode = false;

View File

@@ -31,24 +31,29 @@ Future<String> evalTestAppInChrome({
final Completer<String> resultCompleter = Completer<String>();
server = await io.HttpServer.bind('localhost', serverPort);
final Cascade cascade = Cascade()
.add((Request request) async {
if (request.requestedUri.path.endsWith('/test-result')) {
resultCompleter.complete(await request.readAsString());
return Response.ok('Test results received');
}
return Response.notFound('');
})
.add(createStaticHandler(appDirectory));
.add((Request request) async {
if (request.requestedUri.path.endsWith('/test-result')) {
resultCompleter.complete(await request.readAsString());
return Response.ok('Test results received');
}
return Response.notFound('');
})
.add(createStaticHandler(appDirectory));
shelf_io.serveRequests(server, cascade.handler);
final io.Directory userDataDirectory = io.Directory.systemTemp.createTempSync('flutter_chrome_user_data.');
chrome = await Chrome.launch(ChromeOptions(
headless: true,
debugPort: browserDebugPort,
url: appUrl,
userDataDirectory: userDataDirectory.path,
windowHeight: 500,
windowWidth: 500,
), onError: resultCompleter.completeError);
final io.Directory userDataDirectory = io.Directory.systemTemp.createTempSync(
'flutter_chrome_user_data.',
);
chrome = await Chrome.launch(
ChromeOptions(
headless: true,
debugPort: browserDebugPort,
url: appUrl,
userDataDirectory: userDataDirectory.path,
windowHeight: 500,
windowWidth: 500,
),
onError: resultCompleter.completeError,
);
return await resultCompleter.future;
} finally {
chrome?.stop();
@@ -82,19 +87,22 @@ class AppServer {
}
cascade = cascade.add((Request request) async {
final Response response = await staticHandler(request);
return response.change(headers: <String, Object>{
'cache-control': cacheControl,
});
return response.change(headers: <String, Object>{'cache-control': cacheControl});
});
shelf_io.serveRequests(server, cascade.handler);
final io.Directory userDataDirectory = io.Directory.systemTemp.createTempSync('flutter_chrome_user_data.');
final io.Directory userDataDirectory = io.Directory.systemTemp.createTempSync(
'flutter_chrome_user_data.',
);
final Completer<String> chromeErrorCompleter = Completer<String>();
chrome = await Chrome.launch(ChromeOptions(
headless: headless,
debugPort: browserDebugPort,
url: appUrl,
userDataDirectory: userDataDirectory.path,
), onError: chromeErrorCompleter.complete);
chrome = await Chrome.launch(
ChromeOptions(
headless: headless,
debugPort: browserDebugPort,
url: appUrl,
userDataDirectory: userDataDirectory.path,
),
onError: chromeErrorCompleter.complete,
);
return AppServer._(server, chrome, chromeErrorCompleter.future);
}

View File

@@ -18,7 +18,14 @@ final String _scriptLocation = path.fromUri(Platform.script);
final String _flutterRoot = path.dirname(path.dirname(path.dirname(_scriptLocation)));
final String _exampleDirectoryPath = path.join(_flutterRoot, 'examples', 'api');
final String _packageDirectoryPath = path.join(_flutterRoot, 'packages');
final String _dartUIDirectoryPath = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib');
final String _dartUIDirectoryPath = path.join(
_flutterRoot,
'bin',
'cache',
'pkg',
'sky_engine',
'lib',
);
final List<String> _knownUnlinkedExamples = <String>[
// These are template files that aren't expected to be linked.
@@ -29,11 +36,7 @@ final List<String> _knownUnlinkedExamples = <String>[
void main(List<String> args) {
final ArgParser argParser = ArgParser();
argParser.addFlag(
'help',
negatable: false,
help: 'Print help for this command.',
);
argParser.addFlag('help', negatable: false, help: 'Print help for this command.');
argParser.addOption(
'examples',
valueHelp: 'path',
@@ -134,7 +137,9 @@ class SampleChecker {
// Get a list of all the example link paths that appear in the source files.
final (Set<String> exampleLinks, Set<LinkInfo> malformedLinks) = getExampleLinks(packages);
// Also add in any that might be found in the dart:ui directory.
final (Set<String> uiExampleLinks, Set<LinkInfo> uiMalformedLinks) = getExampleLinks(dartUIPath);
final (Set<String> uiExampleLinks, Set<LinkInfo> uiMalformedLinks) = getExampleLinks(
dartUIPath,
);
exampleLinks.addAll(uiExampleLinks);
malformedLinks.addAll(uiMalformedLinks);
@@ -163,8 +168,9 @@ class SampleChecker {
}
if (missingFilenames.isNotEmpty) {
final StringBuffer buffer =
StringBuffer('The following examples are not linked from any source file API doc comments:\n');
final StringBuffer buffer = StringBuffer(
'The following examples are not linked from any source file API doc comments:\n',
);
for (final String name in missingFilenames) {
buffer.writeln(' $name');
}
@@ -173,14 +179,15 @@ class SampleChecker {
}
if (malformedLinks.isNotEmpty) {
final StringBuffer buffer =
StringBuffer('The following malformed links were found in API doc comments:\n');
final StringBuffer buffer = StringBuffer(
'The following malformed links were found in API doc comments:\n',
);
for (final LinkInfo link in malformedLinks) {
buffer.writeln(' $link');
}
buffer.write(
'Correct the formatting of these links so that they match the exact pattern:\n'
r" r'\*\* See code in (?<path>.+) \*\*'"
r" r'\*\* See code in (?<path>.+) \*\*'",
);
foundError(buffer.toString().split('\n'));
}
@@ -193,27 +200,28 @@ class SampleChecker {
}
List<File> getFiles(Directory directory, [Pattern? filenamePattern]) {
final List<File> filenames = directory
.listSync(recursive: true)
.map((FileSystemEntity entity) {
if (entity is File) {
return entity;
} else {
return null;
}
})
.where((File? filename) =>
filename != null && (filenamePattern == null || filename.absolute.path.contains(filenamePattern)))
.map<File>((File? s) => s!)
.toList();
final List<File> filenames =
directory
.listSync(recursive: true)
.map((FileSystemEntity entity) {
if (entity is File) {
return entity;
} else {
return null;
}
})
.where(
(File? filename) =>
filename != null &&
(filenamePattern == null || filename.absolute.path.contains(filenamePattern)),
)
.map<File>((File? s) => s!)
.toList();
return filenames;
}
List<File> getExampleFilenames(Directory directory) {
return getFiles(
directory.childDirectory('lib'),
RegExp(r'\d+\.dart$'),
);
return getFiles(directory.childDirectory('lib'), RegExp(r'\d+\.dart$'));
}
(Set<String>, Set<LinkInfo>) getExampleLinks(Directory searchDirectory) {
@@ -225,7 +233,9 @@ class SampleChecker {
// something that is at minimum "///*seecode<something>*" to indicate that it
// looks like an example link. It should be narrowed if we start getting false
// positives.
final RegExp malformedLinkRe = RegExp(r'^(?<malformed>\s*///\s*\*\*?\s*[sS][eE][eE]\s*[Cc][Oo][Dd][Ee].+\*\*?)');
final RegExp malformedLinkRe = RegExp(
r'^(?<malformed>\s*///\s*\*\*?\s*[sS][eE][eE]\s*[Cc][Oo][Dd][Ee].+\*\*?)',
);
for (final File file in files) {
final String contents = file.readAsStringSync();
final List<String> lines = contents.split('\n');

View File

@@ -25,18 +25,26 @@ import '../utils.dart';
///
/// If a compilation unit can not be resolved, this function ignores the
/// corresponding dart source file and logs an error using [foundError].
Future<void> analyzeWithRules(String flutterRootDirectory, List<AnalyzeRule> rules, {
Future<void> analyzeWithRules(
String flutterRootDirectory,
List<AnalyzeRule> rules, {
Iterable<String>? includePaths,
Iterable<String>? excludePaths,
}) async {
if (!Directory(flutterRootDirectory).existsSync()) {
foundError(<String>['Analyzer error: the specified $flutterRootDirectory does not exist.']);
}
final Iterable<String> includes = includePaths?.map((String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath'))
?? <String>[path.canonicalize(flutterRootDirectory)];
final Iterable<String> includes =
includePaths?.map(
(String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath'),
) ??
<String>[path.canonicalize(flutterRootDirectory)];
final AnalysisContextCollection collection = AnalysisContextCollection(
includedPaths: includes.toList(),
excludedPaths: excludePaths?.map((String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath')).toList(),
excludedPaths:
excludePaths
?.map((String relativePath) => path.canonicalize('$flutterRootDirectory/$relativePath'))
.toList(),
);
final List<String> analyzerErrors = <String>[];
@@ -51,7 +59,9 @@ Future<void> analyzeWithRules(String flutterRootDirectory, List<AnalyzeRule> rul
rule.applyTo(unit);
}
} else {
analyzerErrors.add('Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.');
analyzerErrors.add(
'Analyzer error: file $unit could not be resolved. Expected "ResolvedUnitResult", got ${unit.runtimeType}.',
);
}
}
}

View File

@@ -55,13 +55,13 @@ class _DoubleClampVisitor extends RecursiveAstVisitor<void> {
// We don't care about directives or comments.
@override
void visitImportDirective(ImportDirective node) { }
void visitImportDirective(ImportDirective node) {}
@override
void visitExportDirective(ExportDirective node) { }
void visitExportDirective(ExportDirective node) {}
@override
void visitComment(Comment node) { }
void visitComment(Comment node) {}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
@@ -76,19 +76,35 @@ class _DoubleClampVisitor extends RecursiveAstVisitor<void> {
// final f = 1.clamp;
// final y = f(0, 2) // The inferred return type is num.
PropertyAccess(
target: Expression(staticType: DartType(isDartCoreDouble: true) || DartType(isDartCoreNum: true) || DartType(isDartCoreInt: true)),
) => false,
target: Expression(
staticType: DartType(isDartCoreDouble: true) ||
DartType(isDartCoreNum: true) ||
DartType(isDartCoreInt: true),
),
) =>
false,
// Expressions like `final int x = 1.clamp(0, 2);` should be allowed.
MethodInvocation(
target: Expression(staticType: DartType(isDartCoreInt: true)),
argumentList: ArgumentList(arguments: [Expression(staticType: DartType(isDartCoreInt: true)), Expression(staticType: DartType(isDartCoreInt: true))]),
) => true,
argumentList: ArgumentList(
arguments: [
Expression(staticType: DartType(isDartCoreInt: true)),
Expression(staticType: DartType(isDartCoreInt: true)),
],
),
) =>
true,
// Otherwise, disallow num.clamp() invocations.
MethodInvocation(
target: Expression(staticType: DartType(isDartCoreDouble: true) || DartType(isDartCoreNum: true) || DartType(isDartCoreInt: true)),
) => false,
target: Expression(
staticType: DartType(isDartCoreDouble: true) ||
DartType(isDartCoreNum: true) ||
DartType(isDartCoreInt: true),
),
) =>
false,
_ => true,
};

View File

@@ -50,7 +50,7 @@ class _NoStopwatches implements AnalyzeRule {
for (final AstNode node in entry.value)
'${locationInFile(entry.key, node)}: ${node.parent}',
'\n${bold}Stopwatches introduce flakes by falling out of sync with the FakeAsync used in testing.$reset',
'A Stopwatch that stays in sync with FakeAsync is available through the Gesture or Test bindings, through samplingClock.'
'A Stopwatch that stays in sync with FakeAsync is available through the Gesture or Test bindings, through samplingClock.',
]);
}
@@ -83,8 +83,11 @@ class _StopwatchVisitor extends RecursiveAstVisitor<void> {
// The cached version, call this method instead of _checkIfImplementsStopwatchRecursively.
bool _implementsStopwatch(ClassElement classElement) {
return classElement.library.isDartCore
? classElement.name == 'Stopwatch'
:_isStopwatchClassElementCache.putIfAbsent(classElement, () => _checkIfImplementsStopwatchRecursively(classElement));
? classElement.name == 'Stopwatch'
: _isStopwatchClassElementCache.putIfAbsent(
classElement,
() => _checkIfImplementsStopwatchRecursively(classElement),
);
}
bool _isInternal(LibraryElement libraryElement) {
@@ -96,19 +99,22 @@ class _StopwatchVisitor extends RecursiveAstVisitor<void> {
bool _hasTrailingFlutterIgnore(AstNode node) {
return compilationUnit.content
.substring(node.offset + node.length, compilationUnit.lineInfo.getOffsetOfLineAfter(node.offset + node.length))
.contains(_ignoreStopwatch);
.substring(
node.offset + node.length,
compilationUnit.lineInfo.getOffsetOfLineAfter(node.offset + node.length),
)
.contains(_ignoreStopwatch);
}
// We don't care about directives or comments, skip them.
@override
void visitImportDirective(ImportDirective node) { }
void visitImportDirective(ImportDirective node) {}
@override
void visitExportDirective(ExportDirective node) { }
void visitExportDirective(ExportDirective node) {}
@override
void visitComment(Comment node) { }
void visitComment(Comment node) {}
@override
void visitConstructorName(ConstructorName node) {
@@ -118,7 +124,8 @@ class _StopwatchVisitor extends RecursiveAstVisitor<void> {
return;
}
final bool isAllowed = switch (element.returnType) {
InterfaceType(element: final ClassElement classElement) => !_implementsStopwatch(classElement),
InterfaceType(element: final ClassElement classElement) =>
!_implementsStopwatch(classElement),
InterfaceType(element: InterfaceElement()) => true,
};
if (isAllowed || _hasTrailingFlutterIgnore(node)) {
@@ -132,8 +139,9 @@ class _StopwatchVisitor extends RecursiveAstVisitor<void> {
final bool isAllowed = switch (node.staticElement) {
ExecutableElement(
returnType: DartType(element: final ClassElement classElement),
library: final LibraryElement libraryElement
) => _isInternal(libraryElement) || !_implementsStopwatch(classElement),
library: final LibraryElement libraryElement,
) =>
_isInternal(libraryElement) || !_implementsStopwatch(classElement),
Element() || null => true,
};
if (isAllowed || _hasTrailingFlutterIgnore(node)) {

View File

@@ -17,7 +17,8 @@ import 'analyze.dart';
final AnalyzeRule protectPublicStateSubtypes = _ProtectPublicStateSubtypes();
class _ProtectPublicStateSubtypes implements AnalyzeRule {
final Map<ResolvedUnitResult, List<MethodDeclaration>> _errors = <ResolvedUnitResult, List<MethodDeclaration>>{};
final Map<ResolvedUnitResult, List<MethodDeclaration>> _errors =
<ResolvedUnitResult, List<MethodDeclaration>>{};
@override
void applyTo(ResolvedUnitResult unit) {
@@ -35,15 +36,13 @@ class _ProtectPublicStateSubtypes implements AnalyzeRule {
return;
}
foundError(
<String>[
for (final MapEntry<ResolvedUnitResult, List<MethodDeclaration>> entry in _errors.entries)
for (final MethodDeclaration method in entry.value)
'${locationInFile(entry.key, method, workingDirectory)}: $method - missing "@protected" annotation.',
'\nPublic State subtypes should add @protected when overriding methods,',
'to avoid exposing internal logic to developers.',
],
);
foundError(<String>[
for (final MapEntry<ResolvedUnitResult, List<MethodDeclaration>> entry in _errors.entries)
for (final MethodDeclaration method in entry.value)
'${locationInFile(entry.key, method, workingDirectory)}: $method - missing "@protected" annotation.',
'\nPublic State subtypes should add @protected when overriding methods,',
'to avoid exposing internal logic to developers.',
]);
}
@override

View File

@@ -12,22 +12,23 @@ import '../utils.dart';
import 'analyze.dart';
/// Verify that no RenderBox subclasses call compute* instead of get* for
/// computing the intrinsic dimensions. The full list of RenderBox intrinsic
/// methods checked by this rule is listed in [candidates].
/// computing the intrinsic dimensions. The [candidates] variable contains the
/// full list of RenderBox intrinsic method invocations checked by this rule.
final AnalyzeRule renderBoxIntrinsicCalculation = _RenderBoxIntrinsicCalculationRule();
const Map<String, String> candidates = <String, String> {
const Map<String, String> candidates = <String, String>{
'computeDryBaseline': 'getDryBaseline',
'computeDryLayout': 'getDryLayout',
'computeDistanceToActualBaseline': 'getDistanceToBaseline, or getDistanceToActualBaseline',
'computeMaxIntrinsicHeight': 'getMaxIntrinsicHeight',
'computeMinIntrinsicHeight': 'getMinIntrinsicHeight',
'computeMaxIntrinsicWidth': 'getMaxIntrinsicWidth',
'computeMinIntrinsicWidth': 'getMinIntrinsicWidth'
'computeMinIntrinsicWidth': 'getMinIntrinsicWidth',
};
class _RenderBoxIntrinsicCalculationRule implements AnalyzeRule {
final Map<ResolvedUnitResult, List<(AstNode, String)>> _errors = <ResolvedUnitResult, List<(AstNode, String)>>{};
final Map<ResolvedUnitResult, List<(AstNode, String)>> _errors =
<ResolvedUnitResult, List<(AstNode, String)>>{};
@override
void applyTo(ResolvedUnitResult unit) {
@@ -60,30 +61,36 @@ class _RenderBoxIntrinsicCalculationRule implements AnalyzeRule {
class _RenderBoxSubclassVisitor extends RecursiveAstVisitor<void> {
final List<(AstNode, String)> violationNodes = <(AstNode, String)>[];
static final Map<InterfaceElement, bool> _isRenderBoxClassElementCache = <InterfaceElement, bool>{};
static final Map<InterfaceElement, bool> _isRenderBoxClassElementCache =
<InterfaceElement, bool>{};
// The cached version, call this method instead of _checkIfImplementsRenderBox.
static bool _implementsRenderBox(InterfaceElement interfaceElement) {
// Framework naming convention: a RenderObject subclass names have "Render" in its name.
if (!interfaceElement.name.contains('Render')) {
return false;
}
return interfaceElement.name == 'RenderBox'
|| _isRenderBoxClassElementCache.putIfAbsent(interfaceElement, () => _checkIfImplementsRenderBox(interfaceElement));
return interfaceElement.name == 'RenderBox' ||
_isRenderBoxClassElementCache.putIfAbsent(
interfaceElement,
() => _checkIfImplementsRenderBox(interfaceElement),
);
}
static bool _checkIfImplementsRenderBox(InterfaceElement element) {
return element.allSupertypes.any((InterfaceType interface) => _implementsRenderBox(interface.element));
return element.allSupertypes.any(
(InterfaceType interface) => _implementsRenderBox(interface.element),
);
}
// We don't care about directives, comments, or asserts.
@override
void visitImportDirective(ImportDirective node) { }
void visitImportDirective(ImportDirective node) {}
@override
void visitExportDirective(ExportDirective node) { }
void visitExportDirective(ExportDirective node) {}
@override
void visitComment(Comment node) { }
void visitComment(Comment node) {}
@override
void visitAssertStatement(AssertStatement node) { }
void visitAssertStatement(AssertStatement node) {}
@override
void visitClassDeclaration(ClassDeclaration node) {
@@ -101,15 +108,16 @@ class _RenderBoxSubclassVisitor extends RecursiveAstVisitor<void> {
return;
}
final bool isCallingSuperImplementation = switch (node.parent) {
PropertyAccess(target: SuperExpression()) ||
MethodInvocation(target: SuperExpression()) => true,
PropertyAccess(target: SuperExpression()) || MethodInvocation(target: SuperExpression()) =>
true,
_ => false,
};
if (isCallingSuperImplementation) {
return;
}
final Element? declaredInClassElement = node.staticElement?.declaration?.enclosingElement;
if (declaredInClassElement is InterfaceElement && _implementsRenderBox(declaredInClassElement)) {
if (declaredInClassElement is InterfaceElement &&
_implementsRenderBox(declaredInClassElement)) {
violationNodes.add((node, correctMethodName));
}
}

View File

@@ -32,33 +32,20 @@ Future<void> postProcess() async {
}
final String checkoutPath = Platform.environment['SDK_CHECKOUT_PATH']!;
final String docsPath = path.join(checkoutPath, 'dev', 'docs');
await runProcessWithValidations(
<String>[
'curl',
'-L',
'https://storage.googleapis.com/flutter_infra_release/flutter/$revision/api_docs.zip',
'--output',
zipDestination,
'--fail',
],
docsPath,
);
await runProcessWithValidations(<String>[
'curl',
'-L',
'https://storage.googleapis.com/flutter_infra_release/flutter/$revision/api_docs.zip',
'--output',
zipDestination,
'--fail',
], docsPath);
// Unzip to docs folder.
await runProcessWithValidations(
<String>[
'unzip',
'-o',
zipDestination,
],
docsPath,
);
await runProcessWithValidations(<String>['unzip', '-o', zipDestination], docsPath);
// Generate versions file.
await runProcessWithValidations(
<String>['flutter', '--version'],
docsPath,
);
await runProcessWithValidations(<String>['flutter', '--version'], docsPath);
final File versionFile = File('version');
final String version = versionFile.readAsStringSync();
// Recreate footer
@@ -84,7 +71,9 @@ Future<String> gitRevision({
if (fullLength) {
return gitRevision;
}
return gitRevision.length > kGitRevisionLength ? gitRevision.substring(0, kGitRevisionLength) : gitRevision;
return gitRevision.length > kGitRevisionLength
? gitRevision.substring(0, kGitRevisionLength)
: gitRevision;
}
/// Wrapper function to run a subprocess checking exit code and printing stderr and stdout.
@@ -96,8 +85,11 @@ Future<void> runProcessWithValidations(
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
bool verbose = true,
}) async {
final ProcessResult result =
processManager.runSync(command, stdoutEncoding: utf8, workingDirectory: workingDirectory);
final ProcessResult result = processManager.runSync(
command,
stdoutEncoding: utf8,
workingDirectory: workingDirectory,
);
if (result.exitCode == 0) {
if (verbose) {
print('stdout: ${result.stdout}');
@@ -123,21 +115,31 @@ Future<String> getBranchName({
if (luciBranch != null && luciBranch.trim().isNotEmpty) {
return luciBranch.trim();
}
final ProcessResult gitResult = processManager.runSync(<String>['git', 'status', '-b', '--porcelain']);
final ProcessResult gitResult = processManager.runSync(<String>[
'git',
'status',
'-b',
'--porcelain',
]);
if (gitResult.exitCode != 0) {
throw 'git status exit with non-zero exit code: ${gitResult.exitCode}';
}
final RegExpMatch? gitBranchMatch = gitBranchRegexp.firstMatch((gitResult.stdout as String).trim().split('\n').first);
final RegExpMatch? gitBranchMatch = gitBranchRegexp.firstMatch(
(gitResult.stdout as String).trim().split('\n').first,
);
return gitBranchMatch == null ? '' : gitBranchMatch.group(1)!.split('...').first;
}
/// Updates the footer of the api documentation with the correct branch and versions.
/// [footerPath] is the path to the location of the footer js file and [version] is a
/// string with the version calculated by the flutter tool.
Future<void> createFooter(File footerFile, String version,
{@visibleForTesting String? timestampParam,
@visibleForTesting String? branchParam,
@visibleForTesting String? revisionParam}) async {
Future<void> createFooter(
File footerFile,
String version, {
@visibleForTesting String? timestampParam,
@visibleForTesting String? branchParam,
@visibleForTesting String? revisionParam,
}) async {
final String timestamp = timestampParam ?? DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
final String gitBranch = branchParam ?? await getBranchName();
final String revision = revisionParam ?? await gitRevision();

View File

@@ -26,15 +26,19 @@ Future<void> main(List<String> rawArguments) async {
final ArgParser argParser = ArgParser();
argParser.addOption(
'temp_dir',
help: 'A location where temporary files may be written. Defaults to a '
help:
'A location where temporary files may be written. Defaults to a '
'directory in the system temp folder. Will write a few GiB of data, '
'so it should have sufficient free space. If a temp_dir is not '
'specified, then the default temp_dir will be created, used, and '
'removed automatically.',
);
argParser.addOption('revision',
help: 'The Flutter git repo revision to build the '
'archive with. Must be the full 40-character hash. Required.');
argParser.addOption(
'revision',
help:
'The Flutter git repo revision to build the '
'archive with. Must be the full 40-character hash. Required.',
);
argParser.addOption(
'branch',
allowed: Branch.values.map<String>((Branch branch) => branch.name),
@@ -42,32 +46,26 @@ Future<void> main(List<String> rawArguments) async {
);
argParser.addOption(
'output',
help: 'The path to the directory where the output archive should be '
help:
'The path to the directory where the output archive should be '
'written. If --output is not specified, the archive will be written to '
"the current directory. If the output directory doesn't exist, it, and "
'the path to it, will be created.',
);
argParser.addFlag(
'publish',
help: 'If set, will publish the archive to Google Cloud Storage upon '
help:
'If set, will publish the archive to Google Cloud Storage upon '
'successful creation of the archive. Will publish under this '
'directory: $baseUrl$releaseFolder',
);
argParser.addFlag(
'force',
abbr: 'f',
help: 'Overwrite a previously uploaded package.',
);
argParser.addFlag('force', abbr: 'f', help: 'Overwrite a previously uploaded package.');
argParser.addFlag(
'dry_run',
negatable: false,
help: 'Prints gsutil commands instead of executing them.',
);
argParser.addFlag(
'help',
negatable: false,
help: 'Print help for this command.',
);
argParser.addFlag('help', negatable: false, help: 'Print help for this command.');
final ArgResults parsedArguments = argParser.parse(rawArguments);

View File

@@ -47,14 +47,8 @@ class ArchiveCreator {
processManager: processManager,
subprocessOutput: subprocessOutput,
platform: platform,
)..environment['PUB_CACHE'] = path.join(
tempDir.path, '.pub-cache',
);
final String flutterExecutable = path.join(
flutterRoot.absolute.path,
'bin',
'flutter',
);
)..environment['PUB_CACHE'] = path.join(tempDir.path, '.pub-cache');
final String flutterExecutable = path.join(flutterRoot.absolute.path, 'bin', 'flutter');
final String dartExecutable = path.join(
flutterRoot.absolute.path,
'bin',
@@ -93,11 +87,10 @@ class ArchiveCreator {
required this.revision,
required this.strict,
required this.tempDir,
}) :
assert(revision.length == 40),
_processRunner = processRunner,
_flutter = flutterExecutable,
_dart = dartExecutable;
}) : assert(revision.length == 40),
_processRunner = processRunner,
_flutter = flutterExecutable,
_dart = dartExecutable;
/// The platform to use for the environment and determining which
/// platform we're running on.
@@ -140,11 +133,13 @@ class ArchiveCreator {
late String _flutter;
late String _dart;
late final Future<String> _dartArch = (() async {
// Parse 'arch' out of a string like '... "os_arch"\n'.
return (await _runDart(<String>['--version']))
.trim().split(' ').last.replaceAll('"', '').split('_')[1];
})();
late final Future<String> _dartArch =
(() async {
// Parse 'arch' out of a string like '... "os_arch"\n'.
return (await _runDart(<String>[
'--version',
])).trim().split(' ').last.replaceAll('"', '').split('_')[1];
})();
/// Returns a default archive name when given a Git revision.
/// Used when an output filename is not given.
@@ -179,10 +174,7 @@ class ArchiveCreator {
/// Performs all of the steps needed to create an archive.
Future<File> createArchive() async {
assert(_version.isNotEmpty, 'Must run initializeRepo before createArchive');
final File outputFile = fs.file(path.join(
outputDir.absolute.path,
await _archiveName,
));
final File outputFile = fs.file(path.join(outputDir.absolute.path, await _archiveName));
await _installMinGitIfNeeded();
await _populateCaches();
await _validate();
@@ -202,19 +194,14 @@ class ArchiveCreator {
// Validate that the dart binary is codesigned
try {
// TODO(fujino): Use the conductor https://github.com/flutter/flutter/issues/81701
await _processRunner.runProcess(
<String>[
'codesign',
'-vvvv',
'--check-notarization',
_dart,
],
workingDirectory: flutterRoot,
);
await _processRunner.runProcess(<String>[
'codesign',
'-vvvv',
'--check-notarization',
_dart,
], workingDirectory: flutterRoot);
} on PreparePackageException catch (e) {
throw PreparePackageException(
'The binary $_dart was not codesigned!\n${e.message}',
);
throw PreparePackageException('The binary $_dart was not codesigned!\n${e.message}');
}
}
@@ -240,7 +227,7 @@ class ArchiveCreator {
throw PreparePackageException(
'Git error when checking for a version tag attached to revision $revision.\n'
'Perhaps there is no tag at that revision?:\n'
'$exception'
'$exception',
);
}
} else {
@@ -284,7 +271,9 @@ class ArchiveCreator {
final File gitFile = fs.file(path.join(tempDir.absolute.path, 'mingit.zip'));
await gitFile.writeAsBytes(data, flush: true);
final Directory minGitPath = fs.directory(path.join(flutterRoot.absolute.path, 'bin', 'mingit'));
final Directory minGitPath = fs.directory(
path.join(flutterRoot.absolute.path, 'bin', 'mingit'),
);
await minGitPath.create(recursive: true);
await _unzipArchive(gitFile, workingDirectory: minGitPath);
}
@@ -303,37 +292,46 @@ class ArchiveCreator {
final http.Client client = http.Client();
final Directory preloadCache = fs.directory(path.join(flutterRoot.path, '.pub-preload-cache'));
preloadCache.createSync(recursive: true);
/// Fetch a single package.
Future<void> fetchPackageArchive(String name, String version) async {
await pool.withResource(() async {
stderr.write('Fetching package archive for $name-$version.\n');
int retries = 7;
while (true) {
retries-=1;
retries -= 1;
try {
final Uri packageListingUrl = Uri.parse('https://pub.dev/api/packages/$name');
// Fetch the package listing to obtain the package download url.
final http.Response packageListingResponse = await client.get(packageListingUrl);
if (packageListingResponse.statusCode != 200) {
throw Exception('Downloading $packageListingUrl failed. Status code ${packageListingResponse.statusCode}.');
throw Exception(
'Downloading $packageListingUrl failed. Status code ${packageListingResponse.statusCode}.',
);
}
final dynamic decodedPackageListing = json.decode(packageListingResponse.body);
if (decodedPackageListing is! Map) {
throw const FormatException('Package listing should be a map');
}
final dynamic versions = decodedPackageListing['versions'];
final dynamic versions = decodedPackageListing['versions'];
if (versions is! List) {
throw const FormatException('.versions should be a list');
}
final Map<String, dynamic> versionDescription = versions.firstWhere(
(dynamic description) {
if (description is! Map) {
throw const FormatException('.versions elements should be maps');
}
return description['version'] == version;
},
orElse: () => throw FormatException('Could not find $name-$version in package listing')
) as Map<String, dynamic>;
final Map<String, dynamic> versionDescription =
versions.firstWhere(
(dynamic description) {
if (description is! Map) {
throw const FormatException('.versions elements should be maps');
}
return description['version'] == version;
},
orElse:
() =>
throw FormatException(
'Could not find $name-$version in package listing',
),
)
as Map<String, dynamic>;
final dynamic downloadUrl = versionDescription['archive_url'];
if (downloadUrl is! String) {
throw const FormatException('archive_url should be a string');
@@ -345,11 +343,11 @@ class ArchiveCreator {
final http.Request request = http.Request('get', Uri.parse(downloadUrl));
final http.StreamedResponse response = await client.send(request);
if (response.statusCode != 200) {
throw Exception('Downloading ${request.url} failed. Status code ${response.statusCode}.');
throw Exception(
'Downloading ${request.url} failed. Status code ${response.statusCode}.',
);
}
final File archiveFile = fs.file(
path.join(preloadCache.path, '$name-$version.tar.gz'),
);
final File archiveFile = fs.file(path.join(preloadCache.path, '$name-$version.tar.gz'));
await response.stream.pipe(archiveFile.openWrite());
final Stream<List<int>> archiveStream = archiveFile.openRead();
final Digest r = await sha256.bind(archiveStream).first;
@@ -370,7 +368,9 @@ class ArchiveCreator {
}
});
}
final Map<String, dynamic> cacheDescription = json.decode(await _runFlutter(<String>['pub', 'cache', 'list'])) as Map<String, dynamic>;
final Map<String, dynamic> cacheDescription =
json.decode(await _runFlutter(<String>['pub', 'cache', 'list'])) as Map<String, dynamic>;
final Map<String, dynamic> packages = cacheDescription['packages'] as Map<String, dynamic>;
final List<Future<void>> downloads = <Future<void>>[];
for (final MapEntry<String, dynamic> package in packages.entries) {
@@ -416,24 +416,23 @@ class ArchiveCreator {
'--',
'**/.packages',
]);
/// Remove package_config files and any contents in .dart_tool
await _runGit(<String>[
'clean',
'-f',
'-x',
'--',
'**/.dart_tool/',
]);
await _runGit(<String>['clean', '-f', '-x', '--', '**/.dart_tool/']);
// Ensure the above commands do not clean out the cache
final Directory flutterCache = fs.directory(path.join(flutterRoot.absolute.path, 'bin', 'cache'));
final Directory flutterCache = fs.directory(
path.join(flutterRoot.absolute.path, 'bin', 'cache'),
);
if (!flutterCache.existsSync()) {
throw Exception('The flutter cache was not found at ${flutterCache.path}!');
}
/// Remove git subfolder from .pub-cache, this contains the flutter goldens
/// and new flutter_gallery.
final Directory gitCache = fs.directory(path.join(flutterRoot.absolute.path, '.pub-cache', 'git'));
final Directory gitCache = fs.directory(
path.join(flutterRoot.absolute.path, '.pub-cache', 'git'),
);
if (gitCache.existsSync()) {
gitCache.deleteSync(recursive: true);
}
@@ -449,24 +448,24 @@ class ArchiveCreator {
}
Future<String> _runDart(List<String> args, {Directory? workingDirectory}) {
return _processRunner.runProcess(
<String>[_dart, ...args],
workingDirectory: workingDirectory ?? flutterRoot,
);
return _processRunner.runProcess(<String>[
_dart,
...args,
], workingDirectory: workingDirectory ?? flutterRoot);
}
Future<String> _runFlutter(List<String> args, {Directory? workingDirectory}) {
return _processRunner.runProcess(
<String>[_flutter, ...args],
workingDirectory: workingDirectory ?? flutterRoot,
);
return _processRunner.runProcess(<String>[
_flutter,
...args,
], workingDirectory: workingDirectory ?? flutterRoot);
}
Future<String> _runGit(List<String> args, {Directory? workingDirectory}) {
return _processRunner.runProcess(
<String>['git', ...args],
workingDirectory: workingDirectory ?? flutterRoot,
);
return _processRunner.runProcess(<String>[
'git',
...args,
], workingDirectory: workingDirectory ?? flutterRoot);
}
/// Unpacks the given zip file into the currentDirectory (if set), or the
@@ -475,16 +474,9 @@ class ArchiveCreator {
workingDirectory ??= fs.directory(path.dirname(archive.absolute.path));
List<String> commandLine;
if (platform.isWindows) {
commandLine = <String>[
'7za',
'x',
archive.absolute.path,
];
commandLine = <String>['7za', 'x', archive.absolute.path];
} else {
commandLine = <String>[
'unzip',
archive.absolute.path,
];
commandLine = <String>['unzip', archive.absolute.path];
}
return _processRunner.runProcess(commandLine, workingDirectory: workingDirectory);
}
@@ -494,10 +486,11 @@ class ArchiveCreator {
List<String> commandLine;
if (platform.isWindows) {
// Unhide the .git folder, https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/attrib.
await _processRunner.runProcess(
<String>['attrib', '-h', '.git'],
workingDirectory: fs.directory(source.absolute.path),
);
await _processRunner.runProcess(<String>[
'attrib',
'-h',
'.git',
], workingDirectory: fs.directory(source.absolute.path));
commandLine = <String>[
'7za',
'a',

View File

@@ -26,13 +26,13 @@ class ArchivePublisher {
bool subprocessOutput = true,
required this.fs,
this.platform = const LocalPlatform(),
}) : assert(revision.length == 40),
platformName = platform.operatingSystem.toLowerCase(),
metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}',
_processRunner = ProcessRunner(
processManager: processManager,
subprocessOutput: subprocessOutput,
);
}) : assert(revision.length == 40),
platformName = platform.operatingSystem.toLowerCase(),
metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}',
_processRunner = ProcessRunner(
processManager: processManager,
subprocessOutput: subprocessOutput,
);
final Platform platform;
final FileSystem fs;
@@ -45,8 +45,10 @@ class ArchivePublisher {
final File outputFile;
final ProcessRunner _processRunner;
final bool dryRun;
String get destinationArchivePath => '${branch.name}/$platformName/${path.basename(outputFile.path)}';
static String getMetadataFilename(Platform platform) => 'releases_${platform.operatingSystem.toLowerCase()}.json';
String get destinationArchivePath =>
'${branch.name}/$platformName/${path.basename(outputFile.path)}';
static String getMetadataFilename(Platform platform) =>
'releases_${platform.operatingSystem.toLowerCase()}.json';
Future<String> _getChecksum(File archiveFile) async {
final AccumulatorSink<Digest> digestSink = AccumulatorSink<Digest>();
@@ -68,15 +70,10 @@ class ArchivePublisher {
final String destGsPath = '$gsReleaseFolder/$destinationArchivePath';
if (!forceUpload) {
if (await _cloudPathExists(destGsPath) && !dryRun) {
throw PreparePackageException(
'File $destGsPath already exists on cloud storage!',
);
throw PreparePackageException('File $destGsPath already exists on cloud storage!');
}
}
await _cloudCopy(
src: outputFile.absolute.path,
dest: destGsPath,
);
await _cloudCopy(src: outputFile.absolute.path, dest: destGsPath);
assert(tempDir.existsSync());
final String gcsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}';
await _publishMetadata(gcsPath);
@@ -179,7 +176,12 @@ class ArchivePublisher {
return '';
}
return _processRunner.runProcess(
<String>['python3', path.join(platform.environment['DEPOT_TOOLS']!, 'gsutil.py'), '--', ...args],
<String>[
'python3',
path.join(platform.environment['DEPOT_TOOLS']!, 'gsutil.py'),
'--',
...args,
],
workingDirectory: workingDirectory,
failOk: failOk,
);
@@ -188,9 +190,7 @@ class ArchivePublisher {
/// Determine if a file exists at a given [cloudPath].
Future<bool> _cloudPathExists(String cloudPath) async {
try {
await _runGsUtil(
<String>['stat', cloudPath],
);
await _runGsUtil(<String>['stat', cloudPath]);
} on PreparePackageException {
// `gsutil stat gs://path/to/file` will exit with 1 if file does not exist
return false;
@@ -198,11 +198,7 @@ class ArchivePublisher {
return true;
}
Future<String> _cloudCopy({
required String src,
required String dest,
int? cacheSeconds,
}) async {
Future<String> _cloudCopy({required String src, required String dest, int? cacheSeconds}) async {
// We often don't have permission to overwrite, but
// we have permission to remove, so that's what we do.
await _runGsUtil(<String>['rm', dest], failOk: true);

View File

@@ -6,7 +6,8 @@ import 'dart:io' hide Platform;
const String gobMirror = 'https://flutter.googlesource.com/mirrors/flutter';
const String githubRepo = 'https://github.com/flutter/flutter.git';
const String mingitForWindowsUrl = 'https://storage.googleapis.com/flutter_infra_release/mingit/'
const String mingitForWindowsUrl =
'https://storage.googleapis.com/flutter_infra_release/mingit/'
'603511c649b00bbef0a6122a827ac419b656bc19/mingit.zip';
const String releaseFolder = '/releases';
const String gsBase = 'gs://flutter_infra_release';
@@ -17,12 +18,7 @@ const String frameworkVersionTag = 'frameworkVersionFromGit';
const String dartVersionTag = 'dartSdkVersion';
const String dartTargetArchTag = 'dartTargetArch';
enum Branch {
beta,
stable,
master,
main;
}
enum Branch { beta, stable, master, main }
/// Exception class for when a process fails to run, so we can catch
/// it and provide something more readable than a stack trace.

View File

@@ -74,38 +74,35 @@ class ProcessRunner {
workingDirectory: workingDirectory.absolute.path,
environment: environment,
);
process.stdout.listen(
(List<int> event) {
output.addAll(event);
if (subprocessOutput) {
stdout.add(event);
}
},
onDone: () async => stdoutComplete.complete(),
);
process.stdout.listen((List<int> event) {
output.addAll(event);
if (subprocessOutput) {
stdout.add(event);
}
}, onDone: () async => stdoutComplete.complete());
if (subprocessOutput) {
process.stderr.listen(
(List<int> event) {
stderr.add(event);
},
onDone: () async => stderrComplete.complete(),
);
process.stderr.listen((List<int> event) {
stderr.add(event);
}, onDone: () async => stderrComplete.complete());
} else {
stderrComplete.complete();
}
} on ProcessException catch (e) {
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
final String message =
'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
'failed with:\n$e';
throw PreparePackageException(message);
} on ArgumentError catch (e) {
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
final String message =
'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
'failed with:\n$e';
throw PreparePackageException(message);
}
final int exitCode = await allComplete();
if (exitCode != 0 && !failOk) {
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed';
final String message =
'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed';
throw PreparePackageException(
message,
ProcessResult(0, exitCode, null, 'returned $exitCode'),

View File

@@ -8,7 +8,6 @@ dependencies:
analyzer: 6.11.0
args: 2.6.0
crypto: 3.0.6
dart_style: 2.3.7
intl: 0.19.0
file: 7.0.1
flutter_devicelab:
@@ -78,4 +77,4 @@ dependencies:
dev_dependencies:
test_api: 0.7.4
# PUBSPEC CHECKSUM: e284
# PUBSPEC CHECKSUM: cbf1

View File

@@ -18,7 +18,9 @@ import 'utils.dart';
/// If `expectNonZeroExit` is false and the process exits with a non-zero exit
/// code fails the test immediately by exiting the test process with exit code
/// 1.
Stream<String> runAndGetStdout(String executable, List<String> arguments, {
Stream<String> runAndGetStdout(
String executable,
List<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
bool expectNonZeroExit = false,
@@ -82,7 +84,9 @@ class CommandResult {
///
/// `outputMode` controls where the standard output from the command process
/// goes. See [OutputMode].
Future<Command> startCommand(String executable, List<String> arguments, {
Future<Command> startCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
OutputMode outputMode = OutputMode.print,
@@ -90,12 +94,15 @@ Future<Command> startCommand(String executable, List<String> arguments, {
void Function(String, io.Process)? outputListener,
}) async {
final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
final String commandDescription =
'${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
print('RUNNING: cd $cyan$relativeWorkingDir$reset; $green$commandDescription$reset');
final Stopwatch time = Stopwatch()..start();
print('workingDirectory: $workingDirectory, executable: $executable, arguments: $arguments');
final io.Process process = await io.Process.start(executable, arguments,
final io.Process process = await io.Process.start(
executable,
arguments,
workingDirectory: workingDirectory,
environment: environment,
);
@@ -103,36 +110,36 @@ Future<Command> startCommand(String executable, List<String> arguments, {
process,
time,
process.stdout
.transform<String>(const Utf8Decoder())
.transform(const LineSplitter())
.where((String line) => removeLine == null || !removeLine(line))
.map<String>((String line) {
final String formattedLine = '$line\n';
if (outputListener != null) {
outputListener(formattedLine, process);
}
switch (outputMode) {
case OutputMode.print:
print(line);
case OutputMode.capture:
break;
}
return line;
})
.join('\n'),
.transform<String>(const Utf8Decoder())
.transform(const LineSplitter())
.where((String line) => removeLine == null || !removeLine(line))
.map<String>((String line) {
final String formattedLine = '$line\n';
if (outputListener != null) {
outputListener(formattedLine, process);
}
switch (outputMode) {
case OutputMode.print:
print(line);
case OutputMode.capture:
break;
}
return line;
})
.join('\n'),
process.stderr
.transform<String>(const Utf8Decoder())
.transform(const LineSplitter())
.map<String>((String line) {
switch (outputMode) {
case OutputMode.print:
print(line);
case OutputMode.capture:
break;
}
return line;
})
.join('\n'),
.transform<String>(const Utf8Decoder())
.transform(const LineSplitter())
.map<String>((String line) {
switch (outputMode) {
case OutputMode.print:
print(line);
case OutputMode.capture:
break;
}
return line;
})
.join('\n'),
);
}
@@ -150,7 +157,9 @@ Future<Command> startCommand(String executable, List<String> arguments, {
///
/// `outputMode` controls where the standard output from the command process
/// goes. See [OutputMode].
Future<CommandResult> runCommand(String executable, List<String> arguments, {
Future<CommandResult> runCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
Map<String, String>? environment,
bool expectNonZeroExit = false,
@@ -160,7 +169,8 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
bool Function(String)? removeLine,
void Function(String, io.Process)? outputListener,
}) async {
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
final String commandDescription =
'${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
final String relativeWorkingDir = workingDirectory ?? path.relative(io.Directory.current.path);
if (dryRun) {
printProgress(_prettyPrintRunCommand(executable, arguments, workingDirectory));
@@ -172,7 +182,9 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
);
}
final Command command = await startCommand(executable, arguments,
final Command command = await startCommand(
executable,
arguments,
workingDirectory: workingDirectory,
environment: environment,
outputMode: outputMode,
@@ -187,7 +199,8 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
await command._savedStderr,
);
if ((result.exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && result.exitCode != expectedExitCode)) {
if ((result.exitCode == 0) == expectNonZeroExit ||
(expectedExitCode != null && result.exitCode != expectedExitCode)) {
// Print the output when we get unexpected results (unless output was
// printed already).
switch (outputMode) {
@@ -199,8 +212,7 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
}
final String allOutput = '${result.flattenedStdout}\n${result.flattenedStderr}';
foundError(<String>[
if (failureMessage != null)
failureMessage,
if (failureMessage != null) failureMessage,
'${bold}Command: $green$commandDescription$reset',
if (failureMessage == null)
'$bold${red}Command exited with exit code ${result.exitCode} but expected ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'} exit code.$reset',
@@ -209,12 +221,16 @@ Future<CommandResult> runCommand(String executable, List<String> arguments, {
'${bold}stdout and stderr output:\n$allOutput',
]);
} else {
print('ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
print(
'ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset',
);
}
return result;
}
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(io.Platform.script))));
final String _flutterRoot = path.dirname(
path.dirname(path.dirname(path.fromUri(io.Platform.script))),
);
String _prettyPrintRunCommand(String executable, List<String> arguments, String? workingDirectory) {
final StringBuffer output = StringBuffer();

View File

@@ -20,8 +20,14 @@ final String _testAppDirectory = path.join(_flutterRoot, 'dev', 'integration_tes
final String _testAppWebDirectory = path.join(_testAppDirectory, 'web');
final String _appBuildDirectory = path.join(_testAppDirectory, 'build', 'web');
final String _target = path.join('lib', 'service_worker_test.dart');
final String _targetWithCachedResources = path.join('lib', 'service_worker_test_cached_resources.dart');
final String _targetWithBlockedServiceWorkers = path.join('lib', 'service_worker_test_blocked_service_workers.dart');
final String _targetWithCachedResources = path.join(
'lib',
'service_worker_test_cached_resources.dart',
);
final String _targetWithBlockedServiceWorkers = path.join(
'lib',
'service_worker_test_blocked_service_workers.dart',
);
final String _targetPath = path.join(_testAppDirectory, _target);
enum ServiceWorkerTestType {
@@ -52,15 +58,42 @@ Future<void> main() async {
// to LUCI.
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsNonceOn);
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn);
await runWebServiceWorkerTest(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsShort,
);
await runWebServiceWorkerTest(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent,
);
await runWebServiceWorkerTest(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn,
);
await runWebServiceWorkerTest(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsNonceOn,
);
await runWebServiceWorkerTestWithCachingResources(
headless: false,
testType: ServiceWorkerTestType.withoutFlutterJs,
);
await runWebServiceWorkerTestWithCachingResources(
headless: false,
testType: ServiceWorkerTestType.withFlutterJs,
);
await runWebServiceWorkerTestWithCachingResources(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsShort,
);
await runWebServiceWorkerTestWithCachingResources(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent,
);
await runWebServiceWorkerTestWithCachingResources(
headless: false,
testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn,
);
await runWebServiceWorkerTestWithGeneratedEntrypoint(headless: false);
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
await runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: false);
@@ -74,26 +107,32 @@ Future<void> main() async {
// Regression test for https://github.com/flutter/flutter/issues/109093.
//
// Tests the entrypoint that's generated by `flutter create`.
Future<void> runWebServiceWorkerTestWithGeneratedEntrypoint({
required bool headless,
}) async {
Future<void> runWebServiceWorkerTestWithGeneratedEntrypoint({required bool headless}) async {
await _generateEntrypoint();
await runWebServiceWorkerTestWithCachingResources(headless: headless, testType: ServiceWorkerTestType.generatedEntrypoint);
await runWebServiceWorkerTestWithCachingResources(
headless: headless,
testType: ServiceWorkerTestType.generatedEntrypoint,
);
}
Future<void> _generateEntrypoint() async {
final Directory tempDirectory = Directory.systemTemp.createTempSync('flutter_web_generated_entrypoint.');
await runCommand(
_flutter,
<String>[ 'create', 'generated_entrypoint_test' ],
workingDirectory: tempDirectory.path,
final Directory tempDirectory = Directory.systemTemp.createTempSync(
'flutter_web_generated_entrypoint.',
);
await runCommand(_flutter, <String>[
'create',
'generated_entrypoint_test',
], workingDirectory: tempDirectory.path);
final File generatedEntrypoint = File(
path.join(tempDirectory.path, 'generated_entrypoint_test', 'web', 'index.html'),
);
final File generatedEntrypoint = File(path.join(tempDirectory.path, 'generated_entrypoint_test', 'web', 'index.html'));
final String generatedEntrypointCode = generatedEntrypoint.readAsStringSync();
final File testEntrypoint = File(path.join(
_testAppWebDirectory,
_testTypeToIndexFile(ServiceWorkerTestType.generatedEntrypoint),
));
final File testEntrypoint = File(
path.join(
_testAppWebDirectory,
_testTypeToIndexFile(ServiceWorkerTestType.generatedEntrypoint),
),
);
testEntrypoint.writeAsStringSync(generatedEntrypointCode);
tempDirectory.deleteSync(recursive: true);
}
@@ -104,67 +143,62 @@ Future<void> _setAppVersion(int version) async {
(await targetFile.readAsString()).replaceFirst(
RegExp(r'CLOSE\?version=\d+'),
'CLOSE?version=$version',
)
),
);
}
String _testTypeToIndexFile(ServiceWorkerTestType type) {
return switch (type) {
ServiceWorkerTestType.blockedServiceWorkers => 'index_with_blocked_service_workers.html',
ServiceWorkerTestType.withFlutterJs => 'index_with_flutterjs.html',
ServiceWorkerTestType.withoutFlutterJs => 'index_without_flutterjs.html',
ServiceWorkerTestType.withFlutterJsShort => 'index_with_flutterjs_short.html',
ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent => 'index_with_flutterjs_entrypoint_loaded.html',
ServiceWorkerTestType.withFlutterJsTrustedTypesOn => 'index_with_flutterjs_el_tt_on.html',
ServiceWorkerTestType.withFlutterJsNonceOn => 'index_with_flutterjs_el_nonce.html',
ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion => 'index_with_flutterjs_custom_sw_version.html',
ServiceWorkerTestType.generatedEntrypoint => 'generated_entrypoint.html',
ServiceWorkerTestType.blockedServiceWorkers => 'index_with_blocked_service_workers.html',
ServiceWorkerTestType.withFlutterJs => 'index_with_flutterjs.html',
ServiceWorkerTestType.withoutFlutterJs => 'index_without_flutterjs.html',
ServiceWorkerTestType.withFlutterJsShort => 'index_with_flutterjs_short.html',
ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent =>
'index_with_flutterjs_entrypoint_loaded.html',
ServiceWorkerTestType.withFlutterJsTrustedTypesOn => 'index_with_flutterjs_el_tt_on.html',
ServiceWorkerTestType.withFlutterJsNonceOn => 'index_with_flutterjs_el_nonce.html',
ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion =>
'index_with_flutterjs_custom_sw_version.html',
ServiceWorkerTestType.generatedEntrypoint => 'generated_entrypoint.html',
};
}
Future<void> _rebuildApp({ required int version, required ServiceWorkerTestType testType, required String target }) async {
Future<void> _rebuildApp({
required int version,
required ServiceWorkerTestType testType,
required String target,
}) async {
await _setAppVersion(version);
await runCommand(
_flutter,
<String>[ 'clean' ],
workingDirectory: _testAppDirectory,
);
await runCommand(
'cp',
<String>[
_testTypeToIndexFile(testType),
'index.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand(_flutter, <String>['clean'], workingDirectory: _testAppDirectory);
await runCommand('cp', <String>[
_testTypeToIndexFile(testType),
'index.html',
], workingDirectory: _testAppWebDirectory);
await runCommand(
_flutter,
<String>['build', 'web', '--web-resources-cdn', '--profile', '-t', target],
workingDirectory: _testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
}
void _expectRequestCounts(
Map<String, int> expectedCounts,
Map<String, int> requestedPathCounts,
) {
void _expectRequestCounts(Map<String, int> expectedCounts, Map<String, int> requestedPathCounts) {
expect(requestedPathCounts, expectedCounts);
requestedPathCounts.clear();
}
Future<void> _waitForAppToLoad(
Map<String, int> waitForCounts,
Map<String, int> requestedPathCounts,
AppServer? server
Map<String, int> waitForCounts,
Map<String, int> requestedPathCounts,
AppServer? server,
) async {
print('Waiting for app to load $waitForCounts');
await Future.any(<Future<Object?>>[
() async {
int tries = 1;
while (!waitForCounts.entries.every((MapEntry<String, int> entry) => (requestedPathCounts[entry.key] ?? 0) >= entry.value)) {
while (!waitForCounts.entries.every(
(MapEntry<String, int> entry) => (requestedPathCounts[entry.key] ?? 0) >= entry.value,
)) {
if (tries++ % 20 == 0) {
print('Still waiting. Requested so far: $requestedPathCounts');
}
@@ -205,9 +239,7 @@ Future<void> runWebServiceWorkerTest({
String? reportedVersion;
Future<void> startAppServer({
required String cacheControl,
}) async {
Future<void> startAppServer({required String cacheControl}) async {
final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
server = await AppServer.start(
@@ -235,14 +267,10 @@ Future<void> runWebServiceWorkerTest({
}
// Preserve old index.html as index_og.html so we can restore it later for other tests
await runCommand(
'mv',
<String>[
'index.html',
'index_og.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index.html',
'index_og.html',
], workingDirectory: _testAppWebDirectory);
final bool shouldExpectFlutterJs = testType != ServiceWorkerTestType.withoutFlutterJs;
@@ -256,31 +284,19 @@ Future<void> runWebServiceWorkerTest({
print('Call update() on the current web worker');
await startAppServer(cacheControl: 'max-age=0');
await waitForAppToLoad(<String, int> {
if (shouldExpectFlutterJs)
'flutter.js': 1,
'CLOSE': 1,
});
await waitForAppToLoad(<String, int>{if (shouldExpectFlutterJs) 'flutter.js': 1, 'CLOSE': 1});
expect(reportedVersion, '1');
reportedVersion = null;
await server!.chrome.reloadPage(ignoreCache: true);
await waitForAppToLoad(<String, int> {
if (shouldExpectFlutterJs)
'flutter.js': 2,
'CLOSE': 2,
});
await waitForAppToLoad(<String, int>{if (shouldExpectFlutterJs) 'flutter.js': 2, 'CLOSE': 2});
expect(reportedVersion, '1');
reportedVersion = null;
await _rebuildApp(version: 2, testType: testType, target: _target);
await server!.chrome.reloadPage(ignoreCache: true);
await waitForAppToLoad(<String, int>{
if (shouldExpectFlutterJs)
'flutter.js': 3,
'CLOSE': 3,
});
await waitForAppToLoad(<String, int>{if (shouldExpectFlutterJs) 'flutter.js': 3, 'CLOSE': 3});
expect(reportedVersion, '2');
reportedVersion = null;
@@ -294,18 +310,14 @@ Future<void> runWebServiceWorkerTest({
print('With cache: test first page load');
await startAppServer(cacheControl: 'max-age=3600');
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter_service_worker.js': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter_service_worker.js': 1});
expectRequestCounts(<String, int>{
// Even though the server is caching index.html is downloaded twice,
// once by the initial page load, and once by the service worker.
// Other resources are loaded once only by the service worker.
'index.html': 2,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'main.dart.js': 1,
'flutter_service_worker.js': 1,
'flutter_bootstrap.js': 1,
@@ -314,26 +326,16 @@ Future<void> runWebServiceWorkerTest({
'assets/fonts/MaterialIcons-Regular.otf': 1,
'CLOSE': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
expect(reportedVersion, '1');
reportedVersion = null;
print('With cache: test page reload');
await server!.chrome.reloadPage();
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter_service_worker.js': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter_service_worker.js': 1});
expectRequestCounts(<String, int>{
'flutter_service_worker.js': 1,
'CLOSE': 1,
});
expectRequestCounts(<String, int>{'flutter_service_worker.js': 1, 'CLOSE': 1});
expect(reportedVersion, '1');
reportedVersion = null;
@@ -342,44 +344,34 @@ Future<void> runWebServiceWorkerTest({
// Since we're caching, we need to ignore cache when reloading the page.
await server!.chrome.reloadPage(ignoreCache: true);
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter_service_worker.js': 2,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter_service_worker.js': 2});
expectRequestCounts(<String, int>{
'index.html': 2,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'flutter_service_worker.js': 2,
'flutter_bootstrap.js': 1,
'main.dart.js': 1,
'assets/AssetManifest.bin.json': 1,
'assets/FontManifest.json': 1,
'CLOSE': 1,
if (!headless)
'favicon.png': 1,
if (!headless) 'favicon.png': 1,
});
expect(reportedVersion, '2');
reportedVersion = null;
await server!.stop();
//////////////////////////////////////////////////////
// Non-caching server
//////////////////////////////////////////////////////
print('No cache: test first page load');
await _rebuildApp(version: 3, testType: testType, target: _target);
await startAppServer(cacheControl: 'max-age=0');
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter_service_worker.js': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter_service_worker.js': 1});
expectRequestCounts(<String, int>{
'index.html': 2,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'main.dart.js': 1,
'assets/FontManifest.json': 1,
'flutter_service_worker.js': 1,
@@ -388,11 +380,7 @@ Future<void> runWebServiceWorkerTest({
'assets/fonts/MaterialIcons-Regular.otf': 1,
'CLOSE': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
expect(reportedVersion, '3');
@@ -402,18 +390,15 @@ Future<void> runWebServiceWorkerTest({
await server!.chrome.reloadPage();
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'flutter_service_worker.js': 1,
});
expectRequestCounts(<String, int>{
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'flutter_service_worker.js': 1,
'CLOSE': 1,
if (!headless)
'manifest.json': 1,
if (!headless) 'manifest.json': 1,
});
expect(reportedVersion, '3');
reportedVersion = null;
@@ -427,38 +412,26 @@ Future<void> runWebServiceWorkerTest({
// this test I wasn't able to figure out what's wrong with the way we run
// Chrome from tests.
await server!.chrome.reloadPage(ignoreCache: true);
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter_service_worker.js': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter_service_worker.js': 1});
expectRequestCounts(<String, int>{
'index.html': 2,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'flutter_service_worker.js': 2,
'flutter_bootstrap.js': 1,
'main.dart.js': 1,
'assets/AssetManifest.bin.json': 1,
'assets/FontManifest.json': 1,
'CLOSE': 1,
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
expect(reportedVersion, '4');
reportedVersion = null;
} finally {
await runCommand(
'mv',
<String>[
'index_og.html',
'index.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index_og.html',
'index.html',
], workingDirectory: _testAppWebDirectory);
await _setAppVersion(1);
await server?.stop();
}
@@ -468,7 +441,7 @@ Future<void> runWebServiceWorkerTest({
Future<void> runWebServiceWorkerTestWithCachingResources({
required bool headless,
required ServiceWorkerTestType testType
required ServiceWorkerTestType testType,
}) async {
final Map<String, int> requestedPathCounts = <String, int>{};
void expectRequestCounts(Map<String, int> expectedCounts) =>
@@ -478,9 +451,7 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
_waitForAppToLoad(waitForCounts, requestedPathCounts, server);
Future<void> startAppServer({
required String cacheControl,
}) async {
Future<void> startAppServer({required String cacheControl}) async {
final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
server = await AppServer.start(
@@ -493,7 +464,7 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
browserDebugPort: browserDebugPort,
appDirectory: _appBuildDirectory,
additionalRequestHandlers: <Handler>[
(Request request) {
(Request request) {
final String requestedPath = request.url.path;
requestedPathCounts.putIfAbsent(requestedPath, () => 0);
requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
@@ -507,18 +478,17 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
}
// Preserve old index.html as index_og.html so we can restore it later for other tests
await runCommand(
'mv',
<String>[
'index.html',
'index_og.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index.html',
'index_og.html',
], workingDirectory: _testAppWebDirectory);
final bool usesFlutterBootstrapJs = testType == ServiceWorkerTestType.generatedEntrypoint;
final bool shouldExpectFlutterJs = !usesFlutterBootstrapJs && testType != ServiceWorkerTestType.withoutFlutterJs;
print('BEGIN runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)');
final bool shouldExpectFlutterJs =
!usesFlutterBootstrapJs && testType != ServiceWorkerTestType.withoutFlutterJs;
print(
'BEGIN runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)',
);
try {
//////////////////////////////////////////////////////
@@ -538,8 +508,7 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
// once by the initial page load, and once by the service worker.
// Other resources are loaded once only by the service worker.
'index.html': 2,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'main.dart.js': 1,
'flutter_service_worker.js': 1,
'flutter_bootstrap.js': usesFlutterBootstrapJs ? 2 : 1,
@@ -547,11 +516,7 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
'assets/AssetManifest.bin.json': 1,
'assets/fonts/MaterialIcons-Regular.otf': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
print('With cache: test first page reload');
@@ -598,8 +563,7 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
});
expectRequestCounts(<String, int>{
'index.html': 2,
if (shouldExpectFlutterJs)
'flutter.js': 1,
if (shouldExpectFlutterJs) 'flutter.js': 1,
'main.dart.js': 1,
'flutter_service_worker.js': 2,
'flutter_bootstrap.js': usesFlutterBootstrapJs ? 2 : 1,
@@ -607,29 +571,22 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
'assets/AssetManifest.bin.json': 1,
'assets/fonts/MaterialIcons-Regular.otf': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'favicon.png': 1,
},
if (!headless) ...<String, int>{'favicon.png': 1},
});
} finally {
await runCommand(
'mv',
<String>[
'index_og.html',
'index.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index_og.html',
'index.html',
], workingDirectory: _testAppWebDirectory);
await server?.stop();
}
print('END runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)');
print(
'END runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)',
);
}
Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({
required bool headless
}) async {
Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({required bool headless}) async {
final Map<String, int> requestedPathCounts = <String, int>{};
void expectRequestCounts(Map<String, int> expectedCounts) =>
_expectRequestCounts(expectedCounts, requestedPathCounts);
@@ -638,9 +595,7 @@ Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({
Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
_waitForAppToLoad(waitForCounts, requestedPathCounts, server);
Future<void> startAppServer({
required String cacheControl,
}) async {
Future<void> startAppServer({required String cacheControl}) async {
final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
server = await AppServer.start(
@@ -667,24 +622,22 @@ Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({
}
// Preserve old index.html as index_og.html so we can restore it later for other tests
await runCommand(
'mv',
<String>[
'index.html',
'index_og.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index.html',
'index_og.html',
], workingDirectory: _testAppWebDirectory);
print('BEGIN runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)');
try {
await _rebuildApp(version: 1, testType: ServiceWorkerTestType.blockedServiceWorkers, target: _targetWithBlockedServiceWorkers);
await _rebuildApp(
version: 1,
testType: ServiceWorkerTestType.blockedServiceWorkers,
target: _targetWithBlockedServiceWorkers,
);
print('Ensure app starts (when service workers are blocked)');
await startAppServer(cacheControl: 'max-age=3600');
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1});
expectRequestCounts(<String, int>{
'index.html': 1,
'flutter.js': 1,
@@ -693,30 +646,20 @@ Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({
'assets/fonts/MaterialIcons-Regular.otf': 1,
'CLOSE': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
} finally {
await runCommand(
'mv',
<String>[
'index_og.html',
'index.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index_og.html',
'index.html',
], workingDirectory: _testAppWebDirectory);
await server?.stop();
}
print('END runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)');
}
/// Regression test for https://github.com/flutter/flutter/issues/130212.
Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
required bool headless,
}) async {
Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({required bool headless}) async {
final Map<String, int> requestedPathCounts = <String, int>{};
void expectRequestCounts(Map<String, int> expectedCounts) =>
_expectRequestCounts(expectedCounts, requestedPathCounts);
@@ -725,9 +668,7 @@ Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
_waitForAppToLoad(waitForCounts, requestedPathCounts, server);
Future<void> startAppServer({
required String cacheControl,
}) async {
Future<void> startAppServer({required String cacheControl}) async {
final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests();
final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests();
server = await AppServer.start(
@@ -740,7 +681,7 @@ Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
browserDebugPort: browserDebugPort,
appDirectory: _appBuildDirectory,
additionalRequestHandlers: <Handler>[
(Request request) {
(Request request) {
final String requestedPath = request.url.path;
requestedPathCounts.putIfAbsent(requestedPath, () => 0);
requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
@@ -754,18 +695,18 @@ Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
}
// Preserve old index.html as index_og.html so we can restore it later for other tests
await runCommand(
'mv',
<String>[
'index.html',
'index_og.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index.html',
'index_og.html',
], workingDirectory: _testAppWebDirectory);
print('BEGIN runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)');
try {
await _rebuildApp(version: 1, testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion, target: _target);
await _rebuildApp(
version: 1,
testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion,
target: _target,
);
print('Test page load');
await startAppServer(cacheControl: 'max-age=0');
@@ -785,19 +726,12 @@ Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
'assets/AssetManifest.bin.json': 1,
'assets/fonts/MaterialIcons-Regular.otf': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
print('Test page reload, ensure service worker is not reloaded');
await server!.chrome.reloadPage(ignoreCache: true);
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter.js': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter.js': 1});
expectRequestCounts(<String, int>{
'index.html': 1,
'flutter.js': 1,
@@ -806,20 +740,17 @@ Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
'assets/fonts/MaterialIcons-Regular.otf': 1,
'CLOSE': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
print('Test page reload after rebuild, ensure service worker is not reloaded');
await _rebuildApp(version: 1, testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion, target: _target);
await _rebuildApp(
version: 1,
testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion,
target: _target,
);
await server!.chrome.reloadPage(ignoreCache: true);
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
'flutter.js': 1,
});
await waitForAppToLoad(<String, int>{'CLOSE': 1, 'flutter.js': 1});
expectRequestCounts(<String, int>{
'index.html': 1,
'flutter.js': 1,
@@ -828,21 +759,13 @@ Future<void> runWebServiceWorkerTestWithCustomServiceWorkerVersion({
'assets/fonts/MaterialIcons-Regular.otf': 1,
'CLOSE': 1,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.png'.
if (!headless)
...<String, int>{
'manifest.json': 1,
'favicon.png': 1,
},
if (!headless) ...<String, int>{'manifest.json': 1, 'favicon.png': 1},
});
} finally {
await runCommand(
'mv',
<String>[
'index_og.html',
'index.html',
],
workingDirectory: _testAppWebDirectory,
);
await runCommand('mv', <String>[
'index_og.html',
'index.html',
], workingDirectory: _testAppWebDirectory);
await server?.stop();
}
print('END runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)');

View File

@@ -12,11 +12,13 @@ import '../utils.dart';
Future<void> addToAppLifeCycleRunner() async {
if (Platform.isMacOS) {
printProgress('${green}Running add-to-app life cycle iOS integration tests$reset...');
final String addToAppDir = path.join(flutterRoot, 'dev', 'integration_tests', 'ios_add2app_life_cycle');
await runCommand('./build_and_test.sh',
<String>[],
workingDirectory: addToAppDir,
final String addToAppDir = path.join(
flutterRoot,
'dev',
'integration_tests',
'ios_add2app_life_cycle',
);
await runCommand('./build_and_test.sh', <String>[], workingDirectory: addToAppDir);
} else {
throw Exception('Only iOS has add-to-add lifecycle tests at this time.');
}

View File

@@ -9,12 +9,8 @@ import '../utils.dart';
Future<void> analyzeRunner() async {
printProgress('${green}Running analysis testing$reset');
await runCommand(
'dart',
<String>[
'--enable-asserts',
path.join(flutterRoot, 'dev', 'bots', 'analyze.dart'),
],
workingDirectory: flutterRoot,
);
await runCommand('dart', <String>[
'--enable-asserts',
path.join(flutterRoot, 'dev', 'bots', 'analyze.dart'),
], workingDirectory: flutterRoot);
}

View File

@@ -25,10 +25,13 @@ import '../utils.dart';
Future<void> androidJava11IntegrationToolTestsRunner() async {
final String toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools');
final List<String> allTests = Directory(path.join(toolsPath, 'test', 'android_java11_integration.shard'))
.listSync(recursive: true).whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList();
final List<String> allTests =
Directory(path.join(toolsPath, 'test', 'android_java11_integration.shard'))
.listSync(recursive: true)
.whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart'))
.toList();
await runDartTest(
toolsPath,

View File

@@ -11,10 +11,13 @@ import '../utils.dart';
Future<void> androidPreviewIntegrationToolTestsRunner() async {
final String toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools');
final List<String> allTests = Directory(path.join(toolsPath, 'test', 'android_preview_integration.shard'))
.listSync(recursive: true).whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList();
final List<String> allTests =
Directory(path.join(toolsPath, 'test', 'android_preview_integration.shard'))
.listSync(recursive: true)
.whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart'))
.toList();
await runDartTest(
toolsPath,

View File

@@ -11,40 +11,25 @@ import '../utils.dart';
Future<void> customerTestingRunner() async {
printProgress('${green}Running customer testing$reset');
await runCommand(
'git',
const <String>[
'fetch',
'origin',
'master',
],
workingDirectory: flutterRoot,
);
await runCommand(
'git',
const <String>[
'branch',
'-f',
'master',
'origin/master',
],
workingDirectory: flutterRoot,
);
await runCommand('git', const <String>[
'fetch',
'origin',
'master',
], workingDirectory: flutterRoot);
await runCommand('git', const <String>[
'branch',
'-f',
'master',
'origin/master',
], workingDirectory: flutterRoot);
final Map<String, String> env = Platform.environment;
final String? revision = env['REVISION'];
if (revision != null) {
await runCommand(
'git',
<String>[
'checkout',
revision,
],
workingDirectory: flutterRoot,
);
await runCommand('git', <String>['checkout', revision], workingDirectory: flutterRoot);
}
final String winScript = path.join(flutterRoot, 'dev', 'customer_testing', 'ci.bat');
await runCommand(
Platform.isWindows? winScript: './ci.sh',
Platform.isWindows ? winScript : './ci.sh',
<String>[],
workingDirectory: path.join(flutterRoot, 'dev', 'customer_testing'),
);

View File

@@ -7,14 +7,11 @@ import '../utils.dart';
Future<void> docsRunner() async {
printProgress('${green}Running flutter doc tests$reset');
await runCommand(
'./dev/bots/docs.sh',
const <String>[
'--output',
'dev/docs/api_docs.zip',
'--keep-staging',
'--staging-dir',
'dev/docs',
],
workingDirectory: flutterRoot,
);}
await runCommand('./dev/bots/docs.sh', const <String>[
'--output',
'dev/docs/api_docs.zip',
'--keep-staging',
'--staging-dir',
'dev/docs',
], workingDirectory: flutterRoot);
}

View File

@@ -22,63 +22,39 @@ import '../utils.dart';
Future<void> runFlutterDriverAndroidTests() async {
print('Running Flutter Driver Android tests...');
await runCommand(
'flutter',
<String>[
'drive',
'lib/flutter_rendered_blue_rectangle_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
],
workingDirectory: path.join(
'dev',
'integration_tests',
'native_driver_test',
),
);
await runCommand('flutter', <String>[
'drive',
'lib/flutter_rendered_blue_rectangle_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
], workingDirectory: path.join('dev', 'integration_tests', 'native_driver_test'));
await runCommand(
'flutter',
<String>[
'drive',
'lib/platform_view_blue_orange_gradient_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
],
workingDirectory: path.join(
'dev',
'integration_tests',
'native_driver_test',
),
);
await runCommand('flutter', <String>[
'drive',
'lib/platform_view_blue_orange_gradient_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
], workingDirectory: path.join('dev', 'integration_tests', 'native_driver_test'));
await runCommand(
'flutter',
<String>[
'drive',
'lib/external_texture_smiley_face_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
],
workingDirectory: path.join(
'dev',
'integration_tests',
'native_driver_test',
),
);
await runCommand('flutter', <String>[
'drive',
'lib/external_texture_smiley_face_main.dart',
// There are no reason to enable development flags for this test.
// Disable them to work around flakiness issues, and in general just
// make less things start up unnecessarily.
'--no-dds',
'--no-enable-dart-profiling',
'--test-arguments=test',
'--test-arguments=--reporter=expanded',
], workingDirectory: path.join('dev', 'integration_tests', 'native_driver_test'));
}

View File

@@ -13,68 +13,47 @@ import '../utils.dart';
/// Executes the test suite for the flutter/packages repo.
Future<void> flutterPackagesRunner() async {
Future<void> runAnalyze() async {
printProgress('${green}Running analysis for flutter/packages$reset');
final Directory checkout = Directory.systemTemp.createTempSync('flutter_packages.');
await runCommand(
'git',
const <String>[
'-c',
'core.longPaths=true',
'clone',
'https://github.com/flutter/packages.git',
'.',
],
workingDirectory: checkout.path,
);
await runCommand('git', const <String>[
'-c',
'core.longPaths=true',
'clone',
'https://github.com/flutter/packages.git',
'.',
], workingDirectory: checkout.path);
final String packagesCommit = await getFlutterPackagesVersion(flutterRoot: flutterRoot);
await runCommand(
'git',
<String>[
'-c',
'core.longPaths=true',
'checkout',
packagesCommit,
],
workingDirectory: checkout.path,
);
await runCommand('git', <String>[
'-c',
'core.longPaths=true',
'checkout',
packagesCommit,
], workingDirectory: checkout.path);
// Prep the repository tooling.
// This test does not use tool_runner.sh because in this context the test
// should always run on the entire packages repo, while tool_runner.sh
// is designed for flutter/packages CI and only analyzes changed repository
// files when run for anything but master.
final String toolDir = path.join(checkout.path, 'script', 'tool');
await runCommand(
'dart',
const <String>[
'pub',
'get',
],
workingDirectory: toolDir,
);
await runCommand('dart', const <String>['pub', 'get'], workingDirectory: toolDir);
final String toolScript = path.join(toolDir, 'bin', 'flutter_plugin_tools.dart');
await runCommand(
'dart',
<String>[
'run',
toolScript,
'analyze',
// Fetch the oldest possible dependencies, rather than the newest, to
// insulate flutter/flutter from out-of-band failures when new versions
// of dependencies are published. This compensates for the fact that
// flutter/packages doesn't use pinned dependencies, and for the
// purposes of this test using old dependencies is fine. See
// https://github.com/flutter/flutter/issues/129633
'--downgrade',
'--custom-analysis=script/configs/custom_analysis.yaml',
],
workingDirectory: checkout.path,
);
await runCommand('dart', <String>[
'run',
toolScript,
'analyze',
// Fetch the oldest possible dependencies, rather than the newest, to
// insulate flutter/flutter from out-of-band failures when new versions
// of dependencies are published. This compensates for the fact that
// flutter/packages doesn't use pinned dependencies, and for the
// purposes of this test using old dependencies is fine. See
// https://github.com/flutter/flutter/issues/129633
'--downgrade',
'--custom-analysis=script/configs/custom_analysis.yaml',
], workingDirectory: checkout.path);
}
await selectSubshard(<String, ShardRunner>{
'analyze': runAnalyze,
});
await selectSubshard(<String, ShardRunner>{'analyze': runAnalyze});
}
/// Returns the commit hash of the flutter/packages repository that's rolled in.
@@ -92,7 +71,12 @@ Future<String> getFlutterPackagesVersion({
String? packagesVersionFile,
required String flutterRoot,
}) async {
final String flutterPackagesVersionFile = path.join(flutterRoot, 'bin', 'internal', 'flutter_packages.version');
final String flutterPackagesVersionFile = path.join(
flutterRoot,
'bin',
'internal',
'flutter_packages.version',
);
final File versionFile = fileSystem.file(packagesVersionFile ?? flutterPackagesVersionFile);
final String versionFileContents = await versionFile.readAsString();

View File

@@ -9,7 +9,9 @@ import 'package:path/path.dart' as path;
import '../utils.dart';
Future<void> frameworkCoverageRunner() async {
final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info'));
final File coverageFile = File(
path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info'),
);
if (!coverageFile.existsSync()) {
foundError(<String>[
'${red}Coverage file not found.$reset',
@@ -19,7 +21,8 @@ Future<void> frameworkCoverageRunner() async {
return;
}
coverageFile.deleteSync();
await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'),
await runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter'),
options: const <String>['--coverage'],
);
if (!coverageFile.existsSync()) {

View File

@@ -14,7 +14,10 @@ import '../utils.dart';
import 'run_test_harness_tests.dart';
Future<void> frameworkTestsRunner() async {
final List<String> trackWidgetCreationAlternatives = <String>['--track-widget-creation', '--no-track-widget-creation'];
final List<String> trackWidgetCreationAlternatives = <String>[
'--track-widget-creation',
'--no-track-widget-creation',
];
Future<void> runWidgets() async {
printProgress('${green}Running packages/flutter tests $reset for ${cyan}test/widgets/$reset');
@@ -22,7 +25,7 @@ Future<void> frameworkTestsRunner() async {
await runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter'),
options: <String>[trackWidgetCreationOption],
tests: <String>[ path.join('test', 'widgets') + path.separator ],
tests: <String>[path.join('test', 'widgets') + path.separator],
);
}
// Try compiling code outside of the packages/flutter directory with and without --track-widget-creation
@@ -42,7 +45,10 @@ Future<void> frameworkTestsRunner() async {
// Run profile mode tests (see packages/flutter/test_profile/README.md)
await runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter'),
options: <String>['--dart-define=dart.vm.product=false', '--dart-define=dart.vm.profile=true'],
options: <String>[
'--dart-define=dart.vm.product=false',
'--dart-define=dart.vm.profile=true',
],
tests: <String>['test_profile${path.separator}'],
);
}
@@ -55,15 +61,19 @@ Future<void> frameworkTestsRunner() async {
);
}
Future<void> runLibraries() async {
final List<String> tests = Directory(path.join(flutterRoot, 'packages', 'flutter', 'test'))
.listSync(followLinks: false)
.whereType<Directory>()
.where((Directory dir) => !dir.path.endsWith('widgets'))
.map<String>((Directory dir) => path.join('test', path.basename(dir.path)) + path.separator)
.toList();
printProgress('${green}Running packages/flutter tests$reset for $cyan${tests.join(", ")}$reset');
final List<String> tests =
Directory(path.join(flutterRoot, 'packages', 'flutter', 'test'))
.listSync(followLinks: false)
.whereType<Directory>()
.where((Directory dir) => !dir.path.endsWith('widgets'))
.map<String>(
(Directory dir) => path.join('test', path.basename(dir.path)) + path.separator,
)
.toList();
printProgress(
'${green}Running packages/flutter tests$reset for $cyan${tests.join(", ")}$reset',
);
for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
await runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter'),
@@ -74,17 +84,15 @@ Future<void> frameworkTestsRunner() async {
}
Future<void> runExampleTests() async {
await runCommand(
flutter,
<String>['config', '--enable-${Platform.operatingSystem}-desktop'],
workingDirectory: flutterRoot,
);
await runCommand(
dart,
<String>[path.join(flutterRoot, 'dev', 'tools', 'examples_smoke_test.dart')],
workingDirectory: path.join(flutterRoot, 'examples', 'api'),
);
for (final FileSystemEntity entity in Directory(path.join(flutterRoot, 'examples')).listSync()) {
await runCommand(flutter, <String>[
'config',
'--enable-${Platform.operatingSystem}-desktop',
], workingDirectory: flutterRoot);
await runCommand(dart, <String>[
path.join(flutterRoot, 'dev', 'tools', 'examples_smoke_test.dart'),
], workingDirectory: path.join(flutterRoot, 'examples', 'api'));
for (final FileSystemEntity entity
in Directory(path.join(flutterRoot, 'examples')).listSync()) {
if (entity is! Directory || !Directory(path.join(entity.path, 'test')).existsSync()) {
continue;
}
@@ -105,14 +113,25 @@ Future<void> frameworkTestsRunner() async {
required Set<String> disallowed,
}) async {
try {
await runCommand(
flutter,
<String>[
'build', 'appbundle', '--$modeArgument', path.join('lib', sourceFile),
],
workingDirectory: tracingDirectory,
await runCommand(flutter, <String>[
'build',
'appbundle',
'--$modeArgument',
path.join('lib', sourceFile),
], workingDirectory: tracingDirectory);
final Archive archive = ZipDecoder().decodeBytes(
File(
path.join(
tracingDirectory,
'build',
'app',
'outputs',
'bundle',
modeArgument,
'app-$modeArgument.aab',
),
).readAsBytesSync(),
);
final Archive archive = ZipDecoder().decodeBytes(File(path.join(tracingDirectory, 'build', 'app', 'outputs', 'bundle', modeArgument, 'app-$modeArgument.aab')).readAsBytesSync());
final ArchiveFile libapp = archive.findFile('base/lib/arm64-v8a/libapp.so')!;
final Uint8List libappBytes = libapp.content as Uint8List; // bytes decompressed here
final String libappStrings = utf8.decode(libappBytes, allowMalformed: true);
@@ -120,72 +139,78 @@ Future<void> frameworkTestsRunner() async {
final List<String> results = <String>[];
for (final String pattern in allowed) {
if (!libappStrings.contains(pattern)) {
results.add('When building with --$modeArgument, expected to find "$pattern" in libapp.so but could not find it.');
results.add(
'When building with --$modeArgument, expected to find "$pattern" in libapp.so but could not find it.',
);
}
}
for (final String pattern in disallowed) {
if (libappStrings.contains(pattern)) {
results.add('When building with --$modeArgument, expected to not find "$pattern" in libapp.so but did find it.');
results.add(
'When building with --$modeArgument, expected to not find "$pattern" in libapp.so but did find it.',
);
}
}
return results;
} catch (error, stackTrace) {
return <String>[
error.toString(),
...stackTrace.toString().trimRight().split('\n'),
];
return <String>[error.toString(), ...stackTrace.toString().trimRight().split('\n')];
}
}
final List<String> results = <String>[];
results.addAll(await verifyTracingAppBuild(
modeArgument: 'profile',
sourceFile: 'control.dart', // this is the control, the other two below are the actual test
allowed: <String>{
'TIMELINE ARGUMENTS TEST CONTROL FILE',
'toTimelineArguments used in non-debug build', // we call toTimelineArguments directly to check the message does exist
},
disallowed: <String>{
'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE',
},
));
results.addAll(await verifyTracingAppBuild(
modeArgument: 'profile',
sourceFile: 'test.dart',
allowed: <String>{
'BUILT IN PROFILE MODE', 'RenderTest.performResize called', // controls
'BUILD', 'LAYOUT', 'PAINT', // we output these to the timeline in profile builds
// (LAYOUT and PAINT also exist because of NEEDS-LAYOUT and NEEDS-PAINT in RenderObject.toStringShort)
},
disallowed: <String>{
'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE',
'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only
'toTimelineArguments used in non-debug build', // entire function should get dropped by tree shaker
},
));
results.addAll(await verifyTracingAppBuild(
modeArgument: 'release',
sourceFile: 'test.dart',
allowed: <String>{
'BUILT IN RELEASE MODE', 'RenderTest.performResize called', // controls
},
disallowed: <String>{
'BUILT IN DEBUG MODE', 'BUILT IN PROFILE MODE',
'BUILD', 'LAYOUT', 'PAINT', // these are only used in Timeline.startSync calls that should not appear in release builds
'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only
'toTimelineArguments used in non-debug build', // not included in release builds
},
));
results.addAll(
await verifyTracingAppBuild(
modeArgument: 'profile',
sourceFile: 'control.dart', // this is the control, the other two below are the actual test
allowed: <String>{
'TIMELINE ARGUMENTS TEST CONTROL FILE',
'toTimelineArguments used in non-debug build', // we call toTimelineArguments directly to check the message does exist
},
disallowed: <String>{'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE'},
),
);
results.addAll(
await verifyTracingAppBuild(
modeArgument: 'profile',
sourceFile: 'test.dart',
allowed: <String>{
'BUILT IN PROFILE MODE', 'RenderTest.performResize called', // controls
'BUILD', 'LAYOUT', 'PAINT', // we output these to the timeline in profile builds
// (LAYOUT and PAINT also exist because of NEEDS-LAYOUT and NEEDS-PAINT in RenderObject.toStringShort)
},
disallowed: <String>{
'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE',
'TestWidget.debugFillProperties called',
'RenderTest.debugFillProperties called', // debug only
'toTimelineArguments used in non-debug build', // entire function should get dropped by tree shaker
},
),
);
results.addAll(
await verifyTracingAppBuild(
modeArgument: 'release',
sourceFile: 'test.dart',
allowed: <String>{
'BUILT IN RELEASE MODE', 'RenderTest.performResize called', // controls
},
disallowed: <String>{
'BUILT IN DEBUG MODE', 'BUILT IN PROFILE MODE',
'BUILD',
'LAYOUT',
'PAINT', // these are only used in Timeline.startSync calls that should not appear in release builds
'TestWidget.debugFillProperties called',
'RenderTest.debugFillProperties called', // debug only
'toTimelineArguments used in non-debug build', // not included in release builds
},
),
);
if (results.isNotEmpty) {
foundError(results);
}
}
Future<void> runFixTests(String package) async {
final List<String> args = <String>[
'fix',
'--compare-to-golden',
];
final List<String> args = <String>['fix', '--compare-to-golden'];
await runCommand(
dart,
args,
@@ -194,14 +219,10 @@ Future<void> frameworkTestsRunner() async {
}
Future<void> runPrivateTests() async {
final List<String> args = <String>[
'run',
'bin/test_private.dart',
];
final List<String> args = <String>['run', 'bin/test_private.dart'];
final Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': flutterRoot,
if (Directory(pubCache).existsSync())
'PUB_CACHE': pubCache,
if (Directory(pubCache).existsSync()) 'PUB_CACHE': pubCache,
};
adjustEnvironmentToEnableFlutterAsserts(environment);
await runCommand(
@@ -217,7 +238,9 @@ Future<void> frameworkTestsRunner() async {
// These tests need to be platform agnostic as they are only run on a linux
// machine to save on execution time and cost.
Future<void> runSlow() async {
printProgress('${green}Running slow package tests$reset for directories other than packages/flutter');
printProgress(
'${green}Running slow package tests$reset for directories other than packages/flutter',
);
await runTracingTests();
await runFixTests('flutter');
await runFixTests('flutter_test');
@@ -227,21 +250,26 @@ Future<void> frameworkTestsRunner() async {
}
Future<void> runMisc() async {
printProgress('${green}Running package tests$reset for directories other than packages/flutter');
printProgress(
'${green}Running package tests$reset for directories other than packages/flutter',
);
await testHarnessTestsRunner();
await runExampleTests();
await runFlutterTest(
path.join(flutterRoot, 'dev', 'a11y_assessments'),
tests: <String>[ 'test' ],
tests: <String>['test'],
);
await runDartTest(path.join(flutterRoot, 'dev', 'bots'));
await runDartTest(
path.join(flutterRoot, 'dev', 'devicelab'),
ensurePrecompiledTool: false, // See https://github.com/flutter/flutter/issues/86209
ensurePrecompiledTool: false, // See https://github.com/flutter/flutter/issues/86209
);
await runDartTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true);
// TODO(gspencergoog): Remove the exception for fatalWarnings once https://github.com/flutter/flutter/issues/113782 has landed.
await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false);
await runFlutterTest(
path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'),
fatalWarnings: false,
);
await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'ui'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'tools'));
@@ -249,19 +277,31 @@ Future<void> frameworkTestsRunner() async {
await runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_defaults'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'));
await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: <String>[path.join('test', 'src', 'real_tests')]);
await runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), options: <String>[
'--enable-vmservice',
// Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard.
'--exclude-tags=web',
]);
await runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter_driver'),
tests: <String>[path.join('test', 'src', 'real_tests')],
);
await runFlutterTest(
path.join(flutterRoot, 'packages', 'integration_test'),
options: <String>[
'--enable-vmservice',
// Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard.
'--exclude-tags=web',
],
);
// Run java unit tests for integration_test
//
// Generate Gradle wrapper if it doesn't exist.
Process.runSync(
flutter,
<String>['build', 'apk', '--config-only'],
workingDirectory: path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android'),
workingDirectory: path.join(
flutterRoot,
'packages',
'integration_test',
'example',
'android',
),
);
await runCommand(
path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android', 'gradlew$bat'),
@@ -270,7 +310,13 @@ Future<void> frameworkTestsRunner() async {
'--tests',
'dev.flutter.plugins.integration_test.FlutterDeviceScreenshotTest',
],
workingDirectory: path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android'),
workingDirectory: path.join(
flutterRoot,
'packages',
'integration_test',
'example',
'android',
),
);
await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'));
await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'));
@@ -278,11 +324,11 @@ Future<void> frameworkTestsRunner() async {
await runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'));
await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'));
const String httpClientWarning =
'Warning: At least one test in this suite creates an HttpClient. When running a test suite that uses\n'
'TestWidgetsFlutterBinding, all HTTP requests will return status code 400, and no network request\n'
'will actually be made. Any test expecting a real network connection and status code will fail.\n'
'To test code that needs an HttpClient, provide your own HttpClient implementation to the code under\n'
'test, so that your test can consistently provide a testable response to the code under test.';
'Warning: At least one test in this suite creates an HttpClient. When running a test suite that uses\n'
'TestWidgetsFlutterBinding, all HTTP requests will return status code 400, and no network request\n'
'will actually be made. Any test expecting a real network connection and status code will fail.\n'
'To test code that needs an HttpClient, provide your own HttpClient implementation to the code under\n'
'test, so that your test can consistently provide a testable response to the code under test.';
await runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter_test'),
script: path.join('test', 'bindings_test_failure.dart'),
@@ -292,8 +338,8 @@ Future<void> frameworkTestsRunner() async {
final Iterable<Match> matches = httpClientWarning.allMatches(result.flattenedStdout!);
if (matches.isEmpty || matches.length > 1) {
return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n\n'
'stdout:\n${result.flattenedStdout}\n\n'
'stderr:\n${result.flattenedStderr}';
'stdout:\n${result.flattenedStdout}\n\n'
'stderr:\n${result.flattenedStderr}';
}
return null;
},

View File

@@ -8,24 +8,16 @@ import '../utils.dart';
// Runs flutter_precache.
Future<void> fuchsiaPrecacheRunner() async {
printProgress('${green}Running flutter precache tests$reset');
await runCommand(
'flutter',
const <String>[
'config',
'--enable-fuchsia',
],
workingDirectory: flutterRoot,
);
await runCommand(
'flutter',
const <String>[
'precache',
'--flutter_runner',
'--fuchsia',
'--no-android',
'--no-ios',
'--force',
],
workingDirectory: flutterRoot,
);
await runCommand('flutter', const <String>[
'config',
'--enable-fuchsia',
], workingDirectory: flutterRoot);
await runCommand('flutter', const <String>[
'precache',
'--flutter_runner',
'--fuchsia',
'--no-android',
'--no-ios',
'--force',
], workingDirectory: flutterRoot);
}

View File

@@ -17,17 +17,13 @@ import '../utils.dart';
Future<void> skpGeneratorTestsRunner() async {
printProgress('${green}Running skp_generator from flutter/tests$reset');
final Directory checkout = Directory.systemTemp.createTempSync('flutter_skp_generator.');
await runCommand(
'git',
<String>[
'-c',
'core.longPaths=true',
'clone',
'https://github.com/flutter/tests.git',
'.',
],
workingDirectory: checkout.path,
);
await runCommand('git', <String>[
'-c',
'core.longPaths=true',
'clone',
'https://github.com/flutter/tests.git',
'.',
], workingDirectory: checkout.path);
await runCommand(
'./build.sh',
const <String>[],

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show File, Platform;
import 'dart:io' show File, Platform;
import 'package:path/path.dart' as path;
@@ -19,11 +19,12 @@ String get platformFolderName {
if (Platform.isLinux) {
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.',
);
}
Future<void> testHarnessTestsRunner() async {
printProgress('${green}Running test harness tests...$reset');
await _validateEngineHash();
@@ -55,10 +56,10 @@ Future<void> testHarnessTestsRunner() async {
printOutput: false,
outputChecker: (CommandResult result) {
return result.flattenedStdout!.contains('failingPendingTimerTest')
? null
: 'Failed to find the stack trace for the pending Timer.\n\n'
'stdout:\n${result.flattenedStdout}\n\n'
'stderr:\n${result.flattenedStderr}';
? null
: 'Failed to find the stack trace for the pending Timer.\n\n'
'stdout:\n${result.flattenedStdout}\n\n'
'stderr:\n${result.flattenedStderr}';
},
),
() => runFlutterTest(
@@ -67,16 +68,17 @@ Future<void> testHarnessTestsRunner() async {
expectFailure: true,
printOutput: false,
outputChecker: (CommandResult result) {
const String expectedError = '══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════\n'
const String expectedError =
'══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════\n'
'The following StateError was thrown running a test (but after the test had completed):\n'
'Bad state: Exception thrown after test completed.';
if (result.flattenedStdout!.contains(expectedError)) {
return null;
}
return 'Failed to find expected output on stdout.\n\n'
'Expected output:\n$expectedError\n\n'
'Actual stdout:\n${result.flattenedStdout}\n\n'
'Actual stderr:\n${result.flattenedStderr}';
'Expected output:\n$expectedError\n\n'
'Actual stdout:\n${result.flattenedStdout}\n\n'
'Actual stderr:\n${result.flattenedStderr}';
},
),
() => runFlutterTest(
@@ -134,7 +136,15 @@ Future<void> testHarnessTestsRunner() async {
/// Verify the Flutter Engine is the revision in
/// bin/cache/internal/engine.version.
Future<void> _validateEngineHash() async {
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',
);
if (runningInDartHHHBot) {
// The Dart HHH bots intentionally modify the local artifact cache
@@ -145,7 +155,9 @@ Future<void> _validateEngineHash() async {
return;
}
final String expectedVersion = File(engineVersionFile).readAsStringSync().trim();
final CommandResult result = await runCommand(flutterTester, <String>['--help'], outputMode: OutputMode.capture);
final CommandResult result = await runCommand(flutterTester, <String>[
'--help',
], outputMode: OutputMode.capture);
if (result.flattenedStdout!.isNotEmpty) {
foundError(<String>[
'${red}The stdout of `$flutterTester --help` was not empty:$reset',
@@ -165,6 +177,8 @@ Future<void> _validateEngineHash() async {
return;
}
if (!actualVersion.contains(expectedVersion)) {
foundError(<String>['${red}Expected "Flutter Engine Version: $expectedVersion", but found "$actualVersion".$reset']);
foundError(<String>[
'${red}Expected "Flutter Engine Version: $expectedVersion", but found "$actualVersion".$reset',
]);
}
}

View File

@@ -14,16 +14,12 @@ import '../utils.dart';
Future<void> verifyCodesignedTestRunner() async {
printProgress('${green}Running binaries codesign verification$reset');
await runCommand(
'flutter',
<String>[
'precache',
'--android',
'--ios',
'--macos'
],
workingDirectory: flutterRoot,
);
await runCommand('flutter', <String>[
'precache',
'--android',
'--ios',
'--macos',
], workingDirectory: flutterRoot);
await verifyExist(flutterRoot);
await verifySignatures(flutterRoot);
@@ -43,7 +39,7 @@ const List<String> expectedEntitlements = <String>[
/// This list should be kept in sync with the actual contents of Flutter's
/// cache.
List<String> binariesWithEntitlements(String flutterRoot) {
return <String> [
return <String>[
'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot',
'artifacts/engine/android-arm64-profile/darwin-x64/gen_snapshot',
@@ -75,8 +71,7 @@ List<String> binariesWithEntitlements(String flutterRoot) {
'dart-sdk/bin/dartaotruntime',
'dart-sdk/bin/utils/gen_snapshot',
'dart-sdk/bin/utils/wasm-opt',
]
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
].map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
}
/// Binaries that are only expected to be codesigned.
@@ -105,8 +100,7 @@ List<String> binariesWithoutEntitlements(String flutterRoot) {
'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
'artifacts/ios-deploy/ios-deploy',
]
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
].map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
}
/// Binaries that are not expected to be codesigned.
@@ -119,11 +113,9 @@ List<String> unsignedBinaries(String flutterRoot) {
'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter',
'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter',
'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter',
]
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
].map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
}
/// xcframeworks that are expected to be codesigned.
///
/// This list should be kept in sync with the actual contents of Flutter's
@@ -139,8 +131,7 @@ List<String> signedXcframeworks(String flutterRoot) {
'artifacts/engine/darwin-x64-profile/FlutterMacOS.xcframework',
'artifacts/engine/darwin-x64-release/FlutterMacOS.xcframework',
'artifacts/engine/darwin-x64/FlutterMacOS.xcframework',
]
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
].map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
}
/// Verify the existence of all expected binaries in cache.
@@ -151,25 +142,30 @@ List<String> signedXcframeworks(String flutterRoot) {
/// [binariesWithEntitlements], [binariesWithoutEntitlements], and
/// [unsignedBinaries] lists should be updated accordingly.
Future<void> verifyExist(
String flutterRoot,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()
String flutterRoot, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final List<String> binaryPaths = await findBinaryPaths(
path.join(flutterRoot, 'bin', 'cache'),
processManager: processManager,
);
final List<String> expectedSigned = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
final List<String> expectedSigned =
binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
final List<String> expectedUnsigned = unsignedBinaries(flutterRoot);
final Set<String> foundFiles = <String>{
for (final String binaryPath in binaryPaths)
if (expectedSigned.contains(binaryPath)) binaryPath
else if (expectedUnsigned.contains(binaryPath)) binaryPath
else throw Exception('Found unexpected binary in cache: $binaryPath'),
if (expectedSigned.contains(binaryPath))
binaryPath
else if (expectedUnsigned.contains(binaryPath))
binaryPath
else
throw Exception('Found unexpected binary in cache: $binaryPath'),
};
if (foundFiles.length < expectedSigned.length) {
final List<String> unfoundFiles = <String>[
for (final String file in expectedSigned) if (!foundFiles.contains(file)) file,
for (final String file in expectedSigned)
if (!foundFiles.contains(file)) file,
];
print(
'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n'
@@ -185,16 +181,17 @@ Future<void> verifyExist(
/// Verify code signatures and entitlements of all binaries in the cache.
Future<void> verifySignatures(
String flutterRoot,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
) async {
String flutterRoot, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final List<String> unsignedFiles = <String>[];
final List<String> wrongEntitlementBinaries = <String>[];
final List<String> unexpectedFiles = <String>[];
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
final List<String> binariesAndXcframeworks =
(await findBinaryPaths(cacheDirectory, processManager: processManager)) + (await findXcframeworksPaths(cacheDirectory, processManager: processManager));
(await findBinaryPaths(cacheDirectory, processManager: processManager)) +
(await findXcframeworksPaths(cacheDirectory, processManager: processManager));
for (final String pathToCheck in binariesAndXcframeworks) {
bool verifySignature = false;
@@ -220,13 +217,11 @@ Future<void> verifySignatures(
continue;
}
print('Verifying the code signature of $pathToCheck');
final io.ProcessResult codeSignResult = await processManager.run(
<String>[
'codesign',
'-vvv',
pathToCheck,
],
);
final io.ProcessResult codeSignResult = await processManager.run(<String>[
'codesign',
'-vvv',
pathToCheck,
]);
if (codeSignResult.exitCode != 0) {
unsignedFiles.add(pathToCheck);
print(
@@ -238,7 +233,11 @@ Future<void> verifySignatures(
}
if (verifyEntitlements) {
print('Verifying entitlements of $pathToCheck');
if (!(await hasExpectedEntitlements(pathToCheck, flutterRoot, processManager: processManager))) {
if (!(await hasExpectedEntitlements(
pathToCheck,
flutterRoot,
processManager: processManager,
))) {
wrongEntitlementBinaries.add(pathToCheck);
}
}
@@ -280,22 +279,18 @@ Future<void> verifySignatures(
/// Find every binary file in the given [rootDirectory].
Future<List<String>> findBinaryPaths(
String rootDirectory,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()
String rootDirectory, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final List<String> allBinaryPaths = <String>[];
final io.ProcessResult result = await processManager.run(
<String>[
'find',
rootDirectory,
'-type',
'f',
],
);
final List<String> allFiles = (result.stdout as String)
.split('\n')
.where((String s) => s.isNotEmpty)
.toList();
final io.ProcessResult result = await processManager.run(<String>[
'find',
rootDirectory,
'-type',
'f',
]);
final List<String> allFiles =
(result.stdout as String).split('\n').where((String s) => s.isNotEmpty).toList();
await Future.forEach(allFiles, (String filePath) async {
if (await isBinary(filePath, processManager: processManager)) {
@@ -308,22 +303,19 @@ Future<List<String>> findBinaryPaths(
/// Find every xcframework in the given [rootDirectory].
Future<List<String>> findXcframeworksPaths(
String rootDirectory,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()
}) async {
final io.ProcessResult result = await processManager.run(
<String>[
'find',
rootDirectory,
'-type',
'd',
'-name',
'*xcframework',
],
);
final List<String> allXcframeworkPaths = LineSplitter.split(result.stdout as String)
.where((String s) => s.isNotEmpty)
.toList();
String rootDirectory, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final io.ProcessResult result = await processManager.run(<String>[
'find',
rootDirectory,
'-type',
'd',
'-name',
'*xcframework',
]);
final List<String> allXcframeworkPaths =
LineSplitter.split(result.stdout as String).where((String s) => s.isNotEmpty).toList();
for (final String path in allXcframeworkPaths) {
print('Found: $path\n');
}
@@ -332,35 +324,31 @@ Future<List<String>> findXcframeworksPaths(
/// Check mime-type of file at [filePath] to determine if it is binary.
Future<bool> isBinary(
String filePath,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
) async {
final io.ProcessResult result = await processManager.run(
<String>[
'file',
'--mime-type',
'-b', // is binary
filePath,
],
);
String filePath, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final io.ProcessResult result = await processManager.run(<String>[
'file',
'--mime-type',
'-b', // is binary
filePath,
]);
return (result.stdout as String).contains('application/x-mach-binary');
}
/// Check if the binary has the expected entitlements.
Future<bool> hasExpectedEntitlements(
String binaryPath,
String flutterRoot,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
) async {
final io.ProcessResult entitlementResult = await processManager.run(
<String>[
'codesign',
'--display',
'--entitlements',
':-',
binaryPath,
],
);
String flutterRoot, {
@visibleForTesting ProcessManager processManager = const LocalProcessManager(),
}) async {
final io.ProcessResult entitlementResult = await processManager.run(<String>[
'codesign',
'--display',
'--entitlements',
':-',
binaryPath,
]);
if (entitlementResult.exitCode != 0) {
print(
@@ -373,8 +361,7 @@ Future<bool> hasExpectedEntitlements(
bool passes = true;
final String output = entitlementResult.stdout as String;
for (final String entitlement in expectedEntitlements) {
final bool entitlementExpected =
binariesWithEntitlements(flutterRoot).contains(binaryPath);
final bool entitlementExpected = binariesWithEntitlements(flutterRoot).contains(binaryPath);
if (output.contains(entitlement) != entitlementExpected) {
print(
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '

View File

@@ -3,7 +3,19 @@
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io' show Directory, File, FileSystemEntity, HttpClient, HttpClientRequest, HttpClientResponse, Platform, Process, RawSocket, SocketDirection, SocketException;
import 'dart:io'
show
Directory,
File,
FileSystemEntity,
HttpClient,
HttpClientRequest,
HttpClientResponse,
Platform,
Process,
RawSocket,
SocketDirection,
SocketException;
import 'dart:math' as math;
import 'package:file/local.dart';
import 'package:path/path.dart' as path;
@@ -16,7 +28,6 @@ import '../utils.dart';
typedef ShardRunner = Future<void> Function();
class WebTestsSuite {
WebTestsSuite(this.flutterTestArgs);
/// Tests that we don't run on Web.
@@ -59,15 +70,15 @@ class WebTestsSuite {
'test/rendering/platform_view_test.dart',
],
'skwasm': <String>[
// These tests are not compilable on the web due to dependencies on
// VM-specific functionality.
'test/services/message_codecs_vm_test.dart',
'test/examples/sector_layout_test.dart',
// These tests are not compilable on the web due to dependencies on
// VM-specific functionality.
'test/services/message_codecs_vm_test.dart',
'test/examples/sector_layout_test.dart',
// These tests are broken and need to be fixed.
// TODO(jacksongardner): https://github.com/flutter/flutter/issues/71604
'test/material/text_field_test.dart',
'test/widgets/performance_overlay_test.dart',
// These tests are broken and need to be fixed.
// TODO(jacksongardner): https://github.com/flutter/flutter/issues/71604
'test/material/text_field_test.dart',
'test/widgets/performance_overlay_test.dart',
],
};
@@ -80,10 +91,10 @@ class WebTestsSuite {
/// and make sure it runs _all_ shards.
///
/// The last shard also runs the Web plugin tests.
int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT')
? int.parse(Platform.environment['WEB_SHARD_COUNT']!)
: 8;
int get webShardCount =>
Platform.environment.containsKey('WEB_SHARD_COUNT')
? int.parse(Platform.environment['WEB_SHARD_COUNT']!)
: 8;
static const List<String> _kAllBuildModes = <String>['debug', 'profile', 'release'];
@@ -91,7 +102,6 @@ class WebTestsSuite {
/// Coarse-grained integration tests running on the Web.
Future<void> webLongRunningTestsRunner() async {
final String engineVersionFile = path.join(flutterRoot, 'bin', 'internal', 'engine.version');
final String engineRealmFile = path.join(flutterRoot, 'bin', 'internal', 'engine.realm');
final String engineVersion = File(engineVersionFile).readAsStringSync().trim();
@@ -144,8 +154,13 @@ class WebTestsSuite {
// This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix.
() => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'debug', renderer: 'html'),
() => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'profile', renderer: 'canvaskit'),
() => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'release', renderer: 'html'),
() => _runWebE2eTest(
'profile_diagnostics_integration',
buildMode: 'profile',
renderer: 'canvaskit',
),
() =>
_runWebE2eTest('profile_diagnostics_integration', buildMode: 'release', renderer: 'html'),
// This test is only known to work in debug mode.
() => _runWebE2eTest('scroll_wheel_integration', buildMode: 'debug', renderer: 'html'),
@@ -162,14 +177,30 @@ class WebTestsSuite {
() => _runWebE2eTest('url_strategy_integration', buildMode: 'release', renderer: 'html'),
// This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix.
() => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'debug', renderer: 'auto'),
() => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'profile', renderer: 'canvaskit'),
() => _runWebE2eTest(
'capabilities_integration_canvaskit',
buildMode: 'debug',
renderer: 'auto',
),
() => _runWebE2eTest(
'capabilities_integration_canvaskit',
buildMode: 'profile',
renderer: 'canvaskit',
),
() => _runWebE2eTest('capabilities_integration_html', buildMode: 'release', renderer: 'html'),
// This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix.
// CacheWidth and CacheHeight are only currently supported in CanvasKit mode, so we don't run the test in HTML mode.
() => _runWebE2eTest('cache_width_cache_height_integration', buildMode: 'debug', renderer: 'auto'),
() => _runWebE2eTest('cache_width_cache_height_integration', buildMode: 'profile', renderer: 'canvaskit'),
() => _runWebE2eTest(
'cache_width_cache_height_integration',
buildMode: 'debug',
renderer: 'auto',
),
() => _runWebE2eTest(
'cache_width_cache_height_integration',
buildMode: 'profile',
renderer: 'canvaskit',
),
() => _runWebTreeshakeTest(),
@@ -185,17 +216,45 @@ class WebTestsSuite {
() => _runGalleryE2eWebTest('profile', canvasKit: true),
() => _runGalleryE2eWebTest('release'),
() => _runGalleryE2eWebTest('release', canvasKit: true),
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
() =>
runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn),
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsNonceOn),
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn),
() => runWebServiceWorkerTest(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsShort,
),
() => runWebServiceWorkerTest(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent,
),
() => runWebServiceWorkerTest(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn,
),
() => runWebServiceWorkerTest(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsNonceOn,
),
() => runWebServiceWorkerTestWithCachingResources(
headless: true,
testType: ServiceWorkerTestType.withoutFlutterJs,
),
() => runWebServiceWorkerTestWithCachingResources(
headless: true,
testType: ServiceWorkerTestType.withFlutterJs,
),
() => runWebServiceWorkerTestWithCachingResources(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsShort,
),
() => runWebServiceWorkerTestWithCachingResources(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent,
),
() => runWebServiceWorkerTestWithCachingResources(
headless: true,
testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn,
),
() => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true),
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
() => runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: true),
@@ -206,23 +265,25 @@ class WebTestsSuite {
() => _runWebDebugTest('lib/stack_trace.dart'),
() => _runWebDebugTest('lib/framework_stack_trace.dart'),
() => _runWebDebugTest('lib/web_directory_loading.dart'),
() => _runWebDebugTest('lib/web_resources_cdn_test.dart',
additionalArguments: <String>[
'--dart-define=TEST_FLUTTER_ENGINE_VERSION=$engineVersion',
]),
() => _runWebDebugTest(
'lib/web_resources_cdn_test.dart',
additionalArguments: <String>['--dart-define=TEST_FLUTTER_ENGINE_VERSION=$engineVersion'],
),
() => _runWebDebugTest('test/test.dart'),
() => _runWebDebugTest('lib/null_safe_main.dart'),
() => _runWebDebugTest('lib/web_define_loading.dart',
() => _runWebDebugTest(
'lib/web_define_loading.dart',
additionalArguments: <String>[
'--dart-define=test.valueA=Example,A',
'--dart-define=test.valueB=Value',
]
],
),
() => _runWebReleaseTest('lib/web_define_loading.dart',
() => _runWebReleaseTest(
'lib/web_define_loading.dart',
additionalArguments: <String>[
'--dart-define=test.valueA=Example,A',
'--dart-define=test.valueB=Value',
]
],
),
() => _runWebDebugTest('lib/assertion_test.dart'),
() => _runWebReleaseTest('lib/assertion_test.dart'),
@@ -295,15 +356,11 @@ class WebTestsSuite {
String expectResponseFileContent = '',
}) async {
printProgress('${green}Running integration tests $target in $buildMode mode.$reset');
await runCommand(
flutter,
<String>[ 'clean' ],
workingDirectory: testAppDirectory,
);
await runCommand(flutter, <String>['clean'], workingDirectory: testAppDirectory);
// This must match the testOutputsDirectory defined in flutter_driver's driver/common.dart.
final String driverOutputPath = Platform.environment['FLUTTER_TEST_OUTPUTS_DIR'] ?? path.join(testAppDirectory, 'build');
final String responseFile =
path.join(driverOutputPath, 'integration_response_data.json');
final String driverOutputPath =
Platform.environment['FLUTTER_TEST_OUTPUTS_DIR'] ?? path.join(testAppDirectory, 'build');
final String responseFile = path.join(driverOutputPath, 'integration_response_data.json');
if (File(responseFile).existsSync()) {
File(responseFile).deleteSync();
}
@@ -335,13 +392,10 @@ class WebTestsSuite {
'--dart-define=FLUTTER_WEB_USE_SKIA=false',
'--dart-define=FLUTTER_WEB_USE_SKWASM=false',
],
],
expectNonZeroExit: expectFailure,
workingDirectory: testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
removeLine: (String line) {
if (!silenceBrowserOutput) {
return false;
@@ -374,25 +428,19 @@ class WebTestsSuite {
// The app is compiled in `--profile` mode to prevent the compiler from
// minifying the symbols.
Future<void> _runWebTreeshakeTest() async {
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web_e2e_tests');
final String target = path.join('lib', 'treeshaking_main.dart');
await runCommand(
flutter,
<String>[ 'clean' ],
workingDirectory: testAppDirectory,
final String testAppDirectory = path.join(
flutterRoot,
'dev',
'integration_tests',
'web_e2e_tests',
);
final String target = path.join('lib', 'treeshaking_main.dart');
await runCommand(flutter, <String>['clean'], workingDirectory: testAppDirectory);
await runCommand(
flutter,
<String>[
'build',
'web',
'--target=$target',
'--profile',
],
<String>['build', 'web', '--target=$target', '--profile'],
workingDirectory: testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
final File mainDartJs = File(path.join(testAppDirectory, 'build', 'web', 'main.dart.js'));
@@ -426,7 +474,7 @@ class WebTestsSuite {
if (count > kMaxExpectedDebugFillProperties) {
throw Exception(
'Too many occurrences of "$word" in compiled JavaScript.\n'
'Expected no more than $kMaxExpectedDebugFillProperties, but found $count.'
'Expected no more than $kMaxExpectedDebugFillProperties, but found $count.',
);
}
}
@@ -440,30 +488,32 @@ class WebTestsSuite {
///
/// The test is written using `package:integration_test` (despite the "e2e" in
/// the name, which is there for historic reasons).
Future<void> _runGalleryE2eWebTest(String buildMode, { bool canvasKit = false }) async {
Future<void> _runGalleryE2eWebTest(String buildMode, {bool canvasKit = false}) async {
// TODO(yjbanov): this is temporarily disabled due to https://github.com/flutter/flutter/issues/147731
if (buildMode == 'debug' && canvasKit) {
print('SKIPPED: Gallery e2e web test in debug CanvasKit mode due to https://github.com/flutter/flutter/issues/147731');
print(
'SKIPPED: Gallery e2e web test in debug CanvasKit mode due to https://github.com/flutter/flutter/issues/147731',
);
return;
}
printProgress('${green}Running flutter_gallery integration test in --$buildMode using ${canvasKit ? 'CanvasKit' : 'HTML'} renderer.$reset');
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery');
await runCommand(
flutter,
<String>[ 'clean' ],
workingDirectory: testAppDirectory,
printProgress(
'${green}Running flutter_gallery integration test in --$buildMode using ${canvasKit ? 'CanvasKit' : 'HTML'} renderer.$reset',
);
final String testAppDirectory = path.join(
flutterRoot,
'dev',
'integration_tests',
'flutter_gallery',
);
await runCommand(flutter, <String>['clean'], workingDirectory: testAppDirectory);
await runCommand(
flutter,
<String>[
...flutterTestArgs,
'drive',
if (canvasKit)
'--dart-define=FLUTTER_WEB_USE_SKIA=true',
if (!canvasKit)
'--dart-define=FLUTTER_WEB_USE_SKIA=false',
if (!canvasKit)
'--dart-define=FLUTTER_WEB_AUTO_DETECT=false',
if (canvasKit) '--dart-define=FLUTTER_WEB_USE_SKIA=true',
if (!canvasKit) '--dart-define=FLUTTER_WEB_USE_SKIA=false',
if (!canvasKit) '--dart-define=FLUTTER_WEB_AUTO_DETECT=false',
'--driver=test_driver/transitions_perf_e2e_test.dart',
'--target=test_driver/transitions_perf_e2e.dart',
'--browser-name=chrome',
@@ -472,9 +522,7 @@ class WebTestsSuite {
'--$buildMode',
],
workingDirectory: testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
}
@@ -483,24 +531,12 @@ class WebTestsSuite {
final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web');
// Build the app.
await runCommand(flutter, <String>['clean'], workingDirectory: testAppDirectory);
await runCommand(
flutter,
<String>[ 'clean' ],
<String>['build', 'web', '--$buildMode', '-t', entrypoint],
workingDirectory: testAppDirectory,
);
await runCommand(
flutter,
<String>[
'build',
'web',
'--$buildMode',
'-t',
entrypoint,
],
workingDirectory: testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
// Run the app.
@@ -514,24 +550,20 @@ class WebTestsSuite {
);
if (!result.contains('--- TEST SUCCEEDED ---')) {
foundError(<String>[
result,
'${red}Web stack trace integration test failed.$reset',
]);
foundError(<String>[result, '${red}Web stack trace integration test failed.$reset']);
}
}
/// Debug mode is special because `flutter build web` doesn't build in debug mode.
///
/// Instead, we use `flutter run --debug` and sniff out the standard output.
Future<void> _runWebDebugTest(String target, {
List<String> additionalArguments = const<String>[],
Future<void> _runWebDebugTest(
String target, {
List<String> additionalArguments = const <String>[],
}) async {
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
bool success = false;
final Map<String, String> environment = <String, String>{
'FLUTTER_WEB': 'true',
};
final Map<String, String> environment = <String, String>{'FLUTTER_WEB': 'true'};
adjustEnvironmentToEnableFlutterAsserts(environment);
final CommandResult result = await runCommand(
flutter,
@@ -576,18 +608,15 @@ class WebTestsSuite {
}
/// Run a web integration test in release mode.
Future<void> _runWebReleaseTest(String target, {
List<String> additionalArguments = const<String>[],
Future<void> _runWebReleaseTest(
String target, {
List<String> additionalArguments = const <String>[],
}) async {
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web');
// Build the app.
await runCommand(
flutter,
<String>[ 'clean' ],
workingDirectory: testAppDirectory,
);
await runCommand(flutter, <String>['clean'], workingDirectory: testAppDirectory);
await runCommand(
flutter,
<String>[
@@ -600,9 +629,7 @@ class WebTestsSuite {
target,
],
workingDirectory: testAppDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
// Run the app.
@@ -616,35 +643,43 @@ class WebTestsSuite {
);
if (!result.contains('--- TEST SUCCEEDED ---')) {
foundError(<String>[
result,
'${red}Web release mode test failed.$reset',
]);
foundError(<String>[result, '${red}Web release mode test failed.$reset']);
}
}
Future<void> _runWebUnitTests(String webRenderer, bool useWasm) async {
final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
final Directory flutterPackageTestDirectory = Directory(path.join(flutterPackageDirectory.path, 'test'));
final Directory flutterPackageDirectory = Directory(
path.join(flutterRoot, 'packages', 'flutter'),
);
final Directory flutterPackageTestDirectory = Directory(
path.join(flutterPackageDirectory.path, 'test'),
);
final List<String> allTests = flutterPackageTestDirectory
.listSync()
.whereType<Directory>()
.expand((Directory directory) => directory
.listSync(recursive: true)
.where((FileSystemEntity entity) => entity.path.endsWith('_test.dart'))
)
.whereType<File>()
.map<String>((File file) => path.relative(file.path, from: flutterPackageDirectory.path))
.where((String filePath) => !kWebTestFileKnownFailures[webRenderer]!.contains(path.split(filePath).join('/')))
.toList()
// Finally we shuffle the list because we want the average cost per file to be uniformly
// distributed. If the list is not sorted then different shards and batches may have
// very different characteristics.
// We use a constant seed for repeatability.
..shuffle(math.Random(0));
final List<String> allTests =
flutterPackageTestDirectory
.listSync()
.whereType<Directory>()
.expand(
(Directory directory) => directory
.listSync(recursive: true)
.where((FileSystemEntity entity) => entity.path.endsWith('_test.dart')),
)
.whereType<File>()
.map<String>(
(File file) => path.relative(file.path, from: flutterPackageDirectory.path),
)
.where(
(String filePath) =>
!kWebTestFileKnownFailures[webRenderer]!.contains(path.split(filePath).join('/')),
)
.toList()
// Finally we shuffle the list because we want the average cost per file to be uniformly
// distributed. If the list is not sorted then different shards and batches may have
// very different characteristics.
// We use a constant seed for repeatability.
..shuffle(math.Random(0));
assert(webShardCount >= 1);
final int testsPerShard = (allTests.length / webShardCount).ceil();
@@ -652,15 +687,13 @@ class WebTestsSuite {
// This for loop computes all but the last shard.
for (int index = 0; index < webShardCount - 1; index += 1) {
subshards['$index'] = () => _runFlutterWebTest(
webRenderer,
flutterPackageDirectory.path,
allTests.sublist(
index * testsPerShard,
(index + 1) * testsPerShard,
),
useWasm,
);
subshards['$index'] =
() => _runFlutterWebTest(
webRenderer,
flutterPackageDirectory.path,
allTests.sublist(index * testsPerShard, (index + 1) * testsPerShard),
useWasm,
);
}
// The last shard also runs the flutter_web_plugins tests.
@@ -671,10 +704,7 @@ class WebTestsSuite {
await _runFlutterWebTest(
webRenderer,
flutterPackageDirectory.path,
allTests.sublist(
(webShardCount - 1) * testsPerShard,
allTests.length,
),
allTests.sublist((webShardCount - 1) * testsPerShard, allTests.length),
useWasm,
);
await _runFlutterWebTest(
@@ -734,9 +764,7 @@ class WebTestsSuite {
...tests,
],
workingDirectory: workingDirectory,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
// metriciFile is a transitional file that needs to be deleted once it is parsed.
// TODO(godofredoc): Ensure metricFile is parsed and aggregated before deleting.
@@ -794,7 +822,8 @@ class WebTestsSuite {
final HttpClientRequest request = await client.getUrl(chromeDriverUrl);
final HttpClientResponse response = await request.close();
final String responseString = await response.transform(utf8.decoder).join();
final Map<String, dynamic> webDriverStatus = json.decode(responseString) as Map<String, dynamic>;
final Map<String, dynamic> webDriverStatus =
json.decode(responseString) as Map<String, dynamic>;
client.close();
final bool webDriverReady = (webDriverStatus['value'] as Map<String, dynamic>)['ready'] as bool;
if (!webDriverReady) {

View File

@@ -75,7 +75,7 @@ typedef ShardRunner = Future<void> Function();
/// Environment variables to override the local engine when running `pub test`,
/// if such flags are provided to `test.dart`.
final Map<String,String> localEngineEnv = <String, String>{};
final Map<String, String> localEngineEnv = <String, String>{};
const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME';
@@ -101,7 +101,9 @@ Future<void> main(List<String> args) async {
localEngineEnv['FLUTTER_LOCAL_ENGINE_HOST'] = arg.substring('--local-engine-host='.length);
flutterTestArgs.add(arg);
} else if (arg.startsWith('--local-engine-src-path=')) {
localEngineEnv['FLUTTER_LOCAL_ENGINE_SRC_PATH'] = arg.substring('--local-engine-src-path='.length);
localEngineEnv['FLUTTER_LOCAL_ENGINE_SRC_PATH'] = arg.substring(
'--local-engine-src-path='.length,
);
flutterTestArgs.add(arg);
} else if (arg.startsWith('--test-randomize-ordering-seed=')) {
shuffleSeed = arg.substring('--test-randomize-ordering-seed='.length);
@@ -155,7 +157,8 @@ Future<void> main(List<String> args) async {
'snippets': _runSnippetsTests,
'docs': docsRunner,
'verify_binaries_codesigned': verifyCodesignedTestRunner,
kTestHarnessShardName: testHarnessTestsRunner, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc.
kTestHarnessShardName:
testHarnessTestsRunner, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc.
});
} catch (error, stackTrace) {
foundError(<String>[
@@ -197,8 +200,10 @@ Future<void> _runCommandsToolTests() async {
}
Future<void> _runWebToolTests() async {
final List<File> allFiles = Directory(path.join(_toolsPath, 'test', 'web.shard'))
.listSync(recursive: true).whereType<File>().toList();
final List<File> allFiles =
Directory(
path.join(_toolsPath, 'test', 'web.shard'),
).listSync(recursive: true).whereType<File>().toList();
final List<String> allTests = <String>[];
for (final File file in allFiles) {
if (file.path.endsWith('_test.dart')) {
@@ -223,10 +228,13 @@ Future<void> _runToolHostCrossArchTests() {
}
Future<void> _runIntegrationToolTests() async {
final List<String> allTests = Directory(path.join(_toolsPath, 'test', 'integration.shard'))
.listSync(recursive: true).whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList();
final List<String> allTests =
Directory(path.join(_toolsPath, 'test', 'integration.shard'))
.listSync(recursive: true)
.whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart'))
.toList();
await runDartTest(
_toolsPath,
@@ -245,10 +253,13 @@ Future<void> _runToolTests() async {
Future<void> _runSnippetsTests() async {
final String snippetsPath = path.join(flutterRoot, 'dev', 'snippets');
final List<String> allTests = Directory(path.join(snippetsPath, 'test'))
.listSync(recursive: true).whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList();
final List<String> allTests =
Directory(path.join(snippetsPath, 'test'))
.listSync(recursive: true)
.whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath))
.where((String testPath) => path.basename(testPath).endsWith('_test.dart'))
.toList();
await runDartTest(
snippetsPath,
@@ -282,31 +293,30 @@ Future<void> runForbiddenFromReleaseTests() async {
// First, a smoke test.
final List<String> smokeTestArgs = <String>[
path.join(flutterRoot, 'dev', 'forbidden_from_release_tests', 'bin', 'main.dart'),
'--snapshot', path.join(tempDirectory.path, 'snapshot.arm64-v8a.json'),
'--package-config', path.join(flutterRoot, 'examples', 'hello_world', '.dart_tool', 'package_config.json'),
'--forbidden-type', 'package:flutter/src/widgets/framework.dart::Widget',
'--snapshot',
path.join(tempDirectory.path, 'snapshot.arm64-v8a.json'),
'--package-config',
path.join(flutterRoot, 'examples', 'hello_world', '.dart_tool', 'package_config.json'),
'--forbidden-type',
'package:flutter/src/widgets/framework.dart::Widget',
];
await runCommand(
dart,
smokeTestArgs,
workingDirectory: flutterRoot,
expectNonZeroExit: true,
);
await runCommand(dart, smokeTestArgs, workingDirectory: flutterRoot, expectNonZeroExit: true);
// Actual test.
final List<String> args = <String>[
path.join(flutterRoot, 'dev', 'forbidden_from_release_tests', 'bin', 'main.dart'),
'--snapshot', path.join(tempDirectory.path, 'snapshot.arm64-v8a.json'),
'--package-config', path.join(flutterRoot, 'examples', 'hello_world', '.dart_tool', 'package_config.json'),
'--forbidden-type', 'package:flutter/src/widgets/widget_inspector.dart::WidgetInspectorService',
'--forbidden-type', 'package:flutter/src/widgets/framework.dart::DebugCreator',
'--forbidden-type', 'package:flutter/src/foundation/print.dart::debugPrint',
'--snapshot',
path.join(tempDirectory.path, 'snapshot.arm64-v8a.json'),
'--package-config',
path.join(flutterRoot, 'examples', 'hello_world', '.dart_tool', 'package_config.json'),
'--forbidden-type',
'package:flutter/src/widgets/widget_inspector.dart::WidgetInspectorService',
'--forbidden-type',
'package:flutter/src/widgets/framework.dart::DebugCreator',
'--forbidden-type',
'package:flutter/src/foundation/print.dart::debugPrint',
];
await runCommand(
dart,
args,
workingDirectory: flutterRoot,
);
await runCommand(dart, args, workingDirectory: flutterRoot);
}
/// Verifies that APK, and IPA (if on macOS), and native desktop builds the
@@ -317,21 +327,35 @@ Future<void> runForbiddenFromReleaseTests() async {
///
/// Also does some checking about types included in hello_world.
Future<void> _runBuildTests() async {
final List<Directory> exampleDirectories = Directory(path.join(flutterRoot, 'examples')).listSync()
// API example builds will be tested in a separate shard.
.where((FileSystemEntity entity) => entity is Directory && path.basename(entity.path) != 'api').cast<Directory>().toList()
..add(Directory(path.join(flutterRoot, 'packages', 'integration_test', 'example')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'android_views')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'channels')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'hybrid_android_views')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'ios_platform_view_tests')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'ios_app_with_extensions')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'platform_interaction')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'spell_check')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'ui')));
final List<Directory> exampleDirectories =
Directory(path.join(flutterRoot, 'examples'))
.listSync()
// API example builds will be tested in a separate shard.
.where(
(FileSystemEntity entity) => entity is Directory && path.basename(entity.path) != 'api',
)
.cast<Directory>()
.toList()
..add(Directory(path.join(flutterRoot, 'packages', 'integration_test', 'example')))
..add(
Directory(
path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'),
),
)
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'android_views')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'channels')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'hybrid_android_views')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery')))
..add(
Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'ios_platform_view_tests')),
)
..add(
Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'ios_app_with_extensions')),
)
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'platform_interaction')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'spell_check')))
..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'ui')));
// The tests are randomly distributed into subshards so as to get a uniform
// distribution of costs, but the seed is fixed so that issues are reproducible.
@@ -341,20 +365,20 @@ Future<void> _runBuildTests() async {
...<ShardRunner>[
// Web compilation tests.
() => _flutterBuildDart2js(
path.join('dev', 'integration_tests', 'web'),
path.join('lib', 'main.dart'),
),
path.join('dev', 'integration_tests', 'web'),
path.join('lib', 'main.dart'),
),
// Should not fail to compile with dart:io.
() => _flutterBuildDart2js(
path.join('dev', 'integration_tests', 'web_compile_tests'),
path.join('lib', 'dart_io_import.dart'),
),
path.join('dev', 'integration_tests', 'web_compile_tests'),
path.join('lib', 'dart_io_import.dart'),
),
// Should be able to compile with a call to:
// BackgroundIsolateBinaryMessenger.ensureInitialized.
() => _flutterBuildDart2js(
path.join('dev', 'integration_tests', 'web_compile_tests'),
path.join('lib', 'background_isolate_binary_messenger.dart'),
),
path.join('dev', 'integration_tests', 'web_compile_tests'),
path.join('lib', 'background_isolate_binary_messenger.dart'),
),
],
runForbiddenFromReleaseTests,
]..shuffle(math.Random(0));
@@ -370,113 +394,195 @@ Future<void> _runExampleProjectBuildTests(Directory exampleDirectory, [File? mai
if (mainFile != null) path.relative(mainFile.path, from: exampleDirectory.absolute.path),
];
if (Directory(path.join(examplePath, 'android')).existsSync()) {
await _flutterBuildApk(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildApk(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildApk(
examplePath,
release: false,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
await _flutterBuildApk(
examplePath,
release: true,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
} else {
print('Example project ${path.basename(examplePath)} has no android directory, skipping apk');
}
if (Platform.isMacOS) {
if (Directory(path.join(examplePath, 'ios')).existsSync()) {
await _flutterBuildIpa(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildIpa(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildIpa(
examplePath,
release: false,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
await _flutterBuildIpa(
examplePath,
release: true,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
} else {
print('Example project ${path.basename(examplePath)} has no ios directory, skipping ipa');
}
}
if (Platform.isLinux) {
if (Directory(path.join(examplePath, 'linux')).existsSync()) {
await _flutterBuildLinux(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildLinux(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildLinux(
examplePath,
release: false,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
await _flutterBuildLinux(
examplePath,
release: true,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
} else {
print('Example project ${path.basename(examplePath)} has no linux directory, skipping Linux');
}
}
if (Platform.isMacOS) {
if (Directory(path.join(examplePath, 'macos')).existsSync()) {
await _flutterBuildMacOS(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildMacOS(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildMacOS(
examplePath,
release: false,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
await _flutterBuildMacOS(
examplePath,
release: true,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
} else {
print('Example project ${path.basename(examplePath)} has no macos directory, skipping macOS');
}
}
if (Platform.isWindows) {
if (Directory(path.join(examplePath, 'windows')).existsSync()) {
await _flutterBuildWin32(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildWin32(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
await _flutterBuildWin32(
examplePath,
release: false,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
await _flutterBuildWin32(
examplePath,
release: true,
additionalArgs: additionalArgs,
verifyCaching: verifyCaching,
);
} else {
print('Example project ${path.basename(examplePath)} has no windows directory, skipping Win32');
print(
'Example project ${path.basename(examplePath)} has no windows directory, skipping Win32',
);
}
}
}
Future<void> _flutterBuildApk(String relativePathToApplication, {
Future<void> _flutterBuildApk(
String relativePathToApplication, {
required bool release,
bool verifyCaching = false,
List<String> additionalArgs = const <String>[],
}) async {
printProgress('${green}Testing APK ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...');
await _flutterBuild(relativePathToApplication, 'APK', 'apk',
printProgress(
'${green}Testing APK ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...',
);
await _flutterBuild(
relativePathToApplication,
'APK',
'apk',
release: release,
verifyCaching: verifyCaching,
additionalArgs: additionalArgs
additionalArgs: additionalArgs,
);
}
Future<void> _flutterBuildIpa(String relativePathToApplication, {
Future<void> _flutterBuildIpa(
String relativePathToApplication, {
required bool release,
List<String> additionalArgs = const <String>[],
bool verifyCaching = false,
}) async {
assert(Platform.isMacOS);
printProgress('${green}Testing IPA ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...');
await _flutterBuild(relativePathToApplication, 'IPA', 'ios',
printProgress(
'${green}Testing IPA ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...',
);
await _flutterBuild(
relativePathToApplication,
'IPA',
'ios',
release: release,
verifyCaching: verifyCaching,
additionalArgs: <String>[...additionalArgs, '--no-codesign'],
);
}
Future<void> _flutterBuildLinux(String relativePathToApplication, {
Future<void> _flutterBuildLinux(
String relativePathToApplication, {
required bool release,
bool verifyCaching = false,
List<String> additionalArgs = const <String>[],
}) async {
assert(Platform.isLinux);
await runCommand(flutter, <String>['config', '--enable-linux-desktop']);
printProgress('${green}Testing Linux ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...');
await _flutterBuild(relativePathToApplication, 'Linux', 'linux',
printProgress(
'${green}Testing Linux ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...',
);
await _flutterBuild(
relativePathToApplication,
'Linux',
'linux',
release: release,
verifyCaching: verifyCaching,
additionalArgs: additionalArgs
additionalArgs: additionalArgs,
);
}
Future<void> _flutterBuildMacOS(String relativePathToApplication, {
Future<void> _flutterBuildMacOS(
String relativePathToApplication, {
required bool release,
bool verifyCaching = false,
List<String> additionalArgs = const <String>[],
}) async {
assert(Platform.isMacOS);
await runCommand(flutter, <String>['config', '--enable-macos-desktop']);
printProgress('${green}Testing macOS ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...');
await _flutterBuild(relativePathToApplication, 'macOS', 'macos',
printProgress(
'${green}Testing macOS ${release ? 'release' : 'debug'} build$reset for $cyan$relativePathToApplication$reset...',
);
await _flutterBuild(
relativePathToApplication,
'macOS',
'macos',
release: release,
verifyCaching: verifyCaching,
additionalArgs: additionalArgs
additionalArgs: additionalArgs,
);
}
Future<void> _flutterBuildWin32(String relativePathToApplication, {
Future<void> _flutterBuildWin32(
String relativePathToApplication, {
required bool release,
bool verifyCaching = false,
List<String> additionalArgs = const <String>[],
}) async {
assert(Platform.isWindows);
printProgress('${green}Testing ${release ? 'release' : 'debug'} Windows build$reset for $cyan$relativePathToApplication$reset...');
await _flutterBuild(relativePathToApplication, 'Windows', 'windows',
printProgress(
'${green}Testing ${release ? 'release' : 'debug'} Windows build$reset for $cyan$relativePathToApplication$reset...',
);
await _flutterBuild(
relativePathToApplication,
'Windows',
'windows',
release: release,
verifyCaching: verifyCaching,
additionalArgs: additionalArgs
additionalArgs: additionalArgs,
);
}
@@ -488,36 +594,26 @@ Future<void> _flutterBuild(
bool verifyCaching = false,
List<String> additionalArgs = const <String>[],
}) async {
await runCommand(flutter,
<String>[
'build',
platformBuildName,
...additionalArgs,
if (release)
'--release'
else
'--debug',
'-v',
],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
);
await runCommand(flutter, <String>[
'build',
platformBuildName,
...additionalArgs,
if (release) '--release' else '--debug',
'-v',
], workingDirectory: path.join(flutterRoot, relativePathToApplication));
if (verifyCaching) {
printProgress('${green}Testing $platformLabel cache$reset for $cyan$relativePathToApplication$reset...');
await runCommand(flutter,
<String>[
'build',
platformBuildName,
'--performance-measurement-file=perf.json',
...additionalArgs,
if (release)
'--release'
else
'--debug',
'-v',
],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
printProgress(
'${green}Testing $platformLabel cache$reset for $cyan$relativePathToApplication$reset...',
);
await runCommand(flutter, <String>[
'build',
platformBuildName,
'--performance-measurement-file=perf.json',
...additionalArgs,
if (release) '--release' else '--debug',
'-v',
], workingDirectory: path.join(flutterRoot, relativePathToApplication));
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) {
foundError(<String>[
@@ -532,21 +628,24 @@ bool _allTargetsCached(File performanceFile) {
if (dryRun) {
return true;
}
final Map<String, Object?> data = json.decode(performanceFile.readAsStringSync())
as Map<String, Object?>;
final List<Map<String, Object?>> targets = (data['targets']! as List<Object?>)
.cast<Map<String, Object?>>();
final Map<String, Object?> data =
json.decode(performanceFile.readAsStringSync()) as Map<String, Object?>;
final List<Map<String, Object?>> targets =
(data['targets']! as List<Object?>).cast<Map<String, Object?>>();
return targets.every((Map<String, Object?> element) => element['skipped'] == true);
}
Future<void> _flutterBuildDart2js(String relativePathToApplication, String target, { bool expectNonZeroExit = false }) async {
Future<void> _flutterBuildDart2js(
String relativePathToApplication,
String target, {
bool expectNonZeroExit = false,
}) async {
printProgress('${green}Testing Dart2JS build$reset for $cyan$relativePathToApplication$reset...');
await runCommand(flutter,
await runCommand(
flutter,
<String>['build', 'web', '-v', '--target=$target'],
workingDirectory: path.join(flutterRoot, relativePathToApplication),
expectNonZeroExit: expectNonZeroExit,
environment: <String, String>{
'FLUTTER_WEB': 'true',
},
environment: <String, String>{'FLUTTER_WEB': 'true'},
);
}

View File

@@ -8,9 +8,12 @@ import 'token_logger.dart';
/// Base class for code generation templates.
abstract class TokenTemplate {
const TokenTemplate(this.blockName, this.fileName, this._tokens, {
const TokenTemplate(
this.blockName,
this.fileName,
this._tokens, {
this.colorSchemePrefix = 'Theme.of(context).colorScheme.',
this.textThemePrefix = 'Theme.of(context).textTheme.'
this.textThemePrefix = 'Theme.of(context).textTheme.',
});
/// Name of the code block that this template will generate.
@@ -170,10 +173,10 @@ abstract class TokenTemplate {
String? _numToString(Object? value, [int? digits]) {
return switch (value) {
null => null,
null => null,
double.infinity => 'double.infinity',
num() => digits == null ? value.toString() : value.toStringAsFixed(digits),
_ => getToken(value as String).toString(),
num() => digits == null ? value.toString() : value.toStringAsFixed(digits),
_ => getToken(value as String).toString(),
};
}
@@ -193,8 +196,14 @@ abstract class TokenTemplate {
if (!tokenAvailable(widthToken) && !tokenAvailable(heightToken)) {
throw Exception('Unable to find width, height, or size tokens for $componentToken');
}
final String? width = _numToString(tokenAvailable(widthToken) ? getToken(widthToken)! as num : double.infinity, 0);
final String? height = _numToString(tokenAvailable(heightToken) ? getToken(heightToken)! as num : double.infinity, 0);
final String? width = _numToString(
tokenAvailable(widthToken) ? getToken(widthToken)! as num : double.infinity,
0,
);
final String? height = _numToString(
tokenAvailable(heightToken) ? getToken(heightToken)! as num : double.infinity,
0,
);
return 'const Size($width, $height)';
}
return 'const Size.square(${_numToString(getToken(sizeToken))})';
@@ -206,8 +215,8 @@ abstract class TokenTemplate {
/// - "SHAPE_FAMILY_ROUNDED_CORNERS" which maps to [RoundedRectangleBorder].
/// - "SHAPE_FAMILY_CIRCULAR" which maps to a [StadiumBorder].
String shape(String componentToken, [String prefix = 'const ']) {
final Map<String, dynamic> shape = getToken(getToken('$componentToken.shape') as String) as Map<String, dynamic>;
final Map<String, dynamic> shape =
getToken(getToken('$componentToken.shape') as String) as Map<String, dynamic>;
switch (shape['family']) {
case 'SHAPE_FAMILY_ROUNDED_CORNERS':
final double topLeft = shape['topLeft'] as double;
@@ -222,18 +231,18 @@ abstract class TokenTemplate {
}
if (topLeft == topRight && bottomLeft == bottomRight) {
return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.vertical('
'${topLeft > 0 ? 'top: Radius.circular($topLeft)':''}'
'${topLeft > 0 && bottomLeft > 0 ? ',':''}'
'${bottomLeft > 0 ? 'bottom: Radius.circular($bottomLeft)':''}'
'))';
'${topLeft > 0 ? 'top: Radius.circular($topLeft)' : ''}'
'${topLeft > 0 && bottomLeft > 0 ? ',' : ''}'
'${bottomLeft > 0 ? 'bottom: Radius.circular($bottomLeft)' : ''}'
'))';
}
return '${prefix}RoundedRectangleBorder(borderRadius: '
'BorderRadius.only('
'topLeft: Radius.circular(${shape['topLeft']}), '
'topRight: Radius.circular(${shape['topRight']}), '
'bottomLeft: Radius.circular(${shape['bottomLeft']}), '
'bottomRight: Radius.circular(${shape['bottomRight']})))';
case 'SHAPE_FAMILY_CIRCULAR':
'BorderRadius.only('
'topLeft: Radius.circular(${shape['topLeft']}), '
'topRight: Radius.circular(${shape['topRight']}), '
'bottomLeft: Radius.circular(${shape['bottomLeft']}), '
'bottomRight: Radius.circular(${shape['bottomRight']})))';
case 'SHAPE_FAMILY_CIRCULAR':
return '${prefix}StadiumBorder()';
}
print('Unsupported shape family type: ${shape['family']} for $componentToken');
@@ -242,27 +251,24 @@ abstract class TokenTemplate {
/// Generate a [BorderSide] for the given component.
String border(String componentToken) {
if (!tokenAvailable('$componentToken.color')) {
return 'null';
}
final String borderColor = componentColor(componentToken);
final double width = (
getToken('$componentToken.width', optional: true) ??
getToken('$componentToken.height', optional: true) ??
1.0
) as double;
final double width =
(getToken('$componentToken.width', optional: true) ??
getToken('$componentToken.height', optional: true) ??
1.0)
as double;
return 'BorderSide(color: $borderColor${width != 1.0 ? ", width: $width" : ""})';
}
/// Generate a [TextTheme] text style name for the given component token.
String textStyle(String componentToken) {
return '$textThemePrefix${getToken("$componentToken.text-style")}';
}
String textStyleWithColor(String componentToken) {
if (!tokenAvailable('$componentToken.text-style')) {
return 'null';
}

View File

@@ -13,8 +13,8 @@ class TokenLogger {
void init({
required Map<String, dynamic> allTokens,
required Map<String, List<String>> versionMap
}){
required Map<String, List<String>> versionMap,
}) {
_allTokens = allTokens;
_versionMap = versionMap;
}
@@ -83,7 +83,11 @@ class TokenLogger {
if (_unavailableTokens.isNotEmpty) {
print('');
print('\x1B[31m' 'Some referenced tokens do not exist: ${_unavailableTokens.length}' '\x1B[0m');
print(
'\x1B[31m'
'Some referenced tokens do not exist: ${_unavailableTokens.length}'
'\x1B[0m',
);
for (final String token in _unavailableTokens) {
print(' $token');
}

View File

@@ -4,9 +4,9 @@
void main(List<String> args) {
final String type = switch (args.first) {
'--material' => 'material',
'--material' => 'material',
'--cupertino' => 'cupertino',
_ => '',
_ => '',
};
print('''
// Copyright 2014 The Flutter Authors. All rights reserved.

View File

@@ -34,18 +34,18 @@ void testNoDoubleClamp(int input) {
nonDoubleClamp.clamp(0, 2);
input.clamp(0, 2);
input.clamp(0.0, 2); // bad.
input.clamp(0.0, 2); // bad.
input.toDouble().clamp(0, 2); // bad.
nonDoubleClamp2?.clamp(0, 2);
nullableInt?.clamp(0, 2);
nullableInt?.clamp(0, 2.0); // bad
nullableDouble?.clamp(0, 2); // bad.
nullableInt?.clamp(0, 2.0); // bad
nullableDouble?.clamp(0, 2); // bad.
// ignore: unused_local_variable
final ClassWithAClampMethod Function(double, double)? tearOff1 = nonDoubleClamp2?.clamp;
// ignore: unused_local_variable
final num Function(num, num)? tearOff2 = nullableInt?.clamp; // bad.
final num Function(num, num)? tearOff2 = nullableInt?.clamp; // bad.
// ignore: unused_local_variable
final num Function(num, num)? tearOff3 = nullableDouble?.clamp; // bad.
}

View File

@@ -6,7 +6,7 @@ import '../../foo/fake_render_box.dart';
mixin ARenderBoxMixin on RenderBox {
@override
void computeMaxIntrinsicWidth() { }
void computeMaxIntrinsicWidth() {}
@override
void computeMinIntrinsicWidth() => computeMaxIntrinsicWidth(); // BAD
@@ -41,7 +41,7 @@ class RenderBoxSubclass2 extends RenderBox with ARenderBoxMixin {
@override
void computeMaxIntrinsicWidth() {
super.computeMinIntrinsicHeight(); // OK
super.computeMaxIntrinsicWidth(); // OK
super.computeMaxIntrinsicWidth(); // OK
final void Function() f = super.computeDryBaseline; // OK
f();
}

View File

@@ -8,49 +8,63 @@ typedef ExternalStopwatchConstructor = externallib.MyStopwatch Function();
class StopwatchAtHome extends Stopwatch {
StopwatchAtHome();
StopwatchAtHome.create(): this();
StopwatchAtHome.create() : this();
Stopwatch get stopwatch => this;
}
void testNoStopwatches(Stopwatch stopwatch) {
stopwatch.runtimeType; // OK for now, but we probably want to catch public APIs that take a Stopwatch?
final Stopwatch localVariable = Stopwatch(); // Bad: introducing Stopwatch from dart:core.
Stopwatch().runtimeType; // Bad: introducing Stopwatch from dart:core.
// OK for now, but we probably want to catch public APIs that take a Stopwatch?
stopwatch.runtimeType;
// Bad: introducing Stopwatch from dart:core.
final Stopwatch localVariable = Stopwatch();
// Bad: introducing Stopwatch from dart:core.
Stopwatch().runtimeType;
(localVariable..runtimeType) // OK: not directly introducing Stopwatch.
.runtimeType;
(localVariable..runtimeType) // OK: not directly introducing Stopwatch.
.runtimeType;
StopwatchAtHome().runtimeType; // Bad: introducing a Stopwatch subclass.
// Bad: introducing a Stopwatch subclass.
StopwatchAtHome().runtimeType;
Stopwatch anotherStopwatch = stopwatch; // OK: not directly introducing Stopwatch.
StopwatchAtHome Function() constructor = StopwatchAtHome.new; // Bad: introducing a Stopwatch constructor.
// OK: not directly introducing Stopwatch.
Stopwatch anotherStopwatch = stopwatch;
// Bad: introducing a Stopwatch constructor.
StopwatchAtHome Function() constructor = StopwatchAtHome.new;
assert(() {
anotherStopwatch = constructor()..runtimeType;
constructor = StopwatchAtHome.create; // Bad: introducing a Stopwatch constructor.
// Bad: introducing a Stopwatch constructor.
constructor = StopwatchAtHome.create;
anotherStopwatch = constructor()..runtimeType;
return true;
}());
anotherStopwatch.runtimeType;
externallib.MyStopwatch.create(); // Bad: introducing an external Stopwatch constructor.
// Bad: introducing an external Stopwatch constructor.
externallib.MyStopwatch.create();
ExternalStopwatchConstructor? externalConstructor;
assert(() {
externalConstructor = externallib.MyStopwatch.new; // Bad: introducing an external Stopwatch constructor.
// Bad: introducing an external Stopwatch constructor.
externalConstructor = externallib.MyStopwatch.new;
return true;
}());
externalConstructor?.call();
externallib.stopwatch.runtimeType; // Bad: introducing an external Stopwatch.
externallib.createMyStopwatch().runtimeType; // Bad: calling an external function that returns a Stopwatch.
externallib.createStopwatch().runtimeType; // Bad: calling an external function that returns a Stopwatch.
externalConstructor = externallib.createMyStopwatch; // Bad: introducing the tear-off form of an external function that returns a Stopwatch.
// Bad: introducing an external Stopwatch.
externallib.stopwatch.runtimeType;
// Bad: calling an external function that returns a Stopwatch.
externallib.createMyStopwatch().runtimeType;
// Bad: calling an external function that returns a Stopwatch.
externallib.createStopwatch().runtimeType;
// Bad: introducing the tear-off form of an external function that returns a Stopwatch.
externalConstructor = externallib.createMyStopwatch;
constructor.call().stopwatch; // OK: existing instance.
// OK: existing instance.
constructor.call().stopwatch;
}
void testStopwatchIgnore(Stopwatch stopwatch) {
Stopwatch().runtimeType; // flutter_ignore: stopwatch (see analyze.dart)
Stopwatch().runtimeType; // flutter_ignore: some_other_ignores, stopwatch (see analyze.dart)
Stopwatch().runtimeType; // flutter_ignore: stopwatch (see analyze.dart)
Stopwatch().runtimeType; // flutter_ignore: some_other_ignores, stopwatch (see analyze.dart)
}

View File

@@ -4,10 +4,12 @@
@Deprecated(
'This is the reason and what you should use instead. '
'This feature was deprecated after v1.2.3.'
'This feature was deprecated after v1.2.3.',
)
void test1() { }
void test1() {}
// The code below is intentionally miss-formatted for testing.
// dart format off
@Deprecated(
'Missing space ->.' //ignore: missing_whitespace_between_adjacent_strings
'This feature was deprecated after v1.2.3.'
@@ -105,3 +107,14 @@ void test18() { }
'Missing the version line. '
)
void test19() { }
// dart format on
// flutter_ignore: deprecation_syntax, https://github.com/flutter/flutter/issues/000000
@Deprecated('Missing the version line. ')
void test20() {}
class TestClass1 {
// flutter_ignore: deprecation_syntax, https://github.com/flutter/flutter/issues/000000
@Deprecated('Missing the version line. ')
void test() {}
}

View File

@@ -1,37 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: type=lint
bool isThereMeaningOfLife = true;
void main() {
if (isThereMeaningOfLife) {}
if(isThereMeaningOfLife) {}
//^
switch (isThereMeaningOfLife) {
case false:
case true:
}
switch(isThereMeaningOfLife) {
// ^
case false:
case true:
}
for (int index = 0; index < 10; index++) {}
for(int index = 0; index < 10; index++) {}
// ^
while (isThereMeaningOfLife) {}
while(isThereMeaningOfLife) {}
// ^
try {
} catch (e) {}
try {
} catch(e) {}
// ^
}

View File

@@ -7,7 +7,7 @@
class MyStopwatch implements Stopwatch {
MyStopwatch();
MyStopwatch.create(): this();
MyStopwatch.create() : this();
@override
Duration get elapsed => throw UnimplementedError();
@@ -28,13 +28,13 @@ class MyStopwatch implements Stopwatch {
bool get isRunning => throw UnimplementedError();
@override
void reset() { }
void reset() {}
@override
void start() { }
void start() {}
@override
void stop() { }
void stop() {}
}
final MyStopwatch stopwatch = MyStopwatch.create();

View File

@@ -38,8 +38,7 @@ const List<String> expectedUiErrors = <String>[
'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:16:20: (top-level declaration) (unused_field)',
];
final RegExp errorPrefixRE =
RegExp(r'^([-a-z0-9/_.:]+): .*(\([-a-z_ ]+\) \([-a-z_ ]+\))$');
final RegExp errorPrefixRE = RegExp(r'^([-a-z0-9/_.:]+): .*(\([-a-z_ ]+\) \([-a-z_ ]+\))$');
String removeLintDescriptions(String error) {
final RegExpMatch? match = errorPrefixRE.firstMatch(error);
if (match != null) {
@@ -56,54 +55,62 @@ void main() {
return;
}
test('analyze_snippet_code smoke test', () {
final ProcessResult process = Process.runSync(
'../../bin/cache/dart-sdk/bin/dart',
<String>[
test(
'analyze_snippet_code smoke test',
() {
final ProcessResult process = Process.runSync('../../bin/cache/dart-sdk/bin/dart', <String>[
'--enable-asserts',
'analyze_snippet_code.dart',
'--no-include-dart-ui',
'test/analyze-snippet-code-test-input',
],
);
expect(process.stdout, isEmpty);
final List<String> stderrLines = process.stderr.toString().split('\n');
expect(stderrLines.length, stderrLines.toSet().length,
reason: 'found duplicates in $stderrLines');
final List<String> stderrNoDescriptions =
stderrLines.map(removeLintDescriptions).toList();
expect(stderrNoDescriptions, <String>[
...expectedMainErrors,
'Found 18 snippet code errors.',
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
'', // because we end with a newline, split gives us an extra blank line
]);
expect(process.exitCode, 1);
}, skip: true); // TODO(scheglov): Restore after landing Dart SDK changes, https://github.com/flutter/flutter/issues/154413
]);
expect(process.stdout, isEmpty);
final List<String> stderrLines = process.stderr.toString().split('\n');
expect(
stderrLines.length,
stderrLines.toSet().length,
reason: 'found duplicates in $stderrLines',
);
final List<String> stderrNoDescriptions = stderrLines.map(removeLintDescriptions).toList();
expect(stderrNoDescriptions, <String>[
...expectedMainErrors,
'Found 18 snippet code errors.',
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
'', // because we end with a newline, split gives us an extra blank line
]);
expect(process.exitCode, 1);
},
// TODO(scheglov): Restore after landing Dart SDK changes, https://github.com/flutter/flutter/issues/154413
skip: true,
);
test('Analyzes dart:ui code', () {
final ProcessResult process = Process.runSync(
'../../bin/cache/dart-sdk/bin/dart',
<String>[
test(
'Analyzes dart:ui code',
() {
final ProcessResult process = Process.runSync('../../bin/cache/dart-sdk/bin/dart', <String>[
'--enable-asserts',
'analyze_snippet_code.dart',
'--dart-ui-location=test/analyze-snippet-code-test-dart-ui',
'test/analyze-snippet-code-test-input',
],
);
expect(process.stdout, isEmpty);
final List<String> stderrLines = process.stderr.toString().split('\n');
expect(stderrLines.length, stderrLines.toSet().length,
reason: 'found duplicates in $stderrLines');
final List<String> stderrNoDescriptions =
stderrLines.map(removeLintDescriptions).toList();
expect(stderrNoDescriptions, <String>[
...expectedUiErrors,
...expectedMainErrors,
'Found 23 snippet code errors.',
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
'', // because we end with a newline, split gives us an extra blank line
]);
expect(process.exitCode, 1);
}, skip: true); // TODO(scheglov): Restore after landing Dart SDK changes, https://github.com/flutter/flutter/issues/154413
]);
expect(process.stdout, isEmpty);
final List<String> stderrLines = process.stderr.toString().split('\n');
expect(
stderrLines.length,
stderrLines.toSet().length,
reason: 'found duplicates in $stderrLines',
);
final List<String> stderrNoDescriptions = stderrLines.map(removeLintDescriptions).toList();
expect(stderrNoDescriptions, <String>[
...expectedUiErrors,
...expectedMainErrors,
'Found 23 snippet code errors.',
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
'', // because we end with a newline, split gives us an extra blank line
]);
expect(process.exitCode, 1);
},
// TODO(scheglov): Restore after landing Dart SDK changes, https://github.com/flutter/flutter/issues/154413
skip: true,
);
}

View File

@@ -16,7 +16,7 @@ import 'common.dart';
typedef AsyncVoidCallback = Future<void> Function();
Future<String> capture(AsyncVoidCallback callback, { bool shouldHaveErrors = false }) async {
Future<String> capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async {
final StringBuffer buffer = StringBuffer();
final PrintCallback oldPrint = print;
try {
@@ -27,7 +27,12 @@ Future<String> capture(AsyncVoidCallback callback, { bool shouldHaveErrors = fal
expect(
hasError,
shouldHaveErrors,
reason: buffer.isEmpty ? '(No output to report.)' : hasError ? 'Unexpected errors:\n$buffer' : 'Unexpected success:\n$buffer',
reason:
buffer.isEmpty
? '(No output to report.)'
: hasError
? 'Unexpected errors:\n$buffer'
: 'Unexpected success:\n$buffer',
);
} finally {
print = oldPrint;
@@ -44,64 +49,104 @@ Future<String> capture(AsyncVoidCallback callback, { bool shouldHaveErrors = fal
void main() {
final String testRootPath = path.join('test', 'analyze-test-input', 'root');
final String dartName = Platform.isWindows ? 'dart.exe' : 'dart';
final String dartPath = path.canonicalize(path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', dartName));
final String dartPath = path.canonicalize(
path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', dartName),
);
final String testGenDefaultsPath = path.join('test', 'analyze-gen-defaults');
test('analyze.dart - verifyDeprecations', () async {
final String result = await capture(() => verifyDeprecations(testRootPath, minimumMatches: 2), shouldHaveErrors: true);
final String lines = <String>[
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:12: Deprecation notice does not match required pattern. There might be a missing space character at the end of the line.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:18: Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: STYLE_GUIDE_URL',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:25: Deprecation notice should be a grammatically correct sentence and end with a period; notice appears to be "Also bad grammar".',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:29: Deprecation notice does not match required pattern.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:32: Deprecation notice does not match required pattern.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:37: Deprecation notice does not match required pattern. It might be missing the line saying "This feature was deprecated after...".',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:41: Deprecation notice does not match required pattern. There might not be an explanatory message.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:48: End of deprecation notice does not match required pattern.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:51: Unexpected deprecation notice indent.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:70: Deprecation notice does not accurately indicate a beta branch version number; please see RELEASES_URL to find the latest beta build version number.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:76: Deprecation notice does not accurately indicate a beta branch version number; please see RELEASES_URL to find the latest beta build version number.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:99: Deprecation notice does not match required pattern. You might have used double quotes (") for the string instead of single quotes (\').',
]
.map((String line) {
return line
.replaceAll('/', Platform.isWindows ? r'\' : '/')
.replaceAll('STYLE_GUIDE_URL', 'https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md')
.replaceAll('RELEASES_URL', 'https://flutter.dev/docs/development/tools/sdk/releases');
})
.join('\n');
expect(result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'║ See: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
);
});
test(
'analyze.dart - verifyDeprecations',
() async {
final String result = await capture(
() => verifyDeprecations(testRootPath, minimumMatches: 2),
shouldHaveErrors: true,
);
final String lines = <String>[
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:14: Deprecation notice does not match required pattern. There might be a missing space character at the end of the line.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:20: Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: STYLE_GUIDE_URL',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:27: Deprecation notice should be a grammatically correct sentence and end with a period; notice appears to be "Also bad grammar".',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:31: Deprecation notice does not match required pattern.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:34: Deprecation notice does not match required pattern.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:39: Deprecation notice does not match required pattern. It might be missing the line saying "This feature was deprecated after...".',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:43: Deprecation notice does not match required pattern. There might not be an explanatory message.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:50: End of deprecation notice does not match required pattern.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:53: Unexpected deprecation notice indent.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:72: Deprecation notice does not accurately indicate a beta branch version number; please see RELEASES_URL to find the latest beta build version number.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:78: Deprecation notice does not accurately indicate a beta branch version number; please see RELEASES_URL to find the latest beta build version number.',
'║ test/analyze-test-input/root/packages/foo/deprecation.dart:101: Deprecation notice does not match required pattern. You might have used double quotes (") for the string instead of single quotes (\').',
]
.map((String line) {
return line
.replaceAll('/', Platform.isWindows ? r'\' : '/')
.replaceAll(
'STYLE_GUIDE_URL',
'https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md',
)
.replaceAll(
'RELEASES_URL',
'https://flutter.dev/docs/development/tools/sdk/releases',
);
})
.join('\n');
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'║ See: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
},
// TODO(goderbauer): Update and re-enable this after formatting changes have landed.
skip: true,
);
test('analyze.dart - verifyGoldenTags', () async {
final List<String> result = (await capture(() => verifyGoldenTags(testRootPath, minimumMatches: 6), shouldHaveErrors: true)).split('\n');
const String noTag = "Files containing golden tests must be tagged using @Tags(<String>['reduced-test-set']) "
'at the top of the file before import statements.';
const String missingTag = "Files containing golden tests must be tagged with 'reduced-test-set'.";
final List<String> lines = <String>[
'║ test/analyze-test-input/root/packages/foo/golden_missing_tag.dart: $missingTag',
'║ test/analyze-test-input/root/packages/foo/golden_no_tag.dart: $noTag',
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.toList();
expect(result.length, 4 + lines.length, reason: 'output had unexpected number of lines:\n${result.join('\n')}');
expect(result[0], '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════');
final List<String> result = (await capture(
() => verifyGoldenTags(testRootPath, minimumMatches: 6),
shouldHaveErrors: true,
)).split('\n');
const String noTag =
"Files containing golden tests must be tagged using @Tags(<String>['reduced-test-set']) "
'at the top of the file before import statements.';
const String missingTag =
"Files containing golden tests must be tagged with 'reduced-test-set'.";
final List<String> lines =
<String>[
'║ test/analyze-test-input/root/packages/foo/golden_missing_tag.dart: $missingTag',
'║ test/analyze-test-input/root/packages/foo/golden_no_tag.dart: $noTag',
].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).toList();
expect(
result.length,
4 + lines.length,
reason: 'output had unexpected number of lines:\n${result.join('\n')}',
);
expect(
result[0],
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════',
);
expect(result.getRange(1, result.length - 3).toSet(), lines.toSet());
expect(result[result.length - 3], '║ See: https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md');
expect(result[result.length - 2], '╚═══════════════════════════════════════════════════════════════════════════════');
expect(
result[result.length - 3],
'║ See: https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md',
);
expect(
result[result.length - 2],
'╚═══════════════════════════════════════════════════════════════════════════════',
);
expect(result[result.length - 1], ''); // trailing newline
});
test('analyze.dart - verifyNoMissingLicense', () async {
final String result = await capture(() => verifyNoMissingLicense(testRootPath, checkMinimums: false), shouldHaveErrors: true);
final String file = 'test/analyze-test-input/root/packages/foo/foo.dart'
.replaceAll('/', Platform.isWindows ? r'\' : '/');
expect(result,
final String result = await capture(
() => verifyNoMissingLicense(testRootPath, checkMinimums: false),
shouldHaveErrors: true,
);
final String file = 'test/analyze-test-input/root/packages/foo/foo.dart'.replaceAll(
'/',
Platform.isWindows ? r'\' : '/',
);
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'║ The following file does not have the right license header for dart files:\n'
'$file\n'
@@ -110,75 +155,67 @@ void main() {
'║ // Use of this source code is governed by a BSD-style license that can be\n'
'║ // found in the LICENSE file.\n'
'║ ...followed by a blank line.\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
test('analyze.dart - verifyNoTrailingSpaces', () async {
final String result = await capture(() => verifyNoTrailingSpaces(testRootPath, minimumMatches: 2), shouldHaveErrors: true);
final String lines = <String>[
'║ test/analyze-test-input/root/packages/foo/spaces.txt:5: trailing U+0020 space character',
'║ test/analyze-test-input/root/packages/foo/spaces.txt:9: trailing blank line',
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.join('\n');
expect(result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
final String result = await capture(
() => verifyNoTrailingSpaces(testRootPath, minimumMatches: 2),
shouldHaveErrors: true,
);
});
test('analyze.dart - verifySpacesAfterFlowControlStatements', () async {
final String result = await capture(() => verifySpacesAfterFlowControlStatements(testRootPath, minimumMatches: 2), shouldHaveErrors: true);
final String lines = <String>[
'║ test/analyze-test-input/root/packages/foo/spaces_after_flow.dart:11: no space after flow control statement',
'║ test/analyze-test-input/root/packages/foo/spaces_after_flow.dart:18: no space after flow control statement',
'║ test/analyze-test-input/root/packages/foo/spaces_after_flow.dart:25: no space after flow control statement',
'║ test/analyze-test-input/root/packages/foo/spaces_after_flow.dart:29: no space after flow control statement',
'║ test/analyze-test-input/root/packages/foo/spaces_after_flow.dart:35: no space after flow control statement',
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.join('\n');
expect(result,
'║ test/analyze-test-input/root/packages/foo/spaces.txt:5: trailing U+0020 space character',
'║ test/analyze-test-input/root/packages/foo/spaces.txt:9: trailing blank line',
].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n');
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
test('analyze.dart - verifyRepositoryLinks', () async {
final String result = await capture(() => verifyRepositoryLinks(testRootPath), shouldHaveErrors: true);
final String result = await capture(
() => verifyRepositoryLinks(testRootPath),
shouldHaveErrors: true,
);
const String bannedBranch = 'master';
final String file = Platform.isWindows ?
r'test\analyze-test-input\root\packages\foo\bad_repository_links.dart' :
'test/analyze-test-input/root/packages/foo/bad_repository_links.dart';
final String file =
Platform.isWindows
? r'test\analyze-test-input\root\packages\foo\bad_repository_links.dart'
: 'test/analyze-test-input/root/packages/foo/bad_repository_links.dart';
final String lines = <String>[
'$file contains https://android.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://chromium.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://cs.opensource.google.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://dart.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://flutter.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://source.chromium.org/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://github.com/flutter/flutter/tree/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://raw.githubusercontent.com/flutter/flutter/blob/$bannedBranch/file1, which uses the banned "master" branch.',
'║ Change the URLs above to the expected pattern by using the "main" branch if it exists, otherwise adding the repository to the list of exceptions in analyze.dart.',
]
.join('\n');
expect(result,
'$file contains https://android.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://chromium.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://cs.opensource.google.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://dart.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://flutter.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://source.chromium.org/+/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://github.com/flutter/flutter/tree/$bannedBranch/file1, which uses the banned "master" branch.',
'$file contains https://raw.githubusercontent.com/flutter/flutter/blob/$bannedBranch/file1, which uses the banned "master" branch.',
'║ Change the URLs above to the expected pattern by using the "main" branch if it exists, otherwise adding the repository to the list of exceptions in analyze.dart.',
].join('\n');
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
test('analyze.dart - verifyNoBinaries - positive', () async {
final String result = await capture(() => verifyNoBinaries(
testRootPath,
legacyBinaries: <Hash256>{const Hash256(0x39A050CD69434936, 0, 0, 0)},
), shouldHaveErrors: !Platform.isWindows);
final String result = await capture(
() => verifyNoBinaries(
testRootPath,
legacyBinaries: <Hash256>{const Hash256(0x39A050CD69434936, 0, 0, 0)},
),
shouldHaveErrors: !Platform.isWindows,
);
if (!Platform.isWindows) {
expect(result,
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'║ test/analyze-test-input/root/packages/foo/serviceaccount.enc:0: file is not valid UTF-8\n'
'║ All files in this repository must be UTF-8. In particular, images and other binaries\n'
@@ -188,51 +225,78 @@ void main() {
'║ for example, the "assets-for-api-docs" repository is used for images in API docs.\n'
'║ To add assets to flutter_tools templates, see the instructions in the wiki:\n'
'║ https://github.com/flutter/flutter/blob/main/docs/tool/Managing-template-image-assets.md\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
}
});
test('analyze.dart - verifyInternationalizations - comparison fails', () async {
final String result = await capture(() => verifyInternationalizations(testRootPath, dartPath), shouldHaveErrors: true);
final String genLocalizationsScript = path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart');
expect(result,
contains('$dartName $genLocalizationsScript --cupertino'));
expect(result,
contains('$dartName $genLocalizationsScript --material'));
final String generatedFile = path.join(testRootPath, 'packages', 'flutter_localizations',
'lib', 'src', 'l10n', 'generated_material_localizations.dart');
expect(result,
contains('The contents of $generatedFile are different from that produced by gen_localizations.'));
expect(result,
contains(r'Did you forget to run gen_localizations.dart after updating a .arb file?'));
final String result = await capture(
() => verifyInternationalizations(testRootPath, dartPath),
shouldHaveErrors: true,
);
final String genLocalizationsScript = path.join(
'dev',
'tools',
'localization',
'bin',
'gen_localizations.dart',
);
expect(result, contains('$dartName $genLocalizationsScript --cupertino'));
expect(result, contains('$dartName $genLocalizationsScript --material'));
final String generatedFile = path.join(
testRootPath,
'packages',
'flutter_localizations',
'lib',
'src',
'l10n',
'generated_material_localizations.dart',
);
expect(
result,
contains(
'The contents of $generatedFile are different from that produced by gen_localizations.',
),
);
expect(
result,
contains(r'Did you forget to run gen_localizations.dart after updating a .arb file?'),
);
});
test('analyze.dart - verifyNoBinaries - negative', () async {
await capture(() => verifyNoBinaries(
testRootPath,
legacyBinaries: <Hash256>{
const Hash256(0xA8100AE6AA1940D0, 0xB663BB31CD466142, 0xEBBDBD5187131B92, 0xD93818987832EB89), // sha256("\xff")
const Hash256(0x155644D3F13D98BF, 0, 0, 0),
},
));
await capture(
() => verifyNoBinaries(
testRootPath,
legacyBinaries: <Hash256>{
const Hash256(
0xA8100AE6AA1940D0,
0xB663BB31CD466142,
0xEBBDBD5187131B92,
0xD93818987832EB89,
), // sha256("\xff")
const Hash256(0x155644D3F13D98BF, 0, 0, 0),
},
),
);
});
test('analyze.dart - verifyNullInitializedDebugExpensiveFields', () async {
final String result = await capture(() => verifyNullInitializedDebugExpensiveFields(
testRootPath,
minimumMatches: 1,
), shouldHaveErrors: true);
final String result = await capture(
() => verifyNullInitializedDebugExpensiveFields(testRootPath, minimumMatches: 1),
shouldHaveErrors: true,
);
expect(result, contains(':15'));
expect(result, isNot(contains(':12')));
expect(result, contains(':16'));
expect(result, isNot(contains(':13')));
});
test('analyze.dart - verifyTabooDocumentation', () async {
final String result = await capture(() => verifyTabooDocumentation(
testRootPath,
minimumMatches: 1,
), shouldHaveErrors: true);
final String result = await capture(
() => verifyTabooDocumentation(testRootPath, minimumMatches: 1),
shouldHaveErrors: true,
);
expect(result, isNot(contains(':19')));
expect(result, contains(':20'));
@@ -240,67 +304,74 @@ void main() {
});
test('analyze.dart - clampDouble', () async {
final String result = await capture(() => analyzeWithRules(
testRootPath,
<AnalyzeRule>[noDoubleClamp],
includePaths: <String>['packages/flutter/lib'],
), shouldHaveErrors: true);
final String result = await capture(
() => analyzeWithRules(
testRootPath,
<AnalyzeRule>[noDoubleClamp],
includePaths: <String>['packages/flutter/lib'],
),
shouldHaveErrors: true,
);
final String lines = <String>[
'║ packages/flutter/lib/bar.dart:37: input.clamp(0.0, 2)',
'║ packages/flutter/lib/bar.dart:38: input.toDouble().clamp(0, 2)',
'║ packages/flutter/lib/bar.dart:42: nullableInt?.clamp(0, 2.0)',
'║ packages/flutter/lib/bar.dart:43: nullableDouble?.clamp(0, 2)',
'║ packages/flutter/lib/bar.dart:48: nullableInt?.clamp',
'║ packages/flutter/lib/bar.dart:50: nullableDouble?.clamp',
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.join('\n');
expect(result,
'║ packages/flutter/lib/bar.dart:37: input.clamp(0.0, 2)',
'║ packages/flutter/lib/bar.dart:38: input.toDouble().clamp(0, 2)',
'║ packages/flutter/lib/bar.dart:42: nullableInt?.clamp(0, 2.0)',
'║ packages/flutter/lib/bar.dart:43: nullableDouble?.clamp(0, 2)',
'║ packages/flutter/lib/bar.dart:48: nullableInt?.clamp',
'║ packages/flutter/lib/bar.dart:50: nullableDouble?.clamp',
].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n');
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'\n'
'║ For performance reasons, we use a custom "clampDouble" function instead of using "double.clamp".\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
test('analyze.dart - stopwatch', () async {
final String result = await capture(() => analyzeWithRules(
testRootPath,
<AnalyzeRule>[noStopwatches],
includePaths: <String>['packages/flutter/lib'],
), shouldHaveErrors: true);
final String result = await capture(
() => analyzeWithRules(
testRootPath,
<AnalyzeRule>[noStopwatches],
includePaths: <String>['packages/flutter/lib'],
),
shouldHaveErrors: true,
);
final String lines = <String>[
'║ packages/flutter/lib/stopwatch.dart:18: Stopwatch()',
'║ packages/flutter/lib/stopwatch.dart:19: Stopwatch()',
'║ packages/flutter/lib/stopwatch.dart:24: StopwatchAtHome()',
'║ packages/flutter/lib/stopwatch.dart:27: StopwatchAtHome.new',
'║ packages/flutter/lib/stopwatch.dart:30: StopwatchAtHome.create',
'║ packages/flutter/lib/stopwatch.dart:36: externallib.MyStopwatch.create()',
'║ packages/flutter/lib/stopwatch.dart:40: externallib.MyStopwatch.new',
'║ packages/flutter/lib/stopwatch.dart:45: externallib.stopwatch',
'║ packages/flutter/lib/stopwatch.dart:46: externallib.createMyStopwatch()',
'║ packages/flutter/lib/stopwatch.dart:47: externallib.createStopwatch()',
'║ packages/flutter/lib/stopwatch.dart:48: externallib.createMyStopwatch'
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.join('\n');
expect(result,
'║ packages/flutter/lib/stopwatch.dart:20: Stopwatch()',
'║ packages/flutter/lib/stopwatch.dart:22: Stopwatch()',
'║ packages/flutter/lib/stopwatch.dart:28: StopwatchAtHome()',
'║ packages/flutter/lib/stopwatch.dart:33: StopwatchAtHome.new',
'║ packages/flutter/lib/stopwatch.dart:37: StopwatchAtHome.create',
'║ packages/flutter/lib/stopwatch.dart:44: externallib.MyStopwatch.create()',
'║ packages/flutter/lib/stopwatch.dart:49: externallib.MyStopwatch.new',
'║ packages/flutter/lib/stopwatch.dart:55: externallib.stopwatch',
'║ packages/flutter/lib/stopwatch.dart:57: externallib.createMyStopwatch()',
'║ packages/flutter/lib/stopwatch.dart:59: externallib.createStopwatch()',
'║ packages/flutter/lib/stopwatch.dart:61: externallib.createMyStopwatch',
].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n');
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'\n'
'║ Stopwatches introduce flakes by falling out of sync with the FakeAsync used in testing.\n'
'║ A Stopwatch that stays in sync with FakeAsync is available through the Gesture or Test bindings, through samplingClock.\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
test('analyze.dart - RenderBox intrinsics', () async {
final String result = await capture(() => analyzeWithRules(
testRootPath,
<AnalyzeRule>[renderBoxIntrinsicCalculation],
includePaths: <String>['packages/flutter/lib'],
), shouldHaveErrors: true);
final String result = await capture(
() => analyzeWithRules(
testRootPath,
<AnalyzeRule>[renderBoxIntrinsicCalculation],
includePaths: <String>['packages/flutter/lib'],
),
shouldHaveErrors: true,
);
final String lines = <String>[
'║ packages/flutter/lib/renderbox_intrinsics.dart:12: computeMaxIntrinsicWidth(). Consider calling getMaxIntrinsicWidth instead.',
'║ packages/flutter/lib/renderbox_intrinsics.dart:16: f = computeMaxIntrinsicWidth. Consider calling getMaxIntrinsicWidth instead.',
@@ -308,35 +379,33 @@ void main() {
'║ packages/flutter/lib/renderbox_intrinsics.dart:24: computeDryLayout(). Consider calling getDryLayout instead.',
'║ packages/flutter/lib/renderbox_intrinsics.dart:31: computeDistanceToActualBaseline(). Consider calling getDistanceToBaseline, or getDistanceToActualBaseline instead.',
'║ packages/flutter/lib/renderbox_intrinsics.dart:36: computeMaxIntrinsicHeight(). Consider calling getMaxIntrinsicHeight instead.',
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.join('\n');
expect(result,
].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n');
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'\n'
'║ Typically the get* methods should be used to obtain the intrinsics of a RenderBox.\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
test('analyze.dart - verifyMaterialFilesAreUpToDateWithTemplateFiles', () async {
String result = await capture(() => verifyMaterialFilesAreUpToDateWithTemplateFiles(
testGenDefaultsPath,
dartPath,
), shouldHaveErrors: true);
String result = await capture(
() => verifyMaterialFilesAreUpToDateWithTemplateFiles(testGenDefaultsPath, dartPath),
shouldHaveErrors: true,
);
final String lines = <String>[
'║ chip.dart is not up-to-date with the token template file.',
]
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
.join('\n');
'║ chip.dart is not up-to-date with the token template file.',
].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n');
const String errorStart = '╔═';
result = result.substring(result.indexOf(errorStart));
expect(result,
expect(
result,
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
'$lines\n'
'║ See: https://github.com/flutter/flutter/blob/main/dev/tools/gen_defaults to update the token template files.\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n'
'╚═══════════════════════════════════════════════════════════════════════════════\n',
);
});
}

View File

@@ -40,14 +40,21 @@ void main() {
''');
}
void buildTestFiles({bool missingLinks = false, bool missingTests = false, bool malformedLinks = false}) {
final Directory examplesLib = examples.childDirectory('lib').childDirectory('layer')..createSync(recursive: true);
final File fooExample = examplesLib.childFile('foo_example.0.dart')
..createSync(recursive: true)
..writeAsStringSync('// Example for foo');
final File barExample = examplesLib.childFile('bar_example.0.dart')
..createSync(recursive: true)
..writeAsStringSync('// Example for bar');
void buildTestFiles({
bool missingLinks = false,
bool missingTests = false,
bool malformedLinks = false,
}) {
final Directory examplesLib = examples.childDirectory('lib').childDirectory('layer')
..createSync(recursive: true);
final File fooExample =
examplesLib.childFile('foo_example.0.dart')
..createSync(recursive: true)
..writeAsStringSync('// Example for foo');
final File barExample =
examplesLib.childFile('bar_example.0.dart')
..createSync(recursive: true)
..writeAsStringSync('// Example for bar');
if (missingLinks) {
examplesLib.childFile('missing_example.0.dart')
..createSync(recursive: true)
@@ -68,32 +75,52 @@ void main() {
..createSync(recursive: true)
..writeAsStringSync('// test for foo example');
}
final Directory flutterPackage = packages.childDirectory('flutter').childDirectory('lib').childDirectory('src')
..createSync(recursive: true);
final Directory flutterPackage = packages
.childDirectory('flutter')
.childDirectory('lib')
.childDirectory('src')..createSync(recursive: true);
if (malformedLinks) {
writeLink(source: flutterPackage.childDirectory('layer').childFile('foo.dart'), example: fooExample, alternateLink: '*See Code *');
writeLink(source: flutterPackage.childDirectory('layer').childFile('bar.dart'), example: barExample, alternateLink: ' ** See code examples/api/lib/layer/bar_example.0.dart **');
writeLink(
source: flutterPackage.childDirectory('layer').childFile('foo.dart'),
example: fooExample,
alternateLink: '*See Code *',
);
writeLink(
source: flutterPackage.childDirectory('layer').childFile('bar.dart'),
example: barExample,
alternateLink: ' ** See code examples/api/lib/layer/bar_example.0.dart **',
);
} else {
writeLink(source: flutterPackage.childDirectory('layer').childFile('foo.dart'), example: fooExample);
writeLink(source: flutterPackage.childDirectory('layer').childFile('bar.dart'), example: barExample);
writeLink(
source: flutterPackage.childDirectory('layer').childFile('foo.dart'),
example: fooExample,
);
writeLink(
source: flutterPackage.childDirectory('layer').childFile('bar.dart'),
example: barExample,
);
}
}
setUp(() {
fs = MemoryFileSystem(style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix);
fs = MemoryFileSystem(
style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix,
);
// Get the root prefix of the current directory so that on Windows we get a
// correct root prefix.
flutterRoot = fs.directory(path.join(path.rootPrefix(fs.currentDirectory.absolute.path), 'flutter sdk'))..createSync(recursive: true);
flutterRoot = fs.directory(
path.join(path.rootPrefix(fs.currentDirectory.absolute.path), 'flutter sdk'),
)..createSync(recursive: true);
fs.currentDirectory = flutterRoot;
examples = flutterRoot.childDirectory('examples').childDirectory('api')..createSync(recursive: true);
examples = flutterRoot.childDirectory('examples').childDirectory('api')
..createSync(recursive: true);
packages = flutterRoot.childDirectory('packages')..createSync(recursive: true);
dartUIPath = flutterRoot
.childDirectory('bin')
.childDirectory('cache')
.childDirectory('pkg')
.childDirectory('sky_engine')
.childDirectory('lib')
..createSync(recursive: true);
.childDirectory('bin')
.childDirectory('cache')
.childDirectory('pkg')
.childDirectory('sky_engine')
.childDirectory('lib')..createSync(recursive: true);
checker = SampleChecker(
examples: examples,
packages: packages,
@@ -106,21 +133,20 @@ void main() {
test('check_code_samples.dart - checkCodeSamples catches missing links', () async {
buildTestFiles(missingLinks: true);
bool? success;
final String result = await capture(
() async {
success = checker.checkCodeSamples();
},
shouldHaveErrors: true,
);
final String result = await capture(() async {
success = checker.checkCodeSamples();
}, shouldHaveErrors: true);
final String lines = <String>[
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════',
'║ The following examples are not linked from any source file API doc comments:',
'║ examples/api/lib/layer/missing_example.0.dart',
'║ Either link them to a source file API doc comment, or remove them.',
'╚═══════════════════════════════════════════════════════════════════════════════',
].map((String line) {
return line.replaceAll('/', Platform.isWindows ? r'\' : '/');
}).join('\n');
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════',
'║ The following examples are not linked from any source file API doc comments:',
'║ examples/api/lib/layer/missing_example.0.dart',
'║ Either link them to a source file API doc comment, or remove them.',
'╚═══════════════════════════════════════════════════════════════════════════════',
]
.map((String line) {
return line.replaceAll('/', Platform.isWindows ? r'\' : '/');
})
.join('\n');
expect(result, equals('$lines\n'));
expect(success, equals(false));
});
@@ -128,12 +154,9 @@ void main() {
test('check_code_samples.dart - checkCodeSamples catches malformed links', () async {
buildTestFiles(malformedLinks: true);
bool? success;
final String result = await capture(
() async {
success = checker.checkCodeSamples();
},
shouldHaveErrors: true,
);
final String result = await capture(() async {
success = checker.checkCodeSamples();
}, shouldHaveErrors: true);
final bool isWindows = Platform.isWindows;
final String lines = <String>[
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════',
@@ -147,9 +170,12 @@ void main() {
'╔═╡ERROR #2╞════════════════════════════════════════════════════════════════════',
'║ The following malformed links were found in API doc comments:',
if (!isWindows) '║ /flutter sdk/packages/flutter/lib/src/layer/foo.dart:6: ///*See Code *',
if (!isWindows) '║ /flutter sdk/packages/flutter/lib/src/layer/bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **',
if (isWindows) r'C:\flutter sdk\packages\flutter\lib\src\layer\foo.dart:6: ///*See Code *',
if (isWindows) r'║ C:\flutter sdk\packages\flutter\lib\src\layer\bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **',
if (!isWindows)
'/flutter sdk/packages/flutter/lib/src/layer/bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **',
if (isWindows)
r'║ C:\flutter sdk\packages\flutter\lib\src\layer\foo.dart:6: ///*See Code *',
if (isWindows)
r'║ C:\flutter sdk\packages\flutter\lib\src\layer\bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **',
'║ Correct the formatting of these links so that they match the exact pattern:',
r"║ r'\*\* See code in (?<path>.+) \*\*'",
'╚═══════════════════════════════════════════════════════════════════════════════',
@@ -161,20 +187,19 @@ void main() {
test('check_code_samples.dart - checkCodeSamples catches missing tests', () async {
buildTestFiles(missingTests: true);
bool? success;
final String result = await capture(
() async {
success = checker.checkCodeSamples();
},
shouldHaveErrors: true,
);
final String result = await capture(() async {
success = checker.checkCodeSamples();
}, shouldHaveErrors: true);
final String lines = <String>[
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════',
'║ The following example test files are missing:',
'║ examples/api/test/layer/bar_example.0_test.dart',
'╚═══════════════════════════════════════════════════════════════════════════════',
].map((String line) {
return line.replaceAll('/', Platform.isWindows ? r'\' : '/');
}).join('\n');
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════',
'║ The following example test files are missing:',
'║ examples/api/test/layer/bar_example.0_test.dart',
'╚═══════════════════════════════════════════════════════════════════════════════',
]
.map((String line) {
return line.replaceAll('/', Platform.isWindows ? r'\' : '/');
})
.join('\n');
expect(result, equals('$lines\n'));
expect(success, equals(false));
});
@@ -182,11 +207,9 @@ void main() {
test('check_code_samples.dart - checkCodeSamples succeeds', () async {
buildTestFiles();
bool? success;
final String result = await capture(
() async {
success = checker.checkCodeSamples();
},
);
final String result = await capture(() async {
success = checker.checkCodeSamples();
});
expect(result, isEmpty);
expect(success, equals(true));
});
@@ -205,9 +228,10 @@ Future<String> capture(AsyncVoidCallback callback, {bool shouldHaveErrors = fals
expect(
hasError,
shouldHaveErrors,
reason: buffer.isEmpty
? '(No output to report.)'
: hasError
reason:
buffer.isEmpty
? '(No output to report.)'
: hasError
? 'Unexpected errors:\n$buffer'
: 'Unexpected success:\n$buffer',
);

View File

@@ -11,7 +11,8 @@ import './common.dart';
void main() async {
const String flutterRoot = '/a/b/c';
final List<String> allExpectedFiles = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
final List<String> allExpectedFiles =
binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
final String allFilesStdout = allExpectedFiles.join('\n');
final List<String> allExpectedXcframeworks = signedXcframeworks(flutterRoot);
final String allXcframeworksStdout = allExpectedXcframeworks.join('\n');
@@ -19,28 +20,21 @@ void main() async {
group('verifyExist', () {
test('Not all files found', () async {
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: <String>[
'find',
'/a/b/c/bin/cache',
'-type',
'f',
],
stdout: '/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
),
const FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
'/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
],
stdout: 'application/x-mach-binary',
),
],
);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['find', '/a/b/c/bin/cache', '-type', 'f'],
stdout: '/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
),
const FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
'/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
],
stdout: 'application/x-mach-binary',
),
]);
expect(
() async => verifyExist(flutterRoot, processManager: processManager),
throwsExceptionWith('Did not find all expected binaries!'),
@@ -50,25 +44,16 @@ void main() async {
test('All files found', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
final FakeCommand findCmd = FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'f',],
command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
stdout: allFilesStdout,
);
);
commandList.add(findCmd);
for (final String expectedFile in allExpectedFiles) {
commandList.add(
FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
expectedFile,
],
command: <String>['file', '--mime-type', '-b', expectedFile],
stdout: 'application/x-mach-binary',
)
),
);
}
final ProcessManager processManager = FakeProcessManager.list(commandList);
@@ -80,44 +65,37 @@ void main() async {
test('All binary files found', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
final FakeCommand findCmd = FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'f',],
command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
stdout: allFilesStdout,
);
commandList.add(findCmd);
for (final String expectedFile in allExpectedFiles) {
commandList.add(
FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
expectedFile,
],
command: <String>['file', '--mime-type', '-b', expectedFile],
stdout: 'application/x-mach-binary',
)
),
);
}
final ProcessManager processManager = FakeProcessManager.list(commandList);
final List<String> foundFiles = await findBinaryPaths('$flutterRoot/bin/cache', processManager: processManager);
final List<String> foundFiles = await findBinaryPaths(
'$flutterRoot/bin/cache',
processManager: processManager,
);
expect(foundFiles, allExpectedFiles);
});
test('Empty file list', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const FakeCommand findCmd = FakeCommand(
command: <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'f',],
command: <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
);
commandList.add(findCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final List<String> foundFiles = await findBinaryPaths('$flutterRoot/bin/cache', processManager: processManager);
final List<String> foundFiles = await findBinaryPaths(
'$flutterRoot/bin/cache',
processManager: processManager,
);
expect(foundFiles, <String>[]);
});
@@ -133,115 +111,94 @@ void main() async {
'*xcframework',
],
stdout: allXcframeworksStdout,
)
),
];
final ProcessManager processManager = FakeProcessManager.list(commandList);
final List<String> foundFiles = await findXcframeworksPaths('$flutterRoot/bin/cache', processManager: processManager);
final List<String> foundFiles = await findXcframeworksPaths(
'$flutterRoot/bin/cache',
processManager: processManager,
);
expect(foundFiles, allExpectedXcframeworks);
});
group('isBinary', () {
test('isTrue', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand findCmd = FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
fileToCheck,
],
stdout: 'application/x-mach-binary',
);
commandList.add(findCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await isBinary(fileToCheck, processManager: processManager);
expect(result, isTrue);
group('isBinary', () {
test('isTrue', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand findCmd = FakeCommand(
command: <String>['file', '--mime-type', '-b', fileToCheck],
stdout: 'application/x-mach-binary',
);
commandList.add(findCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await isBinary(fileToCheck, processManager: processManager);
expect(result, isTrue);
});
test('isFalse', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand findCmd = FakeCommand(
command: <String>['file', '--mime-type', '-b', fileToCheck],
stdout: 'text/xml',
);
commandList.add(findCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await isBinary(fileToCheck, processManager: processManager);
expect(result, isFalse);
});
});
test('isFalse', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand findCmd = FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
group('hasExpectedEntitlements', () {
test('expected entitlements', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand codesignCmd = FakeCommand(
command: <String>['codesign', '--display', '--entitlements', ':-', fileToCheck],
);
commandList.add(codesignCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await hasExpectedEntitlements(
fileToCheck,
],
stdout: 'text/xml',
);
commandList.add(findCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await isBinary(fileToCheck, processManager: processManager);
expect(result, isFalse);
});
});
flutterRoot,
processManager: processManager,
);
expect(result, isTrue);
});
group('hasExpectedEntitlements', () {
test('expected entitlements', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand codesignCmd = FakeCommand(
command: <String>[
'codesign',
'--display',
'--entitlements',
':-',
fileToCheck,
],
);
commandList.add(codesignCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await hasExpectedEntitlements(fileToCheck, flutterRoot, processManager: processManager);
expect(result, isTrue);
});
test('unexpected entitlements', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand codesignCmd = FakeCommand(
command: <String>[
'codesign',
'--display',
'--entitlements',
':-',
fileToCheck,
],
exitCode: 1,
);
commandList.add(codesignCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await hasExpectedEntitlements(fileToCheck, flutterRoot, processManager: processManager);
expect(result, isFalse);
});
test('unexpected entitlements', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
const String fileToCheck = '/a/b/c/one.zip';
const FakeCommand codesignCmd = FakeCommand(
command: <String>['codesign', '--display', '--entitlements', ':-', fileToCheck],
exitCode: 1,
);
commandList.add(codesignCmd);
final ProcessManager processManager = FakeProcessManager.list(commandList);
final bool result = await hasExpectedEntitlements(
fileToCheck,
flutterRoot,
processManager: processManager,
);
expect(result, isFalse);
});
});
});
group('verifySignatures', () {
test('succeeds if every binary is codesigned and has correct entitlements', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
final FakeCommand findCmd = FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'f',],
command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
stdout: allFilesStdout,
);
);
commandList.add(findCmd);
for (final String expectedFile in allExpectedFiles) {
commandList.add(
FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
expectedFile,
],
command: <String>['file', '--mime-type', '-b', expectedFile],
stdout: 'application/x-mach-binary',
)
),
);
}
commandList.add(
@@ -258,41 +215,19 @@ void main() async {
),
);
for (final String expectedFile in allExpectedFiles) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'-vvv',
expectedFile,
],
)
);
commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedFile]));
if (withEntitlements.contains(expectedFile)) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'--display',
'--entitlements',
':-',
expectedFile,
],
command: <String>['codesign', '--display', '--entitlements', ':-', expectedFile],
stdout: expectedEntitlements.join('\n'),
)
),
);
}
}
for (final String expectedXcframework in allExpectedXcframeworks) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'-vvv',
expectedXcframework,
],
)
);
commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedXcframework]));
}
final ProcessManager processManager = FakeProcessManager.list(commandList);
await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes);
@@ -301,25 +236,16 @@ void main() async {
test('fails if binaries do not have the right entitlements', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
final FakeCommand findCmd = FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'f',],
command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
stdout: allFilesStdout,
);
);
commandList.add(findCmd);
for (final String expectedFile in allExpectedFiles) {
commandList.add(
FakeCommand(
command: <String>[
'file',
'--mime-type',
'-b',
expectedFile,
],
command: <String>['file', '--mime-type', '-b', expectedFile],
stdout: 'application/x-mach-binary',
)
),
);
}
commandList.add(
@@ -336,39 +262,17 @@ void main() async {
),
);
for (final String expectedFile in allExpectedFiles) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'-vvv',
expectedFile,
],
)
);
commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedFile]));
if (withEntitlements.contains(expectedFile)) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'--display',
'--entitlements',
':-',
expectedFile,
],
)
command: <String>['codesign', '--display', '--entitlements', ':-', expectedFile],
),
);
}
}
for (final String expectedXcframework in allExpectedXcframeworks) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'-vvv',
expectedXcframework,
],
)
);
commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedXcframework]));
}
final ProcessManager processManager = FakeProcessManager.list(commandList);

View File

@@ -24,10 +24,10 @@ void tryToDelete(Directory directory) {
Matcher throwsExceptionWith(String messageSubString) {
return throwsA(
isA<Exception>().having(
(Exception e) => e.toString(),
'description',
contains(messageSubString),
),
isA<Exception>().having(
(Exception e) => e.toString(),
'description',
contains(messageSubString),
),
);
}

View File

@@ -16,9 +16,7 @@ void main() async {
const String branchName = 'stable';
test('getBranchName does not call git if env LUCI_BRANCH provided', () async {
final Platform platform = FakePlatform(
environment: <String, String>{
'LUCI_BRANCH': branchName,
},
environment: <String, String>{'LUCI_BRANCH': branchName},
);
final ProcessManager processManager = FakeProcessManager.empty();
final String calculatedBranchName = await getBranchName(
@@ -29,49 +27,36 @@ void main() async {
});
test('getBranchName calls git if env LUCI_BRANCH not provided', () async {
final Platform platform = FakePlatform(
environment: <String, String>{},
);
final Platform platform = FakePlatform(environment: <String, String>{});
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: <String>['git', 'status', '-b', '--porcelain'],
stdout: '## $branchName',
),
],
);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['git', 'status', '-b', '--porcelain'],
stdout: '## $branchName',
),
]);
final String calculatedBranchName = await getBranchName(platform: platform, processManager: processManager);
expect(
calculatedBranchName,
branchName,
);
expect(processManager, hasNoRemainingExpectations);
});
test('getBranchName calls git if env LUCI_BRANCH is empty', () async {
final Platform platform = FakePlatform(
environment: <String, String>{
'LUCI_BRANCH': '',
},
);
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: <String>['git', 'status', '-b', '--porcelain'],
stdout: '## $branchName',
),
],
);
final String calculatedBranchName = await getBranchName(
platform: platform,
processManager: processManager,
);
expect(
calculatedBranchName,
branchName,
expect(calculatedBranchName, branchName);
expect(processManager, hasNoRemainingExpectations);
});
test('getBranchName calls git if env LUCI_BRANCH is empty', () async {
final Platform platform = FakePlatform(environment: <String, String>{'LUCI_BRANCH': ''});
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['git', 'status', '-b', '--porcelain'],
stdout: '## $branchName',
),
]);
final String calculatedBranchName = await getBranchName(
platform: platform,
processManager: processManager,
);
expect(calculatedBranchName, branchName);
expect(processManager, hasNoRemainingExpectations);
});
});
@@ -79,14 +64,9 @@ void main() async {
group('gitRevision', () {
test('Return short format', () async {
const String commitHash = 'e65f01793938e13cac2d321b9fcdc7939f9b2ea6';
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: commitHash,
),
],
);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: commitHash),
]);
final String revision = await gitRevision(processManager: processManager);
expect(processManager, hasNoRemainingExpectations);
expect(revision, commitHash.substring(0, 10));
@@ -94,14 +74,9 @@ void main() async {
test('Return full length', () async {
const String commitHash = 'e65f01793938e13cac2d321b9fcdc7939f9b2ea6';
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: commitHash,
),
],
);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: commitHash),
]);
final String revision = await gitRevision(fullLength: true, processManager: processManager);
expect(processManager, hasNoRemainingExpectations);
expect(revision, commitHash);
@@ -111,29 +86,25 @@ void main() async {
group('runProcessWithValidation', () {
test('With no error', () async {
const List<String> command = <String>['git', 'rev-parse', 'HEAD'];
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: command,
),
],
);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: command),
]);
await runProcessWithValidations(command, '', processManager: processManager, verbose: false);
expect(processManager, hasNoRemainingExpectations);
});
test('With error', () async {
const List<String> command = <String>['git', 'rev-parse', 'HEAD'];
final ProcessManager processManager = FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
command: command,
exitCode: 1,
),
],
);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: command, exitCode: 1),
]);
try {
await runProcessWithValidations(command, '', processManager: processManager, verbose: false);
await runProcessWithValidations(
command,
'',
processManager: processManager,
verbose: false,
);
throw Exception('Exception was not thrown');
} on CommandException catch (e) {
expect(e, isA<Exception>());
@@ -157,7 +128,13 @@ void main() async {
''';
final MemoryFileSystem fs = MemoryFileSystem();
final File footerFile = fs.file('/a/b/c/footer.js')..createSync(recursive: true);
await createFooter(footerFile, '3.0.0', timestampParam: '2022-09-22 14:09', branchParam: 'stable', revisionParam: 'abcdef');
await createFooter(
footerFile,
'3.0.0',
timestampParam: '2022-09-22 14:09',
branchParam: 'stable',
revisionParam: 'abcdef',
);
final String content = await footerFile.readAsString();
expect(content, expectedContent);
});

View File

@@ -25,57 +25,66 @@ void main() {
// it can't find an executable.
final ProcessRunner processRunner = ProcessRunner(subprocessOutput: false);
expect(
expectAsync1((List<String> commandLine) async {
return processRunner.runProcess(commandLine);
})(<String>['this_executable_better_not_exist_2857632534321']),
throwsA(isA<PreparePackageException>()));
expectAsync1((List<String> commandLine) async {
return processRunner.runProcess(commandLine);
})(<String>['this_executable_better_not_exist_2857632534321']),
throwsA(isA<PreparePackageException>()),
);
await expectLater(
() => processRunner.runProcess(<String>['this_executable_better_not_exist_2857632534321']),
throwsA(isA<PreparePackageException>().having(
(PreparePackageException error) => error.message,
'message',
contains('ProcessException: Failed to find "this_executable_better_not_exist_2857632534321" in the search path'),
)),
throwsA(
isA<PreparePackageException>().having(
(PreparePackageException error) => error.message,
'message',
contains(
'ProcessException: Failed to find "this_executable_better_not_exist_2857632534321" in the search path',
),
),
),
);
});
for (final String platformName in <String>[Platform.macOS, Platform.linux, Platform.windows]) {
final FakePlatform platform = FakePlatform(
operatingSystem: platformName,
environment: <String, String>{
'DEPOT_TOOLS': platformName == Platform.windows ? path.join('D:', 'depot_tools'): '/depot_tools',
'DEPOT_TOOLS':
platformName == Platform.windows ? path.join('D:', 'depot_tools') : '/depot_tools',
},
);
group('ProcessRunner for $platform', () {
test('Returns stdout', () async {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['echo', 'test',],
stdout: 'output',
stderr: 'error',
),
const FakeCommand(command: <String>['echo', 'test'], stdout: 'output', stderr: 'error'),
]);
final ProcessRunner processRunner = ProcessRunner(
subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
subprocessOutput: false,
platform: platform,
processManager: fakeProcessManager,
);
final String output = await processRunner.runProcess(<String>['echo', 'test']);
expect(output, equals('output'));
});
test('Throws on process failure', () async {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['echo', 'test',],
command: <String>['echo', 'test'],
stdout: 'output',
stderr: 'error',
exitCode: -1,
),
]);
final ProcessRunner processRunner = ProcessRunner(
subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
subprocessOutput: false,
platform: platform,
processManager: fakeProcessManager,
);
expect(
expectAsync1((List<String> commandLine) async {
return processRunner.runProcess(commandLine);
})(<String>['echo', 'test']),
throwsA(isA<PreparePackageException>()));
expectAsync1((List<String> commandLine) async {
return processRunner.runProcess(commandLine);
})(<String>['echo', 'test']),
throwsA(isA<PreparePackageException>()),
);
});
});
@@ -116,10 +125,15 @@ void main() {
platform: platform,
httpReader: fakeHttpReader,
);
flutter = path.join(creator.flutterRoot.absolute.path,
'bin', 'flutter');
dart = path.join(creator.flutterRoot.absolute.path,
'bin', 'cache', 'dart-sdk', 'bin', 'dart');
flutter = path.join(creator.flutterRoot.absolute.path, 'bin', 'flutter');
dart = path.join(
creator.flutterRoot.absolute.path,
'bin',
'cache',
'dart-sdk',
'bin',
'dart',
);
});
tearDown(() async {
@@ -128,59 +142,84 @@ void main() {
test('sets PUB_CACHE properly', () async {
final String createBase = path.join(tempDir.absolute.path, 'create_');
final String archiveName = path.join(tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
final String archiveName = path.join(
tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}',
);
processManager.addCommands(convertResults(<String, List<ProcessResult>?>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
'git reset --hard $testRef': null,
'git remote set-url origin https://github.com/flutter/flutter.git': null,
'git gc --prune=now --aggressive': null,
'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
'$flutter --version --machine': <ProcessResult>[
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
],
'$dart --version': <ProcessResult>[
ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
],
if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
'$flutter doctor': null,
'$flutter update-packages': null,
'$flutter precache': null,
'$flutter ide-config': null,
'$flutter create --template=app ${createBase}app': null,
'$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
'git clean -f -x -- **/.packages': null,
'git clean -f -x -- **/.dart_tool/': null,
if (platform.isMacOS) 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': null,
if (platform.isWindows) 'attrib -h .git': null,
if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux) 'tar cJf $archiveName --verbose flutter': null,
}));
processManager.addCommands(
convertResults(<String, List<ProcessResult>?>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
'git reset --hard $testRef': null,
'git remote set-url origin https://github.com/flutter/flutter.git': null,
'git gc --prune=now --aggressive': null,
'git describe --tags --exact-match $testRef': <ProcessResult>[
ProcessResult(0, 0, 'v1.2.3', ''),
],
'$flutter --version --machine': <ProcessResult>[
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
],
'$dart --version': <ProcessResult>[
ProcessResult(
0,
0,
'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"',
'',
),
],
if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
'$flutter doctor': null,
'$flutter update-packages': null,
'$flutter precache': null,
'$flutter ide-config': null,
'$flutter create --template=app ${createBase}app': null,
'$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'$flutter pub cache list': <ProcessResult>[ProcessResult(0, 0, '{"packages":{}}', '')],
'git clean -f -x -- **/.packages': null,
'git clean -f -x -- **/.dart_tool/': null,
if (platform.isMacOS)
'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}':
null,
if (platform.isWindows) 'attrib -h .git': null,
if (platform.isWindows)
'7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS)
'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux)
'tar cJf $archiveName --verbose flutter': null,
}),
);
await creator.initializeRepo();
await creator.createArchive();
});
test('calls the right commands for archive output', () async {
final String createBase = path.join(tempDir.absolute.path, 'create_');
final String archiveName = path.join(tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
final String archiveName = path.join(
tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}',
);
final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
'git reset --hard $testRef': null,
'git remote set-url origin https://github.com/flutter/flutter.git': null,
'git gc --prune=now --aggressive': null,
'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
'git describe --tags --exact-match $testRef': <ProcessResult>[
ProcessResult(0, 0, 'v1.2.3', ''),
],
'$flutter --version --machine': <ProcessResult>[
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
],
'$dart --version': <ProcessResult>[
ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
ProcessResult(
0,
0,
'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"',
'',
),
],
if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
'$flutter doctor': null,
@@ -190,14 +229,19 @@ void main() {
'$flutter create --template=app ${createBase}app': null,
'$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
'$flutter pub cache list': <ProcessResult>[ProcessResult(0, 0, '{"packages":{}}', '')],
'git clean -f -x -- **/.packages': null,
'git clean -f -x -- **/.dart_tool/': null,
if (platform.isMacOS) 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': null,
if (platform.isMacOS)
'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}':
null,
if (platform.isWindows) 'attrib -h .git': null,
if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux) 'tar cJf $archiveName --verbose flutter': null,
if (platform.isWindows)
'7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS)
'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux)
'tar cJf $archiveName --verbose flutter': null,
};
processManager.addCommands(convertResults(calls));
creator = ArchiveCreator(
@@ -217,20 +261,29 @@ void main() {
test('adds the arch name to the archive for non-x64', () async {
final String createBase = path.join(tempDir.absolute.path, 'create_');
final String archiveName = path.join(tempDir.absolute.path,
'flutter_${platformName}_arm64_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
final String archiveName = path.join(
tempDir.absolute.path,
'flutter_${platformName}_arm64_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}',
);
final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
'git reset --hard $testRef': null,
'git remote set-url origin https://github.com/flutter/flutter.git': null,
'git gc --prune=now --aggressive': null,
'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
'git describe --tags --exact-match $testRef': <ProcessResult>[
ProcessResult(0, 0, 'v1.2.3', ''),
],
'$flutter --version --machine': <ProcessResult>[
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
],
'$dart --version': <ProcessResult>[
ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_arm64"', ''),
ProcessResult(
0,
0,
'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_arm64"',
'',
),
],
if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
'$flutter doctor': null,
@@ -240,14 +293,19 @@ void main() {
'$flutter create --template=app ${createBase}app': null,
'$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
'$flutter pub cache list': <ProcessResult>[ProcessResult(0, 0, '{"packages":{}}', '')],
'git clean -f -x -- **/.packages': null,
'git clean -f -x -- **/.dart_tool/': null,
if (platform.isMacOS) 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': null,
if (platform.isMacOS)
'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}':
null,
if (platform.isWindows) 'attrib -h .git': null,
if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux) 'tar cJf $archiveName --verbose flutter': null,
if (platform.isWindows)
'7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS)
'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux)
'tar cJf $archiveName --verbose flutter': null,
};
processManager.addCommands(convertResults(calls));
creator = ArchiveCreator(
@@ -267,8 +325,9 @@ void main() {
test('throws when a command errors out', () async {
final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter':
<ProcessResult>[ProcessResult(0, 0, 'output1', '')],
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': <ProcessResult>[
ProcessResult(0, 0, 'output1', ''),
],
'git reset --hard $testRef': <ProcessResult>[ProcessResult(0, -1, 'output2', '')],
};
processManager.addCommands(convertResults(calls));
@@ -277,20 +336,29 @@ void main() {
test('non-strict mode calls the right commands', () async {
final String createBase = path.join(tempDir.absolute.path, 'create_');
final String archiveName = path.join(tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
final String archiveName = path.join(
tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}',
);
final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
'git reset --hard $testRef': null,
'git remote set-url origin https://github.com/flutter/flutter.git': null,
'git gc --prune=now --aggressive': null,
'git describe --tags --abbrev=0 $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
'git describe --tags --abbrev=0 $testRef': <ProcessResult>[
ProcessResult(0, 0, 'v1.2.3', ''),
],
'$flutter --version --machine': <ProcessResult>[
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
],
'$dart --version': <ProcessResult>[
ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
ProcessResult(
0,
0,
'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"',
'',
),
],
if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
'$flutter doctor': null,
@@ -300,13 +368,16 @@ void main() {
'$flutter create --template=app ${createBase}app': null,
'$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
'$flutter pub cache list': <ProcessResult>[ProcessResult(0, 0, '{"packages":{}}', '')],
'git clean -f -x -- **/.packages': null,
'git clean -f -x -- **/.dart_tool/': null,
if (platform.isWindows) 'attrib -h .git': null,
if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux) 'tar cJf $archiveName --verbose flutter': null,
if (platform.isWindows)
'7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS)
'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux)
'tar cJf $archiveName --verbose flutter': null,
};
processManager.addCommands(convertResults(calls));
creator = ArchiveCreator(
@@ -327,22 +398,44 @@ void main() {
test('fails if binary is not codesigned', () async {
final String createBase = path.join(tempDir.absolute.path, 'create_');
final String archiveName = path.join(tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
final ProcessResult codesignFailure = ProcessResult(1, 1, '', 'code object is not signed at all');
final String binPath = path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
final String archiveName = path.join(
tempDir.absolute.path,
'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}',
);
final ProcessResult codesignFailure = ProcessResult(
1,
1,
'',
'code object is not signed at all',
);
final String binPath = path.join(
tempDir.path,
'flutter',
'bin',
'cache',
'dart-sdk',
'bin',
'dart',
);
final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
'git reset --hard $testRef': null,
'git remote set-url origin https://github.com/flutter/flutter.git': null,
'git gc --prune=now --aggressive': null,
'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
'git describe --tags --exact-match $testRef': <ProcessResult>[
ProcessResult(0, 0, 'v1.2.3', ''),
],
'$flutter --version --machine': <ProcessResult>[
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
],
'$dart --version': <ProcessResult>[
ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
ProcessResult(
0,
0,
'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"',
'',
),
],
if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
'$flutter doctor': null,
@@ -352,14 +445,18 @@ void main() {
'$flutter create --template=app ${createBase}app': null,
'$flutter create --template=package ${createBase}package': null,
'$flutter create --template=plugin ${createBase}plugin': null,
'$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
'$flutter pub cache list': <ProcessResult>[ProcessResult(0, 0, '{"packages":{}}', '')],
'git clean -f -x -- **/.packages': null,
'git clean -f -x -- **/.dart_tool/': null,
if (platform.isMacOS) 'codesign -vvvv --check-notarization $binPath': <ProcessResult>[codesignFailure],
if (platform.isMacOS)
'codesign -vvvv --check-notarization $binPath': <ProcessResult>[codesignFailure],
if (platform.isWindows) 'attrib -h .git': null,
if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
if (platform.isWindows)
'7za a -tzip -mx=9 $archiveName flutter': null
else if (platform.isMacOS)
'zip -r -9 --symlinks $archiveName flutter': null
else if (platform.isLinux)
'tar cJf $archiveName flutter': null,
};
processManager.addCommands(convertResults(calls));
creator = ArchiveCreator(
@@ -377,11 +474,13 @@ void main() {
await expectLater(
() => creator.createArchive(),
throwsA(isA<PreparePackageException>().having(
(PreparePackageException exception) => exception.message,
'message',
contains('The binary $binPath was not codesigned!'),
)),
throwsA(
isA<PreparePackageException>().having(
(PreparePackageException exception) => exception.message,
'message',
contains('The binary $binPath was not codesigned!'),
),
),
);
}, skip: !platform.isMacOS); // [intended] codesign is only available on macOS
});
@@ -390,16 +489,20 @@ void main() {
late FakeProcessManager processManager;
late Directory tempDir;
late FileSystem fs;
final String gsutilCall = platform.isWindows
? 'python3 ${path.join("D:", "depot_tools", "gsutil.py")}'
: 'python3 ${path.join("/", "depot_tools", "gsutil.py")}';
final String gsutilCall =
platform.isWindows
? 'python3 ${path.join("D:", "depot_tools", "gsutil.py")}'
: 'python3 ${path.join("/", "depot_tools", "gsutil.py")}';
final String releasesName = 'releases_$platformName.json';
final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
final String archiveMime = platform.isLinux ? 'application/x-gtar' : 'application/zip';
final String gsArchivePath = 'gs://flutter_infra_release/releases/stable/$platformName/$archiveName';
final String gsArchivePath =
'gs://flutter_infra_release/releases/stable/$platformName/$archiveName';
setUp(() async {
fs = MemoryFileSystem.test(style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix);
fs = MemoryFileSystem.test(
style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix,
);
processManager = FakeProcessManager.list(<FakeCommand>[]);
tempDir = fs.systemTempDirectory.createTempSync('flutter_prepage_package_test.');
});
@@ -459,7 +562,8 @@ void main() {
'$gsutilCall -- rm $gsArchivePath': null,
'$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
'$gsutilCall -- rm $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath':
null,
};
processManager.addCommands(convertResults(calls));
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
@@ -492,7 +596,10 @@ void main() {
expect(contents, contains('"hash": "$testRef"'));
expect(contents, contains('"channel": "stable"'));
expect(contents, contains('"archive": "stable/$platformName/$archiveName"'));
expect(contents, contains('"sha256": "f69f4865f861193a91d1c5544a894167a7137b788d10bac8edbf5d095f45cb4d"'));
expect(
contents,
contains('"sha256": "f69f4865f861193a91d1c5544a894167a7137b788d10bac8edbf5d095f45cb4d"'),
);
// Make sure existing entries are preserved.
expect(contents, contains('"hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"'));
expect(contents, contains('"hash": "b9bd51cc36b706215915711e580851901faebb40"'));
@@ -506,7 +613,9 @@ void main() {
// Make sure the new entry is first (and hopefully it takes less than a
// minute to go from publishArchive above to this line!).
expect(
DateTime.now().difference(DateTime.parse((releases[0] as Map<String, dynamic>)['release_date'] as String)),
DateTime.now().difference(
DateTime.parse((releases[0] as Map<String, dynamic>)['release_date'] as String),
),
lessThan(const Duration(minutes: 1)),
);
const JsonEncoder encoder = JsonEncoder.withIndent(' ');
@@ -561,7 +670,8 @@ void main() {
'$gsutilCall -- rm $gsArchivePath': null,
'$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
'$gsutilCall -- rm $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath':
null,
};
processManager.addCommands(convertResults(calls));
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
@@ -627,7 +737,8 @@ void main() {
'$gsutilCall -- rm $gsArchivePath': null,
'$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
'$gsutilCall -- rm $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath':
null,
};
processManager.addCommands(convertResults(calls));
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
@@ -660,7 +771,6 @@ void main() {
expect((releases['releases'] as List<dynamic>).length, equals(2));
});
test('updates base_url from old bucket to new bucket', () async {
final String archivePath = path.join(tempDir.absolute.path, archiveName);
final String jsonPath = path.join(tempDir.absolute.path, releasesName);
@@ -709,7 +819,8 @@ void main() {
'$gsutilCall -- rm $gsArchivePath': null,
'$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
'$gsutilCall -- rm $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath':
null,
};
processManager.addCommands(convertResults(calls));
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
@@ -739,59 +850,67 @@ void main() {
expect(releaseFile.existsSync(), isTrue);
final String contents = releaseFile.readAsStringSync();
final Map<String, dynamic> jsonData = json.decode(contents) as Map<String, dynamic>;
expect(jsonData['base_url'], 'https://storage.googleapis.com/flutter_infra_release/releases');
expect(
jsonData['base_url'],
'https://storage.googleapis.com/flutter_infra_release/releases',
);
});
test('publishArchive throws if forceUpload is false and artifact already exists on cloud storage', () async {
final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
final ArchivePublisher publisher = ArchivePublisher(
tempDir,
testRef,
Branch.stable,
<String, String>{
'frameworkVersionFromGit': 'v1.2.3',
'dartSdkVersion': '3.2.1',
'dartTargetArch': 'x64',
},
outputFile,
false,
fs: fs,
processManager: processManager,
subprocessOutput: false,
platform: platform,
);
final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
// This process returns 0 because file already exists
'$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 0, '', '')],
};
processManager.addCommands(convertResults(calls));
expect(() async => publisher.publishArchive(), throwsException);
});
test(
'publishArchive throws if forceUpload is false and artifact already exists on cloud storage',
() async {
final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
final ArchivePublisher publisher = ArchivePublisher(
tempDir,
testRef,
Branch.stable,
<String, String>{
'frameworkVersionFromGit': 'v1.2.3',
'dartSdkVersion': '3.2.1',
'dartTargetArch': 'x64',
},
outputFile,
false,
fs: fs,
processManager: processManager,
subprocessOutput: false,
platform: platform,
);
final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
// This process returns 0 because file already exists
'$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 0, '', '')],
};
processManager.addCommands(convertResults(calls));
expect(() async => publisher.publishArchive(), throwsException);
},
);
test('publishArchive does not throw if forceUpload is true and artifact already exists on cloud storage', () async {
final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
final ArchivePublisher publisher = ArchivePublisher(
tempDir,
testRef,
Branch.stable,
<String, String>{
'frameworkVersionFromGit': 'v1.2.3',
'dartSdkVersion': '3.2.1',
'dartTargetArch': 'x64',
},
outputFile,
false,
fs: fs,
processManager: processManager,
subprocessOutput: false,
platform: platform,
);
final String archivePath = path.join(tempDir.absolute.path, archiveName);
final String jsonPath = path.join(tempDir.absolute.path, releasesName);
final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
final String releasesJson = '''
test(
'publishArchive does not throw if forceUpload is true and artifact already exists on cloud storage',
() async {
final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName));
final ArchivePublisher publisher = ArchivePublisher(
tempDir,
testRef,
Branch.stable,
<String, String>{
'frameworkVersionFromGit': 'v1.2.3',
'dartSdkVersion': '3.2.1',
'dartTargetArch': 'x64',
},
outputFile,
false,
fs: fs,
processManager: processManager,
subprocessOutput: false,
platform: platform,
);
final String archivePath = path.join(tempDir.absolute.path, archiveName);
final String jsonPath = path.join(tempDir.absolute.path, releasesName);
final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
final String releasesJson = '''
{
"base_url": "https://storage.googleapis.com/flutter_infra_release/releases",
"current_release": {
@@ -826,20 +945,22 @@ void main() {
]
}
''';
fs.file(jsonPath).writeAsStringSync(releasesJson);
fs.file(archivePath).writeAsStringSync('archive contents');
final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
'$gsutilCall -- cp $gsJsonPath $jsonPath': null,
'$gsutilCall -- rm $gsArchivePath': null,
'$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
'$gsutilCall -- rm $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
};
processManager.addCommands(convertResults(calls));
assert(tempDir.existsSync());
await publisher.generateLocalMetadata();
await publisher.publishArchive(true);
});
fs.file(jsonPath).writeAsStringSync(releasesJson);
fs.file(archivePath).writeAsStringSync('archive contents');
final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
'$gsutilCall -- cp $gsJsonPath $jsonPath': null,
'$gsutilCall -- rm $gsArchivePath': null,
'$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
'$gsutilCall -- rm $gsJsonPath': null,
'$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath':
null,
};
processManager.addCommands(convertResults(calls));
assert(tempDir.existsSync());
await publisher.generateLocalMetadata();
await publisher.publishArchive(true);
},
);
});
}
}
@@ -850,17 +971,17 @@ List<FakeCommand> convertResults(Map<String, List<ProcessResult>?> results) {
final List<ProcessResult>? candidates = results[key];
final List<String> args = key.split(' ');
if (candidates == null) {
commands.add(FakeCommand(
command: args,
));
commands.add(FakeCommand(command: args));
} else {
for (final ProcessResult result in candidates) {
commands.add(FakeCommand(
command: args,
exitCode: result.exitCode,
stderr: result.stderr.toString(),
stdout: result.stdout.toString(),
));
commands.add(
FakeCommand(
command: args,
exitCode: result.exitCode,
stderr: result.stderr.toString(),
stdout: result.stdout.toString(),
),
);
}
}
}

View File

@@ -33,7 +33,7 @@ void main() {
'║ stdout and stderr output:',
'║ test',
'',
'╚═══════════════════════════════════════════════════════════════════════════════'
'╚═══════════════════════════════════════════════════════════════════════════════',
]);
} finally {
print = oldPrint;
@@ -55,7 +55,7 @@ void main() {
startsWith('║ Command: '),
'║ Command exited with exit code 1 but expected zero exit code.',
startsWith('║ Working directory: '),
'╚═══════════════════════════════════════════════════════════════════════════════'
'╚═══════════════════════════════════════════════════════════════════════════════',
]);
} finally {
print = oldPrint;

View File

@@ -28,7 +28,7 @@ void expectExitCode(ProcessResult result, int expectedExitCode) {
'STDOUT:\n'
'${result.stdout}\n'
'STDERR:\n'
'${result.stderr}'
'${result.stderr}',
);
}
}
@@ -86,7 +86,9 @@ void main() {
group('flutter/packages version', () {
final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
final fs.File packagesVersionFile = memoryFileSystem.file(path.join('bin','internal','flutter_packages.version'));
final fs.File packagesVersionFile = memoryFileSystem.file(
path.join('bin', 'internal', 'flutter_packages.version'),
);
const String kSampleHash = '592b5b27431689336fa4c721a099eedf787aeb56';
setUpAll(() {
packagesVersionFile.createSync(recursive: true);
@@ -94,13 +96,21 @@ void main() {
test('commit hash', () async {
packagesVersionFile.writeAsStringSync(kSampleHash);
final String actualHash = await getFlutterPackagesVersion(flutterRoot: flutterRoot, fileSystem: memoryFileSystem, packagesVersionFile: packagesVersionFile.path);
final String actualHash = await getFlutterPackagesVersion(
flutterRoot: flutterRoot,
fileSystem: memoryFileSystem,
packagesVersionFile: packagesVersionFile.path,
);
expect(actualHash, kSampleHash);
});
test('commit hash with newlines', () async {
packagesVersionFile.writeAsStringSync('\n$kSampleHash\n');
final String actualHash = await getFlutterPackagesVersion(flutterRoot: flutterRoot, fileSystem: memoryFileSystem, packagesVersionFile: packagesVersionFile.path);
final String actualHash = await getFlutterPackagesVersion(
flutterRoot: flutterRoot,
fileSystem: memoryFileSystem,
packagesVersionFile: packagesVersionFile.path,
);
expect(actualHash, kSampleHash);
});
});
@@ -109,8 +119,8 @@ void main() {
const ProcessManager processManager = LocalProcessManager();
Future<ProcessResult> runScript([
Map<String, String>? environment,
List<String> otherArgs = const <String>[],
Map<String, String>? environment,
List<String> otherArgs = const <String>[],
]) async {
final String dart = path.absolute(
path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', 'dart'),
@@ -126,40 +136,38 @@ void main() {
test('subshards tests correctly', () async {
// When updating this test, try to pick shard numbers that ensure we're checking
// that unequal test distributions don't miss tests.
ProcessResult result = await runScript(
<String, String>{'SHARD': kTestHarnessShardName, 'SUBSHARD': '1_3'},
);
ProcessResult result = await runScript(<String, String>{
'SHARD': kTestHarnessShardName,
'SUBSHARD': '1_3',
});
expectExitCode(result, 0);
expect(result.stdout, contains('Selecting subshard 1 of 3 (tests 1-3 of 9)'));
result = await runScript(
<String, String>{'SHARD': kTestHarnessShardName, 'SUBSHARD': '3_3'},
);
result = await runScript(<String, String>{'SHARD': kTestHarnessShardName, 'SUBSHARD': '3_3'});
expectExitCode(result, 0);
expect(result.stdout, contains('Selecting subshard 3 of 3 (tests 7-9 of 9)'));
});
test('exits with code 1 when SUBSHARD index greater than total', () async {
final ProcessResult result = await runScript(
<String, String>{'SHARD': kTestHarnessShardName, 'SUBSHARD': '100_99'},
);
final ProcessResult result = await runScript(<String, String>{
'SHARD': kTestHarnessShardName,
'SUBSHARD': '100_99',
});
expectExitCode(result, 1);
expect(result.stdout, contains('Invalid subshard name'));
});
test('exits with code 255 when invalid SUBSHARD name', () async {
final ProcessResult result = await runScript(
<String, String>{'SHARD': kTestHarnessShardName, 'SUBSHARD': 'invalid_name'},
);
final ProcessResult result = await runScript(<String, String>{
'SHARD': kTestHarnessShardName,
'SUBSHARD': 'invalid_name',
});
expectExitCode(result, 255);
expect(result.stdout, contains('Invalid subshard name'));
});
test('--dry-run prints every test that would run', () async {
final ProcessResult result = await runScript(
<String, String> {},
<String>['--dry-run'],
);
final ProcessResult result = await runScript(<String, String>{}, <String>['--dry-run']);
expectExitCode(result, 0);
expect(result.stdout, contains('|> bin/flutter'));
}, testOn: 'posix');
@@ -171,7 +179,7 @@ void main() {
void testSubsharding(int testCount, int subshardCount) {
String failureReason(String reason) {
return 'Subsharding test failed for testCount=$testCount, subshardCount=$subshardCount.\n'
'$reason';
'$reason';
}
final List<int> tests = makeTests(testCount);
@@ -205,13 +213,15 @@ void main() {
extraTestsInThisShard,
isNonNegative,
reason: failureReason(
'Subsharding uneven. Subshard ${i + 1} had too few tests: ${subshards[i].length}'),
'Subsharding uneven. Subshard ${i + 1} had too few tests: ${subshards[i].length}',
),
);
expect(
extraTestsInThisShard,
lessThanOrEqualTo(1),
reason: failureReason(
'Subsharding uneven. Subshard ${i + 1} had too many tests: ${subshards[i].length}'),
'Subsharding uneven. Subshard ${i + 1} had too many tests: ${subshards[i].length}',
),
);
}
}

View File

@@ -6,11 +6,7 @@ import 'dart:convert';
import 'dart:io';
class TestSpecs {
TestSpecs({
required this.path,
required this.startTime,
});
TestSpecs({required this.path, required this.startTime});
final String path;
int startTime;
@@ -25,9 +21,7 @@ class TestSpecs {
int get endTime => _endTime ?? 0;
String toJson() {
return json.encode(
<String, String>{'path': path, 'runtime': milliseconds.toString()}
);
return json.encode(<String, String>{'path': path, 'runtime': milliseconds.toString()});
}
}
@@ -65,26 +59,27 @@ class TestFileReporterResults {
final int suiteID = group['suiteID']! as int;
addMetricDone(suiteID, entry['time']! as int, testSpecs);
} else if (entry.containsKey('error')) {
final String stackTrace = entry.containsKey('stackTrace') ? entry['stackTrace']! as String : '';
final String stackTrace =
entry.containsKey('stackTrace') ? entry['stackTrace']! as String : '';
errors.add('${entry['error']}\n $stackTrace');
} else if (entry.containsKey('success') && entry['success'] == true) {
hasFailedTests = false;
}
}
return TestFileReporterResults._(allTestSpecs: testSpecs, hasFailedTests: hasFailedTests, errors: errors);
return TestFileReporterResults._(
allTestSpecs: testSpecs,
hasFailedTests: hasFailedTests,
errors: errors,
);
}
final Map<int, TestSpecs> allTestSpecs;
final bool hasFailedTests;
final List<String> errors;
static void addTestSpec(Map<String, Object?> suite, int time, Map<int, TestSpecs> allTestSpecs) {
allTestSpecs[suite['id']! as int] = TestSpecs(
path: suite['path']! as String,
startTime: time,
);
allTestSpecs[suite['id']! as int] = TestSpecs(path: suite['path']! as String, startTime: time);
}
static void addMetricDone(int suiteID, int time, Map<int, TestSpecs> allTestSpecs) {

View File

@@ -50,16 +50,16 @@ enum Channel { dev, beta, stable }
String getChannelName(Channel channel) {
return switch (channel) {
Channel.beta => 'beta',
Channel.dev => 'dev',
Channel.beta => 'beta',
Channel.dev => 'dev',
Channel.stable => 'stable',
};
}
Channel fromChannelName(String? name) {
return switch (name) {
'beta' => Channel.beta,
'dev' => Channel.dev,
'beta' => Channel.beta,
'dev' => Channel.dev,
'stable' => Channel.stable,
_ => throw ArgumentError('Invalid channel name.'),
};
@@ -69,16 +69,16 @@ enum PublishedPlatform { linux, macos, windows }
String getPublishedPlatform(PublishedPlatform platform) {
return switch (platform) {
PublishedPlatform.linux => 'linux',
PublishedPlatform.macos => 'macos',
PublishedPlatform.linux => 'linux',
PublishedPlatform.macos => 'macos',
PublishedPlatform.windows => 'windows',
};
}
PublishedPlatform fromPublishedPlatform(String name) {
return switch (name) {
'linux' => PublishedPlatform.linux,
'macos' => PublishedPlatform.macos,
'linux' => PublishedPlatform.linux,
'macos' => PublishedPlatform.macos,
'windows' => PublishedPlatform.windows,
_ => throw ArgumentError('Invalid published platform name.'),
};
@@ -150,42 +150,36 @@ class ProcessRunner {
workingDirectory: workingDirectory.absolute.path,
environment: environment,
);
process.stdout.listen(
(List<int> event) {
output.addAll(event);
if (subprocessOutput) {
stdout.add(event);
}
},
onDone: () async => stdoutComplete.complete(),
);
process.stdout.listen((List<int> event) {
output.addAll(event);
if (subprocessOutput) {
stdout.add(event);
}
}, onDone: () async => stdoutComplete.complete());
if (subprocessOutput) {
process.stderr.listen(
(List<int> event) {
stderr.add(event);
},
onDone: () async => stderrComplete.complete(),
);
process.stderr.listen((List<int> event) {
stderr.add(event);
}, onDone: () async => stderrComplete.complete());
} else {
stderrComplete.complete();
}
} on ProcessException catch (e) {
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
final String message =
'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
'failed with:\n$e';
throw UnpublishException(message);
} on ArgumentError catch (e) {
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
final String message =
'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
'failed with:\n$e';
throw UnpublishException(message);
}
final int exitCode = await allComplete();
if (exitCode != 0 && !failOk) {
final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed';
throw UnpublishException(
message,
ProcessResult(0, exitCode, null, 'returned $exitCode'),
);
final String message =
'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed';
throw UnpublishException(message, ProcessResult(0, exitCode, null, 'returned $exitCode'));
}
return utf8.decoder.convert(output).trim();
}
@@ -200,12 +194,12 @@ class ArchiveUnpublisher {
this.confirmed = false,
ProcessManager? processManager,
bool subprocessOutput = true,
}) : assert(revisionsBeingRemoved.length == 40),
metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}',
_processRunner = ProcessRunner(
processManager: processManager ?? const LocalProcessManager(),
subprocessOutput: subprocessOutput,
);
}) : assert(revisionsBeingRemoved.length == 40),
metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}',
_processRunner = ProcessRunner(
processManager: processManager ?? const LocalProcessManager(),
subprocessOutput: subprocessOutput,
);
final PublishedPlatform platform;
final String metadataGsPath;
@@ -214,17 +208,23 @@ class ArchiveUnpublisher {
final bool confirmed;
final Directory tempDir;
final ProcessRunner _processRunner;
static String getMetadataFilename(PublishedPlatform platform) => 'releases_${getPublishedPlatform(platform)}.json';
static String getMetadataFilename(PublishedPlatform platform) =>
'releases_${getPublishedPlatform(platform)}.json';
/// Remove the archive from Google Storage.
Future<void> unpublishArchive() async {
final Map<String, dynamic> jsonData = await _loadMetadata();
final List<Map<String, String>> releases = (jsonData['releases'] as List<dynamic>).map<Map<String, String>>((dynamic entry) {
final Map<String, dynamic> mapEntry = entry as Map<String, dynamic>;
return mapEntry.cast<String, String>();
}).toList();
final List<Map<String, String>> releases =
(jsonData['releases'] as List<dynamic>).map<Map<String, String>>((dynamic entry) {
final Map<String, dynamic> mapEntry = entry as Map<String, dynamic>;
return mapEntry.cast<String, String>();
}).toList();
final Map<Channel, Map<String, String>> paths = await _getArchivePaths(releases);
releases.removeWhere((Map<String, String> value) => revisionsBeingRemoved.contains(value['hash']) && channels.contains(fromChannelName(value['channel'])));
releases.removeWhere(
(Map<String, String> value) =>
revisionsBeingRemoved.contains(value['hash']) &&
channels.contains(fromChannelName(value['channel'])),
);
releases.sort((Map<String, String> a, Map<String, String> b) {
final DateTime aDate = DateTime.parse(a['release_date']!);
final DateTime bDate = DateTime.parse(b['release_date']!);
@@ -232,22 +232,29 @@ class ArchiveUnpublisher {
});
jsonData['releases'] = releases;
for (final Channel channel in channels) {
if (!revisionsBeingRemoved.contains((jsonData['current_release'] as Map<String, dynamic>)[getChannelName(channel)])) {
if (!revisionsBeingRemoved.contains(
(jsonData['current_release'] as Map<String, dynamic>)[getChannelName(channel)],
)) {
// Don't replace the current release if it's not one of the revisions we're removing.
continue;
}
final Map<String, String> replacementRelease = releases.firstWhere((Map<String, String> value) => value['channel'] == getChannelName(channel));
(jsonData['current_release'] as Map<String, dynamic>)[getChannelName(channel)] = replacementRelease['hash'];
final Map<String, String> replacementRelease = releases.firstWhere(
(Map<String, String> value) => value['channel'] == getChannelName(channel),
);
(jsonData['current_release'] as Map<String, dynamic>)[getChannelName(channel)] =
replacementRelease['hash'];
print(
'${confirmed ? 'Reverting' : 'Would revert'} current ${getChannelName(channel)} '
'${getPublishedPlatform(platform)} release to ${replacementRelease['hash']} (version ${replacementRelease['version']}).'
'${getPublishedPlatform(platform)} release to ${replacementRelease['hash']} (version ${replacementRelease['version']}).',
);
}
await _cloudRemoveArchive(paths);
await _updateMetadata(jsonData);
}
Future<Map<Channel, Map<String, String>>> _getArchivePaths(List<Map<String, String>> releases) async {
Future<Map<Channel, Map<String, String>>> _getArchivePaths(
List<Map<String, String>> releases,
) async {
final Set<String> hashes = <String>{};
final Map<Channel, Map<String, String>> paths = <Channel, Map<String, String>>{};
for (final Map<String, String> revision in releases) {
@@ -259,18 +266,20 @@ class ArchiveUnpublisher {
paths[channel]![hash] = revision['archive']!;
}
}
final Set<String> missingRevisions = revisionsBeingRemoved.difference(hashes.intersection(revisionsBeingRemoved));
final Set<String> missingRevisions = revisionsBeingRemoved.difference(
hashes.intersection(revisionsBeingRemoved),
);
if (missingRevisions.isNotEmpty) {
final bool plural = missingRevisions.length > 1;
throw UnpublishException('Revision${plural ? 's' : ''} $missingRevisions ${plural ? 'are' : 'is'} not present in the server metadata.');
throw UnpublishException(
'Revision${plural ? 's' : ''} $missingRevisions ${plural ? 'are' : 'is'} not present in the server metadata.',
);
}
return paths;
}
Future<Map<String, dynamic>> _loadMetadata() async {
final File metadataFile = File(
path.join(tempDir.absolute.path, getMetadataFilename(platform)),
);
final File metadataFile = File(path.join(tempDir.absolute.path, getMetadataFilename(platform)));
// Always run this, even in dry runs.
await _runGsUtil(<String>['cp', metadataGsPath, metadataFile.absolute.path], confirm: true);
final String currentMetadata = metadataFile.readAsStringSync();
@@ -293,12 +302,12 @@ class ArchiveUnpublisher {
// Windows wants to echo the commands that execute in gsutil.bat to the
// stdout when we do that. So, we copy the file locally and then read it
// back in.
final File metadataFile = File(
path.join(tempDir.absolute.path, getMetadataFilename(platform)),
);
final File metadataFile = File(path.join(tempDir.absolute.path, getMetadataFilename(platform)));
const JsonEncoder encoder = JsonEncoder.withIndent(' ');
metadataFile.writeAsStringSync(encoder.convert(jsonData));
print('${confirmed ? 'Overwriting' : 'Would overwrite'} $metadataGsPath with contents of ${metadataFile.absolute.path}');
print(
'${confirmed ? 'Overwriting' : 'Would overwrite'} $metadataGsPath with contents of ${metadataFile.absolute.path}',
);
await _cloudReplaceDest(metadataFile.absolute.path, metadataGsPath);
}
@@ -310,11 +319,7 @@ class ArchiveUnpublisher {
}) async {
final List<String> command = <String>['gsutil', '--', ...args];
if (confirm) {
return _processRunner.runProcess(
command,
workingDirectory: workingDirectory,
failOk: failOk,
);
return _processRunner.runProcess(command, workingDirectory: workingDirectory, failOk: failOk);
} else {
print('Would run: ${command.join(' ')}');
return '';
@@ -372,25 +377,34 @@ void _printBanner(String message) {
/// Prepares a flutter git repo to be removed from the published cloud storage.
Future<void> main(List<String> rawArguments) async {
final List<String> allowedChannelValues = Channel.values.map<String>((Channel channel) => getChannelName(channel)).toList();
final List<String> allowedPlatformNames = PublishedPlatform.values.map<String>((PublishedPlatform platform) => getPublishedPlatform(platform)).toList();
final List<String> allowedChannelValues =
Channel.values.map<String>((Channel channel) => getChannelName(channel)).toList();
final List<String> allowedPlatformNames =
PublishedPlatform.values
.map<String>((PublishedPlatform platform) => getPublishedPlatform(platform))
.toList();
final ArgParser argParser = ArgParser();
argParser.addOption(
'temp_dir',
help: 'A location where temporary files may be written. Defaults to a '
help:
'A location where temporary files may be written. Defaults to a '
'directory in the system temp folder. If a temp_dir is not '
'specified, then by default a generated temporary directory will be '
'created, used, and removed automatically when the script exits.',
);
argParser.addMultiOption('revision',
help: 'The Flutter git repo revisions to remove from the published site. '
'Must be full 40-character hashes. More than one may be specified, '
'either by giving the option more than once, or by giving a comma '
'separated list. Required.');
argParser.addMultiOption(
'revision',
help:
'The Flutter git repo revisions to remove from the published site. '
'Must be full 40-character hashes. More than one may be specified, '
'either by giving the option more than once, or by giving a comma '
'separated list. Required.',
);
argParser.addMultiOption(
'channel',
allowed: allowedChannelValues,
help: 'The Flutter channels to remove the archives corresponding to the '
help:
'The Flutter channels to remove the archives corresponding to the '
'revisions given with --revision. More than one may be specified, '
'either by giving the option more than once, or by giving a '
'comma separated list. If not specified, then the archives from all '
@@ -399,24 +413,22 @@ Future<void> main(List<String> rawArguments) async {
argParser.addMultiOption(
'platform',
allowed: allowedPlatformNames,
help: 'The Flutter platforms to remove the archive from. May specify more '
help:
'The Flutter platforms to remove the archive from. May specify more '
'than one, either by giving the option more than once, or by giving a '
'comma separated list. If not specified, then the archives from all '
'platforms that a revision appears in will be removed.',
);
argParser.addFlag(
'confirm',
help: 'If set, will actually remove the archive from Google Cloud Storage '
help:
'If set, will actually remove the archive from Google Cloud Storage '
'upon successful execution of this script. Published archives will be '
'removed from this directory: $baseUrl$releaseFolder. This option '
'must be set to perform any action on the server, otherwise only a dry '
'run is performed.',
);
argParser.addFlag(
'help',
negatable: false,
help: 'Print help for this command.',
);
argParser.addFlag('help', negatable: false, help: 'Print help for this command.');
final ArgResults parsedArguments = argParser.parse(rawArguments);
@@ -437,7 +449,9 @@ Future<void> main(List<String> rawArguments) async {
}
for (final String revision in revisions) {
if (revision.length != 40) {
errorExit('Invalid argument: --revision "$revision" must be the entire hash, not just a prefix.');
errorExit(
'Invalid argument: --revision "$revision" must be the entire hash, not just a prefix.',
);
}
if (revision.contains(RegExp(r'[^a-fA-F0-9]'))) {
errorExit('Invalid argument: --revision "$revision" contains non-hex characters.');
@@ -458,15 +472,21 @@ Future<void> main(List<String> rawArguments) async {
}
if (!(parsedArguments['confirm'] as bool)) {
_printBanner('This will be just a dry run. To actually perform the changes below, re-run with --confirm argument.');
_printBanner(
'This will be just a dry run. To actually perform the changes below, re-run with --confirm argument.',
);
}
final List<String> channelArg = parsedArguments['channel'] as List<String>;
final List<String> channelOptions = channelArg.isNotEmpty ? channelArg : allowedChannelValues;
final Set<Channel> channels = channelOptions.map<Channel>((String value) => fromChannelName(value)).toSet();
final Set<Channel> channels =
channelOptions.map<Channel>((String value) => fromChannelName(value)).toSet();
final List<String> platformArg = parsedArguments['platform'] as List<String>;
final List<String> platformOptions = platformArg.isNotEmpty ? platformArg : allowedPlatformNames;
final List<PublishedPlatform> platforms = platformOptions.map<PublishedPlatform>((String value) => fromPublishedPlatform(value)).toList();
final List<PublishedPlatform> platforms =
platformOptions
.map<PublishedPlatform>((String value) => fromPublishedPlatform(value))
.toList();
int exitCode = 0;
late String message;
late String stack;
@@ -497,7 +517,9 @@ Future<void> main(List<String> rawArguments) async {
errorExit('$message\n$stack', exitCode: exitCode);
}
if (!(parsedArguments['confirm'] as bool)) {
_printBanner('This was just a dry run. To actually perform the above changes, re-run with --confirm argument.');
_printBanner(
'This was just a dry run. To actually perform the above changes, re-run with --confirm argument.',
);
}
exit(0);
}

View File

@@ -11,6 +11,7 @@ import 'dart:math' as math;
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:collection/collection.dart';
import 'package:file/file.dart' as fs;
import 'package:file/local.dart';
@@ -30,20 +31,27 @@ typedef ShardRunner = Future<void> Function();
/// appropriate error message.
typedef OutputChecker = String? Function(CommandResult);
const Duration _quietTimeout = Duration(minutes: 10); // how long the output should be hidden between calls to printProgress before just being verbose
const Duration _quietTimeout = Duration(
minutes: 10,
); // how long the output should be hidden between calls to printProgress before just being verbose
// If running from LUCI set to False.
final bool isLuci = Platform.environment['LUCI_CI'] == 'True';
final bool isLuci = Platform.environment['LUCI_CI'] == 'True';
final bool hasColor = stdout.supportsAnsiEscapes && !isLuci;
final bool _isRandomizationOff = bool.tryParse(Platform.environment['TEST_RANDOMIZATION_OFF'] ?? '') ?? false;
final bool _isRandomizationOff =
bool.tryParse(Platform.environment['TEST_RANDOMIZATION_OFF'] ?? '') ?? false;
final String bold = hasColor ? '\x1B[1m' : ''; // shard titles
final String red = hasColor ? '\x1B[31m' : ''; // errors
final String green = hasColor ? '\x1B[32m' : ''; // section titles, commands
final String yellow = hasColor ? '\x1B[33m' : ''; // indications that a test was skipped (usually renders orange or brown)
final String yellow =
hasColor
? '\x1B[33m'
: ''; // indications that a test was skipped (usually renders orange or brown)
final String cyan = hasColor ? '\x1B[36m' : ''; // paths
final String reverse = hasColor ? '\x1B[7m' : ''; // clocks
final String gray = hasColor ? '\x1B[30m' : ''; // subtle decorative items (usually renders as dark gray)
final String gray =
hasColor ? '\x1B[30m' : ''; // subtle decorative items (usually renders as dark gray)
final String white = hasColor ? '\x1B[37m' : ''; // last log line (usually renders as light gray)
final String reset = hasColor ? '\x1B[0m' : '';
@@ -65,7 +73,7 @@ const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME';
/// Environment variables to override the local engine when running `pub test`,
/// if such flags are provided to `test.dart`.
final Map<String,String> localEngineEnv = <String, String>{};
final Map<String, String> localEngineEnv = <String, String>{};
/// The arguments to pass to `flutter test` (typically the local engine
/// configuration) -- prefilled with the arguments passed to test.dart.
@@ -88,6 +96,7 @@ void enableDryRun() {
}
_dryRun = true;
}
bool? _dryRun;
const int kESC = 0x1B;
@@ -109,10 +118,10 @@ String get redLine {
String get clock {
final DateTime now = DateTime.now();
return '$reverse'
'${now.hour.toString().padLeft(2, "0")}:'
'${now.minute.toString().padLeft(2, "0")}:'
'${now.second.toString().padLeft(2, "0")}'
'$reset';
'${now.hour.toString().padLeft(2, "0")}:'
'${now.minute.toString().padLeft(2, "0")}:'
'${now.second.toString().padLeft(2, "0")}'
'$reset';
}
String prettyPrintDuration(Duration duration) {
@@ -251,21 +260,29 @@ void _printQuietly(Object? message) {
int index = start;
int length = 0;
while (index < line.length && length < stdout.terminalColumns) {
if (line.codeUnitAt(index) == kESC) { // 0x1B
if (line.codeUnitAt(index) == kESC) {
// 0x1B
index += 1;
if (index < line.length && line.codeUnitAt(index) == kOpenSquareBracket) { // 0x5B, [
if (index < line.length && line.codeUnitAt(index) == kOpenSquareBracket) {
// 0x5B, [
// That was the start of a CSI sequence.
index += 1;
while (index < line.length && line.codeUnitAt(index) >= kCSIParameterRangeStart
&& line.codeUnitAt(index) <= kCSIParameterRangeEnd) { // 0x30..0x3F
while (index < line.length &&
line.codeUnitAt(index) >= kCSIParameterRangeStart &&
line.codeUnitAt(index) <= kCSIParameterRangeEnd) {
// 0x30..0x3F
index += 1; // ...parameter bytes...
}
while (index < line.length && line.codeUnitAt(index) >= kCSIIntermediateRangeStart
&& line.codeUnitAt(index) <= kCSIIntermediateRangeEnd) { // 0x20..0x2F
while (index < line.length &&
line.codeUnitAt(index) >= kCSIIntermediateRangeStart &&
line.codeUnitAt(index) <= kCSIIntermediateRangeEnd) {
// 0x20..0x2F
index += 1; // ...intermediate bytes...
}
if (index < line.length && line.codeUnitAt(index) >= kCSIFinalRangeStart
&& line.codeUnitAt(index) <= kCSIFinalRangeEnd) { // 0x40..0x7E
if (index < line.length &&
line.codeUnitAt(index) >= kCSIFinalRangeStart &&
line.codeUnitAt(index) <= kCSIFinalRangeEnd) {
// 0x40..0x7E
index += 1; // ...final byte.
}
}
@@ -324,6 +341,50 @@ String locationInFile(ResolvedUnitResult unit, AstNode node, String workingDirec
return '${path.relative(path.relative(unit.path, from: workingDirectory))}:${unit.lineInfo.getLocation(node.offset).lineNumber}';
}
/// Whether the given [AstNode] within the `compilationUnit` is under the effect
/// of an inline ignore directive described by `ignoreDirectivePattern`.
///
/// The `compilationUnit` parameter is the parsed dart file containing the given
/// [AstNode]. The `ignoreDirectivePattern` is a [Pattern] that should precisely
/// match the ignore directive of interest (including the slashes, example:
/// `// flutter_ignore: deprecation_syntax`).
///
/// The implementation assumes the `ignoreDirectivePattern` matches no more than
/// one line. It searches for the given `ignoreDirectivePattern` in the
/// `compilationUnit`, that either starts the line above the given `node`, or
/// appears after `node` but on the same line, such that the ignore directive
/// works the same way as dart's "ignore" comment: it can either be added above
/// or after the line that needs to be exemped.
bool hasInlineIgnore(
AstNode node,
ParseStringResult compilationUnit,
Pattern ignoreDirectivePattern,
) {
final LineInfo lineInfo = compilationUnit.lineInfo;
// In case the node has multiple lines, match from its start offset.
final String textAfterNode = compilationUnit.content.substring(
node.offset,
// This assumes every line ends with a newline character (including the last
// line) and the new line character is not included to match the given pattern.
lineInfo.getOffsetOfLineAfter(node.offset) - 1,
);
if (textAfterNode.contains(ignoreDirectivePattern)) {
return true;
}
// The lineNumber getter uses one-based index while everything else uses zero-based index.
final int lineNumber = lineInfo.getLocation(node.offset).lineNumber - 1;
if (lineNumber <= 0) {
return false;
}
return compilationUnit.content
.substring(
lineInfo.getOffsetOfLine(lineNumber - 1),
lineInfo.getOffsetOfLine(lineNumber) - 1, // Excludes LF, see the comment above.
)
.trimLeft()
.contains(ignoreDirectivePattern);
}
// The seed used to shuffle tests. If not passed with
// --test-randomize-ordering-seed=<seed> on the command line, it will be set the
// first time it is accessed. Pass zero to turn off shuffling.
@@ -353,7 +414,8 @@ String get shuffleSeed {
// properly when overriding the local engine (for example, because some platform
// dependent targets are only built on some engines).
// See https://github.com/flutter/flutter/issues/72368
Future<void> runDartTest(String workingDirectory, {
Future<void> runDartTest(
String workingDirectory, {
List<String>? testPaths,
bool enableFlutterToolAsserts = true,
bool useBuildRunner = false,
@@ -397,26 +459,18 @@ Future<void> runDartTest(String workingDirectory, {
'--file-reporter=json:${metricFile.path}',
if (shuffleTests) '--test-randomize-ordering-seed=$shuffleSeed',
'-j$cpus',
if (!hasColor)
'--no-color',
if (coverage != null)
'--coverage=$coverage',
if (perTestTimeout != null)
'--timeout=${perTestTimeout.inMilliseconds}ms',
if (runSkipped)
'--run-skipped',
if (tags != null)
...tags.map((String t) => '--tags=$t'),
if (!hasColor) '--no-color',
if (coverage != null) '--coverage=$coverage',
if (perTestTimeout != null) '--timeout=${perTestTimeout.inMilliseconds}ms',
if (runSkipped) '--run-skipped',
if (tags != null) ...tags.map((String t) => '--tags=$t'),
if (testPaths != null)
for (final String testPath in testPaths)
testPath,
for (final String testPath in testPaths) testPath,
];
final Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': flutterRoot,
if (includeLocalEngineEnv)
...localEngineEnv,
if (Directory(pubCache).existsSync())
'PUB_CACHE': pubCache,
if (includeLocalEngineEnv) ...localEngineEnv,
if (Directory(pubCache).existsSync()) 'PUB_CACHE': pubCache,
};
if (enableFlutterToolAsserts) {
adjustEnvironmentToEnableFlutterAsserts(environment);
@@ -439,7 +493,9 @@ Future<void> runDartTest(String workingDirectory, {
return;
}
final TestFileReporterResults test = TestFileReporterResults.fromFile(metricFile); // --file-reporter name
final TestFileReporterResults test = TestFileReporterResults.fromFile(
metricFile,
); // --file-reporter name
final File info = fileSystem.file(path.join(flutterRoot, 'error.log'));
info.writeAsStringSync(json.encode(test.errors));
@@ -452,8 +508,7 @@ Future<void> runDartTest(String workingDirectory, {
}
if (testList.isNotEmpty) {
final String testJson = json.encode(testList);
final File testResults = fileSystem.file(
path.join(flutterRoot, 'test_results.json'));
final File testResults = fileSystem.file(path.join(flutterRoot, 'test_results.json'));
testResults.writeAsStringSync(testJson);
}
} on fs.FileSystemException catch (e) {
@@ -467,7 +522,8 @@ Future<void> runDartTest(String workingDirectory, {
metricFile.deleteSync();
}
Future<void> runFlutterTest(String workingDirectory, {
Future<void> runFlutterTest(
String workingDirectory, {
String? script,
bool expectFailure = false,
bool printOutput = true,
@@ -478,7 +534,10 @@ Future<void> runFlutterTest(String workingDirectory, {
bool shuffleTests = true,
bool fatalWarnings = true,
}) async {
assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both');
assert(
!printOutput || outputChecker == null,
'Output either can be printed or checked but not both',
);
final List<String> tags = <String>[];
// Recipe-configured reduced test shards will only execute tests with the
@@ -508,8 +567,7 @@ Future<void> runFlutterTest(String workingDirectory, {
'${red}Could not find test$reset: $green$fullScriptPath$reset',
'Working directory: $cyan$workingDirectory$reset',
'Script: $green$script$reset',
if (!printOutput)
'This is one of the tests that does not normally print output.',
if (!printOutput) 'This is one of the tests that does not normally print output.',
]);
return;
}
@@ -518,9 +576,8 @@ Future<void> runFlutterTest(String workingDirectory, {
args.addAll(tests);
final OutputMode outputMode = outputChecker == null && printOutput
? OutputMode.print
: OutputMode.capture;
final OutputMode outputMode =
outputChecker == null && printOutput ? OutputMode.print : OutputMode.capture;
final CommandResult result = await runCommand(
flutter,
@@ -558,8 +615,10 @@ void adjustEnvironmentToEnableFlutterAsserts(Map<String, String> environment) {
environment['FLUTTER_TOOL_ARGS'] = toolsArgs.trim();
}
Future<void> selectShard(Map<String, ShardRunner> shards) => _runFromList(shards, kShardKey, 'shard', 0);
Future<void> selectSubshard(Map<String, ShardRunner> subshards) => _runFromList(subshards, kSubshardKey, 'subshard', 1);
Future<void> selectShard(Map<String, ShardRunner> shards) =>
_runFromList(shards, kShardKey, 'shard', 0);
Future<void> selectSubshard(Map<String, ShardRunner> subshards) =>
_runFromList(subshards, kSubshardKey, 'subshard', 1);
Future<void> runShardRunnerIndexOfTotalSubshard(List<ShardRunner> tests) async {
final List<ShardRunner> sublist = selectIndexOfTotalSubshard<ShardRunner>(tests);
@@ -567,6 +626,7 @@ Future<void> runShardRunnerIndexOfTotalSubshard(List<ShardRunner> tests) async {
await test();
}
}
/// Parse (one-)index/total-named subshards from environment variable SUBSHARD
/// and equally distribute [tests] between them.
/// The format of SUBSHARD is "{index}_{total number of shards}".
@@ -639,14 +699,20 @@ List<T> selectIndexOfTotalSubshard<T>(List<T> tests, {String subshardKey = kSubs
// Lastly, compute the indices of the items in buckets[index].
// We derive this from the toal number items in previous buckets and the number
// of items in this bucket.
final int numberOfItemsInPreviousBuckets = subShardIndex == 0 ? 0 : buckets.sublist(0, subShardIndex - 1).sum;
final int numberOfItemsInPreviousBuckets =
subShardIndex == 0 ? 0 : buckets.sublist(0, subShardIndex - 1).sum;
final int start = numberOfItemsInPreviousBuckets;
final int end = start + buckets[subShardIndex - 1];
return (start, end);
}
Future<void> _runFromList(Map<String, ShardRunner> items, String key, String name, int positionInTaskName) async {
Future<void> _runFromList(
Map<String, ShardRunner> items,
String key,
String name,
int positionInTaskName,
) async {
try {
String? item = Platform.environment[key];
if (item == null && Platform.environment.containsKey(CIRRUS_TASK_NAME)) {
@@ -683,8 +749,7 @@ Future<void> _runFromList(Map<String, ShardRunner> items, String key, String nam
/// Returns null if the contents are good. Returns a string if they are bad.
/// The string is an error message.
Future<String?> verifyVersion(File file) async {
final RegExp pattern = RegExp(
r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$');
final RegExp pattern = RegExp(r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$');
if (!file.existsSync()) {
return 'The version logic failed to create the Flutter version file.';
}