33// for details. All rights reserved. Use of this source code is governed by a
44// BSD-style license that can be found in the LICENSE file.
55
6- // Run tests like on the given builder.
6+ // Run tests like on the given builder and/or named configuration .
77
88import 'dart:async' ;
99import 'dart:collection' ;
@@ -55,7 +55,7 @@ Future<ProcessResult> runProcess(String executable, List<String> arguments,
5555 await Process .run (executable, arguments, runInShell: runInShell);
5656 if (processResult.exitCode != 0 ) {
5757 final command =
58- ( [executable].. addAll ( arguments)) .map (simpleShellSingleQuote).join (" " );
58+ [executable, ... arguments] .map (simpleShellSingleQuote).join (" " );
5959 throw new Exception ("Command exited ${processResult .exitCode }: $command \n "
6060 "${processResult .stdout }\n ${processResult .stderr }" );
6161 }
@@ -73,7 +73,7 @@ Future<ProcessResult> runProcessInheritStdio(
7373 final processResult = new ProcessResult (process.pid, exitCode, "" , "" );
7474 if (processResult.exitCode != 0 ) {
7575 final command =
76- ( [executable].. addAll ( arguments)) .map (simpleShellSingleQuote).join (" " );
76+ [executable, ... arguments] .map (simpleShellSingleQuote).join (" " );
7777 throw new Exception ("Command exited ${processResult .exitCode }: $command " );
7878 }
7979 return processResult;
@@ -319,12 +319,18 @@ Future<BuildSearchResult> searchForApproximateBuild(
319319void main (List <String > args) async {
320320 final parser = new ArgParser ();
321321 parser.addOption ("builder" ,
322- abbr: "b" , help: "Run tests like on the given buider " );
322+ abbr: "b" , help: "Run tests like on the given builder " );
323323 parser.addOption ("branch" ,
324324 abbr: "B" ,
325325 help: "Select the builders building this branch" ,
326326 defaultsTo: "master" );
327327 parser.addOption ("commit" , abbr: "C" , help: "Compare with this commit" );
328+ parser.addFlag ("deflake" ,
329+ help: "Re-run failing newly tests $deflakingCount times." );
330+ parser.addFlag ("report-flakes" ,
331+ help: "Report test failures for tests known to be flaky.\n "
332+ "This ignores all flakiness data from CI but flakes\n "
333+ "detected by --deflake will remain hidden" );
328334 parser.addFlag ("list-configurations" ,
329335 help: "Output list of configurations." , negatable: false );
330336 parser.addOption ("named-configuration" ,
@@ -354,8 +360,8 @@ Usage: test.dart -b [BUILDER] -n [CONFIGURATION] [OPTION]... [--]
354360
355361Run tests and compare with the results on the given builder. Either the -n or
356362the -b option, or both, must be used. Any options following -- and non-option
357- arguments will be forwarded to test.py invocations. The results for the specified
358- named configuration will be downloaded from the specified builder. If only a
363+ arguments will be forwarded to test.py invocations. The specified named
364+ configuration's results will be downloaded from the specified builder. If only a
359365named configuration is specified, the results are downloaded from the
360366appropriate builders. If only a builder is specified, the default named
361367configuration is used if the builder only has a single named configuration.
@@ -404,7 +410,7 @@ ${parser.usage}""");
404410 exitCode = 1 ;
405411 return ;
406412 }
407- if (2 <= namedConfigurations.length) {
413+ if (namedConfigurations.length > 1 ) {
408414 final builder = builders.single;
409415 stderr.writeln (
410416 "error: The builder $builder is testing multiple named configurations" );
@@ -418,7 +424,7 @@ ${parser.usage}""");
418424 }
419425 final namedConfiguration = namedConfigurations.single;
420426 final localConfiguration =
421- options["local-configuration" ] ?? namedConfiguration;
427+ options["local-configuration" ] as String ?? namedConfiguration;
422428 for (final builder in builders) {
423429 if (localConfiguration != namedConfiguration) {
424430 print ("Testing the named configuration $localConfiguration "
@@ -452,28 +458,34 @@ ${parser.usage}""");
452458 "as baseline instead of $commit for $builder " );
453459 inexactBuilds[builder] = buildSearchResult.commit;
454460 }
455- final buildNumber = buildSearchResult.build;
461+ final buildNumber = buildSearchResult.build. toString () ;
456462 print ("Downloading results from builder $builder build $buildNumber ..." );
457- await cpGsutil (
458- buildFileCloudPath (builder, buildNumber.toString (), "results.json" ),
463+ await cpGsutil (buildFileCloudPath (builder, buildNumber, "results.json" ),
459464 "${outDirectory .path }/previous.json" );
460- await cpGsutil (
461- buildFileCloudPath (builder, buildNumber.toString (), "flaky.json" ),
462- "${outDirectory .path }/flaky.json" );
465+ if (! options["report-flakes" ]) {
466+ await cpGsutil (buildFileCloudPath (builder, buildNumber, "flaky.json" ),
467+ "${outDirectory .path }/flaky.json" );
468+ }
463469 print ("Downloaded baseline results from builder $builder " );
464470 // Merge the results for the builders.
465- if (2 <= builders.length) {
471+ if (builders.length > 1 ) {
466472 mergedResults
467473 .addAll (await loadResultsMap ("${outDirectory .path }/previous.json" ));
468- mergedFlaky
469- .addAll (await loadResultsMap ("${outDirectory .path }/flaky.json" ));
474+ if (! options["report-flakes" ]) {
475+ mergedFlaky
476+ .addAll (await loadResultsMap ("${outDirectory .path }/flaky.json" ));
477+ }
470478 }
471479 }
472480
473481 // Write out the merged results for the builders.
474- if (2 <= builders.length) {
482+ if (builders.length > 1 ) {
475483 await new File ("${outDirectory .path }/previous.json" ).writeAsString (
476484 mergedResults.values.map ((data) => jsonEncode (data) + "\n " ).join ("" ));
485+ }
486+
487+ // Ensure that there is a flaky.json even if it wasn't downloaded.
488+ if (builders.length > 1 || options["report-flakes" ]) {
477489 await new File ("${outDirectory .path }/flaky.json" ).writeAsString (
478490 mergedFlaky.values.map ((data) => jsonEncode (data) + "\n " ).join ("" ));
479491 }
@@ -502,62 +514,18 @@ ${parser.usage}""");
502514 "--silent-failures" ,
503515 "--write-results" ,
504516 "--write-logs" ,
505- ]..addAll (options.rest);
517+ ...options.rest,
518+ ];
506519 print ("" .padLeft (80 , "=" ));
507520 print ("Running tests" );
508521 print ("" .padLeft (80 , "=" ));
509- await runProcessInheritStdio ("python" , ["tools/test.py" ].. addAll ( arguments) ,
522+ await runProcessInheritStdio ("python" , ["tools/test.py" , ... arguments] ,
510523 runInShell: Platform .isWindows);
511524
512- // Find the list of tests to deflake.
513- final deflakeListOutput = await runProcess (Platform .resolvedExecutable, [
514- "tools/bots/compare_results.dart" ,
515- "--changed" ,
516- "--failing" ,
517- "--passing" ,
518- "--flakiness-data=${outDirectory .path }/flaky.json" ,
519- "${outDirectory .path }/previous.json" ,
520- "${outDirectory .path }/results.json" ,
521- ]);
522- final deflakeListPath = "${outDirectory .path }/deflake.list" ;
523- final deflakeListFile = new File (deflakeListPath);
524- await deflakeListFile.writeAsString (deflakeListOutput.stdout);
525-
526- // Deflake the changed tests.
527- final deflakingResultsPaths = < String > [];
528- for (int i = 1 ;
529- deflakeListOutput.stdout != "" && i <= deflakingCount;
530- i++ ) {
531- print ("" .padLeft (80 , "=" ));
532- print ("Running deflaking iteration $i " );
533- print ("" .padLeft (80 , "=" ));
534- final deflakeDirectory = new Directory ("${outDirectory .path }/$i " );
535- await deflakeDirectory.create ();
536- final deflakeArguments = < String > [
537- "--named-configuration=$localConfiguration " ,
538- "--output-directory=${deflakeDirectory .path }" ,
539- "--clean-exit" ,
540- "--silent-failures" ,
541- "--write-results" ,
542- "--test-list=$deflakeListPath " ,
543- ]..addAll (options.rest);
544- await runProcessInheritStdio (
545- "python" , ["tools/test.py" ]..addAll (deflakeArguments),
546- runInShell: Platform .isWindows);
547- deflakingResultsPaths.add ("${deflakeDirectory .path }/results.json" );
525+ if (options["deflake" ]) {
526+ await deflake (outDirectory, localConfiguration, options.rest);
548527 }
549528
550- // Update the flakiness information based on what we've learned.
551- print ("Updating flakiness information..." );
552- await runProcess (
553- Platform .resolvedExecutable,
554- [
555- "tools/bots/update_flakiness.dart" ,
556- "--input=${outDirectory .path }/flaky.json" ,
557- "--output=${outDirectory .path }/flaky.json" ,
558- "${outDirectory .path }/results.json" ,
559- ]..addAll (deflakingResultsPaths));
560-
561529 // Write out the final comparison.
562530 print ("" .padLeft (80 , "=" ));
563531 print ("Test Results" );
@@ -589,3 +557,53 @@ ${parser.usage}""");
589557 await outDirectory.delete (recursive: true );
590558 }
591559}
560+
561+ void deflake (Directory outDirectory, String localConfiguration,
562+ List <String > testPyArgs) async {
563+ // Find the list of tests to deflake.
564+ final deflakeListOutput = await runProcess (Platform .resolvedExecutable, [
565+ "tools/bots/compare_results.dart" ,
566+ "--changed" ,
567+ "--failing" ,
568+ "--passing" ,
569+ "--flakiness-data=${outDirectory .path }/flaky.json" ,
570+ "${outDirectory .path }/previous.json" ,
571+ "${outDirectory .path }/results.json" ,
572+ ]);
573+ final deflakeListPath = "${outDirectory .path }/deflake.list" ;
574+ final deflakeListFile = new File (deflakeListPath);
575+ await deflakeListFile.writeAsString (deflakeListOutput.stdout);
576+
577+ // Deflake the changed tests.
578+ final deflakingResultsPaths = < String > [];
579+ for (int i = 1 ; deflakeListOutput.stdout != "" && i <= deflakingCount; i++ ) {
580+ print ("" .padLeft (80 , "=" ));
581+ print ("Running deflaking iteration $i " );
582+ print ("" .padLeft (80 , "=" ));
583+ final deflakeDirectory = new Directory ("${outDirectory .path }/$i " );
584+ await deflakeDirectory.create ();
585+ final deflakeArguments = < String > [
586+ "--named-configuration=$localConfiguration " ,
587+ "--output-directory=${deflakeDirectory .path }" ,
588+ "--clean-exit" ,
589+ "--silent-failures" ,
590+ "--write-results" ,
591+ "--test-list=$deflakeListPath " ,
592+ ...testPyArgs,
593+ ];
594+ await runProcessInheritStdio (
595+ "python" , ["tools/test.py" , ...deflakeArguments],
596+ runInShell: Platform .isWindows);
597+ deflakingResultsPaths.add ("${deflakeDirectory .path }/results.json" );
598+ }
599+
600+ // Update the flakiness information based on what we've learned.
601+ print ("Updating flakiness information..." );
602+ await runProcess (Platform .resolvedExecutable, [
603+ "tools/bots/update_flakiness.dart" ,
604+ "--input=${outDirectory .path }/flaky.json" ,
605+ "--output=${outDirectory .path }/flaky.json" ,
606+ "${outDirectory .path }/results.json" ,
607+ ...deflakingResultsPaths,
608+ ]);
609+ }
0 commit comments