diff options
Diffstat (limited to 'tests/DumpRenderTree')
8 files changed, 637 insertions, 87 deletions
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java index a389461..97a8b25 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java @@ -18,6 +18,7 @@ package com.android.dumprendertree; import android.os.Handler; import android.os.Message; +import android.webkit.WebStorage; import java.util.HashMap; @@ -25,7 +26,7 @@ public class CallbackProxy extends Handler implements EventSender, LayoutTestCon private EventSender mEventSender; private LayoutTestController mLayoutTestController; - + private static final int EVENT_DOM_LOG = 1; private static final int EVENT_FIRE_KBD = 2; private static final int EVENT_KEY_DOWN_1 = 3; @@ -57,6 +58,8 @@ public class CallbackProxy extends Handler implements EventSender, LayoutTestCon private static final int LAYOUT_SET_WINDOW_KEY = 38; private static final int LAYOUT_TEST_REPAINT = 39; private static final int LAYOUT_WAIT_UNTIL_DONE = 40; + private static final int LAYOUT_DUMP_DATABASE_CALLBACKS = 41; + private static final int LAYOUT_SET_CAN_OPEN_WINDOWS = 42; CallbackProxy(EventSender eventSender, LayoutTestController layoutTestController) { @@ -190,6 +193,14 @@ public class CallbackProxy extends Handler implements EventSender, LayoutTestCon case LAYOUT_WAIT_UNTIL_DONE: mLayoutTestController.waitUntilDone(); break; + + case LAYOUT_DUMP_DATABASE_CALLBACKS: + mLayoutTestController.dumpDatabaseCallbacks(); + break; + + case LAYOUT_SET_CAN_OPEN_WINDOWS: + mLayoutTestController.setCanOpenWindows(); + break; } } @@ -325,4 +336,20 @@ public class CallbackProxy extends Handler implements EventSender, LayoutTestCon obtainMessage(LAYOUT_WAIT_UNTIL_DONE).sendToTarget(); } + public void dumpDatabaseCallbacks() { + obtainMessage(LAYOUT_DUMP_DATABASE_CALLBACKS).sendToTarget(); + } + + public void clearAllDatabases() { + WebStorage.getInstance().deleteAllData(); + } + + public void setDatabaseQuota(long quota) { + WebStorage.getInstance().setQuotaForOrigin("file://", quota); + } + + public void setCanOpenWindows() { + obtainMessage(LAYOUT_SET_CAN_OPEN_WINDOWS).sendToTarget(); + } + } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java index 4f162b3..ede5197 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java @@ -83,7 +83,20 @@ public class FileFilter { }; static final String [] ignoreTestList = { - }; + // RegExp is exponatal + "fast/regex/test1.html", + "fast/regex/slow.html", + // RegExp is too large, causing OOM + "fast/js/regexp-charclass-crash.html", + // The Android browser has no notion of private browsing. + "storage/private-browsing-readonly.html", + "storage/domstorage/localstorage/private-browsing-affects-storage.html", + "storage/domstorage/sessionstorage/private-browsing-affects-storage.html", + // Android layout tests are stored in "layout_tests". The following two + // tests expect "LayoutTests" in their output. + "storage/domstorage/localstorage/iframe-events.html", + "storage/domstorage/sessionstorage/iframe-events.html" + }; static void fillIgnoreResultSet() { // need test plugin @@ -195,11 +208,7 @@ public class FileFilter { ignoreResultList.add("fast/loader/local-iFrame-source-from-local.html"); // extra spacing because iFrames rendered next to each other on Apple ignoreResultList.add("fast/loader/opaque-base-url.html"); - // RegExp is too large, causing OOM - ignoreResultList.add("fast/js/regexp-charclass-crash.html"); ignoreResultList.add("fast/text/plain-text-line-breaks.html"); - - } static void fillBugTable() { diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java index 6166dd0..e1d802a 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java @@ -58,5 +58,8 @@ public interface LayoutTestController { public void queueLoad(String Url, String frameTarget); public void setAcceptsEditing(boolean b); - + + // For storage tests + public void dumpDatabaseCallbacks(); + public void setCanOpenWindows(); } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java index a03490d..2eecef8 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java @@ -16,6 +16,9 @@ package com.android.dumprendertree; +import com.android.dumprendertree.forwarder.AdbUtils; +import com.android.dumprendertree.forwarder.ForwardServer; + import android.app.Instrumentation; import android.content.Intent; import android.os.Bundle; @@ -42,7 +45,7 @@ class MyTestRecorder { private BufferedOutputStream mBufferedOutputFailedStream; private BufferedOutputStream mBufferedOutputNoresultStream; private BufferedOutputStream mBufferedOutputTimedoutStream; - + public void passed(String layout_file) { try { mBufferedOutputPassedStream.write(layout_file.getBytes()); @@ -52,7 +55,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void failed(String layout_file) { try { mBufferedOutputFailedStream.write(layout_file.getBytes()); @@ -62,7 +65,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void noresult(String layout_file) { try { mBufferedOutputNoresultStream.write(layout_file.getBytes()); @@ -72,7 +75,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void timedout(String url) { try { mBufferedOutputTimedoutStream.write(url.getBytes()); @@ -82,14 +85,14 @@ class MyTestRecorder { e.printStackTrace(); } } - + public MyTestRecorder(boolean resume) { try { File resultsPassedFile = new File("/sdcard/layout_tests_passed.txt"); File resultsFailedFile = new File("/sdcard/layout_tests_failed.txt"); File noExpectedResultFile = new File("/sdcard/layout_tests_nontext.txt"); File resultTimedoutFile = new File("/sdcard/layout_tests_timedout.txt"); - + mBufferedOutputPassedStream = new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume)); mBufferedOutputFailedStream = @@ -102,7 +105,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void close() { try { mBufferedOutputPassedStream.close(); @@ -120,7 +123,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh private static final String LOGTAG = "LayoutTests"; static final int DEFAULT_TIMEOUT_IN_MILLIS = 5000; - + static final String LAYOUT_TESTS_ROOT = "/sdcard/android/layout_tests/"; static final String LAYOUT_TESTS_RESULT_DIR = "/sdcard/android/layout_tests_results/"; static final String ANDROID_EXPECTED_RESULT_DIR = "/sdcard/android/expected_results/"; @@ -139,14 +142,35 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh static final String LAYOUT_RESULTS_CRASHED_RESULT_FILE = "results/layout_tests_crashed.txt"; static final String LAYOUT_TESTS_RUNNER = "run_layout_tests.py"; + static final String HTTP_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/"; + static final String HTTPS_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/ssl/"; + static final String HTTP_LOCAL_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/local/"; + static final String HTTP_MEDIA_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/media/"; + static final String HTTP_WML_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/wml/"; + + + private ForwardServer fs8000, fs8080, fs8443; + private MyTestRecorder mResultRecorder; private Vector<String> mTestList; private boolean mRebaselineResults; private String mTestPathPrefix; private boolean mFinished; - + public LayoutTestsAutoTest() { super("com.android.dumprendertree", TestShellActivity.class); + + int addr = -1; + try { + addr = AdbUtils.resolve("android-browser-test.mtv.corp.google.com"); + } catch (IOException ioe) { + Log.e(LOGTAG, "failed to resolve server address.", ioe); + } + if(addr != -1) { + fs8000 = new ForwardServer(8000, addr, 8000); + fs8080 = new ForwardServer(8080, addr, 8080); + fs8443 = new ForwardServer(8443, addr, 8443); + } } // This function writes the result of the layout test to @@ -157,7 +181,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh bundle.putBoolean(file, result); inst.sendStatus(0, bundle); } - + private void getTestList() { // Read test list. try { @@ -174,7 +198,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh Log.e(LOGTAG, "Error while reading test list : " + e.getMessage()); } } - + private void resumeTestList() { // read out the test name it stoped last time. try { @@ -189,7 +213,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE); } } - + private void clearTestStatus() { // Delete TEST_STATUS_FILE try { @@ -208,13 +232,13 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh // Write actual results to result directory. return shortName.replaceFirst(LAYOUT_TESTS_ROOT, LAYOUT_TESTS_RESULT_DIR) + "-result.txt"; } - + private String getExpectedResultFile(String test) { int pos = test.lastIndexOf('.'); if(pos == -1) return null; String shortName = test.substring(0, pos); - return shortName + "-expected.txt"; + return shortName + "-expected.txt"; } private String getAndroidExpectedResultFile(String expectedResultFile) { @@ -224,7 +248,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh // Wrap up private void failedCase(String file) { Log.w("Layout test: ", file + " failed"); - mResultRecorder.failed(file); + mResultRecorder.failed(file); } private void passedCase(String file) { @@ -236,7 +260,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh Log.v("Layout test:", file + " no expected result"); mResultRecorder.noresult(file); } - + private void processResult(String testFile, String actualResultFile, String expectedResultFile) { Log.v(LOGTAG, " Processing result: " + testFile); @@ -257,13 +281,13 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh break; } } - + if (passing) { passedCase(testFile); } else { failedCase(testFile); } - + fe.close(); fr.close(); } catch (FileNotFoundException ex) { @@ -278,7 +302,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh noresultCase(testFile); } } - + private void runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout) { activity.setCallback(new TestShellCallback() { public void finished() { @@ -287,7 +311,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh LayoutTestsAutoTest.this.notifyAll(); } } - + public void timedOut(String url) { } }); @@ -306,16 +330,16 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh resultFile = getAndroidExpectedResultFile(expectedResultFile); } - + mFinished = false; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setClass(activity, TestShellActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra(TestShellActivity.TEST_URL, "file://" + test); + intent.putExtra(TestShellActivity.TEST_URL, getTestUrl(test)); intent.putExtra(TestShellActivity.RESULT_FILE, resultFile); intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout); activity.startActivity(intent); - + // Wait until done. synchronized (this) { while(!mFinished){ @@ -324,18 +348,18 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh } catch (InterruptedException e) { } } } - + if (!mRebaselineResults) { String expectedResultFile = getExpectedResultFile(test); File f = new File(expectedResultFile); if (!f.exists()) { expectedResultFile = getAndroidExpectedResultFile(expectedResultFile); } - + processResult(test, resultFile, expectedResultFile); } - } - + } + // Invokes running of layout tests // and waits till it has finished running. public void executeLayoutTests(boolean resume) { @@ -348,28 +372,28 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh } this.mTestList = new Vector<String>(); - + // Read settings try { this.mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getCanonicalPath(); - } catch (IOException e) { + } catch (IOException e) { Log.e(LOGTAG, "Cannot find test path prefix: " + e.getMessage()); return; } - + this.mRebaselineResults = runner.mRebaseline; - + int timeout = runner.mTimeoutInMillis; if (timeout <= 0) { timeout = DEFAULT_TIMEOUT_IN_MILLIS; } - + this.mResultRecorder = new MyTestRecorder(resume); - + if (!resume) clearTestStatus(); - + getTestList(); if (resume) resumeTestList(); @@ -377,6 +401,15 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh TestShellActivity activity = (TestShellActivity) getActivity(); // Run tests. + int addr = -1; + try{ + addr = AdbUtils.resolve("android-browser-test.mtv.corp.google.com"); + } catch (IOException ioe) { + Log.w(LOGTAG, "error while resolving test host name", ioe); + } + if(addr == -1) { + Log.w(LOGTAG, "failed to resolve test host. http tests will fail."); + } for (int i = 0; i < mTestList.size(); i++) { String s = mTestList.elementAt(i); FsUtils.updateTestStatus(TEST_STATUS_FILE, s); @@ -385,10 +418,48 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh } FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE"); - + if(fs8000 != null) + fs8000.stop(); + if(fs8080 != null) + fs8080.stop(); + if(fs8443 != null) + fs8443.stop(); + activity.finish(); } + private void startForwardServerIfNeeded() { + try { + if(fs8000 != null) + fs8000.start(); + if(fs8080 != null) + fs8080.start(); + if(fs8443 != null) + fs8443.start(); + } catch (IOException ioe) { + Log.w(LOGTAG, "failed to start forwarder. http tests will fail.", ioe); + } + } + + private String getTestUrl(String path) { + String url = null; + if (!path.startsWith(HTTP_TESTS_PREFIX)) { + url = "file://" + path; + } else { + startForwardServerIfNeeded(); + if (path.startsWith(HTTPS_TESTS_PREFIX)) { + // still cut the URL after "http/tests/" + url = "https://127.0.0.1:8443/" + path.substring(HTTP_TESTS_PREFIX.length()); + } else if (!path.startsWith(HTTP_LOCAL_TESTS_PREFIX) + && !path.startsWith(HTTP_MEDIA_TESTS_PREFIX) + && !path.startsWith(HTTP_WML_TESTS_PREFIX)) { + url = "http://127.0.0.1:8000/" + path.substring(HTTP_TESTS_PREFIX.length()); + } else { + url = "file://" + path; + } + } + return url; + } private String getTestPath() { LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); @@ -403,10 +474,10 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh Log.e("LayoutTestsAutoTest", "Cannot get cannonical path " + e.getMessage()); } Log.v("LayoutTestsAutoTest", " Test path : " + test_path); - + return test_path; } - + public void generateTestList() { try { File tests_list = new File(LAYOUT_TESTS_LIST_FILE); @@ -431,7 +502,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh } catch (Exception e) { e.printStackTrace(); } - + executeLayoutTests(false); } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index 09f7cbc..30e1d99 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -34,6 +34,7 @@ import android.webkit.JsResult; import android.webkit.SslErrorHandler; import android.webkit.WebChromeClient; import android.webkit.WebSettings; +import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.LinearLayout; @@ -43,6 +44,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Vector; public class TestShellActivity extends Activity implements LayoutTestController { @@ -88,7 +91,7 @@ public class TestShellActivity extends Activity implements LayoutTestController } public void clearCache() { - mWebView.clearCache(true); + mWebView.freeMemory(); } @Override @@ -100,48 +103,11 @@ public class TestShellActivity extends Activity implements LayoutTestController setContentView(contentView); mWebView = new WebView(this); - mWebView.getSettings().setJavaScriptEnabled(true); - mWebView.setWebChromeClient(mChromeClient); - mWebView.setWebViewClient(new WebViewClient(){ - - @Override - public void onPageFinished(WebView view, String url) { - Log.v(LOGTAG, "onPageFinished, url=" + url); - super.onPageFinished(view, url); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - Log.v(LOGTAG, "onPageStarted, url=" + url); - super.onPageStarted(view, url, favicon); - } - - @Override - public void onReceivedError(WebView view, int errorCode, String description, - String failingUrl) { - Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode - + ", desc=" + description + ", url=" + failingUrl); - super.onReceivedError(view, errorCode, description, failingUrl); - } - - @Override - public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, - String host, String realm) { - handler.cancel(); - } - - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, - SslError error) { - handler.proceed(); - } - - }); mEventSender = new WebViewEventSender(mWebView); mCallbackProxy = new CallbackProxy(mEventSender, this); - mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController"); - mWebView.addJavascriptInterface(mCallbackProxy, "eventSender"); + setupWebViewForLayoutTests(mWebView, mCallbackProxy); + contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f)); mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); @@ -262,8 +228,8 @@ public class TestShellActivity extends Activity implements LayoutTestController @Override public void onLowMemory() { super.onLowMemory(); - Log.e(LOGTAG, "Low memory, kill self"); - System.exit(1); + Log.e(LOGTAG, "Low memory, clearing caches"); + mWebView.freeMemory(); } // Dump the page @@ -290,6 +256,12 @@ public class TestShellActivity extends Activity implements LayoutTestController if (mDialogStrings != null) os.write(mDialogStrings.toString().getBytes()); mDialogStrings = null; + if (mDatabaseCallbackStrings != null) + os.write(mDatabaseCallbackStrings.toString().getBytes()); + mDatabaseCallbackStrings = null; + if (mConsoleMessages != null) + os.write(mConsoleMessages.toString().getBytes()); + mConsoleMessages = null; if (webkitData != null) os.write(webkitData.getBytes()); os.flush(); @@ -438,6 +410,51 @@ public class TestShellActivity extends Activity implements LayoutTestController mWebView.invalidate(); } + public void dumpDatabaseCallbacks() { + Log.v(LOGTAG, "dumpDatabaseCallbacks called."); + mDumpDatabaseCallbacks = true; + } + + public void setCanOpenWindows() { + Log.v(LOGTAG, "setCanOpenWindows called."); + mCanOpenWindows = true; + } + + private final WebViewClient mViewClient = new WebViewClient(){ + @Override + public void onPageFinished(WebView view, String url) { + Log.v(LOGTAG, "onPageFinished, url=" + url); + super.onPageFinished(view, url); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + Log.v(LOGTAG, "onPageStarted, url=" + url); + super.onPageStarted(view, url, favicon); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, + String failingUrl) { + Log.v(LOGTAG, "onReceivedError, errorCode=" + errorCode + + ", desc=" + description + ", url=" + failingUrl); + super.onReceivedError(view, errorCode, description, failingUrl); + } + + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, + String host, String realm) { + handler.cancel(); + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, + SslError error) { + handler.proceed(); + } + }; + + private final WebChromeClient mChromeClient = new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { @@ -512,6 +529,78 @@ public class TestShellActivity extends Activity implements LayoutTestController result.confirm(); return true; } + + @Override + public boolean onJsTimeout() { + Log.v(LOGTAG, "JavaScript timeout"); + return false; + } + + @Override + public void onExceededDatabaseQuota(String url_str, + String databaseIdentifier, long currentQuota, long totalUsedQuota, + WebStorage.QuotaUpdater callback) { + if (mDumpDatabaseCallbacks) { + if (mDatabaseCallbackStrings == null) { + mDatabaseCallbackStrings = new StringBuffer(); + } + + String protocol = ""; + String host = ""; + int port = 0; + + try { + URL url = new URL(url_str); + protocol = url.getProtocol(); + host = url.getHost(); + if (url.getPort() > -1) { + port = url.getPort(); + } + } catch (MalformedURLException e) {} + + String databaseCallbackString = + "UI DELEGATE DATABASE CALLBACK: " + + "exceededDatabaseQuotaForSecurityOrigin:{" + protocol + + ", " + host + ", " + port + "} database:" + + databaseIdentifier + "\n"; + Log.v(LOGTAG, "LOG: "+databaseCallbackString); + mDatabaseCallbackStrings.append(databaseCallbackString); + } + // Give 5MB more quota. + callback.updateQuota(currentQuota + 1024 * 1024 * 5); + } + + @Override + public void addMessageToConsole(String message, int lineNumber, + String sourceID) { + if (mConsoleMessages == null) { + mConsoleMessages = new StringBuffer(); + } + String consoleMessage = "CONSOLE MESSAGE: line " + + lineNumber +": "+ message +"\n"; + mConsoleMessages.append(consoleMessage); + Log.v(LOGTAG, "LOG: "+consoleMessage); + } + + @Override + public boolean onCreateWindow(WebView view, boolean dialog, + boolean userGesture, Message resultMsg) { + if (!mCanOpenWindows) { + return false; + } + + // We never display the new window, just create the view and + // allow it's content to execute and be recorded by the test + // runner. + + WebView newWindowView = new WebView(TestShellActivity.this); + setupWebViewForLayoutTests(newWindowView, mCallbackProxy); + WebView.WebViewTransport transport = + (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(newWindowView); + resultMsg.sendToTarget(); + return true; + } }; private void resetTestStatus() { @@ -520,9 +609,35 @@ public class TestShellActivity extends Activity implements LayoutTestController mTimedOut = false; mDumpTitleChanges = false; mRequestedWebKitData = false; + mDumpDatabaseCallbacks = false; + mCanOpenWindows = false; mEventSender.resetMouse(); } + private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) { + if (webview == null) { + return; + } + + WebSettings settings = webview.getSettings(); + settings.setAppCacheEnabled(true); + settings.setAppCachePath(getApplicationContext().getCacheDir().getPath()); + settings.setAppCacheMaxSize(Long.MAX_VALUE); + settings.setJavaScriptEnabled(true); + settings.setJavaScriptCanOpenWindowsAutomatically(true); + settings.setSupportMultipleWindows(true); + settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); + settings.setDatabaseEnabled(true); + settings.setDatabasePath(getDir("databases",0).getAbsolutePath()); + settings.setDomStorageEnabled(true); + + webview.addJavascriptInterface(callbackProxy, "layoutTestController"); + webview.addJavascriptInterface(callbackProxy, "eventSender"); + + webview.setWebChromeClient(mChromeClient); + webview.setWebViewClient(mViewClient); + } + private WebView mWebView; private WebViewEventSender mEventSender; private AsyncHandler mHandler; @@ -550,6 +665,10 @@ public class TestShellActivity extends Activity implements LayoutTestController private StringBuffer mDialogStrings; private boolean mKeepWebHistory; private Vector mWebHistory; + private boolean mDumpDatabaseCallbacks; + private StringBuffer mDatabaseCallbackStrings; + private StringBuffer mConsoleMessages; + private boolean mCanOpenWindows; static final String TIMEOUT_STR = "**Test timeout"; diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/AdbUtils.java b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/AdbUtils.java new file mode 100644 index 0000000..9a3e9c2 --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/AdbUtils.java @@ -0,0 +1,112 @@ +package com.android.dumprendertree.forwarder; + +import android.util.Log; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +public class AdbUtils { + + private static final String ADB_OK = "OKAY"; + private static final int ADB_PORT = 5037; + private static final String ADB_HOST = "127.0.0.1"; + private static final int ADB_RESPONSE_SIZE = 4; + + private static final String LOGTAG = "AdbUtils"; + + /** + * + * Convert integer format IP into xxx.xxx.xxx.xxx format + * + * @param host IP address in integer format + * @return human readable format + */ + public static String convert(int host) { + return ((host >> 24) & 0xFF) + "." + + ((host >> 16) & 0xFF) + "." + + ((host >> 8) & 0xFF) + "." + + (host & 0xFF); + } + + /** + * + * Resolve DNS name into IP address + * + * @param host DNS name + * @return IP address in integer format + * @throws IOException + */ + public static int resolve(String host) throws IOException { + Socket localSocket = new Socket(ADB_HOST, ADB_PORT); + DataInputStream dis = new DataInputStream(localSocket.getInputStream()); + OutputStream os = localSocket.getOutputStream(); + int count_read = 0; + byte[] buf = new byte[128]; + + if (localSocket == null || dis == null || os == null) + return -1; + String cmd = "dns:" + host; + + if(!sendAdbCmd(dis, os, cmd)) + return -1; + + count_read = dis.readInt(); + localSocket.close(); + return count_read; + } + + /** + * + * Send an ADB command using existing socket connection + * + * the streams provided must be from a socket connected to adbd already + * + * @param is input stream of the socket connection + * @param os output stream of the socket + * @param cmd the adb command to send + * @return if adb gave a success response + * @throws IOException + */ + public static boolean sendAdbCmd(InputStream is, OutputStream os, + String cmd) throws IOException { + byte[] buf = new byte[ADB_RESPONSE_SIZE]; + + cmd = String.format("%04X", cmd.length()) + cmd; + os.write(cmd.getBytes()); + int read = is.read(buf); + if(read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) { + Log.w(LOGTAG, "adb cmd faild."); + return false; + } + return true; + } + + /** + * + * Get a tcp socket connection to specified IP address and port proxied by adb + * + * The proxying is transparent, e.g. if a socket is returned, then it can be written to and + * read from as if it is directly connected to the target + * + * @param remoteAddress IP address of the host to connect to + * @param remotePort port of the host to connect to + * @return a valid Socket instance if successful, null otherwise + */ + public static Socket getForwardedSocket(int remoteAddress, int remotePort) { + try { + Socket socket = new Socket(ADB_HOST, ADB_PORT); + String cmd = "tcp:" + remotePort + ":" + convert(remoteAddress); + if(!sendAdbCmd(socket.getInputStream(), socket.getOutputStream(), cmd)) { + socket.close(); + return null; + } + return socket; + } catch (IOException ioe) { + Log.w(LOGTAG, "error creating adb socket", ioe); + return null; + } + } +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/ForwardServer.java b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/ForwardServer.java new file mode 100644 index 0000000..74e018e --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/ForwardServer.java @@ -0,0 +1,117 @@ +package com.android.dumprendertree.forwarder; + +import android.util.Log; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashSet; +import java.util.Set; + +/** + * + * A port forwarding server. Listens at specified local port and forward the tcp communications to + * external host/port via adb networking proxy. + * + */ +public class ForwardServer { + + private static final String LOGTAG = "ForwardServer"; + + private int remotePort; + private int remoteAddress; + private int localPort; + private ServerSocket serverSocket; + private boolean started; + + private Set<Forwarder> forwarders; + + public ForwardServer(int localPort, int remoteAddress, int remotePort) { + this.localPort = localPort; + this.remoteAddress = remoteAddress; + this.remotePort = remotePort; + started = false; + forwarders = new HashSet<Forwarder>(); + } + + public synchronized void start() throws IOException { + if(!started) { + serverSocket = new ServerSocket(localPort); + Thread serverThread = new Thread(new ServerRunner(serverSocket)); + serverThread.setName(LOGTAG); + serverThread.start(); + started = true; + } + } + + public synchronized void stop() { + if(started) { + synchronized (forwarders) { + for(Forwarder forwarder : forwarders) + forwarder.stop(); + forwarders.clear(); + } + try { + serverSocket.close(); + } catch (IOException ioe) { + Log.v(LOGTAG, "exception while closing", ioe); + } finally { + started = false; + } + } + } + + public synchronized boolean isRunning() { + return started; + } + + private class ServerRunner implements Runnable { + + private ServerSocket socket; + + public ServerRunner(ServerSocket socket) { + this.socket = socket; + } + + public void run() { + try { + while (true) { + Socket localSocket = socket.accept(); + Socket remoteSocket = AdbUtils.getForwardedSocket(remoteAddress, remotePort); + if(remoteSocket == null) { + try { + localSocket.close(); + } catch (IOException ioe) { + Log.w(LOGTAG, "error while closing socket", ioe); + } finally { + Log.w(LOGTAG, "failed to start forwarding from " + localSocket); + } + } else { + Forwarder forwarder = new Forwarder(localSocket, remoteSocket, + ForwardServer.this); + forwarder.start(); + } + } + } catch (IOException ioe) { + return; + } + } + } + + public void register(Forwarder forwarder) { + synchronized (forwarders) { + if(!forwarders.contains(forwarder)) { + forwarders.add(forwarder); + } + } + } + + public void unregister(Forwarder recyclable) { + synchronized (forwarders) { + if(forwarders.contains(recyclable)) { + recyclable.stop(); + forwarders.remove(recyclable); + } + } + } +}
\ No newline at end of file diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/Forwarder.java b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/Forwarder.java new file mode 100644 index 0000000..e1e04a7 --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/Forwarder.java @@ -0,0 +1,92 @@ +package com.android.dumprendertree.forwarder; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * + * Worker class for {@link ForwardServer}. A Forwarder will be created once the ForwardServer + * accepts an incoming connection, and it will then forward the incoming/outgoing streams to a + * connection already proxied by adb networking (see also {@link AdbUtils}). + * + */ +public class Forwarder { + + private ForwardServer server; + private Socket from, to; + + private static final String LOGTAG = "Forwarder"; + + public Forwarder (Socket from, Socket to, ForwardServer server) { + this.server = server; + this.from = from; + this.to = to; + server.register(this); + } + + public void start() { + Thread outgoing = new Thread(new SocketPipe(from, to)); + Thread incoming = new Thread(new SocketPipe(to, from)); + outgoing.setName(LOGTAG); + incoming.setName(LOGTAG); + outgoing.start(); + incoming.start(); + } + + public void stop() { + shutdown(from); + shutdown(to); + } + + private void shutdown(Socket socket) { + try { + socket.shutdownInput(); + } catch (IOException e) { + Log.v(LOGTAG, "Socket#shutdownInput", e); + } + try { + socket.shutdownOutput(); + } catch (IOException e) { + Log.v(LOGTAG, "Socket#shutdownOutput", e); + } + try { + socket.close(); + } catch (IOException e) { + Log.v(LOGTAG, "Socket#close", e); + } + } + + private class SocketPipe implements Runnable { + + private Socket in, out; + + public SocketPipe(Socket in, Socket out) { + this.in = in; + this.out = out; + } + + public void run() { + try { + int length; + InputStream is = in.getInputStream(); + OutputStream os = out.getOutputStream(); + byte[] buffer = new byte[4096]; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } catch (IOException ioe) { + } finally { + server.unregister(Forwarder.this); + } + } + + @Override + public String toString() { + return "SocketPipe{" + in + "=>" + out + "}"; + } + } +} |
