mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This allows analyze tests to interpret inline comments in a specific format as error message expectations. e.g.,: Adding `// ERROR: this is bad because A, B, C, D` to line 50 in `file.ext` would match an error message that looks like `../path/path/file.ext: 50: this is bad because A, B, C, D` ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
409 lines
17 KiB
Dart
409 lines
17 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import '../analyze.dart';
|
|
import '../custom_rules/analyze.dart';
|
|
import '../custom_rules/no_double_clamp.dart';
|
|
import '../custom_rules/no_stop_watches.dart';
|
|
import '../custom_rules/render_box_intrinsics.dart';
|
|
import '../utils.dart';
|
|
import 'common.dart';
|
|
|
|
typedef AsyncVoidCallback = Future<void> Function();
|
|
|
|
Future<String> capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async {
|
|
final StringBuffer buffer = StringBuffer();
|
|
final PrintCallback oldPrint = print;
|
|
try {
|
|
print = (Object? line) {
|
|
buffer.writeln(line);
|
|
};
|
|
await callback();
|
|
expect(
|
|
hasError,
|
|
shouldHaveErrors,
|
|
reason:
|
|
buffer.isEmpty
|
|
? '(No output to report.)'
|
|
: hasError
|
|
? 'Unexpected errors:\n$buffer'
|
|
: 'Unexpected success:\n$buffer',
|
|
);
|
|
} finally {
|
|
print = oldPrint;
|
|
resetErrorStatus();
|
|
}
|
|
if (stdout.supportsAnsiEscapes) {
|
|
// Remove ANSI escapes when this test is running on a terminal.
|
|
return buffer.toString().replaceAll(RegExp(r'(\x9B|\x1B\[)[0-?]{1,3}[ -/]*[@-~]'), '');
|
|
} else {
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
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 testGenDefaultsPath = path.join('test', 'analyze-gen-defaults');
|
|
|
|
test('matchesErrorsInFile matcher basic test', () async {
|
|
final String result = await capture(() async {
|
|
foundError(<String>[
|
|
'meta.dart:5: error #1',
|
|
'meta.dart:5: error #2',
|
|
'meta.dart:6: error #3',
|
|
'',
|
|
'Error summary',
|
|
]);
|
|
}, shouldHaveErrors: true);
|
|
final File fixture = File(path.join(testRootPath, 'packages', 'foo', 'meta.dart'));
|
|
expect(result, matchesErrorsInFile(fixture, endsWith: <String>['', 'Error summary']));
|
|
});
|
|
|
|
test('analyze.dart - verifyDeprecations', () async {
|
|
final String result = await capture(
|
|
() => verifyDeprecations(testRootPath, minimumMatches: 2),
|
|
shouldHaveErrors: true,
|
|
);
|
|
final File fixture = File(path.join(testRootPath, 'packages', 'foo', 'deprecation.dart'));
|
|
expect(
|
|
result,
|
|
matchesErrorsInFile(
|
|
fixture,
|
|
endsWith: <String>[
|
|
'See: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes',
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
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╞════════════════════════════════════════════════════════════════════',
|
|
);
|
|
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 - 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,
|
|
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
|
|
'║ The following file does not have the right license header for dart files:\n'
|
|
'║ $file\n'
|
|
'║ The expected license header is:\n'
|
|
'║ // Copyright 2014 The Flutter Authors. All rights reserved.\n'
|
|
'║ // 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',
|
|
);
|
|
});
|
|
|
|
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',
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - verifyRepositoryLinks', () async {
|
|
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 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,
|
|
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
|
|
'$lines\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,
|
|
);
|
|
if (!Platform.isWindows) {
|
|
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'
|
|
'║ must not be checked into this repository. This is because we are very sensitive to the\n'
|
|
'║ size of the repository as it is distributed to all our developers. If you have a binary\n'
|
|
'║ to which you need access, you should consider how to fetch it from another repository;\n'
|
|
'║ 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',
|
|
);
|
|
}
|
|
});
|
|
|
|
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?'),
|
|
);
|
|
});
|
|
|
|
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),
|
|
},
|
|
),
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - verifyNullInitializedDebugExpensiveFields', () async {
|
|
final String result = await capture(
|
|
() => verifyNullInitializedDebugExpensiveFields(testRootPath, minimumMatches: 1),
|
|
shouldHaveErrors: true,
|
|
);
|
|
|
|
final File fixture = File(path.join(testRootPath, 'packages', 'flutter', 'lib', 'bar.dart'));
|
|
expect(
|
|
result,
|
|
matchesErrorsInFile(
|
|
fixture,
|
|
endsWith: <String>[
|
|
'',
|
|
'Fields annotated with @_debugOnly must null initialize,',
|
|
'to ensure both the field and initializer are removed from profile/release mode.',
|
|
'These fields should be written as:',
|
|
'field = kDebugMode ? <DebugValue> : null;',
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - verifyTabooDocumentation', () async {
|
|
final String result = await capture(
|
|
() => verifyTabooDocumentation(testRootPath, minimumMatches: 1),
|
|
shouldHaveErrors: true,
|
|
);
|
|
|
|
final File fixture = File(
|
|
path.join(testRootPath, 'packages', 'flutter', 'lib', 'taboo_words.dart'),
|
|
);
|
|
expect(
|
|
result,
|
|
matchesErrorsInFile(
|
|
fixture,
|
|
endsWith: <String>[
|
|
'',
|
|
'Avoid the word "simply" in documentation. See https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#use-the-passive-voice-recommend-do-not-require-never-say-things-are-simple for details.',
|
|
'In many cases these words can be omitted without loss of generality; in other cases it may require a bit of rewording to avoid implying that the task is simple.',
|
|
'Similarly, avoid using "note:" or the phrase "note that". See https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#avoid-empty-prose for details.',
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - clampDouble', () async {
|
|
final String result = await capture(
|
|
() => analyzeWithRules(
|
|
testRootPath,
|
|
<AnalyzeRule>[noDoubleClamp],
|
|
includePaths: <String>['packages/flutter/lib'],
|
|
),
|
|
shouldHaveErrors: true,
|
|
);
|
|
|
|
final File fixture = File(
|
|
path.join(testRootPath, 'packages', 'flutter', 'lib', 'double_clamp.dart'),
|
|
);
|
|
expect(
|
|
result,
|
|
matchesErrorsInFile(
|
|
fixture,
|
|
endsWith: <String>[
|
|
'', // empty line before the last sentence.
|
|
'For performance reasons, we use a custom "clampDouble" function instead of using "double.clamp".',
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - stopwatch', () async {
|
|
final String result = await capture(
|
|
() => analyzeWithRules(
|
|
testRootPath,
|
|
<AnalyzeRule>[noStopwatches],
|
|
includePaths: <String>['packages/flutter/lib'],
|
|
),
|
|
shouldHaveErrors: true,
|
|
);
|
|
|
|
final File fixture = File(
|
|
path.join(testRootPath, 'packages', 'flutter', 'lib', 'stopwatch.dart'),
|
|
);
|
|
expect(
|
|
result,
|
|
matchesErrorsInFile(
|
|
fixture,
|
|
endsWith: <String>[
|
|
'',
|
|
'Stopwatches introduce flakes by falling out of sync with the FakeAsync used in testing.',
|
|
'A Stopwatch that stays in sync with FakeAsync is available through the Gesture or Test bindings, through samplingClock.',
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - RenderBox intrinsics', () async {
|
|
final String result = await capture(
|
|
() => analyzeWithRules(
|
|
testRootPath,
|
|
<AnalyzeRule>[renderBoxIntrinsicCalculation],
|
|
includePaths: <String>['packages/flutter/lib'],
|
|
),
|
|
shouldHaveErrors: true,
|
|
);
|
|
final File fixture = File(
|
|
path.join(testRootPath, 'packages', 'flutter', 'lib', 'renderbox_intrinsics.dart'),
|
|
);
|
|
expect(
|
|
result,
|
|
matchesErrorsInFile(
|
|
fixture,
|
|
endsWith: <String>[
|
|
'',
|
|
'Typically the get* methods should be used to obtain the intrinsics of a RenderBox.',
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test('analyze.dart - verifyMaterialFilesAreUpToDateWithTemplateFiles', () async {
|
|
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');
|
|
const String errorStart = '╔═';
|
|
result = result.substring(result.indexOf(errorStart));
|
|
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',
|
|
);
|
|
});
|
|
}
|