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.os.Bundle;
20import android.os.Handler;
21import android.os.Message;
22import android.webkit.WebView;
23import android.webkit.WebViewClassic;
24
25import name.fraser.neil.plaintext.diff_match_patch;
26
27import java.util.LinkedList;
28
29/**
30 * A result object for which the expected output is text. It does not have an image
31 * expected result.
32 *
33 * <p>Created if layoutTestController.dumpAsText() was called.
34 */
35public class TextResult extends AbstractResult {
36
37    private static final int MSG_DOCUMENT_AS_TEXT = 0;
38
39    private String mExpectedResult;
40    private String mExpectedResultPath;
41    private String mActualResult;
42    private String mRelativePath;
43    private boolean mDidTimeOut;
44    private ResultCode mResultCode;
45    transient private Message mResultObtainedMsg;
46
47    private boolean mDumpChildFramesAsText;
48
49    transient private Handler mHandler = new Handler() {
50        @Override
51        public void handleMessage(Message msg) {
52            if (msg.what == MSG_DOCUMENT_AS_TEXT) {
53                mActualResult = (String)msg.obj;
54                mResultObtainedMsg.sendToTarget();
55            }
56        }
57    };
58
59    public TextResult(String relativePath) {
60        mRelativePath = relativePath;
61    }
62
63    public void setDumpChildFramesAsText(boolean dumpChildFramesAsText) {
64        mDumpChildFramesAsText = dumpChildFramesAsText;
65    }
66
67    /**
68     * Used to recreate the Result when received by the service.
69     *
70     * @param bundle
71     *      bundle with data used to recreate the result
72     */
73    public TextResult(Bundle bundle) {
74        mExpectedResult = bundle.getString("expectedTextualResult");
75        mExpectedResultPath = bundle.getString("expectedTextualResultPath");
76        mActualResult = bundle.getString("actualTextualResult");
77        setAdditionalTextOutputString(bundle.getString("additionalTextOutputString"));
78        mRelativePath = bundle.getString("relativePath");
79        mDidTimeOut = bundle.getBoolean("didTimeOut");
80    }
81
82    @Override
83    public void clearResults() {
84        super.clearResults();
85        mExpectedResult = null;
86        mActualResult = null;
87    }
88
89    @Override
90    public ResultCode getResultCode() {
91        if (mResultCode == null) {
92            mResultCode = resultsMatch() ? AbstractResult.ResultCode.RESULTS_MATCH
93                    : AbstractResult.ResultCode.RESULTS_DIFFER;
94        }
95        return mResultCode;
96    }
97
98    private boolean resultsMatch() {
99        assert mExpectedResult != null;
100        assert mActualResult != null;
101        // Trim leading and trailing empty lines, as other WebKit platforms do.
102        String leadingEmptyLines = "^\\n+";
103        String trailingEmptyLines = "\\n+$";
104        String trimmedExpectedResult = mExpectedResult.replaceFirst(leadingEmptyLines, "")
105                .replaceFirst(trailingEmptyLines, "");
106        String trimmedActualResult = mActualResult.replaceFirst(leadingEmptyLines, "")
107                .replaceFirst(trailingEmptyLines, "");
108        return trimmedExpectedResult.equals(trimmedActualResult);
109    }
110
111    @Override
112    public boolean didCrash() {
113        return false;
114    }
115
116    @Override
117    public boolean didTimeOut() {
118        return mDidTimeOut;
119    }
120
121    @Override
122    public void setDidTimeOut() {
123        mDidTimeOut = true;
124    }
125
126    @Override
127    public byte[] getActualImageResult() {
128        return null;
129    }
130
131    @Override
132    public String getActualTextResult() {
133        String additionalTextResultString = getAdditionalTextOutputString();
134        if (additionalTextResultString != null) {
135            return additionalTextResultString + mActualResult;
136        }
137
138        return mActualResult;
139    }
140
141    @Override
142    public void setExpectedImageResult(byte[] expectedResult) {
143        /** This method is not applicable to this type of result */
144    }
145
146    @Override
147    public void setExpectedImageResultPath(String relativePath) {
148        /** This method is not applicable to this type of result */
149    }
150
151    @Override
152    public String getExpectedImageResultPath() {
153        /** This method is not applicable to this type of result */
154        return null;
155    }
156
157    @Override
158    public void setExpectedTextResultPath(String relativePath) {
159        mExpectedResultPath = relativePath;
160    }
161
162    @Override
163    public String getExpectedTextResultPath() {
164        return mExpectedResultPath;
165    }
166
167    @Override
168    public void setExpectedTextResult(String expectedResult) {
169        // For text results, we use an empty string for the expected result when none is
170        // present, as other WebKit platforms do.
171        mExpectedResult = expectedResult == null ? "" : expectedResult;
172    }
173
174    @Override
175    public String getDiffAsHtml() {
176        assert mExpectedResult != null;
177        assert mActualResult != null;
178
179        StringBuilder html = new StringBuilder();
180        html.append("<table class=\"visual_diff\">");
181        html.append("    <tr class=\"headers\">");
182        html.append("        <td colspan=\"2\">Expected result:</td>");
183        html.append("        <td class=\"space\"></td>");
184        html.append("        <td colspan=\"2\">Actual result:</td>");
185        html.append("    </tr>");
186
187        appendDiffHtml(html);
188
189        html.append("    <tr class=\"footers\">");
190        html.append("        <td colspan=\"2\"></td>");
191        html.append("        <td class=\"space\"></td>");
192        html.append("        <td colspan=\"2\"></td>");
193        html.append("    </tr>");
194        html.append("</table>");
195
196        return html.toString();
197    }
198
199    private void appendDiffHtml(StringBuilder html) {
200        LinkedList<diff_match_patch.Diff> diffs =
201                new diff_match_patch().diff_main(mExpectedResult, mActualResult);
202
203        diffs = VisualDiffUtils.splitDiffsOnNewline(diffs);
204
205        LinkedList<String> expectedLines = new LinkedList<String>();
206        LinkedList<Integer> expectedLineNums = new LinkedList<Integer>();
207        LinkedList<String> actualLines = new LinkedList<String>();
208        LinkedList<Integer> actualLineNums = new LinkedList<Integer>();
209
210        VisualDiffUtils.generateExpectedResultLines(diffs, expectedLineNums, expectedLines);
211        VisualDiffUtils.generateActualResultLines(diffs, actualLineNums, actualLines);
212        // TODO: We should use a map for each line number and lines pair.
213        assert expectedLines.size() == expectedLineNums.size();
214        assert actualLines.size() == actualLineNums.size();
215        assert expectedLines.size() == actualLines.size();
216
217        html.append(VisualDiffUtils.getHtml(expectedLineNums, expectedLines,
218                actualLineNums, actualLines));
219    }
220
221    @Override
222    public TestType getType() {
223        return TestType.TEXT;
224    }
225
226    @Override
227    public void obtainActualResults(WebView webview, Message resultObtainedMsg) {
228        mResultObtainedMsg = resultObtainedMsg;
229        Message msg = mHandler.obtainMessage(MSG_DOCUMENT_AS_TEXT);
230
231        /**
232         * arg1 - should dump top frame as text
233         * arg2 - should dump child frames as text
234         */
235        msg.arg1 = 1;
236        msg.arg2 = mDumpChildFramesAsText ? 1 : 0;
237        WebViewClassic.fromWebView(webview).documentAsText(msg);
238    }
239
240    @Override
241    public Bundle getBundle() {
242        Bundle bundle = new Bundle();
243        bundle.putString("expectedTextualResult", mExpectedResult);
244        bundle.putString("expectedTextualResultPath", mExpectedResultPath);
245        bundle.putString("actualTextualResult", getActualTextResult());
246        bundle.putString("additionalTextOutputString", getAdditionalTextOutputString());
247        bundle.putString("relativePath", mRelativePath);
248        bundle.putBoolean("didTimeOut", mDidTimeOut);
249        bundle.putString("type", getType().name());
250        return bundle;
251    }
252
253    @Override
254    public String getRelativePath() {
255        return mRelativePath;
256    }
257}
258