forked from firka/flutter
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:
committed by
GitHub
parent
8e0993eda8
commit
5491c8c146
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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}.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)');
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>[],
|
||||
|
||||
@@ -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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'} '
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
// ^
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user