Summarizer.java revision ea46f2595a8718d4478e016fd40b2d57658289cf
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dumprendertree2; 18 19import java.io.File; 20import java.text.SimpleDateFormat; 21import java.util.ArrayList; 22import java.util.Collections; 23import java.util.Date; 24import java.util.List; 25 26/** 27 * A class that collects information about tests that ran and can create HTML 28 * files with summaries and easy navigation. 29 */ 30public class Summarizer { 31 32 private static final String LOG_TAG = "Summarizer"; 33 34 private static final String CSS = 35 "<style type=\"text/css\">" + 36 "* {" + 37 " font-family: Verdana;" + 38 " border: 0;" + 39 " margin: 0;" + 40 " padding: 0;}" + 41 "body {" + 42 " margin: 10px;}" + 43 "h1 {" + 44 " font-size: 24px;" + 45 " margin: 4px 0 4px 0;}" + 46 "h2 {" + 47 " font-size:18px;" + 48 " text-transform: uppercase;" + 49 " margin: 20px 0 3px 0;}" + 50 "h3, h3 a {" + 51 " font-size: 14px;" + 52 " color: black;" + 53 " text-decoration: none;" + 54 " margin-bottom: 4px;}" + 55 "h3 a span.path {" + 56 " text-decoration: underline;}" + 57 "h3 span.tri {" + 58 " text-decoration: none;" + 59 " float: left;" + 60 " width: 20px;}" + 61 "h3 span.sqr {" + 62 " text-decoration: none;" + 63 " color: #8ee100;" + 64 " float: left;" + 65 " width: 20px;}" + 66 "h3 img {" + 67 " width: 8px;" + 68 " margin-right: 4px;}" + 69 "div.diff {" + 70 " margin-bottom: 25px;}" + 71 "div.diff a {" + 72 " font-size: 12px;" + 73 " color: #888;}" + 74 "table.visual_diff {" + 75 " border-bottom: 0px solid;" + 76 " border-collapse: collapse;" + 77 " width: 100%;" + 78 " margin-bottom: 2px;}" + 79 "table.visual_diff tr.headers td {" + 80 " border-bottom: 1px solid;" + 81 " border-top: 0;" + 82 " padding-bottom: 3px;}" + 83 "table.visual_diff tr.results td {" + 84 " border-top: 1px dashed;" + 85 " border-right: 1px solid;" + 86 " font-size: 15px;" + 87 " vertical-align: top;}" + 88 "table.visual_diff tr.results td.line_count {" + 89 " background-color:#aaa;" + 90 " min-width:20px;" + 91 " text-align: right;" + 92 " border-right: 1px solid;" + 93 " border-left: 1px solid;" + 94 " padding: 2px 1px 2px 0px;}" + 95 "table.visual_diff tr.results td.line {" + 96 " padding: 2px 0px 2px 4px;" + 97 " border-right: 1px solid;" + 98 " width: 49.8%;}" + 99 "table.visual_diff tr.footers td {" + 100 " border-top: 1px solid;" + 101 " border-bottom: 0;}" + 102 "table.visual_diff tr td.space {" + 103 " border: 0;" + 104 " width: 0.4%}" + 105 "div.space {" + 106 " margin-top:4px;}" + 107 "span.eql {" + 108 " background-color: #f3f3f3;}" + 109 "span.del {" + 110 " background-color: #ff8888; }" + 111 "span.ins {" + 112 " background-color: #88ff88; }" + 113 "span.fail {" + 114 " color: red;}" + 115 "span.pass {" + 116 " color: green;}" + 117 "span.time_out {" + 118 " color: orange;}" + 119 "table.summary {" + 120 " border: 1px solid black;" + 121 " margin-top: 20px;}" + 122 "table.summary td {" + 123 " padding: 3px;}" + 124 "span.listItem {" + 125 " font-size: 11px;" + 126 " font-weight: normal;" + 127 " text-transform: uppercase;" + 128 " padding: 3px;" + 129 " -webkit-border-radius: 4px;}" + 130 "span." + AbstractResult.ResultCode.PASS.name() + "{" + 131 " background-color: #8ee100;" + 132 " color: black;}" + 133 "span." + AbstractResult.ResultCode.FAIL_RESULT_DIFFERS.name() + "{" + 134 " background-color: #ccc;" + 135 " color: black;}" + 136 "span." + AbstractResult.ResultCode.FAIL_NO_EXPECTED_RESULT.name() + "{" + 137 " background-color: #a700e4;" + 138 " color: #fff;}" + 139 "span." + AbstractResult.ResultCode.FAIL_TIMED_OUT.name() + "{" + 140 " background-color: #f3cb00;" + 141 " color: black;}" + 142 "span." + AbstractResult.ResultCode.FAIL_CRASHED.name() + "{" + 143 " background-color: #c30000;" + 144 " color: #fff;}" + 145 "span.noLtc {" + 146 " background-color: #944000;" + 147 " color: #fff;}" + 148 "span.noEventSender {" + 149 " background-color: #815600;" + 150 " color: #fff;}" + 151 "</style>"; 152 153 private static final String SCRIPT = 154 "<script type=\"text/javascript\">" + 155 " function toggleDisplay(id) {" + 156 " element = document.getElementById(id);" + 157 " triangle = document.getElementById('tri.' + id);" + 158 " if (element.style.display == 'none') {" + 159 " element.style.display = 'inline';" + 160 " triangle.innerHTML = '▼ ';" + 161 " } else {" + 162 " element.style.display = 'none';" + 163 " triangle.innerHTML = '▶ ';" + 164 " }" + 165 " }" + 166 "</script>"; 167 168 /** TODO: Make it a setting */ 169 private static final String HTML_SUMMARY_RELATIVE_PATH = "summary.html"; 170 private static final String TXT_SUMMARY_RELATIVE_PATH = "summary.txt"; 171 172 private int mCrashedTestsCount = 0; 173 private List<AbstractResult> mFailedNotIgnoredTests = new ArrayList<AbstractResult>(); 174 private List<AbstractResult> mIgnoredTests = new ArrayList<AbstractResult>(); 175 private List<String> mPassedNotIgnoredTests = new ArrayList<String>(); 176 177 private FileFilter mFileFilter; 178 private String mResultsRootDirPath; 179 180 private String mTitleString; 181 182 public Summarizer(FileFilter fileFilter, String resultsRootDirPath) { 183 mFileFilter = fileFilter; 184 mResultsRootDirPath = resultsRootDirPath; 185 } 186 187 public void appendTest(AbstractResult result) { 188 String relativePath = result.getRelativePath(); 189 190 if (result.getResultCode() == AbstractResult.ResultCode.FAIL_CRASHED) { 191 mCrashedTestsCount++; 192 } 193 194 if (mFileFilter.isIgnoreRes(relativePath)) { 195 mIgnoredTests.add(result); 196 } else if (result.getResultCode() == AbstractResult.ResultCode.PASS) { 197 mPassedNotIgnoredTests.add(relativePath); 198 } else { 199 mFailedNotIgnoredTests.add(result); 200 } 201 } 202 203 public void summarize() { 204 createHtmlSummary(); 205 createTxtSummary(); 206 } 207 208 public void reset() { 209 mCrashedTestsCount = 0; 210 mFailedNotIgnoredTests.clear(); 211 mIgnoredTests.clear(); 212 mPassedNotIgnoredTests.clear(); 213 mTitleString = null; 214 } 215 216 private void createTxtSummary() { 217 StringBuilder txt = new StringBuilder(); 218 219 txt.append(getTitleString() + "\n"); 220 if (mCrashedTestsCount > 0) { 221 txt.append("CRASHED (total among all tests): " + mCrashedTestsCount + "\n"); 222 txt.append("-------------"); 223 } 224 txt.append("FAILED: " + mFailedNotIgnoredTests.size() + "\n"); 225 txt.append("IGNORED: " + mIgnoredTests.size() + "\n"); 226 txt.append("PASSED: " + mPassedNotIgnoredTests.size() + "\n"); 227 228 FsUtils.writeDataToStorage(new File(mResultsRootDirPath, TXT_SUMMARY_RELATIVE_PATH), 229 txt.toString().getBytes(), false); 230 } 231 232 private void createHtmlSummary() { 233 StringBuilder html = new StringBuilder(); 234 235 html.append("<html><head>"); 236 html.append(CSS); 237 html.append(SCRIPT); 238 html.append("</head><body>"); 239 240 createTopSummaryTable(html); 241 242 createResultsListWithDiff(html, "Failed", mFailedNotIgnoredTests); 243 244 createResultsListWithDiff(html, "Ignored", mIgnoredTests); 245 246 createResultsListNoDiff(html, "Passed", mPassedNotIgnoredTests); 247 248 html.append("</body></html>"); 249 250 FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_SUMMARY_RELATIVE_PATH), 251 html.toString().getBytes(), false); 252 } 253 254 private String getTitleString() { 255 if (mTitleString == null) { 256 int total = mFailedNotIgnoredTests.size() + 257 mPassedNotIgnoredTests.size() + 258 mIgnoredTests.size(); 259 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 260 mTitleString = " - total of " + total + " tests - " + dateFormat.format(new Date()); 261 } 262 263 return mTitleString; 264 } 265 266 private void createTopSummaryTable(StringBuilder html) { 267 html.append("<h1>" + getTitleString() + "</h1>"); 268 269 html.append("<table class=\"summary\">"); 270 createSummaryTableRow(html, "CRASHED", mCrashedTestsCount); 271 createSummaryTableRow(html, "FAILED", mFailedNotIgnoredTests.size()); 272 createSummaryTableRow(html, "IGNORED", mIgnoredTests.size()); 273 createSummaryTableRow(html, "PASSED", mPassedNotIgnoredTests.size()); 274 html.append("</table>"); 275 } 276 277 private void createSummaryTableRow(StringBuilder html, String caption, int size) { 278 html.append("<tr>"); 279 html.append(" <td>" + caption + "</td>"); 280 html.append(" <td>" + size + "</td>"); 281 html.append("</tr>"); 282 } 283 284 private void createResultsListWithDiff(StringBuilder html, String title, 285 List<AbstractResult> resultsList) { 286 String relativePath; 287 String id = ""; 288 AbstractResult.ResultCode resultCode; 289 290 Collections.sort(resultsList); 291 html.append("<h2>" + title + " [" + resultsList.size() + "]</h2>"); 292 for (AbstractResult result : resultsList) { 293 relativePath = result.getRelativePath(); 294 resultCode = result.getResultCode(); 295 296 html.append("<h3>"); 297 298 if (resultCode == AbstractResult.ResultCode.PASS) { 299 html.append("<span class=\"sqr\">■ </span>"); 300 html.append("<span class=\"path\">" + relativePath + "</span>"); 301 } else { 302 /** 303 * Technically, two different paths could end up being the same, because 304 * ':' is a valid character in a path. However, it is probably not going 305 * to cause any problems in this case 306 */ 307 id = relativePath.replace(File.separator, ":"); 308 html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');"); 309 html.append("return false;\">"); 310 html.append("<span class=\"tri\" id=\"tri." + id + "\">▶ </span>"); 311 html.append("<span class=\"path\">" + relativePath + "</span>"); 312 html.append("</a>"); 313 } 314 315 html.append(" <span class=\"listItem " + resultCode.name() + "\">"); 316 html.append(resultCode.toString()); 317 html.append("</span>"); 318 319 /** Detect missing LTC function */ 320 String additionalTextOutputString = result.getAdditionalTextOutputString(); 321 if (additionalTextOutputString != null && 322 additionalTextOutputString.contains("com.android.dumprendertree") && 323 additionalTextOutputString.contains("has no method")) { 324 if (additionalTextOutputString.contains("LayoutTestController")) { 325 html.append(" <span class=\"listItem noLtc\">LTC function missing</span>"); 326 } 327 if (additionalTextOutputString.contains("EventSender")) { 328 html.append(" <span class=\"listItem noEventSender\">"); 329 html.append("ES function missing</span>"); 330 } 331 } 332 333 html.append("</h3>"); 334 335 if (resultCode != AbstractResult.ResultCode.PASS) { 336 html.append("<div class=\"diff\" style=\"display: none;\" id=\"" + id + "\">"); 337 html.append(result.getDiffAsHtml()); 338 html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');"); 339 html.append("return false;\">Hide</a>"); 340 html.append("</div>"); 341 } 342 343 html.append("<div class=\"space\"></div>"); 344 } 345 } 346 347 private void createResultsListNoDiff(StringBuilder html, String title, 348 List<String> resultsList) { 349 Collections.sort(resultsList); 350 html.append("<h2>Passed [" + resultsList.size() + "]</h2>"); 351 for (String result : resultsList) { 352 html.append("<h3>"); 353 html.append("<span class=\"sqr\">■ </span>"); 354 html.append("<span class=\"path\">" + result + "</span>"); 355 html.append("</h3>"); 356 html.append("<div class=\"space\"></div>"); 357 } 358 } 359}