@@ -124,6 +124,9 @@ Future<void> run(List<String> arguments) async {
124124 printProgress ('Goldens...' );
125125 await verifyGoldenTags (flutterPackages);
126126
127+ printProgress ('Prevent flakes from Stopwatches...' );
128+ await verifyNoStopwatches (flutterPackages);
129+
127130 printProgress ('Skip test comments...' );
128131 await verifySkipTestComments (flutterRoot);
129132
@@ -584,6 +587,48 @@ Future<void> verifyGoldenTags(String workingDirectory, { int minimumMatches = 20
584587 }
585588}
586589
590+ /// Use of Stopwatches can introduce test flakes as the logical time of a
591+ /// stopwatch can fall out of sync with the mocked time of FakeAsync in testing.
592+ /// The Clock object provides a safe stopwatch instead, which is paired with
593+ /// FakeAsync as part of the test binding.
594+ final RegExp _findStopwatchPattern = RegExp (r'Stopwatch\(\)' );
595+ const String _ignoreStopwatch = '// flutter_ignore: stopwatch (see analyze.dart)' ;
596+ const String _ignoreStopwatchForFile = '// flutter_ignore_for_file: stopwatch (see analyze.dart)' ;
597+
598+ Future <void > verifyNoStopwatches (String workingDirectory, { int minimumMatches = 2000 }) async {
599+ final List <String > errors = < String > [];
600+ await for (final File file in _allFiles (workingDirectory, 'dart' , minimumMatches: minimumMatches)) {
601+ if (file.path.contains ('flutter_tool' )) {
602+ // Skip flutter_tool package.
603+ continue ;
604+ }
605+ int lineNumber = 1 ;
606+ final List <String > lines = file.readAsLinesSync ();
607+ for (final String line in lines) {
608+ // If the file is being ignored, skip parsing the rest of the lines.
609+ if (line.contains (_ignoreStopwatchForFile)) {
610+ break ;
611+ }
612+
613+ if (line.contains (_findStopwatchPattern)
614+ && ! line.contains (_leadingComment)
615+ && ! line.contains (_ignoreStopwatch)) {
616+ // Stopwatch found
617+ errors.add ('\t ${file .path }:$lineNumber ' );
618+ }
619+ lineNumber++ ;
620+ }
621+ }
622+ if (errors.isNotEmpty) {
623+ foundError (< String > [
624+ 'Stopwatch use was found in the following files:' ,
625+ ...errors,
626+ '${bold }Stopwatches introduce flakes by falling out of sync with the FakeAsync used in testing.$reset ' ,
627+ 'A Stopwatch that stays in sync with FakeAsync is available through the Gesture or Test bindings, through samplingClock.'
628+ ]);
629+ }
630+ }
631+
587632final RegExp _findDeprecationPattern = RegExp (r'@[Dd]eprecated' );
588633final RegExp _deprecationStartPattern = RegExp (r'^(?<indent> *)@Deprecated\($' ); // flutter_ignore: deprecation_syntax (see analyze.dart)
589634final RegExp _deprecationMessagePattern = RegExp (r"^ *'(?<message>.+) '$" );
0 commit comments