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 = '&#x25bc; ';" +
161            "        } else {" +
162            "            element.style.display = 'none';" +
163            "            triangle.innerHTML = '&#x25b6; ';" +
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\">&#x25a0; </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 + "\">&#x25b6; </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\">&#x25a0; </span>");
354            html.append("<span class=\"path\">" + result + "</span>");
355            html.append("</h3>");
356            html.append("<div class=\"space\"></div>");
357        }
358    }
359}