| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 // The following set of variables should be set by the caller that | |
| 6 // #sources this file. | |
| 7 /** Whether to include elapsed time. */ | |
| 8 | |
| 9 part of test_controller; | |
| 10 | |
| 11 bool includeTime; | |
| 12 | |
| 13 /** Path to DRT executable. */ | |
| 14 String drt; | |
| 15 | |
| 16 /** Whether to regenerate layout test files. */ | |
| 17 bool regenerate; | |
| 18 | |
| 19 /** Whether to output test summary. */ | |
| 20 bool summarize; | |
| 21 | |
| 22 /** Whether to print results immediately as they come in. */ | |
| 23 bool immediate; | |
| 24 | |
| 25 /** Format strings to use for test result messages. */ | |
| 26 String passFormat, failFormat, errorFormat, listFormat; | |
| 27 | |
| 28 /** Location of the running test file. */ | |
| 29 String sourceDir; | |
| 30 | |
| 31 /** Path of the running test file. */ | |
| 32 String testfile; | |
| 33 | |
| 34 /** URL of the child test file. */ | |
| 35 String baseUrl; | |
| 36 | |
| 37 /** The print function to use. */ | |
| 38 Function tprint; | |
| 39 | |
| 40 /** A callback function to notify the caller we are done. */ | |
| 41 Function notifyDone; | |
| 42 | |
| 43 // Variable below here are local to this file. | |
| 44 var passCount = 0, failCount = 0, errorCount = 0; | |
| 45 DateTime start; | |
| 46 | |
| 47 class Macros { | |
| 48 static const String testTime = '<TIME>'; | |
| 49 static const String testfile = '<FILENAME>'; | |
| 50 static const String testGroup = '<GROUPNAME>'; | |
| 51 static const String testDescription = '<TESTNAME>'; | |
| 52 static const String testMessage = '<MESSAGE>'; | |
| 53 static const String testStacktrace = '<STACK>'; | |
| 54 } | |
| 55 | |
| 56 String formatMessage(filename, groupname, | |
| 57 [testname = '', testTime = '', result = '', | |
| 58 message = '', stack = '']) { | |
| 59 var format = errorFormat; | |
| 60 if (result == 'pass') format = passFormat; | |
| 61 else if (result == 'fail') format = failFormat; | |
| 62 return format. | |
| 63 replaceAll(Macros.testTime, testTime). | |
| 64 replaceAll(Macros.testfile, filename). | |
| 65 replaceAll(Macros.testGroup, groupname). | |
| 66 replaceAll(Macros.testDescription, testname). | |
| 67 replaceAll(Macros.testMessage, message). | |
| 68 replaceAll(Macros.testStacktrace, stack); | |
| 69 } | |
| 70 | |
| 71 void outputResult(start, label, result, [message = '']) { | |
| 72 var idx = label.lastIndexOf('###'); | |
| 73 var group = '', test = ''; | |
| 74 if (idx >= 0) { | |
| 75 group = '${label.substring(0, idx).replaceAll("###", " ")} '; | |
| 76 test = '${label.substring(idx+3)} '; | |
| 77 } else { | |
| 78 test = '$label '; | |
| 79 } | |
| 80 var elapsed = ''; | |
| 81 if (includeTime) { | |
| 82 var end = new DateTime.now(); | |
| 83 double duration = (end.difference(start)).inMilliseconds.toDouble(); | |
| 84 duration /= 1000; | |
| 85 elapsed = '${duration.toStringAsFixed(3)}s '; | |
| 86 } | |
| 87 tprint(formatMessage('$testfile ', group, test, elapsed, result, message)); | |
| 88 } | |
| 89 | |
| 90 pass(start, label) { | |
| 91 ++passCount; | |
| 92 outputResult(start, label, 'pass'); | |
| 93 } | |
| 94 | |
| 95 fail(start, label, message) { | |
| 96 ++failCount; | |
| 97 outputResult(start, label, 'fail', message); | |
| 98 } | |
| 99 | |
| 100 error(start, label, message) { | |
| 101 ++errorCount; | |
| 102 outputResult(start, label, 'error', message); | |
| 103 } | |
| 104 | |
| 105 void printSummary(String testFile, int passed, int failed, int errors, | |
| 106 [String uncaughtError = '']) { | |
| 107 tprint(''); | |
| 108 if (passed == 0 && failed == 0 && errors == 0) { | |
| 109 tprint('$testFile: No tests found.'); | |
| 110 } else if (failed == 0 && errors == 0 && uncaughtError == null) { | |
| 111 tprint('$testFile: All $passed tests passed.'); | |
| 112 } else { | |
| 113 if (uncaughtError != null) { | |
| 114 tprint('$testFile: Top-level uncaught error: $uncaughtError'); | |
| 115 } | |
| 116 tprint('$testFile: $passed PASSED, $failed FAILED, $errors ERRORS'); | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 complete() { | |
| 121 if (summarize) { | |
| 122 printSummary(testfile, passCount, failCount, errorCount); | |
| 123 } | |
| 124 notifyDone(failCount > 0 ? -1 : 0); | |
| 125 } | |
| 126 | |
| 127 /* | |
| 128 * Run an external process [cmd] with command line arguments [args]. | |
| 129 * [timeout] can be used to forcefully terminate the process after | |
| 130 * some number of seconds. This is used by runCommand and startProcess. | |
| 131 * If [procId] is non-zero (i.e. called from startProcess) then a reference | |
| 132 * to the [Process] will be put in a map with key [procId]; in this case | |
| 133 * the process can be terminated later by calling [stopProcess] and | |
| 134 * passing in the [procId]. | |
| 135 * [outputMonitor] is an optional function that will be called back with each | |
| 136 * line of output from the process. | |
| 137 * Returns a [Future] for when the process terminates. | |
| 138 */ | |
| 139 Future _processHelper(String command, List<String> args, | |
| 140 List stdout, List stderr, | |
| 141 int timeout, int procId, Function outputMonitor, bool raw) { | |
| 142 var timer = null; | |
| 143 return Process.start(command, args).then((process) { | |
| 144 | |
| 145 timer = new Timer(new Duration(seconds: timeout), () { | |
| 146 timer = null; | |
| 147 process.kill(); | |
| 148 }); | |
| 149 | |
| 150 if (raw) { | |
| 151 process.stdout.listen((c) { stdout.addAll(c); }); | |
| 152 } else { | |
| 153 _pipeStream(process.stdout, stdout, outputMonitor); | |
| 154 } | |
| 155 _pipeStream(process.stderr, stderr, outputMonitor); | |
| 156 return process.exitCode; | |
| 157 }).then((exitCode) { | |
| 158 if (timer != null) { | |
| 159 timer.cancel(); | |
| 160 } | |
| 161 return exitCode; | |
| 162 }) | |
| 163 .catchError((e) { | |
| 164 stderr.add("#Error starting process $command: ${e.error}"); | |
| 165 }); | |
| 166 } | |
| 167 | |
| 168 void _pipeStream(Stream stream, List<String> destination, | |
| 169 Function outputMonitor) { | |
| 170 stream | |
| 171 .transform(UTF8.decoder) | |
| 172 .transform(new LineTransformer()) | |
| 173 .listen((String line) { | |
| 174 if (outputMonitor != null) { | |
| 175 outputMonitor(line); | |
| 176 } | |
| 177 destination.add(line); | |
| 178 }); | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Run an external process [cmd] with command line arguments [args]. | |
| 183 * [timeout] can be used to forcefully terminate the process after | |
| 184 * some number of seconds. | |
| 185 * Returns a [Future] for when the process terminates. | |
| 186 */ | |
| 187 Future runCommand(String command, List<String> args, | |
| 188 List stdout, List stderr, | |
| 189 {int timeout: 300, Function outputMonitor, | |
| 190 bool raw: false}) { | |
| 191 return _processHelper(command, args, stdout, stderr, | |
| 192 timeout, 0, outputMonitor, raw); | |
| 193 } | |
| 194 | |
| 195 String parseLabel(String line) { | |
| 196 if (line.startsWith('CONSOLE MESSAGE')) { | |
| 197 var idx = line.indexOf('#TEST '); | |
| 198 if (idx > 0) { | |
| 199 return line.substring(idx + 6); | |
| 200 } | |
| 201 } | |
| 202 return null; | |
| 203 } | |
| 204 | |
| 205 runTextLayoutTest(testNum) { | |
| 206 var url = '$baseUrl?test=$testNum'; | |
| 207 var stdout = new List(); | |
| 208 var stderr = new List(); | |
| 209 start = new DateTime.now(); | |
| 210 runCommand(drt, [url], stdout, stderr).then((e) { | |
| 211 if (stdout.length > 0 && stdout[stdout.length-1].startsWith('#EOF')) { | |
| 212 stdout.removeLast(); | |
| 213 } | |
| 214 var done = false; | |
| 215 var i = 0; | |
| 216 var label = null; | |
| 217 var contentMarker = 'layer at '; | |
| 218 while (i < stdout.length) { | |
| 219 if (label == null && (label = parseLabel(stdout[i])) != null) { | |
| 220 if (label == 'NONEXISTENT') { | |
| 221 complete(); | |
| 222 return; | |
| 223 } | |
| 224 } else if (stdout[i].startsWith(contentMarker)) { | |
| 225 if (label == null) { | |
| 226 complete(); | |
| 227 return; | |
| 228 } | |
| 229 var expectedFileName = | |
| 230 '$sourceDir${Platform.pathSeparator}' | |
| 231 '${label.replaceAll("###", "_") | |
| 232 .replaceAll(new RegExp("[^A-Za-z0-9]"),"_")}.txt'; | |
| 233 var expected = new File(expectedFileName); | |
| 234 if (regenerate) { | |
| 235 var osink = expected.openWrite(); | |
| 236 while (i < stdout.length) { | |
| 237 osink.write(stdout[i]); | |
| 238 osink.write('\n'); | |
| 239 i++; | |
| 240 } | |
| 241 osink.close(); | |
| 242 pass(start, label); | |
| 243 } else if (!expected.existsSync()) { | |
| 244 fail(start, label, 'No expectation file'); | |
| 245 } else { | |
| 246 var lines = expected.readAsLinesSync(); | |
| 247 var actualLength = stdout.length - i; | |
| 248 var compareCount = min(lines.length, actualLength); | |
| 249 var match = true; | |
| 250 for (var j = 0; j < compareCount; j++) { | |
| 251 if (lines[j] != stdout[i + j]) { | |
| 252 fail(start, label, 'Expectation differs at line ${j + 1}'); | |
| 253 match = false; | |
| 254 break; | |
| 255 } | |
| 256 } | |
| 257 if (match) { | |
| 258 if (lines.length != actualLength) { | |
| 259 fail(start, label, 'Expectation file has wrong length'); | |
| 260 } else { | |
| 261 pass(start, label); | |
| 262 } | |
| 263 } | |
| 264 } | |
| 265 done = true; | |
| 266 break; | |
| 267 } | |
| 268 i++; | |
| 269 } | |
| 270 if (label != null) { | |
| 271 if (!done) error(start, label, 'Failed to parse output'); | |
| 272 runTextLayoutTest(testNum + 1); | |
| 273 } | |
| 274 }); | |
| 275 } | |
| 276 | |
| 277 runPixelLayoutTest(int testNum) { | |
| 278 var url = '$baseUrl?test=$testNum'; | |
| 279 var stdout = new List(); | |
| 280 var stderr = new List(); | |
| 281 start = new DateTime.now(); | |
| 282 runCommand(drt, ["$url'-p"], stdout, stderr, raw:true).then((exitCode) { | |
| 283 var contentMarker = 'Content-Length: '; | |
| 284 var eol = '\n'.codeUnitAt(0); | |
| 285 var pos = 0; | |
| 286 var label = null; | |
| 287 var done = false; | |
| 288 | |
| 289 while(pos < stdout.length) { | |
| 290 StringBuffer sb = new StringBuffer(); | |
| 291 while (pos < stdout.length && stdout[pos] != eol) { | |
| 292 sb.writeCharCode(stdout[pos++]); | |
| 293 } | |
| 294 if (++pos >= stdout.length && line == '') break; | |
| 295 var line = sb.toString(); | |
| 296 | |
| 297 if (label == null && (label = parseLabel(line)) != null) { | |
| 298 if (label == 'NONEXISTENT') { | |
| 299 complete(); | |
| 300 } | |
| 301 } else if (line.startsWith(contentMarker)) { | |
| 302 if (label == null) { | |
| 303 complete(); | |
| 304 } | |
| 305 var len = int.parse(line.substring(contentMarker.length)); | |
| 306 var expectedFileName = | |
| 307 '$sourceDir${Platform.pathSeparator}' | |
| 308 '${label.replaceAll("###","_"). | |
| 309 replaceAll(new RegExp("[^A-Za-z0-9]"),"_")}.png'; | |
| 310 var expected = new File(expectedFileName); | |
| 311 if (regenerate) { | |
| 312 var osink = expected.openWrite(); | |
| 313 stdout.removeRange(0, pos); | |
| 314 stdout.length = len; | |
| 315 osink.add(stdout); | |
| 316 osink.close(); | |
| 317 pass(start, label); | |
| 318 } else if (!expected.existsSync()) { | |
| 319 fail(start, label, 'No expectation file'); | |
| 320 } else { | |
| 321 var bytes = expected.readAsBytesSync(); | |
| 322 if (bytes.length != len) { | |
| 323 fail(start, label, 'Expectation file has wrong length'); | |
| 324 } else { | |
| 325 var match = true; | |
| 326 for (var j = 0; j < len; j++) { | |
| 327 if (bytes[j] != stdout[pos + j]) { | |
| 328 fail(start, label, 'Expectation differs at byte ${j + 1}'); | |
| 329 match = false; | |
| 330 break; | |
| 331 } | |
| 332 } | |
| 333 if (match) pass(start, label); | |
| 334 } | |
| 335 } | |
| 336 done = true; | |
| 337 break; | |
| 338 } | |
| 339 } | |
| 340 if (label != null) { | |
| 341 if (!done) error(start, label, 'Failed to parse output'); | |
| 342 runPixelLayoutTest(testNum + 1); | |
| 343 } | |
| 344 }); | |
| 345 } | |
| 346 | |
| 347 void init() { | |
| 348 // Get the name of the directory that has the expectation files | |
| 349 // (by stripping .dart suffix from test file path). | |
| 350 // Create it if it does not exist. | |
| 351 sourceDir = testfile.substring(0, testfile.length - 5); | |
| 352 if (regenerate) { | |
| 353 var d = new Directory(sourceDir); | |
| 354 if (!d.existsSync()) { | |
| 355 d.createSync(); | |
| 356 } | |
| 357 } | |
| 358 } | |
| 359 | |
| 360 void runPixelLayoutTests() { | |
| 361 init(); | |
| 362 runPixelLayoutTest(0); | |
| 363 } | |
| 364 | |
| 365 void runTextLayoutTests() { | |
| 366 init(); | |
| 367 runTextLayoutTest(0); | |
| 368 } | |
| OLD | NEW |