| 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 /** A pipeline task for running DumpRenderTree. */ | 5 /** A pipeline task for running DumpRenderTree. */ |
| 6 class DrtTask extends RunProcessTask { | 6 class DrtTask extends RunProcessTask { |
| 7 | 7 |
| 8 String _testFileTemplate; | 8 String _testFileTemplate; |
| 9 | 9 |
| 10 DrtTask(this._testFileTemplate, String htmlFileTemplate) { | 10 DrtTask(this._testFileTemplate, String htmlFileTemplate) { |
| 11 init(config.drtPath, ['--no-timeout', htmlFileTemplate], config.timeout); | 11 init(config.drtPath, ['--no-timeout', htmlFileTemplate], config.timeout); |
| 12 } | 12 } |
| 13 } |
| 13 | 14 |
| 14 // In order to extract the relevant parts of the DRT render text | |
| 15 // output we use a somewhat kludgy approach, but it should be robust. | |
| 16 // DRT formats output with indentation, and we know that the test title | |
| 17 // is on a line with 12 spaces indent followed by 'text run', while the | |
| 18 // IFrame body elements are all indented at least 18 spaces. | |
| 19 const TEST_LABEL_LINE_PREFIX = ' text run'; | |
| 20 const BODY_LINE_PREFIX = ' '; | |
| 21 | |
| 22 void execute(Path testfile, List stdout, List stderr, bool logging, | |
| 23 Function exitHandler) { | |
| 24 | |
| 25 var testname = expandMacros(_testFileTemplate, testfile); | |
| 26 var isLayout = isLayoutRenderTest(testname) || config.generateRenders; | |
| 27 | |
| 28 if (!isLayout) { | |
| 29 super.execute(testfile, stdout, stderr, logging, exitHandler); | |
| 30 } else { | |
| 31 var tmpLog = new List<String>(); | |
| 32 super.execute(testfile, tmpLog, tmpLog, true, | |
| 33 (code) { | |
| 34 var layoutFile = layoutFileName(testname); | |
| 35 var layouts = getFileContents(layoutFile, false); | |
| 36 var i = 0; | |
| 37 StringBuffer sbuf = null; | |
| 38 if (config.generateRenders) { | |
| 39 sbuf = new StringBuffer(); | |
| 40 } | |
| 41 while ( i < tmpLog.length) { | |
| 42 var line = tmpLog[i]; | |
| 43 if (logging) { | |
| 44 stdout.add(line); | |
| 45 } | |
| 46 if (line.startsWith(TEST_LABEL_LINE_PREFIX)) { | |
| 47 var j = i+1; | |
| 48 var start = -1, end = -1; | |
| 49 // Walk forward to the next label or end of log. | |
| 50 while (j < tmpLog.length && | |
| 51 !tmpLog[j].startsWith(TEST_LABEL_LINE_PREFIX)) { | |
| 52 // Is this a body render line? | |
| 53 if (tmpLog[j].startsWith(BODY_LINE_PREFIX)) { | |
| 54 if (start < 0) { // Is it the first? | |
| 55 start = j; | |
| 56 } | |
| 57 } else { // Not a render line. | |
| 58 if (start >= 0 && end < 0) { | |
| 59 // We were just in a set of render lines, so this | |
| 60 // line is the first non-member. | |
| 61 end = j; | |
| 62 } | |
| 63 } | |
| 64 j++; | |
| 65 } | |
| 66 if (start >= 0) { // We have some render lines. | |
| 67 if (end < 0) { | |
| 68 end = tmpLog.length; // Sanity. | |
| 69 } | |
| 70 var actualLayout = new List<String>(); | |
| 71 while (start < end) { | |
| 72 actualLayout.add( | |
| 73 tmpLog[start++].substring(BODY_LINE_PREFIX.length)); | |
| 74 } | |
| 75 var testName = checkTest(testfile, line, layouts, | |
| 76 actualLayout, stdout); | |
| 77 if (testName == null) { | |
| 78 code = -1; | |
| 79 } else if (config.generateRenders) { | |
| 80 sbuf.add(testName); | |
| 81 sbuf.add('\n'); | |
| 82 for (var renderLine in actualLayout) { | |
| 83 sbuf.add(renderLine); | |
| 84 sbuf.add('\n'); | |
| 85 } | |
| 86 } | |
| 87 } | |
| 88 i = j; | |
| 89 } else { | |
| 90 i++; | |
| 91 } | |
| 92 } | |
| 93 if (config.generateRenders) { | |
| 94 createFile(layoutFile, sbuf.toString()); | |
| 95 } | |
| 96 exitHandler(code); | |
| 97 }); | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 /** | |
| 102 * Verify whether a test passed - it must pass the code expectations, | |
| 103 * and have validated layout. Report success or failure in a test | |
| 104 * result message. Upon success the render section name is returned | |
| 105 * (useful for `config.generateRenders`); otherwise null is returned. | |
| 106 */ | |
| 107 String checkTest(Path testfile, String label, List layouts, | |
| 108 List actual, List out) { | |
| 109 var testGroup = null; | |
| 110 var testName = null; | |
| 111 | |
| 112 // The label line has the form: | |
| 113 // "result:duration:<test>//message" | |
| 114 // where <test> is a test name or series of one or more group names | |
| 115 // followed by a test name, separated by ###. | |
| 116 | |
| 117 // First extract test state, duration, name and message. If the test | |
| 118 // passed we can ignore these and continue to layout verification, but | |
| 119 // if the test failed we want to know that so we can report failure. | |
| 120 // | |
| 121 // TODO(gram) - currently we lose the stack trace although the user | |
| 122 // will get it in the overall output if they used --verbose. We may | |
| 123 // want to fix this properly at some point. | |
| 124 var labelParser = const RegExp('\"\([a-z]*\):\([0-9]*\):\(.*\)//\(.*\)\"'); | |
| 125 Match match = labelParser.firstMatch(label); | |
| 126 var result = match.group(1); | |
| 127 var duration = parseDouble(match.group(2)) / 1000; | |
| 128 var test = match.group(3); | |
| 129 var message = match.group(4); | |
| 130 | |
| 131 // Split name up with group. | |
| 132 var idx = test.lastIndexOf('###'); | |
| 133 if (idx >= 0) { | |
| 134 testGroup = test.substring(0, idx).replaceAll('###', ' '); | |
| 135 testName = test.substring(idx+3); | |
| 136 } else { | |
| 137 testGroup = ''; | |
| 138 testName = test; | |
| 139 } | |
| 140 var section = '[${_pad(testGroup)}$testName]'; | |
| 141 | |
| 142 if (config.generateRenders) { | |
| 143 // Do nothing; fake a pass. | |
| 144 out.add(_formatMessage(config.passFormat, | |
| 145 testfile, testGroup, testName, duration, '')); | |
| 146 } else if (result != 'pass') { | |
| 147 // The test failed separately from layout; just report that | |
| 148 // failure. | |
| 149 out.add(_formatMessage( | |
| 150 (result == 'fail' ? config.failFormat : config.errorFormat), | |
| 151 testfile, testGroup, testName, duration, message)); | |
| 152 return null; | |
| 153 } else { | |
| 154 // The test passed, at least the expectations. So check the layout. | |
| 155 var expected = _getLayout(layouts, section); | |
| 156 var failMessage = null; | |
| 157 var lineNum = 0; | |
| 158 if (expected != null) { | |
| 159 while (lineNum < expected.length) { | |
| 160 if (lineNum >= actual.length) { | |
| 161 failMessage = 'expected "${expected[lineNum]}" but got nothing'; | |
| 162 break; | |
| 163 } else { | |
| 164 if (expected[lineNum] != actual[lineNum]) { | |
| 165 failMessage = 'expected "${expected[lineNum]}" ' | |
| 166 'but got "${actual[lineNum]}"'; | |
| 167 break; | |
| 168 } | |
| 169 } | |
| 170 lineNum++; | |
| 171 } | |
| 172 if (failMessage == null && lineNum < actual.length) { | |
| 173 failMessage = 'expected nothing but got "${actual[lineNum]}"'; | |
| 174 } | |
| 175 } | |
| 176 if (failMessage != null) { | |
| 177 out.add(_formatMessage(config.failFormat, | |
| 178 testfile, testGroup, testName, duration, | |
| 179 'Layout content mismatch at line $lineNum: ' | |
| 180 '$failMessage')); | |
| 181 return null; | |
| 182 } else { | |
| 183 out.add(_formatMessage(config.passFormat, | |
| 184 testfile, testGroup, testName, duration, '')); | |
| 185 } | |
| 186 } | |
| 187 return section; | |
| 188 } | |
| 189 | |
| 190 /** Get the expected layout for a test. */ | |
| 191 List _getLayout(List layouts, String section) { | |
| 192 List layout = new List(); | |
| 193 for (var i = 0; i < layouts.length; i++) { | |
| 194 if (layouts[i] == section) { | |
| 195 ++i; | |
| 196 while (i < layouts.length && !layouts[i].startsWith('[')) { | |
| 197 layout.add(layouts[i++]); | |
| 198 } | |
| 199 break; | |
| 200 } | |
| 201 } | |
| 202 return layout; | |
| 203 } | |
| 204 | |
| 205 /** Pad a string with a rightmost space unless it is empty. */ | |
| 206 static String _pad(s) => (s.length > 0) ? '$s ' : s; | |
| 207 | |
| 208 /** Format a test result message. */ | |
| 209 String _formatMessage(String format, | |
| 210 Path testfile, String testGroup, String testName, | |
| 211 double duration, String message) { | |
| 212 String fname = makePathAbsolute(testfile.directoryPath.toString()); | |
| 213 return "###${format. | |
| 214 replaceAll(Macros.testTime, '${duration.toStringAsFixed(3)}s '). | |
| 215 replaceAll(Macros.testfile, _pad(fname)). | |
| 216 replaceAll(Macros.testGroup, _pad(testGroup)). | |
| 217 replaceAll(Macros.testDescription, _pad(testName)). | |
| 218 replaceAll(Macros.testMessage, _pad(message))}"; | |
| 219 } | |
| 220 } | |
| OLD | NEW |