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