Index: utils/testrunner/drt_task.dart |
=================================================================== |
--- utils/testrunner/drt_task.dart (revision 11963) |
+++ utils/testrunner/drt_task.dart (working copy) |
@@ -5,7 +5,216 @@ |
/** A pipeline task for running DumpRenderTree. */ |
class DrtTask extends RunProcessTask { |
- DrtTask(String htmlFileTemplate) { |
+ String _testFileTemplate; |
+ |
+ DrtTask(this._testFileTemplate, String htmlFileTemplate) { |
init(config.drtPath, ['--no-timeout', htmlFileTemplate], config.timeout); |
} |
+ |
+ // In order to extract the relevant parts of the DRT render text |
+ // output we use a somewhat kludgy approach, but it should be robust. |
+ // DRT formats output with indentation, and we know that the test title |
+ // is on a line with 12 spaces indent followed by 'text run', while the |
+ // IFrame body elements are all indented at least 18 spaces. |
+ const TEST_LABEL_LINE_PREFIX = ' text run'; |
+ const BODY_LINE_PREFIX = ' '; |
+ |
+ void execute(Path testfile, List stdout, List stderr, bool logging, |
+ Function exitHandler) { |
+ |
+ var testname = expandMacros(_testFileTemplate, testfile); |
+ var isLayout = isLayoutRenderTest(testname) || config.generateRenders; |
+ |
+ if (!isLayout) { |
+ super.execute(testfile, stdout, stderr, logging, exitHandler); |
+ } else { |
+ var tmpLog = new List<String>(); |
+ super.execute(testfile, tmpLog, tmpLog, true, |
+ (code) { |
+ var layoutFile = layoutFileName(testname); |
+ var layouts = getFileContents(layoutFile, false); |
+ var i = 0; |
+ StringBuffer sbuf = null; |
+ if (config.generateRenders) { |
+ sbuf = new StringBuffer(); |
+ } |
+ while ( i < tmpLog.length) { |
+ var line = tmpLog[i]; |
+ if (logging) { |
+ stdout.add(line); |
+ } |
+ if (line.startsWith(TEST_LABEL_LINE_PREFIX)) { |
+ var j = i+1; |
+ var start = -1, end = -1; |
+ // Walk forward to the next label or end of log. |
+ while (j < tmpLog.length && |
+ !tmpLog[j].startsWith(TEST_LABEL_LINE_PREFIX)) { |
+ // Is this a body render line? |
+ if (tmpLog[j].startsWith(BODY_LINE_PREFIX)) { |
+ if (start < 0) { // Is it the first? |
+ start = j; |
+ } |
+ } else { // Not a render line. |
+ if (start >= 0 && end < 0) { |
+ // We were just in a set of render lines, so this |
+ // line is the first non-member. |
+ end = j; |
+ } |
+ } |
+ j++; |
+ } |
+ if (start >= 0) { // We have some render lines. |
+ if (end < 0) { |
+ end = tmpLog.length; // Sanity. |
+ } |
+ var actualLayout = new List<String>(); |
+ while (start < end) { |
+ actualLayout.add( |
+ tmpLog[start++].substring(BODY_LINE_PREFIX.length)); |
+ } |
+ var testName = checkTest(testfile, line, layouts, |
+ actualLayout, stdout); |
+ if (testName == null) { |
+ code = -1; |
+ } else if (config.generateRenders) { |
+ sbuf.add(testName); |
+ sbuf.add('\n'); |
+ for (var renderLine in actualLayout) { |
+ sbuf.add(renderLine); |
+ sbuf.add('\n'); |
+ } |
+ } |
+ } |
+ i = j; |
+ } else { |
+ i++; |
+ } |
+ } |
+ if (config.generateRenders) { |
+ createFile(layoutFile, sbuf.toString()); |
+ } |
+ exitHandler(code); |
+ }); |
+ } |
+ } |
+ |
+ /** |
+ * Verify whether a test passed - it must pass the code expectations, |
+ * and have validated layout. Report success or failure in a test |
+ * result message. Upon success the render section name is returned |
+ * (useful for `config.generateRenders`); otherwise null is returned. |
+ */ |
+ String checkTest(Path testfile, String label, List layouts, |
+ List actual, List out) { |
+ var testGroup = null; |
+ var testName = null; |
+ |
+ // The label line has the form: |
+ // "result:duration:<test>//message" |
+ // where <test> is a test name or series of one or more group names |
+ // followed by a test name, separated by ###. |
+ |
+ // First extract test state, duration, name and message. If the test |
+ // passed we can ignore these and continue to layout verification, but |
+ // if the test failed we want to know that so we can report failure. |
+ // |
+ // TODO(gram) - currently we lose the stack trace although the user |
+ // will get it in the overall output if they used --verbose. We may |
+ // want to fix this properly at some point. |
+ var labelParser = const RegExp('\"\([a-z]*\):\([0-9]*\):\(.*\)//\(.*\)\"'); |
+ Match match = labelParser.firstMatch(label); |
+ var result = match.group(1); |
+ var duration = parseDouble(match.group(2)) / 1000; |
+ var test = match.group(3); |
+ var message = match.group(4); |
+ |
+ // Split name up with group. |
+ var idx = test.lastIndexOf('###'); |
+ if (idx >= 0) { |
+ testGroup = test.substring(0, idx).replaceAll('###', ' '); |
+ testName = test.substring(idx+3); |
+ } else { |
+ testGroup = ''; |
+ testName = test; |
+ } |
+ var section = '[${_pad(testGroup)}$testName]'; |
+ |
+ if (config.generateRenders) { |
+ // Do nothing; fake a pass. |
+ out.add(_formatMessage(config.passFormat, |
+ testfile, testGroup, testName, duration, '')); |
+ } else if (result != 'pass') { |
+ // The test failed separately from layout; just report that |
+ // failure. |
+ out.add(_formatMessage( |
+ (result == 'fail' ? config.failFormat : config.errorFormat), |
+ testfile, testGroup, testName, duration, message)); |
+ return null; |
+ } else { |
+ // The test passed, at least the expectations. So check the layout. |
+ var expected = _getLayout(layouts, section); |
+ var failMessage = null; |
+ var lineNum = 0; |
+ if (expected != null) { |
+ while (lineNum < expected.length) { |
+ if (lineNum >= actual.length) { |
+ failMessage = 'expected "${expected[lineNum]}" but got nothing'; |
+ break; |
+ } else { |
+ if (expected[lineNum] != actual[lineNum]) { |
+ failMessage = 'expected "${expected[lineNum]}" ' |
+ 'but got "${actual[lineNum]}"'; |
+ break; |
+ } |
+ } |
+ lineNum++; |
+ } |
+ if (failMessage == null && lineNum < actual.length) { |
+ failMessage = 'expected nothing but got "${actual[lineNum]}"'; |
+ } |
+ } |
+ if (failMessage != null) { |
+ out.add(_formatMessage(config.failFormat, |
+ testfile, testGroup, testName, duration, |
+ 'Layout content mismatch at line $lineNum: ' |
+ '$failMessage')); |
+ return null; |
+ } else { |
+ out.add(_formatMessage(config.passFormat, |
+ testfile, testGroup, testName, duration, '')); |
+ } |
+ } |
+ return section; |
+ } |
+ |
+ /** Get the expected layout for a test. */ |
+ List _getLayout(List layouts, String section) { |
+ List layout = new List(); |
+ for (var i = 0; i < layouts.length; i++) { |
+ if (layouts[i] == section) { |
+ ++i; |
+ while (i < layouts.length && !layouts[i].startsWith('[')) { |
+ layout.add(layouts[i++]); |
+ } |
+ break; |
+ } |
+ } |
+ return layout; |
+ } |
+ |
+ /** Pad a string with a rightmost space unless it is empty. */ |
+ static String _pad(s) => (s.length > 0) ? '$s ' : s; |
+ |
+ /** Format a test result message. */ |
+ String _formatMessage(String format, |
+ Path testfile, String testGroup, String testName, |
+ double duration, String message) { |
+ String fname = makePathAbsolute(testfile.directoryPath.toString()); |
+ return "###${format. |
+ replaceAll(Macros.testTime, '${duration.toStringAsFixed(3)}s '). |
+ replaceAll(Macros.testfile, _pad(fname)). |
+ replaceAll(Macros.testGroup, _pad(testGroup)). |
+ replaceAll(Macros.testDescription, _pad(testName)). |
+ replaceAll(Macros.testMessage, _pad(message))}"; |
+ } |
} |