M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
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)
@@ -0,0 +1,39 @@
<?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">
<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:screenOrientation="portrait"/>
<activity android:name="ReliabilityTestActivity" android:screenOrientation="portrait"/>
</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" />
</manifest>
@@ -0,0 +1,4 @@
/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
+320
View File
@@ -0,0 +1,320 @@
#!/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/android/layout_tests
For example, /sdcard/android/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/android/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/android/running_test.txt, if the content is "#DONE", it's done
shell_cmd_str = adb_cmd + " shell cat /sdcard/android/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 = '30000'
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/android/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)
+134
View File
@@ -0,0 +1,134 @@
#!/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/android/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/android/page_cycler/moz/start.html"
sys.exit(1)
else:
path = ' '.join(args);
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 path \"" + path + "\" -e timeout " + timeout_ms
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.")
options, args = option_parser.parse_args();
main(options, args)
+276
View File
@@ -0,0 +1,276 @@
#!/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)
@@ -0,0 +1,501 @@
/*
* 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.MockGeolocation;
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 SET_GEOLOCATION_PERMISSION = 43;
private static final int OVERRIDE_PREFERENCE = 44;
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();
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 SET_GEOLOCATION_PERMISSION:
mLayoutTestController.setGeolocationPermission(
msg.arg1 == 1 ? true : false);
break;
case OVERRIDE_PREFERENCE:
String key = msg.getData().getString("key");
boolean value = msg.getData().getBoolean("value");
mLayoutTestController.overridePreference(key, value);
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).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) {
WebStorage.getInstance().setAppCacheMaximumSize(size);
}
public void setCanOpenWindows() {
obtainMessage(LAYOUT_SET_CAN_OPEN_WINDOWS).sendToTarget();
}
public void setMockGeolocationPosition(double latitude,
double longitude,
double accuracy) {
MockGeolocation.getInstance().setPosition(latitude,
longitude,
accuracy);
}
public void setMockGeolocationError(int code, String message) {
MockGeolocation.getInstance().setError(code, message);
}
public void setGeolocationPermission(boolean allow) {
obtainMessage(SET_GEOLOCATION_PERMISSION, allow ? 1 : 0, 0).sendToTarget();
}
public void overridePreference(String key, boolean value) {
Message message = obtainMessage(OVERRIDE_PREFERENCE);
message.getData().putString("key", key);
message.getData().putBoolean("value", value);
message.sendToTarget();
}
}
@@ -0,0 +1,39 @@
/*
* 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);
}
@@ -0,0 +1,179 @@
/*
* 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 = {
"editing/selection/move-left-right.html", // Causes DumpRenderTree to hang
"fast/js/regexp-charclass-crash.html", // RegExp is too large, causing OOM
"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 HTML5 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("http/tests/appcache/empty-manifest.html"); // flaky
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("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
// Will always fail
ignoreResultList.add("dom/svg/level3/xpath"); // XPath not supported
ignoreResultList.add("fast/workers"); // workers not supported
ignoreResultList.add("fast/xpath"); // XPath not supported
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/private-browsing-readonly.html"); // private browsing not supported
ignoreResultList.add("websocket/tests/workers"); // workers not supported
// 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-properties.html"); // xslt and xpath elements missing from property list
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/gc-9.html"); // requires xpath support
ignoreResultList.add("fast/dom/global-constructors.html"); // requires xslt and xpath support
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
ignoreResultList.add("svg"); // svg is not supported
}
}
@@ -0,0 +1,196 @@
/*
* 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;
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 = "/sdcard/android/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;
}
@@ -0,0 +1,209 @@
/*
* 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.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 HTTP_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/";
static final String HTTPS_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/ssl/";
static final String HTTP_LOCAL_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/local/";
static final String HTTP_MEDIA_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/media/";
static final String HTTP_WML_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/wml/";
private FsUtils() {
//no creation of instances
}
public static void findLayoutTestsRecursively(BufferedOutputStream bos,
String dir, boolean ignoreResultsInDir) throws IOException {
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);
findLayoutTestsRecursively(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.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');
}
}
}
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);
}
}
}
@@ -0,0 +1,33 @@
/*
* 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() {
}
}
@@ -0,0 +1,70 @@
/*
* 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();
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);
}
@@ -0,0 +1,95 @@
/*
* 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 delay_str = (String) icicle.get("delay");
if(delay_str != null) {
try {
this.mDelay = Integer.parseInt(delay_str);
} catch (Exception e) {
}
}
String r = (String)icicle.get("rebaseline");
this.mRebaseline = (r != null && r.toLowerCase().equals("true"));
String logtime = (String) icicle.get("logtime");
this.mLogtime = (logtime != null
&& logtime.toLowerCase().equals("true"));
String drawTime = (String) icicle.get("drawtime");
this.mGetDrawTime = (drawTime != null
&& drawTime.toLowerCase().equals("true"));
mSaveImagePath = (String) icicle.get("saveimage");
mJsEngine = (String) icicle.get("jsengine");
super.onCreate(icicle);
}
public String mTestPath;
public String mSaveImagePath;
public int mTimeoutInMillis;
public int mDelay;
public boolean mRebaseline;
public boolean mLogtime;
public boolean mGetDrawTime;
public String mJsEngine;
}
@@ -0,0 +1,487 @@
/*
* 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.ForwardServer;
import com.android.dumprendertree.forwarder.ForwardService;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
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 resultsPassedFile = new File("/sdcard/layout_tests_passed.txt");
File resultsFailedFile = new File("/sdcard/layout_tests_failed.txt");
File resultsIgnoreResultFile = new File("/sdcard/layout_tests_ignored.txt");
File noExpectedResultFile = new File("/sdcard/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 LAYOUT_TESTS_ROOT = "/sdcard/android/layout_tests/";
static final String LAYOUT_TESTS_RESULT_DIR = "/sdcard/android/layout_tests_results/";
static final String ANDROID_EXPECTED_RESULT_DIR = "/sdcard/android/expected_results/";
static final String LAYOUT_TESTS_LIST_FILE = "/sdcard/android/layout_tests_list.txt";
static final String TEST_STATUS_FILE = "/sdcard/android/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;
public LayoutTestsAutoTest() {
super("com.android.dumprendertree", TestShellActivity.class);
}
// This function writes the result of the layout test to
// Am status so that it can be picked up from a script.
private void passOrFailCallback(String file, boolean result) {
Instrumentation inst = getInstrumentation();
Bundle bundle = new Bundle();
bundle.putBoolean(file, result);
inst.sendStatus(0, bundle);
}
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());
}
}
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()));
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
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);
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) {
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);
}
});
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);
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;
// JSC is the default JavaScript engine.
mJsEngine = runner.mJsEngine == null ? "jsc" : 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.DUMP_AS_TEXT);
// Run tests.
int addr = -1;
try{
addr = AdbUtils.resolve("android-browser-test.mtv.corp.google.com");
} catch (IOException ioe) {
Log.w(LOGTAG, "error while resolving test host name", ioe);
}
if(addr == -1) {
Log.w(LOGTAG, "failed to resolve test host. http tests will fail.");
}
for (int i = 0; i < mTestList.size(); i++) {
String s = mTestList.elementAt(i);
boolean ignoreResult = mTestListIgnoreResult.elementAt(i);
FsUtils.updateTestStatus(TEST_STATUS_FILE, s);
// Run tests
runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis, ignoreResult);
}
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.findLayoutTestsRecursively(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 {
String out_dir = getActivity().getApplicationContext().getCacheDir().getPath() + "/";
for( int i=0; i< LAYOUT_TESTS_RESULTS_REFERENCE_FILES.length; i++) {
InputStream in = getActivity().getAssets().open(LAYOUT_TESTS_RESULTS_REFERENCE_FILES[i]);
OutputStream out = new FileOutputStream(out_dir + 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();
}
}
}
@@ -0,0 +1,227 @@
/*
* 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 dalvik.system.VMRuntime;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.os.Debug;
import android.os.Process;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> {
private final static String LOGTAG = "LoadTest";
private final static String LOAD_TEST_RESULT = "/sdcard/load_test_result.txt";
private boolean mFinished;
static final String LOAD_TEST_RUNNER_FILES[] = {
"run_page_cycler.py"
};
public LoadTestsAutoTest() {
super("com.android.dumprendertree", 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);
}
// Invokes running of layout tests
// and waits till it has finished running.
public void runPageCyclerTest() {
LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
if (runner.mTestPath == null) {
Log.e(LOGTAG, "No test specified");
return;
}
TestShellActivity activity = (TestShellActivity) getActivity();
Log.v(LOGTAG, "About to run tests, calling gc first...");
freeMem();
// Run tests
runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis,
runner.mGetDrawTime, runner.mSaveImagePath);
activity.clearCache();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
dumpMemoryInfo();
// Kill activity
activity.finish();
}
private void freeMem() {
Log.v(LOGTAG, "freeMem: calling gc/finalization...");
final VMRuntime runtime = VMRuntime.getRuntime();
runtime.gcSoftReferences();
runtime.runFinalizationSync();
runtime.gcSoftReferences();
runtime.runFinalizationSync();
runtime.gcSoftReferences();
runtime.runFinalizationSync();
Runtime.getRuntime().runFinalization();
Runtime.getRuntime().gc();
Runtime.getRuntime().gc();
}
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,
boolean getDrawTime, String saveImagePath) {
activity.setCallback(new TestShellCallback() {
public void finished() {
synchronized (LoadTestsAutoTest.this) {
mFinished = true;
LoadTestsAutoTest.this.notifyAll();
}
}
public void timedOut(String url) {
}
});
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);
intent.putExtra(TestShellActivity.GET_DRAW_TIME, getDrawTime);
if (saveImagePath != null)
intent.putExtra(TestShellActivity.SAVE_IMAGE, saveImagePath);
activity.startActivity(intent);
// Wait until done.
synchronized (this) {
while(!mFinished) {
try {
this.wait();
} catch (InterruptedException e) { }
}
}
}
public void copyRunnerAssetsToCache() {
try {
String out_dir = getActivity().getApplicationContext()
.getCacheDir().getPath() + "/";
for( int i=0; i< LOAD_TEST_RUNNER_FILES.length; i++) {
InputStream in = getActivity().getAssets().open(
LOAD_TEST_RUNNER_FILES[i]);
OutputStream out = new FileOutputStream(
out_dir + 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();
}
}
}
@@ -0,0 +1,79 @@
/*
* 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.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 = "/sdcard/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);
startActivity(intent);
}
@Override
void processDirectory(String path, boolean selection) {
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);
startActivity(intent);
}
private void generateTestList(String path) {
try {
File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
FsUtils.findLayoutTestsRecursively(bos, path, false); // Don't ignore results
bos.flush();
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Error when creating test list: " + e.getMessage());
}
}
}
@@ -0,0 +1,189 @@
/*
* 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.content.Intent;
import android.os.Handler;
import android.os.Message;
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;
public class ReliabilityTest extends ActivityInstrumentationTestCase2<ReliabilityTestActivity> {
private static final String LOGTAG = "ReliabilityTest";
private static final String PKG_NAME = "com.android.dumprendertree";
private static final String TEST_LIST_FILE = "/sdcard/android/reliability_tests_list.txt";
private static final String TEST_STATUS_FILE = "/sdcard/android/reliability_running_test.txt";
private static final String TEST_TIMEOUT_FILE = "/sdcard/android/reliability_timeout_test.txt";
private static final String TEST_LOAD_TIME_FILE = "/sdcard/android/reliability_load_time.txt";
private static final String TEST_DONE = "#DONE";
static final String RELIABILITY_TEST_RUNNER_FILES[] = {
"run_reliability_tests.py"
};
public ReliabilityTest() {
super(PKG_NAME, ReliabilityTestActivity.class);
}
public void runReliabilityTest() throws Throwable {
// ReliabilityTestActivity activity = getActivity();
LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner)getInstrumentation();
File testListFile = new File(TEST_LIST_FILE);
if(!testListFile.exists())
throw new FileNotFoundException("test list file not found.");
BufferedReader listReader = new BufferedReader(
new FileReader(testListFile));
//always try to resume first, hence cleaning up status will be the
//responsibility of driver scripts
String lastUrl = FsUtils.readTestStatus(TEST_STATUS_FILE);
if(lastUrl != null && !TEST_DONE.equals(lastUrl))
fastForward(listReader, lastUrl);
String url = null;
Handler handler = null;
boolean timeoutFlag = false;
long start, elapsed;
Intent intent = new Intent(runner.getContext(), ReliabilityTestActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ReliabilityTestActivity activity = (ReliabilityTestActivity)runner.startActivitySync(
intent);
//read from BufferedReader instead of populating a list in advance,
//this will avoid excessive memory usage in case of a large list
while((url = listReader.readLine()) != null) {
url = url.trim();
if(url.length() == 0)
continue;
start = System.currentTimeMillis();
Log.v(LOGTAG, "Testing URL: " + url);
FsUtils.updateTestStatus(TEST_STATUS_FILE, url);
activity.reset();
//use message to send new URL to avoid interacting with
//WebView in non-UI thread
handler = activity.getHandler();
Message msg = handler.obtainMessage(
ReliabilityTestActivity.MSG_NAVIGATE,
runner.mTimeoutInMillis, runner.mDelay);
msg.getData().putString(ReliabilityTestActivity.MSG_NAV_URL, url);
msg.getData().putBoolean(ReliabilityTestActivity.MSG_NAV_LOGTIME,
runner.mLogtime);
handler.sendMessage(msg);
timeoutFlag = activity.waitUntilDone();
elapsed = System.currentTimeMillis() - start;
if(elapsed < 1000) {
Log.w(LOGTAG, "Page load finished in " + elapsed
+ "ms, too soon?");
} else {
Log.v(LOGTAG, "Page load finished in " + elapsed + "ms");
}
if(timeoutFlag) {
writeTimeoutFile(url);
}
if(runner.mLogtime) {
writeLoadTime(url, activity.getPageLoadTime());
}
System.runFinalization();
System.gc();
System.gc();
}
activity.finish();
FsUtils.updateTestStatus(TEST_STATUS_FILE, TEST_DONE);
// activity.finish();
listReader.close();
}
public void copyRunnerAssetsToCache() {
try {
String out_dir = getActivity().getApplicationContext()
.getCacheDir().getPath() + "/";
for( int i=0; i< RELIABILITY_TEST_RUNNER_FILES.length; i++) {
InputStream in = getActivity().getAssets().open(
RELIABILITY_TEST_RUNNER_FILES[i]);
OutputStream out = new FileOutputStream(
out_dir + RELIABILITY_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) {
Log.e(LOGTAG, "Cannot extract scripts for testing.", e);
}
}
private void fastForward(BufferedReader testListReader, String lastUrl) {
//fastforward the BufferedReader to the position right after last url
if(lastUrl == null)
return;
String line = null;
try {
while((line = testListReader.readLine()) != null) {
if(lastUrl.equals(line))
return;
}
} catch (IOException ioe) {
Log.e(LOGTAG, "Error while reading test list.", ioe);
return;
}
}
private void writeTimeoutFile(String s) {
//append to the file containing the list of timeout urls
try {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(TEST_TIMEOUT_FILE, true));
bos.write(s.getBytes());
bos.write('\n');
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Cannot update file " + TEST_TIMEOUT_FILE, e);
}
}
private void writeLoadTime(String s, long time) {
//append to the file containing the list of timeout urls
try {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(TEST_LOAD_TIME_FILE, true));
bos.write((s + '|' + time + '\n').getBytes());
bos.close();
} catch (Exception e) {
Log.e(LOGTAG, "Cannot update file " + TEST_LOAD_TIME_FILE, e);
}
}
}
@@ -0,0 +1,307 @@
/*
* 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.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 (ActivityThread.currentActivityThread() != null) {
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);
}
}
}
@@ -0,0 +1,869 @@
/*
* 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.Intent;
import android.content.DialogInterface.OnClickListener;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ViewGroup;
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.WebStorage;
import android.webkit.WebView;
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.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";
public class AsyncHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_TIMEOUT) {
mTimedOut = true;
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) {
TestShellActivity.this.dump(mTimedOut, (String)msg.obj);
return;
}
super.handleMessage(msg);
}
}
public void requestWebKitData() {
Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
if (mRequestedWebKitData)
throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
mRequestedWebKitData = true;
Log.v(LOGTAG, "message sent to WebView to dump text.");
switch (mDumpDataType) {
case DUMP_AS_TEXT:
mWebView.documentAsText(callback);
break;
case EXT_REPR:
mWebView.externalRepresentation(callback);
break;
default:
finished();
break;
}
}
public void clearCache() {
mWebView.freeMemory();
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout contentView = new LinearLayout(this);
contentView.setOrientation(LinearLayout.VERTICAL);
setContentView(contentView);
mWebView = new WebView(this);
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.
mWebView.setJsFlags("--expose_gc");
mHandler = new AsyncHandler();
Intent intent = getIntent();
if (intent != null) {
executeIntent(intent);
}
}
@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;
}
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);
mGetDrawtime = intent.getBooleanExtra(GET_DRAW_TIME, false);
mSaveImagePath = intent.getStringExtra(SAVE_IMAGE);
Log.v(LOGTAG, " Loading " + mTestUrl);
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(){
public void onClick(DialogInterface dialog, int which) {
TestShellActivity.this.finish();
}
})
.setNegativeButton("No", new OnClickListener(){
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(TIMEOUT_IN_MILLIS, 10000);
executeIntent(intent);
}
@Override
protected void onStop() {
super.onStop();
mWebView.stopLoading();
}
@Override
protected void onDestroy() {
super.onDestroy();
mWebView.destroy();
mWebView = 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;
}
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
public void dumpAsText() {
mDumpDataType = DumpDataType.DUMP_AS_TEXT;
if (mWebView != null) {
String url = mWebView.getUrl();
Log.v(LOGTAG, "dumpAsText called: "+url);
}
}
public void waitUntilDone() {
mWaitUntilDone = true;
String url = mWebView.getUrl();
Log.v(LOGTAG, "waitUntilDone called: " + url);
}
public void notifyDone() {
String url = mWebView.getUrl();
Log.v(LOGTAG, "notifyDone called: " + url);
if (mWaitUntilDone) {
mWaitUntilDone = false;
mChromeClient.onProgressChanged(mWebView, 101);
}
}
public void display() {
mWebView.invalidate();
}
public void clearBackForwardList() {
mWebView.clearHistory();
}
public void dumpBackForwardList() {
//printf("\n============== Back Forward List ==============\n");
// mWebHistory
//printf("===============================================\n");
}
public void dumpChildFrameScrollPositions() {
// TODO Auto-generated method stub
}
public void dumpEditingCallbacks() {
// TODO Auto-generated method stub
}
public void dumpSelectionRect() {
// TODO Auto-generated method stub
}
public void dumpTitleChanges() {
if (!mDumpTitleChanges) {
mTitleChanges = new StringBuffer();
}
mDumpTitleChanges = true;
}
public void keepWebHistory() {
if (!mKeepWebHistory) {
mWebHistory = new Vector();
}
mKeepWebHistory = true;
}
public void queueBackNavigation(int howfar) {
// TODO Auto-generated method stub
}
public void queueForwardNavigation(int howfar) {
// TODO Auto-generated method stub
}
public void queueLoad(String Url, String frameTarget) {
// TODO Auto-generated method stub
}
public void queueReload() {
mWebView.reload();
}
public void queueScript(String scriptToRunInCurrentContext) {
mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
}
public void repaintSweepHorizontally() {
// TODO Auto-generated method stub
}
public void setAcceptsEditing(boolean b) {
// TODO Auto-generated method stub
}
public void setMainFrameIsFirstResponder(boolean b) {
// TODO Auto-generated method stub
}
public void setWindowIsKey(boolean b) {
// This is meant to show/hide the window. The best I can find
// is setEnabled()
mWebView.setEnabled(b);
}
public void testRepaint() {
mWebView.invalidate();
}
public void dumpDatabaseCallbacks() {
Log.v(LOGTAG, "dumpDatabaseCallbacks called.");
mDumpDatabaseCallbacks = true;
}
public void setCanOpenWindows() {
Log.v(LOGTAG, "setCanOpenWindows called.");
mCanOpenWindows = true;
}
/**
* Sets the Geolocation permission state to be used for all future requests.
*/
public void setGeolocationPermission(boolean allow) {
mGeolocationPermissionSet = true;
mGeolocationPermission = allow;
}
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 (key.equals(WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED)) {
mWebView.getSettings().setAppCacheEnabled(value);
}
}
private final WebViewClient mViewClient = new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
Log.v(LOGTAG, "onPageFinished, url=" + url);
mPageFinished = true;
// get page draw time
if (FsUtils.isTestPageUrl(url)) {
if (mGetDrawtime) {
long[] times = new long[DRAW_RUNS];
times = getDrawWebViewTime(mWebView, DRAW_RUNS);
FsUtils.writeDrawTime(DRAW_TIME_LOG, url, times);
}
if (mSaveImagePath != null) {
String name = FsUtils.getLastSegmentInPath(url);
drawPageToFile(mSaveImagePath + "/" + name + ".png", mWebView);
}
}
// 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.
if (finished()) {
return;
}
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 onProgressChanged(WebView view, int newProgress) {
// notifyDone calls this with 101%. We only want to update this flag if this
// is the real call from WebCore.
if (newProgress == 100) {
mOneHundredPercentComplete = true;
}
// With the flag updated, we can now proceed as normal whether the progress update came from
// WebCore or notifyDone.
if (newProgress >= 100) {
// finished() will check if we are ready to move to the next test and do so if we are.
if (finished()) {
return;
}
if (!mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) {
String url = mWebView.getUrl();
Log.v(LOGTAG, "Finished: "+ url);
requestWebKitData();
} else {
String url = mWebView.getUrl();
if (mTimedOut) {
Log.v(LOGTAG, "Timed out before finishing: " + url);
} else if (mWaitUntilDone) {
Log.v(LOGTAG, "Waiting for notifyDone: " + url);
} else if (mRequestedWebKitData) {
Log.v(LOGTAG, "Requested webkit data ready: " + url);
}
}
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
if (title.length() > 30)
title = "..."+title.substring(title.length()-30);
setTitle(title);
if (mDumpTitleChanges) {
mTitleChanges.append("TITLE CHANGED: ");
mTitleChanges.append(title);
mTitleChanges.append("\n");
}
}
@Override
public boolean onJsAlert(WebView view, String url, String message,
JsResult result) {
if (mDialogStrings == null) {
mDialogStrings = new StringBuffer();
}
mDialogStrings.append("ALERT: ");
mDialogStrings.append(message);
mDialogStrings.append('\n');
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message,
JsResult result) {
if (mDialogStrings == null) {
mDialogStrings = new StringBuffer();
}
mDialogStrings.append("CONFIRM: ");
mDialogStrings.append(message);
mDialogStrings.append('\n');
result.confirm();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
if (mDialogStrings == null) {
mDialogStrings = new StringBuffer();
}
mDialogStrings.append("PROMPT: ");
mDialogStrings.append(message);
mDialogStrings.append(", default text: ");
mDialogStrings.append(defaultValue);
mDialogStrings.append('\n');
result.confirm();
return true;
}
@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);
}
/**
* Instructs the client to show a prompt to ask the user to set the
* Geolocation permission state for the specified origin.
*/
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
if (mGeolocationPermissionSet) {
callback.invoke(origin, mGeolocationPermission, false);
}
}
@Override
public void onConsoleMessage(String message, int lineNumber,
String sourceID) {
if (mConsoleMessages == null) {
mConsoleMessages = new StringBuffer();
}
String consoleMessage = "CONSOLE MESSAGE: line "
+ lineNumber +": "+ message +"\n";
mConsoleMessages.append(consoleMessage);
Log.v(LOGTAG, "LOG: "+consoleMessage);
}
@Override
public boolean onCreateWindow(WebView view, boolean dialog,
boolean userGesture, Message resultMsg) {
if (!mCanOpenWindows) {
// 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);
}
}
private void resetTestStatus() {
mWaitUntilDone = false;
mDumpDataType = mDefaultDumpDataType;
mTimedOut = false;
mDumpTitleChanges = false;
mRequestedWebKitData = false;
mDumpDatabaseCallbacks = false;
mCanOpenWindows = false;
mEventSender.resetMouse();
mEventSender.clearTouchPoints();
mEventSender.clearTouchMetaState();
mPageFinished = false;
mOneHundredPercentComplete = false;
mDumpWebKitData = false;
mGetDrawtime = false;
mSaveImagePath = null;
}
private long[] getDrawWebViewTime(WebView view, int count) {
if (count == 0)
return null;
long[] ret = new long[count];
long start;
Canvas canvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);
canvas.setBitmap(bitmap);
for (int i = 0; i < count; i++) {
start = System.currentTimeMillis();
view.draw(canvas);
ret[i] = System.currentTimeMillis() - start;
}
return ret;
}
private void drawPageToFile(String fileName, WebView view) {
Canvas canvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(view.getContentWidth(), view.getContentHeight(),
Config.ARGB_8888);
canvas.setBitmap(bitmap);
view.drawPage(canvas);
try {
FileOutputStream fos = new FileOutputStream(fileName);
if(!bitmap.compress(CompressFormat.PNG, 90, fos)) {
Log.w(LOGTAG, "Failed to compress and save image.");
}
} catch (IOException ioe) {
Log.e(LOGTAG, "", ioe);
}
bitmap.recycle();
}
private boolean canMoveToNextTest() {
return (mDumpWebKitData && mOneHundredPercentComplete && mPageFinished && !mWaitUntilDone) || mTimedOut;
}
private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
if (webview == null) {
return;
}
WebSettings settings = webview.getSettings();
settings.setAppCacheEnabled(true);
settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
settings.setAppCacheMaxSize(Long.MAX_VALUE);
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setSupportMultipleWindows(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
settings.setDatabaseEnabled(true);
settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
settings.setDomStorageEnabled(true);
settings.setWorkersEnabled(false);
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.
webview.setTouchInterval(-1);
}
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 String mSaveImagePath;
private BufferedReader mTestListReader;
private boolean mGetDrawtime;
// 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 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;
private boolean mOneHundredPercentComplete = false;
static final String TIMEOUT_STR = "**Test timeout";
static final int MSG_TIMEOUT = 0;
static final int MSG_WEBKIT_DATA = 1;
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 int DRAW_RUNS = 5;
static final String DRAW_TIME_LOG = "/sdcard/android/page_draw_time.txt";
private boolean mGeolocationPermissionSet;
private boolean mGeolocationPermission;
}
@@ -0,0 +1,22 @@
/*
* 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 timedOut(String url);
}
@@ -0,0 +1,346 @@
/*
* 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;
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() {
// We only support single touch so examine the first touch point only.
// If multi touch is enabled in the future, we need to re-examine this to send
// all the touch points with the event.
TouchPoint tp = mTouchPoints.get(0);
if (tp == null) {
return;
}
tp.setDownTime(SystemClock.uptimeMillis());
MotionEvent event = MotionEvent.obtain(tp.downTime(), tp.downTime(),
MotionEvent.ACTION_DOWN, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
}
public void touchMove() {
TouchPoint tp = mTouchPoints.get(0);
if (tp == null) {
return;
}
if (!tp.hasMoved()) {
return;
}
MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
tp.setMoved(false);
}
public void touchEnd() {
TouchPoint tp = mTouchPoints.get(0);
if (tp == null) {
return;
}
MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
if (tp.isReleased()) {
mTouchPoints.remove(0);
}
}
public void touchCancel() {
TouchPoint tp = mTouchPoints.get(0);
if (tp == null) {
return;
}
if (tp.cancelled()) {
MotionEvent event = MotionEvent.obtain(tp.downTime(), SystemClock.uptimeMillis(),
MotionEvent.ACTION_CANCEL, tp.getX(), tp.getY(), mTouchMetaState);
mWebView.onTouchEvent(event);
}
}
public void cancelTouchPoint(int id) {
TouchPoint tp = mTouchPoints.get(0);
if (tp == null) {
return;
}
tp.cancel();
}
public void addTouchPoint(int x, int y) {
mTouchPoints.add(new TouchPoint(contentsToWindowX(x), contentsToWindowY(y)));
if (mTouchPoints.size() > 1) {
Log.w(LOGTAG, "Adding more than one touch point, but multi touch is not supported!");
}
}
public void updateTouchPoint(int id, int x, int y) {
TouchPoint tp = mTouchPoints.get(0);
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(0);
if (tp == null) {
return;
}
tp.release();
}
public void clearTouchPoints() {
mTouchPoints.clear();
}
public void clearTouchMetaState() {
mTouchMetaState = 0;
}
private int contentsToWindowX(int x) {
return (int) (x * mWebView.getScale()) - mWebView.getScrollX();
}
private int contentsToWindowY(int y) {
return (int) (y * mWebView.getScale()) - mWebView.getScrollY();
}
private WebView mWebView = null;
private int mouseX;
private int mouseY;
private class TouchPoint {
private int mX;
private int mY;
private long mDownTime;
private boolean mReleased;
private boolean mMoved;
private boolean mCancelled;
public TouchPoint(int x, int y) {
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 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;
}
@@ -0,0 +1,127 @@
/*
* 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;
}
}
}
@@ -0,0 +1,133 @@
/*
* 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);
}
}
}
}
@@ -0,0 +1,115 @@
/*
* 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.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 = "/sdcard/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;
}
}
@@ -0,0 +1,108 @@
/*
* 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";
public Forwarder (Socket from, Socket to, ForwardServer server) {
this.server = server;
this.from = from;
this.to = to;
server.register(this);
}
public void start() {
Thread outgoing = new Thread(new SocketPipe(from, to));
Thread incoming = new Thread(new SocketPipe(to, from));
outgoing.setName(LOGTAG);
incoming.setName(LOGTAG);
outgoing.start();
incoming.start();
}
public void stop() {
shutdown(from);
shutdown(to);
}
private void shutdown(Socket socket) {
try {
socket.shutdownInput();
} catch (IOException e) {
Log.v(LOGTAG, "Socket#shutdownInput", e);
}
try {
socket.shutdownOutput();
} catch (IOException e) {
Log.v(LOGTAG, "Socket#shutdownOutput", e);
}
try {
socket.close();
} catch (IOException e) {
Log.v(LOGTAG, "Socket#close", e);
}
}
private class SocketPipe implements Runnable {
private Socket in, out;
public SocketPipe(Socket in, Socket out) {
this.in = in;
this.out = out;
}
public void run() {
try {
int length;
InputStream is = in.getInputStream();
OutputStream os = out.getOutputStream();
byte[] buffer = new byte[4096];
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
} catch (IOException ioe) {
} finally {
server.unregister(Forwarder.this);
}
}
@Override
public String toString() {
return "SocketPipe{" + in + "=>" + out + "}";
}
}
}