blob: c77d98ae55c904230b23deade26889e046a7e4ff [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.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
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.*;
// TestRecorder creates two files, one for passing tests
// and another for failing tests and writes the paths to
// layout tests one line at a time. TestRecorder does not
// have ability to clear the results.
class TestRecorder {
public void passed(String layout_file) {
try {
mBufferedOutputPassedStream.write(layout_file.getBytes());
mBufferedOutputPassedStream.write('\n');
mBufferedOutputPassedStream.flush();
} catch(Exception e) {
e.printStackTrace();
}
}
public void failed(String layout_file, String reason) {
try {
mBufferedOutputFailedStream.write(layout_file.getBytes());
mBufferedOutputFailedStream.write(" : ".getBytes());
mBufferedOutputFailedStream.write(reason.getBytes());
mBufferedOutputFailedStream.write('\n');
mBufferedOutputFailedStream.flush();
} catch(Exception e) {
e.printStackTrace();
}
}
public void nontext(String layout_file) {
try {
mBufferedOutputNontextStream.write(layout_file.getBytes());
mBufferedOutputNontextStream.write('\n');
mBufferedOutputNontextStream.flush();
} catch(Exception e) {
e.printStackTrace();
}
}
public TestRecorder(boolean resume) {
try {
File resultsPassedFile = new File("/sdcard/layout_tests_passed.txt");
File resultsFailedFile = new File("/sdcard/layout_tests_failed.txt");
File resultsNontextFile = new File("/sdcard/layout_tests_nontext.txt");
mBufferedOutputPassedStream =
new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume));
mBufferedOutputFailedStream =
new BufferedOutputStream(new FileOutputStream(resultsFailedFile, resume));
mBufferedOutputNontextStream =
new BufferedOutputStream(new FileOutputStream(resultsNontextFile, resume));
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
mBufferedOutputPassedStream.close();
mBufferedOutputFailedStream.close();
mBufferedOutputNontextStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private BufferedOutputStream mBufferedOutputPassedStream;
private BufferedOutputStream mBufferedOutputFailedStream;
private BufferedOutputStream mBufferedOutputNontextStream;
}
public class HTMLHostActivity extends Activity
implements LayoutTestController {
public class AsyncHandler extends Handler {
@Override
public void handleMessage(Message msg) {
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);
}
}
public void requestWebKitData() {
Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
if (mRequestedWebKitData)
throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
mRequestedWebKitData = true;
if (mDumpAsText) {
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);
mEventSender = new WebViewEventSender(mWebView);
mCallbackProxy = new CallbackProxy(mEventSender, this);
mFinishedRunning = false;
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);
}
private void getTestList() {
// Read test list.
try {
BufferedReader inReader = new BufferedReader(new FileReader(LAYOUT_TESTS_LIST_FILE));
String line = inReader.readLine();
while (line != null) {
if (line.startsWith(mTestPathPrefix))
mTestList.add(line);
line = inReader.readLine();
}
inReader.close();
Log.v(LOGTAG, "Test list has " + mTestList.size() + " test(s).");
} catch (Exception e) {
Log.e(LOGTAG, "Error while reading test list : " + e.getMessage());
}
}
private void resumeTestList() {
// read out the test name it stoped last time.
try {
BufferedReader inReader = new BufferedReader(new FileReader(TEST_STATUS_FILE));
String line = inReader.readLine();
for (int i = 0; i < mTestList.size(); i++) {
if (mTestList.elementAt(i).equals(line)) {
mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size()));
break;
}
}
inReader.close();
} catch (Exception e) {
Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE);
}
}
private void clearTestStatus() {
// Delete TEST_STATUS_FILE
try {
File f = new File(TEST_STATUS_FILE);
if (f.delete())
Log.v(LOGTAG, "Deleted " + TEST_STATUS_FILE);
else
Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE);
} catch (Exception e) {
Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage());
}
}
private void updateTestStatus(String s) {
// Write TEST_STATUS_FILE
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_STATUS_FILE));
bos.write(s.getBytes());
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE);
}
}
protected void onResume() {
super.onResume();
if (mTestList == null)
mTestList = new Vector<String>();
if (mTestList.isEmpty()) {
// Read settings
Intent intent = getIntent();
mTestPathPrefix = intent.getStringExtra(TEST_PATH_PREFIX);
mSingleTestMode = intent.getBooleanExtra(SINGLE_TEST_MODE, false);
boolean resume = intent.getBooleanExtra(RESUME_FROM_CRASH, false);
mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 8000);
mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
if (mTestPathPrefix == null)
throw new AssertionError("mTestPathPrefix cannot be null");
Log.v(LOGTAG, "Run tests with prefix: " + mTestPathPrefix);
mResultRecorder = new TestRecorder(resume);
if (!resume)
clearTestStatus();
if (!mSingleTestMode) {
getTestList();
if (resume)
resumeTestList();
} else {
mTestList.add(mTestPathPrefix);
}
if (!mTestList.isEmpty())
runTestAtIndex(0);
else
mWebView.loadUrl("about:");
}
}
protected void onStop() {
super.onStop();
mWebView.stopLoading();
}
protected void onDestroy() {
super.onDestroy();
mResultRecorder.close();
mWebView.destroy();
mWebView = null;
}
public void onLowMemory() {
super.onLowMemory();
// Simulate a crash
Log.e(LOGTAG, "Low memory, killing self");
System.exit(1);
}
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);
}
// Run a test at specified index in the test list.
// Stops activity if run out of tests.
protected void runTestAtIndex(int testIndex) {
mTestIndex = testIndex;
resetTestStatus();
if (testIndex == mTestList.size()) {
finished();
return;
}
String s = mTestList.elementAt(testIndex);
if (!mSingleTestMode)
updateTestStatus(s);
Log.v(LOGTAG, " Running test: "+s);
mWebView.loadUrl("file://"+s);
if (!mSingleTestMode) {
// Create a timeout timer
Message m = mHandler.obtainMessage(MSG_TIMEOUT);
mHandler.sendMessageDelayed(m, mTimeoutInMillis);
}
}
// Dump the page
public void dump(boolean timeout, String webkitData) {
String currentTest = mTestList.elementAt(mTestIndex);
String resultFile = currentTest.substring(0, currentTest.lastIndexOf('.'));
// dumpAsText version can be directly compared to expected results
if (mDumpAsText) {
resultFile += "-results.txt";
} else {
resultFile += "-android-results.txt";
}
try {
FileOutputStream os = new FileOutputStream(resultFile);
if (timeout) {
Log.w("Layout test: Timeout", resultFile);
os.write(TIMEOUT_STR.getBytes());
os.write('\n');
}
if (mDumpTitleChanges)
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();
}
processResult(timeout, currentTest);
runTestAtIndex(mTestIndex + 1);
}
// Wrap up
public void failedCase(String file, String reason) {
Log.w("Layout test: ", file + " failed " + reason);
mResultRecorder.failed(file, reason);
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) {
Log.v("Layout test:", file + " passed");
mResultRecorder.passed(file);
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 nontextCase(String file) {
Log.v("Layout test:", file + " nontext");
mResultRecorder.nontext(file);
}
public void setCallback(HTMLHostCallbackInterface callback) {
mCallback = callback;
}
public void processResult(boolean timeout, String test_path) {
Log.v(LOGTAG, " Processing result: " + test_path);
// remove the extension
String short_file = test_path.substring(0, test_path.lastIndexOf('.'));
if (timeout) {
failedCase(test_path, "TIMEDOUT");
return;
}
// Only check results that we can check, ie dumpAsText results
String dumpFile = short_file + "-results.txt";
File f = new File(dumpFile);
if (f.exists()) {
try {
FileInputStream fr = new FileInputStream(short_file+"-results.txt");
FileInputStream fe = new FileInputStream(short_file+"-expected.txt");
// If the length is different then they are different
int diff = fe.available() - fr.available();
if (diff > 1 || diff < 0) {
failedCase(test_path, " different length");
fr.close();
fe.close();
return;
}
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(test_path, " @offset: "+i);
fr.close();
fe.close();
return;
}
}
if (br.length != be.length && be[be.length-1] == '\n') {
Log.d(LOGTAG, "Extra new line being ignore:" + test_path);
}
fr.close();
fe.close();
passedCase(test_path);
} catch (FileNotFoundException ex) {
// TODO do something here
} catch (IOException ex) {
// Failed on available() or read()
}
return;
}
File nontext_result = new File(short_file + "-android-results.txt");
if (nontext_result.exists())
mResultRecorder.nontext(test_path);
}
public void finished() {
if (mCallback != null) {
mCallback.waitForFinish();
}
mFinishedRunning = true;
finish();
}
// LayoutTestController Functions
public void dumpAsText() {
mDumpAsText = true;
if (mWebView != null) {
String url = mWebView.getUrl();
Log.v(LOGTAG, "dumpAsText called: "+url);
}
}
public void waitUntilDone() {
mWaitUntilDone = true;
String url = mWebView.getUrl();
Log.v(LOGTAG, "waitUntilDone called: " + url);
}
public void notifyDone() {
String url = mWebView.getUrl();
Log.v(LOGTAG, "notifyDone called: " + url);
if (mWaitUntilDone) {
mWaitUntilDone = 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 (!mDumpTitleChanges) {
mTitleChanges = new StringBuffer();
}
mDumpTitleChanges = true;
}
public void keepWebHistory() {
if (!mKeepWebHistory) {
mWebHistory = new Vector();
}
mKeepWebHistory = 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
// TODO(fqian): need to sync on mFinisheRunning
public boolean hasFinishedRunning() {
return mFinishedRunning;
}
private final WebChromeClient mChromeClient = new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
if (!mSingleTestMode && !mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) {
String url = mWebView.getUrl();
Log.v(LOGTAG, "Finished: "+ url);
mHandler.removeMessages(MSG_TIMEOUT);
requestWebKitData();
} else {
String url = mWebView.getUrl();
if (mSingleTestMode) {
Log.v(LOGTAG, "Single test mode: " + url);
} else if (mTimedOut) {
Log.v(LOGTAG, "Timed out before finishing: " + url);
} else if (mWaitUntilDone) {
Log.v(LOGTAG, "Waiting for notifyDone: " + url);
} else if (mRequestedWebKitData) {
Log.v(LOGTAG, "Requested webkit data ready: " + url);
}
}
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
if (title.length() > 30)
title = "..."+title.substring(title.length()-30);
setTitle(title);
if (mDumpTitleChanges) {
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');
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message,
JsResult result) {
if (mDialogStrings == null) {
mDialogStrings = new StringBuffer();
}
mDialogStrings.append("CONFIRM: ");
mDialogStrings.append(message);
mDialogStrings.append('\n');
result.confirm();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
if (mDialogStrings == null) {
mDialogStrings = new StringBuffer();
}
mDialogStrings.append("PROMPT: ");
mDialogStrings.append(message);
mDialogStrings.append(", default text: ");
mDialogStrings.append(defaultValue);
mDialogStrings.append('\n');
result.confirm();
return true;
}
};
private void resetTestStatus() {
mWaitUntilDone = false;
mDumpAsText = false;
mTimedOut = false;
mDumpTitleChanges = false;
mRequestedWebKitData = false;
mEventSender.resetMouse();
}
private TestRecorder mResultRecorder;
private HTMLHostCallbackInterface mCallback = null;
private CallbackProxy mCallbackProxy;
private WebView mWebView;
private WebViewEventSender mEventSender;
private Vector<String> mTestList;
private int mTestIndex;
private int mTimeoutInMillis;
private String mTestPathPrefix;
private boolean mSingleTestMode;
private AsyncHandler mHandler;
private boolean mFinishedRunning;
private boolean mTimedOut;
private boolean mRequestedWebKitData;
private boolean mDumpAsText;
private boolean mWaitUntilDone;
private boolean mDumpTitleChanges;
private StringBuffer mTitleChanges;
private StringBuffer mDialogStrings;
private boolean mKeepWebHistory;
private Vector mWebHistory;
static final String TIMEOUT_STR = "**Test timeout";
static final int MSG_TIMEOUT = 0;
static final int MSG_WEBKIT_DATA = 1;
static final String LOGTAG="DumpRenderTree";
static final String LAYOUT_TESTS_ROOT = "/sdcard/android/layout_tests/";
static final String LAYOUT_TESTS_LIST_FILE = "/sdcard/layout_tests_list.txt";
static final String TEST_STATUS_FILE = "/sdcard/running_test.txt";
static final String RESUME_FROM_CRASH = "ResumeFromCrash";
static final String TEST_PATH_PREFIX = "TestPathPrefix";
static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
static final String SINGLE_TEST_MODE = "SingleTestMode";
}