Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// Compares the test log of a build step with previous builds. | 5 /// Compares the test log of a build step with previous builds. |
| 6 /// | 6 /// |
| 7 /// Use this to detect flakiness of failures, especially timeouts. | 7 /// Use this to detect flakiness of failures, especially timeouts. |
| 8 | 8 |
| 9 import 'dart:async'; | 9 import 'dart:async'; |
| 10 | 10 |
| 11 import 'package:gardening/src/buildbot_structures.dart'; | 11 import 'package:gardening/src/buildbot_structures.dart'; |
| 12 import 'package:gardening/src/buildbot_data.dart'; | 12 import 'package:gardening/src/buildbot_data.dart'; |
| 13 import 'package:gardening/src/client.dart'; | 13 import 'package:gardening/src/client.dart'; |
| 14 import 'package:gardening/src/util.dart'; | 14 import 'package:gardening/src/util.dart'; |
| 15 | 15 |
| 16 Future mainInternal(BuildbotClient client, List<String> args, | 16 Future mainInternal(BuildbotClient client, List<String> args, |
| 17 {int runCount: 10}) async { | 17 {int runCount: 10}) async { |
| 18 printBuildResultsSummary( | 18 printBuildResultsSummary( |
| 19 await loadBuildResults(client, args, runCount: runCount)); | 19 await loadBuildResults(client, args, runCount: runCount), args); |
| 20 } | 20 } |
| 21 | 21 |
| 22 /// Loads [BuildResult]s for the [runCount] last builds for the build(s) in | 22 /// Loads [BuildResult]s for the [runCount] last builds for the build(s) in |
| 23 /// [args]. [args] can be a list of [BuildGroup] names or a list of log uris. | 23 /// [args]. [args] can be a list of [BuildGroup] names or a list of log uris. |
| 24 Future<Map<BuildUri, List<BuildResult>>> loadBuildResults( | 24 Future<Map<BuildUri, List<BuildResult>>> loadBuildResults( |
| 25 BuildbotClient client, List<String> args, | 25 BuildbotClient client, List<String> args, |
| 26 {int runCount: 10}) async { | 26 {int runCount: 10}) async { |
| 27 List<BuildUri> buildUriList = <BuildUri>[]; | 27 List<BuildUri> buildUriList = <BuildUri>[]; |
| 28 for (BuildGroup buildGroup in buildGroups) { | 28 for (BuildGroup buildGroup in buildGroups) { |
| 29 if (args.contains(buildGroup.groupName)) { | 29 if (args.contains(buildGroup.groupName)) { |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 45 <BuildUri, List<BuildResult>>{}; | 45 <BuildUri, List<BuildResult>>{}; |
| 46 for (BuildUri buildUri in buildUriList) { | 46 for (BuildUri buildUri in buildUriList) { |
| 47 List<BuildResult> results = | 47 List<BuildResult> results = |
| 48 await readBuildResults(client, buildUri, runCount); | 48 await readBuildResults(client, buildUri, runCount); |
| 49 buildResults[buildUri] = results; | 49 buildResults[buildUri] = results; |
| 50 } | 50 } |
| 51 return buildResults; | 51 return buildResults; |
| 52 } | 52 } |
| 53 | 53 |
| 54 /// Prints summaries for the [buildResults]. | 54 /// Prints summaries for the [buildResults]. |
| 55 // TODO(johnniwinther): Improve printing of multiple [BuildUri] results. | 55 void printBuildResultsSummary( |
| 56 void printBuildResultsSummary(Map<BuildUri, List<BuildResult>> buildResults) { | 56 Map<BuildUri, List<BuildResult>> buildResults, List<String> args) { |
| 57 List<Summary> emptySummaries = <Summary>[]; | |
| 58 List<Summary> nonEmptySummaries = <Summary>[]; | |
| 57 buildResults.forEach((BuildUri buildUri, List<BuildResult> results) { | 59 buildResults.forEach((BuildUri buildUri, List<BuildResult> results) { |
| 58 print(generateBuildResultsSummary(buildUri, results)); | 60 Summary summary = new Summary(buildUri, results); |
| 61 if (summary.isEmpty) { | |
| 62 emptySummaries.add(summary); | |
| 63 } else { | |
| 64 nonEmptySummaries.add(summary); | |
| 65 } | |
| 59 }); | 66 }); |
| 67 StringBuffer sb = new StringBuffer(); | |
| 68 if (nonEmptySummaries.isEmpty) { | |
| 69 if (emptySummaries.isNotEmpty) { | |
| 70 if (LOG || emptySummaries.length < 3) { | |
| 71 if (emptySummaries.length == 1) { | |
| 72 sb.write('No errors found for build bot:\n'); | |
|
floitsch
2017/08/08 13:42:02
I would use "writeln"
here and in the other place
Johnni Winther
2017/08/08 14:23:30
Done.
| |
| 73 sb.write(emptySummaries.single.buildUri); | |
| 74 } else { | |
| 75 sb.write('No errors found for any of these build bots:\n'); | |
| 76 for (Summary summary in emptySummaries) { | |
| 77 sb.write('${summary.buildUri}\n'); | |
| 78 } | |
| 79 } | |
| 80 } else { | |
| 81 sb.write('No errors found for any of the ' | |
| 82 '${emptySummaries.length} bots.'); | |
| 83 } | |
| 84 } else { | |
| 85 sb.write('No build bot results found for args: ${args}'); | |
| 86 } | |
| 87 } else { | |
| 88 for (Summary summary in nonEmptySummaries) { | |
| 89 summary.printOn(sb); | |
| 90 } | |
| 91 if (emptySummaries.isNotEmpty) { | |
| 92 if (LOG || emptySummaries.length < 3) { | |
| 93 sb.write('No errors found for the remaining build bots:\n'); | |
| 94 for (Summary summary in emptySummaries) { | |
| 95 sb.write('${summary.buildUri}\n'); | |
| 96 } | |
| 97 } else { | |
| 98 sb.write( | |
| 99 'No errors found for the ${emptySummaries.length} remaining bots.'); | |
| 100 } | |
| 101 } | |
| 102 } | |
| 103 print(sb); | |
| 60 } | 104 } |
| 61 | 105 |
| 62 /// Creates a [BuildResult] for [buildUri] and, if it contains failures, the | 106 /// Creates a [BuildResult] for [buildUri] and, if it contains failures, the |
| 63 /// [BuildResult]s for the previous [runCount] builds. | 107 /// [BuildResult]s for the previous [runCount] builds. |
| 64 Future<List<BuildResult>> readBuildResults( | 108 Future<List<BuildResult>> readBuildResults( |
| 65 BuildbotClient client, BuildUri buildUri, int runCount) async { | 109 BuildbotClient client, BuildUri buildUri, int runCount) async { |
| 66 List<BuildResult> summaries = <BuildResult>[]; | 110 List<BuildResult> summaries = <BuildResult>[]; |
| 67 BuildResult summary = await client.readResult(buildUri); | 111 BuildResult summary = await client.readResult(buildUri); |
| 68 summaries.add(summary); | 112 summaries.add(summary); |
| 69 if (summary.hasFailures) { | 113 if (summary.hasFailures) { |
| 70 for (int i = 0; i < runCount; i++) { | 114 for (int i = 0; i < runCount; i++) { |
| 71 buildUri = summary.buildUri.prev(); | 115 buildUri = summary.buildUri.prev(); |
| 72 summary = await client.readResult(buildUri); | 116 summary = await client.readResult(buildUri); |
| 73 summaries.add(summary); | 117 summaries.add(summary); |
| 74 } | 118 } |
| 75 } | 119 } |
| 76 return summaries; | 120 return summaries; |
| 77 } | 121 } |
| 78 | 122 |
| 79 /// Generate a summary of the timeouts and other failures in [results]. | 123 class Summary { |
| 80 String generateBuildResultsSummary( | 124 final BuildUri buildUri; |
| 81 BuildUri buildUri, List<BuildResult> results) { | 125 final List<BuildResult> results; |
| 82 StringBuffer sb = new StringBuffer(); | 126 final Set<TestConfiguration> timeoutIds = new Set<TestConfiguration>(); |
| 83 sb.write('Results for $buildUri:\n'); | 127 final Set<TestConfiguration> errorIds = new Set<TestConfiguration>(); |
| 84 Set<TestConfiguration> timeoutIds = new Set<TestConfiguration>(); | 128 |
| 85 for (BuildResult result in results) { | 129 Summary(this.buildUri, this.results) { |
| 86 timeoutIds.addAll(result.timeouts.map((TestFailure failure) => failure.id)); | 130 for (BuildResult result in results) { |
| 131 timeoutIds | |
| 132 .addAll(result.timeouts.map((TestFailure failure) => failure.id)); | |
| 133 errorIds.addAll(result.errors.map((TestFailure failure) => failure.id)); | |
| 134 } | |
| 87 } | 135 } |
| 88 if (timeoutIds.isNotEmpty) { | 136 |
| 89 Map<TestConfiguration, Map<int, Map<String, Timing>>> map = | 137 bool get isEmpty => timeoutIds.isEmpty && errorIds.isEmpty; |
| 90 <TestConfiguration, Map<int, Map<String, Timing>>>{}; | 138 |
| 91 Set<String> stepNames = new Set<String>(); | 139 /// Generate a summary of the timeouts and other failures in [results]. |
| 92 for (BuildResult result in results) { | 140 void printOn(StringBuffer sb) { |
| 93 for (Timing timing in result.timings) { | 141 if (timeoutIds.isNotEmpty) { |
| 94 Map<int, Map<String, Timing>> builds = | 142 Map<TestConfiguration, Map<int, Map<String, Timing>>> map = |
| 95 map.putIfAbsent(timing.step.id, () => <int, Map<String, Timing>>{}); | 143 <TestConfiguration, Map<int, Map<String, Timing>>>{}; |
| 96 stepNames.add(timing.step.stepName); | 144 Set<String> stepNames = new Set<String>(); |
| 97 builds.putIfAbsent(timing.uri.buildNumber, () => <String, Timing>{})[ | 145 for (BuildResult result in results) { |
| 98 timing.step.stepName] = timing; | 146 for (Timing timing in result.timings) { |
| 147 Map<int, Map<String, Timing>> builds = map.putIfAbsent( | |
| 148 timing.step.id, () => <int, Map<String, Timing>>{}); | |
| 149 stepNames.add(timing.step.stepName); | |
| 150 builds.putIfAbsent(timing.uri.buildNumber, () => <String, Timing>{})[ | |
| 151 timing.step.stepName] = timing; | |
| 152 } | |
| 99 } | 153 } |
| 100 } | 154 sb.write('Timeouts for ${buildUri} :\n'); |
| 101 sb.write('Timeouts for ${buildUri} :\n'); | 155 map.forEach( |
| 102 map.forEach((TestConfiguration id, Map<int, Map<String, Timing>> timings) { | 156 (TestConfiguration id, Map<int, Map<String, Timing>> timings) { |
| 103 if (!timeoutIds.contains(id)) return; | 157 if (!timeoutIds.contains(id)) return; |
| 104 sb.write('$id\n'); | 158 sb.write('$id\n'); |
| 105 sb.write( | 159 sb.write( |
| 106 '${' ' * 8} ${stepNames.map((t) => padRight(t, 14)).join(' ')}\n'); | 160 '${' ' * 8} ${stepNames.map((t) => padRight(t, 14)).join(' ')}\n'); |
| 107 for (BuildResult result in results) { | 161 for (BuildResult result in results) { |
| 108 int buildNumber = result.buildUri.buildNumber; | 162 int buildNumber = result.buildUri.buildNumber; |
| 109 Map<String, Timing> steps = timings[buildNumber] ?? const {}; | 163 Map<String, Timing> steps = timings[buildNumber] ?? const {}; |
| 110 sb.write(padRight(' ${buildNumber}: ', 8)); | 164 sb.write(padRight(' ${buildNumber}: ', 8)); |
| 111 for (String stepName in stepNames) { | 165 for (String stepName in stepNames) { |
| 112 Timing timing = steps[stepName]; | 166 Timing timing = steps[stepName]; |
| 113 if (timing != null) { | 167 if (timing != null) { |
| 114 sb.write(' ${timing.time}'); | 168 sb.write(' ${timing.time}'); |
| 115 } else { | 169 } else { |
| 116 sb.write(' --------------'); | 170 sb.write(' --------------'); |
| 171 } | |
| 117 } | 172 } |
| 173 sb.write('\n'); | |
| 118 } | 174 } |
| 119 sb.write('\n'); | 175 sb.write('\n'); |
| 176 }); | |
| 177 } | |
| 178 if (errorIds.isNotEmpty) { | |
| 179 Map<TestConfiguration, Map<int, TestFailure>> map = | |
| 180 <TestConfiguration, Map<int, TestFailure>>{}; | |
| 181 for (BuildResult result in results) { | |
| 182 for (TestFailure failure in result.errors) { | |
| 183 map.putIfAbsent(failure.id, () => <int, TestFailure>{})[ | |
| 184 failure.uri.buildNumber] = failure; | |
| 185 } | |
| 120 } | 186 } |
| 121 sb.write('\n'); | 187 sb.write('Errors for ${buildUri} :\n'); |
| 122 }); | 188 // TODO(johnniwinther): Improve comparison of non-timeouts. |
| 123 } | 189 map.forEach((TestConfiguration id, Map<int, TestFailure> failures) { |
| 124 Set<TestConfiguration> errorIds = new Set<TestConfiguration>(); | 190 if (!errorIds.contains(id)) return; |
| 125 for (BuildResult result in results) { | 191 sb.write('$id\n'); |
| 126 errorIds.addAll(result.errors.map((TestFailure failure) => failure.id)); | 192 for (BuildResult result in results) { |
| 127 } | 193 int buildNumber = result.buildUri.buildNumber; |
| 128 if (errorIds.isNotEmpty) { | 194 TestFailure failure = failures[buildNumber]; |
| 129 Map<TestConfiguration, Map<int, TestFailure>> map = | 195 sb.write(padRight(' ${buildNumber}: ', 8)); |
| 130 <TestConfiguration, Map<int, TestFailure>>{}; | 196 if (failure != null) { |
| 131 for (BuildResult result in results) { | 197 sb.write(padRight(failure.expected, 10)); |
| 132 for (TestFailure failure in result.errors) { | 198 sb.write(' / '); |
| 133 map.putIfAbsent(failure.id, () => <int, TestFailure>{})[ | 199 sb.write(padRight(failure.actual, 10)); |
| 134 failure.uri.buildNumber] = failure; | 200 } else { |
| 135 } | 201 sb.write(' ' * 10); |
| 136 } | 202 sb.write(' / '); |
| 137 sb.write('Errors for ${buildUri} :\n'); | 203 sb.write(padRight('-- OK --', 10)); |
| 138 // TODO(johnniwinther): Improve comparison of non-timeouts. | 204 } |
| 139 map.forEach((TestConfiguration id, Map<int, TestFailure> failures) { | 205 sb.write('\n'); |
| 140 if (!errorIds.contains(id)) return; | |
| 141 sb.write('$id\n'); | |
| 142 for (BuildResult result in results) { | |
| 143 int buildNumber = result.buildUri.buildNumber; | |
| 144 TestFailure failure = failures[buildNumber]; | |
| 145 sb.write(padRight(' ${buildNumber}: ', 8)); | |
| 146 if (failure != null) { | |
| 147 sb.write(padRight(failure.expected, 10)); | |
| 148 sb.write(' / '); | |
| 149 sb.write(padRight(failure.actual, 10)); | |
| 150 } else { | |
| 151 sb.write(' ' * 10); | |
| 152 sb.write(' / '); | |
| 153 sb.write(padRight('-- OK --', 10)); | |
| 154 } | 206 } |
| 155 sb.write('\n'); | 207 sb.write('\n'); |
| 156 } | 208 }); |
| 157 sb.write('\n'); | 209 } |
| 158 }); | 210 if (timeoutIds.isEmpty && errorIds.isEmpty) { |
| 211 sb.write('No errors found for ${buildUri}'); | |
| 212 } | |
| 159 } | 213 } |
| 160 if (timeoutIds.isEmpty && errorIds.isEmpty) { | |
| 161 sb.write('No errors found.'); | |
| 162 } | |
| 163 return sb.toString(); | |
| 164 } | 214 } |
| OLD | NEW |