Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 14b1d2f

Browse files
committed
[infra] Make deflaking optional in test.dart
This adds a "--deflake" flag to test.dart. Deflaking will only run if that flag is set. Deflaking is time consuming and delays the information that users want to see when most of the time they know that the result is not caused by flakiness. A typical session may look like this: * Run test.dart with a broad test selector without deflaking. * Re-run test.dart with a narrow test selector for suspected flakes with "--deflake". This second step is optional. Additionally, a new "--report-flakes" flag is added that can be used to report test failures for tests known to be flaky. Change-Id: I543d0b40c32065eb0a50338c55e7050b7887abce Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102381 Reviewed-by: Jonas Termansen <[email protected]>
1 parent 181aeb3 commit 14b1d2f

File tree

1 file changed

+85
-67
lines changed

1 file changed

+85
-67
lines changed

tools/test.dart

Lines changed: 85 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
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

88
import 'dart:async';
99
import '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(
319319
void 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
355361
Run tests and compare with the results on the given builder. Either the -n or
356362
the -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
359365
named configuration is specified, the results are downloaded from the
360366
appropriate builders. If only a builder is specified, the default named
361367
configuration 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

Comments
 (0)