Merge "Remove WebViewClassic specific test code" into klp-dev

This commit is contained in:
Jonathan Dixon
2013-08-30 19:15:41 +00:00
committed by Android (Google) Code Review
74 changed files with 0 additions and 23320 deletions

View File

@@ -1,38 +0,0 @@
/*
* Copyright (C) 2011 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 android.webkit;
import com.android.frameworks.coretests.R;
import android.app.Activity;
import android.os.Bundle;
public class AccessibilityInjectorTestActivity extends Activity {
private WebView mWebView;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.accessibility_injector_test);
mWebView = (WebView) findViewById(R.id.webview);
}
public WebView getWebView() {
return mWebView;
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright (C) 2009 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 android.webkit;
import android.test.AndroidTestCase;
import android.util.Log;
import android.webkit.CacheManager.CacheResult;
import android.webkit.PluginData;
import android.webkit.UrlInterceptHandler;
import java.util.LinkedList;
import java.util.Map;
public class UrlInterceptRegistryTest extends AndroidTestCase {
/**
* To run these tests: $ mmm
* frameworks/base/tests/CoreTests/android && adb remount && adb
* sync $ adb shell am instrument -w -e class \
* android.webkit.UrlInterceptRegistryTest \
* android.core/android.test.InstrumentationTestRunner
*/
private static class MockUrlInterceptHandler implements UrlInterceptHandler {
private PluginData mData;
private String mUrl;
public MockUrlInterceptHandler(PluginData data, String url) {
mData = data;
mUrl = url;
}
public CacheResult service(String url, Map<String, String> headers) {
return null;
}
public PluginData getPluginData(String url,
Map<String,
String> headers) {
if (mUrl.equals(url)) {
return mData;
}
return null;
}
}
public void testGetPluginData() {
PluginData data = new PluginData(null, 0 , null, 200);
String url = new String("url1");
MockUrlInterceptHandler handler1 =
new MockUrlInterceptHandler(data, url);
data = new PluginData(null, 0 , null, 404);
url = new String("url2");
MockUrlInterceptHandler handler2 =
new MockUrlInterceptHandler(data, url);
assertTrue(UrlInterceptRegistry.registerHandler(handler1));
assertTrue(UrlInterceptRegistry.registerHandler(handler2));
data = UrlInterceptRegistry.getPluginData("url1", null);
assertTrue(data != null);
assertTrue(data.getStatusCode() == 200);
data = UrlInterceptRegistry.getPluginData("url2", null);
assertTrue(data != null);
assertTrue(data.getStatusCode() == 404);
assertTrue(UrlInterceptRegistry.unregisterHandler(handler1));
assertTrue(UrlInterceptRegistry.unregisterHandler(handler2));
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2006 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 android.webkit;
import android.test.AndroidTestCase;
import android.text.format.DateFormat;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.webkit.DateSorter;
import java.util.Calendar;
import java.util.Date;
public class WebkitTest extends AndroidTestCase {
private static final String LOGTAG = WebkitTest.class.getName();
@MediumTest
public void testDateSorter() throws Exception {
/**
* Note: check the logging output manually to test
* nothing automated yet, besides object creation
*/
DateSorter dateSorter = new DateSorter(mContext);
Date date = new Date();
for (int i = 0; i < DateSorter.DAY_COUNT; i++) {
Log.i(LOGTAG, "Boundary " + i + " " + dateSorter.getBoundary(i));
Log.i(LOGTAG, "Label " + i + " " + dateSorter.getLabel(i));
}
Calendar c = Calendar.getInstance();
long time = c.getTimeInMillis();
int index;
Log.i(LOGTAG, "now: " + dateSorter.getIndex(time));
for (int i = 0; i < 20; i++) {
time -= 8 * 60 * 60 * 1000; // 8 hours
date.setTime(time);
c.setTime(date);
index = dateSorter.getIndex(time);
Log.i(LOGTAG, "time: " + DateFormat.format("yyyy/MM/dd HH:mm:ss", c).toString() +
" " + index + " " + dateSorter.getLabel(index));
}
}
}

View File

@@ -1,128 +0,0 @@
/*
* Copyright (C) 2010 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 android.webkit;
import android.test.AndroidTestCase;
public class ZoomManagerTest extends AndroidTestCase {
private ZoomManager zoomManager;
@Override
public void setUp() {
WebView webView = new WebView(this.getContext());
WebViewClassic webViewClassic = WebViewClassic.fromWebView(webView);
CallbackProxy callbackProxy = new CallbackProxy(this.getContext(), webViewClassic);
zoomManager = new ZoomManager(webViewClassic, callbackProxy);
zoomManager.init(1.00f);
}
public void testInit() {
testInit(0.01f);
testInit(1.00f);
testInit(1.25f);
}
private void testInit(float density) {
zoomManager.init(density);
actualScaleTest(density);
defaultScaleTest(density);
assertEquals(zoomManager.getDefaultMaxZoomScale(), zoomManager.getMaxZoomScale());
assertEquals(zoomManager.getDefaultMinZoomScale(), zoomManager.getMinZoomScale());
assertEquals(density, zoomManager.getTextWrapScale());
}
public void testUpdateDefaultZoomDensity() {
// test the basic case where the actual values are equal to the defaults
testUpdateDefaultZoomDensity(0.01f);
testUpdateDefaultZoomDensity(1.00f);
testUpdateDefaultZoomDensity(1.25f);
}
private void testUpdateDefaultZoomDensity(float density) {
zoomManager.updateDefaultZoomDensity(density);
defaultScaleTest(density);
}
public void testUpdateDefaultZoomDensityWithSmallMinZoom() {
// test the case where the minZoomScale has changed to be < the default
float newDefaultScale = 1.50f;
float minZoomScale = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * newDefaultScale;
WebViewCore.ViewState minViewState = new WebViewCore.ViewState();
minViewState.mMinScale = minZoomScale - 0.1f;
zoomManager.updateZoomRange(minViewState, 0, 0);
zoomManager.updateDefaultZoomDensity(newDefaultScale);
defaultScaleTest(newDefaultScale);
}
public void testUpdateDefaultZoomDensityWithLargeMinZoom() {
// test the case where the minZoomScale has changed to be > the default
float newDefaultScale = 1.50f;
float minZoomScale = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * newDefaultScale;
WebViewCore.ViewState minViewState = new WebViewCore.ViewState();
minViewState.mMinScale = minZoomScale + 0.1f;
zoomManager.updateZoomRange(minViewState, 0, 0);
zoomManager.updateDefaultZoomDensity(newDefaultScale);
defaultScaleTest(newDefaultScale);
}
public void testUpdateDefaultZoomDensityWithSmallMaxZoom() {
// test the case where the maxZoomScale has changed to be < the default
float newDefaultScale = 1.50f;
float maxZoomScale = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * newDefaultScale;
WebViewCore.ViewState maxViewState = new WebViewCore.ViewState();
maxViewState.mMaxScale = maxZoomScale - 0.1f;
zoomManager.updateZoomRange(maxViewState, 0, 0);
zoomManager.updateDefaultZoomDensity(newDefaultScale);
defaultScaleTest(newDefaultScale);
}
public void testUpdateDefaultZoomDensityWithLargeMaxZoom() {
// test the case where the maxZoomScale has changed to be > the default
float newDefaultScale = 1.50f;
float maxZoomScale = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * newDefaultScale;
WebViewCore.ViewState maxViewState = new WebViewCore.ViewState();
maxViewState.mMaxScale = maxZoomScale + 0.1f;
zoomManager.updateZoomRange(maxViewState, 0, 0);
zoomManager.updateDefaultZoomDensity(newDefaultScale);
defaultScaleTest(newDefaultScale);
}
public void testComputeScaleWithLimits() {
final float maxScale = zoomManager.getMaxZoomScale();
final float minScale = zoomManager.getMinZoomScale();
assertTrue(maxScale > minScale);
assertEquals(maxScale, zoomManager.computeScaleWithLimits(maxScale));
assertEquals(maxScale, zoomManager.computeScaleWithLimits(maxScale + .01f));
assertEquals(minScale, zoomManager.computeScaleWithLimits(minScale));
assertEquals(minScale, zoomManager.computeScaleWithLimits(minScale - .01f));
}
private void actualScaleTest(float actualScale) {
assertEquals(actualScale, zoomManager.getScale());
assertEquals(1 / actualScale, zoomManager.getInvScale());
}
private void defaultScaleTest(float defaultScale) {
final float maxDefault = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * defaultScale;
final float minDefault = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * defaultScale;
assertEquals(defaultScale, zoomManager.getDefaultScale());
assertEquals(1 / defaultScale, zoomManager.getInvDefaultScale());
assertEquals(maxDefault, zoomManager.getDefaultMaxZoomScale());
assertEquals(minDefault, zoomManager.getDefaultMinZoomScale());
}
}

View File

@@ -1,12 +0,0 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := DumpRenderTree
include $(BUILD_PACKAGE)

View File

@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree">
<application android:name="HTMLHostApp">
<uses-library android:name="android.test.runner" />
<activity android:name="Menu" android:label="Dump Render Tree"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.TEST" />
</intent-filter>
</activity>
<activity android:name="TestShellActivity"
android:launchMode="singleTop"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Light"/>
<activity android:name="ReliabilityTestActivity" android:screenOrientation="portrait"
android:theme="@android:style/Theme.Light"/>
</application>
<instrumentation android:name=".LayoutTestsAutoRunner"
android:targetPackage="com.android.dumprendertree"
android:label="Layout test automation runner"
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_SDCARD" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-sdk android:minSdkVersion="5"
android:targetSdkVersion="5" />
</manifest>

View File

@@ -1,4 +0,0 @@
/sdcard/android/layout_tests/http/tests/xmlhttprequest/basic-auth.html
/sdcard/android/layout_tests/http/tests/xmlhttprequest/failed-auth.html
/sdcard/android/layout_tests/http/tests/xmlhttprequest/cross-origin-authorization.html
/sdcard/android/layout_tests/http/tests/xmlhttprequest/cross-origin-no-authorization.html

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,320 +0,0 @@
#!/usr/bin/python
"""Run layout tests using Android emulator and instrumentation.
First, you need to get an SD card or sdcard image that has layout tests on it.
Layout tests are in following directory:
/sdcard/webkit/layout_tests
For example, /sdcard/webkit/layout_tests/fast
Usage:
Run all tests under fast/ directory:
run_layout_tests.py, or
run_layout_tests.py fast
Run all tests under a sub directory:
run_layout_tests.py fast/dom
Run a single test:
run_layout_tests.py fast/dom/
After a merge, if there are changes of layout tests in SD card, you need to
use --refresh-test-list option *once* to re-generate test list on the card.
Some other options are:
--rebaseline generates expected layout tests results under /sdcard/webkit/expected_result/
--time-out-ms (default is 8000 millis) for each test
--adb-options="-e" passes option string to adb
--results-directory=..., (default is ./layout-test-results) directory name under which results are stored.
--js-engine the JavaScript engine currently in use, determines which set of Android-specific expected results we should use, should be 'jsc' or 'v8'
"""
import logging
import optparse
import os
import subprocess
import sys
import time
def CountLineNumber(filename):
"""Compute the number of lines in a given file.
Args:
filename: a file name related to the current directory.
"""
fp = open(os.path.abspath(filename), "r");
lines = 0
for line in fp.readlines():
lines = lines + 1
fp.close()
return lines
def DumpRenderTreeFinished(adb_cmd):
""" Check if DumpRenderTree finished running tests
Args:
output: adb_cmd string
"""
# pull /sdcard/webkit/running_test.txt, if the content is "#DONE", it's done
shell_cmd_str = adb_cmd + " shell cat /sdcard/webkit/running_test.txt"
adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
return adb_output.strip() == "#DONE"
def DiffResults(marker, new_results, old_results, diff_results, strip_reason,
new_count_first=True):
""" Given two result files, generate diff and
write to diff_results file. All arguments are absolute paths
to files.
"""
old_file = open(old_results, "r")
new_file = open(new_results, "r")
diff_file = open(diff_results, "a")
# Read lines from each file
ndict = new_file.readlines()
cdict = old_file.readlines()
# Write marker to diff file
diff_file.writelines(marker + "\n")
diff_file.writelines("###############\n")
# Strip reason from result lines
if strip_reason is True:
for i in range(0, len(ndict)):
ndict[i] = ndict[i].split(' ')[0] + "\n"
for i in range(0, len(cdict)):
cdict[i] = cdict[i].split(' ')[0] + "\n"
params = {
"new": [0, ndict, cdict, "+"],
"miss": [0, cdict, ndict, "-"]
}
if new_count_first:
order = ["new", "miss"]
else:
order = ["miss", "new"]
for key in order:
for line in params[key][1]:
if line not in params[key][2]:
if line[-1] != "\n":
line += "\n";
diff_file.writelines(params[key][3] + line)
params[key][0] += 1
logging.info(marker + " >>> " + str(params["new"][0]) + " new, " +
str(params["miss"][0]) + " misses")
diff_file.writelines("\n\n")
old_file.close()
new_file.close()
diff_file.close()
return
def CompareResults(ref_dir, results_dir):
"""Compare results in two directories
Args:
ref_dir: the reference directory having layout results as references
results_dir: the results directory
"""
logging.info("Comparing results to " + ref_dir)
diff_result = os.path.join(results_dir, "layout_tests_diff.txt")
if os.path.exists(diff_result):
os.remove(diff_result)
files=["crashed", "failed", "passed", "nontext"]
for f in files:
result_file_name = "layout_tests_" + f + ".txt"
DiffResults(f, os.path.join(results_dir, result_file_name),
os.path.join(ref_dir, result_file_name), diff_result,
False, f != "passed")
logging.info("Detailed diffs are in " + diff_result)
def main(options, args):
"""Run the tests. Will call sys.exit when complete.
Args:
options: a dictionary of command line options
args: a list of sub directories or files to test
"""
# Set up logging format.
log_level = logging.INFO
if options.verbose:
log_level = logging.DEBUG
logging.basicConfig(level=log_level,
format='%(message)s')
# Include all tests if none are specified.
if not args:
path = '/';
else:
path = ' '.join(args);
adb_cmd = "adb ";
if options.adb_options:
adb_cmd += options.adb_options
# Re-generate the test list if --refresh-test-list is on
if options.refresh_test_list:
logging.info("Generating test list.");
generate_test_list_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#generateTestList -e path \"" + path + "\" -w com.android.dumprendertree/.LayoutTestsAutoRunner"
adb_output = subprocess.Popen(generate_test_list_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
if adb_output.find('Process crashed') != -1:
logging.info("Aborting because cannot generate test list.\n" + adb_output)
sys.exit(1)
logging.info("Running tests")
# Count crashed tests.
crashed_tests = []
timeout_ms = '15000'
if options.time_out_ms:
timeout_ms = options.time_out_ms
# Run test until it's done
run_layout_test_cmd_prefix = adb_cmd + " shell am instrument"
run_layout_test_cmd_postfix = " -e path \"" + path + "\" -e timeout " + timeout_ms
if options.rebaseline:
run_layout_test_cmd_postfix += " -e rebaseline true"
# If the JS engine is not specified on the command line, try reading the
# JS_ENGINE environment variable, which is used by the build system in
# external/webkit/Android.mk.
js_engine = options.js_engine
if not js_engine and os.environ.has_key('JS_ENGINE'):
js_engine = os.environ['JS_ENGINE']
if js_engine:
run_layout_test_cmd_postfix += " -e jsengine " + js_engine
run_layout_test_cmd_postfix += " -w com.android.dumprendertree/.LayoutTestsAutoRunner"
# Call LayoutTestsAutoTest::startLayoutTests.
run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests" + run_layout_test_cmd_postfix
adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
while not DumpRenderTreeFinished(adb_cmd):
# Get the running_test.txt
logging.error("DumpRenderTree crashed, output:\n" + adb_output)
shell_cmd_str = adb_cmd + " shell cat /sdcard/webkit/running_test.txt"
crashed_test = ""
while not crashed_test:
(crashed_test, err) = subprocess.Popen(
shell_cmd_str, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
crashed_test = crashed_test.strip()
if not crashed_test:
logging.error('Cannot get crashed test name, device offline?')
logging.error('stderr: ' + err)
logging.error('retrying in 10s...')
time.sleep(10)
logging.info(crashed_test + " CRASHED");
crashed_tests.append(crashed_test);
logging.info("Resuming layout test runner...");
# Call LayoutTestsAutoTest::resumeLayoutTests
run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests" + run_layout_test_cmd_postfix
adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
if adb_output.find('INSTRUMENTATION_FAILED') != -1:
logging.error("Error happened : " + adb_output)
sys.exit(1)
logging.debug(adb_output);
logging.info("Done\n");
# Pull results from /sdcard
results_dir = options.results_directory
if not os.path.exists(results_dir):
os.makedirs(results_dir)
if not os.path.isdir(results_dir):
logging.error("Cannot create results dir: " + results_dir);
sys.exit(1);
result_files = ["/sdcard/layout_tests_passed.txt",
"/sdcard/layout_tests_failed.txt",
"/sdcard/layout_tests_ignored.txt",
"/sdcard/layout_tests_nontext.txt"]
for file in result_files:
shell_cmd_str = adb_cmd + " pull " + file + " " + results_dir
adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
logging.debug(adb_output)
# Create the crash list.
fp = open(results_dir + "/layout_tests_crashed.txt", "w");
for crashed_test in crashed_tests:
fp.writelines(crashed_test + '\n')
fp.close()
# Count the number of tests in each category.
passed_tests = CountLineNumber(results_dir + "/layout_tests_passed.txt")
logging.info(str(passed_tests) + " passed")
failed_tests = CountLineNumber(results_dir + "/layout_tests_failed.txt")
logging.info(str(failed_tests) + " failed")
ignored_tests = CountLineNumber(results_dir + "/layout_tests_ignored.txt")
logging.info(str(ignored_tests) + " ignored results")
crashed_tests = CountLineNumber(results_dir + "/layout_tests_crashed.txt")
logging.info(str(crashed_tests) + " crashed")
nontext_tests = CountLineNumber(results_dir + "/layout_tests_nontext.txt")
logging.info(str(nontext_tests) + " no dumpAsText")
logging.info(str(passed_tests + failed_tests + ignored_tests + crashed_tests + nontext_tests) + " TOTAL")
logging.info("Results are stored under: " + results_dir + "\n")
# Comparing results to references to find new fixes and regressions.
results_dir = os.path.abspath(options.results_directory)
ref_dir = options.ref_directory
# if ref_dir is null, cannonify ref_dir to the script dir.
if not ref_dir:
script_self = sys.argv[0]
script_dir = os.path.dirname(script_self)
ref_dir = os.path.join(script_dir, "results")
ref_dir = os.path.abspath(ref_dir)
CompareResults(ref_dir, results_dir)
if '__main__' == __name__:
option_parser = optparse.OptionParser()
option_parser.add_option("", "--rebaseline", action="store_true",
default=False,
help="generate expected results for those tests not having one")
option_parser.add_option("", "--time-out-ms",
default=None,
help="set the timeout for each test")
option_parser.add_option("", "--verbose", action="store_true",
default=False,
help="include debug-level logging")
option_parser.add_option("", "--refresh-test-list", action="store_true",
default=False,
help="re-generate test list, it may take some time.")
option_parser.add_option("", "--adb-options",
default=None,
help="pass options to adb, such as -d -e, etc");
option_parser.add_option("", "--results-directory",
default="layout-test-results",
help="directory which results are stored.")
option_parser.add_option("", "--ref-directory",
default=None,
dest="ref_directory",
help="directory where reference results are stored.")
option_parser.add_option("", "--js-engine",
default=None,
help="The JavaScript engine currently in use, which determines which set of Android-specific expected results we should use. Should be 'jsc' or 'v8'.");
options, args = option_parser.parse_args();
main(options, args)

View File

@@ -1,163 +0,0 @@
#!/usr/bin/python
"""Run page cycler tests using Android instrumentation.
First, you need to get an SD card or sdcard image that has page cycler tests.
Usage:
Run a single page cycler test:
run_page_cycler.py "file:///sdcard/webkit/page_cycler/moz/start.html\?auto=1\&iterations=10"
"""
import logging
import optparse
import os
import subprocess
import sys
import time
def main(options, args):
"""Run the tests. Will call sys.exit when complete.
"""
# Set up logging format.
log_level = logging.INFO
if options.verbose:
log_level = logging.DEBUG
logging.basicConfig(level=log_level,
format='%(message)s')
# Include all tests if none are specified.
if not args:
print "need a URL, e.g. file:///sdcard/webkit/page_cycler/moz/start.html\?auto=1\&iterations=10"
print " or remote:android-browser-test:80/page_cycler/"
sys.exit(1)
else:
path = ' '.join(args);
if path[:7] == "remote:":
remote_path = path[7:]
else:
remote_path = None
adb_cmd = "adb ";
if options.adb_options:
adb_cmd += options.adb_options
logging.info("Running the test ...")
# Count crashed tests.
crashed_tests = []
timeout_ms = '0'
if options.time_out_ms:
timeout_ms = options.time_out_ms
# Run test until it's done
run_load_test_cmd_prefix = adb_cmd + " shell am instrument"
run_load_test_cmd_postfix = " -w com.android.dumprendertree/.LayoutTestsAutoRunner"
# Call LoadTestsAutoTest::runTest.
run_load_test_cmd = run_load_test_cmd_prefix + " -e class com.android.dumprendertree.LoadTestsAutoTest#runPageCyclerTest -e timeout " + timeout_ms
if remote_path:
if options.suite:
run_load_test_cmd += " -e suite %s -e forward %s " % (options.suite,
remote_path)
else:
print "for network mode, need to specify --suite as well."
sys.exit(1)
if options.iteration:
run_load_test_cmd += " -e iteration %s" % options.iteration
else:
run_load_test_cmd += " -e path \"%s\" " % path
if options.drawtime:
run_load_test_cmd += " -e drawtime true "
if options.save_image:
run_load_test_cmd += " -e saveimage \"%s\"" % options.save_image
run_load_test_cmd += run_load_test_cmd_postfix
(adb_output, adb_error) = subprocess.Popen(run_load_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
fail_flag = False
for line in adb_output.splitlines():
line = line.strip()
if line.find('INSTRUMENTATION_CODE') == 0:
if not line[22:] == '-1':
fail_flag = True
break
if (line.find('INSTRUMENTATION_FAILED') != -1 or
line.find('Process crashed.') != -1):
fail_flag = True
break
if fail_flag:
logging.error("Error happened : " + adb_output)
sys.exit(1)
logging.info(adb_output);
logging.info(adb_error);
logging.info("Done\n");
# Pull results from /sdcard/load_test_result.txt
results_dir = options.results_directory
if not os.path.exists(results_dir):
os.makedirs(results_dir)
if not os.path.isdir(results_dir):
logging.error("Cannot create results dir: " + results_dir)
sys.exit(1)
result_file = "/sdcard/load_test_result.txt"
shell_cmd_str = adb_cmd + " pull " + result_file + " " + results_dir
(adb_output, err) = subprocess.Popen(
shell_cmd_str, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if not os.path.isfile(os.path.join(results_dir, "load_test_result.txt")):
logging.error("Failed to pull result file.")
logging.error("adb stdout:")
logging.error(adb_output)
logging.error("adb stderr:")
logging.error(err)
logging.info("Results are stored under: " + results_dir + "/load_test_result.txt\n")
if '__main__' == __name__:
option_parser = optparse.OptionParser()
option_parser.add_option("-t", "--time-out-ms",
default=None,
help="set the timeout for each test")
option_parser.add_option("-v", "--verbose", action="store_true",
default=False,
help="include debug-level logging")
option_parser.add_option("-a", "--adb-options",
default=None,
help="pass options to adb, such as -d -e, etc");
option_parser.add_option("-r", "--results-directory",
default="layout-test-results",
help="directory which results are stored.")
option_parser.add_option("-d", "--drawtime", action="store_true",
default=False,
help="log draw time for each page rendered.")
option_parser.add_option("-s", "--save-image",
default=None,
help="stores rendered page to a location on device.")
option_parser.add_option("-u", "--suite",
default=None,
help="(for network mode) specify the suite to"
" run by name")
option_parser.add_option("-i", "--iteration",
default="5",
help="(for network mode) specify how many iterations"
" to run")
options, args = option_parser.parse_args();
main(options, args)

View File

@@ -1,276 +0,0 @@
#!/usr/bin/python2.4
"""Run reliability tests using Android instrumentation.
A test file consists of list web sites to test is needed as a parameter
Usage:
run_reliability_tests.py path/to/url/list
"""
import logging
import optparse
import os
import subprocess
import sys
import time
from Numeric import *
TEST_LIST_FILE = "/sdcard/android/reliability_tests_list.txt"
TEST_STATUS_FILE = "/sdcard/android/reliability_running_test.txt"
TEST_TIMEOUT_FILE = "/sdcard/android/reliability_timeout_test.txt"
TEST_LOAD_TIME_FILE = "/sdcard/android/reliability_load_time.txt"
HTTP_URL_FILE = "urllist_http"
HTTPS_URL_FILE = "urllist_https"
NUM_URLS = 25
def DumpRenderTreeFinished(adb_cmd):
"""Check if DumpRenderTree finished running.
Args:
adb_cmd: adb command string
Returns:
True if DumpRenderTree has finished, False otherwise
"""
# pull test status file and look for "#DONE"
shell_cmd_str = adb_cmd + " shell cat " + TEST_STATUS_FILE
adb_output = subprocess.Popen(shell_cmd_str,
shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
return adb_output.strip() == "#DONE"
def RemoveDeviceFile(adb_cmd, file_name):
shell_cmd_str = adb_cmd + " shell rm " + file_name
subprocess.Popen(shell_cmd_str,
shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
def Bugreport(url, bugreport_dir, adb_cmd):
"""Pull a bugreport from the device."""
bugreport_filename = "%s/reliability_bugreport_%d.txt" % (bugreport_dir,
int(time.time()))
# prepend the report with url
handle = open(bugreport_filename, "w")
handle.writelines("Bugreport for crash in url - %s\n\n" % url)
handle.close()
cmd = "%s bugreport >> %s" % (adb_cmd, bugreport_filename)
os.system(cmd)
def ProcessPageLoadTime(raw_log):
"""Processes the raw page load time logged by test app."""
log_handle = open(raw_log, "r")
load_times = {}
for line in log_handle:
line = line.strip()
pair = line.split("|")
if len(pair) != 2:
logging.info("Line has more than one '|': " + line)
continue
if pair[0] not in load_times:
load_times[pair[0]] = []
try:
pair[1] = int(pair[1])
except ValueError:
logging.info("Lins has non-numeric load time: " + line)
continue
load_times[pair[0]].append(pair[1])
log_handle.close()
# rewrite the average time to file
log_handle = open(raw_log, "w")
for url, times in load_times.iteritems():
# calculate std
arr = array(times)
avg = average(arr)
d = arr - avg
std = sqrt(sum(d * d) / len(arr))
output = ("%-70s%-10d%-10d%-12.2f%-12.2f%s\n" %
(url, min(arr), max(arr), avg, std,
array2string(arr)))
log_handle.write(output)
log_handle.close()
def main(options, args):
"""Send the url list to device and start testing, restart if crashed."""
# Set up logging format.
log_level = logging.INFO
if options.verbose:
log_level = logging.DEBUG
logging.basicConfig(level=log_level,
format="%(message)s")
# Include all tests if none are specified.
if not args:
print "Missing URL list file"
sys.exit(1)
else:
path = args[0]
if not options.crash_file:
print "Missing crash file name, use --crash-file to specify"
sys.exit(1)
else:
crashed_file = options.crash_file
if not options.timeout_file:
print "Missing timeout file, use --timeout-file to specify"
sys.exit(1)
else:
timedout_file = options.timeout_file
if not options.delay:
manual_delay = 0
else:
manual_delay = options.delay
if not options.bugreport:
bugreport_dir = "."
else:
bugreport_dir = options.bugreport
if not os.path.exists(bugreport_dir):
os.makedirs(bugreport_dir)
if not os.path.isdir(bugreport_dir):
logging.error("Cannot create results dir: " + bugreport_dir)
sys.exit(1)
adb_cmd = "adb "
if options.adb_options:
adb_cmd += options.adb_options + " "
# push url list to device
test_cmd = adb_cmd + " push \"" + path + "\" \"" + TEST_LIST_FILE + "\""
proc = subprocess.Popen(test_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(adb_output, adb_error) = proc.communicate()
if proc.returncode != 0:
logging.error("failed to push url list to device.")
logging.error(adb_output)
logging.error(adb_error)
sys.exit(1)
# clean up previous results
RemoveDeviceFile(adb_cmd, TEST_STATUS_FILE)
RemoveDeviceFile(adb_cmd, TEST_TIMEOUT_FILE)
RemoveDeviceFile(adb_cmd, TEST_LOAD_TIME_FILE)
logging.info("Running the test ...")
# Count crashed tests.
crashed_tests = []
if options.time_out_ms:
timeout_ms = options.time_out_ms
# Run test until it's done
test_cmd_prefix = adb_cmd + " shell am instrument"
test_cmd_postfix = " -w com.android.dumprendertree/.LayoutTestsAutoRunner"
# Call ReliabilityTestsAutoTest#startReliabilityTests
test_cmd = (test_cmd_prefix + " -e class "
"com.android.dumprendertree.ReliabilityTest#"
"runReliabilityTest -e timeout %s -e delay %s" %
(str(timeout_ms), str(manual_delay)))
if options.logtime:
test_cmd += " -e logtime true"
test_cmd += test_cmd_postfix
adb_output = subprocess.Popen(test_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
while not DumpRenderTreeFinished(adb_cmd):
logging.error("DumpRenderTree exited before all URLs are visited.")
shell_cmd_str = adb_cmd + " shell cat " + TEST_STATUS_FILE
crashed_test = ""
while not crashed_test:
(crashed_test, err) = subprocess.Popen(
shell_cmd_str, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
crashed_test = crashed_test.strip()
if not crashed_test:
logging.error('Cannot get crashed test name, device offline?')
logging.error('stderr: ' + err)
logging.error('retrying in 10s...')
time.sleep(10)
logging.info(crashed_test + " CRASHED")
crashed_tests.append(crashed_test)
Bugreport(crashed_test, bugreport_dir, adb_cmd)
logging.info("Resuming reliability test runner...")
adb_output = subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
if (adb_output.find("INSTRUMENTATION_FAILED") != -1 or
adb_output.find("Process crashed.") != -1):
logging.error("Error happened : " + adb_output)
sys.exit(1)
logging.info(adb_output)
logging.info("Done\n")
if crashed_tests:
file_handle = open(crashed_file, "w")
file_handle.writelines("\n".join(crashed_tests))
logging.info("Crashed URL list stored in: " + crashed_file)
file_handle.close()
else:
logging.info("No crash found.")
# get timeout file from sdcard
test_cmd = (adb_cmd + "pull \"" + TEST_TIMEOUT_FILE + "\" \""
+ timedout_file + "\"")
subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
if options.logtime:
# get logged page load times from sdcard
test_cmd = (adb_cmd + "pull \"" + TEST_LOAD_TIME_FILE + "\" \""
+ options.logtime + "\"")
subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
ProcessPageLoadTime(options.logtime)
if "__main__" == __name__:
option_parser = optparse.OptionParser()
option_parser.add_option("-t", "--time-out-ms",
default=60000,
help="set the timeout for each test")
option_parser.add_option("-v", "--verbose", action="store_true",
default=False,
help="include debug-level logging")
option_parser.add_option("-a", "--adb-options",
default=None,
help="pass options to adb, such as -d -e, etc")
option_parser.add_option("-c", "--crash-file",
default="reliability_crashed_sites.txt",
help="the list of sites that cause browser to crash")
option_parser.add_option("-f", "--timeout-file",
default="reliability_timedout_sites.txt",
help="the list of sites that timedout during test")
option_parser.add_option("-d", "--delay",
default=0,
help="add a manual delay between pages (in ms)")
option_parser.add_option("-b", "--bugreport",
default=".",
help="the directory to store bugreport for crashes")
option_parser.add_option("-l", "--logtime",
default=None,
help="Logs page load time for each url to the file")
opts, arguments = option_parser.parse_args()
main(opts, arguments)

View File

@@ -1,529 +0,0 @@
/*
* 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 android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.webkit.WebStorage;
import java.util.HashMap;
public class CallbackProxy extends Handler implements EventSender, LayoutTestController {
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;
private static final int EVENT_KEY_DOWN_2 = 4;
private static final int EVENT_LEAP = 5;
private static final int EVENT_MOUSE_CLICK = 6;
private static final int EVENT_MOUSE_DOWN = 7;
private static final int EVENT_MOUSE_MOVE = 8;
private static final int EVENT_MOUSE_UP = 9;
private static final int EVENT_TOUCH_START = 10;
private static final int EVENT_TOUCH_MOVE = 11;
private static final int EVENT_TOUCH_END = 12;
private static final int EVENT_TOUCH_CANCEL = 13;
private static final int EVENT_ADD_TOUCH_POINT = 14;
private static final int EVENT_UPDATE_TOUCH_POINT = 15;
private static final int EVENT_RELEASE_TOUCH_POINT = 16;
private static final int EVENT_CLEAR_TOUCH_POINTS = 17;
private static final int EVENT_CANCEL_TOUCH_POINT = 18;
private static final int EVENT_SET_TOUCH_MODIFIER = 19;
private static final int LAYOUT_CLEAR_LIST = 20;
private static final int LAYOUT_DISPLAY = 21;
private static final int LAYOUT_DUMP_TEXT = 22;
private static final int LAYOUT_DUMP_HISTORY = 23;
private static final int LAYOUT_DUMP_CHILD_SCROLL = 24;
private static final int LAYOUT_DUMP_EDIT_CB = 25;
private static final int LAYOUT_DUMP_SEL_RECT = 26;
private static final int LAYOUT_DUMP_TITLE_CHANGES = 27;
private static final int LAYOUT_KEEP_WEB_HISTORY = 28;
private static final int LAYOUT_NOTIFY_DONE = 29;
private static final int LAYOUT_QUEUE_BACK_NAV = 30;
private static final int LAYOUT_QUEUE_FWD_NAV = 31;
private static final int LAYOUT_QUEUE_LOAD = 32;
private static final int LAYOUT_QUEUE_RELOAD = 33;
private static final int LAYOUT_QUEUE_SCRIPT = 34;
private static final int LAYOUT_REPAINT_HORZ = 35;
private static final int LAYOUT_SET_ACCEPT_EDIT = 36;
private static final int LAYOUT_MAIN_FIRST_RESP = 37;
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;
private static final int OVERRIDE_PREFERENCE = 43;
private static final int LAYOUT_DUMP_CHILD_FRAMES_TEXT = 44;
private static final int SET_XSS_AUDITOR_ENABLED = 45;
CallbackProxy(EventSender eventSender,
LayoutTestController layoutTestController) {
mEventSender = eventSender;
mLayoutTestController = layoutTestController;
}
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_DOM_LOG:
mEventSender.enableDOMUIEventLogging(msg.arg1);
break;
case EVENT_FIRE_KBD:
mEventSender.fireKeyboardEventsToElement(msg.arg1);
break;
case EVENT_KEY_DOWN_1:
HashMap map = (HashMap) msg.obj;
mEventSender.keyDown((String) map.get("character"),
(String[]) map.get("withModifiers"));
break;
case EVENT_KEY_DOWN_2:
mEventSender.keyDown((String)msg.obj);
break;
case EVENT_LEAP:
mEventSender.leapForward(msg.arg1);
break;
case EVENT_MOUSE_CLICK:
mEventSender.mouseClick();
break;
case EVENT_MOUSE_DOWN:
mEventSender.mouseDown();
break;
case EVENT_MOUSE_MOVE:
mEventSender.mouseMoveTo(msg.arg1, msg.arg2);
break;
case EVENT_MOUSE_UP:
mEventSender.mouseUp();
break;
case EVENT_TOUCH_START:
mEventSender.touchStart();
break;
case EVENT_TOUCH_MOVE:
mEventSender.touchMove();
break;
case EVENT_TOUCH_END:
mEventSender.touchEnd();
break;
case EVENT_TOUCH_CANCEL:
mEventSender.touchCancel();
break;
case EVENT_ADD_TOUCH_POINT:
mEventSender.addTouchPoint(msg.arg1, msg.arg2);
break;
case EVENT_UPDATE_TOUCH_POINT:
Bundle args = (Bundle) msg.obj;
int x = args.getInt("x");
int y = args.getInt("y");
int id = args.getInt("id");
mEventSender.updateTouchPoint(id, x, y);
break;
case EVENT_SET_TOUCH_MODIFIER:
Bundle modifierArgs = (Bundle) msg.obj;
String modifier = modifierArgs.getString("modifier");
boolean enabled = modifierArgs.getBoolean("enabled");
mEventSender.setTouchModifier(modifier, enabled);
break;
case EVENT_RELEASE_TOUCH_POINT:
mEventSender.releaseTouchPoint(msg.arg1);
break;
case EVENT_CLEAR_TOUCH_POINTS:
mEventSender.clearTouchPoints();
break;
case EVENT_CANCEL_TOUCH_POINT:
mEventSender.cancelTouchPoint(msg.arg1);
break;
case LAYOUT_CLEAR_LIST:
mLayoutTestController.clearBackForwardList();
break;
case LAYOUT_DISPLAY:
mLayoutTestController.display();
break;
case LAYOUT_DUMP_TEXT:
mLayoutTestController.dumpAsText(msg.arg1 == 1);
break;
case LAYOUT_DUMP_CHILD_FRAMES_TEXT:
mLayoutTestController.dumpChildFramesAsText();
break;
case LAYOUT_DUMP_HISTORY:
mLayoutTestController.dumpBackForwardList();
break;
case LAYOUT_DUMP_CHILD_SCROLL:
mLayoutTestController.dumpChildFrameScrollPositions();
break;
case LAYOUT_DUMP_EDIT_CB:
mLayoutTestController.dumpEditingCallbacks();
break;
case LAYOUT_DUMP_SEL_RECT:
mLayoutTestController.dumpSelectionRect();
break;
case LAYOUT_DUMP_TITLE_CHANGES:
mLayoutTestController.dumpTitleChanges();
break;
case LAYOUT_KEEP_WEB_HISTORY:
mLayoutTestController.keepWebHistory();
break;
case LAYOUT_NOTIFY_DONE:
mLayoutTestController.notifyDone();
break;
case LAYOUT_QUEUE_BACK_NAV:
mLayoutTestController.queueBackNavigation(msg.arg1);
break;
case LAYOUT_QUEUE_FWD_NAV:
mLayoutTestController.queueForwardNavigation(msg.arg1);
break;
case LAYOUT_QUEUE_LOAD:
HashMap<String, String> loadMap =
(HashMap<String, String>) msg.obj;
mLayoutTestController.queueLoad(loadMap.get("Url"),
loadMap.get("frameTarget"));
break;
case LAYOUT_QUEUE_RELOAD:
mLayoutTestController.queueReload();
break;
case LAYOUT_QUEUE_SCRIPT:
mLayoutTestController.queueScript((String)msg.obj);
break;
case LAYOUT_REPAINT_HORZ:
mLayoutTestController.repaintSweepHorizontally();
break;
case LAYOUT_SET_ACCEPT_EDIT:
mLayoutTestController.setAcceptsEditing(
msg.arg1 == 1 ? true : false);
break;
case LAYOUT_MAIN_FIRST_RESP:
mLayoutTestController.setMainFrameIsFirstResponder(
msg.arg1 == 1 ? true : false);
break;
case LAYOUT_SET_WINDOW_KEY:
mLayoutTestController.setWindowIsKey(
msg.arg1 == 1 ? true : false);
break;
case LAYOUT_TEST_REPAINT:
mLayoutTestController.testRepaint();
break;
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;
case OVERRIDE_PREFERENCE:
String key = msg.getData().getString("key");
boolean value = msg.getData().getBoolean("value");
mLayoutTestController.overridePreference(key, value);
break;
case SET_XSS_AUDITOR_ENABLED:
mLayoutTestController.setXSSAuditorEnabled(msg.arg1 == 1);
break;
}
}
// EventSender Methods
public void enableDOMUIEventLogging(int DOMNode) {
obtainMessage(EVENT_DOM_LOG, DOMNode, 0).sendToTarget();
}
public void fireKeyboardEventsToElement(int DOMNode) {
obtainMessage(EVENT_FIRE_KBD, DOMNode, 0).sendToTarget();
}
public void keyDown(String character, String[] withModifiers) {
// TODO Auto-generated method stub
HashMap map = new HashMap();
map.put("character", character);
map.put("withModifiers", withModifiers);
obtainMessage(EVENT_KEY_DOWN_1, map).sendToTarget();
}
public void keyDown(String character) {
obtainMessage(EVENT_KEY_DOWN_2, character).sendToTarget();
}
public void leapForward(int milliseconds) {
obtainMessage(EVENT_LEAP, milliseconds, 0).sendToTarget();
}
public void mouseClick() {
obtainMessage(EVENT_MOUSE_CLICK).sendToTarget();
}
public void mouseDown() {
obtainMessage(EVENT_MOUSE_DOWN).sendToTarget();
}
public void mouseMoveTo(int X, int Y) {
obtainMessage(EVENT_MOUSE_MOVE, X, Y).sendToTarget();
}
public void mouseUp() {
obtainMessage(EVENT_MOUSE_UP).sendToTarget();
}
public void touchStart() {
obtainMessage(EVENT_TOUCH_START).sendToTarget();
}
public void addTouchPoint(int x, int y) {
obtainMessage(EVENT_ADD_TOUCH_POINT, x, y).sendToTarget();
}
public void updateTouchPoint(int id, int x, int y) {
Bundle map = new Bundle();
map.putInt("x", x);
map.putInt("y", y);
map.putInt("id", id);
obtainMessage(EVENT_UPDATE_TOUCH_POINT, map).sendToTarget();
}
public void setTouchModifier(String modifier, boolean enabled) {
Bundle map = new Bundle();
map.putString("modifier", modifier);
map.putBoolean("enabled", enabled);
obtainMessage(EVENT_SET_TOUCH_MODIFIER, map).sendToTarget();
}
public void touchMove() {
obtainMessage(EVENT_TOUCH_MOVE).sendToTarget();
}
public void releaseTouchPoint(int id) {
obtainMessage(EVENT_RELEASE_TOUCH_POINT, id, 0).sendToTarget();
}
public void touchEnd() {
obtainMessage(EVENT_TOUCH_END).sendToTarget();
}
public void touchCancel() {
obtainMessage(EVENT_TOUCH_CANCEL).sendToTarget();
}
public void clearTouchPoints() {
obtainMessage(EVENT_CLEAR_TOUCH_POINTS).sendToTarget();
}
public void cancelTouchPoint(int id) {
obtainMessage(EVENT_CANCEL_TOUCH_POINT, id, 0).sendToTarget();
}
// LayoutTestController Methods
public void clearBackForwardList() {
obtainMessage(LAYOUT_CLEAR_LIST).sendToTarget();
}
public void display() {
obtainMessage(LAYOUT_DISPLAY).sendToTarget();
}
public void dumpAsText() {
obtainMessage(LAYOUT_DUMP_TEXT, 0).sendToTarget();
}
public void dumpAsText(boolean enablePixelTests) {
obtainMessage(LAYOUT_DUMP_TEXT, enablePixelTests ? 1 : 0).sendToTarget();
}
public void dumpChildFramesAsText() {
obtainMessage(LAYOUT_DUMP_CHILD_FRAMES_TEXT).sendToTarget();
}
public void dumpBackForwardList() {
obtainMessage(LAYOUT_DUMP_HISTORY).sendToTarget();
}
public void dumpChildFrameScrollPositions() {
obtainMessage(LAYOUT_DUMP_CHILD_SCROLL).sendToTarget();
}
public void dumpEditingCallbacks() {
obtainMessage(LAYOUT_DUMP_EDIT_CB).sendToTarget();
}
public void dumpSelectionRect() {
obtainMessage(LAYOUT_DUMP_SEL_RECT).sendToTarget();
}
public void dumpTitleChanges() {
obtainMessage(LAYOUT_DUMP_TITLE_CHANGES).sendToTarget();
}
public void keepWebHistory() {
obtainMessage(LAYOUT_KEEP_WEB_HISTORY).sendToTarget();
}
public void notifyDone() {
obtainMessage(LAYOUT_NOTIFY_DONE).sendToTarget();
}
public void queueBackNavigation(int howfar) {
obtainMessage(LAYOUT_QUEUE_BACK_NAV, howfar, 0).sendToTarget();
}
public void queueForwardNavigation(int howfar) {
obtainMessage(LAYOUT_QUEUE_FWD_NAV, howfar, 0).sendToTarget();
}
public void queueLoad(String Url, String frameTarget) {
HashMap <String, String>map = new HashMap<String, String>();
map.put("Url", Url);
map.put("frameTarget", frameTarget);
obtainMessage(LAYOUT_QUEUE_LOAD, map).sendToTarget();
}
public void queueReload() {
obtainMessage(LAYOUT_QUEUE_RELOAD).sendToTarget();
}
public void queueScript(String scriptToRunInCurrentContext) {
obtainMessage(LAYOUT_QUEUE_SCRIPT,
scriptToRunInCurrentContext).sendToTarget();
}
public void repaintSweepHorizontally() {
obtainMessage(LAYOUT_REPAINT_HORZ).sendToTarget();
}
public void setAcceptsEditing(boolean b) {
obtainMessage(LAYOUT_SET_ACCEPT_EDIT, b ? 1 : 0, 0).sendToTarget();
}
public void setMainFrameIsFirstResponder(boolean b) {
obtainMessage(LAYOUT_MAIN_FIRST_RESP, b ? 1 : 0, 0).sendToTarget();
}
public void setWindowIsKey(boolean b) {
obtainMessage(LAYOUT_SET_WINDOW_KEY, b ? 1 : 0, 0).sendToTarget();
}
public void testRepaint() {
obtainMessage(LAYOUT_TEST_REPAINT).sendToTarget();
}
public void waitUntilDone() {
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 setAppCacheMaximumSize(long size) {
android.webkit.WebStorageClassic.getInstance().setAppCacheMaximumSize(size);
}
public void setCanOpenWindows() {
obtainMessage(LAYOUT_SET_CAN_OPEN_WINDOWS).sendToTarget();
}
public void setMockGeolocationPosition(double latitude,
double longitude,
double accuracy) {
// Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity
// as we need access to the Webview.
mLayoutTestController.setMockGeolocationPosition(latitude,
longitude,
accuracy);
}
public void setMockGeolocationError(int code, String message) {
// Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity
// as we need access to the Webview.
mLayoutTestController.setMockGeolocationError(code, message);
}
public void setGeolocationPermission(boolean allow) {
// Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity
// as we need access to the Webview.
mLayoutTestController.setGeolocationPermission(allow);
}
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
// Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity
// as we need access to the Webview.
mLayoutTestController.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
canProvideGamma, gamma);
}
public void overridePreference(String key, boolean value) {
Message message = obtainMessage(OVERRIDE_PREFERENCE);
message.getData().putString("key", key);
message.getData().putBoolean("value", value);
message.sendToTarget();
}
public void setXSSAuditorEnabled(boolean flag) {
obtainMessage(SET_XSS_AUDITOR_ENABLED, flag ? 1 : 0, 0).sendToTarget();
}
}

View File

@@ -1,39 +0,0 @@
/*
* 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;
public interface EventSender {
public void mouseDown();
public void mouseUp();
public void mouseClick();
public void mouseMoveTo(int X, int Y);
public void leapForward(int milliseconds);
public void keyDown (String character, String[] withModifiers);
public void keyDown (String character);
public void enableDOMUIEventLogging(int DOMNode);
public void fireKeyboardEventsToElement(int DOMNode);
public void touchStart();
public void touchMove();
public void touchEnd();
public void touchCancel();
public void addTouchPoint(int x, int y);
public void updateTouchPoint(int id, int x, int y);
public void setTouchModifier(String modifier, boolean enabled);
public void releaseTouchPoint(int id);
public void clearTouchPoints();
public void cancelTouchPoint(int id);
}

View File

@@ -1,229 +0,0 @@
/*
* 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.util.Vector;
import android.util.*;
public class FileFilter {
private static final String LOGTAG = "FileFilter";
// Returns whether we should ignore this test and skip running it.
// Currently we use this only for tests that crash or hang DumpRenderTree.
// TODO: Fix these and eliminate this method.
public static boolean ignoreTest(String file) {
for (int i = 0; i < ignoreTestList.length; i++) {
if (file.endsWith(ignoreTestList[i])) {
Log.v(LOGTAG, "File path in list of ignored tests: " + file);
return true;
}
}
return false;
}
// Returns whether a directory does not contain layout tests and so can be
// ignored.
public static boolean isNonTestDir(String file) {
for (int i = 0; i < nonTestDirs.length; i++) {
if (file.endsWith(nonTestDirs[i])) {
return true;
}
}
return false;
}
// Returns whether we should ignore the result of this test.
public static boolean ignoreResult(String file) {
for (int i = 0; i < ignoreResultList.size(); i++) {
if (file.endsWith(ignoreResultList.get(i))) {
Log.v(LOGTAG, "File path in list of ignored results: " + file);
return true;
}
}
return false;
}
final static Vector<String> ignoreResultList = new Vector<String>();
static {
fillIgnoreResultList();
}
static final String[] nonTestDirs = {
".", // ignore hidden directories and files
"resources", // ignore resource directories
".svn", // don't run anything under .svn folder
"platform" // No-Android specific tests
};
static final String[] ignoreTestList = {
"canvas/philip/tests/2d.drawImage.broken.html", // blocks test, http://b/2982500
"editing/selection/move-left-right.html", // Causes DumpRenderTree to hang
"fast/js/excessive-comma-usage.html", // Tests huge initializer list, causes OOM.
"fast/js/regexp-charclass-crash.html", // RegExp is too large, causing OOM
"fast/js/regexp-overflow.html", // Result is too large, causing OOM when reading by DRT, http://b/2697589
"fast/regex/test1.html", // Causes DumpRenderTree to hang with V8
"fast/regex/slow.html", // Causes DumpRenderTree to hang with V8
};
static void fillIgnoreResultList() {
// This first block of tests are for features for which Android
// should pass all tests. They are skipped only temporarily.
// TODO: Fix these failing tests and remove them from this list.
ignoreResultList.add("fast/dom/HTMLKeygenElement/keygen.html"); // Missing layoutTestController.shadowRoot()
ignoreResultList.add("fast/dom/Geolocation/window-close-crash.html"); // Missing layoutTestContoller.setCloseRemainingWindowsWhenComplete()
ignoreResultList.add("fast/dom/Geolocation/page-reload-cancel-permission-requests.html"); // Missing layoutTestController.numberOfPendingGeolocationPermissionRequests()
ignoreResultList.add("fast/dom/HTMLLinkElement/link-and-subresource-test.html"); // Missing layoutTestController.dumpResourceResponseMIMETypes()
ignoreResultList.add("fast/dom/HTMLLinkElement/prefetch.html"); // Missing layoutTestController.dumpResourceResponseMIMETypes()
ignoreResultList.add("fast/dom/HTMLLinkElement/subresource.html"); // Missing layoutTestController.dumpResourceResponseMIMETypes()
ignoreResultList.add("fast/encoding/char-decoding.html"); // fails in Java HTTP stack, see http://b/issue?id=3047156
ignoreResultList.add("fast/encoding/hanarei-blog32-fc2-com.html"); // fails in Java HTTP stack, see http://b/issue?id=3046986
ignoreResultList.add("fast/encoding/mailto-always-utf-8.html"); // Requires waitForPolicyDelegate(), see http://b/issue?id=3043468
ignoreResultList.add("fast/encoding/percent-escaping.html"); // fails in Java HTTP stack, see http://b/issue?id=3046984
ignoreResultList.add("http/tests/appcache/empty-manifest.html"); // flaky
ignoreResultList.add("http/tests/appcache/fallback.html"); // http://b/issue?id=2713004
ignoreResultList.add("http/tests/appcache/foreign-fallback.html"); // Flaky, may be due to DRT, see http://b/3285647
ignoreResultList.add("http/tests/appcache/foreign-iframe-main.html"); // flaky - skips states
ignoreResultList.add("http/tests/appcache/manifest-with-empty-file.html"); // flaky
ignoreResultList.add("http/tests/appcache/origin-quota.html"); // needs clearAllApplicationCaches(), see http://b/issue?id=2944196
ignoreResultList.add("storage/database-lock-after-reload.html"); // Succeeds but DumpRenderTree does not read result correctly
ignoreResultList.add("storage/hash-change-with-xhr.html"); // Succeeds but DumpRenderTree does not read result correctly
ignoreResultList.add("storage/open-database-creation-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
ignoreResultList.add("storage/statement-error-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
ignoreResultList.add("storage/statement-success-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
ignoreResultList.add("storage/storageinfo-query-usage.html"); // Need window.webkitStorageInfo
ignoreResultList.add("storage/transaction-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
ignoreResultList.add("storage/transaction-error-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
ignoreResultList.add("storage/transaction-success-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
ignoreResultList.add("storage/domstorage/localstorage/storagetracker/storage-tracker-1-prepare.html"); // Missing layoutTestController.originsWithLocalStorage()
ignoreResultList.add("storage/domstorage/localstorage/storagetracker/storage-tracker-2-create.html"); // Missing layoutTestController.originsWithLocalStorage()
ignoreResultList.add("storage/domstorage/localstorage/storagetracker/storage-tracker-3-delete-all.html"); // Missing layoutTestController.originsWithLocalStorage()
ignoreResultList.add("storage/domstorage/localstorage/storagetracker/storage-tracker-4-create.html"); // Missing layoutTestController.originsWithLocalStorage()
ignoreResultList.add("storage/domstorage/localstorage/storagetracker/storage-tracker-5-delete-one.html"); // Missing layoutTestController.originsWithLocalStorage()
// Expected failures due to unsupported features or tests unsuitable for Android.
ignoreResultList.add("fast/encoding/char-decoding-mac.html"); // Mac-specific encodings (also marked Won't Fix in Chromium, bug 7388)
ignoreResultList.add("fast/encoding/char-encoding-mac.html"); // Mac-specific encodings (also marked Won't Fix in Chromium, bug 7388)
ignoreResultList.add("fast/encoding/idn-security.html"); // Mac-specific IDN checks (also marked Won't Fix in Chromium, bug 21814)
ignoreResultList.add("fast/events/touch/basic-multi-touch-events.html"); // Requires multi-touch gestures not supported by Android system
ignoreResultList.add("fast/events/touch/touch-coords-in-zoom-and-scroll.html"); // Requires eventSender.zoomPageIn(),zoomPageOut()
ignoreResultList.add("fast/events/touch/touch-target.html"); // Requires multi-touch gestures not supported by Android system
ignoreResultList.add("fast/workers"); // workers not supported
ignoreResultList.add("http/tests/cookies/third-party-cookie-relaxing.html"); // We don't support conditional acceptance of third-party cookies
ignoreResultList.add("http/tests/eventsource/workers"); // workers not supported
ignoreResultList.add("http/tests/workers"); // workers not supported
ignoreResultList.add("http/tests/xmlhttprequest/workers"); // workers not supported
ignoreResultList.add("storage/domstorage/localstorage/private-browsing-affects-storage.html"); // private browsing not supported
ignoreResultList.add("storage/domstorage/sessionstorage/private-browsing-affects-storage.html"); // private browsing not supported
ignoreResultList.add("storage/indexeddb"); // indexeddb not supported
ignoreResultList.add("storage/private-browsing-noread-nowrite.html"); // private browsing not supported
ignoreResultList.add("storage/private-browsing-readonly.html"); // private browsing not supported
ignoreResultList.add("websocket/tests/workers"); // workers not supported
ignoreResultList.add("dom/xhtml/level2/html/htmldocument04.xhtml"); // /mnt/sdcard on SR uses lowercase filesystem, this test checks filename and is case senstive.
ignoreResultList.add("dom/html/level2/html/htmldocument04.html"); // ditto
// Expected failures due to missing expected results
ignoreResultList.add("dom/xhtml/level3/core/canonicalform08.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/canonicalform09.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/documentgetinputencoding03.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/entitygetinputencoding02.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/entitygetxmlversion02.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri05.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri07.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri09.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri10.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri11.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri15.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri17.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri18.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodelookupnamespaceuri01.xhtml");
ignoreResultList.add("dom/xhtml/level3/core/nodelookupprefix19.xhtml");
// TODO: These need to be triaged
ignoreResultList.add("fast/css/case-transform.html"); // will not fix #619707
ignoreResultList.add("fast/dom/Element/offsetLeft-offsetTop-body-quirk.html"); // different screen size result in extra spaces in Apple compared to us
ignoreResultList.add("fast/dom/Window/Plug-ins.html"); // need test plugin
ignoreResultList.add("fast/dom/Window/window-screen-properties.html"); // pixel depth
ignoreResultList.add("fast/dom/Window/window-xy-properties.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/dom/attribute-namespaces-get-set.html"); // http://b/733229
ignoreResultList.add("fast/dom/object-embed-plugin-scripting.html"); // dynamic plugins not supported
ignoreResultList.add("fast/dom/tabindex-clamp.html"); // there is extra spacing in the file due to multiple input boxes fitting on one line on Apple, ours are wrapped. Space at line ends are stripped.
ignoreResultList.add("fast/events/anchor-image-scrolled-x-y.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/arrow-navigation.html"); // http://b/735233
ignoreResultList.add("fast/events/capture-on-target.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/dblclick-addEventListener.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/drag-in-frames.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/drag-outside-window.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/event-view-toString.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/frame-click-focus.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/frame-tab-focus.html"); // http://b/734308
ignoreResultList.add("fast/events/iframe-object-onload.html"); // there is extra spacing in the file due to multiple frame boxes fitting on one line on Apple, ours are wrapped. Space at line ends are stripped.
ignoreResultList.add("fast/events/input-image-scrolled-x-y.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseclick-target-and-positioning.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseover-mouseout.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseover-mouseout2.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseup-outside-button.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/mouseup-outside-document.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/onclick-list-marker.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/ondragenter.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/onload-webkit-before-webcore.html"); // missing space in textrun, ok as text is wrapped. ignore. #714933
ignoreResultList.add("fast/events/option-tab.html"); // http://b/734308
ignoreResultList.add("fast/events/window-events-bubble.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/window-events-bubble2.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/events/window-events-capture.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/forms/drag-into-textarea.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/forms/focus-control-to-page.html"); // http://b/716638
ignoreResultList.add("fast/forms/focus2.html"); // http://b/735111
ignoreResultList.add("fast/forms/form-data-encoding-2.html"); // charset convert. #516936 ignore, won't fix
ignoreResultList.add("fast/forms/form-data-encoding.html"); // charset convert. #516936 ignore, won't fix
ignoreResultList.add("fast/forms/input-appearance-maxlength.html"); // execCommand "insertText" not supported
ignoreResultList.add("fast/forms/input-select-on-click.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/forms/listbox-onchange.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/forms/listbox-selection.html"); // http://b/735116
ignoreResultList.add("fast/forms/onselect-textarea.html"); // requires eventSender.mouseMoveTo, mouseDown & mouseUp and abs. position of mouse to select a word. ignore, won't fix #716583
ignoreResultList.add("fast/forms/onselect-textfield.html"); // requires eventSender.mouseMoveTo, mouseDown & mouseUp and abs. position of mouse to select a word. ignore, won't fix #716583
ignoreResultList.add("fast/forms/plaintext-mode-1.html"); // not implemented queryCommandEnabled:BackColor, Undo & Redo
ignoreResultList.add("fast/forms/search-cancel-button-mouseup.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/forms/search-event-delay.html"); // http://b/735120
ignoreResultList.add("fast/forms/select-empty-list.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/forms/select-type-ahead-non-latin.html"); // http://b/735244
ignoreResultList.add("fast/forms/selected-index-assert.html"); // not capturing the console messages
ignoreResultList.add("fast/forms/selection-functions.html"); // there is extra spacing as the text areas and input boxes fit next to each other on Apple, but are wrapped on our screen.
ignoreResultList.add("fast/forms/textarea-appearance-wrap.html"); // Our text areas are a little thinner than Apples. Also RTL test failes
ignoreResultList.add("fast/forms/textarea-initial-caret-position.html"); // Text selection done differently on our platform. When a inputbox gets focus, the entire block is selected.
ignoreResultList.add("fast/forms/textarea-no-scroll-on-blur.html"); // Text selection done differently on our platform. When a inputbox gets focus, the entire block is selected.
ignoreResultList.add("fast/forms/textarea-paste-newline.html"); // Copy&Paste commands not supported
ignoreResultList.add("fast/forms/textarea-scrolled-endline-caret.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/frames/iframe-window-focus.html"); // http://b/735140
ignoreResultList.add("fast/frames/frameElement-widthheight.html"); // screen width&height are different
ignoreResultList.add("fast/frames/frame-js-url-clientWidth.html"); // screen width&height are different
ignoreResultList.add("fast/html/tab-order.html"); // http://b/719289
ignoreResultList.add("fast/js/navigator-mimeTypes-length.html"); // dynamic plugins not supported
ignoreResultList.add("fast/js/string-capitalization.html"); // http://b/516936
ignoreResultList.add("fast/loader/local-JavaScript-from-local.html"); // Requires LayoutTests to exist at /tmp/LayoutTests
ignoreResultList.add("fast/loader/local-iFrame-source-from-local.html"); // Requires LayoutTests to exist at /tmp/LayoutTests
ignoreResultList.add("fast/loader/opaque-base-url.html"); // extra spacing because iFrames rendered next to each other on Apple
ignoreResultList.add("fast/overflow/scroll-vertical-not-horizontal.html"); // http://b/735196
ignoreResultList.add("fast/parser/script-tag-with-trailing-slash.html"); // not capturing the console messages
ignoreResultList.add("fast/replaced/image-map.html"); // requires eventSender.mouseDown(),mouseUp()
ignoreResultList.add("fast/text/plain-text-line-breaks.html"); // extra spacing because iFrames rendered next to each other on Apple
ignoreResultList.add("profiler"); // profiler is not supported
}
}

View File

@@ -1,196 +0,0 @@
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.File;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.DialogInterface;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.os.Bundle;
import android.os.Environment;
public abstract class FileList extends ListActivity
{
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode)
{
case KeyEvent.KEYCODE_DPAD_LEFT:
if (mPath.length() > mBaseLength) {
File f = new File(mPath);
mFocusFile = f.getName();
mFocusIndex = 0;
f = f.getParentFile();
mPath = f.getPath();
updateList();
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
{
Map map = (Map) getListView().getItemAtPosition(getListView().getSelectedItemPosition());
String path = (String)map.get("path");
if ((new File(path)).isDirectory()) {
mPath = path;
mFocusFile = null;
updateList();
} else {
processFile(path, false);
}
return true;
}
default:
break;
}
return super.onKeyDown(keyCode, event);
}
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setupPath();
updateList();
}
protected List getData()
{
List myData = new ArrayList<HashMap>();
File f = new File(mPath);
if (!f.exists()) {
addItem(myData, "!LayoutTests path missing!", "");
return myData;
}
String[] files = f.list();
Arrays.sort(files);
for (int i = 0; i < files.length; i++) {
StringBuilder sb = new StringBuilder(mPath);
sb.append(File.separatorChar);
sb.append(files[i]);
String path = sb.toString();
File c = new File(path);
if (fileFilter(c)) {
if (c.isDirectory()) {
addItem(myData, "<"+files[i]+">", path);
if (mFocusFile != null && mFocusFile.equals(files[i]))
mFocusIndex = myData.size()-1;
}
else
addItem(myData, files[i], path);
}
}
return myData;
}
protected void addItem(List<Map> data, String name, String path)
{
HashMap temp = new HashMap();
temp.put("title", name);
temp.put("path", path);
data.add(temp);
}
protected void onListItemClick(ListView l, View v, int position, long id)
{
Map map = (Map) l.getItemAtPosition(position);
final String path = (String)map.get("path");
if ((new File(path)).isDirectory()) {
final CharSequence[] items = {"Open", "Run"};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Select an Action");
builder.setSingleChoiceItems(items, -1,
new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case OPEN_DIRECTORY:
dialog.dismiss();
mPath = path;
mFocusFile = null;
updateList();
break;
case RUN_TESTS:
dialog.dismiss();
processDirectory(path, false);
break;
}
}
});
builder.create().show();
} else {
processFile(path, false);
}
}
/*
* This function is called when the user has selected a directory in the
* list and wants to perform an action on it instead of navigating into
* the directory.
*/
abstract void processDirectory(String path, boolean selection);
/*
* This function is called when the user has selected a file in the
* file list. The selected file could be a file or a directory.
* The flag indicates if this was from a selection or not.
*/
abstract void processFile(String filename, boolean selection);
/*
* This function is called when the file list is being built. Return
* true if the file is to be added to the file list.
*/
abstract boolean fileFilter(File f);
protected void updateList() {
setListAdapter(new SimpleAdapter(this,
getData(),
android.R.layout.simple_list_item_1,
new String[] {"title"},
new int[] {android.R.id.text1}));
String title = mPath; //.substring(mBaseLength-11); // show the word LayoutTests
setTitle(title);
getListView().setSelection(mFocusIndex);
}
protected void setupPath() {
mPath = Environment.getExternalStorageDirectory() + "/webkit/layout_tests";
mBaseLength = mPath.length();
}
protected String mPath;
protected int mBaseLength;
protected String mFocusFile;
protected int mFocusIndex;
private final static int OPEN_DIRECTORY = 0;
private final static int RUN_TESTS = 1;
}

View File

@@ -1,225 +0,0 @@
/*
* Copyright (C) 2009 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 com.android.dumprendertree.forwarder.ForwardService;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Pattern;
public class FsUtils {
private static final String LOGTAG = "FsUtils";
static final String EXTERNAL_DIR = Environment.getExternalStorageDirectory().toString();
static final String HTTP_TESTS_PREFIX =
EXTERNAL_DIR + "/webkit/layout_tests/http/tests/";
static final String HTTPS_TESTS_PREFIX =
EXTERNAL_DIR + "/webkit/layout_tests/http/tests/ssl/";
static final String HTTP_LOCAL_TESTS_PREFIX =
EXTERNAL_DIR + "/webkit/layout_tests/http/tests/local/";
static final String HTTP_MEDIA_TESTS_PREFIX =
EXTERNAL_DIR + "/webkit/layout_tests/http/tests/media/";
static final String HTTP_WML_TESTS_PREFIX =
EXTERNAL_DIR + "/webkit/layout_tests/http/tests/wml/";
private FsUtils() {
//no creation of instances
}
/**
* @return the number of tests in the list.
*/
public static int writeLayoutTestListRecursively(BufferedOutputStream bos,
String dir, boolean ignoreResultsInDir) throws IOException {
int testCount = 0;
Log.v(LOGTAG, "Searching tests under " + dir);
File d = new File(dir);
if (!d.isDirectory()) {
throw new AssertionError("A directory expected, but got " + dir);
}
ignoreResultsInDir |= FileFilter.ignoreResult(dir);
String[] files = d.list();
for (int i = 0; i < files.length; i++) {
String s = dir + "/" + files[i];
File f = new File(s);
if (f.isDirectory()) {
// If this is not a test directory, we don't recurse into it.
if (!FileFilter.isNonTestDir(s)) {
Log.v(LOGTAG, "Recursing on " + s);
testCount += writeLayoutTestListRecursively(bos, s, ignoreResultsInDir);
}
continue;
}
// If this test should be ignored, we skip it completely.
if (FileFilter.ignoreTest(s)) {
Log.v(LOGTAG, "Ignoring: " + s);
continue;
}
if ((s.toLowerCase().endsWith(".html")
|| s.toLowerCase().endsWith(".xml")
|| s.toLowerCase().endsWith(".xhtml"))
&& !s.endsWith("TEMPLATE.html")) {
Log.v(LOGTAG, "Recording " + s);
bos.write(s.getBytes());
// If the result of this test should be ignored, we still run the test.
if (ignoreResultsInDir || FileFilter.ignoreResult(s)) {
bos.write((" IGNORE_RESULT").getBytes());
}
bos.write('\n');
testCount++;
}
}
return testCount;
}
public static void updateTestStatus(String statusFile, String s) {
try {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(statusFile));
bos.write(s.getBytes());
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Cannot update file " + statusFile);
}
}
public static String readTestStatus(String statusFile) {
// read out the test name it stopped last time.
String status = null;
File testStatusFile = new File(statusFile);
if(testStatusFile.exists()) {
try {
BufferedReader inReader = new BufferedReader(
new FileReader(testStatusFile));
status = inReader.readLine();
inReader.close();
} catch (IOException e) {
Log.e(LOGTAG, "Error reading test status.", e);
}
}
return status;
}
public static String getTestUrl(String path) {
String url = null;
if (!path.startsWith(HTTP_TESTS_PREFIX)) {
url = "file://" + path;
} else {
ForwardService.getForwardService().startForwardService();
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;
}
public static boolean diffIgnoreSpaces(String file1, String file2) throws IOException {
BufferedReader br1 = new BufferedReader(new FileReader(file1));
BufferedReader br2 = new BufferedReader(new FileReader(file2));
boolean same = true;
Pattern trailingSpace = Pattern.compile("\\s+$");
while(true) {
String line1 = br1.readLine();
String line2 = br2.readLine();
if (line1 == null && line2 == null)
break;
if (line1 != null) {
line1 = trailingSpace.matcher(line1).replaceAll("");
} else {
line1 = "";
}
if (line2 != null) {
line2 = trailingSpace.matcher(line2).replaceAll("");
} else {
line2 = "";
}
if(!line1.equals(line2)) {
same = false;
break;
}
}
br1.close();
br2.close();
return same;
}
public static boolean isTestPageUrl(String url) {
int qmPostion = url.indexOf('?');
int slashPostion = url.lastIndexOf('/');
if (slashPostion < qmPostion) {
String fileName = url.substring(slashPostion + 1, qmPostion);
if ("index.html".equals(fileName)) {
return true;
}
}
return false;
}
public static String getLastSegmentInPath(String path) {
int endPos = path.lastIndexOf('/');
path = path.substring(0, endPos);
endPos = path.lastIndexOf('/');
return path.substring(endPos + 1);
}
public static void writeDrawTime(String fileName, String url, long[] times) {
StringBuffer lineBuffer = new StringBuffer();
// grab the last segment of path in url
lineBuffer.append(getLastSegmentInPath(url));
for (long time : times) {
lineBuffer.append('\t');
lineBuffer.append(time);
}
lineBuffer.append('\n');
String line = lineBuffer.toString();
Log.v(LOGTAG, "logging draw times: " + line);
try {
FileWriter fw = new FileWriter(fileName, true);
fw.write(line);
fw.close();
} catch (IOException ioe) {
Log.e(LOGTAG, "Failed to log draw times", ioe);
}
}
}

View File

@@ -1,33 +0,0 @@
/*
* 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 android.app.Application;
public class HTMLHostApp extends Application {
public HTMLHostApp() {
}
public void onCreate() {
}
public void onTerminate() {
}
}

View File

@@ -1,82 +0,0 @@
/*
* 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;
public interface LayoutTestController {
public void dumpAsText(boolean enablePixelTests);
public void dumpChildFramesAsText();
public void waitUntilDone();
public void notifyDone();
// Force a redraw of the page
public void display();
// Used with pixel dumps of content
public void testRepaint();
// If the page title changes, add the information to the output.
public void dumpTitleChanges();
public void dumpBackForwardList();
public void dumpChildFrameScrollPositions();
public void dumpEditingCallbacks();
// Show/Hide window for window.onBlur() testing
public void setWindowIsKey(boolean b);
// Mac function, used to disable events going to the window
public void setMainFrameIsFirstResponder(boolean b);
public void dumpSelectionRect();
// invalidate and draw one line at a time of the web view.
public void repaintSweepHorizontally();
// History testing functions
public void keepWebHistory();
public void clearBackForwardList();
// navigate after page load has finished
public void queueBackNavigation(int howfar);
public void queueForwardNavigation(int howfar);
// Reload when the page load has finished
public void queueReload();
// Execute the provided script in current context when page load has finished.
public void queueScript(String scriptToRunInCurrentContext);
// Load the provided URL into the provided frame
public void queueLoad(String Url, String frameTarget);
public void setAcceptsEditing(boolean b);
// For storage tests
public void dumpDatabaseCallbacks();
public void setCanOpenWindows();
// For Geolocation tests
public void setGeolocationPermission(boolean allow);
public void overridePreference(String key, boolean value);
// For XSSAuditor tests
public void setXSSAuditorEnabled(boolean flag);
// For Geolocation tests
public void setMockGeolocationPosition(double latitude, double longitude, double accuracy);
public void setMockGeolocationError(int code, String message);
// For DeviceOrientation tests
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma);
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright (C) 2008 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 android.os.Bundle;
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
import junit.framework.TestSuite;
/**
* Instrumentation Test Runner for all DumpRenderTree tests.
*
* Running all tests:
*
* adb shell am instrument \
* -w com.android.dumprendertree.LayoutTestsAutoRunner
*/
public class LayoutTestsAutoRunner extends InstrumentationTestRunner {
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
suite.addTestSuite(LayoutTestsAutoTest.class);
suite.addTestSuite(LoadTestsAutoTest.class);
return suite;
}
@Override
public ClassLoader getLoader() {
return LayoutTestsAutoRunner.class.getClassLoader();
}
@Override
public void onCreate(Bundle icicle) {
this.mTestPath = (String) icicle.get("path");
String timeout_str = (String) icicle.get("timeout");
if (timeout_str != null) {
try {
this.mTimeoutInMillis = Integer.parseInt(timeout_str);
} catch (Exception e) {
e.printStackTrace();
}
}
String r = icicle.getString("rebaseline");
this.mRebaseline = (r != null && r.toLowerCase().equals("true"));
mJsEngine = icicle.getString("jsengine");
mPageCyclerSuite = icicle.getString("suite");
mPageCyclerForwardHost = icicle.getString("forward");
mPageCyclerIteration = icicle.getString("iteration", "5");
super.onCreate(icicle);
}
String mPageCyclerSuite;
String mPageCyclerForwardHost;
String mPageCyclerIteration;
String mTestPath;
int mTimeoutInMillis = 0;
boolean mRebaseline;
String mJsEngine;
}

View File

@@ -1,494 +0,0 @@
/*
* Copyright (C) 2008 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 com.android.dumprendertree.TestShellActivity.DumpDataType;
import com.android.dumprendertree.forwarder.AdbUtils;
import com.android.dumprendertree.forwarder.ForwardService;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;
// TestRecorder creates four files ...
// - passing tests
// - failing tests
// - tests for which results are ignored
// - tests with no text results available
// TestRecorder does not have the ability to clear the results.
class MyTestRecorder {
private BufferedOutputStream mBufferedOutputPassedStream;
private BufferedOutputStream mBufferedOutputFailedStream;
private BufferedOutputStream mBufferedOutputIgnoreResultStream;
private BufferedOutputStream mBufferedOutputNoResultStream;
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) {
try {
mBufferedOutputFailedStream.write(layout_file.getBytes());
mBufferedOutputFailedStream.write('\n');
mBufferedOutputFailedStream.flush();
} catch(Exception e) {
e.printStackTrace();
}
}
public void ignoreResult(String layout_file) {
try {
mBufferedOutputIgnoreResultStream.write(layout_file.getBytes());
mBufferedOutputIgnoreResultStream.write('\n');
mBufferedOutputIgnoreResultStream.flush();
} catch(Exception e) {
e.printStackTrace();
}
}
public void noResult(String layout_file) {
try {
mBufferedOutputNoResultStream.write(layout_file.getBytes());
mBufferedOutputNoResultStream.write('\n');
mBufferedOutputNoResultStream.flush();
} catch(Exception e) {
e.printStackTrace();
}
}
public MyTestRecorder(boolean resume) {
try {
File externalDir = Environment.getExternalStorageDirectory();
File resultsPassedFile = new File(externalDir, "layout_tests_passed.txt");
File resultsFailedFile = new File(externalDir, "layout_tests_failed.txt");
File resultsIgnoreResultFile = new File(externalDir, "layout_tests_ignored.txt");
File noExpectedResultFile = new File(externalDir, "layout_tests_nontext.txt");
mBufferedOutputPassedStream =
new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume));
mBufferedOutputFailedStream =
new BufferedOutputStream(new FileOutputStream(resultsFailedFile, resume));
mBufferedOutputIgnoreResultStream =
new BufferedOutputStream(new FileOutputStream(resultsIgnoreResultFile, resume));
mBufferedOutputNoResultStream =
new BufferedOutputStream(new FileOutputStream(noExpectedResultFile, resume));
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
mBufferedOutputPassedStream.close();
mBufferedOutputFailedStream.close();
mBufferedOutputIgnoreResultStream.close();
mBufferedOutputNoResultStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> {
private static final String LOGTAG = "LayoutTests";
static final int DEFAULT_TIMEOUT_IN_MILLIS = 5000;
static final String EXTERNAL_DIR = Environment.getExternalStorageDirectory().toString();
static final String LAYOUT_TESTS_ROOT = EXTERNAL_DIR + "/webkit/layout_tests/";
static final String LAYOUT_TESTS_RESULT_DIR = EXTERNAL_DIR + "/webkit/layout_tests_results/";
static final String ANDROID_EXPECTED_RESULT_DIR = EXTERNAL_DIR + "/webkit/expected_results/";
static final String LAYOUT_TESTS_LIST_FILE = EXTERNAL_DIR + "/webkit/layout_tests_list.txt";
static final String TEST_STATUS_FILE = EXTERNAL_DIR + "/webkit/running_test.txt";
static final String LAYOUT_TESTS_RESULTS_REFERENCE_FILES[] = {
"results/layout_tests_passed.txt",
"results/layout_tests_failed.txt",
"results/layout_tests_nontext.txt",
"results/layout_tests_crashed.txt",
"run_layout_tests.py"
};
static final String LAYOUT_RESULTS_FAILED_RESULT_FILE = "results/layout_tests_failed.txt";
static final String LAYOUT_RESULTS_NONTEXT_RESULT_FILE = "results/layout_tests_nontext.txt";
static final String LAYOUT_RESULTS_CRASHED_RESULT_FILE = "results/layout_tests_crashed.txt";
static final String LAYOUT_TESTS_RUNNER = "run_layout_tests.py";
private MyTestRecorder mResultRecorder;
private Vector<String> mTestList;
// Whether we should ignore the result for the corresponding test. Ordered same as mTestList.
private Vector<Boolean> mTestListIgnoreResult;
private boolean mRebaselineResults;
// The JavaScript engine currently in use. This determines which set of Android-specific
// expected test results we use.
private String mJsEngine;
private String mTestPathPrefix;
private boolean mFinished;
private int mTestCount;
private int mResumeIndex;
public LayoutTestsAutoTest() {
super(TestShellActivity.class);
}
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)) {
String[] components = line.split(" ");
mTestList.add(components[0]);
mTestListIgnoreResult.add(components.length > 1 && components[1].equals("IGNORE_RESULT"));
}
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());
}
mTestCount = mTestList.size();
}
private void resumeTestList() {
// read out the test name it stoped last time.
try {
String line = FsUtils.readTestStatus(TEST_STATUS_FILE);
for (int i = 0; i < mTestList.size(); i++) {
if (mTestList.elementAt(i).equals(line)) {
mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size()));
mTestListIgnoreResult = new Vector<Boolean>(mTestListIgnoreResult.subList(i+1, mTestListIgnoreResult.size()));
mResumeIndex = i + 1;
break;
}
}
} 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 String getResultFile(String test) {
String shortName = test.substring(0, test.lastIndexOf('.'));
// Write actual results to result directory.
return shortName.replaceFirst(LAYOUT_TESTS_ROOT, LAYOUT_TESTS_RESULT_DIR) + "-result.txt";
}
// Gets the file which contains WebKit's expected results for this test.
private String getExpectedResultFile(String test) {
// The generic result is at <path>/<name>-expected.txt
// First try the Android-specific result at
// platform/android-<js-engine>/<path>/<name>-expected.txt
// then
// platform/android/<path>/<name>-expected.txt
int pos = test.lastIndexOf('.');
if (pos == -1)
return null;
String genericExpectedResult = test.substring(0, pos) + "-expected.txt";
String androidExpectedResultsDir = "platform/android-" + mJsEngine + "/";
String androidExpectedResult = genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT,
LAYOUT_TESTS_ROOT + androidExpectedResultsDir);
File f = new File(androidExpectedResult);
if (f.exists())
return androidExpectedResult;
androidExpectedResultsDir = "platform/android/";
androidExpectedResult = genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT,
LAYOUT_TESTS_ROOT + androidExpectedResultsDir);
f = new File(androidExpectedResult);
return f.exists() ? androidExpectedResult : genericExpectedResult;
}
// Gets the file which contains the actual results of running the test on
// Android, generated by a previous run which set a new baseline.
private String getAndroidExpectedResultFile(String expectedResultFile) {
return expectedResultFile.replaceFirst(LAYOUT_TESTS_ROOT, ANDROID_EXPECTED_RESULT_DIR);
}
// Wrap up
private void failedCase(String file) {
Log.w("Layout test: ", file + " failed");
mResultRecorder.failed(file);
}
private void passedCase(String file) {
Log.v("Layout test:", file + " passed");
mResultRecorder.passed(file);
}
private void ignoreResultCase(String file) {
Log.v("Layout test:", file + " ignore result");
mResultRecorder.ignoreResult(file);
}
private void noResultCase(String file) {
Log.v("Layout test:", file + " no expected result");
mResultRecorder.noResult(file);
}
private void processResult(String testFile, String actualResultFile, String expectedResultFile, boolean ignoreResult) {
Log.v(LOGTAG, " Processing result: " + testFile);
if (ignoreResult) {
ignoreResultCase(testFile);
return;
}
File actual = new File(actualResultFile);
File expected = new File(expectedResultFile);
if (actual.exists() && expected.exists()) {
try {
if (FsUtils.diffIgnoreSpaces(actualResultFile, expectedResultFile)) {
passedCase(testFile);
} else {
failedCase(testFile);
}
} catch (FileNotFoundException ex) {
Log.e(LOGTAG, "File not found : " + ex.getMessage());
} catch (IOException ex) {
Log.e(LOGTAG, "IO Error : " + ex.getMessage());
}
return;
}
if (!expected.exists()) {
noResultCase(testFile);
}
}
private void runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout, boolean ignoreResult, int testNumber) {
activity.setCallback(new TestShellCallback() {
public void finished() {
synchronized (LayoutTestsAutoTest.this) {
mFinished = true;
LayoutTestsAutoTest.this.notifyAll();
}
}
public void timedOut(String url) {
Log.v(LOGTAG, "layout timeout: " + url);
}
@Override
public void dumpResult(String webViewDump) {
}
});
String resultFile = getResultFile(test);
if (resultFile == null) {
// Simply ignore this test.
return;
}
if (mRebaselineResults) {
String expectedResultFile = getExpectedResultFile(test);
File f = new File(expectedResultFile);
if (f.exists()) {
return; // don't run test and don't overwrite default tests.
}
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, FsUtils.getTestUrl(test));
intent.putExtra(TestShellActivity.RESULT_FILE, resultFile);
intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout);
intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, mTestCount);
intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, testNumber);
intent.putExtra(TestShellActivity.STOP_ON_REF_ERROR, true);
activity.startActivity(intent);
// Wait until done.
synchronized (this) {
while(!mFinished){
try {
this.wait();
} catch (InterruptedException e) { }
}
}
if (!mRebaselineResults) {
String expectedResultFile = getExpectedResultFile(test);
File f = new File(expectedResultFile);
if (!f.exists()) {
expectedResultFile = getAndroidExpectedResultFile(expectedResultFile);
}
processResult(test, resultFile, expectedResultFile, ignoreResult);
}
}
// Invokes running of layout tests
// and waits till it has finished running.
public void executeLayoutTests(boolean resume) {
LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
// A convenient method to be called by another activity.
if (runner.mTestPath == null) {
Log.e(LOGTAG, "No test specified");
return;
}
this.mTestList = new Vector<String>();
this.mTestListIgnoreResult = new Vector<Boolean>();
// Read settings
mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getAbsolutePath();
mRebaselineResults = runner.mRebaseline;
// V8 is the default JavaScript engine.
mJsEngine = runner.mJsEngine == null ? "v8" : runner.mJsEngine;
int timeout = runner.mTimeoutInMillis;
if (timeout <= 0) {
timeout = DEFAULT_TIMEOUT_IN_MILLIS;
}
this.mResultRecorder = new MyTestRecorder(resume);
if (!resume)
clearTestStatus();
getTestList();
if (resume)
resumeTestList();
TestShellActivity activity = getActivity();
activity.setDefaultDumpDataType(DumpDataType.EXT_REPR);
// Run tests.
for (int i = 0; i < mTestList.size(); i++) {
String s = mTestList.elementAt(i);
boolean ignoreResult = mTestListIgnoreResult.elementAt(i);
FsUtils.updateTestStatus(TEST_STATUS_FILE, s);
// Run tests
// i is 0 based, but test count is 1 based so add 1 to i here.
runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis, ignoreResult,
i + 1 + mResumeIndex);
}
FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE");
ForwardService.getForwardService().stopForwardService();
activity.finish();
}
private String getTestPath() {
LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
String test_path = LAYOUT_TESTS_ROOT;
if (runner.mTestPath != null) {
test_path += runner.mTestPath;
}
test_path = new File(test_path).getAbsolutePath();
Log.v("LayoutTestsAutoTest", " Test path : " + test_path);
return test_path;
}
public void generateTestList() {
try {
File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
FsUtils.writeLayoutTestListRecursively(bos, getTestPath(), false); // Don't ignore results
bos.flush();
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Error when creating test list: " + e.getMessage());
}
}
// Running all the layout tests at once sometimes
// causes the dumprendertree to run out of memory.
// So, additional tests are added to run the tests
// in chunks.
public void startLayoutTests() {
try {
File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
if (!tests_list.exists())
generateTestList();
} catch (Exception e) {
e.printStackTrace();
}
executeLayoutTests(false);
}
public void resumeLayoutTests() {
executeLayoutTests(true);
}
public void copyResultsAndRunnerAssetsToCache() {
try {
Context targetContext = getInstrumentation().getTargetContext();
File cacheDir = targetContext.getCacheDir();
for( int i=0; i< LAYOUT_TESTS_RESULTS_REFERENCE_FILES.length; i++) {
InputStream in = targetContext.getAssets().open(
LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i]);
OutputStream out = new FileOutputStream(new File(cacheDir,
LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i]));
byte[] buf = new byte[2048];
int len;
while ((len = in.read(buf)) >= 0 ) {
out.write(buf, 0, len);
}
out.close();
in.close();
}
}catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,298 +0,0 @@
/*
* Copyright (C) 2008 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 com.android.dumprendertree.forwarder.AdbUtils;
import com.android.dumprendertree.forwarder.ForwardServer;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Debug;
import android.os.Environment;
import android.os.Process;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> {
private final static String LOGTAG = "LoadTest";
private final static String LOAD_TEST_RESULT =
Environment.getExternalStorageDirectory() + "/load_test_result.txt";
private final static int MAX_GC_WAIT_SEC = 10;
private final static int LOCAL_PORT = 17171;
private boolean mFinished;
static final String LOAD_TEST_RUNNER_FILES[] = {
"run_page_cycler.py"
};
private ForwardServer mForwardServer;
public LoadTestsAutoTest() {
super(TestShellActivity.class);
}
// This function writes the result of the layout test to
// Am status so that it can be picked up from a script.
public void passOrFailCallback(String file, boolean result) {
Instrumentation inst = getInstrumentation();
Bundle bundle = new Bundle();
bundle.putBoolean(file, result);
inst.sendStatus(0, bundle);
}
private String setUpForwarding(String forwardInfo, String suite, String iteration) throws IOException {
// read forwarding information first
Pattern forwardPattern = Pattern.compile("(.*):(\\d+)/(.*)/");
Matcher matcher = forwardPattern.matcher(forwardInfo);
if (!matcher.matches()) {
throw new RuntimeException("Invalid forward information");
}
String host = matcher.group(1);
int port = Integer.parseInt(matcher.group(2));
mForwardServer = new ForwardServer(LOCAL_PORT, AdbUtils.resolve(host), port);
mForwardServer.start();
return String.format("http://127.0.0.1:%d/%s/%s/start.html?auto=1&iterations=%s",
LOCAL_PORT, matcher.group(3), suite, iteration);
}
// Invokes running of layout tests
// and waits till it has finished running.
public void runPageCyclerTest() throws IOException {
LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
if (runner.mPageCyclerSuite != null) {
// start forwarder to use page cycler suites hosted on external web server
if (runner.mPageCyclerForwardHost == null) {
throw new RuntimeException("no forwarder information provided");
}
runner.mTestPath = setUpForwarding(runner.mPageCyclerForwardHost,
runner.mPageCyclerSuite, runner.mPageCyclerIteration);
Log.d(LOGTAG, "using path: " + runner.mTestPath);
}
if (runner.mTestPath == null) {
throw new RuntimeException("No test specified");
}
final TestShellActivity activity = (TestShellActivity) getActivity();
Log.v(LOGTAG, "About to run tests, calling gc first...");
freeMem();
// Run tests
runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis);
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
activity.clearCache();
}
});
if (mForwardServer != null) {
mForwardServer.stop();
mForwardServer = null;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
dumpMemoryInfo();
// Kill activity
activity.finish();
}
private void freeMem() {
Log.v(LOGTAG, "freeMem: calling gc...");
final CountDownLatch latch = new CountDownLatch(1);
@SuppressWarnings("unused")
Object dummy = new Object() {
// this object instance is used to track gc
@Override
protected void finalize() throws Throwable {
latch.countDown();
super.finalize();
}
};
dummy = null;
System.gc();
try {
if (!latch.await(MAX_GC_WAIT_SEC, TimeUnit.SECONDS)) {
Log.w(LOGTAG, "gc did not happen in 10s");
}
} catch (InterruptedException e) {
//ignore
}
}
private void printRow(PrintStream ps, String format, Object...objs) {
ps.println(String.format(format, objs));
}
private void dumpMemoryInfo() {
try {
freeMem();
Log.v(LOGTAG, "Dumping memory information.");
FileOutputStream out = new FileOutputStream(LOAD_TEST_RESULT, true);
PrintStream ps = new PrintStream(out);
ps.print("\n\n\n");
ps.println("** MEMINFO in pid " + Process.myPid()
+ " [com.android.dumprendertree] **");
String formatString = "%17s %8s %8s %8s %8s";
long nativeMax = Debug.getNativeHeapSize() / 1024;
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.totalMemory() / 1024;
long dalvikFree = runtime.freeMemory() / 1024;
long dalvikAllocated = dalvikMax - dalvikFree;
Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memInfo);
final int nativeShared = memInfo.nativeSharedDirty;
final int dalvikShared = memInfo.dalvikSharedDirty;
final int otherShared = memInfo.otherSharedDirty;
final int nativePrivate = memInfo.nativePrivateDirty;
final int dalvikPrivate = memInfo.dalvikPrivateDirty;
final int otherPrivate = memInfo.otherPrivateDirty;
printRow(ps, formatString, "", "native", "dalvik", "other", "total");
printRow(ps, formatString, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax);
printRow(ps, formatString, "allocated:", nativeAllocated, dalvikAllocated, "N/A",
nativeAllocated + dalvikAllocated);
printRow(ps, formatString, "free:", nativeFree, dalvikFree, "N/A",
nativeFree + dalvikFree);
printRow(ps, formatString, "(Pss):", memInfo.nativePss, memInfo.dalvikPss,
memInfo.otherPss, memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss);
printRow(ps, formatString, "(shared dirty):", nativeShared, dalvikShared, otherShared,
nativeShared + dalvikShared + otherShared);
printRow(ps, formatString, "(priv dirty):", nativePrivate, dalvikPrivate, otherPrivate,
nativePrivate + dalvikPrivate + otherPrivate);
ps.print("\n\n\n");
ps.flush();
ps.close();
out.flush();
out.close();
} catch (IOException e) {
Log.e(LOGTAG, e.getMessage());
}
}
// A convenient method to be called by another activity.
private void runTestAndWaitUntilDone(TestShellActivity activity, String url, int timeout) {
activity.setCallback(new TestShellCallback() {
@Override
public void finished() {
synchronized (LoadTestsAutoTest.this) {
mFinished = true;
LoadTestsAutoTest.this.notifyAll();
}
}
@Override
public void timedOut(String url) {
}
@Override
public void dumpResult(String webViewDump) {
String lines[] = webViewDump.split("\\r?\\n");
for (String line : lines) {
line = line.trim();
// parse for a line like this:
// totals: 9620.00 11947.00 10099.75 380.38
// and return the 3rd number, which is mean
if (line.startsWith("totals:")) {
line = line.substring(7).trim(); // strip "totals:"
String[] numbers = line.split("\\s+");
if (numbers.length == 4) {
Bundle b = new Bundle();
b.putString("mean", numbers[2]);
getInstrumentation().sendStatus(Activity.RESULT_FIRST_USER, b);
}
}
}
}
});
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, url);
intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout);
intent.putExtra(TestShellActivity.RESULT_FILE, LOAD_TEST_RESULT);
activity.startActivity(intent);
// Wait until done.
synchronized (this) {
while(!mFinished) {
try {
this.wait();
} catch (InterruptedException e) { }
}
}
}
public void copyRunnerAssetsToCache() {
try {
Context targetContext = getInstrumentation().getTargetContext();
File cacheDir = targetContext.getCacheDir();
for( int i=0; i< LOAD_TEST_RUNNER_FILES.length; i++) {
InputStream in = targetContext.getAssets().open(
LOAD_TEST_RUNNER_FILES[i]);
OutputStream out = new FileOutputStream(
new File(cacheDir, LOAD_TEST_RUNNER_FILES[i]));
byte[] buf = new byte[2048];
int len;
while ((len = in.read(buf)) >= 0 ) {
out.write(buf, 0, len);
}
out.close();
in.close();
}
}catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,90 +0,0 @@
/*
* 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 android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class Menu extends FileList {
private static final int MENU_START = 0x01;
private static String LOGTAG = "MenuActivity";
static final String LAYOUT_TESTS_LIST_FILE =
Environment.getExternalStorageDirectory() + "/android/layout_tests_list.txt";
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
}
boolean fileFilter(File f) {
if (f.getName().startsWith("."))
return false;
if (f.getName().equalsIgnoreCase("resources"))
return false;
if (f.isDirectory())
return true;
if (f.getPath().toLowerCase().endsWith("ml"))
return true;
return false;
}
void processFile(String filename, boolean selection) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(this, TestShellActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(TestShellActivity.TEST_URL, "file://" + filename);
intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, 1);
intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, 1);
startActivity(intent);
}
@Override
void processDirectory(String path, boolean selection) {
int testCount = generateTestList(path);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(this, TestShellActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(TestShellActivity.UI_AUTO_TEST, LAYOUT_TESTS_LIST_FILE);
intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, testCount);
// TestShellActivity will process this intent once and increment the test index
// before running the first test, so pass 0 here to allow for that.
intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, 0);
startActivity(intent);
}
private int generateTestList(String path) {
int testCount = 0;
try {
File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
testCount = FsUtils.writeLayoutTestListRecursively(
bos, path, false); // Don't ignore results
bos.flush();
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Error when creating test list: " + e.getMessage());
}
return testCount;
}
}

View File

@@ -1,308 +0,0 @@
/*
* Copyright (C) 2009 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 android.app.Activity;
import android.app.ActivityThread;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.ViewGroup;
import android.webkit.HttpAuthHandler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
public class ReliabilityTestActivity extends Activity {
public static final String TEST_URL_ACTION = "com.andrdoid.dumprendertree.TestUrlAction";
public static final String PARAM_URL = "URL";
public static final String PARAM_TIMEOUT = "Timeout";
public static final int RESULT_TIMEOUT = 0xDEAD;
public static final int MSG_TIMEOUT = 0xC001;
public static final int MSG_NAVIGATE = 0xC002;
public static final String MSG_NAV_URL = "url";
public static final String MSG_NAV_LOGTIME = "logtime";
private static final String LOGTAG = "ReliabilityTestActivity";
private WebView webView;
private SimpleWebViewClient webViewClient;
private SimpleChromeClient chromeClient;
private Handler handler;
private boolean timeoutFlag;
private boolean logTime;
private boolean pageDone;
private Object pageDoneLock;
private int pageStartCount;
private int manualDelay;
private long startTime;
private long pageLoadTime;
private PageDoneRunner pageDoneRunner = new PageDoneRunner();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(LOGTAG, "onCreate, inst=" + Integer.toHexString(hashCode()));
LinearLayout contentView = new LinearLayout(this);
contentView.setOrientation(LinearLayout.VERTICAL);
setContentView(contentView);
setTitle("Idle");
webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(false);
webView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
webViewClient = new SimpleWebViewClient();
chromeClient = new SimpleChromeClient();
webView.setWebViewClient(webViewClient);
webView.setWebChromeClient(chromeClient);
contentView.addView(webView, new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0f));
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TIMEOUT:
handleTimeout();
return;
case MSG_NAVIGATE:
manualDelay = msg.arg2;
navigate(msg.getData().getString(MSG_NAV_URL), msg.arg1);
logTime = msg.getData().getBoolean(MSG_NAV_LOGTIME);
return;
}
}
};
pageDoneLock = new Object();
}
public void reset() {
synchronized (pageDoneLock) {
pageDone = false;
}
timeoutFlag = false;
pageStartCount = 0;
chromeClient.resetJsTimeout();
}
private void navigate(String url, int timeout) {
if(url == null) {
Log.v(LOGTAG, "URL is null, cancelling...");
finish();
}
webView.stopLoading();
if(logTime) {
webView.clearCache(true);
}
startTime = System.currentTimeMillis();
Log.v(LOGTAG, "Navigating to URL: " + url);
webView.loadUrl(url);
if(timeout != 0) {
//set a timer with specified timeout (in ms)
handler.sendMessageDelayed(handler.obtainMessage(MSG_TIMEOUT),
timeout);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.v(LOGTAG, "onDestroy, inst=" + Integer.toHexString(hashCode()));
webView.clearCache(true);
webView.destroy();
}
private boolean isPageDone() {
synchronized (pageDoneLock) {
return pageDone;
}
}
private void setPageDone(boolean pageDone) {
synchronized (pageDoneLock) {
this.pageDone = pageDone;
pageDoneLock.notifyAll();
}
}
private void handleTimeout() {
int progress = webView.getProgress();
webView.stopLoading();
Log.v(LOGTAG, "Page timeout triggered, progress = " + progress);
timeoutFlag = true;
handler.postDelayed(pageDoneRunner, manualDelay);
}
public boolean waitUntilDone() {
validateNotAppThread();
synchronized (pageDoneLock) {
while(!isPageDone()) {
try {
pageDoneLock.wait();
} catch (InterruptedException ie) {
//no-op
}
}
}
return timeoutFlag;
}
public Handler getHandler() {
return handler;
}
private final void validateNotAppThread() {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException(
"This method can not be called from the main application thread");
}
}
public long getPageLoadTime() {
return pageLoadTime;
}
class SimpleWebViewClient extends WebViewClient {
@Override
public void onReceivedError(WebView view, int errorCode, String description,
String failingUrl) {
Log.v(LOGTAG, "Received WebCore error: code=" + errorCode
+ ", description=" + description
+ ", url=" + failingUrl);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//ignore certificate error
Log.v(LOGTAG, "Received SSL error: " + error.toString());
handler.proceed();
}
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
String realm) {
// cancel http auth request
handler.cancel();
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
pageStartCount++;
Log.v(LOGTAG, "onPageStarted: " + url);
}
@Override
public void onPageFinished(WebView view, String url) {
Log.v(LOGTAG, "onPageFinished: " + url);
// let handleTimeout take care of finishing the page
if(!timeoutFlag)
handler.postDelayed(new WebViewStatusChecker(), 500);
}
}
class SimpleChromeClient extends WebChromeClient {
private int timeoutCounter = 0;
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
result.confirm();
return true;
}
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
result.confirm();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
JsPromptResult result) {
result.confirm();
return true;
}
@Override
public boolean onJsTimeout() {
timeoutCounter++;
Log.v(LOGTAG, "JavaScript timeout, count=" + timeoutCounter);
return timeoutCounter > 2;
}
public void resetJsTimeout() {
timeoutCounter = 0;
}
@Override
public void onReceivedTitle(WebView view, String title) {
ReliabilityTestActivity.this.setTitle(title);
}
}
class WebViewStatusChecker implements Runnable {
private int initialStartCount;
public WebViewStatusChecker() {
initialStartCount = pageStartCount;
}
public void run() {
if (initialStartCount == pageStartCount && !isPageDone()) {
handler.removeMessages(MSG_TIMEOUT);
webView.stopLoading();
handler.postDelayed(pageDoneRunner, manualDelay);
}
}
}
class PageDoneRunner implements Runnable {
public void run() {
Log.v(LOGTAG, "Finishing URL: " + webView.getUrl());
pageLoadTime = System.currentTimeMillis() - startTime;
setPageDone(true);
}
}
}

View File

@@ -1,946 +0,0 @@
/*
* 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 com.android.dumprendertree.forwarder.ForwardService;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.ConsoleMessage;
import android.webkit.CookieManager;
import android.webkit.GeolocationPermissions;
import android.webkit.HttpAuthHandler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebSettingsClassic;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClassic;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
import java.io.BufferedReader;
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.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
public class TestShellActivity extends Activity implements LayoutTestController {
static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP}
// String constants for use with layoutTestController.overridePreferences
private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
"WebKitOfflineWebApplicationCacheEnabled";
private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey";
public class AsyncHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_TIMEOUT) {
mTimedOut = true;
mWebView.stopLoading();
if (mCallback != null)
mCallback.timedOut(mWebView.getUrl());
if (!mRequestedWebKitData) {
requestWebKitData();
} else {
// if timed out and webkit data has been dumped before
// finish directly
finished();
}
return;
} else if (msg.what == MSG_WEBKIT_DATA) {
Log.v(LOGTAG, "Received WebView dump data");
mHandler.removeMessages(MSG_DUMP_TIMEOUT);
TestShellActivity.this.dump(mTimedOut, (String)msg.obj);
return;
} else if (msg.what == MSG_DUMP_TIMEOUT) {
throw new RuntimeException("WebView dump timeout, is it pegged?");
}
super.handleMessage(msg);
}
}
public void requestWebKitData() {
setDumpTimeout(DUMP_TIMEOUT_MS);
Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
if (mRequestedWebKitData)
throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
mRequestedWebKitData = true;
Log.v(LOGTAG, "message sent to WebView to dump text.");
switch (mDumpDataType) {
case DUMP_AS_TEXT:
callback.arg1 = mDumpTopFrameAsText ? 1 : 0;
callback.arg2 = mDumpChildFramesAsText ? 1 : 0;
mWebViewClassic.documentAsText(callback);
break;
case EXT_REPR:
mWebViewClassic.externalRepresentation(callback);
break;
default:
finished();
break;
}
}
private void setDumpTimeout(long timeout) {
Log.v(LOGTAG, "setting dump timeout at " + timeout);
Message msg = mHandler.obtainMessage(MSG_DUMP_TIMEOUT);
mHandler.sendMessageDelayed(msg, timeout);
}
public void clearCache() {
mWebView.freeMemory();
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_PROGRESS);
LinearLayout contentView = new LinearLayout(this);
contentView.setOrientation(LinearLayout.VERTICAL);
setContentView(contentView);
CookieManager.setAcceptFileSchemeCookies(true);
mWebView = new WebView(this);
mWebViewClassic = WebViewClassic.fromWebView(mWebView);
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.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0.0f));
mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
// Expose window.gc function to JavaScript. JSC build exposes
// this function by default, but V8 requires the flag to turn it on.
// WebView::setJsFlags is noop in JSC build.
mWebViewClassic.setJsFlags("--expose_gc");
mHandler = new AsyncHandler();
Intent intent = getIntent();
if (intent != null) {
executeIntent(intent);
}
// This is asynchronous, but it gets processed by WebCore before it starts loading pages.
mWebViewClassic.setUseMockDeviceOrientation();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
executeIntent(intent);
}
private void executeIntent(Intent intent) {
resetTestStatus();
if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
return;
}
mTotalTestCount = intent.getIntExtra(TOTAL_TEST_COUNT, mTotalTestCount);
mCurrentTestNumber = intent.getIntExtra(CURRENT_TEST_NUMBER, mCurrentTestNumber);
mTestUrl = intent.getStringExtra(TEST_URL);
if (mTestUrl == null) {
mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST);
if(mUiAutoTestPath != null) {
beginUiAutoTest();
}
return;
}
mResultFile = intent.getStringExtra(RESULT_FILE);
mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0);
mStopOnRefError = intent.getBooleanExtra(STOP_ON_REF_ERROR, false);
setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount);
float ratio = (float)mCurrentTestNumber / mTotalTestCount;
int progress = (int)(ratio * Window.PROGRESS_END);
getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress);
Log.v(LOGTAG, " Loading " + mTestUrl);
if (mTestUrl.contains("/dumpAsText/")) {
dumpAsText(false);
}
mWebView.loadUrl(mTestUrl);
if (mTimeoutInMillis > 0) {
// Create a timeout timer
Message m = mHandler.obtainMessage(MSG_TIMEOUT);
mHandler.sendMessageDelayed(m, mTimeoutInMillis);
}
}
private void beginUiAutoTest() {
try {
mTestListReader = new BufferedReader(
new FileReader(mUiAutoTestPath));
} catch (IOException ioe) {
Log.e(LOGTAG, "Failed to open test list for read.", ioe);
finishUiAutoTest();
return;
}
moveToNextTest();
}
private void finishUiAutoTest() {
try {
if(mTestListReader != null)
mTestListReader.close();
} catch (IOException ioe) {
Log.w(LOGTAG, "Failed to close test list file.", ioe);
}
ForwardService.getForwardService().stopForwardService();
finished();
}
private void moveToNextTest() {
String url = null;
try {
url = mTestListReader.readLine();
} catch (IOException ioe) {
Log.e(LOGTAG, "Failed to read next test.", ioe);
finishUiAutoTest();
return;
}
if (url == null) {
mUiAutoTestPath = null;
finishUiAutoTest();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("All tests finished. Exit?")
.setCancelable(false)
.setPositiveButton("Yes", new OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
TestShellActivity.this.finish();
}
})
.setNegativeButton("No", new OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.create().show();
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(url));
intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, ++mCurrentTestNumber);
intent.putExtra(TIMEOUT_IN_MILLIS, 10000);
executeIntent(intent);
}
@Override
protected void onStop() {
super.onStop();
mWebView.stopLoading();
}
@Override
protected void onDestroy() {
super.onDestroy();
mWebView.destroy();
mWebView = null;
mWebViewClassic = null;
}
@Override
public void onLowMemory() {
super.onLowMemory();
Log.e(LOGTAG, "Low memory, clearing caches");
mWebView.freeMemory();
}
// Dump the page
public void dump(boolean timeout, String webkitData) {
mDumpWebKitData = true;
if (mResultFile == null || mResultFile.length() == 0) {
finished();
return;
}
if (mCallback != null) {
mCallback.dumpResult(webkitData);
}
try {
File parentDir = new File(mResultFile).getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
FileOutputStream os = new FileOutputStream(mResultFile);
if (timeout) {
Log.w("Layout test: Timeout", mResultFile);
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;
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();
os.close();
} catch (IOException ex) {
Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());
}
finished();
}
public void setCallback(TestShellCallback callback) {
mCallback = callback;
}
public boolean finished() {
if (canMoveToNextTest()) {
mHandler.removeMessages(MSG_TIMEOUT);
if (mUiAutoTestPath != null) {
//don't really finish here
moveToNextTest();
} else {
if (mCallback != null) {
mCallback.finished();
}
}
return true;
}
return false;
}
public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) {
mDefaultDumpDataType = defaultDumpDataType;
}
// .......................................
// LayoutTestController Functions
@Override
public void dumpAsText(boolean enablePixelTests) {
// Added after webkit update to r63859. See trac.webkit.org/changeset/63730.
if (enablePixelTests) {
Log.v(LOGTAG, "dumpAsText(enablePixelTests == true) not implemented on Android!");
}
mDumpDataType = DumpDataType.DUMP_AS_TEXT;
mDumpTopFrameAsText = true;
if (mWebView != null) {
String url = mWebView.getUrl();
Log.v(LOGTAG, "dumpAsText called: "+url);
}
}
@Override
public void dumpChildFramesAsText() {
mDumpDataType = DumpDataType.DUMP_AS_TEXT;
mDumpChildFramesAsText = true;
if (mWebView != null) {
String url = mWebView.getUrl();
Log.v(LOGTAG, "dumpChildFramesAsText called: "+url);
}
}
@Override
public void waitUntilDone() {
mWaitUntilDone = true;
String url = mWebView.getUrl();
Log.v(LOGTAG, "waitUntilDone called: " + url);
}
@Override
public void notifyDone() {
String url = mWebView.getUrl();
Log.v(LOGTAG, "notifyDone called: " + url);
if (mWaitUntilDone) {
mWaitUntilDone = false;
if (!mRequestedWebKitData && !mTimedOut && !finished()) {
requestWebKitData();
}
}
}
@Override
public void display() {
mWebView.invalidate();
}
@Override
public void clearBackForwardList() {
mWebView.clearHistory();
}
@Override
public void dumpBackForwardList() {
//printf("\n============== Back Forward List ==============\n");
// mWebHistory
//printf("===============================================\n");
}
@Override
public void dumpChildFrameScrollPositions() {
// TODO Auto-generated method stub
}
@Override
public void dumpEditingCallbacks() {
// TODO Auto-generated method stub
}
@Override
public void dumpSelectionRect() {
// TODO Auto-generated method stub
}
@Override
public void dumpTitleChanges() {
if (!mDumpTitleChanges) {
mTitleChanges = new StringBuffer();
}
mDumpTitleChanges = true;
}
@Override
public void keepWebHistory() {
if (!mKeepWebHistory) {
mWebHistory = new Vector();
}
mKeepWebHistory = true;
}
@Override
public void queueBackNavigation(int howfar) {
// TODO Auto-generated method stub
}
@Override
public void queueForwardNavigation(int howfar) {
// TODO Auto-generated method stub
}
@Override
public void queueLoad(String Url, String frameTarget) {
// TODO Auto-generated method stub
}
@Override
public void queueReload() {
mWebView.reload();
}
@Override
public void queueScript(String scriptToRunInCurrentContext) {
mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
}
@Override
public void repaintSweepHorizontally() {
// TODO Auto-generated method stub
}
@Override
public void setAcceptsEditing(boolean b) {
// TODO Auto-generated method stub
}
@Override
public void setMainFrameIsFirstResponder(boolean b) {
// TODO Auto-generated method stub
}
@Override
public void setWindowIsKey(boolean b) {
// This is meant to show/hide the window. The best I can find
// is setEnabled()
mWebView.setEnabled(b);
}
@Override
public void testRepaint() {
mWebView.invalidate();
}
@Override
public void dumpDatabaseCallbacks() {
Log.v(LOGTAG, "dumpDatabaseCallbacks called.");
mDumpDatabaseCallbacks = true;
}
@Override
public void setCanOpenWindows() {
Log.v(LOGTAG, "setCanOpenWindows called.");
mCanOpenWindows = true;
}
@Override
public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
WebViewClassic.fromWebView(mWebView).setMockGeolocationPosition(latitude, longitude,
accuracy);
}
@Override
public void setMockGeolocationError(int code, String message) {
WebViewClassic.fromWebView(mWebView).setMockGeolocationError(code, message);
}
@Override
public void setGeolocationPermission(boolean allow) {
Log.v(LOGTAG, "setGeolocationPermission() allow=" + allow);
WebViewClassic.fromWebView(mWebView).setMockGeolocationPermission(allow);
}
@Override
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
WebViewClassic.fromWebView(mWebView).setMockDeviceOrientation(canProvideAlpha, alpha,
canProvideBeta, beta, canProvideGamma, gamma);
}
@Override
public void overridePreference(String key, boolean value) {
// TODO: We should look up the correct WebView for the frame which
// called the layoutTestController method. Currently, we just use the
// WebView for the main frame. EventSender suffers from the same
// problem.
if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
mWebViewClassic.getSettings().setAppCacheEnabled(value);
} else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
// Cache the maximum possible number of pages.
mWebViewClassic.getSettings().setPageCacheCapacity(Integer.MAX_VALUE);
} else {
Log.w(LOGTAG, "LayoutTestController.overridePreference(): " +
"Unsupported preference '" + key + "'");
}
}
@Override
public void setXSSAuditorEnabled (boolean flag) {
mWebViewClassic.getSettings().setXSSAuditorEnabled(flag);
}
private final WebViewClient mViewClient = new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
Log.v(LOGTAG, "onPageFinished, url=" + url);
mPageFinished = true;
// Calling finished() will check if we've met all the conditions for completing
// this test and move to the next one if we are ready. Otherwise we ask WebCore to
// dump the page.
if (finished()) {
return;
}
if (!mWaitUntilDone && !mRequestedWebKitData && !mTimedOut) {
requestWebKitData();
} else {
if (mWaitUntilDone) {
Log.v(LOGTAG, "page finished loading but waiting for notifyDone to be called: " + url);
}
if (mRequestedWebKitData) {
Log.v(LOGTAG, "page finished loading but webkit data has already been requested: " + url);
}
if (mTimedOut) {
Log.v(LOGTAG, "page finished loading but already timed out: " + url);
}
}
super.onPageFinished(view, url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.v(LOGTAG, "onPageStarted, url=" + url);
mPageFinished = false;
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) {
if (handler.useHttpAuthUsernamePassword() && view != null) {
String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
if (credentials != null && credentials.length == 2) {
handler.proceed(credentials[0], credentials[1]);
return;
}
}
handler.cancel();
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
handler.proceed();
}
};
private final WebChromeClient mChromeClient = new WebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount + ": "+ 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;
}
@Override
public boolean onJsTimeout() {
Log.v(LOGTAG, "JavaScript timeout");
return false;
}
@Override
public void onExceededDatabaseQuota(String url_str,
String databaseIdentifier, long currentQuota,
long estimatedSize, 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 onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
throw new RuntimeException(
"The WebCore mock used by DRT should bypass the usual permissions flow.");
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
String msg = "CONSOLE MESSAGE: line " + consoleMessage.lineNumber() + ": "
+ consoleMessage.message() + "\n";
if (mConsoleMessages == null) {
mConsoleMessages = new StringBuffer();
}
mConsoleMessages.append(msg);
Log.v(LOGTAG, "LOG: " + msg);
// the rationale here is that if there's an error of either type, and the test was
// waiting for "notifyDone" signal to finish, then there's no point in waiting
// anymore because the JS execution is already terminated at this point and a
// "notifyDone" will never come out so it's just wasting time till timeout kicks in
if ((msg.contains("Uncaught ReferenceError:") || msg.contains("Uncaught TypeError:"))
&& mWaitUntilDone && mStopOnRefError) {
Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError.");
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
notifyDone();
}
}, 500);
}
return true;
}
@Override
public boolean onCreateWindow(WebView view, boolean dialog,
boolean userGesture, Message resultMsg) {
if (!mCanOpenWindows) {
// We can't open windows, so just send null back.
WebView.WebViewTransport transport =
(WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(null);
resultMsg.sendToTarget();
return true;
}
// We never display the new window, just create the view and
// allow it's content to execute and be recorded by the test
// runner.
HashMap<String, Object> jsIfaces = new HashMap<String, Object>();
jsIfaces.put("layoutTestController", mCallbackProxy);
jsIfaces.put("eventSender", mCallbackProxy);
WebView newWindowView = new NewWindowWebView(TestShellActivity.this, jsIfaces);
setupWebViewForLayoutTests(newWindowView, mCallbackProxy);
WebView.WebViewTransport transport =
(WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(newWindowView);
resultMsg.sendToTarget();
return true;
}
@Override
public void onCloseWindow(WebView view) {
view.destroy();
}
};
private static class NewWindowWebView extends WebView {
public NewWindowWebView(Context context, Map<String, Object> jsIfaces) {
super(context, null, 0, jsIfaces, false);
}
}
private void resetTestStatus() {
mWaitUntilDone = false;
mDumpDataType = mDefaultDumpDataType;
mDumpTopFrameAsText = false;
mDumpChildFramesAsText = false;
mTimedOut = false;
mDumpTitleChanges = false;
mRequestedWebKitData = false;
mDumpDatabaseCallbacks = false;
mCanOpenWindows = false;
mEventSender.resetMouse();
mEventSender.clearTouchPoints();
mEventSender.clearTouchMetaState();
mPageFinished = false;
mDumpWebKitData = false;
setDefaultWebSettings(mWebView);
CookieManager.getInstance().removeAllCookie();
mWebViewClassic.setUseMockGeolocation();
}
private boolean canMoveToNextTest() {
return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut;
}
private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
if (webview == null) {
return;
}
setDefaultWebSettings(webview);
webview.setWebChromeClient(mChromeClient);
webview.setWebViewClient(mViewClient);
// Setting a touch interval of -1 effectively disables the optimisation in WebView
// that stops repeated touch events flooding WebCore. The Event Sender only sends a
// single event rather than a stream of events (like what would generally happen in
// a real use of touch events in a WebView) and so if the WebView drops the event,
// the test will fail as the test expects one callback for every touch it synthesizes.
WebViewClassic.fromWebView(webview).setTouchInterval(-1);
}
public void setDefaultWebSettings(WebView webview) {
WebSettingsClassic settings = WebViewClassic.fromWebView(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);
settings.setWorkersEnabled(false);
settings.setXSSAuditorEnabled(false);
settings.setPageCacheCapacity(0);
settings.setProperty("use_minimal_memory", "false");
settings.setAllowUniversalAccessFromFileURLs(true);
settings.setAllowFileAccessFromFileURLs(true);
}
private WebViewClassic mWebViewClassic;
private WebView mWebView;
private WebViewEventSender mEventSender;
private AsyncHandler mHandler;
private TestShellCallback mCallback;
private CallbackProxy mCallbackProxy;
private String mTestUrl;
private String mResultFile;
private int mTimeoutInMillis;
private String mUiAutoTestPath;
private BufferedReader mTestListReader;
private int mTotalTestCount;
private int mCurrentTestNumber;
private boolean mStopOnRefError;
// States
private boolean mTimedOut;
private boolean mRequestedWebKitData;
private boolean mFinishedRunning;
// Layout test controller variables.
private DumpDataType mDumpDataType;
private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR;
private boolean mDumpTopFrameAsText;
private boolean mDumpChildFramesAsText;
private boolean mWaitUntilDone;
private boolean mDumpTitleChanges;
private StringBuffer mTitleChanges;
private StringBuffer mDialogStrings;
private boolean mKeepWebHistory;
private Vector mWebHistory;
private boolean mDumpDatabaseCallbacks;
private StringBuffer mDatabaseCallbackStrings;
private StringBuffer mConsoleMessages;
private boolean mCanOpenWindows;
private boolean mPageFinished = false;
private boolean mDumpWebKitData = false;
static final String TIMEOUT_STR = "**Test timeout";
static final long DUMP_TIMEOUT_MS = 100000; // 100s timeout for dumping webview content
static final int MSG_TIMEOUT = 0;
static final int MSG_WEBKIT_DATA = 1;
static final int MSG_DUMP_TIMEOUT = 2;
static final String LOGTAG="TestShell";
static final String TEST_URL = "TestUrl";
static final String RESULT_FILE = "ResultFile";
static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
static final String UI_AUTO_TEST = "UiAutoTest";
static final String GET_DRAW_TIME = "GetDrawTime";
static final String SAVE_IMAGE = "SaveImage";
static final String TOTAL_TEST_COUNT = "TestCount";
static final String CURRENT_TEST_NUMBER = "TestNumber";
static final String STOP_ON_REF_ERROR = "StopOnReferenceError";
static final int DRAW_RUNS = 5;
static final String DRAW_TIME_LOG = Environment.getExternalStorageDirectory() +
"/android/page_draw_time.txt";
}

View File

@@ -1,23 +0,0 @@
/*
* 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;
public interface TestShellCallback {
public void finished();
public void dumpResult(String webViewDump);
public void timedOut(String url);
}

View File

@@ -1,413 +0,0 @@
/*
* 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 android.os.SystemClock;
import android.util.*;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.webkit.WebView;
import java.util.Arrays;
import java.util.Vector;
public class WebViewEventSender implements EventSender {
private static final String LOGTAG = "WebViewEventSender";
WebViewEventSender(WebView webView) {
mWebView = webView;
mWebView.getSettings().setBuiltInZoomControls(true);
mTouchPoints = new Vector<TouchPoint>();
}
public void resetMouse() {
mouseX = mouseY = 0;
}
public void enableDOMUIEventLogging(int DOMNode) {
// TODO Auto-generated method stub
}
public void fireKeyboardEventsToElement(int DOMNode) {
// TODO Auto-generated method stub
}
public void keyDown(String character, String[] withModifiers) {
Log.e("EventSender", "KeyDown: " + character + "("
+ character.getBytes()[0] + ") Modifiers: "
+ Arrays.toString(withModifiers));
KeyEvent modifier = null;
if (withModifiers != null && withModifiers.length > 0) {
for (int i = 0; i < withModifiers.length; i++) {
int keyCode = modifierMapper(withModifiers[i]);
modifier = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mWebView.onKeyDown(modifier.getKeyCode(), modifier);
}
}
int keyCode = keyMapper(character.toLowerCase().toCharArray()[0]);
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mWebView.onKeyDown(event.getKeyCode(), event);
}
public void keyDown(String character) {
keyDown(character, null);
}
public void leapForward(int milliseconds) {
// TODO Auto-generated method stub
}
public void mouseClick() {
mouseDown();
mouseUp();
}
public void mouseDown() {
long ts = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_DOWN, mouseX, mouseY, 0);
mWebView.onTouchEvent(event);
}
public void mouseMoveTo(int X, int Y) {
mouseX= X;
mouseY= Y;
}
public void mouseUp() {
long ts = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_UP, mouseX, mouseY, 0);
mWebView.onTouchEvent(event);
}
// Assumes lowercase chars, case needs to be
// handled by calling function.
static int keyMapper(char c) {
// handle numbers
if (c >= '0' && c<= '9') {
int offset = c - '0';
return KeyEvent.KEYCODE_0 + offset;
}
// handle characters
if (c >= 'a' && c <= 'z') {
int offset = c - 'a';
return KeyEvent.KEYCODE_A + offset;
}
// handle all others
switch (c) {
case '*':
return KeyEvent.KEYCODE_STAR;
case '#':
return KeyEvent.KEYCODE_POUND;
case ',':
return KeyEvent.KEYCODE_COMMA;
case '.':
return KeyEvent.KEYCODE_PERIOD;
case '\t':
return KeyEvent.KEYCODE_TAB;
case ' ':
return KeyEvent.KEYCODE_SPACE;
case '\n':
return KeyEvent.KEYCODE_ENTER;
case '\b':
case 0x7F:
return KeyEvent.KEYCODE_DEL;
case '~':
return KeyEvent.KEYCODE_GRAVE;
case '-':
return KeyEvent.KEYCODE_MINUS;
case '=':
return KeyEvent.KEYCODE_EQUALS;
case '(':
return KeyEvent.KEYCODE_LEFT_BRACKET;
case ')':
return KeyEvent.KEYCODE_RIGHT_BRACKET;
case '\\':
return KeyEvent.KEYCODE_BACKSLASH;
case ';':
return KeyEvent.KEYCODE_SEMICOLON;
case '\'':
return KeyEvent.KEYCODE_APOSTROPHE;
case '/':
return KeyEvent.KEYCODE_SLASH;
default:
break;
}
return c;
}
static int modifierMapper(String modifier) {
if (modifier.equals("ctrlKey")) {
return KeyEvent.KEYCODE_ALT_LEFT;
} else if (modifier.equals("shiftKey")) {
return KeyEvent.KEYCODE_SHIFT_LEFT;
} else if (modifier.equals("altKey")) {
return KeyEvent.KEYCODE_SYM;
} else if (modifier.equals("metaKey")) {
return KeyEvent.KEYCODE_UNKNOWN;
}
return KeyEvent.KEYCODE_UNKNOWN;
}
public void touchStart() {
final int numPoints = mTouchPoints.size();
if (numPoints == 0) {
return;
}
int[] pointerIds = new int[numPoints];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
long downTime = SystemClock.uptimeMillis();
for (int i = 0; i < numPoints; ++i) {
pointerIds[i] = mTouchPoints.get(i).getId();
pointerCoords[i] = new MotionEvent.PointerCoords();
pointerCoords[i].x = mTouchPoints.get(i).getX();
pointerCoords[i].y = mTouchPoints.get(i).getY();
mTouchPoints.get(i).setDownTime(downTime);
}
MotionEvent event = MotionEvent.obtain(downTime, downTime,
MotionEvent.ACTION_DOWN, numPoints, pointerIds, pointerCoords,
mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
mWebView.onTouchEvent(event);
}
public void touchMove() {
final int numPoints = mTouchPoints.size();
if (numPoints == 0) {
return;
}
int[] pointerIds = new int[numPoints];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
int numMovedPoints = 0;
for (int i = 0; i < numPoints; ++i) {
TouchPoint tp = mTouchPoints.get(i);
if (tp.hasMoved()) {
pointerIds[numMovedPoints] = mTouchPoints.get(i).getId();
pointerCoords[i] = new MotionEvent.PointerCoords();
pointerCoords[numMovedPoints].x = mTouchPoints.get(i).getX();
pointerCoords[numMovedPoints].y = mTouchPoints.get(i).getY();
++numMovedPoints;
tp.setMoved(false);
}
}
if (numMovedPoints == 0) {
return;
}
MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE,
numMovedPoints, pointerIds, pointerCoords,
mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
mWebView.onTouchEvent(event);
}
public void touchEnd() {
final int numPoints = mTouchPoints.size();
if (numPoints == 0) {
return;
}
int[] pointerIds = new int[numPoints];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
for (int i = 0; i < numPoints; ++i) {
pointerIds[i] = mTouchPoints.get(i).getId();
pointerCoords[i] = new MotionEvent.PointerCoords();
pointerCoords[i].x = mTouchPoints.get(i).getX();
pointerCoords[i].y = mTouchPoints.get(i).getY();
}
MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
numPoints, pointerIds, pointerCoords,
mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
mWebView.onTouchEvent(event);
for (int i = numPoints - 1; i >= 0; --i) { // remove released points.
TouchPoint tp = mTouchPoints.get(i);
if (tp.isReleased()) {
mTouchPoints.remove(i);
}
}
}
public void touchCancel() {
final int numPoints = mTouchPoints.size();
if (numPoints == 0) {
return;
}
int[] pointerIds = new int[numPoints];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
long cancelTime = SystemClock.uptimeMillis();
int numCanceledPoints = 0;
for (int i = 0; i < numPoints; ++i) {
TouchPoint tp = mTouchPoints.get(i);
if (tp.cancelled()) {
pointerIds[numCanceledPoints] = mTouchPoints.get(i).getId();
pointerCoords[numCanceledPoints] = new MotionEvent.PointerCoords();
pointerCoords[numCanceledPoints].x = mTouchPoints.get(i).getX();
pointerCoords[numCanceledPoints].y = mTouchPoints.get(i).getY();
++numCanceledPoints;
}
}
if (numCanceledPoints == 0) {
return;
}
MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).downTime(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL,
numCanceledPoints, pointerIds, pointerCoords,
mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
mWebView.onTouchEvent(event);
}
public void cancelTouchPoint(int id) {
TouchPoint tp = mTouchPoints.get(id);
if (tp == null) {
return;
}
tp.cancel();
}
public void addTouchPoint(int x, int y) {
final int numPoints = mTouchPoints.size();
int id;
if (numPoints == 0) {
id = 0;
} else {
id = mTouchPoints.get(numPoints - 1).getId() + 1;
}
mTouchPoints.add(new TouchPoint(id, contentsToWindowX(x), contentsToWindowY(y)));
}
public void updateTouchPoint(int i, int x, int y) {
TouchPoint tp = mTouchPoints.get(i);
if (tp == null) {
return;
}
tp.update(contentsToWindowX(x), contentsToWindowY(y));
tp.setMoved(true);
}
public void setTouchModifier(String modifier, boolean enabled) {
int mask = 0;
if ("alt".equals(modifier.toLowerCase())) {
mask = KeyEvent.META_ALT_ON;
} else if ("shift".equals(modifier.toLowerCase())) {
mask = KeyEvent.META_SHIFT_ON;
} else if ("ctrl".equals(modifier.toLowerCase())) {
mask = KeyEvent.META_SYM_ON;
}
if (enabled) {
mTouchMetaState |= mask;
} else {
mTouchMetaState &= ~mask;
}
}
public void releaseTouchPoint(int id) {
TouchPoint tp = mTouchPoints.get(id);
if (tp == null) {
return;
}
tp.release();
}
public void clearTouchPoints() {
mTouchPoints.clear();
}
public void clearTouchMetaState() {
mTouchMetaState = 0;
}
private int contentsToWindowX(int x) {
return Math.round(x * mWebView.getScale()) - mWebView.getScrollX();
}
private int contentsToWindowY(int y) {
return Math.round(y * mWebView.getScale()) - mWebView.getScrollY();
}
private WebView mWebView = null;
private int mouseX;
private int mouseY;
private class TouchPoint {
private int mId;
private int mX;
private int mY;
private long mDownTime;
private boolean mReleased;
private boolean mMoved;
private boolean mCancelled;
public TouchPoint(int id, int x, int y) {
mId = id;
mX = x;
mY = y;
mReleased = false;
mMoved = false;
mCancelled = false;
}
public void setDownTime(long downTime) { mDownTime = downTime; }
public long downTime() { return mDownTime; }
public void cancel() { mCancelled = true; }
public boolean cancelled() { return mCancelled; }
public void release() { mReleased = true; }
public boolean isReleased() { return mReleased; }
public void setMoved(boolean moved) { mMoved = moved; }
public boolean hasMoved() { return mMoved; }
public int getId() { return mId; }
public int getX() { return mX; }
public int getY() { return mY; }
public void update(int x, int y) {
mX = x;
mY = y;
}
}
private Vector<TouchPoint> mTouchPoints;
private int mTouchMetaState;
}

View File

@@ -1,127 +0,0 @@
/*
* Copyright (C) 2009 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.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;
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;
}
}
}

View File

@@ -1,133 +0,0 @@
/*
* Copyright (C) 2009 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.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);
}
}
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (C) 2009 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.forwarder;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import android.os.Environment;
import android.util.Log;
public class ForwardService {
private ForwardServer fs8000, fs8080, fs8443;
private static ForwardService inst;
private static final String LOGTAG = "ForwardService";
private static final String DEFAULT_TEST_HOST = "android-browser-test.mtv.corp.google.com";
private static final String FORWARD_HOST_CONF =
Environment.getExternalStorageDirectory() + "/drt_forward_host.txt";
private ForwardService() {
int addr = getForwardHostAddr();
if (addr != -1) {
fs8000 = new ForwardServer(8000, addr, 8000);
fs8080 = new ForwardServer(8080, addr, 8080);
fs8443 = new ForwardServer(8443, addr, 8443);
}
}
public static ForwardService getForwardService() {
if (inst == null) {
inst = new ForwardService();
}
return inst;
}
public void startForwardService() {
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);
return;
}
}
public void stopForwardService() {
if (fs8000 != null) {
fs8000.stop();
fs8000 = null;
}
if (fs8080 != null) {
fs8080.stop();
fs8080 = null;
}
if (fs8443 != null) {
fs8443.stop();
fs8443 = null;
}
Log.v(LOGTAG, "forwarders stopped.");
}
private static int getForwardHostAddr() {
int addr = -1;
String host = null;
File forwardHostConf = new File(FORWARD_HOST_CONF);
if (forwardHostConf.isFile()) {
BufferedReader hostReader = null;
try {
hostReader = new BufferedReader(new FileReader(forwardHostConf));
host = hostReader.readLine();
Log.v(LOGTAG, "read forward host from file: " + host);
} catch (IOException ioe) {
Log.v(LOGTAG, "cannot read forward host from file", ioe);
} finally {
if (hostReader != null) {
try {
hostReader.close();
} catch (IOException ioe) {
// burn!!!
}
}
}
}
if (host == null || host.length() == 0)
host = DEFAULT_TEST_HOST;
try {
addr = AdbUtils.resolve(host);
} catch (IOException ioe) {
Log.e(LOGTAG, "failed to resolve server address", ioe);
}
return addr;
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright (C) 2009 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.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";
private static final int BUFFER_SIZE = 16384;
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[BUFFER_SIZE];
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 + "}";
}
}
}

View File

@@ -1,29 +0,0 @@
#
# Copyright (C) 2010 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := diff_match_patch
LOCAL_PACKAGE_NAME := DumpRenderTree2
include $(BUILD_PACKAGE)

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree2">
<application>
<uses-library android:name="android.test.runner" />
<activity android:name=".ui.DirListActivity"
android:label="Dump Render Tree 2"
android:configChanges="orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.TEST" />
</intent-filter>
</activity>
<!-- android:launchMode="singleTask" is there so we only have a one instance
of this activity. However, it doesn't seem to work exactly like described in the
documentation, because the behaviour of the application suggest
there is only a single task for all 3 activities. We don't understand
how exactly it all works, but at the moment it works just fine.
It can lead to some weird behaviour in the future. -->
<activity android:name=".TestsListActivity"
android:label="Tests' list activity"
android:launchMode="singleTask"
android:configChanges="orientation">
</activity>
<activity android:name=".LayoutTestsExecutor"
android:theme="@style/WhiteBackground"
android:label="Layout tests' executor"
android:process=":executor">
</activity>
<service android:name="ManagerService">
</service>
</application>
<instrumentation android:name="com.android.dumprendertree2.scriptsupport.ScriptTestRunner"
android:targetPackage="com.android.dumprendertree2"
android:label="Layout tests script runner" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_SDCARD" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>

View File

@@ -1,163 +0,0 @@
#!/usr/bin/python
#
# Copyright (C) 2010 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.
#
"""Start, stop, or restart apache2 server.
Apache2 must be installed with mod_php!
Usage:
run_apache2.py start|stop|restart
"""
import sys
import os
import subprocess
import logging
import optparse
import time
def main(run_cmd, options):
# Setup logging class
logging.basicConfig(level=logging.INFO, format='%(message)s')
if not run_cmd in ("start", "stop", "restart"):
logging.info("illegal argument: " + run_cmd)
logging.info("Usage: python run_apache2.py start|stop|restart")
return False
# Create /tmp/WebKit if it doesn't exist. This is needed for various files used by apache2
tmp_WebKit = os.path.join("/tmp", "WebKit")
if not os.path.exists(tmp_WebKit):
os.mkdir(tmp_WebKit)
# Get the path to android tree root based on the script location.
# Basically we go 5 levels up
parent = os.pardir
script_location = os.path.abspath(os.path.dirname(sys.argv[0]))
android_tree_root = os.path.join(script_location, parent, parent, parent, parent, parent)
android_tree_root = os.path.normpath(android_tree_root)
# If any of these is relative, then it's relative to ServerRoot (in our case android_tree_root)
webkit_path = os.path.join("external", "webkit")
if (options.tests_root_directory != None):
# if options.tests_root_directory is absolute, os.getcwd() is discarded!
layout_tests_path = os.path.normpath(os.path.join(os.getcwd(), options.tests_root_directory))
else:
layout_tests_path = os.path.join(webkit_path, "LayoutTests")
http_conf_path = os.path.join(layout_tests_path, "http", "conf")
# Prepare the command to set ${APACHE_RUN_USER} and ${APACHE_RUN_GROUP}
envvars_path = os.path.join("/etc", "apache2", "envvars")
export_envvars_cmd = "source " + envvars_path
error_log_path = os.path.join(tmp_WebKit, "apache2-error.log")
custom_log_path = os.path.join(tmp_WebKit, "apache2-access.log")
# Prepare the command to (re)start/stop the server with specified settings
apache2_restart_template = "apache2 -k %s"
directives = " -c \"ServerRoot " + android_tree_root + "\""
# The default config in apache2-debian-httpd.conf listens on ports 8080 and
# 8443. We also need to listen on port 8000 for HTTP tests.
directives += " -c \"Listen 8000\""
# We use http/tests as the document root as the HTTP tests use hardcoded
# resources at the server root. We then use aliases to make available the
# complete set of tests and the required scripts.
directives += " -c \"DocumentRoot " + os.path.join(layout_tests_path, "http", "tests/") + "\""
directives += " -c \"Alias /LayoutTests " + layout_tests_path + "\""
directives += " -c \"Alias /Tools/DumpRenderTree/android " + \
os.path.join(webkit_path, "Tools", "DumpRenderTree", "android") + "\""
directives += " -c \"Alias /ThirdPartyProject.prop " + \
os.path.join(webkit_path, "ThirdPartyProject.prop") + "\""
# This directive is commented out in apache2-debian-httpd.conf for some reason
# However, it is useful to browse through tests in the browser, so it's added here.
# One thing to note is that because of problems with mod_dir and port numbers, mod_dir
# is turned off. That means that there _must_ be a trailing slash at the end of URL
# for auto indexes to work correctly.
directives += " -c \"LoadModule autoindex_module /usr/lib/apache2/modules/mod_autoindex.so\""
directives += " -c \"ErrorLog " + error_log_path +"\""
directives += " -c \"CustomLog " + custom_log_path + " combined\""
directives += " -c \"SSLCertificateFile " + os.path.join(http_conf_path, "webkit-httpd.pem") + \
"\""
directives += " -c \"User ${APACHE_RUN_USER}\""
directives += " -c \"Group ${APACHE_RUN_GROUP}\""
directives += " -C \"TypesConfig " + \
os.path.join(android_tree_root, http_conf_path, "mime.types") + "\""
conf_file_cmd = " -f " + \
os.path.join(android_tree_root, http_conf_path, "apache2-debian-httpd.conf")
# Try to execute the commands
logging.info("Will " + run_cmd + " apache2 server.")
# It is worth noting here that if the configuration file with which we restart the server points
# to a different PidFile it will not work and will result in a second apache2 instance.
if (run_cmd == 'restart'):
logging.info("First will stop...")
if execute_cmd(envvars_path, error_log_path,
export_envvars_cmd + " && " + (apache2_restart_template % ('stop')) + directives + conf_file_cmd) == False:
logging.info("Failed to stop Apache2")
return False
logging.info("Stopped. Will start now...")
# We need to sleep breifly to avoid errors with apache being stopped and started too quickly
time.sleep(0.5)
if execute_cmd(envvars_path, error_log_path,
export_envvars_cmd + " && " +
(apache2_restart_template % (run_cmd)) + directives +
conf_file_cmd) == False:
logging.info("Failed to start Apache2")
return False
logging.info("Successfully started")
return True
def execute_cmd(envvars_path, error_log_path, cmd):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
# Output the stdout from the command to console
logging.info(out)
# Report any errors
if p.returncode != 0:
logging.info("!! ERRORS:")
if err.find(envvars_path) != -1:
logging.info(err)
elif err.find('command not found') != -1:
logging.info("apache2 is probably not installed")
else:
logging.info(err)
logging.info("Try looking in " + error_log_path + " for details")
return False
return True
if __name__ == "__main__":
option_parser = optparse.OptionParser(usage="Usage: %prog [options] start|stop|restart")
option_parser.add_option("", "--tests-root-directory",
help="The directory from which to take the tests, default is external/webkit/LayoutTests in this checkout of the Android tree")
options, args = option_parser.parse_args();
if len(args) < 1:
run_cmd = ""
else:
run_cmd = args[0]
main(run_cmd, options)

View File

@@ -1,99 +0,0 @@
#!/usr/bin/python
"""Run layout tests on the device.
It runs the specified tests on the device, downloads the summaries to the temporary directory
and optionally shows the detailed results the host's default browser.
Usage:
run_layout_tests.py --show-results-in-browser test-relative-path
"""
import logging
import optparse
import os
import re
import sys
import subprocess
import tempfile
import webbrowser
import run_apache2
#TODO: These should not be hardcoded
RESULTS_ABSOLUTE_PATH = "/sdcard/layout-test-results/"
DETAILS_HTML = "details.html"
SUMMARY_TXT = "summary.txt"
def main(path, options):
tmpdir = tempfile.gettempdir()
# Restart the server
if run_apache2.main("restart", options) == False:
return
# Run the tests in path
adb_cmd = "adb"
if options.serial:
adb_cmd += " -s " + options.serial
cmd = adb_cmd + " shell am instrument "
cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests "
cmd += "-e path \"" + path + "\" "
cmd += "-w com.android.dumprendertree2/com.android.dumprendertree2.scriptsupport.ScriptTestRunner"
logging.info("Running the tests...")
logging.debug("Command = %s" % cmd)
(stdoutdata, stderrdata) = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if stderrdata != "":
logging.info("Failed to start tests:\n%s", stderrdata)
return
if re.search("^INSTRUMENTATION_STATUS_CODE: -1", stdoutdata, re.MULTILINE) != None:
logging.info("Failed to run the tests. Is DumpRenderTree2 installed on the device?")
return
if re.search("^OK \([0-9]+ tests?\)", stdoutdata, re.MULTILINE) == None:
logging.info("DumpRenderTree2 failed to run correctly:\n%s", stdoutdata)
return
logging.info("Downloading the summaries...")
# Download the txt summary to tmp folder
summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT)
cmd = "rm -f " + summary_txt_tmp_path + ";"
cmd += adb_cmd + " pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path
subprocess.Popen(cmd, shell=True).wait()
# Download the html summary to tmp folder
details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML)
cmd = "rm -f " + details_html_tmp_path + ";"
cmd += adb_cmd + " pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path
subprocess.Popen(cmd, shell=True).wait()
# Print summary to console
logging.info("All done.\n")
cmd = "cat " + summary_txt_tmp_path
os.system(cmd)
logging.info("")
# Open the browser with summary
if options.show_results_in_browser != "false":
webbrowser.open(details_html_tmp_path)
if __name__ == "__main__":
option_parser = optparse.OptionParser(usage="Usage: %prog [options] test-relative-path")
option_parser.add_option("", "--show-results-in-browser", default="true",
help="Show the results the host's default web browser, default=true")
option_parser.add_option("", "--tests-root-directory",
help="The directory from which to take the tests, default is external/webkit/LayoutTests in this checkout of the Android tree")
option_parser.add_option("-s", "--serial", default=None, help="Specify the serial number of device to run test on")
options, args = option_parser.parse_args();
logging.basicConfig(level=logging.INFO, format='%(message)s')
if len(args) > 1:
logging.fatal("Usage: run_layout_tests.py [options] test-relative-path")
else:
if len(args) < 1:
path = "";
else:
path = args[0]
main(path, options);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/icon"
android:layout_width="80px"
android:adjustViewBounds="true"
android:paddingLeft="15px"
android:paddingRight="15px"
android:paddingTop="15px"
android:paddingBottom="15px"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="60px"
android:gravity="center_vertical"
android:textSize="14sp"
/>
</LinearLayout>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/run_all"
android:title="@string/run_all_tests" />
</menu>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 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.
-->
<resources>
<string name="dialog_run_abort_dir_title_prefix">Directory:</string>
<string name="dialog_run_abort_dir_msg">This will run all the tests in this directory and all
the subdirectories. It may take a few hours!</string>
<string name="dialog_run_abort_dir_ok_button">Run tests!</string>
<string name="dialog_run_abort_dir_abort_button">Abort</string>
<string name="dialog_progress_title">Loading items.</string>
<string name="dialog_progress_msg">Please wait...</string>
<string name="runner_preloading_title">Preloading tests...</string>
<string name="run_all_tests">Run all tests in the current directory</string>
</resources>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2010 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.
-->
<resources>
<style name="WhiteBackground">
<item name="android:background">@android:color/white</item>
</style>
</resources>

View File

@@ -1,249 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.webkit.WebView;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* A class that represent a result of the test. It is responsible for returning the result's
* raw data and generating its own diff in HTML format.
*/
public abstract class AbstractResult implements Comparable<AbstractResult>, Serializable {
private static final String LOG_TAG = "AbstractResult";
public enum TestType {
TEXT {
@Override
public AbstractResult createResult(Bundle bundle) {
return new TextResult(bundle);
}
},
RENDER_TREE {
@Override
public AbstractResult createResult(Bundle bundle) {
/** TODO: RenderTree tests are not yet supported */
return null;
}
};
public abstract AbstractResult createResult(Bundle bundle);
}
/**
* A code representing the result of comparing actual and expected results.
*/
public enum ResultCode implements Serializable {
RESULTS_MATCH("Results match"),
RESULTS_DIFFER("Results differ"),
NO_EXPECTED_RESULT("No expected result"),
NO_ACTUAL_RESULT("No actual result");
private String mTitle;
private ResultCode(String title) {
mTitle = title;
}
@Override
public String toString() {
return mTitle;
}
}
String mAdditionalTextOutputString;
public int compareTo(AbstractResult another) {
return getRelativePath().compareTo(another.getRelativePath());
}
public void setAdditionalTextOutputString(String additionalTextOutputString) {
mAdditionalTextOutputString = additionalTextOutputString;
}
public String getAdditionalTextOutputString() {
return mAdditionalTextOutputString;
}
public byte[] getBytes() {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(this);
} finally {
if (baos != null) {
baos.close();
}
if (oos != null) {
oos.close();
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to serialize result: " + getRelativePath(), e);
}
return baos == null ? null : baos.toByteArray();
}
public static AbstractResult create(byte[] bytes) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
AbstractResult result = null;
try {
try {
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
result = (AbstractResult)ois.readObject();
} finally {
if (bais != null) {
bais.close();
}
if (ois != null) {
ois.close();
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to deserialize result!", e);
} catch (ClassNotFoundException e) {
Log.e(LOG_TAG, "Unable to deserialize result!", e);
}
return result;
}
public void clearResults() {
mAdditionalTextOutputString = null;
}
/**
* Makes the result object obtain the results of the test from the webview
* and store them in the format that suits itself bests. This method is asynchronous.
* The message passed as a parameter is a message that should be sent to its target
* when the result finishes obtaining the result.
*
* @param webview
* @param resultObtainedMsg
*/
public abstract void obtainActualResults(WebView webview, Message resultObtainedMsg);
public abstract void setExpectedImageResult(byte[] expectedResult);
public abstract void setExpectedImageResultPath(String relativePath);
public abstract String getExpectedImageResultPath();
public abstract void setExpectedTextResult(String expectedResult);
public abstract void setExpectedTextResultPath(String relativePath);
public abstract String getExpectedTextResultPath();
/**
* Returns result's image data that can be written to the disk. It can be null
* if there is an error of some sort or for example the test times out.
*
* <p> Some tests will not provide data (like text tests)
*
* @return
* results image data
*/
public abstract byte[] getActualImageResult();
/**
* Returns result's text data. It can be null
* if there is an error of some sort or for example the test times out.
*
* @return
* results text data
*/
public abstract String getActualTextResult();
/**
* Returns the status code representing the result of comparing actual and expected results.
*
* @return
* the status code from comparing actual and expected results
*/
public abstract ResultCode getResultCode();
/**
* Returns whether this test crashed.
*
* @return
* whether this test crashed
*/
public abstract boolean didCrash();
/**
* Returns whether this test timed out.
*
* @return
* whether this test timed out
*/
public abstract boolean didTimeOut();
/**
* Sets that this test timed out.
*/
public abstract void setDidTimeOut();
/**
* Returns whether the test passed.
*
* @return
* whether the test passed
*/
public boolean didPass() {
// Tests that crash can't have timed out or have an actual result.
assert !(didCrash() && didTimeOut());
assert !(didCrash() && getResultCode() != ResultCode.NO_ACTUAL_RESULT);
return !didCrash() && !didTimeOut() && getResultCode() == ResultCode.RESULTS_MATCH;
}
/**
* Return the type of the result data.
*
* @return
* the type of the result data.
*/
public abstract TestType getType();
public abstract String getRelativePath();
/**
* Returns a piece of HTML code that presents a visual diff between a result and
* the expected result.
*
* @return
* a piece of HTML code with a visual diff between the result and the expected result
*/
public abstract String getDiffAsHtml();
public abstract Bundle getBundle();
}

View File

@@ -1,119 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.util.Log;
import android.webkit.ConsoleMessage;
import java.net.MalformedURLException;
import java.net.URL;
/**
* A class that stores consoles messages, database callbacks, alert messages, etc.
*/
public class AdditionalTextOutput {
private static final String LOG_TAG = "AdditionalTextOutput";
/**
* Ordering of enums is important as it determines ordering of the toString method!
* StringBuilders will be printed in the order the corresponding types appear here.
*/
private enum OutputType {
JS_DIALOG,
EXCEEDED_DB_QUOTA_MESSAGE,
CONSOLE_MESSAGE;
}
StringBuilder[] mOutputs = new StringBuilder[OutputType.values().length];
private StringBuilder getStringBuilderForType(OutputType outputType) {
int index = outputType.ordinal();
if (mOutputs[index] == null) {
mOutputs[index] = new StringBuilder();
}
return mOutputs[index];
}
public void appendExceededDbQuotaMessage(String urlString, String databaseIdentifier) {
StringBuilder output = getStringBuilderForType(OutputType.EXCEEDED_DB_QUOTA_MESSAGE);
String protocol = "";
String host = "";
int port = 0;
try {
URL url = new URL(urlString);
protocol = url.getProtocol();
host = url.getHost();
if (url.getPort() > -1) {
port = url.getPort();
}
} catch (MalformedURLException e) {
Log.e(LOG_TAG, "urlString=" + urlString + " databaseIdentifier=" + databaseIdentifier,
e);
}
output.append("UI DELEGATE DATABASE CALLBACK: ");
output.append("exceededDatabaseQuotaForSecurityOrigin:{");
output.append(protocol + ", " + host + ", " + port + "} ");
output.append("database:" + databaseIdentifier + "\n");
}
public void appendConsoleMessage(ConsoleMessage consoleMessage) {
StringBuilder output = getStringBuilderForType(OutputType.CONSOLE_MESSAGE);
output.append("CONSOLE MESSAGE: line " + consoleMessage.lineNumber());
output.append(": " + consoleMessage.message() + "\n");
}
public void appendJsAlert(String message) {
StringBuilder output = getStringBuilderForType(OutputType.JS_DIALOG);
output.append("ALERT: ");
output.append(message);
output.append('\n');
}
public void appendJsConfirm(String message) {
StringBuilder output = getStringBuilderForType(OutputType.JS_DIALOG);
output.append("CONFIRM: ");
output.append(message);
output.append('\n');
}
public void appendJsPrompt(String message, String defaultValue) {
StringBuilder output = getStringBuilderForType(OutputType.JS_DIALOG);
output.append("PROMPT: ");
output.append(message);
output.append(", default text: ");
output.append(defaultValue);
output.append('\n');
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (int i = 0; i < mOutputs.length; i++) {
if (mOutputs[i] != null) {
result.append(mOutputs[i].toString());
}
}
return result.toString();
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.os.Bundle;
import android.os.Message;
import android.webkit.WebView;
/**
* A dummy class representing test that crashed.
*
* TODO: All the methods regarding expected results need implementing.
*/
public class CrashedDummyResult extends AbstractResult {
String mRelativePath;
public CrashedDummyResult(String relativePath) {
mRelativePath = relativePath;
}
@Override
public byte[] getActualImageResult() {
return null;
}
@Override
public String getActualTextResult() {
return null;
}
@Override
public Bundle getBundle() {
/** TODO: */
return null;
}
@Override
public String getDiffAsHtml() {
/** TODO: Probably show at least expected results */
return "Ooops, I crashed...";
}
@Override
public String getRelativePath() {
return mRelativePath;
}
@Override
public ResultCode getResultCode() {
return ResultCode.NO_ACTUAL_RESULT;
}
@Override
public boolean didCrash() {
return true;
}
@Override
public boolean didTimeOut() {
return false;
}
@Override
public void setDidTimeOut() {
/** This method is not applicable for this type of result */
assert false;
}
@Override
public TestType getType() {
return null;
}
@Override
public void obtainActualResults(WebView webview, Message resultObtainedMsg) {
/** This method is not applicable for this type of result */
assert false;
}
@Override
public void setExpectedImageResult(byte[] expectedResult) {
/** TODO */
}
@Override
public void setExpectedTextResult(String expectedResult) {
/** TODO */
}
@Override
public String getExpectedImageResultPath() {
/** TODO */
return null;
}
@Override
public String getExpectedTextResultPath() {
/** TODO */
return null;
}
@Override
public void setExpectedImageResultPath(String relativePath) {
/** TODO */
}
@Override
public void setExpectedTextResultPath(String relativePath) {
/** TODO */
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.webkit.WebView;
/**
* A class that acts as a JS interface for webview to mock various touch events,
* mouse actions and key presses.
*
* The methods here just call corresponding methods on EventSenderImpl
* that contains the logic of how to execute the methods.
*/
public class EventSender {
EventSenderImpl mEventSenderImpl = new EventSenderImpl();
public void reset(WebView webView) {
mEventSenderImpl.reset(webView);
}
public void enableDOMUIEventLogging(int domNode) {
mEventSenderImpl.enableDOMUIEventLogging(domNode);
}
public void fireKeyboardEventsToElement(int domNode) {
mEventSenderImpl.fireKeyboardEventsToElement(domNode);
}
public void keyDown(String character, String[] withModifiers) {
mEventSenderImpl.keyDown(character, withModifiers);
}
public void keyDown(String character) {
keyDown(character, null);
}
public void leapForward(int milliseconds) {
mEventSenderImpl.leapForward(milliseconds);
}
public void mouseClick() {
mEventSenderImpl.mouseClick();
}
public void mouseDown() {
mEventSenderImpl.mouseDown();
}
public void mouseMoveTo(int x, int y) {
mEventSenderImpl.mouseMoveTo(x, y);
}
public void mouseUp() {
mEventSenderImpl.mouseUp();
}
public void touchStart() {
mEventSenderImpl.touchStart();
}
public void addTouchPoint(int x, int y) {
mEventSenderImpl.addTouchPoint(x, y);
}
public void updateTouchPoint(int id, int x, int y) {
mEventSenderImpl.updateTouchPoint(id, x, y);
}
public void setTouchModifier(String modifier, boolean enabled) {
mEventSenderImpl.setTouchModifier(modifier, enabled);
}
public void touchMove() {
mEventSenderImpl.touchMove();
}
public void releaseTouchPoint(int id) {
mEventSenderImpl.releaseTouchPoint(id);
}
public void touchEnd() {
mEventSenderImpl.touchEnd();
}
public void touchCancel() {
mEventSenderImpl.touchCancel();
}
public void clearTouchPoints() {
mEventSenderImpl.clearTouchPoints();
}
public void cancelTouchPoint(int id) {
mEventSenderImpl.cancelTouchPoint(id);
}
}

View File

@@ -1,590 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.webkit.WebView;
import java.util.LinkedList;
import java.util.List;
/**
* An implementation of EventSender
*/
public class EventSenderImpl {
private static final String LOG_TAG = "EventSenderImpl";
private static final int MSG_ENABLE_DOM_UI_EVENT_LOGGING = 0;
private static final int MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT = 1;
private static final int MSG_LEAP_FORWARD = 2;
private static final int MSG_KEY_DOWN = 3;
private static final int MSG_MOUSE_DOWN = 4;
private static final int MSG_MOUSE_UP = 5;
private static final int MSG_MOUSE_CLICK = 6;
private static final int MSG_MOUSE_MOVE_TO = 7;
private static final int MSG_ADD_TOUCH_POINT = 8;
private static final int MSG_TOUCH_START = 9;
private static final int MSG_UPDATE_TOUCH_POINT = 10;
private static final int MSG_TOUCH_MOVE = 11;
private static final int MSG_CLEAR_TOUCH_POINTS = 12;
private static final int MSG_TOUCH_CANCEL = 13;
private static final int MSG_RELEASE_TOUCH_POINT = 14;
private static final int MSG_TOUCH_END = 15;
private static final int MSG_SET_TOUCH_MODIFIER = 16;
private static final int MSG_CANCEL_TOUCH_POINT = 17;
private static class Point {
private int mX;
private int mY;
public Point(int x, int y) {
mX = x;
mY = y;
}
public int x() {
return mX;
}
public int y() {
return mY;
}
}
private Point createViewPointFromContentCoordinates(int x, int y) {
return new Point(Math.round(x * mWebView.getScale()) - mWebView.getScrollX(),
Math.round(y * mWebView.getScale()) - mWebView.getScrollY());
}
public static class TouchPoint {
private int mId;
private Point mPoint;
private long mDownTime;
private boolean mReleased = false;
private boolean mMoved = false;
private boolean mCancelled = false;
public TouchPoint(int id, Point point) {
mId = id;
mPoint = point;
}
public int getId() {
return mId;
}
public int getX() {
return mPoint.x();
}
public int getY() {
return mPoint.y();
}
public boolean hasMoved() {
return mMoved;
}
public void move(Point point) {
mPoint = point;
mMoved = true;
}
public void resetHasMoved() {
mMoved = false;
}
public long getDownTime() {
return mDownTime;
}
public void setDownTime(long downTime) {
mDownTime = downTime;
}
public boolean isReleased() {
return mReleased;
}
public void release() {
mReleased = true;
}
public boolean isCancelled() {
return mCancelled;
}
public void cancel() {
mCancelled = true;
}
}
private List<TouchPoint> mTouchPoints;
private int mTouchMetaState;
private Point mMousePoint;
private WebView mWebView;
private Handler mEventSenderHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Bundle bundle;
MotionEvent event;
long ts;
switch (msg.what) {
case MSG_ENABLE_DOM_UI_EVENT_LOGGING:
/** TODO: implement */
break;
case MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT:
/** TODO: implement */
break;
case MSG_LEAP_FORWARD:
/** TODO: implement */
break;
case MSG_KEY_DOWN:
bundle = (Bundle)msg.obj;
String character = bundle.getString("character");
String[] withModifiers = bundle.getStringArray("withModifiers");
if (withModifiers != null && withModifiers.length > 0) {
for (int i = 0; i < withModifiers.length; i++) {
executeKeyEvent(KeyEvent.ACTION_DOWN,
modifierToKeyCode(withModifiers[i]));
}
}
executeKeyEvent(KeyEvent.ACTION_DOWN,
charToKeyCode(character.toLowerCase().toCharArray()[0]));
break;
/** MOUSE */
case MSG_MOUSE_DOWN:
if (mMousePoint != null) {
ts = SystemClock.uptimeMillis();
event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_DOWN, mMousePoint.x(), mMousePoint.y(), 0);
mWebView.onTouchEvent(event);
}
break;
case MSG_MOUSE_UP:
if (mMousePoint != null) {
ts = SystemClock.uptimeMillis();
event = MotionEvent.obtain(ts, ts, MotionEvent.ACTION_UP, mMousePoint.x(), mMousePoint.y(), 0);
mWebView.onTouchEvent(event);
}
break;
case MSG_MOUSE_CLICK:
mouseDown();
mouseUp();
break;
case MSG_MOUSE_MOVE_TO:
mMousePoint = createViewPointFromContentCoordinates(msg.arg1, msg.arg2);
break;
/** TOUCH */
case MSG_ADD_TOUCH_POINT:
int numPoints = getTouchPoints().size();
int id;
if (numPoints == 0) {
id = 0;
} else {
id = getTouchPoints().get(numPoints - 1).getId() + 1;
}
getTouchPoints().add(
new TouchPoint(id, createViewPointFromContentCoordinates(msg.arg1, msg.arg2)));
break;
case MSG_TOUCH_START:
if (getTouchPoints().isEmpty()) {
return;
}
for (int i = 0; i < getTouchPoints().size(); ++i) {
getTouchPoints().get(i).setDownTime(SystemClock.uptimeMillis());
}
executeTouchEvent(MotionEvent.ACTION_DOWN);
break;
case MSG_UPDATE_TOUCH_POINT:
bundle = (Bundle)msg.obj;
int index = bundle.getInt("id");
if (index >= getTouchPoints().size()) {
Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: "
+ index);
break;
}
getTouchPoints().get(index).move(
createViewPointFromContentCoordinates(bundle.getInt("x"), bundle.getInt("y")));
break;
case MSG_TOUCH_MOVE:
/**
* FIXME: At the moment we don't support multi-touch. Hence, we only examine
* the first touch point. In future this method will need rewriting.
*/
if (getTouchPoints().isEmpty()) {
return;
}
executeTouchEvent(MotionEvent.ACTION_MOVE);
for (int i = 0; i < getTouchPoints().size(); ++i) {
getTouchPoints().get(i).resetHasMoved();
}
break;
case MSG_CANCEL_TOUCH_POINT:
if (msg.arg1 >= getTouchPoints().size()) {
Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: "
+ msg.arg1);
break;
}
getTouchPoints().get(msg.arg1).cancel();
break;
case MSG_TOUCH_CANCEL:
/**
* FIXME: At the moment we don't support multi-touch. Hence, we only examine
* the first touch point. In future this method will need rewriting.
*/
if (getTouchPoints().isEmpty()) {
return;
}
executeTouchEvent(MotionEvent.ACTION_CANCEL);
break;
case MSG_RELEASE_TOUCH_POINT:
if (msg.arg1 >= getTouchPoints().size()) {
Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: "
+ msg.arg1);
break;
}
getTouchPoints().get(msg.arg1).release();
break;
case MSG_TOUCH_END:
/**
* FIXME: At the moment we don't support multi-touch. Hence, we only examine
* the first touch point. In future this method will need rewriting.
*/
if (getTouchPoints().isEmpty()) {
return;
}
executeTouchEvent(MotionEvent.ACTION_UP);
// remove released points.
for (int i = getTouchPoints().size() - 1; i >= 0; --i) {
if (getTouchPoints().get(i).isReleased()) {
getTouchPoints().remove(i);
}
}
break;
case MSG_SET_TOUCH_MODIFIER:
bundle = (Bundle)msg.obj;
String modifier = bundle.getString("modifier");
boolean enabled = bundle.getBoolean("enabled");
int mask = 0;
if ("alt".equals(modifier.toLowerCase())) {
mask = KeyEvent.META_ALT_ON;
} else if ("shift".equals(modifier.toLowerCase())) {
mask = KeyEvent.META_SHIFT_ON;
} else if ("ctrl".equals(modifier.toLowerCase())) {
mask = KeyEvent.META_SYM_ON;
}
if (enabled) {
mTouchMetaState |= mask;
} else {
mTouchMetaState &= ~mask;
}
break;
case MSG_CLEAR_TOUCH_POINTS:
getTouchPoints().clear();
break;
default:
break;
}
}
};
public void reset(WebView webView) {
mWebView = webView;
mTouchPoints = null;
mTouchMetaState = 0;
mMousePoint = null;
}
public void enableDOMUIEventLogging(int domNode) {
Message msg = mEventSenderHandler.obtainMessage(MSG_ENABLE_DOM_UI_EVENT_LOGGING);
msg.arg1 = domNode;
msg.sendToTarget();
}
public void fireKeyboardEventsToElement(int domNode) {
Message msg = mEventSenderHandler.obtainMessage(MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT);
msg.arg1 = domNode;
msg.sendToTarget();
}
public void leapForward(int milliseconds) {
Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD);
msg.arg1 = milliseconds;
msg.sendToTarget();
}
public void keyDown(String character, String[] withModifiers) {
Bundle bundle = new Bundle();
bundle.putString("character", character);
bundle.putStringArray("withModifiers", withModifiers);
mEventSenderHandler.obtainMessage(MSG_KEY_DOWN, bundle).sendToTarget();
}
/** MOUSE */
public void mouseDown() {
mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN);
}
public void mouseUp() {
mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP);
}
public void mouseClick() {
mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK);
}
public void mouseMoveTo(int x, int y) {
mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget();
}
/** TOUCH */
public void addTouchPoint(int x, int y) {
mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget();
}
public void touchStart() {
mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START);
}
public void updateTouchPoint(int id, int x, int y) {
Bundle bundle = new Bundle();
bundle.putInt("id", id);
bundle.putInt("x", x);
bundle.putInt("y", y);
mEventSenderHandler.obtainMessage(MSG_UPDATE_TOUCH_POINT, bundle).sendToTarget();
}
public void touchMove() {
mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE);
}
public void cancelTouchPoint(int id) {
Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT);
msg.arg1 = id;
msg.sendToTarget();
}
public void touchCancel() {
mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL);
}
public void releaseTouchPoint(int id) {
Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT);
msg.arg1 = id;
msg.sendToTarget();
}
public void touchEnd() {
mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END);
}
public void setTouchModifier(String modifier, boolean enabled) {
Bundle bundle = new Bundle();
bundle.putString("modifier", modifier);
bundle.putBoolean("enabled", enabled);
mEventSenderHandler.obtainMessage(MSG_SET_TOUCH_MODIFIER, bundle).sendToTarget();
}
public void clearTouchPoints() {
mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS);
}
private List<TouchPoint> getTouchPoints() {
if (mTouchPoints == null) {
mTouchPoints = new LinkedList<TouchPoint>();
}
return mTouchPoints;
}
private void executeTouchEvent(int action) {
int numPoints = getTouchPoints().size();
int[] pointerIds = new int[numPoints];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[numPoints];
for (int i = 0; i < numPoints; ++i) {
boolean isNeeded = false;
switch(action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
isNeeded = true;
break;
case MotionEvent.ACTION_MOVE:
isNeeded = getTouchPoints().get(i).hasMoved();
break;
case MotionEvent.ACTION_CANCEL:
isNeeded = getTouchPoints().get(i).isCancelled();
break;
default:
Log.w(LOG_TAG + "::executeTouchEvent(),", "action not supported:" + action);
break;
}
numPoints = 0;
if (isNeeded) {
pointerIds[numPoints] = getTouchPoints().get(i).getId();
pointerCoords[numPoints] = new MotionEvent.PointerCoords();
pointerCoords[numPoints].x = getTouchPoints().get(i).getX();
pointerCoords[numPoints].y = getTouchPoints().get(i).getY();
++numPoints;
}
}
if (numPoints == 0) {
return;
}
MotionEvent event = MotionEvent.obtain(mTouchPoints.get(0).getDownTime(),
SystemClock.uptimeMillis(), action,
numPoints, pointerIds, pointerCoords,
mTouchMetaState, 1.0f, 1.0f, 0, 0, 0, 0);
mWebView.onTouchEvent(event);
}
private void executeKeyEvent(int action, int keyCode) {
KeyEvent event = new KeyEvent(action, keyCode);
mWebView.onKeyDown(event.getKeyCode(), event);
}
/**
* Assumes lowercase chars, case needs to be handled by calling function.
*/
private static int charToKeyCode(char c) {
// handle numbers
if (c >= '0' && c <= '9') {
int offset = c - '0';
return KeyEvent.KEYCODE_0 + offset;
}
// handle characters
if (c >= 'a' && c <= 'z') {
int offset = c - 'a';
return KeyEvent.KEYCODE_A + offset;
}
// handle all others
switch (c) {
case '*':
return KeyEvent.KEYCODE_STAR;
case '#':
return KeyEvent.KEYCODE_POUND;
case ',':
return KeyEvent.KEYCODE_COMMA;
case '.':
return KeyEvent.KEYCODE_PERIOD;
case '\t':
return KeyEvent.KEYCODE_TAB;
case ' ':
return KeyEvent.KEYCODE_SPACE;
case '\n':
return KeyEvent.KEYCODE_ENTER;
case '\b':
case 0x7F:
return KeyEvent.KEYCODE_DEL;
case '~':
return KeyEvent.KEYCODE_GRAVE;
case '-':
return KeyEvent.KEYCODE_MINUS;
case '=':
return KeyEvent.KEYCODE_EQUALS;
case '(':
return KeyEvent.KEYCODE_LEFT_BRACKET;
case ')':
return KeyEvent.KEYCODE_RIGHT_BRACKET;
case '\\':
return KeyEvent.KEYCODE_BACKSLASH;
case ';':
return KeyEvent.KEYCODE_SEMICOLON;
case '\'':
return KeyEvent.KEYCODE_APOSTROPHE;
case '/':
return KeyEvent.KEYCODE_SLASH;
default:
return c;
}
}
private static int modifierToKeyCode(String modifier) {
if (modifier.equals("ctrlKey")) {
return KeyEvent.KEYCODE_ALT_LEFT;
} else if (modifier.equals("shiftKey")) {
return KeyEvent.KEYCODE_SHIFT_LEFT;
} else if (modifier.equals("altKey")) {
return KeyEvent.KEYCODE_SYM;
}
return KeyEvent.KEYCODE_UNKNOWN;
}
}

View File

@@ -1,313 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.util.Log;
import com.android.dumprendertree2.forwarder.ForwarderManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A utility to filter out some files/directories from the views and tests that run.
*/
public class FileFilter {
private static final String LOG_TAG = "FileFilter";
private static final String TEST_EXPECTATIONS_TXT_PATH =
"platform/android/test_expectations.txt";
private static final String HTTP_TESTS_PATH = "http/tests/";
private static final String SSL_PATH = "ssl/";
private static final String TOKEN_CRASH = "CRASH";
private static final String TOKEN_FAIL = "FAIL";
private static final String TOKEN_SLOW = "SLOW";
private final Set<String> mCrashList = new HashSet<String>();
private final Set<String> mFailList = new HashSet<String>();
private final Set<String> mSlowList = new HashSet<String>();
public FileFilter() {
loadTestExpectations();
}
private static final String trimTrailingSlashIfPresent(String path) {
File file = new File(path);
return file.getPath();
}
public void loadTestExpectations() {
URL url = null;
try {
url = new URL(ForwarderManager.getHostSchemePort(false) +
"LayoutTests/" + TEST_EXPECTATIONS_TXT_PATH);
} catch (MalformedURLException e) {
assert false;
}
try {
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
byte[] httpAnswer = FsUtils.readDataFromUrl(url);
if (httpAnswer == null) {
Log.w(LOG_TAG, "loadTestExpectations(): File not found: " +
TEST_EXPECTATIONS_TXT_PATH);
return;
}
bufferedReader = new BufferedReader(new StringReader(
new String(httpAnswer)));
String line;
String entry;
String[] parts;
String path;
Set<String> tokens;
while (true) {
line = bufferedReader.readLine();
if (line == null) {
break;
}
/** Remove the comment and trim */
entry = line.split("//", 2)[0].trim();
/** Omit empty lines, advance to next line */
if (entry.isEmpty()) {
continue;
}
/** Split on whitespace into path part and the rest */
parts = entry.split("\\s", 2);
/** At this point parts.length >= 1 */
if (parts.length == 1) {
Log.w(LOG_TAG + "::reloadConfiguration",
"There are no options specified for the test!");
continue;
}
path = trimTrailingSlashIfPresent(parts[0]);
/** Split on whitespace */
tokens = new HashSet<String>(Arrays.asList(
parts[1].split("\\s", 0)));
/** Chose the right collections to add to */
if (tokens.contains(TOKEN_CRASH)) {
mCrashList.add(path);
/** If test is on skip list we ignore any further options */
continue;
}
if (tokens.contains(TOKEN_FAIL)) {
mFailList.add(path);
}
if (tokens.contains(TOKEN_SLOW)) {
mSlowList.add(path);
}
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "url=" + url, e);
}
}
/**
* Checks if test is expected to crash.
*
* <p>
* Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
*
* @param testPath
* - a relative path within LayoutTests folder
* @return if the test is supposed to be skipped
*/
public boolean isCrash(String testPath) {
for (String prefix : getPrefixes(testPath)) {
if (mCrashList.contains(prefix)) {
return true;
}
}
return false;
}
/**
* Checks if test result is supposed to be "failed".
*
* <p>
* Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
*
* @param testPath
* - a relative path within LayoutTests folder
* @return if the test result is supposed to be "failed"
*/
public boolean isFail(String testPath) {
for (String prefix : getPrefixes(testPath)) {
if (mFailList.contains(prefix)) {
return true;
}
}
return false;
}
/**
* Checks if test is slow and should have timeout increased.
*
* <p>
* Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
*
* @param testPath
* - a relative path within LayoutTests folder
* @return if the test is slow and should have timeout increased.
*/
public boolean isSlow(String testPath) {
for (String prefix : getPrefixes(testPath)) {
if (mSlowList.contains(prefix)) {
return true;
}
}
return false;
}
/**
* Returns the list of all path prefixes of the given path.
*
* <p>
* e.g. this/is/a/path returns the list: this this/is this/is/a this/is/a/path
*
* @param path
* @return the list of all path prefixes of the given path.
*/
private static List<String> getPrefixes(String path) {
File file = new File(path);
List<String> prefixes = new ArrayList<String>(8);
do {
prefixes.add(file.getPath());
file = file.getParentFile();
} while (file != null);
return prefixes;
}
/**
* Checks if the directory may contain tests or contains just helper files.
*
* @param dirName
* @return
* if the directory may contain tests
*/
public static boolean isTestDir(String dirName) {
return (!dirName.equals("script-tests")
&& !dirName.equals("resources") && !dirName.startsWith("."));
}
/**
* Checks if the file is a test.
* Currently we run .html, .xhtml and .php tests.
*
* @warning You MUST also call isTestDir() on the parent directory before
* assuming that a file is a test.
*
* @param testName
* @return if the file is a test
*/
public static boolean isTestFile(String testName) {
return testName.endsWith(".html")
|| testName.endsWith(".xhtml")
|| testName.endsWith(".php");
}
/**
* Return a URL of the test on the server.
*
* @param relativePath
* @param allowHttps Whether to allow the use of HTTPS, even if the file is in the SSL
* directory.
* @return a URL of the test on the server
*/
public static URL getUrl(String relativePath, boolean allowHttps) {
String urlBase = ForwarderManager.getHostSchemePort(false);
/**
* URL is formed differently for HTTP vs non-HTTP tests, because HTTP tests
* expect different document root. See run_apache2.py and .conf file for details
*/
if (relativePath.startsWith(HTTP_TESTS_PATH)) {
relativePath = relativePath.substring(HTTP_TESTS_PATH.length());
if (relativePath.startsWith(SSL_PATH) && allowHttps) {
urlBase = ForwarderManager.getHostSchemePort(true);
}
} else {
relativePath = "LayoutTests/" + relativePath;
}
try {
return new URL(urlBase + relativePath);
} catch (MalformedURLException e) {
Log.e(LOG_TAG, "Malformed URL!", e);
}
return null;
}
/**
* If the path contains extension (e.g .foo at the end of the file) then it changes
* this (.foo) into newEnding (so it has to contain the dot if we want to preserve it).
*
* <p>If the path doesn't contain an extension, it adds the ending to the path.
*
* @param relativePath
* @param newEnding
* @return
* a new path, containing the newExtension
*/
public static String setPathEnding(String relativePath, String newEnding) {
int dotPos = relativePath.lastIndexOf('.');
if (dotPos == -1) {
return relativePath + newEnding;
}
return relativePath.substring(0, dotPos) + newEnding;
}
}

View File

@@ -1,314 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.util.Log;
import com.android.dumprendertree2.forwarder.ForwarderManager;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
*
*/
public class FsUtils {
public static final String LOG_TAG = "FsUtils";
private static final String SCRIPT_URL = ForwarderManager.getHostSchemePort(false) +
"Tools/DumpRenderTree/android/get_layout_tests_dir_contents.php";
private static final int HTTP_TIMEOUT_MS = 5000;
private static HttpClient sHttpClient;
private static HttpClient getHttpClient() {
if (sHttpClient == null) {
HttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(),
ForwarderManager.HTTP_PORT));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(),
ForwarderManager.HTTPS_PORT));
ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(params,
schemeRegistry);
sHttpClient = new DefaultHttpClient(connectionManager, params);
HttpConnectionParams.setSoTimeout(sHttpClient.getParams(), HTTP_TIMEOUT_MS);
HttpConnectionParams.setConnectionTimeout(sHttpClient.getParams(), HTTP_TIMEOUT_MS);
}
return sHttpClient;
}
public static void writeDataToStorage(File file, byte[] bytes, boolean append) {
Log.d(LOG_TAG, "writeDataToStorage(): " + file.getAbsolutePath());
try {
OutputStream outputStream = null;
try {
file.getParentFile().mkdirs();
file.createNewFile();
Log.d(LOG_TAG, "writeDataToStorage(): File created: " + file.getAbsolutePath());
outputStream = new FileOutputStream(file, append);
outputStream.write(bytes);
} finally {
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "file.getAbsolutePath=" + file.getAbsolutePath() + " append=" + append,
e);
}
}
public static byte[] readDataFromStorage(File file) {
if (!file.exists()) {
Log.d(LOG_TAG, "readDataFromStorage(): File does not exist: "
+ file.getAbsolutePath());
return null;
}
byte[] bytes = null;
try {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
bytes = new byte[(int)file.length()];
fis.read(bytes);
} finally {
if (fis != null) {
fis.close();
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "file.getAbsolutePath=" + file.getAbsolutePath(), e);
}
return bytes;
}
static class UrlDataGetter extends Thread {
private URL mUrl;
private byte[] mBytes;
private boolean mGetComplete;
public UrlDataGetter(URL url) {
mUrl = url;
}
public byte[] get() {
start();
synchronized(this) {
while (!mGetComplete) {
try{
wait();
} catch(InterruptedException e) {
}
}
}
return mBytes;
}
public synchronized void run() {
mGetComplete = false;
HttpGet httpRequest = new HttpGet(mUrl.toString());
ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {
@Override
public byte[] handleResponse(HttpResponse response) throws IOException {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
return null;
}
HttpEntity entity = response.getEntity();
return (entity == null ? null : EntityUtils.toByteArray(entity));
}
};
mBytes = null;
try {
/**
* TODO: Not exactly sure why some requests hang indefinitely, but adding this
* timeout (in static getter for http client) in loop helps.
*/
boolean timedOut;
do {
timedOut = false;
try {
mBytes = getHttpClient().execute(httpRequest, handler);
} catch (SocketTimeoutException e) {
timedOut = true;
Log.w(LOG_TAG, "Expected SocketTimeoutException: " + mUrl, e);
}
} while (timedOut);
} catch (IOException e) {
Log.e(LOG_TAG, "url=" + mUrl, e);
}
mGetComplete = true;
notify();
}
}
public static byte[] readDataFromUrl(URL url) {
if (url == null) {
Log.w(LOG_TAG, "readDataFromUrl(): url is null!");
return null;
}
UrlDataGetter getter = new UrlDataGetter(url);
return getter.get();
}
public static List<String> getLayoutTestsDirContents(String dirRelativePath, boolean recurse,
boolean mode) {
String modeString = (mode ? "folders" : "files");
URL url = null;
try {
url = new URL(SCRIPT_URL +
"?path=" + dirRelativePath +
"&recurse=" + recurse +
"&mode=" + modeString);
} catch (MalformedURLException e) {
Log.e(LOG_TAG, "path=" + dirRelativePath + " recurse=" + recurse + " mode=" +
modeString, e);
return new LinkedList<String>();
}
HttpGet httpRequest = new HttpGet(url.toString());
ResponseHandler<LinkedList<String>> handler = new ResponseHandler<LinkedList<String>>() {
@Override
public LinkedList<String> handleResponse(HttpResponse response)
throws IOException {
LinkedList<String> lines = new LinkedList<String>();
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
return lines;
}
HttpEntity entity = response.getEntity();
if (entity == null) {
return lines;
}
BufferedReader reader =
new BufferedReader(new InputStreamReader(entity.getContent()));
String line;
try {
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} finally {
if (reader != null) {
reader.close();
}
}
return lines;
}
};
try {
return getHttpClient().execute(httpRequest, handler);
} catch (IOException e) {
Log.e(LOG_TAG, "getLayoutTestsDirContents(): HTTP GET failed for URL " + url);
return null;
}
}
public static void closeInputStream(InputStream inputStream) {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
Log.e(LOG_TAG, "Couldn't close stream!", e);
}
}
public static void closeOutputStream(OutputStream outputStream) {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
Log.e(LOG_TAG, "Couldn't close stream!", e);
}
}
public static List<String> loadTestListFromStorage(String path) {
List<String> list = new ArrayList<String>();
if (path != null && !path.isEmpty()) {
try {
File file = new File(path);
Log.d(LOG_TAG, "test list loaded from " + path);
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = null;
while ((line = reader.readLine()) != null) {
list.add(line);
}
reader.close();
} catch (IOException ioe) {
Log.e(LOG_TAG, "failed to load test list", ioe);
}
}
return list;
}
public static void saveTestListToStorage(File file, int start, List<String> testList) {
try {
BufferedWriter writer = new BufferedWriter(
new FileWriter(file));
for (String line : testList.subList(start, testList.size())) {
writer.write(line + '\n');
}
writer.flush();
writer.close();
} catch (IOException e) {
Log.e(LOG_TAG, "failed to write test list", e);
}
}
}

View File

@@ -1,116 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.net.Uri;
import android.util.Log;
import android.webkit.MockGeolocation;
import android.webkit.WebStorage;
import java.io.File;
/**
* A class that is registered as JS interface for webview in LayoutTestExecutor
*/
public class LayoutTestController {
private static final String LOG_TAG = "LayoutTestController";
LayoutTestsExecutor mLayoutTestsExecutor;
public LayoutTestController(LayoutTestsExecutor layoutTestsExecutor) {
mLayoutTestsExecutor = layoutTestsExecutor;
}
public void clearAllDatabases() {
Log.i(LOG_TAG, "clearAllDatabases() called");
WebStorage.getInstance().deleteAllData();
}
public void dumpAsText() {
dumpAsText(false);
}
public void dumpAsText(boolean enablePixelTest) {
mLayoutTestsExecutor.dumpAsText(enablePixelTest);
}
public void dumpChildFramesAsText() {
mLayoutTestsExecutor.dumpChildFramesAsText();
}
public void dumpDatabaseCallbacks() {
mLayoutTestsExecutor.dumpDatabaseCallbacks();
}
public void notifyDone() {
mLayoutTestsExecutor.notifyDone();
}
public void overridePreference(String key, boolean value) {
mLayoutTestsExecutor.overridePreference(key, value);
}
public void setAppCacheMaximumSize(long size) {
Log.i(LOG_TAG, "setAppCacheMaximumSize() called with: " + size);
android.webkit.WebStorageClassic.getInstance().setAppCacheMaximumSize(size);
}
public void setCanOpenWindows() {
mLayoutTestsExecutor.setCanOpenWindows();
}
public void setDatabaseQuota(long quota) {
/** TODO: Reset this before every test! */
Log.i(LOG_TAG, "setDatabaseQuota() called with: " + quota);
WebStorage.getInstance().setQuotaForOrigin(Uri.fromFile(new File("")).toString(),
quota);
}
public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
Log.i(LOG_TAG, "setMockGeolocationPosition(): " + "latitude=" + latitude +
" longitude=" + longitude + " accuracy=" + accuracy);
mLayoutTestsExecutor.setMockGeolocationPosition(latitude, longitude, accuracy);
}
public void setMockGeolocationError(int code, String message) {
Log.i(LOG_TAG, "setMockGeolocationError(): " + "code=" + code + " message=" + message);
mLayoutTestsExecutor.setMockGeolocationError(code, message);
}
public void setGeolocationPermission(boolean allow) {
mLayoutTestsExecutor.setGeolocationPermission(allow);
}
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
// Configuration is in WebKit, so stay on WebCore thread, but go via LayoutTestsExecutor
// as we need access to the Webview.
Log.i(LOG_TAG, "setMockDeviceOrientation(" + canProvideAlpha +
", " + alpha + ", " + canProvideBeta + ", " + beta + ", " + canProvideGamma +
", " + gamma + ")");
mLayoutTestsExecutor.setMockDeviceOrientation(
canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma);
}
public void setXSSAuditorEnabled(boolean flag) {
mLayoutTestsExecutor.setXSSAuditorEnabled(flag);
}
public void waitUntilDone() {
mLayoutTestsExecutor.waitUntilDone();
}
}

View File

@@ -1,732 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.view.Window;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.HttpAuthHandler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebSettingsClassic;
import android.webkit.WebStorage;
import android.webkit.WebStorage.QuotaUpdater;
import android.webkit.WebView;
import android.webkit.WebViewClassic;
import android.webkit.WebViewClient;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This activity executes the test. It contains WebView and logic of LayoutTestController
* functions. It runs in a separate process and sends the results of running the test
* to ManagerService. The reason why is to handle crashing (test that crashes brings down
* whole process with it).
*/
public class LayoutTestsExecutor extends Activity {
private enum CurrentState {
IDLE,
RENDERING_PAGE,
WAITING_FOR_ASYNCHRONOUS_TEST,
OBTAINING_RESULT;
public boolean isRunningState() {
return this == CurrentState.RENDERING_PAGE ||
this == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
}
}
private static final String LOG_TAG = "LayoutTestsExecutor";
public static final String EXTRA_TESTS_FILE = "TestsList";
public static final String EXTRA_TEST_INDEX = "TestIndex";
private static final int MSG_ACTUAL_RESULT_OBTAINED = 0;
private static final int MSG_TEST_TIMED_OUT = 1;
private static final int DEFAULT_TIME_OUT_MS = 15 * 1000;
/** A list of tests that remain to run since last crash */
private List<String> mTestsList;
/**
* This is a number of currently running test. It is 0-based and doesn't reset after
* the crash. Initial index is passed to LayoutTestsExecuter in the intent that starts
* it.
*/
private int mCurrentTestIndex;
/** The total number of tests to run, doesn't reset after crash */
private int mTotalTestCount;
private WebView mCurrentWebView;
private String mCurrentTestRelativePath;
private String mCurrentTestUri;
private CurrentState mCurrentState = CurrentState.IDLE;
private boolean mCurrentTestTimedOut;
private AbstractResult mCurrentResult;
private AdditionalTextOutput mCurrentAdditionalTextOutput;
private LayoutTestController mLayoutTestController = new LayoutTestController(this);
private boolean mCanOpenWindows;
private boolean mDumpDatabaseCallbacks;
private EventSender mEventSender = new EventSender();
private WakeLock mScreenDimLock;
/** COMMUNICATION WITH ManagerService */
private Messenger mManagerServiceMessenger;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mManagerServiceMessenger = new Messenger(service);
startTests();
}
@Override
public void onServiceDisconnected(ComponentName name) {
/** TODO */
}
};
private final Handler mResultHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ACTUAL_RESULT_OBTAINED:
onActualResultsObtained();
break;
case MSG_TEST_TIMED_OUT:
onTestTimedOut();
break;
default:
break;
}
}
};
/** WEBVIEW CONFIGURATION */
private WebViewClient mWebViewClient = new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
/** Some tests fire up many page loads, we don't want to detect them */
if (!url.equals(mCurrentTestUri)) {
return;
}
if (mCurrentState == CurrentState.RENDERING_PAGE) {
onTestFinished();
}
}
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
String host, String realm) {
if (handler.useHttpAuthUsernamePassword() && view != null) {
String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
if (credentials != null && credentials.length == 2) {
handler.proceed(credentials[0], credentials[1]);
return;
}
}
handler.cancel();
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// We ignore SSL errors. In particular, the certificate used by the LayoutTests server
// produces an error as it lacks a CN field.
handler.proceed();
}
};
private WebChromeClient mWebChromeClient = new WebChromeClient() {
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier,
long currentQuota, long estimatedSize, long totalUsedQuota,
QuotaUpdater quotaUpdater) {
/** TODO: This should be recorded as part of the text result */
/** TODO: The quota should also probably be reset somehow for every test? */
if (mDumpDatabaseCallbacks) {
getCurrentAdditionalTextOutput().appendExceededDbQuotaMessage(url,
databaseIdentifier);
}
quotaUpdater.updateQuota(currentQuota + 5 * 1024 * 1024);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
getCurrentAdditionalTextOutput().appendJsAlert(message);
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
getCurrentAdditionalTextOutput().appendJsConfirm(message);
result.confirm();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
JsPromptResult result) {
getCurrentAdditionalTextOutput().appendJsPrompt(message, defaultValue);
result.confirm();
return true;
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
getCurrentAdditionalTextOutput().appendConsoleMessage(consoleMessage);
return true;
}
@Override
public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
Message resultMsg) {
WebView.WebViewTransport transport = (WebView.WebViewTransport)resultMsg.obj;
/** By default windows cannot be opened, so just send null back. */
WebView newWindowWebView = null;
if (mCanOpenWindows) {
/**
* We never display the new window, just create the view and allow it's content to
* execute and be recorded by the executor.
*/
newWindowWebView = createWebViewWithJavascriptInterfaces();
setupWebView(newWindowWebView);
}
transport.setWebView(newWindowWebView);
resultMsg.sendToTarget();
return true;
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
throw new RuntimeException(
"The WebCore mock used by DRT should bypass the usual permissions flow.");
}
};
/** IMPLEMENTATION */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* It detects the crash by catching all the uncaught exceptions. However, we
* still have to kill the process, because after catching the exception the
* activity remains in a strange state, where intents don't revive it.
* However, we send the message to the service to speed up the rebooting
* (we don't have to wait for time-out to kick in).
*/
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
Log.w(LOG_TAG,
"onTestCrashed(): " + mCurrentTestRelativePath + " thread=" + thread, e);
try {
Message serviceMsg =
Message.obtain(null, ManagerService.MSG_CURRENT_TEST_CRASHED);
mManagerServiceMessenger.send(serviceMsg);
} catch (RemoteException e2) {
Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e2);
}
Process.killProcess(Process.myPid());
}
});
requestWindowFeature(Window.FEATURE_PROGRESS);
Intent intent = getIntent();
mTestsList = FsUtils.loadTestListFromStorage(intent.getStringExtra(EXTRA_TESTS_FILE));
mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1);
mTotalTestCount = mCurrentTestIndex + mTestsList.size();
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester");
mScreenDimLock.acquire();
bindService(new Intent(this, ManagerService.class), mServiceConnection,
Context.BIND_AUTO_CREATE);
}
private void reset() {
WebView previousWebView = mCurrentWebView;
resetLayoutTestController();
mCurrentTestTimedOut = false;
mCurrentResult = null;
mCurrentAdditionalTextOutput = null;
mCurrentWebView = createWebViewWithJavascriptInterfaces();
// When we create the first WebView, we need to pause to wait for the WebView thread to spin
// and up and for it to register its message handlers.
if (previousWebView == null) {
try {
Thread.currentThread().sleep(1000);
} catch (Exception e) {}
}
setupWebView(mCurrentWebView);
mEventSender.reset(mCurrentWebView);
setContentView(mCurrentWebView);
if (previousWebView != null) {
Log.d(LOG_TAG + "::reset", "previousWebView != null");
previousWebView.destroy();
}
}
private static class WebViewWithJavascriptInterfaces extends WebView {
public WebViewWithJavascriptInterfaces(
Context context, Map<String, Object> javascriptInterfaces) {
super(context,
null, // attribute set
0, // default style resource ID
javascriptInterfaces,
false); // is private browsing
}
}
private WebView createWebViewWithJavascriptInterfaces() {
Map<String, Object> javascriptInterfaces = new HashMap<String, Object>();
javascriptInterfaces.put("layoutTestController", mLayoutTestController);
javascriptInterfaces.put("eventSender", mEventSender);
return new WebViewWithJavascriptInterfaces(this, javascriptInterfaces);
}
private void setupWebView(WebView webView) {
webView.setWebViewClient(mWebViewClient);
webView.setWebChromeClient(mWebChromeClient);
/**
* Setting a touch interval of -1 effectively disables the optimisation in WebView
* that stops repeated touch events flooding WebCore. The Event Sender only sends a
* single event rather than a stream of events (like what would generally happen in
* a real use of touch events in a WebView) and so if the WebView drops the event,
* the test will fail as the test expects one callback for every touch it synthesizes.
*/
WebViewClassic webViewClassic = WebViewClassic.fromWebView(webView);
webViewClassic.setTouchInterval(-1);
webViewClassic.clearCache(true);
WebSettingsClassic webViewSettings = webViewClassic.getSettings();
webViewSettings.setAppCacheEnabled(true);
webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
// Use of larger values causes unexplained AppCache database corruption.
// TODO: Investigate what's really going on here.
webViewSettings.setAppCacheMaxSize(100 * 1024 * 1024);
webViewSettings.setJavaScriptEnabled(true);
webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webViewSettings.setSupportMultipleWindows(true);
webViewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
webViewSettings.setDatabaseEnabled(true);
webViewSettings.setDatabasePath(getDir("databases", 0).getAbsolutePath());
webViewSettings.setDomStorageEnabled(true);
webViewSettings.setWorkersEnabled(false);
webViewSettings.setXSSAuditorEnabled(false);
webViewSettings.setPageCacheCapacity(0);
// This is asynchronous, but it gets processed by WebCore before it starts loading pages.
WebViewClassic.fromWebView(mCurrentWebView).setUseMockGeolocation();
WebViewClassic.fromWebView(mCurrentWebView).setUseMockDeviceOrientation();
// Must do this after setting the AppCache path.
WebStorage.getInstance().deleteAllData();
}
private void startTests() {
// This is called when the tests are started and after each crash.
// We only send the reset message in the former case.
if (mCurrentTestIndex <= 0) {
sendResetMessage();
}
if (mCurrentTestIndex == 0) {
sendFirstTestMessage();
}
runNextTest();
}
private void sendResetMessage() {
try {
Message serviceMsg = Message.obtain(null, ManagerService.MSG_RESET);
mManagerServiceMessenger.send(serviceMsg);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error sending message to manager service:", e);
}
}
private void sendFirstTestMessage() {
try {
Message serviceMsg = Message.obtain(null, ManagerService.MSG_FIRST_TEST);
Bundle bundle = new Bundle();
bundle.putString("firstTest", mTestsList.get(0));
bundle.putInt("index", mCurrentTestIndex);
serviceMsg.setData(bundle);
mManagerServiceMessenger.send(serviceMsg);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error sending message to manager service:", e);
}
}
private void runNextTest() {
assert mCurrentState == CurrentState.IDLE : "mCurrentState = " + mCurrentState.name();
if (mTestsList.isEmpty()) {
onAllTestsFinished();
return;
}
mCurrentTestRelativePath = mTestsList.remove(0);
Log.i(LOG_TAG, "runNextTest(): Start: " + mCurrentTestRelativePath +
" (" + mCurrentTestIndex + ")");
mCurrentTestUri = FileFilter.getUrl(mCurrentTestRelativePath, true).toString();
reset();
/** Start time-out countdown and the test */
mCurrentState = CurrentState.RENDERING_PAGE;
mResultHandler.sendEmptyMessageDelayed(MSG_TEST_TIMED_OUT, DEFAULT_TIME_OUT_MS);
mCurrentWebView.loadUrl(mCurrentTestUri);
}
private void onTestTimedOut() {
assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
Log.w(LOG_TAG, "onTestTimedOut(): " + mCurrentTestRelativePath);
mCurrentTestTimedOut = true;
/**
* While it is theoretically possible that the test times out because
* of webview becoming unresponsive, it is very unlikely. Therefore it's
* assumed that obtaining results (that calls various webview methods)
* will not itself hang.
*/
obtainActualResultsFromWebView();
}
private void onTestFinished() {
assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
Log.i(LOG_TAG, "onTestFinished(): " + mCurrentTestRelativePath);
mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
obtainActualResultsFromWebView();
}
private void obtainActualResultsFromWebView() {
/**
* If the result has not been set by the time the test finishes we create
* a default type of result.
*/
if (mCurrentResult == null) {
/** TODO: Default type should be RenderTreeResult. We don't support it now. */
mCurrentResult = new TextResult(mCurrentTestRelativePath);
}
mCurrentState = CurrentState.OBTAINING_RESULT;
if (mCurrentTestTimedOut) {
mCurrentResult.setDidTimeOut();
}
mCurrentResult.obtainActualResults(mCurrentWebView,
mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
}
private void onActualResultsObtained() {
assert mCurrentState == CurrentState.OBTAINING_RESULT
: "mCurrentState = " + mCurrentState.name();
Log.i(LOG_TAG, "onActualResultsObtained(): " + mCurrentTestRelativePath);
mCurrentState = CurrentState.IDLE;
reportResultToService();
mCurrentTestIndex++;
updateProgressBar();
runNextTest();
}
private void reportResultToService() {
if (mCurrentAdditionalTextOutput != null) {
mCurrentResult.setAdditionalTextOutputString(mCurrentAdditionalTextOutput.toString());
}
try {
Message serviceMsg =
Message.obtain(null, ManagerService.MSG_PROCESS_ACTUAL_RESULTS);
Bundle bundle = mCurrentResult.getBundle();
bundle.putInt("testIndex", mCurrentTestIndex);
if (!mTestsList.isEmpty()) {
bundle.putString("nextTest", mTestsList.get(0));
}
serviceMsg.setData(bundle);
mManagerServiceMessenger.send(serviceMsg);
} catch (RemoteException e) {
Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
}
}
private void updateProgressBar() {
getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
mCurrentTestIndex * Window.PROGRESS_END / mTotalTestCount);
setTitle(mCurrentTestIndex * 100 / mTotalTestCount + "% " +
"(" + mCurrentTestIndex + "/" + mTotalTestCount + ")");
}
private void onAllTestsFinished() {
mScreenDimLock.release();
try {
Message serviceMsg =
Message.obtain(null, ManagerService.MSG_ALL_TESTS_FINISHED);
mManagerServiceMessenger.send(serviceMsg);
} catch (RemoteException e) {
Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
}
unbindService(mServiceConnection);
}
private AdditionalTextOutput getCurrentAdditionalTextOutput() {
if (mCurrentAdditionalTextOutput == null) {
mCurrentAdditionalTextOutput = new AdditionalTextOutput();
}
return mCurrentAdditionalTextOutput;
}
/** LAYOUT TEST CONTROLLER */
private static final int MSG_WAIT_UNTIL_DONE = 0;
private static final int MSG_NOTIFY_DONE = 1;
private static final int MSG_DUMP_AS_TEXT = 2;
private static final int MSG_DUMP_CHILD_FRAMES_AS_TEXT = 3;
private static final int MSG_SET_CAN_OPEN_WINDOWS = 4;
private static final int MSG_DUMP_DATABASE_CALLBACKS = 5;
private static final int MSG_OVERRIDE_PREFERENCE = 6;
private static final int MSG_SET_XSS_AUDITOR_ENABLED = 7;
/** String constants for use with layoutTestController.overridePreference() */
private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
"WebKitOfflineWebApplicationCacheEnabled";
private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey";
Handler mLayoutTestControllerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
switch (msg.what) {
case MSG_DUMP_AS_TEXT:
if (mCurrentResult == null) {
mCurrentResult = new TextResult(mCurrentTestRelativePath);
}
assert mCurrentResult instanceof TextResult
: "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
break;
case MSG_DUMP_CHILD_FRAMES_AS_TEXT:
/** If dumpAsText was not called we assume that the result should be text */
if (mCurrentResult == null) {
mCurrentResult = new TextResult(mCurrentTestRelativePath);
}
assert mCurrentResult instanceof TextResult
: "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
((TextResult)mCurrentResult).setDumpChildFramesAsText(true);
break;
case MSG_DUMP_DATABASE_CALLBACKS:
mDumpDatabaseCallbacks = true;
break;
case MSG_NOTIFY_DONE:
if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) {
onTestFinished();
}
break;
case MSG_OVERRIDE_PREFERENCE:
/**
* TODO: We should look up the correct WebView for the frame which
* called the layoutTestController method. Currently, we just use the
* WebView for the main frame. EventSender suffers from the same
* problem.
*/
String key = msg.getData().getString("key");
boolean value = msg.getData().getBoolean("value");
if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
WebViewClassic.fromWebView(mCurrentWebView).getSettings().
setAppCacheEnabled(value);
} else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
// Cache the maximum possible number of pages.
WebViewClassic.fromWebView(mCurrentWebView).getSettings().
setPageCacheCapacity(Integer.MAX_VALUE);
} else {
Log.w(LOG_TAG, "LayoutTestController.overridePreference(): " +
"Unsupported preference '" + key + "'");
}
break;
case MSG_SET_CAN_OPEN_WINDOWS:
mCanOpenWindows = true;
break;
case MSG_SET_XSS_AUDITOR_ENABLED:
WebViewClassic.fromWebView(mCurrentWebView).getSettings().
setXSSAuditorEnabled(msg.arg1 == 1);
break;
case MSG_WAIT_UNTIL_DONE:
mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
break;
default:
assert false : "msg.what=" + msg.what;
break;
}
}
};
private void resetLayoutTestController() {
mCanOpenWindows = false;
mDumpDatabaseCallbacks = false;
}
public void dumpAsText(boolean enablePixelTest) {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpAsText(" + enablePixelTest + ") called");
/** TODO: Implement */
if (enablePixelTest) {
Log.w(LOG_TAG, "enablePixelTest not implemented, switching to false");
}
mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_AS_TEXT);
}
public void dumpChildFramesAsText() {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpChildFramesAsText() called");
mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT);
}
public void dumpDatabaseCallbacks() {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpDatabaseCallbacks() called");
mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_DATABASE_CALLBACKS);
}
public void notifyDone() {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called");
mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE);
}
public void overridePreference(String key, boolean value) {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": overridePreference(" + key + ", " + value +
") called");
Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_OVERRIDE_PREFERENCE);
msg.getData().putString("key", key);
msg.getData().putBoolean("value", value);
msg.sendToTarget();
}
public void setCanOpenWindows() {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called");
mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS);
}
public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
WebViewClassic.fromWebView(mCurrentWebView).setMockGeolocationPosition(latitude, longitude,
accuracy);
}
public void setMockGeolocationError(int code, String message) {
WebViewClassic.fromWebView(mCurrentWebView).setMockGeolocationError(code, message);
}
public void setGeolocationPermission(boolean allow) {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": setGeolocationPermission(" + allow +
") called");
WebViewClassic.fromWebView(mCurrentWebView).setMockGeolocationPermission(allow);
}
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": setMockDeviceOrientation(" + canProvideAlpha +
", " + alpha + ", " + canProvideBeta + ", " + beta + ", " + canProvideGamma +
", " + gamma + ")");
WebViewClassic.fromWebView(mCurrentWebView).setMockDeviceOrientation(canProvideAlpha,
alpha, canProvideBeta, beta, canProvideGamma, gamma);
}
public void setXSSAuditorEnabled(boolean flag) {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": setXSSAuditorEnabled(" + flag + ") called");
Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_XSS_AUDITOR_ENABLED);
msg.arg1 = flag ? 1 : 0;
msg.sendToTarget();
}
public void waitUntilDone() {
Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called");
mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE);
}
}

View File

@@ -1,293 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* A service that handles managing the results of tests, informing of crashes, generating
* summaries, etc.
*/
public class ManagerService extends Service {
private static final String LOG_TAG = "ManagerService";
private static final int MSG_CRASH_TIMEOUT_EXPIRED = 0;
private static final int MSG_SUMMARIZER_DONE = 1;
private static final int CRASH_TIMEOUT_MS = 20 * 1000;
/** TODO: make it a setting */
static final String RESULTS_ROOT_DIR_PATH =
Environment.getExternalStorageDirectory() + File.separator + "layout-test-results";
/** TODO: Make it a setting */
private static final List<String> EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES =
new ArrayList<String>(3);
{
EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("platform" + File.separator +
"android-v8" + File.separator);
EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("platform" + File.separator +
"android" + File.separator);
EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("");
}
/** TODO: Make these settings */
private static final String TEXT_RESULT_EXTENSION = "txt";
private static final String IMAGE_RESULT_EXTENSION = "png";
static final int MSG_PROCESS_ACTUAL_RESULTS = 0;
static final int MSG_ALL_TESTS_FINISHED = 1;
static final int MSG_FIRST_TEST = 2;
static final int MSG_CURRENT_TEST_CRASHED = 3;
static final int MSG_RESET = 4;
/**
* This handler is purely for IPC. It is used to create mMessenger
* that generates a binder returned in onBind method.
*/
private Handler mIncomingHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESET:
mSummarizer.reset();
break;
case MSG_FIRST_TEST:
Bundle bundle = msg.getData();
ensureNextTestSetup(bundle.getString("firstTest"), bundle.getInt("index"));
break;
case MSG_PROCESS_ACTUAL_RESULTS:
Log.d(LOG_TAG,"mIncomingHandler: " + msg.getData().getString("relativePath"));
onActualResultsObtained(msg.getData());
break;
case MSG_CURRENT_TEST_CRASHED:
mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
onTestCrashed();
break;
case MSG_ALL_TESTS_FINISHED:
/** We run it in a separate thread to avoid ANR */
new Thread() {
@Override
public void run() {
mSummarizer.setTestsRelativePath(mAllTestsRelativePath);
Message msg = Message.obtain(mInternalMessagesHandler,
MSG_SUMMARIZER_DONE);
mSummarizer.summarize(msg);
}
}.start();
}
}
};
private Messenger mMessenger = new Messenger(mIncomingHandler);
private Handler mInternalMessagesHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CRASH_TIMEOUT_EXPIRED:
onTestCrashed();
break;
case MSG_SUMMARIZER_DONE:
Intent intent = new Intent(ManagerService.this, TestsListActivity.class);
intent.setAction(Intent.ACTION_SHUTDOWN);
/** This flag is needed because we send the intent from the service */
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
}
}
};
private Summarizer mSummarizer;
private String mCurrentlyRunningTest;
private int mCurrentlyRunningTestIndex;
/**
* These are implementation details of getExpectedResultPath() used to reduce the number
* of requests required to the host server.
*/
private String mLastExpectedResultPathRequested;
private String mLastExpectedResultPathFetched;
private String mAllTestsRelativePath;
@Override
public void onCreate() {
super.onCreate();
mSummarizer = new Summarizer(RESULTS_ROOT_DIR_PATH, getApplicationContext());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mAllTestsRelativePath = intent.getStringExtra("path");
assert mAllTestsRelativePath != null;
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
private void onActualResultsObtained(Bundle bundle) {
mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
ensureNextTestSetup(bundle.getString("nextTest"), bundle.getInt("testIndex") + 1);
AbstractResult results =
AbstractResult.TestType.valueOf(bundle.getString("type")).createResult(bundle);
Log.i(LOG_TAG, "onActualResultObtained: " + results.getRelativePath());
handleResults(results);
}
private void ensureNextTestSetup(String nextTest, int index) {
if (nextTest == null) {
Log.w(LOG_TAG, "ensureNextTestSetup(): nextTest=null");
return;
}
mCurrentlyRunningTest = nextTest;
mCurrentlyRunningTestIndex = index;
mInternalMessagesHandler.sendEmptyMessageDelayed(MSG_CRASH_TIMEOUT_EXPIRED, CRASH_TIMEOUT_MS);
}
/**
* This sends an intent to TestsListActivity to restart LayoutTestsExecutor.
* The more detailed description of the flow is in the comment of onNewIntent
* method in TestsListActivity.
*/
private void onTestCrashed() {
handleResults(new CrashedDummyResult(mCurrentlyRunningTest));
Log.w(LOG_TAG, "onTestCrashed(): " + mCurrentlyRunningTest +
" (" + mCurrentlyRunningTestIndex + ")");
Intent intent = new Intent(this, TestsListActivity.class);
intent.setAction(Intent.ACTION_REBOOT);
/** This flag is needed because we send the intent from the service */
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("crashedTestIndex", mCurrentlyRunningTestIndex);
startActivity(intent);
}
private void handleResults(AbstractResult results) {
String relativePath = results.getRelativePath();
results.setExpectedTextResult(getExpectedTextResult(relativePath));
results.setExpectedTextResultPath(getExpectedTextResultPath(relativePath));
results.setExpectedImageResult(getExpectedImageResult(relativePath));
results.setExpectedImageResultPath(getExpectedImageResultPath(relativePath));
dumpActualTextResult(results);
dumpActualImageResult(results);
mSummarizer.appendTest(results);
}
private void dumpActualTextResult(AbstractResult result) {
String testPath = result.getRelativePath();
String actualTextResult = result.getActualTextResult();
if (actualTextResult == null) {
return;
}
String resultPath = FileFilter.setPathEnding(testPath, "-actual." + TEXT_RESULT_EXTENSION);
FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
actualTextResult.getBytes(), false);
}
private void dumpActualImageResult(AbstractResult result) {
String testPath = result.getRelativePath();
byte[] actualImageResult = result.getActualImageResult();
if (actualImageResult == null) {
return;
}
String resultPath = FileFilter.setPathEnding(testPath,
"-actual." + IMAGE_RESULT_EXTENSION);
FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
actualImageResult, false);
}
public String getExpectedTextResult(String relativePath) {
byte[] result = getExpectedResult(relativePath, TEXT_RESULT_EXTENSION);
if (result != null) {
return new String(result);
}
return null;
}
public byte[] getExpectedImageResult(String relativePath) {
return getExpectedResult(relativePath, IMAGE_RESULT_EXTENSION);
}
private byte[] getExpectedResult(String relativePath, String extension) {
String originalRelativePath =
FileFilter.setPathEnding(relativePath, "-expected." + extension);
mLastExpectedResultPathRequested = originalRelativePath;
byte[] bytes = null;
List<String> locations = EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES;
int size = EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.size();
for (int i = 0; bytes == null && i < size; i++) {
relativePath = locations.get(i) + originalRelativePath;
bytes = FsUtils.readDataFromUrl(FileFilter.getUrl(relativePath, false));
}
mLastExpectedResultPathFetched = bytes == null ? null : relativePath;
return bytes;
}
private String getExpectedTextResultPath(String relativePath) {
return getExpectedResultPath(relativePath, TEXT_RESULT_EXTENSION);
}
private String getExpectedImageResultPath(String relativePath) {
return getExpectedResultPath(relativePath, IMAGE_RESULT_EXTENSION);
}
private String getExpectedResultPath(String relativePath, String extension) {
String originalRelativePath =
FileFilter.setPathEnding(relativePath, "-expected." + extension);
if (!originalRelativePath.equals(mLastExpectedResultPathRequested)) {
getExpectedResult(relativePath, extension);
}
return mLastExpectedResultPathFetched;
}
}

View File

@@ -1,578 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Build;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import com.android.dumprendertree2.forwarder.ForwarderManager;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A class that collects information about tests that ran and can create HTML
* files with summaries and easy navigation.
*/
public class Summarizer {
private static final String LOG_TAG = "Summarizer";
private static final String CSS =
"<style type=\"text/css\">" +
"* {" +
" font-family: Verdana;" +
" border: 0;" +
" margin: 0;" +
" padding: 0;}" +
"body {" +
" margin: 10px;}" +
"h1 {" +
" font-size: 24px;" +
" margin: 4px 0 4px 0;}" +
"h2 {" +
" font-size:18px;" +
" text-transform: uppercase;" +
" margin: 20px 0 3px 0;}" +
"h3, h3 a {" +
" font-size: 14px;" +
" color: black;" +
" text-decoration: none;" +
" margin-top: 4px;" +
" margin-bottom: 2px;}" +
"h3 a span.path {" +
" text-decoration: underline;}" +
"h3 span.tri {" +
" text-decoration: none;" +
" float: left;" +
" width: 20px;}" +
"h3 span.sqr {" +
" text-decoration: none;" +
" float: left;" +
" width: 20px;}" +
"h3 span.sqr_pass {" +
" color: #8ee100;}" +
"h3 span.sqr_fail {" +
" color: #c30000;}" +
"span.source {" +
" display: block;" +
" font-size: 10px;" +
" color: #888;" +
" margin-left: 20px;" +
" margin-bottom: 1px;}" +
"span.source a {" +
" font-size: 10px;" +
" color: #888;}" +
"h3 img {" +
" width: 8px;" +
" margin-right: 4px;}" +
"div.diff {" +
" margin-bottom: 25px;}" +
"div.diff a {" +
" font-size: 12px;" +
" color: #888;}" +
"table.visual_diff {" +
" border-bottom: 0px solid;" +
" border-collapse: collapse;" +
" width: 100%;" +
" margin-bottom: 2px;}" +
"table.visual_diff tr.headers td {" +
" border-bottom: 1px solid;" +
" border-top: 0;" +
" padding-bottom: 3px;}" +
"table.visual_diff tr.results td {" +
" border-top: 1px dashed;" +
" border-right: 1px solid;" +
" font-size: 15px;" +
" vertical-align: top;}" +
"table.visual_diff tr.results td.line_count {" +
" background-color:#aaa;" +
" min-width:20px;" +
" text-align: right;" +
" border-right: 1px solid;" +
" border-left: 1px solid;" +
" padding: 2px 1px 2px 0px;}" +
"table.visual_diff tr.results td.line {" +
" padding: 2px 0px 2px 4px;" +
" border-right: 1px solid;" +
" width: 49.8%;}" +
"table.visual_diff tr.footers td {" +
" border-top: 1px solid;" +
" border-bottom: 0;}" +
"table.visual_diff tr td.space {" +
" border: 0;" +
" width: 0.4%}" +
"div.space {" +
" margin-top:4px;}" +
"span.eql {" +
" background-color: #f3f3f3;}" +
"span.del {" +
" background-color: #ff8888; }" +
"span.ins {" +
" background-color: #88ff88; }" +
"table.summary {" +
" border: 1px solid black;" +
" margin-top: 20px;}" +
"table.summary td {" +
" padding: 3px;}" +
"span.listItem {" +
" font-size: 11px;" +
" font-weight: normal;" +
" text-transform: uppercase;" +
" padding: 3px;" +
" -webkit-border-radius: 4px;}" +
"span." + AbstractResult.ResultCode.RESULTS_DIFFER.name() + "{" +
" background-color: #ccc;" +
" color: black;}" +
"span." + AbstractResult.ResultCode.NO_EXPECTED_RESULT.name() + "{" +
" background-color: #a700e4;" +
" color: #fff;}" +
"span.timed_out {" +
" background-color: #f3cb00;" +
" color: black;}" +
"span.crashed {" +
" background-color: #c30000;" +
" color: #fff;}" +
"span.noLtc {" +
" background-color: #944000;" +
" color: #fff;}" +
"span.noEventSender {" +
" background-color: #815600;" +
" color: #fff;}" +
"</style>";
private static final String SCRIPT =
"<script type=\"text/javascript\">" +
" function toggleDisplay(id) {" +
" element = document.getElementById(id);" +
" triangle = document.getElementById('tri.' + id);" +
" if (element.style.display == 'none') {" +
" element.style.display = 'inline';" +
" triangle.innerHTML = '&#x25bc; ';" +
" } else {" +
" element.style.display = 'none';" +
" triangle.innerHTML = '&#x25b6; ';" +
" }" +
" }" +
"</script>";
/** TODO: Make it a setting */
private static final String HTML_DETAILS_RELATIVE_PATH = "details.html";
private static final String TXT_SUMMARY_RELATIVE_PATH = "summary.txt";
private static final int RESULTS_PER_DUMP = 500;
private static final int RESULTS_PER_DB_ACCESS = 50;
private int mCrashedTestsCount = 0;
private List<AbstractResult> mUnexpectedFailures = new ArrayList<AbstractResult>();
private List<AbstractResult> mExpectedFailures = new ArrayList<AbstractResult>();
private List<AbstractResult> mExpectedPasses = new ArrayList<AbstractResult>();
private List<AbstractResult> mUnexpectedPasses = new ArrayList<AbstractResult>();
private Cursor mUnexpectedFailuresCursor;
private Cursor mExpectedFailuresCursor;
private Cursor mUnexpectedPassesCursor;
private Cursor mExpectedPassesCursor;
private FileFilter mFileFilter;
private String mResultsRootDirPath;
private String mTestsRelativePath;
private Date mDate;
private int mResultsSinceLastHtmlDump = 0;
private int mResultsSinceLastDbAccess = 0;
private SummarizerDBHelper mDbHelper;
public Summarizer(String resultsRootDirPath, Context context) {
mFileFilter = new FileFilter();
mResultsRootDirPath = resultsRootDirPath;
/**
* We don't run the database I/O in a separate thread to avoid consumer/producer problem
* and to simplify code.
*/
mDbHelper = new SummarizerDBHelper(context);
mDbHelper.open();
}
public static URI getDetailsUri() {
return new File(ManagerService.RESULTS_ROOT_DIR_PATH + File.separator +
HTML_DETAILS_RELATIVE_PATH).toURI();
}
public void appendTest(AbstractResult result) {
String relativePath = result.getRelativePath();
if (result.didCrash()) {
mCrashedTestsCount++;
}
if (result.didPass()) {
result.clearResults();
if (mFileFilter.isFail(relativePath)) {
mUnexpectedPasses.add(result);
} else {
mExpectedPasses.add(result);
}
} else {
if (mFileFilter.isFail(relativePath)) {
mExpectedFailures.add(result);
} else {
mUnexpectedFailures.add(result);
}
}
if (++mResultsSinceLastDbAccess == RESULTS_PER_DB_ACCESS) {
persistLists();
clearLists();
}
}
private void clearLists() {
mUnexpectedFailures.clear();
mExpectedFailures.clear();
mUnexpectedPasses.clear();
mExpectedPasses.clear();
}
private void persistLists() {
persistListToTable(mUnexpectedFailures, SummarizerDBHelper.UNEXPECTED_FAILURES_TABLE);
persistListToTable(mExpectedFailures, SummarizerDBHelper.EXPECTED_FAILURES_TABLE);
persistListToTable(mUnexpectedPasses, SummarizerDBHelper.UNEXPECTED_PASSES_TABLE);
persistListToTable(mExpectedPasses, SummarizerDBHelper.EXPECTED_PASSES_TABLE);
mResultsSinceLastDbAccess = 0;
}
private void persistListToTable(List<AbstractResult> results, String table) {
for (AbstractResult abstractResult : results) {
mDbHelper.insertAbstractResult(abstractResult, table);
}
}
public void setTestsRelativePath(String testsRelativePath) {
mTestsRelativePath = testsRelativePath;
}
public void summarize(Message onFinishMessage) {
persistLists();
clearLists();
mUnexpectedFailuresCursor =
mDbHelper.getAbstractResults(SummarizerDBHelper.UNEXPECTED_FAILURES_TABLE);
mUnexpectedPassesCursor =
mDbHelper.getAbstractResults(SummarizerDBHelper.UNEXPECTED_PASSES_TABLE);
mExpectedFailuresCursor =
mDbHelper.getAbstractResults(SummarizerDBHelper.EXPECTED_FAILURES_TABLE);
mExpectedPassesCursor =
mDbHelper.getAbstractResults(SummarizerDBHelper.EXPECTED_PASSES_TABLE);
String webKitRevision = getWebKitRevision();
createHtmlDetails(webKitRevision);
createTxtSummary(webKitRevision);
clearLists();
mUnexpectedFailuresCursor.close();
mUnexpectedPassesCursor.close();
mExpectedFailuresCursor.close();
mExpectedPassesCursor.close();
onFinishMessage.sendToTarget();
}
public void reset() {
mCrashedTestsCount = 0;
clearLists();
mDbHelper.reset();
mDate = new Date();
}
private void dumpHtmlToFile(StringBuilder html, boolean append) {
FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DETAILS_RELATIVE_PATH),
html.toString().getBytes(), append);
html.setLength(0);
mResultsSinceLastHtmlDump = 0;
}
private void createTxtSummary(String webKitRevision) {
StringBuilder txt = new StringBuilder();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
txt.append("Path: " + mTestsRelativePath + "\n");
txt.append("Date: " + dateFormat.format(mDate) + "\n");
txt.append("Build fingerprint: " + Build.FINGERPRINT + "\n");
txt.append("WebKit version: " + getWebKitVersionFromUserAgentString() + "\n");
txt.append("WebKit revision: " + webKitRevision + "\n");
txt.append("TOTAL: " + getTotalTestCount() + "\n");
txt.append("CRASHED (among all tests): " + mCrashedTestsCount + "\n");
txt.append("UNEXPECTED FAILURES: " + mUnexpectedFailuresCursor.getCount() + "\n");
txt.append("UNEXPECTED PASSES: " + mUnexpectedPassesCursor.getCount() + "\n");
txt.append("EXPECTED FAILURES: " + mExpectedFailuresCursor.getCount() + "\n");
txt.append("EXPECTED PASSES: " + mExpectedPassesCursor.getCount() + "\n");
FsUtils.writeDataToStorage(new File(mResultsRootDirPath, TXT_SUMMARY_RELATIVE_PATH),
txt.toString().getBytes(), false);
}
private void createHtmlDetails(String webKitRevision) {
StringBuilder html = new StringBuilder();
html.append("<html><head>");
html.append(CSS);
html.append(SCRIPT);
html.append("</head><body>");
createTopSummaryTable(webKitRevision, html);
dumpHtmlToFile(html, false);
createResultsList(html, "Unexpected failures", mUnexpectedFailuresCursor);
createResultsList(html, "Unexpected passes", mUnexpectedPassesCursor);
createResultsList(html, "Expected failures", mExpectedFailuresCursor);
createResultsList(html, "Expected passes", mExpectedPassesCursor);
html.append("</body></html>");
dumpHtmlToFile(html, true);
}
private int getTotalTestCount() {
return mUnexpectedFailuresCursor.getCount() +
mUnexpectedPassesCursor.getCount() +
mExpectedPassesCursor.getCount() +
mExpectedFailuresCursor.getCount();
}
private String getWebKitVersionFromUserAgentString() {
Resources resources = new Resources(new AssetManager(), new DisplayMetrics(),
new Configuration());
String userAgent =
resources.getString(com.android.internal.R.string.web_user_agent);
Matcher matcher = Pattern.compile("AppleWebKit/([0-9]+?\\.[0-9])").matcher(userAgent);
if (matcher.find()) {
return matcher.group(1);
}
return "unknown";
}
private String getWebKitRevision() {
URL url = null;
try {
url = new URL(ForwarderManager.getHostSchemePort(false) + "ThirdPartyProject.prop");
} catch (MalformedURLException e) {
assert false;
}
String thirdPartyProjectContents = new String(FsUtils.readDataFromUrl(url));
Matcher matcher = Pattern.compile("^version=([0-9]+)", Pattern.MULTILINE).matcher(
thirdPartyProjectContents);
if (matcher.find()) {
return matcher.group(1);
}
return "unknown";
}
private void createTopSummaryTable(String webKitRevision, StringBuilder html) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
html.append("<h1>" + "Layout tests' results for: " +
(mTestsRelativePath.equals("") ? "all tests" : mTestsRelativePath) + "</h1>");
html.append("<h3>" + "Date: " + dateFormat.format(new Date()) + "</h3>");
html.append("<h3>" + "Build fingerprint: " + Build.FINGERPRINT + "</h3>");
html.append("<h3>" + "WebKit version: " + getWebKitVersionFromUserAgentString() + "</h3>");
html.append("<h3>" + "WebKit revision: ");
html.append("<a href=\"http://trac.webkit.org/browser/trunk?rev=" + webKitRevision +
"\" target=\"_blank\"><span class=\"path\">" + webKitRevision + "</span></a>");
html.append("</h3>");
html.append("<table class=\"summary\">");
createSummaryTableRow(html, "TOTAL", getTotalTestCount());
createSummaryTableRow(html, "CRASHED (among all tests)", mCrashedTestsCount);
createSummaryTableRow(html, "UNEXPECTED FAILURES", mUnexpectedFailuresCursor.getCount());
createSummaryTableRow(html, "UNEXPECTED PASSES", mUnexpectedPassesCursor.getCount());
createSummaryTableRow(html, "EXPECTED FAILURES", mExpectedFailuresCursor.getCount());
createSummaryTableRow(html, "EXPECTED PASSES", mExpectedPassesCursor.getCount());
html.append("</table>");
}
private void createSummaryTableRow(StringBuilder html, String caption, int size) {
html.append("<tr>");
html.append(" <td>" + caption + "</td>");
html.append(" <td>" + size + "</td>");
html.append("</tr>");
}
private void createResultsList(
StringBuilder html, String title, Cursor cursor) {
String relativePath;
String id = "";
AbstractResult.ResultCode resultCode;
html.append("<h2>" + title + " [" + cursor.getCount() + "]</h2>");
if (!cursor.moveToFirst()) {
return;
}
AbstractResult result;
do {
result = SummarizerDBHelper.getAbstractResult(cursor);
relativePath = result.getRelativePath();
resultCode = result.getResultCode();
html.append("<h3>");
/**
* Technically, two different paths could end up being the same, because
* ':' is a valid character in a path. However, it is probably not going
* to cause any problems in this case
*/
id = relativePath.replace(File.separator, ":");
/** Write the test name */
if (resultCode == AbstractResult.ResultCode.RESULTS_DIFFER) {
html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');");
html.append("return false;\">");
html.append("<span class=\"tri\" id=\"tri." + id + "\">&#x25b6; </span>");
html.append("<span class=\"path\">" + relativePath + "</span>");
html.append("</a>");
} else {
html.append("<a href=\"" + getViewSourceUrl(result.getRelativePath()).toString() + "\"");
html.append(" target=\"_blank\">");
html.append("<span class=\"sqr sqr_" + (result.didPass() ? "pass" : "fail"));
html.append("\">&#x25a0; </span>");
html.append("<span class=\"path\">" + result.getRelativePath() + "</span>");
html.append("</a>");
}
if (!result.didPass()) {
appendTags(html, result);
}
html.append("</h3>");
appendExpectedResultsSources(result, html);
if (resultCode == AbstractResult.ResultCode.RESULTS_DIFFER) {
html.append("<div class=\"diff\" style=\"display: none;\" id=\"" + id + "\">");
html.append(result.getDiffAsHtml());
html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');");
html.append("return false;\">Hide</a>");
html.append(" | ");
html.append("<a href=\"" + getViewSourceUrl(relativePath).toString() + "\"");
html.append(" target=\"_blank\">Show source</a>");
html.append("</div>");
}
html.append("<div class=\"space\"></div>");
if (++mResultsSinceLastHtmlDump == RESULTS_PER_DUMP) {
dumpHtmlToFile(html, true);
}
cursor.moveToNext();
} while (!cursor.isAfterLast());
}
private void appendTags(StringBuilder html, AbstractResult result) {
/** Tag tests which crash, time out or where results don't match */
if (result.didCrash()) {
html.append(" <span class=\"listItem crashed\">Crashed</span>");
} else {
if (result.didTimeOut()) {
html.append(" <span class=\"listItem timed_out\">Timed out</span>");
}
AbstractResult.ResultCode resultCode = result.getResultCode();
if (resultCode != AbstractResult.ResultCode.RESULTS_MATCH) {
html.append(" <span class=\"listItem " + resultCode.name() + "\">");
html.append(resultCode.toString());
html.append("</span>");
}
}
/** Detect missing LTC function */
String additionalTextOutputString = result.getAdditionalTextOutputString();
if (additionalTextOutputString != null &&
additionalTextOutputString.contains("com.android.dumprendertree") &&
additionalTextOutputString.contains("has no method")) {
if (additionalTextOutputString.contains("LayoutTestController")) {
html.append(" <span class=\"listItem noLtc\">LTC function missing</span>");
}
if (additionalTextOutputString.contains("EventSender")) {
html.append(" <span class=\"listItem noEventSender\">");
html.append("ES function missing</span>");
}
}
}
private static final void appendExpectedResultsSources(AbstractResult result,
StringBuilder html) {
String textSource = result.getExpectedTextResultPath();
String imageSource = result.getExpectedImageResultPath();
if (result.didCrash()) {
html.append("<span class=\"source\">Did not look for expected results</span>");
return;
}
if (textSource == null) {
// Show if a text result is missing. We may want to revisit this decision when we add
// support for image results.
html.append("<span class=\"source\">Expected textual result missing</span>");
} else {
html.append("<span class=\"source\">Expected textual result from: ");
html.append("<a href=\"" + ForwarderManager.getHostSchemePort(false) + "LayoutTests/" +
textSource + "\"");
html.append(" target=\"_blank\">");
html.append(textSource + "</a></span>");
}
if (imageSource != null) {
html.append("<span class=\"source\">Expected image result from: ");
html.append("<a href=\"" + ForwarderManager.getHostSchemePort(false) + "LayoutTests/" +
imageSource + "\"");
html.append(" target=\"_blank\">");
html.append(imageSource + "</a></span>");
}
}
private static final URL getViewSourceUrl(String relativePath) {
URL url = null;
try {
url = new URL("http", "localhost", ForwarderManager.HTTP_PORT,
"/Tools/DumpRenderTree/android/view_source.php?src=" +
relativePath);
} catch (MalformedURLException e) {
assert false : "relativePath=" + relativePath;
}
return url;
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.HashSet;
import java.util.Set;
/**
* A basic class that wraps database accesses inside itself and provides functionality to
* store and retrieve AbstractResults.
*/
public class SummarizerDBHelper {
private static final String KEY_ID = "id";
private static final String KEY_PATH = "path";
private static final String KEY_BYTES = "bytes";
private static final String DATABASE_NAME = "SummarizerDB";
private static final int DATABASE_VERSION = 1;
static final String EXPECTED_FAILURES_TABLE = "expectedFailures";
static final String UNEXPECTED_FAILURES_TABLE = "unexpectedFailures";
static final String EXPECTED_PASSES_TABLE = "expextedPasses";
static final String UNEXPECTED_PASSES_TABLE = "unexpextedPasses";
private static final Set<String> TABLES_NAMES = new HashSet<String>();
{
TABLES_NAMES.add(EXPECTED_FAILURES_TABLE);
TABLES_NAMES.add(EXPECTED_PASSES_TABLE);
TABLES_NAMES.add(UNEXPECTED_FAILURES_TABLE);
TABLES_NAMES.add(UNEXPECTED_PASSES_TABLE);
}
private static final void createTables(SQLiteDatabase db) {
String cmd;
for (String tableName : TABLES_NAMES) {
cmd = "create table " + tableName + " ("
+ KEY_ID + " integer primary key autoincrement, "
+ KEY_PATH + " text not null, "
+ KEY_BYTES + " blob not null);";
db.execSQL(cmd);
}
}
private static final void dropTables(SQLiteDatabase db) {
for (String tableName : TABLES_NAMES) {
db.execSQL("DROP TABLE IF EXISTS " + tableName);
}
}
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
dropTables(db);
createTables(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
/** NOOP for now, because we will never upgrade the db */
}
public void reset(SQLiteDatabase db) {
dropTables(db);
createTables(db);
}
}
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
private final Context mContext;
public SummarizerDBHelper(Context ctx) {
mContext = ctx;
mDbHelper = new DatabaseHelper(mContext);
}
public void reset() {
mDbHelper.reset(this.mDb);
}
public void open() throws SQLException {
mDb = mDbHelper.getWritableDatabase();
}
public void close() {
mDbHelper.close();
}
public void insertAbstractResult(AbstractResult result, String table) {
ContentValues cv = new ContentValues();
cv.put(KEY_PATH, result.getRelativePath());
cv.put(KEY_BYTES, result.getBytes());
mDb.insert(table, null, cv);
}
public Cursor getAbstractResults(String table) throws SQLException {
return mDb.query(false, table, new String[] {KEY_BYTES}, null, null, null, null,
KEY_PATH + " ASC", null);
}
public static AbstractResult getAbstractResult(Cursor cursor) {
return AbstractResult.create(cursor.getBlob(cursor.getColumnIndex(KEY_BYTES)));
}
}

View File

@@ -1,203 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import com.android.dumprendertree2.scriptsupport.OnEverythingFinishedCallback;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Gravity;
import android.view.Window;
import android.webkit.WebView;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
/**
* An Activity that generates a list of tests and sends the intent to
* LayoutTestsExecuter to run them. It also restarts the LayoutTestsExecuter
* after it crashes.
*/
public class TestsListActivity extends Activity {
private static final int MSG_TEST_LIST_PRELOADER_DONE = 0;
/** Constants for adding extras to an intent */
public static final String EXTRA_TEST_PATH = "TestPath";
private static ProgressDialog sProgressDialog;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TEST_LIST_PRELOADER_DONE:
sProgressDialog.dismiss();
mTestsList = (ArrayList<String>)msg.obj;
mTotalTestCount = mTestsList.size();
restartExecutor(0);
break;
}
}
};
private ArrayList<String> mTestsList;
private int mTotalTestCount;
private OnEverythingFinishedCallback mOnEverythingFinishedCallback;
private boolean mEverythingFinished;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/** Prepare the progress dialog */
sProgressDialog = new ProgressDialog(TestsListActivity.this);
sProgressDialog.setCancelable(false);
sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
sProgressDialog.setTitle(R.string.dialog_progress_title);
sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
requestWindowFeature(Window.FEATURE_PROGRESS);
Intent intent = getIntent();
if (!intent.getAction().equals(Intent.ACTION_RUN)) {
return;
}
String path = intent.getStringExtra(EXTRA_TEST_PATH);
sProgressDialog.show();
Message doneMsg = Message.obtain(mHandler, MSG_TEST_LIST_PRELOADER_DONE);
Intent serviceIntent = new Intent(this, ManagerService.class);
serviceIntent.putExtra("path", path);
startService(serviceIntent);
new TestsListPreloaderThread(path, doneMsg).start();
}
@Override
protected void onNewIntent(Intent intent) {
if (intent.getAction().equals(Intent.ACTION_REBOOT)) {
onCrashIntent(intent);
} else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
onEverythingFinishedIntent(intent);
}
}
/**
* This method handles an intent that comes from ManageService when crash is detected.
* The intent contains an index in mTestsList of the test that crashed. TestsListActivity
* restarts the LayoutTestsExecutor from the following test in mTestsList, by sending
* an intent to it. This new intent contains a list of remaining tests to run,
* total count of all tests, and the index of the first test to run after restarting.
* LayoutTestExecutor runs then as usual, sending reports to ManagerService. If it
* detects the crash it sends a new intent and the flow repeats.
*/
private void onCrashIntent(Intent intent) {
int nextTestToRun = intent.getIntExtra("crashedTestIndex", -1) + 1;
if (nextTestToRun > 0 && nextTestToRun <= mTotalTestCount) {
restartExecutor(nextTestToRun);
}
}
public void registerOnEverythingFinishedCallback(OnEverythingFinishedCallback callback) {
mOnEverythingFinishedCallback = callback;
if (mEverythingFinished) {
mOnEverythingFinishedCallback.onFinished();
}
}
private void onEverythingFinishedIntent(Intent intent) {
Toast toast = Toast.makeText(this,
"All tests finished.\nPress back key to return to the tests' list.",
Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, -40, 0);
toast.show();
/** Show the details to the user */
WebView webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setEnableSmoothTransition(true);
/** This enables double-tap to zoom */
webView.getSettings().setUseWideViewPort(true);
setContentView(webView);
webView.loadUrl(Summarizer.getDetailsUri().toString());
mEverythingFinished = true;
if (mOnEverythingFinishedCallback != null) {
mOnEverythingFinishedCallback.onFinished();
}
}
/**
* This, together with android:configChanges="orientation" in manifest file, prevents
* the activity from restarting on orientation change.
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putStringArrayList("testsList", mTestsList);
outState.putInt("totalCount", mTotalTestCount);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mTestsList = savedInstanceState.getStringArrayList("testsList");
mTotalTestCount = savedInstanceState.getInt("totalCount");
}
/**
* (Re)starts the executer activity from the given test number (inclusive, 0-based).
* This number is an index in mTestsList, not the sublist passed in the intent.
*
* @param startFrom
* test index in mTestsList to start the tests from (inclusive, 0-based)
*/
private void restartExecutor(int startFrom) {
Intent intent = new Intent();
intent.setClass(this, LayoutTestsExecutor.class);
intent.setAction(Intent.ACTION_RUN);
if (startFrom < mTotalTestCount) {
File testListFile = new File(getExternalFilesDir(null), "test_list.txt");
FsUtils.saveTestListToStorage(testListFile, startFrom, mTestsList);
intent.putExtra(LayoutTestsExecutor.EXTRA_TESTS_FILE, testListFile.getAbsolutePath());
intent.putExtra(LayoutTestsExecutor.EXTRA_TEST_INDEX, startFrom);
} else {
intent.putExtra(LayoutTestsExecutor.EXTRA_TESTS_FILE, "");
}
startActivity(intent);
}
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.os.Environment;
import android.os.Message;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* A Thread that is responsible for generating a lists of tests to run.
*/
public class TestsListPreloaderThread extends Thread {
private static final String LOG_TAG = "TestsListPreloaderThread";
/** A list containing relative paths of tests to run */
private ArrayList<String> mTestsList = new ArrayList<String>();
private FileFilter mFileFilter;
/**
* A relative path to the directory with the tests we want to run or particular test.
* Used up to and including preloadTests().
*/
private String mRelativePath;
private Message mDoneMsg;
/**
* The given path must be relative to the root dir.
*
* @param path
* @param doneMsg
*/
public TestsListPreloaderThread(String path, Message doneMsg) {
mRelativePath = path;
mDoneMsg = doneMsg;
}
@Override
public void run() {
mFileFilter = new FileFilter();
if (FileFilter.isTestFile(mRelativePath)) {
mTestsList.add(mRelativePath);
} else {
loadTestsFromUrl(mRelativePath);
}
mDoneMsg.obj = mTestsList;
mDoneMsg.sendToTarget();
}
/**
* Loads all the tests from the given directories and all the subdirectories
* into mTestsList.
*
* @param dirRelativePath
*/
private void loadTestsFromUrl(String rootRelativePath) {
LinkedList<String> directoriesList = new LinkedList<String>();
directoriesList.add(rootRelativePath);
String relativePath;
String itemName;
while (!directoriesList.isEmpty()) {
relativePath = directoriesList.removeFirst();
List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, true);
if (dirRelativePaths != null) {
for (String dirRelativePath : dirRelativePaths) {
itemName = new File(dirRelativePath).getName();
if (FileFilter.isTestDir(itemName)) {
directoriesList.add(dirRelativePath);
}
}
}
List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(relativePath, false, false);
if (testRelativePaths != null) {
for (String testRelativePath : testRelativePaths) {
itemName = new File(testRelativePath).getName();
if (FileFilter.isTestFile(itemName)) {
/** We choose to skip all the tests that are expected to crash. */
if (!mFileFilter.isCrash(testRelativePath)) {
mTestsList.add(testRelativePath);
} else {
/**
* TODO: Summarizer is now in service - figure out how to send the info.
* Previously: mSummarizer.addSkippedTest(relativePath);
*/
}
}
}
}
}
}
}

View File

@@ -1,257 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.webkit.WebView;
import android.webkit.WebViewClassic;
import name.fraser.neil.plaintext.diff_match_patch;
import java.util.LinkedList;
/**
* A result object for which the expected output is text. It does not have an image
* expected result.
*
* <p>Created if layoutTestController.dumpAsText() was called.
*/
public class TextResult extends AbstractResult {
private static final int MSG_DOCUMENT_AS_TEXT = 0;
private String mExpectedResult;
private String mExpectedResultPath;
private String mActualResult;
private String mRelativePath;
private boolean mDidTimeOut;
private ResultCode mResultCode;
transient private Message mResultObtainedMsg;
private boolean mDumpChildFramesAsText;
transient private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_DOCUMENT_AS_TEXT) {
mActualResult = (String)msg.obj;
mResultObtainedMsg.sendToTarget();
}
}
};
public TextResult(String relativePath) {
mRelativePath = relativePath;
}
public void setDumpChildFramesAsText(boolean dumpChildFramesAsText) {
mDumpChildFramesAsText = dumpChildFramesAsText;
}
/**
* Used to recreate the Result when received by the service.
*
* @param bundle
* bundle with data used to recreate the result
*/
public TextResult(Bundle bundle) {
mExpectedResult = bundle.getString("expectedTextualResult");
mExpectedResultPath = bundle.getString("expectedTextualResultPath");
mActualResult = bundle.getString("actualTextualResult");
setAdditionalTextOutputString(bundle.getString("additionalTextOutputString"));
mRelativePath = bundle.getString("relativePath");
mDidTimeOut = bundle.getBoolean("didTimeOut");
}
@Override
public void clearResults() {
super.clearResults();
mExpectedResult = null;
mActualResult = null;
}
@Override
public ResultCode getResultCode() {
if (mResultCode == null) {
mResultCode = resultsMatch() ? AbstractResult.ResultCode.RESULTS_MATCH
: AbstractResult.ResultCode.RESULTS_DIFFER;
}
return mResultCode;
}
private boolean resultsMatch() {
assert mExpectedResult != null;
assert mActualResult != null;
// Trim leading and trailing empty lines, as other WebKit platforms do.
String leadingEmptyLines = "^\\n+";
String trailingEmptyLines = "\\n+$";
String trimmedExpectedResult = mExpectedResult.replaceFirst(leadingEmptyLines, "")
.replaceFirst(trailingEmptyLines, "");
String trimmedActualResult = mActualResult.replaceFirst(leadingEmptyLines, "")
.replaceFirst(trailingEmptyLines, "");
return trimmedExpectedResult.equals(trimmedActualResult);
}
@Override
public boolean didCrash() {
return false;
}
@Override
public boolean didTimeOut() {
return mDidTimeOut;
}
@Override
public void setDidTimeOut() {
mDidTimeOut = true;
}
@Override
public byte[] getActualImageResult() {
return null;
}
@Override
public String getActualTextResult() {
String additionalTextResultString = getAdditionalTextOutputString();
if (additionalTextResultString != null) {
return additionalTextResultString + mActualResult;
}
return mActualResult;
}
@Override
public void setExpectedImageResult(byte[] expectedResult) {
/** This method is not applicable to this type of result */
}
@Override
public void setExpectedImageResultPath(String relativePath) {
/** This method is not applicable to this type of result */
}
@Override
public String getExpectedImageResultPath() {
/** This method is not applicable to this type of result */
return null;
}
@Override
public void setExpectedTextResultPath(String relativePath) {
mExpectedResultPath = relativePath;
}
@Override
public String getExpectedTextResultPath() {
return mExpectedResultPath;
}
@Override
public void setExpectedTextResult(String expectedResult) {
// For text results, we use an empty string for the expected result when none is
// present, as other WebKit platforms do.
mExpectedResult = expectedResult == null ? "" : expectedResult;
}
@Override
public String getDiffAsHtml() {
assert mExpectedResult != null;
assert mActualResult != null;
StringBuilder html = new StringBuilder();
html.append("<table class=\"visual_diff\">");
html.append(" <tr class=\"headers\">");
html.append(" <td colspan=\"2\">Expected result:</td>");
html.append(" <td class=\"space\"></td>");
html.append(" <td colspan=\"2\">Actual result:</td>");
html.append(" </tr>");
appendDiffHtml(html);
html.append(" <tr class=\"footers\">");
html.append(" <td colspan=\"2\"></td>");
html.append(" <td class=\"space\"></td>");
html.append(" <td colspan=\"2\"></td>");
html.append(" </tr>");
html.append("</table>");
return html.toString();
}
private void appendDiffHtml(StringBuilder html) {
LinkedList<diff_match_patch.Diff> diffs =
new diff_match_patch().diff_main(mExpectedResult, mActualResult);
diffs = VisualDiffUtils.splitDiffsOnNewline(diffs);
LinkedList<String> expectedLines = new LinkedList<String>();
LinkedList<Integer> expectedLineNums = new LinkedList<Integer>();
LinkedList<String> actualLines = new LinkedList<String>();
LinkedList<Integer> actualLineNums = new LinkedList<Integer>();
VisualDiffUtils.generateExpectedResultLines(diffs, expectedLineNums, expectedLines);
VisualDiffUtils.generateActualResultLines(diffs, actualLineNums, actualLines);
// TODO: We should use a map for each line number and lines pair.
assert expectedLines.size() == expectedLineNums.size();
assert actualLines.size() == actualLineNums.size();
assert expectedLines.size() == actualLines.size();
html.append(VisualDiffUtils.getHtml(expectedLineNums, expectedLines,
actualLineNums, actualLines));
}
@Override
public TestType getType() {
return TestType.TEXT;
}
@Override
public void obtainActualResults(WebView webview, Message resultObtainedMsg) {
mResultObtainedMsg = resultObtainedMsg;
Message msg = mHandler.obtainMessage(MSG_DOCUMENT_AS_TEXT);
/**
* arg1 - should dump top frame as text
* arg2 - should dump child frames as text
*/
msg.arg1 = 1;
msg.arg2 = mDumpChildFramesAsText ? 1 : 0;
WebViewClassic.fromWebView(webview).documentAsText(msg);
}
@Override
public Bundle getBundle() {
Bundle bundle = new Bundle();
bundle.putString("expectedTextualResult", mExpectedResult);
bundle.putString("expectedTextualResultPath", mExpectedResultPath);
bundle.putString("actualTextualResult", getActualTextResult());
bundle.putString("additionalTextOutputString", getAdditionalTextOutputString());
bundle.putString("relativePath", mRelativePath);
bundle.putBoolean("didTimeOut", mDidTimeOut);
bundle.putString("type", getType().name());
return bundle;
}
@Override
public String getRelativePath() {
return mRelativePath;
}
}

View File

@@ -1,214 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2;
import name.fraser.neil.plaintext.diff_match_patch;
import java.util.LinkedList;
/**
* Helper methods fo TextResult.getDiffAsHtml()
*/
public class VisualDiffUtils {
private static final int DONT_PRINT_LINE_NUMBER = -1;
/**
* Preprocesses the list of diffs so that new line characters appear only at the end of
* diff.text
*
* @param diffs
* @return
* LinkedList of diffs where new line character appears only on the end of
* diff.text
*/
public static LinkedList<diff_match_patch.Diff> splitDiffsOnNewline(
LinkedList<diff_match_patch.Diff> diffs) {
LinkedList<diff_match_patch.Diff> newDiffs = new LinkedList<diff_match_patch.Diff>();
String[] parts;
int lengthMinusOne;
for (diff_match_patch.Diff diff : diffs) {
parts = diff.text.split("\n", -1);
if (parts.length == 1) {
newDiffs.add(diff);
continue;
}
lengthMinusOne = parts.length - 1;
for (int i = 0; i < lengthMinusOne; i++) {
newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[i] + "\n"));
}
if (!parts[lengthMinusOne].isEmpty()) {
newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[lengthMinusOne]));
}
}
return newDiffs;
}
public static void generateExpectedResultLines(LinkedList<diff_match_patch.Diff> diffs,
LinkedList<Integer> lineNums, LinkedList<String> lines) {
String delSpan = "<span class=\"del\">";
String eqlSpan = "<span class=\"eql\">";
String line = "";
int i = 1;
diff_match_patch.Diff diff;
int size = diffs.size();
boolean isLastDiff;
for (int j = 0; j < size; j++) {
diff = diffs.get(j);
isLastDiff = j == size - 1;
switch (diff.operation) {
case DELETE:
line = processDiff(diff, lineNums, lines, line, i, delSpan, isLastDiff);
if (line.equals("")) {
i++;
}
break;
case INSERT:
// If the line is currently empty and this insertion is the entire line, the
// expected line is absent, so it has no line number.
if (diff.text.endsWith("\n") || isLastDiff) {
lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++);
lines.add(line);
line = "";
}
break;
case EQUAL:
line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff);
if (line.equals("")) {
i++;
}
break;
}
}
}
public static void generateActualResultLines(LinkedList<diff_match_patch.Diff> diffs,
LinkedList<Integer> lineNums, LinkedList<String> lines) {
String insSpan = "<span class=\"ins\">";
String eqlSpan = "<span class=\"eql\">";
String line = "";
int i = 1;
diff_match_patch.Diff diff;
int size = diffs.size();
boolean isLastDiff;
for (int j = 0; j < size; j++) {
diff = diffs.get(j);
isLastDiff = j == size - 1;
switch (diff.operation) {
case INSERT:
line = processDiff(diff, lineNums, lines, line, i, insSpan, isLastDiff);
if (line.equals("")) {
i++;
}
break;
case DELETE:
// If the line is currently empty and deletion is the entire line, the
// actual line is absent, so it has no line number.
if (diff.text.endsWith("\n") || isLastDiff) {
lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++);
lines.add(line);
line = "";
}
break;
case EQUAL:
line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff);
if (line.equals("")) {
i++;
}
break;
}
}
}
/**
* Generate or append a line for a given diff and add it to given collections if necessary.
* It puts diffs in HTML spans.
*
* @param diff
* @param lineNums
* @param lines
* @param line
* @param i
* @param begSpan
* @param forceOutputLine Force the current line to be output
* @return
*/
public static String processDiff(diff_match_patch.Diff diff, LinkedList<Integer> lineNums,
LinkedList<String> lines, String line, int i, String begSpan, boolean forceOutputLine) {
String endSpan = "</span>";
String br = "&nbsp;";
if (diff.text.endsWith("\n") || forceOutputLine) {
lineNums.add(i);
/** TODO: Think of better way to replace stuff */
line += begSpan + diff.text.replace(" ", "&nbsp;&nbsp;")
+ endSpan + br;
lines.add(line);
line = "";
} else {
line += begSpan + diff.text.replace(" ", "&nbsp;&nbsp;") + endSpan;
}
return line;
}
public static String getHtml(LinkedList<Integer> lineNums1, LinkedList<String> lines1,
LinkedList<Integer> lineNums2, LinkedList<String> lines2) {
StringBuilder html = new StringBuilder();
int lineNum;
int size = lines1.size();
for (int i = 0; i < size; i++) {
html.append("<tr class=\"results\">");
html.append(" <td class=\"line_count\">");
lineNum = lineNums1.removeFirst();
if (lineNum > 0) {
html.append(lineNum);
}
html.append(" </td>");
html.append(" <td class=\"line\">");
html.append(lines1.removeFirst());
html.append(" </td>");
html.append(" <td class=\"space\"></td>");
html.append(" <td class=\"line_count\">");
lineNum = lineNums2.removeFirst();
if (lineNum > 0) {
html.append(lineNum);
}
html.append(" </td>");
html.append(" <td class=\"line\">");
html.append(lines2.removeFirst());
html.append(" </td>");
html.append("</tr>");
}
return html.toString();
}
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.forwarder;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* The utility class that can setup a socket allowing the device to communicate with remote
* machines through the machine that the device is connected to via adb.
*/
public class AdbUtils {
private static final String LOG_TAG = "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;
/**
* Creates a new socket that can be configured to serve as a transparent proxy to a
* remote machine. This can be achieved by calling configureSocket()
*
* @return a socket that can be configured to link to remote machine
* @throws IOException
*/
public static Socket createSocket() throws IOException{
return new Socket(ADB_HOST, ADB_PORT);
}
/**
* Configures the connection to serve as a transparent proxy to a remote machine.
* The given streams must belong to a socket created by createSocket().
*
* @param inputStream inputStream of the socket we want to configure
* @param outputStream outputStream of the socket we want to configure
* @param remoteAddress address of the remote machine (as you would type in a browser
* in a machine that the device is connected to via adb)
* @param remotePort port on which to connect
* @return if the configuration suceeded
* @throws IOException
*/
public static boolean configureConnection(InputStream inputStream, OutputStream outputStream,
String remoteAddress, int remotePort) throws IOException {
String cmd = "tcp:" + remotePort + ":" + remoteAddress;
cmd = String.format("%04X", cmd.length()) + cmd;
byte[] buf = new byte[ADB_RESPONSE_SIZE];
outputStream.write(cmd.getBytes());
int read = inputStream.read(buf);
if (read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) {
Log.w(LOG_TAG, "adb cmd failed.");
return false;
}
return true;
}
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.forwarder;
import android.util.Log;
import com.android.dumprendertree2.FsUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* Worker class for {@link Forwarder}. A ConnectionHandler will be created once the Forwarder
* 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 ConnectionHandler {
private static final String LOG_TAG = "ConnectionHandler";
public static interface OnFinishedCallback {
public void onFinished();
}
private class SocketPipeThread extends Thread {
private InputStream mInputStream;
private OutputStream mOutputStream;
public SocketPipeThread(InputStream inputStream, OutputStream outputStream) {
mInputStream = inputStream;
mOutputStream = outputStream;
setName("SocketPipeThread: " + getName());
}
@Override
public void run() {
byte[] buffer = new byte[4096];
int length;
while (true) {
try {
if ((length = mInputStream.read(buffer)) < 0) {
break;
}
mOutputStream.write(buffer, 0, length);
} catch (IOException e) {
/** This exception means one of the streams is closed */
Log.v(LOG_TAG, this.toString(), e);
break;
}
}
synchronized (mThreadsRunning) {
mThreadsRunning--;
if (mThreadsRunning == 0) {
ConnectionHandler.this.stop();
mOnFinishedCallback.onFinished();
}
}
}
@Override
public String toString() {
return getName();
}
}
private Integer mThreadsRunning;
private Socket mFromSocket, mToSocket;
private SocketPipeThread mFromToPipe, mToFromPipe;
private InputStream mFromSocketInputStream, mToSocketInputStream;
private OutputStream mFromSocketOutputStream, mToSocketOutputStream;
private int mPort;
private String mRemoteMachineIpAddress;
private OnFinishedCallback mOnFinishedCallback;
public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket)
throws IOException {
mRemoteMachineIpAddress = remoteMachineIp;
mPort = port;
mFromSocket = fromSocket;
mToSocket = toSocket;
try {
mFromSocketInputStream = mFromSocket.getInputStream();
mToSocketInputStream = mToSocket.getInputStream();
mFromSocketOutputStream = mFromSocket.getOutputStream();
mToSocketOutputStream = mToSocket.getOutputStream();
AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream,
mRemoteMachineIpAddress, mPort);
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to start ConnectionHandler", e);
closeStreams();
throw e;
}
mFromToPipe = new SocketPipeThread(mFromSocketInputStream, mToSocketOutputStream);
mToFromPipe = new SocketPipeThread(mToSocketInputStream, mFromSocketOutputStream);
}
public void registerOnConnectionHandlerFinishedCallback(OnFinishedCallback callback) {
mOnFinishedCallback = callback;
}
private void closeStreams() {
FsUtils.closeInputStream(mFromSocketInputStream);
FsUtils.closeInputStream(mToSocketInputStream);
FsUtils.closeOutputStream(mFromSocketOutputStream);
FsUtils.closeOutputStream(mToSocketOutputStream);
}
public void start() {
/** We have 2 threads running, one for each pipe, that we start here. */
mThreadsRunning = 2;
mFromToPipe.start();
mToFromPipe.start();
}
public void stop() {
shutdown(mFromSocket);
shutdown(mToSocket);
}
private void shutdown(Socket socket) {
synchronized (mFromToPipe) {
synchronized (mToFromPipe) {
/** This will stop the while loop in the run method */
try {
if (!socket.isInputShutdown()) {
socket.shutdownInput();
}
} catch (IOException e) {
Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
}
try {
if (!socket.isOutputShutdown()) {
socket.shutdownOutput();
}
} catch (IOException e) {
Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
}
try {
if (!socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
}
}
}
}
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.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 on localhost on specified port and forwards the tcp
* communications to external socket via adb networking proxy.
*/
public class Forwarder extends Thread {
private static final String LOG_TAG = "Forwarder";
private int mPort;
private String mRemoteMachineIpAddress;
private ServerSocket mServerSocket;
private Set<ConnectionHandler> mConnectionHandlers = new HashSet<ConnectionHandler>();
public Forwarder(int port, String remoteMachineIpAddress) {
mPort = port;
mRemoteMachineIpAddress = remoteMachineIpAddress;
}
@Override
public void start() {
Log.i(LOG_TAG, "start(): Starting fowarder on port: " + mPort);
try {
mServerSocket = new ServerSocket(mPort);
} catch (IOException e) {
Log.e(LOG_TAG, "mPort=" + mPort, e);
return;
}
super.start();
}
@Override
public void run() {
while (true) {
Socket localSocket;
try {
localSocket = mServerSocket.accept();
} catch (IOException e) {
/** This most likely means that mServerSocket is already closed */
Log.w(LOG_TAG, "mPort=" + mPort, e);
break;
}
Socket remoteSocket = null;
final ConnectionHandler connectionHandler;
try {
remoteSocket = AdbUtils.createSocket();
connectionHandler = new ConnectionHandler(
mRemoteMachineIpAddress, mPort, localSocket, remoteSocket);
} catch (IOException exception) {
try {
localSocket.close();
} catch (IOException e) {
Log.e(LOG_TAG, "mPort=" + mPort, e);
}
if (remoteSocket != null) {
try {
remoteSocket.close();
} catch (IOException e) {
Log.e(LOG_TAG, "mPort=" + mPort, e);
}
}
continue;
}
/**
* We have to close the sockets after the ConnectionHandler finishes, so we
* don't get "Too may open files" exception. We also remove the ConnectionHandler
* from the collection to avoid memory issues.
* */
ConnectionHandler.OnFinishedCallback callback =
new ConnectionHandler.OnFinishedCallback() {
@Override
public void onFinished() {
synchronized (this) {
if (!mConnectionHandlers.remove(connectionHandler)) {
assert false : "removeConnectionHandler(): not in the collection";
}
}
}
};
connectionHandler.registerOnConnectionHandlerFinishedCallback(callback);
synchronized (this) {
mConnectionHandlers.add(connectionHandler);
}
connectionHandler.start();
}
synchronized (this) {
for (ConnectionHandler connectionHandler : mConnectionHandlers) {
connectionHandler.stop();
}
}
}
public void finish() {
try {
mServerSocket.close();
} catch (IOException e) {
Log.e(LOG_TAG, "mPort=" + mPort, e);
}
}
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.forwarder;
import java.net.MalformedURLException;
import java.net.URL;
import android.util.Log;
import java.util.HashSet;
import java.util.Set;
/**
* A simple class to start and stop Forwarders running on some ports.
*
* It uses a singleton pattern and is thread safe.
*/
public class ForwarderManager {
private static final String LOG_TAG = "ForwarderManager";
/**
* The IP address of the server serving the tests.
*/
private static final String HOST_IP = "127.0.0.1";
/**
* We use these ports because other webkit platforms do. They are set up in
* external/webkit/LayoutTests/http/conf/apache2-debian-httpd.conf
*/
public static final int HTTP_PORT = 8000;
public static final int HTTPS_PORT = 8443;
private static ForwarderManager forwarderManager;
private Set<Forwarder> mForwarders;
private boolean mIsStarted;
private ForwarderManager() {
mForwarders = new HashSet<Forwarder>(2);
mForwarders.add(new Forwarder(HTTP_PORT, HOST_IP));
mForwarders.add(new Forwarder(HTTPS_PORT, HOST_IP));
}
/**
* Returns the main part of the URL with the trailing slash
*
* @param isHttps
* @return
*/
public static final String getHostSchemePort(boolean isHttps) {
int port;
String protocol;
if (isHttps) {
protocol = "https";
port = HTTPS_PORT;
} else {
protocol = "http";
port = HTTP_PORT;
}
URL url = null;
try {
url = new URL(protocol, HOST_IP, port, "/");
} catch (MalformedURLException e) {
assert false : "isHttps=" + isHttps;
}
return url.toString();
}
public static synchronized ForwarderManager getForwarderManager() {
if (forwarderManager == null) {
forwarderManager = new ForwarderManager();
}
return forwarderManager;
}
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public synchronized void start() {
if (mIsStarted) {
Log.w(LOG_TAG, "start(): ForwarderManager already running! NOOP.");
return;
}
for (Forwarder forwarder : mForwarders) {
forwarder.start();
}
mIsStarted = true;
Log.i(LOG_TAG, "ForwarderManager started.");
}
public synchronized void stop() {
if (!mIsStarted) {
Log.w(LOG_TAG, "stop(): ForwarderManager already stopped! NOOP.");
return;
}
for (Forwarder forwarder : mForwarders) {
forwarder.finish();
}
mIsStarted = false;
Log.i(LOG_TAG, "ForwarderManager stopped.");
}
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.scriptsupport;
/**
* Callback used to inform scriptsupport.Starter that everything is finished and
* we can exit
*/
public interface OnEverythingFinishedCallback {
public void onFinished();
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.scriptsupport;
import android.os.Bundle;
import android.test.InstrumentationTestRunner;
/**
* Extends InstrumentationTestRunner to allow the script to pass arguments to the application
*/
public class ScriptTestRunner extends InstrumentationTestRunner {
String mTestsRelativePath;
@Override
public void onCreate(Bundle arguments) {
mTestsRelativePath = arguments.getString("path");
super.onCreate(arguments);
}
public String getTestsRelativePath() {
return mTestsRelativePath;
}
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.scriptsupport;
import android.content.Intent;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import com.android.dumprendertree2.TestsListActivity;
import com.android.dumprendertree2.forwarder.ForwarderManager;
/**
* A class which provides methods that can be invoked by a script running on the host machine to
* run the tests.
*
* It starts a TestsListActivity and does not return until all the tests finish executing.
*/
public class Starter extends ActivityInstrumentationTestCase2<TestsListActivity> {
private static final String LOG_TAG = "Starter";
private boolean mEverythingFinished;
public Starter() {
super(TestsListActivity.class);
}
/**
* This method is called from adb to start executing the tests. It doesn't return
* until everything is finished so that the script can wait for the end if it needs
* to.
*/
public void startLayoutTests() {
ScriptTestRunner runner = (ScriptTestRunner)getInstrumentation();
String relativePath = runner.getTestsRelativePath();
ForwarderManager.getForwarderManager().start();
Intent intent = new Intent();
intent.setClassName("com.android.dumprendertree2", "TestsListActivity");
intent.setAction(Intent.ACTION_RUN);
intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
setActivityIntent(intent);
getActivity().registerOnEverythingFinishedCallback(new OnEverythingFinishedCallback() {
/** This method is safe to call on any thread */
@Override
public void onFinished() {
synchronized (Starter.this) {
mEverythingFinished = true;
Starter.this.notifyAll();
}
}
});
synchronized (this) {
while (!mEverythingFinished) {
try {
this.wait();
} catch (InterruptedException e) {
Log.e(LOG_TAG, "startLayoutTests()", e);
}
}
}
ForwarderManager.getForwarderManager().stop();
}
}

View File

@@ -1,426 +0,0 @@
/*
* Copyright (C) 2010 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.dumprendertree2.ui;
import com.android.dumprendertree2.FileFilter;
import com.android.dumprendertree2.FsUtils;
import com.android.dumprendertree2.TestsListActivity;
import com.android.dumprendertree2.R;
import com.android.dumprendertree2.forwarder.ForwarderManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* An Activity that allows navigating through tests folders and choosing folders or tests to run.
*/
public class DirListActivity extends ListActivity {
private static final String LOG_TAG = "DirListActivity";
/** TODO: This is just a guess - think of a better way to achieve it */
private static final int MEAN_TITLE_CHAR_SIZE = 13;
private static final int PROGRESS_DIALOG_DELAY_MS = 200;
/** Code for the dialog, used in showDialog and onCreateDialog */
private static final int DIALOG_RUN_ABORT_DIR = 0;
/** Messages codes */
private static final int MSG_LOADED_ITEMS = 0;
private static final int MSG_SHOW_PROGRESS_DIALOG = 1;
private static final CharSequence NO_RESPONSE_MESSAGE =
"No response from host when getting directory contents. Is the host server running?";
/** Initialized lazily before first sProgressDialog.show() */
private static ProgressDialog sProgressDialog;
private ListView mListView;
/** This is a relative path! */
private String mCurrentDirPath;
/**
* A thread responsible for loading the contents of the directory from sd card
* and sending them via Message to main thread that then loads them into
* ListView
*/
private class LoadListItemsThread extends Thread {
private Handler mHandler;
private String mRelativePath;
public LoadListItemsThread(String relativePath, Handler handler) {
mRelativePath = relativePath;
mHandler = handler;
}
@Override
public void run() {
Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS);
msg.obj = getDirList(mRelativePath);
mHandler.sendMessage(msg);
}
}
/**
* Very simple object to use inside ListView as an item.
*/
private static class ListItem implements Comparable<ListItem> {
private String mRelativePath;
private String mName;
private boolean mIsDirectory;
public ListItem(String relativePath, boolean isDirectory) {
mRelativePath = relativePath;
mName = new File(relativePath).getName();
mIsDirectory = isDirectory;
}
public boolean isDirectory() {
return mIsDirectory;
}
public String getRelativePath() {
return mRelativePath;
}
public String getName() {
return mName;
}
@Override
public int compareTo(ListItem another) {
return mRelativePath.compareTo(another.getRelativePath());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ListItem)) {
return false;
}
return mRelativePath.equals(((ListItem)o).getRelativePath());
}
@Override
public int hashCode() {
return mRelativePath.hashCode();
}
}
/**
* A custom adapter that sets the proper icon and label in the list view.
*/
private static class DirListAdapter extends ArrayAdapter<ListItem> {
private Activity mContext;
private ListItem[] mItems;
public DirListAdapter(Activity context, ListItem[] items) {
super(context, R.layout.dirlist_row, items);
mContext = context;
mItems = items;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = mContext.getLayoutInflater();
View row = inflater.inflate(R.layout.dirlist_row, null);
TextView label = (TextView)row.findViewById(R.id.label);
label.setText(mItems[position].getName());
ImageView icon = (ImageView)row.findViewById(R.id.icon);
if (mItems[position].isDirectory()) {
icon.setImageResource(R.drawable.folder);
} else {
icon.setImageResource(R.drawable.runtest);
}
return row;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ForwarderManager.getForwarderManager().start();
mListView = getListView();
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ListItem item = (ListItem)parent.getItemAtPosition(position);
if (item.isDirectory()) {
showDir(item.getRelativePath());
} else {
/** Run the test */
runAllTestsUnder(item.getRelativePath());
}
}
});
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
ListItem item = (ListItem)parent.getItemAtPosition(position);
if (item.isDirectory()) {
Bundle arguments = new Bundle(1);
arguments.putString("name", item.getName());
arguments.putString("relativePath", item.getRelativePath());
showDialog(DIALOG_RUN_ABORT_DIR, arguments);
} else {
/** TODO: Maybe show some info about a test? */
}
return true;
}
});
/** All the paths are relative to test root dir where possible */
showDir("");
}
private void runAllTestsUnder(String relativePath) {
Intent intent = new Intent();
intent.setClass(DirListActivity.this, TestsListActivity.class);
intent.setAction(Intent.ACTION_RUN);
intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
startActivity(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.gui_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.run_all:
runAllTestsUnder(mCurrentDirPath);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
/**
* Moves to the parent directory if one exists. Does not allow to move above
* the test 'root' directory.
*/
public void onBackPressed() {
File currentDirParent = new File(mCurrentDirPath).getParentFile();
if (currentDirParent != null) {
showDir(currentDirParent.getPath());
} else {
showDir("");
}
}
/**
* Prevents the activity from recreating on change of orientation. The title needs to
* be recalculated.
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setTitle(shortenTitle(mCurrentDirPath));
}
@Override
protected Dialog onCreateDialog(int id, final Bundle args) {
Dialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
switch (id) {
case DIALOG_RUN_ABORT_DIR:
builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " +
args.getString("name"));
builder.setMessage(R.string.dialog_run_abort_dir_msg);
builder.setCancelable(true);
builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
removeDialog(DIALOG_RUN_ABORT_DIR);
runAllTestsUnder(args.getString("relativePath"));
}
});
builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
removeDialog(DIALOG_RUN_ABORT_DIR);
}
});
dialog = builder.create();
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
removeDialog(DIALOG_RUN_ABORT_DIR);
}
});
break;
}
return dialog;
}
/**
* Loads the contents of dir into the list view.
*
* @param dirPath
* directory to load into list view
*/
private void showDir(String dirPath) {
mCurrentDirPath = dirPath;
/** Show progress dialog with a delay */
final Handler delayedDialogHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_SHOW_PROGRESS_DIALOG) {
if (sProgressDialog == null) {
sProgressDialog = new ProgressDialog(DirListActivity.this);
sProgressDialog.setCancelable(false);
sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
sProgressDialog.setTitle(R.string.dialog_progress_title);
sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
}
sProgressDialog.show();
}
}
};
Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG);
delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS);
/** Delegate loading contents from SD card to a new thread */
new LoadListItemsThread(mCurrentDirPath, new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_LOADED_ITEMS) {
setTitle(shortenTitle(mCurrentDirPath));
delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
if (sProgressDialog != null) {
sProgressDialog.dismiss();
}
if (msg.obj == null) {
Toast.makeText(DirListActivity.this, NO_RESPONSE_MESSAGE,
Toast.LENGTH_LONG).show();
} else {
setListAdapter(new DirListAdapter(DirListActivity.this,
(ListItem[])msg.obj));
}
}
}
}).start();
}
/**
* TODO: find a neat way to determine number of characters that fit in the title
* bar.
* */
private String shortenTitle(String title) {
if (title.equals("")) {
return "Tests' root dir:";
}
int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE;
if (title.length() > charCount) {
return "..." + title.substring(title.length() - charCount);
} else {
return title;
}
}
/**
* Return the array with contents of the given directory.
* First it contains the subfolders, then the files. Both sorted
* alphabetically.
*
* The dirPath is relative.
*/
private ListItem[] getDirList(String dirPath) {
List<ListItem> subDirs = new ArrayList<ListItem>();
List<ListItem> subFiles = new ArrayList<ListItem>();
List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, true);
if (dirRelativePaths == null) {
return null;
}
for (String dirRelativePath : dirRelativePaths) {
if (FileFilter.isTestDir(new File(dirRelativePath).getName())) {
subDirs.add(new ListItem(dirRelativePath, true));
}
}
List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, false);
if (testRelativePaths == null) {
return null;
}
for (String testRelativePath : testRelativePaths) {
if (FileFilter.isTestFile(new File(testRelativePath).getName())) {
subFiles.add(new ListItem(testRelativePath, false));
}
}
/** Concatenate the two lists */
subDirs.addAll(subFiles);
return subDirs.toArray(new ListItem[subDirs.size()]);
}
}

View File

@@ -1,320 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.webkit.WebSettings;
import android.widget.Spinner;
public class PerformanceTest extends
ActivityInstrumentationTestCase2<ProfileActivity> {
public static class AnimStat {
double aggVal = 0;
double aggSqrVal = 0;
double count = 0;
}
private class StatAggregator extends PlaybackGraphs {
private HashMap<String, Double> mDataMap = new HashMap<String, Double>();
private HashMap<String, AnimStat> mAnimDataMap = new HashMap<String, AnimStat>();
private int mCount = 0;
public void aggregate() {
boolean inAnimTests = mAnimTests != null;
Resources resources = mWeb.getResources();
String animFramerateString = resources.getString(R.string.animation_framerate);
for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
String name = e.getKey();
if (inAnimTests) {
if (name.equals(animFramerateString)) {
// in animation testing phase, record animation framerate and aggregate
// stats, differentiating on values of mAnimTestNr and mDoubleBuffering
String fullName = ANIM_TEST_NAMES[mAnimTestNr] + " " + name;
fullName += mDoubleBuffering ? " tiled" : " webkit";
if (!mAnimDataMap.containsKey(fullName)) {
mAnimDataMap.put(fullName, new AnimStat());
}
AnimStat statVals = mAnimDataMap.get(fullName);
statVals.aggVal += e.getValue();
statVals.aggSqrVal += e.getValue() * e.getValue();
statVals.count += 1;
}
} else {
double aggVal = mDataMap.containsKey(name)
? mDataMap.get(name) : 0;
mDataMap.put(name, aggVal + e.getValue());
}
}
if (inAnimTests) {
return;
}
mCount++;
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
String metricLabel = resources.getString(
Metrics[metricIndex].getLabelId());
String statLabel = resources.getString(
Stats[statIndex].getLabelId());
String label = metricLabel + " " + statLabel;
double aggVal = mDataMap.containsKey(label) ? mDataMap
.get(label) : 0;
aggVal += mStats[metricIndex][statIndex];
mDataMap.put(label, aggVal);
}
}
}
// build the final bundle of results
public Bundle getBundle() {
Bundle b = new Bundle();
int count = (0 == mCount) ? Integer.MAX_VALUE : mCount;
for (Map.Entry<String, Double> e : mDataMap.entrySet()) {
b.putDouble(e.getKey(), e.getValue() / count);
}
for (Map.Entry<String, AnimStat> e : mAnimDataMap.entrySet()) {
String statName = e.getKey();
AnimStat statVals = e.getValue();
double avg = statVals.aggVal/statVals.count;
double stdDev = Math.sqrt((statVals.aggSqrVal / statVals.count) - avg * avg);
b.putDouble(statName, avg);
b.putDouble(statName + " STD DEV", stdDev);
}
return b;
}
}
ProfileActivity mActivity;
ProfiledWebView mWeb;
Spinner mMovementSpinner;
StatAggregator mStats;
private static final String LOGTAG = "PerformanceTest";
private static final String TEST_LOCATION = "webkit/page_cycler";
private static final String URL_PREFIX = "file://";
private static final String URL_POSTFIX = "/index.html?skip=true";
private static final int MAX_ITERATIONS = 4;
private static final String SCROLL_TEST_DIRS[] = {
"alexa25_2011"
};
private static final String ANIM_TEST_DIRS[] = {
"dhtml"
};
public PerformanceTest() {
super(ProfileActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
mWeb = (ProfiledWebView) mActivity.findViewById(R.id.web);
mMovementSpinner = (Spinner) mActivity.findViewById(R.id.movement);
mStats = new StatAggregator();
// use mStats as a condition variable between the UI thread and
// this(the testing) thread
mActivity.setCallback(new ProfileCallback() {
@Override
public void profileCallback(RunData data) {
mStats.setData(data);
synchronized (mStats) {
mStats.notify();
}
}
});
}
private boolean loadUrl(final String url) {
try {
Log.d(LOGTAG, "test starting for url " + url);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mWeb.loadUrl(url);
}
});
synchronized (mStats) {
mStats.wait();
}
mStats.aggregate();
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
private boolean validTest(String nextTest) {
// if testing animations, test must be in mAnimTests
if (mAnimTests == null)
return true;
for (String test : mAnimTests) {
if (test.equals(nextTest)) {
return true;
}
}
return false;
}
private boolean runIteration(String[] testDirs) {
File sdFile = Environment.getExternalStorageDirectory();
for (String testDirName : testDirs) {
File testDir = new File(sdFile, TEST_LOCATION + "/" + testDirName);
Log.d(LOGTAG, "Testing dir: '" + testDir.getAbsolutePath()
+ "', exists=" + testDir.exists());
for (File siteDir : testDir.listFiles()) {
if (!siteDir.isDirectory() || !validTest(siteDir.getName())) {
continue;
}
if (!loadUrl(URL_PREFIX + siteDir.getAbsolutePath()
+ URL_POSTFIX)) {
return false;
}
}
}
return true;
}
private boolean runTestDirs(String[] testDirs) {
for (int i = 0; i < MAX_ITERATIONS; i++)
if (!runIteration(testDirs)) {
return false;
}
return true;
}
private void pushDoubleBuffering() {
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
mWeb.setDoubleBuffering(mDoubleBuffering);
}
});
}
private void setScrollingTestingMode(final boolean scrolled) {
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
mMovementSpinner.setSelection(scrolled ? 0 : 2);
}
});
}
private String[] mAnimTests = null;
private int mAnimTestNr = -1;
private boolean mDoubleBuffering = true;
private static final String[] ANIM_TEST_NAMES = {
"slow", "fast"
};
private static final String[][] ANIM_TESTS = {
{"scrolling", "replaceimages", "layers5", "layers1"},
{"slidingballs", "meter", "slidein", "fadespacing", "colorfade",
"mozilla", "movingtext", "diagball", "zoom", "imageslide"},
};
private boolean checkMedia() {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)
&& !Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
Log.d(LOGTAG, "ARG Can't access sd card!");
// Can't read the SD card, fail and die!
getInstrumentation().sendStatus(1, null);
return false;
}
return true;
}
public void testMetrics() {
setScrollingTestingMode(true);
if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
getInstrumentation().sendStatus(0, mStats.getBundle());
} else {
getInstrumentation().sendStatus(1, null);
}
}
public void testMetricsMinimalMemory() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mWeb.setUseMinimalMemory(true);
}
});
setScrollingTestingMode(true);
if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
getInstrumentation().sendStatus(0, mStats.getBundle());
} else {
getInstrumentation().sendStatus(1, null);
}
}
private boolean runAnimationTests() {
for (int doubleBuffer = 0; doubleBuffer <= 1; doubleBuffer++) {
mDoubleBuffering = doubleBuffer == 1;
pushDoubleBuffering();
for (mAnimTestNr = 0; mAnimTestNr < ANIM_TESTS.length; mAnimTestNr++) {
mAnimTests = ANIM_TESTS[mAnimTestNr];
if (!runTestDirs(ANIM_TEST_DIRS)) {
return false;
}
}
}
return true;
}
public void testAnimations() {
// instead of autoscrolling, load each page until either an timer fires,
// or the animation signals complete via javascript
setScrollingTestingMode(false);
if (checkMedia() && runAnimationTests()) {
getInstrumentation().sendStatus(0, mStats.getBundle());
} else {
getInstrumentation().sendStatus(1, null);
}
}
}

View File

@@ -1,173 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* Interface for playing back WebView tile rendering status. Draws viewport and
* states of tiles and statistics for off-line analysis.
*/
public class PlaybackActivity extends Activity {
private static final float SCROLL_SCALER = 0.125f;
PlaybackView mPlaybackView;
SeekBar mSeekBar;
Button mForward;
Button mBackward;
TextView mFrameDisplay;
private int mFrame = -1;
private int mFrameMax;
private class TouchFrameChangeListener extends SimpleOnGestureListener {
float mDist = 0;
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// aggregate scrolls so that small ones can add up
mDist += distanceY * SCROLL_SCALER;
int intComponent = (int) Math.floor(Math.abs(mDist));
if (intComponent >= 1) {
int scrollDist = (mDist > 0) ? intComponent : -intComponent;
setFrame(null, mFrame + scrollDist);
mDist -= scrollDist;
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
};
private class SeekFrameChangeListener implements OnSeekBarChangeListener {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
setFrame(seekBar, progress);
}
};
private class LoadFileTask extends AsyncTask<String, Void, RunData> {
@Override
protected RunData doInBackground(String... params) {
RunData data = null;
try {
FileInputStream fis = openFileInput(params[0]);
ObjectInputStream in = new ObjectInputStream(fis);
data = (RunData) in.readObject();
in.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return data;
}
@Override
protected void onPostExecute(RunData data) {
if (data == null) {
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.error_no_data),
Toast.LENGTH_LONG).show();
return;
}
mPlaybackView.setData(data);
mFrameMax = data.frames.length - 1;
mSeekBar.setMax(mFrameMax);
setFrame(null, 0);
}
}
private void setFrame(View changer, int f) {
if (f < 0) {
f = 0;
} else if (f > mFrameMax) {
f = mFrameMax;
}
if (mFrame == f) {
return;
}
mFrame = f;
mForward.setEnabled(mFrame != mFrameMax);
mBackward.setEnabled(mFrame != 0);
if (changer != mSeekBar) {
mSeekBar.setProgress(mFrame);
}
mFrameDisplay.setText(Integer.toString(mFrame));
mPlaybackView.setFrame(mFrame);
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.playback);
mPlaybackView = (PlaybackView) findViewById(R.id.playback);
mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
mForward = (Button) findViewById(R.id.forward);
mBackward = (Button) findViewById(R.id.backward);
mFrameDisplay = (TextView) findViewById(R.id.frame_display);
mForward.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setFrame(v, mFrame + 1);
}
});
mBackward.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setFrame(v, mFrame - 1);
}
});
mSeekBar.setOnSeekBarChangeListener(new SeekFrameChangeListener());
mPlaybackView.setOnGestureListener(new TouchFrameChangeListener());
new LoadFileTask().execute(ProfileActivity.TEMP_FILENAME);
}
}

View File

@@ -1,306 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import com.test.tilebenchmark.RunData.TileData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
public class PlaybackGraphs {
private static final int BAR_WIDTH = PlaybackView.TILE_SCALE * 3;
private static final float CANVAS_SCALE = 0.2f;
private static final double IDEAL_FRAMES = 60;
private static final int LABELOFFSET = 100;
private static Paint whiteLabels;
private static double viewportCoverage(TileData view, TileData tile) {
if (tile.left < (view.right * view.scale)
&& tile.right >= (view.left * view.scale)
&& tile.top < (view.bottom * view.scale)
&& tile.bottom >= (view.top * view.scale)) {
return 1.0f;
}
return 0.0f;
}
protected interface MetricGen {
public double getValue(TileData[] frame);
public double getMax();
public int getLabelId();
};
protected static MetricGen[] Metrics = new MetricGen[] {
new MetricGen() {
// framerate graph
@Override
public double getValue(TileData[] frame) {
int renderTimeUS = frame[0].level;
return 1.0e6f / renderTimeUS;
}
@Override
public double getMax() {
return IDEAL_FRAMES;
}
@Override
public int getLabelId() {
return R.string.frames_per_second;
}
}, new MetricGen() {
// coverage graph
@Override
public double getValue(TileData[] frame) {
double total = 0, totalCount = 0;
for (int tileID = 1; tileID < frame.length; tileID++) {
TileData data = frame[tileID];
double coverage = viewportCoverage(frame[0], data);
total += coverage * (data.isReady ? 100 : 0);
totalCount += coverage;
}
if (totalCount == 0) {
return -1;
}
return total / totalCount;
}
@Override
public double getMax() {
return 100;
}
@Override
public int getLabelId() {
return R.string.viewport_coverage;
}
}
};
protected interface StatGen {
public double getValue(double sortedValues[]);
public int getLabelId();
}
public static double getPercentile(double sortedValues[], double ratioAbove) {
if (sortedValues.length == 0)
return -1;
double index = ratioAbove * (sortedValues.length - 1);
int intIndex = (int) Math.floor(index);
if (index == intIndex) {
return sortedValues[intIndex];
}
double alpha = index - intIndex;
return sortedValues[intIndex] * (1 - alpha)
+ sortedValues[intIndex + 1] * (alpha);
}
public static double getMean(double sortedValues[]) {
if (sortedValues.length == 0)
return -1;
double agg = 0;
for (double val : sortedValues) {
agg += val;
}
return agg / sortedValues.length;
}
public static double getStdDev(double sortedValues[]) {
if (sortedValues.length == 0)
return -1;
double agg = 0;
double sqrAgg = 0;
for (double val : sortedValues) {
agg += val;
sqrAgg += val*val;
}
double mean = agg / sortedValues.length;
return Math.sqrt((sqrAgg / sortedValues.length) - (mean * mean));
}
protected static StatGen[] Stats = new StatGen[] {
new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getPercentile(sortedValues, 0.25);
}
@Override
public int getLabelId() {
return R.string.percentile_25;
}
}, new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getPercentile(sortedValues, 0.5);
}
@Override
public int getLabelId() {
return R.string.percentile_50;
}
}, new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getPercentile(sortedValues, 0.75);
}
@Override
public int getLabelId() {
return R.string.percentile_75;
}
}, new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getStdDev(sortedValues);
}
@Override
public int getLabelId() {
return R.string.std_dev;
}
}, new StatGen() {
@Override
public double getValue(double[] sortedValues) {
return getMean(sortedValues);
}
@Override
public int getLabelId() {
return R.string.mean;
}
},
};
public PlaybackGraphs() {
whiteLabels = new Paint();
whiteLabels.setColor(Color.WHITE);
whiteLabels.setTextSize(PlaybackView.TILE_SCALE / 3);
}
private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
protected final double[][] mStats = new double[Metrics.length][Stats.length];
protected HashMap<String, Double> mSingleStats;
private void gatherFrameMetric(int metricIndex, double metricValues[], RunData data) {
// create graph out of rectangles, one per frame
int lastBar = 0;
for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
TileData frame[] = data.frames[frameIndex];
int newBar = (int)((frame[0].top + frame[0].bottom) * frame[0].scale / 2.0f);
MetricGen s = Metrics[metricIndex];
double absoluteValue = s.getValue(frame);
double relativeValue = absoluteValue / s.getMax();
relativeValue = Math.min(1,relativeValue);
relativeValue = Math.max(0,relativeValue);
int rightPos = (int) (-BAR_WIDTH * metricIndex);
int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
ShapeDrawable graphBar = new ShapeDrawable();
graphBar.getPaint().setColor(Color.BLUE);
graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
mShapes.add(graphBar);
metricValues[frameIndex] = absoluteValue;
lastBar = newBar;
}
}
public void setData(RunData data) {
mShapes.clear();
double metricValues[] = new double[data.frames.length];
mSingleStats = data.singleStats;
if (data.frames.length == 0) {
return;
}
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
// calculate metric based on list of frames
gatherFrameMetric(metricIndex, metricValues, data);
// store aggregate statistics per metric (median, and similar)
Arrays.sort(metricValues);
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
mStats[metricIndex][statIndex] =
Stats[statIndex].getValue(metricValues);
}
}
}
public void drawVerticalShiftedShapes(Canvas canvas,
ArrayList<ShapeDrawable> shapes) {
// Shapes drawn here are drawn relative to the viewRect
Rect viewRect = shapes.get(shapes.size() - 1).getBounds();
canvas.translate(0, 5 * PlaybackView.TILE_SCALE - viewRect.top);
for (ShapeDrawable shape : mShapes) {
shape.draw(canvas);
}
for (ShapeDrawable shape : shapes) {
shape.draw(canvas);
}
}
public void draw(Canvas canvas, ArrayList<ShapeDrawable> shapes,
ArrayList<String> strings, Resources resources) {
canvas.scale(CANVAS_SCALE, CANVAS_SCALE);
canvas.translate(BAR_WIDTH * Metrics.length, 0);
canvas.save();
drawVerticalShiftedShapes(canvas, shapes);
canvas.restore();
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
String label = resources.getString(
Metrics[metricIndex].getLabelId());
int xPos = (metricIndex + 1) * -BAR_WIDTH;
int yPos = LABELOFFSET;
canvas.drawText(label, xPos, yPos, whiteLabels);
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
String statLabel = resources.getString(
Stats[statIndex].getLabelId()).substring(0,3);
label = statLabel + " " + resources.getString(
R.string.format_stat, mStats[metricIndex][statIndex]);
yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILE_SCALE
/ 2;
canvas.drawText(label, xPos, yPos, whiteLabels);
}
}
for (int stringIndex = 0; stringIndex < strings.size(); stringIndex++) {
int yPos = LABELOFFSET + stringIndex * PlaybackView.TILE_SCALE / 2;
canvas.drawText(strings.get(stringIndex), 0, yPos, whiteLabels);
}
}
}

View File

@@ -1,224 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import com.test.tilebenchmark.RunData.TileData;
import java.util.ArrayList;
public class PlaybackView extends View {
public static final int TILE_SCALE = 256;
private static final int INVAL_FLAG = -2;
private static final int INVAL_CYCLE = 250;
private Paint levelPaint = null, coordPaint = null, goldPaint = null;
private PlaybackGraphs mGraphs;
private ArrayList<ShapeDrawable> mTempShapes = new ArrayList<ShapeDrawable>();
private RunData mProfData = null;
private GestureDetector mGestureDetector = null;
private ArrayList<String> mRenderStrings = new ArrayList<String>();
private class TileDrawable extends ShapeDrawable {
TileData tile;
String label;
public TileDrawable(TileData t, int colorId) {
this.tile = t;
getPaint().setColor(getResources().getColor(colorId));
if (colorId == R.color.ready_tile
|| colorId == R.color.unready_tile) {
label = (int) (t.left / TILE_SCALE) + ", "
+ (int) (t.top / TILE_SCALE);
// ignore scale value for tiles
setBounds(t.left, t.top,
t.right, t.bottom);
} else {
setBounds((int) (t.left * t.scale),
(int) (t.top * t.scale),
(int) (t.right * t.scale),
(int) (t.bottom * t.scale));
}
}
@SuppressWarnings("unused")
public void setColor(int color) {
getPaint().setColor(color);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (label != null) {
canvas.drawText(Integer.toString(tile.level), getBounds().left,
getBounds().bottom, levelPaint);
canvas.drawText(label, getBounds().left,
((getBounds().bottom + getBounds().top) / 2),
coordPaint);
}
}
}
public PlaybackView(Context context) {
super(context);
init();
}
public PlaybackView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PlaybackView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setOnGestureListener(OnGestureListener gl) {
mGestureDetector = new GestureDetector(getContext(), gl);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return true;
}
private void init() {
levelPaint = new Paint();
levelPaint.setColor(Color.WHITE);
levelPaint.setTextSize(TILE_SCALE / 2);
coordPaint = new Paint();
coordPaint.setColor(Color.BLACK);
coordPaint.setTextSize(TILE_SCALE / 3);
goldPaint = new Paint();
goldPaint.setColor(0xffa0e010);
mGraphs = new PlaybackGraphs();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mTempShapes == null || mTempShapes.isEmpty()) {
return;
}
mGraphs.draw(canvas, mTempShapes, mRenderStrings, getResources());
invalidate(); // may have animations, force redraw
}
private String statString(int labelId, int value) {
return getResources().getString(R.string.format_stat_name,
getResources().getString(labelId), value);
}
private String tileString(int formatStringId, TileData t) {
return getResources().getString(formatStringId,
t.left, t.top, t.right, t.bottom);
}
public int setFrame(int frame) {
if (mProfData == null || mProfData.frames.length == 0) {
return 0;
}
int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 0, numInvals = 0;
mTempShapes.clear();
mRenderStrings.clear();
// create tile shapes (as they're drawn on bottom)
for (TileData t : mProfData.frames[frame]) {
if (t == mProfData.frames[frame][0]){
// viewport 'tile', add coords to render strings
mRenderStrings.add(tileString(R.string.format_view_pos, t));
} else if (t.level != INVAL_FLAG) {
int colorId;
if (t.isReady) {
readyTiles++;
colorId = R.color.ready_tile;
} else {
unreadyTiles++;
colorId = R.color.unready_tile;
}
if (t.left < 0 || t.top < 0) {
unplacedTiles++;
}
mTempShapes.add(new TileDrawable(t, colorId));
} else {
// inval 'tile', count and add coords to render strings
numInvals++;
mRenderStrings.add(tileString(R.string.format_inval_pos, t));
}
}
// create invalidate shapes (drawn above tiles)
int invalId = 0;
for (TileData t : mProfData.frames[frame]) {
if (t.level == INVAL_FLAG && t != mProfData.frames[frame][0]) {
TileDrawable invalShape = new TileDrawable(t,
R.color.inval_region_start);
ValueAnimator tileAnimator = ObjectAnimator.ofInt(invalShape,
"color",
getResources().getColor(R.color.inval_region_start),
getResources().getColor(R.color.inval_region_stop));
tileAnimator.setDuration(numInvals * INVAL_CYCLE);
tileAnimator.setEvaluator(new ArgbEvaluator());
tileAnimator.setRepeatCount(ValueAnimator.INFINITE);
tileAnimator.setRepeatMode(ValueAnimator.RESTART);
float delay = (float) (invalId) * INVAL_CYCLE;
tileAnimator.setStartDelay((int) delay);
invalId++;
tileAnimator.start();
mTempShapes.add(invalShape);
}
}
mRenderStrings.add(statString(R.string.ready_tiles, readyTiles));
mRenderStrings.add(statString(R.string.unready_tiles, unreadyTiles));
mRenderStrings.add(statString(R.string.unplaced_tiles, unplacedTiles));
mRenderStrings.add(statString(R.string.number_invalidates, numInvals));
// draw view rect (using first TileData object, on top)
TileDrawable viewShape = new TileDrawable(mProfData.frames[frame][0],
R.color.view);
mTempShapes.add(viewShape);
this.invalidate();
return frame;
}
public void setData(RunData tileProfilingData) {
mProfData = tileProfilingData;
mGraphs.setData(mProfData);
}
}

View File

@@ -1,337 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import android.app.Activity;
import android.content.Intent;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ToggleButton;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* Interface for profiling the webview's scrolling, with simple controls on how
* to scroll, and what content to load.
*/
public class ProfileActivity extends Activity {
private static final int TIMED_RECORD_MILLIS = 2000;
public interface ProfileCallback {
public void profileCallback(RunData data);
}
public static final String TEMP_FILENAME = "profile.tiles";
Button mInspectButton;
ToggleButton mCaptureButton;
Spinner mVelocitySpinner;
Spinner mMovementSpinner;
EditText mUrl;
ProfiledWebView mWeb;
ProfileCallback mCallback;
LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient();
AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient();
TimedLoggingWebViewClient mTimedLoggingWebViewClient = new TimedLoggingWebViewClient();
private enum TestingState {
NOT_TESTING,
PRE_TESTING,
START_TESTING,
STOP_TESTING,
SAVED_TESTING
};
private class VelocitySelectedListener implements OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
String speedStr = parent.getItemAtPosition(position).toString();
int speedInt = Integer.parseInt(speedStr);
mWeb.setAutoScrollSpeed(speedInt);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
private class MovementSelectedListener implements OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
String movementStr = parent.getItemAtPosition(position).toString();
if (movementStr == getResources().getString(R.string.movement_auto_scroll)) {
mWeb.setWebViewClient(mAutoLoggingWebViewClient);
mCaptureButton.setEnabled(false);
mVelocitySpinner.setEnabled(true);
} else if (movementStr == getResources().getString(R.string.movement_manual)) {
mWeb.setWebViewClient(mLoggingWebViewClient);
mCaptureButton.setEnabled(true);
mVelocitySpinner.setEnabled(false);
} else if (movementStr == getResources().getString(R.string.movement_timed)) {
mWeb.setWebViewClient(mTimedLoggingWebViewClient);
mCaptureButton.setEnabled(false);
mVelocitySpinner.setEnabled(false);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
private class LoggingWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mUrl.setText(url);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.requestFocus();
((ProfiledWebView)view).onPageFinished();
}
}
private class AutoLoggingWebViewClient extends LoggingWebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
startViewProfiling(true);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
setTestingState(TestingState.PRE_TESTING);
}
}
private class TimedLoggingWebViewClient extends LoggingWebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
startViewProfiling(false);
// after a fixed time after page finished, stop testing
new CountDownTimer(TIMED_RECORD_MILLIS, TIMED_RECORD_MILLIS) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
mWeb.stopScrollTest();
}
}.start();
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
setTestingState(TestingState.PRE_TESTING);
}
}
private class StoreFileTask extends
AsyncTask<Pair<String, RunData>, Void, Void> {
@Override
protected Void doInBackground(Pair<String, RunData>... params) {
try {
FileOutputStream fos = openFileOutput(params[0].first,
Context.MODE_PRIVATE);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(params[0].second);
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void v) {
setTestingState(TestingState.SAVED_TESTING);
}
}
public void setTestingState(TestingState state) {
switch (state) {
case NOT_TESTING:
mUrl.setBackgroundResource(R.color.background_not_testing);
mInspectButton.setEnabled(true);
mMovementSpinner.setEnabled(true);
break;
case PRE_TESTING:
mInspectButton.setEnabled(false);
mMovementSpinner.setEnabled(false);
break;
case START_TESTING:
mCaptureButton.setChecked(true);
mUrl.setBackgroundResource(R.color.background_start_testing);
mInspectButton.setEnabled(false);
mMovementSpinner.setEnabled(false);
break;
case STOP_TESTING:
mCaptureButton.setChecked(false);
mUrl.setBackgroundResource(R.color.background_stop_testing);
break;
case SAVED_TESTING:
mInspectButton.setEnabled(true);
mMovementSpinner.setEnabled(true);
break;
}
}
/** auto - automatically scroll. */
private void startViewProfiling(boolean auto) {
// toggle capture button to indicate capture state to user
mWeb.startScrollTest(mCallback, auto);
setTestingState(TestingState.START_TESTING);
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mInspectButton = (Button) findViewById(R.id.inspect);
mCaptureButton = (ToggleButton) findViewById(R.id.capture);
mVelocitySpinner = (Spinner) findViewById(R.id.velocity);
mMovementSpinner = (Spinner) findViewById(R.id.movement);
mUrl = (EditText) findViewById(R.id.url);
mWeb = (ProfiledWebView) findViewById(R.id.web);
setCallback(new ProfileCallback() {
@SuppressWarnings("unchecked")
@Override
public void profileCallback(RunData data) {
new StoreFileTask().execute(new Pair<String, RunData>(
TEMP_FILENAME, data));
Log.d("ProfileActivity", "stored " + data.frames.length + " frames in file");
setTestingState(TestingState.STOP_TESTING);
}
});
// Inspect button (opens PlaybackActivity)
mInspectButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ProfileActivity.this,
PlaybackActivity.class));
}
});
// Velocity spinner
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.velocity_array,
android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mVelocitySpinner.setAdapter(adapter);
mVelocitySpinner.setOnItemSelectedListener(
new VelocitySelectedListener());
mVelocitySpinner.setSelection(3);
// Movement spinner
String content[] = {
getResources().getString(R.string.movement_auto_scroll),
getResources().getString(R.string.movement_manual),
getResources().getString(R.string.movement_timed)
};
adapter = new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_spinner_item, content);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mMovementSpinner.setAdapter(adapter);
mMovementSpinner.setOnItemSelectedListener(
new MovementSelectedListener());
mMovementSpinner.setSelection(0);
// Capture toggle button
mCaptureButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mCaptureButton.isChecked()) {
startViewProfiling(false);
} else {
mWeb.stopScrollTest();
}
}
});
// Custom profiling WebView
mWeb.init(this);
mWeb.setWebViewClient(new LoggingWebViewClient());
// URL text entry
mUrl.setOnEditorActionListener(new OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
String url = mUrl.getText().toString();
mWeb.loadUrl(url);
mWeb.requestFocus();
return true;
}
});
setTestingState(TestingState.NOT_TESTING);
}
public void setCallback(ProfileCallback callback) {
mCallback = callback;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWeb.canGoBack()) {
mWeb.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}

View File

@@ -1,252 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import android.content.Context;
import android.os.CountDownTimer;
import android.util.AttributeSet;
import android.util.Log;
import android.webkit.WebSettingsClassic;
import android.webkit.WebView;
import android.webkit.WebViewClassic;
import java.util.ArrayList;
import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
import com.test.tilebenchmark.RunData.TileData;
public class ProfiledWebView extends WebView implements WebViewClassic.PageSwapDelegate {
private static final String LOGTAG = "ProfiledWebView";
private int mSpeed;
private boolean mIsTesting = false;
private boolean mIsScrolling = false;
private ProfileCallback mCallback;
private long mContentInvalMillis;
private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load,
// before test is forced
// ignore anim end events until this many millis after load
private static final long ANIM_SAFETY_THRESHOLD = 200;
private long mLoadTime;
private long mAnimationTime;
public ProfiledWebView(Context context) {
super(context);
}
public ProfiledWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ProfiledWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ProfiledWebView(Context context, AttributeSet attrs, int defStyle,
boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
}
private class JavaScriptInterface {
Context mContext;
/** Instantiate the interface and set the context */
JavaScriptInterface(Context c) {
mContext = c;
}
public void animationComplete() {
mAnimationTime = System.currentTimeMillis();
}
}
public void init(Context c) {
WebSettingsClassic settings = getWebViewClassic().getSettings();
settings.setJavaScriptEnabled(true);
settings.setSupportZoom(true);
settings.setEnableSmoothTransition(true);
settings.setBuiltInZoomControls(true);
settings.setLoadWithOverviewMode(true);
settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
addJavascriptInterface(new JavaScriptInterface(c), "Android");
mAnimationTime = 0;
mLoadTime = 0;
}
public void setUseMinimalMemory(boolean minimal) {
WebSettingsClassic settings = getWebViewClassic().getSettings();
settings.setProperty("use_minimal_memory", minimal ? "true" : "false");
}
public void onPageFinished() {
mLoadTime = System.currentTimeMillis();
}
@Override
protected void onDraw(android.graphics.Canvas canvas) {
if (mIsTesting && mIsScrolling) {
if (canScrollVertically(1)) {
scrollBy(0, mSpeed);
} else {
stopScrollTest();
mIsScrolling = false;
}
}
super.onDraw(canvas);
}
/*
* Called once the page is loaded to start scrolling for evaluating tiles.
* If autoScrolling isn't set, stop must be called manually. Before
* scrolling, invalidate all content and redraw it, measuring time taken.
*/
public void startScrollTest(ProfileCallback callback, boolean autoScrolling) {
mCallback = callback;
mIsTesting = false;
mIsScrolling = false;
WebSettingsClassic settings = getWebViewClassic().getSettings();
settings.setProperty("tree_updates", "0");
if (autoScrolling) {
// after a while, force it to start even if the pages haven't swapped
new CountDownTimer(LOAD_STALL_MILLIS, LOAD_STALL_MILLIS) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
// invalidate all content, and kick off redraw
Log.d("ProfiledWebView",
"kicking off test with callback registration, and tile discard...");
getWebViewClassic().discardAllTextures();
invalidate();
mIsScrolling = true;
mContentInvalMillis = System.currentTimeMillis();
}
}.start();
} else {
mIsTesting = true;
getWebViewClassic().tileProfilingStart();
}
}
/*
* Called after the manual contentInvalidateAll, after the tiles have all
* been redrawn.
* From PageSwapDelegate.
*/
@Override
public void onPageSwapOccurred(boolean startAnim) {
if (!mIsTesting && mIsScrolling) {
// kick off testing
mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis");
mIsTesting = true;
invalidate(); // ensure a redraw so that auto-scrolling can occur
getWebViewClassic().tileProfilingStart();
}
}
private double animFramerate() {
WebSettingsClassic settings = getWebViewClassic().getSettings();
String updatesString = settings.getProperty("tree_updates");
int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString);
long animationTime;
if (mAnimationTime == 0 || mAnimationTime - mLoadTime < ANIM_SAFETY_THRESHOLD) {
animationTime = System.currentTimeMillis() - mLoadTime;
} else {
animationTime = mAnimationTime - mLoadTime;
}
return updates * 1000.0 / animationTime;
}
public void setDoubleBuffering(boolean useDoubleBuffering) {
WebSettingsClassic settings = getWebViewClassic().getSettings();
settings.setProperty("use_double_buffering", useDoubleBuffering ? "true" : "false");
}
/*
* Called once the page has stopped scrolling
*/
public void stopScrollTest() {
getWebViewClassic().tileProfilingStop();
mIsTesting = false;
if (mCallback == null) {
getWebViewClassic().tileProfilingClear();
return;
}
RunData data = new RunData(getWebViewClassic().tileProfilingNumFrames());
// record the time spent (before scrolling) rendering the page
data.singleStats.put(getResources().getString(R.string.render_millis),
(double)mContentInvalMillis);
// record framerate
double framerate = animFramerate();
Log.d(LOGTAG, "anim framerate was "+framerate);
data.singleStats.put(getResources().getString(R.string.animation_framerate),
framerate);
for (int frame = 0; frame < data.frames.length; frame++) {
data.frames[frame] = new TileData[
getWebViewClassic().tileProfilingNumTilesInFrame(frame)];
for (int tile = 0; tile < data.frames[frame].length; tile++) {
int left = getWebViewClassic().tileProfilingGetInt(frame, tile, "left");
int top = getWebViewClassic().tileProfilingGetInt(frame, tile, "top");
int right = getWebViewClassic().tileProfilingGetInt(frame, tile, "right");
int bottom = getWebViewClassic().tileProfilingGetInt(frame, tile, "bottom");
boolean isReady = getWebViewClassic().tileProfilingGetInt(
frame, tile, "isReady") == 1;
int level = getWebViewClassic().tileProfilingGetInt(frame, tile, "level");
float scale = getWebViewClassic().tileProfilingGetFloat(frame, tile, "scale");
data.frames[frame][tile] = data.new TileData(left, top, right, bottom,
isReady, level, scale);
}
}
getWebViewClassic().tileProfilingClear();
mCallback.profileCallback(data);
}
@Override
public void loadUrl(String url) {
mAnimationTime = 0;
mLoadTime = 0;
if (!url.startsWith("http://") && !url.startsWith("file://")) {
url = "http://" + url;
}
super.loadUrl(url);
}
public void setAutoScrollSpeed(int speedInt) {
mSpeed = speedInt;
}
public WebViewClassic getWebViewClassic() {
return WebViewClassic.fromWebView(this);
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (C) 2011 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.test.tilebenchmark;
import java.io.Serializable;
import java.util.HashMap;
public class RunData implements Serializable {
public TileData[][] frames;
public HashMap<String, Double> singleStats = new HashMap<String, Double>();
public RunData(int frames) {
this.frames = new TileData[frames][];
}
public class TileData implements Serializable {
public int left, top, right, bottom;
public boolean isReady;
public int level;
public float scale;
public TileData(int left, int top, int right, int bottom,
boolean isReady, int level, float scale) {
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
this.isReady = isReady;
this.level = level;
this.scale = scale;
}
public String toString() {
return "Tile (" + left + "," + top + ")->("
+ right + "," + bottom + ")"
+ (isReady ? "ready" : "NOTready") + " at scale " + scale;
}
}
}