| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dumprendertree; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.util.Vector; |
| import java.util.Stack; |
| |
| import android.app.Activity; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| import android.view.ViewGroup; |
| import android.webkit.JsPromptResult; |
| import android.webkit.JsResult; |
| import android.webkit.WebChromeClient; |
| import android.webkit.WebSettings; |
| import android.webkit.WebView; |
| import android.widget.LinearLayout; |
| import android.os.*; |
| import android.test.TestRecorder; |
| |
| // SQLite3 in android has a bunch of bugs which |
| // is causing TestRecorder to not record the results |
| // properly. This class is a wrapper around it and records |
| // results in a file as well. |
| class TestRecorderV2 extends TestRecorder { |
| @Override |
| public void passed(String layout_file) { |
| try { |
| mBufferedOutputPassedStream.write(layout_file.getBytes()); |
| mBufferedOutputPassedStream.write('\n'); |
| mBufferedOutputPassedStream.flush(); |
| } catch(Exception e) { |
| e.printStackTrace(); |
| } |
| super.passed(layout_file); |
| } |
| |
| @Override |
| public void failed(String layout_file, String reason) { |
| try { |
| mBufferedOutputFailedStream.write(layout_file.getBytes()); |
| mBufferedOutputFailedStream.write('\n'); |
| mBufferedOutputFailedStream.flush(); |
| } catch(Exception e) { |
| e.printStackTrace(); |
| } |
| super.failed(layout_file, reason); |
| } |
| |
| public TestRecorderV2() { |
| super(); |
| try { |
| File resultsPassedFile = new File("/sdcard/layout_test_presults.txt"); |
| File resultsFailedFile = new File("/sdcard/layout_test_fresults.txt"); |
| |
| mBufferedOutputPassedStream = |
| new BufferedOutputStream(new FileOutputStream(resultsPassedFile, true)); |
| mBufferedOutputFailedStream = |
| new BufferedOutputStream(new FileOutputStream(resultsFailedFile, true)); |
| |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| protected void finalize() throws Throwable { |
| mBufferedOutputPassedStream.flush(); |
| mBufferedOutputFailedStream.flush(); |
| mBufferedOutputPassedStream.close(); |
| mBufferedOutputFailedStream.close(); |
| } |
| |
| private static BufferedOutputStream mBufferedOutputPassedStream; |
| private static BufferedOutputStream mBufferedOutputFailedStream; |
| } |
| |
| public class HTMLHostActivity extends Activity |
| implements LayoutTestController { |
| |
| private TestRecorderV2 mResultRecorder = new TestRecorderV2(); |
| private HTMLHostCallbackInterface mCallback = null; |
| private CallbackProxy mCallbackProxy; |
| |
| public class FileEntry { |
| public FileEntry(String path, int index) { |
| mPath = path; mIndex=index; |
| } |
| String mPath; |
| int mIndex; |
| } |
| |
| public class AsyncHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| if (msg.what == MSG_DUMP) { |
| this.removeMessages(MSG_TIMEOUT); |
| mTimedOut = false; |
| requestWebKitData(); |
| return; |
| } else if (msg.what == MSG_TIMEOUT) { |
| mTimedOut = true; |
| requestWebKitData(); |
| return; |
| } else if (msg.what == MSG_WEBKIT_DATA) { |
| HTMLHostActivity.this.dump(mTimedOut, (String)msg.obj); |
| return; |
| } |
| |
| super.handleMessage(msg); |
| } |
| |
| void requestWebKitData() { |
| Message callback = obtainMessage(MSG_WEBKIT_DATA); |
| if (dumpAsText) { |
| mWebView.documentAsText(callback); |
| } else { |
| mWebView.externalRepresentation(callback); |
| } |
| } |
| |
| } |
| |
| // Activity methods |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| LinearLayout contentView = new LinearLayout(this); |
| contentView.setOrientation(LinearLayout.VERTICAL); |
| setContentView(contentView); |
| |
| mWebView = new WebView(this); |
| mWebView.getSettings().setJavaScriptEnabled(true); |
| mWebView.setWebChromeClient(mChromeClient); |
| eventSender = new WebViewEventSender(mWebView); |
| mCallbackProxy = new CallbackProxy(eventSender, this); |
| |
| mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController"); |
| mWebView.addJavascriptInterface(mCallbackProxy, "eventSender"); |
| contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f)); |
| |
| mHandler = new AsyncHandler(); |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Bundle savedInstanceState) { |
| super.onRestoreInstanceState(savedInstanceState); |
| } |
| |
| protected void onResume() { |
| super.onResume(); |
| if (mProcessStack == null || mProcessStack.isEmpty() ) { |
| mOutstandingLoads = 0; |
| dumpAsText = false; |
| pageComplete = false; |
| |
| mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); |
| |
| mFinishedStack = new Stack(); |
| |
| Intent intent = getIntent(); |
| if (intent.getData() != null) { |
| File f = new File(intent.getData().toString()); |
| |
| if (f.isDirectory()) { |
| mProcessStack = new Vector(); |
| mProcessStack.add(new FileEntry(intent.getData().toString(), 0)); |
| Log.v(LOGTAG, "Initial dir: "+intent.getData().toString()); |
| loadNextPage(); |
| } else { |
| mCurrentFile = intent.getData().toString(); |
| mWebView.loadUrl("file://"+intent.getData().toString()); |
| } |
| |
| } |
| else |
| mWebView.loadUrl("about:"); |
| } |
| } |
| |
| protected void onStop() { |
| super.onStop(); |
| mWebView.stopLoading(); |
| } |
| |
| protected void onDestroy() { |
| super.onDestroy(); |
| mWebView.destroy(); |
| mWebView = null; |
| } |
| |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| // Log key strokes as they don't seem to be matched |
| //Log.e(LOGTAG, "Event: "+event); |
| return super.dispatchKeyEvent(event); |
| } |
| |
| // Functions |
| |
| protected void loadNextPage() { |
| dumpAsText = false; |
| pageComplete = false; |
| dumpTitleChanges = false; |
| eventSender.resetMouse(); |
| while (!mProcessStack.isEmpty()) { |
| FileEntry fe = (FileEntry)mProcessStack.remove(0); |
| if (fe.mIndex == 0) { |
| System.out.println(); |
| System.out.print(fe.mPath); |
| } |
| Log.v(LOGTAG, "Processing dir: "+fe.mPath+" size: "+mProcessStack.size()); |
| File f = new File(fe.mPath); |
| String [] files = f.list(); |
| for (int i = fe.mIndex; i < files.length; i++) { |
| if (FileFilter.ignoreTest(files[i])) { |
| continue; |
| } |
| File c = new File(f.getPath(), files[i]); |
| if (c.isDirectory()) { |
| Log.v(LOGTAG, "Adding dir: "+fe.mPath+"/"+files[i]); |
| mProcessStack.add(new FileEntry(fe.mPath+"/"+files[i], 0)); |
| } else if (files[i].toLowerCase().endsWith("ml")) { |
| mProcessStack.add(0, new FileEntry(fe.mPath, i+1)); |
| mCurrentFile = fe.mPath+"/"+files[i]; |
| Log.e(LOGTAG, "Processing: "+mCurrentFile); |
| mWebView.loadUrl("file://"+mCurrentFile); |
| |
| // Create a timeout timer |
| Message m = mHandler.obtainMessage(MSG_TIMEOUT); |
| // Some tests can take up to 5secs to run. |
| mHandler.sendMessageDelayed(m, 6000); |
| return; |
| } |
| } |
| Log.v(LOGTAG, "Finished dir: "+fe.mPath+" size: "+mProcessStack.size()); |
| } |
| // If we got to here, then we must have finished completely |
| finished(); |
| } |
| |
| public void scheduleDump() { |
| // Only schedule if we really are ready |
| if (waitToDump || mOutstandingLoads > 0 || mDumpRequested) { |
| return; |
| } |
| mDumpRequested = true; |
| mHandler.obtainMessage(MSG_DUMP).sendToTarget(); |
| } |
| |
| // Dump the page |
| public void dump(boolean timeout, String webkitData) { |
| mDumpRequested = false; |
| System.out.print('.'); |
| |
| // remove the extension |
| String resultFile = mCurrentFile.substring(0, mCurrentFile.lastIndexOf('.')); |
| |
| // store the finished file on the stack so that we can do a diff at the end. |
| mFinishedStack.push(resultFile); |
| |
| // dumpAsText version can be directly compared to expected results |
| if (dumpAsText) { |
| resultFile += "-results.txt"; |
| } else { |
| resultFile += "-android-results.txt"; |
| } |
| try { |
| FileOutputStream os = new FileOutputStream(resultFile); |
| if (timeout) { |
| Log.i("Layout test: Timeout", resultFile); |
| os.write("**Test timeout\n".getBytes()); |
| } |
| if (dumpTitleChanges) |
| os.write(mTitleChanges.toString().getBytes()); |
| if (mDialogStrings != null) |
| os.write(mDialogStrings.toString().getBytes()); |
| mDialogStrings = null; |
| os.write(webkitData.getBytes()); |
| os.flush(); |
| os.close(); |
| } catch (FileNotFoundException ex) { |
| ex.printStackTrace(); |
| } catch (IOException ex) { |
| ex.printStackTrace(); |
| } |
| |
| if (mProcessStack != null) |
| loadNextPage(); |
| else |
| finished(); |
| } |
| |
| // Wrap up |
| public void failedCase(String file, String reason) { |
| Log.i("Layout test:", file + " failed" + reason); |
| mResultRecorder.failed(file, reason); |
| |
| file = file + ".html"; |
| String bugNumber = FileFilter.isKnownBug(file); |
| if (bugNumber != null) { |
| System.out.println("FAIL known:"+bugNumber+ " "+file+reason); |
| return; |
| } |
| if (FileFilter.ignoreResults(file)) { |
| return; |
| } |
| System.out.println("FAIL: "+file+reason); |
| } |
| |
| public void passedCase(String file) { |
| // Add the result to the sqlite database |
| Log.i("Layout test:", file + " passed"); |
| mResultRecorder.passed(file); |
| |
| file = file + ".html"; |
| String bugNumber = FileFilter.isKnownBug(file); |
| if (bugNumber != null) { |
| System.out.println("Bug Fixed: "+bugNumber+ " "+file); |
| return; |
| } |
| |
| if (FileFilter.ignoreResults(file)) { |
| System.out.println("Ignored test passed: "+file); |
| return; |
| } |
| } |
| |
| public void setCallback(HTMLHostCallbackInterface callback) { |
| mCallback = callback; |
| } |
| |
| public void finished() { |
| int passed = 0; |
| while (!mFinishedStack.empty()) { |
| Log.v(LOGTAG, "Comparing dump and reference"); |
| String file = (String)mFinishedStack.pop(); |
| |
| // Only check results that we can check, ie dumpAsText results |
| String dumpFile = file + "-results.txt"; |
| File f = new File(dumpFile); |
| if (f.exists()) { |
| try { |
| FileInputStream fr = new FileInputStream(file+"-results.txt"); |
| FileInputStream fe = new FileInputStream(file+"-expected.txt"); |
| |
| mResultRecorder.started(file); |
| |
| // If the length is different then they are different |
| int diff = fe.available() - fr.available(); |
| if (diff > 1 || diff < 0) { |
| failedCase(file, " different length"); |
| fr.close(); |
| fe.close(); |
| |
| mResultRecorder.finished(file); |
| continue; |
| } |
| byte[] br = new byte[fr.available()]; |
| byte[] be = new byte[fe.available()]; |
| fr.read(br); |
| fe.read(be); |
| boolean fail = false; |
| for (int i = 0; i < br.length; i++) { |
| if (br[i] != be[i]) { |
| failedCase(file, " @offset: "+i); |
| fail = true; |
| break; |
| } |
| } |
| if (br.length != be.length && be[be.length-1] == '\n') { |
| Log.d(LOGTAG, "Extra new line being ignore:" + file); |
| } |
| fr.close(); |
| fe.close(); |
| if (!fail) { |
| passed++; |
| passedCase(file); |
| } |
| } catch (FileNotFoundException ex) { |
| // TODO do something here |
| } catch (IOException ex) { |
| // Failed on available() or read() |
| } |
| mResultRecorder.finished(file); |
| } |
| } |
| |
| if (mCallback != null) { |
| mCallback.waitForFinish(); |
| } |
| |
| finish(); |
| } |
| |
| // LayoutTestController Functions |
| public void dumpAsText() { |
| dumpAsText = true; |
| String url = mWebView.getUrl(); |
| Log.v(LOGTAG, "dumpAsText called:"+url); |
| if (url.length() > 60) |
| url = url.substring(60); |
| } |
| |
| public void waitUntilDone() { |
| waitToDump = true; |
| } |
| public void notifyDone() { |
| waitToDump = false; |
| mChromeClient.onProgressChanged(mWebView, 100); |
| } |
| public void display() { |
| mWebView.invalidate(); |
| } |
| |
| public void clearBackForwardList() { |
| mWebView.clearHistory(); |
| |
| } |
| |
| public void dumpBackForwardList() { |
| //printf("\n============== Back Forward List ==============\n"); |
| // mWebHistory |
| //printf("===============================================\n"); |
| |
| } |
| |
| public void dumpChildFrameScrollPositions() { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void dumpEditingCallbacks() { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void dumpSelectionRect() { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void dumpTitleChanges() { |
| if (!dumpTitleChanges) { |
| mTitleChanges = new StringBuffer(); |
| } |
| dumpTitleChanges = true; |
| } |
| |
| public void keepWebHistory() { |
| if (!keepWebHistory) { |
| mWebHistory = new Vector(); |
| } |
| keepWebHistory = true; |
| |
| } |
| |
| public void queueBackNavigation(int howfar) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void queueForwardNavigation(int howfar) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void queueLoad(String Url, String frameTarget) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void queueReload() { |
| mWebView.reload(); |
| } |
| |
| public void queueScript(String scriptToRunInCurrentContext) { |
| mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); |
| } |
| |
| public void repaintSweepHorizontally() { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void setAcceptsEditing(boolean b) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void setMainFrameIsFirstResponder(boolean b) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void setWindowIsKey(boolean b) { |
| // This is meant to show/hide the window. The best I can find |
| // is setEnabled() |
| mWebView.setEnabled(b); |
| } |
| |
| public void testRepaint() { |
| mWebView.invalidate(); |
| } |
| |
| // Instrumentation calls this to find |
| // if the activity has finished running the layout tests |
| public boolean hasFinishedRunning() { |
| if( mProcessStack == null || mFinishedStack == null) |
| return false; |
| |
| if (mProcessStack.isEmpty() && mFinishedStack.empty()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private final WebChromeClient mChromeClient = new WebChromeClient() { |
| @Override |
| public void onProgressChanged(WebView view, int newProgress) { |
| if (newProgress == 100) { |
| pageComplete = true; |
| String url = mWebView.getUrl(); |
| if (url != null) { |
| Log.v(LOGTAG, "Finished: "+ url); |
| if (url.length() > 60) |
| url = url.substring(60); |
| scheduleDump(); |
| } |
| } |
| } |
| |
| @Override |
| public void onReceivedTitle(WebView view, String title) { |
| if (title.length() > 30) |
| title = "..."+title.substring(title.length()-30); |
| setTitle(title); |
| if (dumpTitleChanges) { |
| mTitleChanges.append("TITLE CHANGED: "); |
| mTitleChanges.append(title); |
| mTitleChanges.append("\n"); |
| } |
| } |
| |
| @Override |
| public boolean onJsAlert(WebView view, String url, String message, |
| JsResult result) { |
| if (mDialogStrings == null) { |
| mDialogStrings = new StringBuffer(); |
| } |
| mDialogStrings.append("ALERT: "); |
| mDialogStrings.append(message); |
| mDialogStrings.append('\n'); |
| return false; |
| } |
| }; |
| |
| private WebView mWebView; |
| private WebViewEventSender eventSender; |
| private Vector mProcessStack; |
| private Stack mFinishedStack; |
| static final String LOGTAG="DumpRenderTree"; |
| private String mCurrentFile; |
| private int mOutstandingLoads; |
| private AsyncHandler mHandler; |
| private boolean mDumpRequested; |
| |
| private boolean dumpAsText; |
| private boolean waitToDump; |
| private boolean pageComplete; |
| |
| private boolean dumpTitleChanges; |
| private StringBuffer mTitleChanges; |
| |
| private StringBuffer mDialogStrings; |
| |
| private boolean keepWebHistory; |
| private Vector mWebHistory; |
| |
| private boolean mTimedOut; |
| |
| static final int MSG_DUMP = 0; |
| static final int MSG_TIMEOUT = 1; |
| static final int MSG_WEBKIT_DATA = 2; |
| |
| } |