Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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 /** | 5 /** |
| 6 * Classes and methods for executing tests. | 6 * Classes and methods for executing tests. |
| 7 * | 7 * |
| 8 * This module includes: | 8 * This module includes: |
| 9 * - Managing parallel execution of tests, including timeout checks. | 9 * - Managing parallel execution of tests, including timeout checks. |
| 10 * - Evaluating the output of each test as pass/fail/crash/timeout. | 10 * - Evaluating the output of each test as pass/fail/crash/timeout. |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 155 arguments.getRange(1, arguments.length - 1); | 155 arguments.getRange(1, arguments.length - 1); |
| 156 } | 156 } |
| 157 | 157 |
| 158 | 158 |
| 159 /** | 159 /** |
| 160 * TestOutput records the output of a completed test: the process's exit code, | 160 * TestOutput records the output of a completed test: the process's exit code, |
| 161 * the standard output and standard error, whether the process timed out, and | 161 * the standard output and standard error, whether the process timed out, and |
| 162 * the time the process took to run. It also contains a pointer to the | 162 * the time the process took to run. It also contains a pointer to the |
| 163 * [TestCase] this is the output of. | 163 * [TestCase] this is the output of. |
| 164 */ | 164 */ |
| 165 class TestOutput { | 165 |
| 166 // TODO(zundel): should be abstract? | |
| 167 class TestOutput { | |
| 168 | |
|
Bill Hesse
2012/02/28 14:06:38
Could this be an interface? Do interfaces have fa
zundel
2012/02/28 14:12:09
you can put a constructor and provide a default im
| |
| 169 TestOutput() { | |
| 170 } | |
|
Bill Hesse
2012/02/28 14:06:38
Is the split between TestOutput and TestOutputImpl
zundel
2012/02/28 14:12:09
On 2012/02/28 14:06:38, Bill Hesse wrote:
> Is the
| |
| 171 | |
|
Bill Hesse
2012/02/28 14:06:38
I think the factory should just be called TestOutp
zundel
2012/02/28 14:12:09
I agree, BUT it turns out that there is an implici
zundel
2012/02/28 14:22:35
I meant factory constructor. Like this:
class Fo
| |
| 172 factory TestOutput.fromCase (testCase, exitCode, timedOut, stdout, stderr, | |
| 173 time) { | |
| 174 if (testCase is BrowserTestCase) { | |
| 175 return new BrowserTestOutputImpl(testCase, exitCode, timedOut, | |
| 176 stdout, stderr, time); | |
| 177 } else if (testCase.configuration['component'] == 'dartc') { | |
| 178 return new AnalysisTestOutputImpl(testCase, exitCode, timedOut, | |
| 179 stdout, stderr, time); | |
| 180 } | |
| 181 return new TestOutputImpl(testCase, exitCode, timedOut, | |
| 182 stdout, stderr, time); | |
| 183 } | |
| 184 | |
| 185 abstract String get result(); | |
| 186 | |
| 187 abstract bool get unexpectedOutput(); | |
| 188 | |
| 189 abstract bool get hasCrashed(); | |
| 190 | |
| 191 abstract bool get hasTimedOut(); | |
| 192 | |
| 193 abstract bool get didFail(); | |
| 194 | |
| 195 List<String> get errors() { return []; } | |
| 196 | |
| 197 List<String> get staticWarnings() { return []; } | |
| 198 | |
| 199 List<String> get warnings() { return []; } | |
| 200 } | |
| 201 | |
| 202 class TestOutputImpl extends TestOutput { | |
| 166 TestCase testCase; | 203 TestCase testCase; |
| 167 int exitCode; | 204 int exitCode; |
| 168 bool timedOut; | 205 bool timedOut; |
| 169 bool failed = false; | 206 bool failed = false; |
| 170 List<String> stdout; | 207 List<String> stdout; |
| 171 List<String> stderr; | 208 List<String> stderr; |
| 172 Duration time; | 209 Duration time; |
| 210 | |
| 173 /** | 211 /** |
| 174 * Set to true if we encounter a condition in the output that indicates we | 212 * Set to true if we encounter a condition in the output that indicates we |
| 175 * need to rerun this test. | 213 * need to rerun this test. |
| 176 */ | 214 */ |
| 177 bool requestRetry; | 215 bool requestRetry; |
| 178 | 216 |
| 179 TestOutput(this.testCase, this.exitCode, this.timedOut, this.stdout, | 217 TestOutputImpl(this.testCase, this.exitCode, this.timedOut, this.stdout, |
| 180 this.stderr, this.time) { | 218 this.stderr, this.time) { |
| 181 testCase.output = this; | 219 testCase.output = this; |
| 182 requestRetry = false; | 220 requestRetry = false; |
| 183 } | 221 } |
| 184 | 222 |
| 185 String get result() => | 223 String get result() => |
| 186 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); | 224 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); |
| 187 | 225 |
| 188 bool get unexpectedOutput() => !testCase.expectedOutcomes.contains(result); | 226 bool get unexpectedOutput() => !testCase.expectedOutcomes.contains(result); |
| 189 | 227 |
| 190 bool get hasCrashed() { | 228 bool get hasCrashed() { |
| 191 if (new Platform().operatingSystem() == 'windows') { | 229 if (new Platform().operatingSystem() == 'windows') { |
| 192 // The VM uses std::abort to terminate on asserts. | 230 // The VM uses std::abort to terminate on asserts. |
| 193 // std::abort terminates with exit code 3 on Windows. | 231 // std::abort terminates with exit code 3 on Windows. |
| 194 if (exitCode == 3) { | 232 if (exitCode == 3) { |
| 195 return !timedOut; | 233 return !timedOut; |
| 196 } | 234 } |
| 197 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); | 235 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); |
| 198 } | 236 } |
| 199 // The Java dartc runner exits with code 253 in case of unhandled | 237 // The Java dartc runner exits with code 253 in case of unhandled |
| 200 // exceptions. | 238 // exceptions. |
| 201 return (!timedOut && ((exitCode < 0) || (exitCode == 253))); | 239 return (!timedOut && ((exitCode < 0) || (exitCode == 253))); |
| 202 } | 240 } |
| 203 | 241 |
| 204 bool get hasTimedOut() => timedOut; | 242 bool get hasTimedOut() => timedOut; |
| 205 | 243 |
| 206 bool get didFail() { | 244 bool get didFail() { |
| 207 if (testCase is !BrowserTestCase) return (exitCode != 0 && !hasCrashed); | 245 return (exitCode != 0 && !hasCrashed); |
| 246 } | |
| 247 | |
| 248 // Reverse result of a negative test. | |
| 249 bool get hasFailed() => (testCase.isNegative ? !didFail : didFail); | |
| 250 } | |
| 208 | 251 |
| 252 class BrowserTestOutputImpl extends TestOutputImpl { | |
|
Bill Hesse
2012/02/28 14:06:38
This is good. A step towards a final refactoring,
| |
| 253 BrowserTestOutputImpl(testCase, exitCode, timedOut, stdout, stderr, time) : | |
| 254 super(testCase, exitCode, timedOut, stdout, stderr, time); | |
| 255 | |
| 256 bool get didFail() { | |
| 209 // Browser case: | 257 // Browser case: |
| 210 // If the browser test failed, it may have been because DumpRenderTree | 258 // If the browser test failed, it may have been because DumpRenderTree |
| 211 // and the virtual framebuffer X server didn't hook up, or DRT crashed with | 259 // and the virtual framebuffer X server didn't hook up, or DRT crashed with |
| 212 // a core dump. Sometimes DRT crashes after it has set the stdout to PASS, | 260 // a core dump. Sometimes DRT crashes after it has set the stdout to PASS, |
| 213 // so we have to do this check first. | 261 // so we have to do this check first. |
| 214 for (String line in stderr) { | 262 for (String line in stderr) { |
| 215 if (line.contains('Gtk-WARNING **: cannot open display: :99') || | 263 if (line.contains('Gtk-WARNING **: cannot open display: :99') || |
| 216 line.contains('Failed to run command. return code=1')) { | 264 line.contains('Failed to run command. return code=1')) { |
| 217 // If we get the X server error, or DRT crashes with a core dump, retry | 265 // If we get the X server error, or DRT crashes with a core dump, retry |
| 218 // the test. | 266 // the test. |
| 219 requestRetry = true; | 267 requestRetry = true; |
| 220 return true; | 268 return true; |
| 221 } | 269 } |
| 222 } | 270 } |
| 223 | 271 |
| 224 // Browser tests fail unless stdout contains | 272 // Browser tests fail unless stdout contains |
| 225 // 'Content-Type: text/plain\nPASS'. | 273 // 'Content-Type: text/plain\nPASS'. |
| 226 String previous_line = ''; | 274 String previous_line = ''; |
| 227 for (String line in stdout) { | 275 for (String line in stdout) { |
| 228 if (line == 'PASS' && previous_line == 'Content-Type: text/plain') { | 276 if (line == 'PASS' && previous_line == 'Content-Type: text/plain') { |
| 229 return (exitCode != 0 && !hasCrashed); | 277 return (exitCode != 0 && !hasCrashed); |
| 230 } | 278 } |
| 231 previous_line = line; | 279 previous_line = line; |
| 232 } | 280 } |
| 233 | |
| 234 return true; | 281 return true; |
| 235 } | 282 } |
| 283 } | |
| 236 | 284 |
| 237 // Reverse result of a negative test. | 285 class AnalysisTestOutputImpl extends TestOutputImpl { |
| 238 bool get hasFailed() => (testCase.isNegative ? !didFail : didFail); | 286 final errors; |
| 287 final warnings; | |
| 288 final staticWarnings; | |
| 289 | |
| 290 AnalysisTestOutputImpl(testCase, exitCode, timedOut, stdout, stderr, time) : | |
| 291 super(testCase, exitCode, timedOut, stdout, stderr, time), | |
| 292 errors = new Map<int, String>(), | |
| 293 warnings = new Map<int, String>(), | |
| 294 staticWarnings = new Map<int, String>() { | |
| 295 | |
| 296 // read the returned list of errors and stuff them away. | |
| 297 for (String line in stderr) { | |
| 298 if (line.length == 0) continue; | |
| 299 List<String> fields = splitMachineError(line); | |
| 300 switch(fields[0]) { | |
| 301 case "ERROR": | |
| 302 errors[fields[4]] = fields; | |
| 303 break; | |
| 304 case "WARNING": | |
| 305 if (fields[1] == "STATIC_TYPE") { | |
| 306 staticWarnings[fields[4]] = fields; | |
| 307 } else { | |
| 308 warnings[fields[4]] = fields; | |
| 309 } | |
| 310 break; | |
| 311 } | |
|
Bill Hesse
2012/02/28 14:06:38
Do you want a default case here, or an assert?
zundel
2012/02/28 14:12:09
There is some output to stderr that needs to be ig
| |
| 312 } | |
| 313 } | |
| 314 | |
| 315 bool get didFail() { | |
| 316 if (hasCrashed) return false; | |
| 317 | |
| 318 if (this.testCase.commandLine.contains('--fatal-type-errors')) { | |
| 319 if (staticWarnings.length != 0) { | |
| 320 return true; | |
| 321 } | |
| 322 } else if (errors.length != 0) { | |
| 323 return true; | |
| 324 } | |
| 325 | |
| 326 // TODO(zundel): more sophisticated analysis | |
| 327 | |
| 328 return false; | |
| 329 //return exitCode != 0; | |
| 330 } | |
| 331 | |
| 332 // Parse a line delimited by the | character using \ as an escape charager | |
| 333 // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ | |
| 334 List<String> splitMachineError(String line) { | |
| 335 StringBuffer field = new StringBuffer(); | |
| 336 List<String> result = []; | |
| 337 bool escaped = false; | |
| 338 for (var i = 0 ; i < line.length; i++) { | |
| 339 var c = line[i]; | |
| 340 if (!escaped && c == '\\') { | |
| 341 escaped = true; | |
| 342 continue; | |
| 343 } | |
| 344 escaped = false; | |
| 345 if (c == '|') { | |
| 346 result.add(field.toString()); | |
| 347 field.clear(); | |
| 348 continue; | |
| 349 } | |
| 350 field.add(c); | |
| 351 } | |
| 352 result.add(field.toString()); | |
| 353 return result; | |
| 354 } | |
| 239 } | 355 } |
| 240 | 356 |
| 241 /** | 357 /** |
| 242 * A RunningProcess actually runs a test, getting the command lines from | 358 * A RunningProcess actually runs a test, getting the command lines from |
| 243 * its [TestCase], starting the test process (and first, a compilation | 359 * its [TestCase], starting the test process (and first, a compilation |
| 244 * process if the TestCase is a [BrowserTestCase]), creating a timeout | 360 * process if the TestCase is a [BrowserTestCase]), creating a timeout |
| 245 * timer, and recording the results in a new [TestOutput] object, which it | 361 * timer, and recording the results in a new [TestOutput] object, which it |
| 246 * attaches to the TestCase. The lifetime of the RunningProcess is limited | 362 * attaches to the TestCase. The lifetime of the RunningProcess is limited |
| 247 * to the time it takes to start the process, run the process, and record | 363 * to the time it takes to start the process, run the process, and record |
| 248 * the result; there are no pointers to it, so it should be available to | 364 * the result; there are no pointers to it, so it should be available to |
| 249 * be garbage collected as soon as it is done. | 365 * be garbage collected as soon as it is done. |
| 250 */ | 366 */ |
| 251 class RunningProcess { | 367 class RunningProcess { |
| 252 ProcessQueue processQueue; | 368 ProcessQueue processQueue; |
| 253 Process process; | 369 Process process; |
| 254 TestCase testCase; | 370 TestCase testCase; |
| 255 bool timedOut = false; | 371 bool timedOut = false; |
| 256 Date startTime; | 372 Date startTime; |
| 257 Timer timeoutTimer; | 373 Timer timeoutTimer; |
| 258 List<String> stdout; | 374 List<String> stdout; |
| 259 List<String> stderr; | 375 List<String> stderr; |
| 260 List<Function> handlers; | 376 List<Function> handlers; |
| 261 bool allowRetries = false; | 377 bool allowRetries = false; |
| 262 | 378 |
| 263 RunningProcess(TestCase this.testCase, | 379 RunningProcess(TestCase this.testCase, |
| 264 [this.allowRetries, this.processQueue]); | 380 [this.allowRetries, this.processQueue]); |
| 265 | 381 |
| 266 void exitHandler(int exitCode) { | 382 void exitHandler(int exitCode) { |
| 267 new TestOutput(testCase, exitCode, timedOut, stdout, | 383 new TestOutput.fromCase(testCase, exitCode, timedOut, stdout, |
| 268 stderr, new Date.now().difference(startTime)); | 384 stderr, new Date.now().difference(startTime)); |
| 269 process.close(); | 385 process.close(); |
| 270 timeoutTimer.cancel(); | 386 timeoutTimer.cancel(); |
| 271 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 387 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
| 272 print(testCase.displayName); | 388 print(testCase.displayName); |
| 273 for (var line in testCase.output.stderr) print(line); | 389 for (var line in testCase.output.stderr) print(line); |
| 274 for (var line in testCase.output.stdout) print(line); | 390 for (var line in testCase.output.stdout) print(line); |
| 275 } | 391 } |
| 276 if (allowRetries != null && allowRetries | 392 if (allowRetries != null && allowRetries |
| 277 && testCase.configuration['component'] == 'webdriver' && | 393 && testCase.configuration['component'] == 'webdriver' && |
| 278 testCase.output.unexpectedOutput && testCase.numRetries > 0) { | 394 testCase.output.unexpectedOutput && testCase.numRetries > 0) { |
| (...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 449 | 565 |
| 450 int _reportResult(String output) { | 566 int _reportResult(String output) { |
| 451 var test = _currentTest; | 567 var test = _currentTest; |
| 452 _currentTest = null; | 568 _currentTest = null; |
| 453 | 569 |
| 454 // output = '>>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT}' | 570 // output = '>>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT}' |
| 455 var outcome = output.split(" ")[2]; | 571 var outcome = output.split(" ")[2]; |
| 456 var exitCode = 0; | 572 var exitCode = 0; |
| 457 if (outcome == "CRASH") exitCode = -10; | 573 if (outcome == "CRASH") exitCode = -10; |
| 458 if (outcome == "FAIL" || outcome == "TIMEOUT") exitCode = 1; | 574 if (outcome == "FAIL" || outcome == "TIMEOUT") exitCode = 1; |
| 459 new TestOutput(test, exitCode, outcome == "TIMEOUT", _testStdout, | 575 new TestOutput.fromCase(test, exitCode, outcome == "TIMEOUT", _testStdout, |
| 460 _testStderr, new Date.now().difference(_startTime)); | 576 _testStderr, new Date.now().difference(_startTime)); |
| 461 test.completed(); | 577 test.completed(); |
| 462 } | 578 } |
| 463 | 579 |
| 464 Function _readOutput(StringInputStream stream, List<String> buffer) { | 580 Function _readOutput(StringInputStream stream, List<String> buffer) { |
| 465 return () { | 581 return () { |
| 466 var status; | 582 var status; |
| 467 var line = stream.readLine(); | 583 var line = stream.readLine(); |
| 468 // Drain the input stream to get the error output. | 584 // Drain the input stream to get the error output. |
| 469 while (line != null) { | 585 while (line != null) { |
| 470 if (line.startsWith('>>> TEST')) { | 586 if (line.startsWith('>>> TEST')) { |
| (...skipping 286 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 757 // the developer doesn't waste his or her time trying to fix a bunch of | 873 // the developer doesn't waste his or her time trying to fix a bunch of |
| 758 // tests that appear to be broken but were actually just flakes that | 874 // tests that appear to be broken but were actually just flakes that |
| 759 // didn't get retried because there had already been one failure. | 875 // didn't get retried because there had already been one failure. |
| 760 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 876 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 761 new RunningProcess(test, allowRetry, this).start(); | 877 new RunningProcess(test, allowRetry, this).start(); |
| 762 } | 878 } |
| 763 _numProcesses++; | 879 _numProcesses++; |
| 764 } | 880 } |
| 765 } | 881 } |
| 766 } | 882 } |
| OLD | NEW |