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.writeln('No errors found for build bot:'); |
| 73 sb.write(emptySummaries.single.buildUri); |
| 74 } else { |
| 75 sb.writeln('No errors found for any of these build bots:'); |
| 76 for (Summary summary in emptySummaries) { |
| 77 sb.writeln('${summary.buildUri}'); |
| 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.writeln('No errors found for the remaining build bots:'); |
| 94 for (Summary summary in emptySummaries) { |
| 95 sb.writeln('${summary.buildUri}'); |
| 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 |