blob: 9521f8038656ff58fa4976408d39a74ef079f5ec [file] [log] [blame]
/*
* 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;
}