Merge "Remove WebViewClassic specific test code" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7ef3bfec0a
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 = '▼ ';" +
|
||||
" } else {" +
|
||||
" element.style.display = 'none';" +
|
||||
" triangle.innerHTML = '▶ ';" +
|
||||
" }" +
|
||||
" }" +
|
||||
"</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 + "\">▶ </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("\">■ </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;
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 = " ";
|
||||
|
||||
if (diff.text.endsWith("\n") || forceOutputLine) {
|
||||
lineNums.add(i);
|
||||
/** TODO: Think of better way to replace stuff */
|
||||
line += begSpan + diff.text.replace(" ", " ")
|
||||
+ endSpan + br;
|
||||
lines.add(line);
|
||||
line = "";
|
||||
} else {
|
||||
line += begSpan + diff.text.replace(" ", " ") + 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user