Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 import 'dart:io'; | |
|
Johnni Winther
2017/08/28 07:14:03
Add copyright header (for all .dart files):
// Co
| |
| 2 import 'dart:async'; | |
| 3 import 'dart:convert'; | |
| 4 import 'package:http/http.dart' as http; | |
| 5 import 'package:html/parser.dart' show parse; | |
| 6 import 'try.dart'; | |
| 7 import 'cache_new.dart'; | |
| 8 | |
| 9 const String LUCI_HOST = "luci-milo.appspot.com"; | |
| 10 // const String BUILD_CHROMIUM_HOST = "build.chromium.org"; | |
|
Johnni Winther
2017/08/28 07:14:03
Remove?
| |
| 11 | |
| 12 typedef void ModifyRequestFunction(HttpClientRequest request); | |
| 13 | |
| 14 /// Base class for communicating with Milo @ LogDog | |
| 15 /// Some information is found through the api | |
| 16 /// <https://docs.google.com/document/d/1HbPp7Sy7ofC | |
| 17 /// U7C9USqcE91VubGg_IIET2GUj9iknev4/edit#> | |
| 18 /// and some information is found via screen-scraping. | |
| 19 class LuciApi { | |
|
Johnni Winther
2017/08/28 07:14:04
Just call this [Luci].
| |
| 20 final HttpClient _client = new HttpClient(); | |
| 21 | |
| 22 LuciApi(); | |
| 23 | |
| 24 /// [getBuildBots] fetches all build bots from luci (we cannot | |
| 25 /// get this from the api). The format is: | |
| 26 /// <li> | |
| 27 /// <a href="/buildbot/client.crashpad/crashpad_win_x86_wow64_rel"> | |
| 28 /// crashpad_win_x86_wow64_rel</a> | |
| 29 /// </li> | |
| 30 /// <h3> client.dart </h3> | |
| 31 /// <li> | |
| 32 /// <a href="/buildbot/client.dart/analyze-linux-be">analyze-linux-be</a> | |
| 33 /// </li> | |
| 34 /// <li> | |
| 35 /// <a href="/buildbot/client.dart/analyze-linux-dev">analyze-linux-dev</a > | |
|
Johnni Winther
2017/08/28 07:14:04
Long line.
| |
| 36 /// </li> | |
| 37 /// <li> | |
| 38 /// <a href="/buildbot/client.dart/analyze-linux-stable"> | |
| 39 /// analyze-linux-stable</a> | |
| 40 /// </li> | |
| 41 /// <li> | |
| 42 /// <a href="/buildbot/client.dart/analyzer-linux-release-be"> | |
| 43 /// analyzer-linux-release-be</a> | |
| 44 /// </li> | |
| 45 /// | |
| 46 /// We look for the section header matching clients, then | |
| 47 /// if we are in the right section, we take the <li> element | |
| 48 /// and transform to a build bot | |
| 49 /// | |
| 50 Future<Try<List<LuciBuildBot>>> getAllBuildBots( | |
| 51 String client, WithCacheFunction withCache) async { | |
| 52 return await tryStartAsync(() => withCache( | |
| 53 () => _makeGetRequest( | |
| 54 new Uri(scheme: 'https', host: LUCI_HOST, path: "/")), | |
| 55 "all_buildbots")) | |
| 56 .then((Try<String> tryRes) => tryRes.bind(parse).bind((htmlDoc) { | |
| 57 var takeSection = | |
| 58 false; // this is really dirty, but the structure of | |
|
Johnni Winther
2017/08/28 07:14:04
Move the comment about the variable declaration (d
| |
| 59 // the document is not really suited for anything else | |
| 60 return htmlDoc.body.children.where((node) { | |
| 61 if (node.localName == "li") return takeSection; | |
| 62 if (node.localName != "h3") { | |
| 63 takeSection = false; | |
| 64 return false; | |
| 65 } | |
| 66 // current node is <h3> | |
| 67 takeSection = client == node.text.trim(); | |
| 68 return false; | |
| 69 }); | |
| 70 }).bind((elements) { | |
| 71 // here we hold an iterable of buildbot elements | |
| 72 // <li> | |
| 73 // <a href="/buildbot/client.dart/analyzer-linux-release-be"> | |
| 74 // analyzer-linux-release-be</a> | |
| 75 // </li> | |
| 76 return elements.map((element) { | |
| 77 var name = element.children[0].text; | |
| 78 var url = element.children[0].attributes['href']; | |
| 79 return new LuciBuildBot(client, name, url); | |
| 80 }).toList(); | |
| 81 })); | |
| 82 } | |
| 83 | |
| 84 /// [getPrimaryBuilders] fetches all primary builders | |
| 85 /// (the ones usually used by gardeners) by not including buildbots with | |
| 86 /// the name -dev, -stable or -integration. | |
| 87 Future<Try<List<LuciBuildBot>>> getPrimaryBuilders( | |
| 88 String client, WithCacheFunction withCache) async { | |
| 89 return await getAllBuildBots(client, withCache) | |
| 90 .then((Try<List<LuciBuildBot>> tryRes) { | |
| 91 return tryRes | |
| 92 .bind((buildBots) => buildBots.where((LuciBuildBot buildBot) { | |
| 93 return !(buildBot.name.contains("-dev") || | |
| 94 buildBot.name.contains("-stable") || | |
| 95 buildBot.name.contains("-integration")); | |
| 96 })); | |
| 97 }); | |
| 98 } | |
| 99 | |
| 100 /// Calling the Milo Api to get latest builds for this bot, | |
| 101 /// where the field [amount] is the number of recent builds to fetch | |
|
Johnni Winther
2017/08/28 07:14:04
Add period to end the sentence.
| |
| 102 Future<Try<List<BuildDetail>>> getBuildBotDetails( | |
| 103 String client, String botName, WithCacheFunction withCache, | |
| 104 [int amount = 20]) async { | |
| 105 var uri = new Uri( | |
| 106 scheme: "https", | |
| 107 host: LUCI_HOST, | |
| 108 path: "prpc/milo.Buildbot/GetBuildbotBuildsJSON"); | |
| 109 var body = { | |
| 110 "master": client, | |
| 111 "builder": botName, | |
| 112 "limit": amount, | |
| 113 "includeCurrent": true | |
| 114 }; | |
| 115 var result = await tryStartAsync(() => withCache( | |
| 116 () => _makePostRequest(uri, JSON.encode(body), { | |
| 117 HttpHeaders.CONTENT_TYPE: "application/json", | |
| 118 HttpHeaders.ACCEPT: "application/json" | |
| 119 }), | |
| 120 '${uri.path.toString()}_${botName}_$amount')); | |
|
Johnni Winther
2017/08/28 07:14:04
Just ${uri.path} since [path] is already a string.
| |
| 121 return result.bind(JSON.decode).bind((json) { | |
| 122 return json["builds"].map((b) { | |
| 123 var build = JSON.decode(UTF8.decode(BASE64.decode(b["data"]))); | |
| 124 return getBuildDetailFromJson(client, botName, build); | |
| 125 }).toList(); | |
| 126 }); | |
| 127 } | |
| 128 | |
| 129 /// Calling the Milo Api to get latest builds for this bot, | |
| 130 /// where the field [amount] is the number of recent builds to fetch | |
|
Johnni Winther
2017/08/28 07:14:03
Add period to end the sentence.
| |
| 131 Future<Try<BuildDetail>> getBuildBotBuildDetails(String client, | |
| 132 String botName, int buildNumber, WithCacheFunction withCache) async { | |
| 133 var uri = new Uri( | |
| 134 scheme: "https", | |
| 135 host: LUCI_HOST, | |
| 136 path: "prpc/milo.Buildbot/GetBuildbotBuildJSON"); | |
| 137 var body = {"master": client, "builder": botName, "buildNum": buildNumber}; | |
| 138 print(body); | |
| 139 var result = await tryStartAsync(() => withCache( | |
| 140 () => _makePostRequest(uri, JSON.encode(body), { | |
| 141 HttpHeaders.CONTENT_TYPE: "application/json", | |
| 142 HttpHeaders.ACCEPT: "application/json" | |
| 143 }), | |
| 144 '${uri.path.toString()}_${botName}_$buildNumber')); | |
| 145 return result.bind(JSON.decode).bind((json) { | |
| 146 var build = JSON.decode(UTF8.decode(BASE64.decode(json["data"]))); | |
| 147 return getBuildDetailFromJson(client, botName, build); | |
| 148 }); | |
| 149 } | |
| 150 | |
| 151 Future<String> _makeGetRequest(Uri uri) async { | |
| 152 var request = await _client.getUrl(uri); | |
| 153 var response = await request.close(); | |
| 154 if (response.statusCode != 200) { | |
| 155 response.drain(); | |
| 156 throw new HttpException(response.reasonPhrase, uri: uri); | |
| 157 } | |
| 158 return response.transform(UTF8.decoder).join(); | |
| 159 } | |
| 160 | |
| 161 Future<String> _makePostRequest( | |
| 162 Uri uri, Object body, Map<String, String> headers) async { | |
| 163 var response = await http.post(uri, body: body, headers: headers); | |
| 164 if (response.statusCode != 200) { | |
| 165 throw new HttpException(response.reasonPhrase, uri: uri); | |
| 166 } | |
| 167 // Prpc outputs a prefix to combat vulnerability | |
|
Johnni Winther
2017/08/28 07:14:04
Add period to end the sentence.
| |
| 168 if (response.body.startsWith(")]}'")) { | |
| 169 return response.body.substring(4); | |
| 170 } | |
| 171 return response.body; | |
| 172 } | |
| 173 | |
| 174 void close() { | |
| 175 _client.close(); | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 BuildDetail getBuildDetailFromJson( | |
|
Johnni Winther
2017/08/28 07:14:03
Add documentation.
| |
| 180 String client, String botName, dynamic build) { | |
| 181 List<GitCommit> changes = build["sourceStamp"]["changes"].map((change) { | |
| 182 return new GitCommit(change["revision"], change["revLink"], change["who"], | |
| 183 change["comments"]) | |
| 184 ..changedFiles = change["files"].map((file) => file["name"]); | |
| 185 }).toList(); | |
| 186 | |
| 187 List<BuildProperty> properties = build["properties"].map((prop) { | |
| 188 return new BuildProperty(prop[0], prop[1].toString(), prop[2]); | |
| 189 }).toList(); | |
| 190 | |
| 191 List<BuildStep> steps = build["steps"].map((step) { | |
| 192 var start = | |
| 193 new DateTime.fromMillisecondsSinceEpoch(step["times"][0] * 1000); | |
| 194 DateTime end = null; | |
| 195 if (step["times"][1] != null) { | |
| 196 end = new DateTime.fromMillisecondsSinceEpoch(step["times"][1] * 1000); | |
| 197 } | |
| 198 return new BuildStep(step["name"], step["text"], step["results"].toString(), | |
| 199 start, end, step["step_number"], step["isStarted"], step["isFinished"]) | |
| 200 ..logs = step["logs"].map((log) => new BuildLog(log[0], log[1])); | |
| 201 }).toList(); | |
| 202 | |
| 203 DateTime end = null; | |
| 204 if (build["times"][1] != null) { | |
| 205 end = new DateTime.fromMillisecondsSinceEpoch(build["times"][1] * 1000); | |
| 206 } | |
| 207 | |
| 208 Timing timing = new Timing( | |
| 209 new DateTime.fromMillisecondsSinceEpoch(build["times"][0] * 1000), end); | |
| 210 | |
| 211 return new BuildDetail() | |
| 212 ..client = client | |
| 213 ..botName = botName | |
| 214 ..buildNumber = build["number"] | |
| 215 ..results = build["text"].join(' ') | |
| 216 ..finished = build["finished"] | |
| 217 ..blameList = build["blame"] | |
| 218 ..allChanges = changes | |
| 219 ..buildProperties = properties | |
| 220 ..steps = steps | |
| 221 ..timing = timing; | |
| 222 } | |
| 223 | |
| 224 // Structured classes to relay information from api and web pages | |
| 225 | |
| 226 /// [LuciBuildBot] holds information about a build bot | |
| 227 class LuciBuildBot { | |
| 228 String client; | |
| 229 String name; | |
| 230 String url; | |
|
Johnni Winther
2017/08/28 07:14:03
Make the fields final.
| |
| 231 | |
| 232 LuciBuildBot(this.client, this.name, this.url); | |
| 233 | |
| 234 @override | |
| 235 String toString() { | |
| 236 return "LuciBuildBot { client: $client, name: $name, url: $url }"; | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 class BuildDetail { | |
|
Johnni Winther
2017/08/28 07:14:03
Add document to the class and all its fields.
| |
| 241 String client; | |
| 242 String botName; | |
| 243 int buildNumber; | |
| 244 String results; | |
| 245 bool finished; | |
| 246 List<BuildStep> steps; | |
| 247 List<BuildProperty> buildProperties; | |
| 248 List<String> blameList; | |
| 249 Timing timing; | |
| 250 List<GitCommit> allChanges; | |
|
Johnni Winther
2017/08/28 07:14:04
Add a constructor that takes these as argument and
| |
| 251 | |
| 252 @override | |
| 253 String toString() { | |
| 254 StringBuffer buffer = new StringBuffer(); | |
| 255 buffer.writeln("--------------------------------------"); | |
| 256 buffer.writeln(results); | |
| 257 buffer.writeln(timing); | |
| 258 buffer.writeln("----------------STEPS-----------------"); | |
| 259 if (steps != null) steps.forEach(buffer.writeln); | |
| 260 buffer.writeln("----------BUILD PROPERTIES------------"); | |
| 261 if (buildProperties != null) buildProperties.forEach(buffer.writeln); | |
| 262 buffer.writeln("-------------BLAME LIST---------------"); | |
| 263 if (blameList != null) blameList.forEach(buffer.writeln); | |
| 264 buffer.writeln("------------ALL CHANGES---------------"); | |
| 265 if (allChanges != null) allChanges.forEach(buffer.writeln); | |
| 266 return buffer.toString(); | |
| 267 } | |
| 268 } | |
| 269 | |
| 270 class BuildStep { | |
|
Johnni Winther
2017/08/28 07:14:03
Add document to the class and all its fields.
| |
| 271 final String name; | |
| 272 final String description; | |
| 273 final String result; | |
| 274 final DateTime start; | |
| 275 final DateTime end; | |
| 276 final int number; | |
| 277 final bool isStarted; | |
| 278 final bool isFinished; | |
| 279 List<BuildLog> logs; | |
| 280 BuildStep(this.name, this.description, this.result, this.start, this.end, | |
|
Johnni Winther
2017/08/28 07:14:04
Add an empty line before the constructor.
| |
| 281 this.number, this.isStarted, this.isFinished); | |
| 282 | |
| 283 @override | |
| 284 String toString() { | |
| 285 StringBuffer buffer = new StringBuffer(); | |
| 286 buffer.writeln( | |
| 287 "${result == '[0, []]' ? 'SUCCESS' : result}: $name - $description ($sta rt, $end)"); | |
|
Johnni Winther
2017/08/28 07:14:03
Long line.
| |
| 288 logs.forEach((subLink) { | |
| 289 buffer.writeln("\t${subLink}"); | |
| 290 }); | |
| 291 return buffer.toString(); | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 class BuildLog { | |
|
Johnni Winther
2017/08/28 07:14:03
Add document to the class and its fields.
| |
| 296 String name; | |
| 297 String url; | |
| 298 BuildLog(this.name, this.url); | |
| 299 | |
| 300 @override | |
| 301 String toString() { | |
| 302 return "$name | $url"; | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 class BuildProperty { | |
|
Johnni Winther
2017/08/28 07:14:03
Add document to the class and its fields.
| |
| 307 String name; | |
| 308 String value; | |
| 309 String source; | |
| 310 BuildProperty(this.name, this.value, this.source); | |
|
Johnni Winther
2017/08/28 07:14:04
Add an empty line before the constructor.
| |
| 311 | |
| 312 @override | |
| 313 String toString() { | |
| 314 return "$name\t$value\t$source"; | |
| 315 } | |
| 316 } | |
| 317 | |
| 318 class Timing { | |
| 319 final DateTime start; | |
| 320 final DateTime end; | |
| 321 Timing(this.start, this.end); | |
| 322 | |
| 323 @override | |
| 324 String toString() { | |
| 325 return "start: $start\tend: $end"; | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 class GitCommit { | |
|
Johnni Winther
2017/08/28 07:14:03
Add document to the class and its fields.
| |
| 330 final String revision; | |
| 331 final String commitUrl; | |
| 332 final String changedBy; | |
| 333 final String comments; | |
| 334 List<String> changedFiles; | |
| 335 GitCommit(this.revision, this.commitUrl, this.changedBy, this.comments); | |
|
Johnni Winther
2017/08/28 07:14:04
Add an empty line before the constructor.
| |
| 336 | |
| 337 @override | |
| 338 String toString() { | |
| 339 StringBuffer buffer = new StringBuffer(); | |
| 340 buffer.writeln("revision: $revision"); | |
| 341 buffer.writeln("commitUrl: $commitUrl"); | |
| 342 buffer.writeln("changedBy: $changedBy"); | |
| 343 buffer.write("\n"); | |
| 344 buffer.writeln(comments); | |
| 345 buffer.write("\nfiles:\n"); | |
| 346 changedFiles.forEach(buffer.writeln); | |
| 347 return buffer.toString(); | |
| 348 } | |
| 349 } | |
| OLD | NEW |