import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,139 @@
#!/bin/bash
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# A collection of functions useful for maintaining android devices
# Run an adb command on all connected device in parallel.
# Usage: adb_all command line to eval. Quoting is optional.
#
# Examples:
# adb_all install Chrome.apk
# adb_all 'shell cat /path/to/file'
#
adb_all() {
if [[ $# == 0 ]]; then
echo "Usage: adb_all <adb command>. Quoting is optional."
echo "Example: adb_all install Chrome.apk"
return 1
fi
local DEVICES=$(adb_get_devices -b)
local NUM_DEVICES=$(echo $DEVICES | wc -w)
if (( $NUM_DEVICES > 1 )); then
echo "Looping over $NUM_DEVICES devices"
fi
_adb_multi "$DEVICES" "$*"
}
# Run a command on each connected device. Quoting the command is suggested but
# not required. The script setups up variable DEVICE to correspond to the
# current serial number. Intended for complex one_liners that don't work in
# adb_all
# Usage: adb_device_loop 'command line to eval'
adb_device_loop() {
if [[ $# == 0 ]]; then
echo "Intended for more complex one-liners that cannot be done with" \
"adb_all."
echo 'Usage: adb_device_loop "echo $DEVICE: $(adb root &&' \
'adb shell cat /data/local.prop)"'
return 1
fi
local DEVICES=$(adb_get_devices)
if [[ -z $DEVICES ]]; then
return
fi
# Do not change DEVICE variable name - part of api
for DEVICE in $DEVICES; do
DEV_TYPE=$(adb -s $DEVICE shell getprop ro.product.device | sed 's/\r//')
echo "Running on $DEVICE ($DEV_TYPE)"
ANDROID_SERIAL=$DEVICE eval "$*"
done
}
# Erases data from any devices visible on adb. To preserve a device,
# disconnect it or:
# 1) Reboot it into fastboot with 'adb reboot bootloader'
# 2) Run wipe_all_devices to wipe remaining devices
# 3) Restore device it with 'fastboot reboot'
#
# Usage: wipe_all_devices [-f]
#
wipe_all_devices() {
if [[ -z $(which adb) || -z $(which fastboot) ]]; then
echo "aborting: adb and fastboot not in path"
return 1
elif ! $(groups | grep -q 'plugdev'); then
echo "If fastboot fails, run: 'sudo adduser $(whoami) plugdev'"
fi
local DEVICES=$(adb_get_devices -b)
if [[ $1 != '-f' ]]; then
echo "This will ERASE ALL DATA from $(echo $DEVICES | wc -w) device."
read -p "Hit enter to continue"
fi
_adb_multi "$DEVICES" "reboot bootloader"
# Subshell to isolate job list
(
for DEVICE in $DEVICES; do
fastboot_erase $DEVICE &
done
wait
)
# Reboot devices together
for DEVICE in $DEVICES; do
fastboot -s $DEVICE reboot
done
}
# Wipe a device in fastboot.
# Usage fastboot_erase [serial]
fastboot_erase() {
if [[ -n $1 ]]; then
echo "Wiping $1"
local SERIAL="-s $1"
else
if [ -z $(fastboot devices) ]; then
echo "No devices in fastboot, aborting."
echo "Check out wipe_all_devices to see if sufficient"
echo "You can put a device in fastboot using adb reboot bootloader"
return 1
fi
local SERIAL=""
fi
fastboot $SERIAL erase cache
fastboot $SERIAL erase userdata
}
# Get list of devices connected via adb
# Args: -b block until adb detects a device
adb_get_devices() {
local DEVICES="$(adb devices | grep 'device$')"
if [[ -z $DEVICES && $1 == '-b' ]]; then
echo '- waiting for device -' >&2
local DEVICES="$(adb wait-for-device devices | grep 'device$')"
fi
echo "$DEVICES" | awk -vORS=' ' '{print $1}' | sed 's/ $/\n/'
}
###################################################
## HELPER FUNCTIONS
###################################################
# Run an adb command in parallel over a device list
_adb_multi() {
local DEVICES=$1
local ADB_ARGS=$2
(
for DEVICE in $DEVICES; do
adb -s $DEVICE $ADB_ARGS &
done
wait
)
}

View file

@ -0,0 +1,24 @@
#!/bin/bash
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Kill a running content shell.
#
# Assumes you have sourced the build/android/envsetup.sh script.
SHELL_PID_LINES=$(adb shell ps | grep ' org.chromium.content_shell')
VAL=$(echo "$SHELL_PID_LINES" | wc -l)
if [ $VAL -lt 1 ] ; then
echo "Not running Content shell."
else
SHELL_PID=$(echo $SHELL_PID_LINES | awk '{print $2}')
if [ "$SHELL_PID" != "" ] ; then
set -x
adb shell kill $SHELL_PID
set -
else
echo "Content shell does not appear to be running."
fi
fi

View file

@ -0,0 +1,155 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Saves logcats from all connected devices.
Usage: adb_logcat_monitor.py <base_dir> [<adb_binary_path>]
This script will repeatedly poll adb for new devices and save logcats
inside the <base_dir> directory, which it attempts to create. The
script will run until killed by an external signal. To test, run the
script in a shell and <Ctrl>-C it after a while. It should be
resilient across phone disconnects and reconnects and start the logcat
early enough to not miss anything.
"""
import logging
import os
import re
import shutil
import signal
import subprocess
import sys
import time
# Map from device_id -> (process, logcat_num)
devices = {}
class TimeoutException(Exception):
"""Exception used to signal a timeout."""
pass
class SigtermError(Exception):
"""Exception used to catch a sigterm."""
pass
def StartLogcatIfNecessary(device_id, adb_cmd, base_dir):
"""Spawns a adb logcat process if one is not currently running."""
process, logcat_num = devices[device_id]
if process:
if process.poll() is None:
# Logcat process is still happily running
return
else:
logging.info('Logcat for device %s has died', device_id)
error_filter = re.compile('- waiting for device -')
for line in process.stderr:
if not error_filter.match(line):
logging.error(device_id + ': ' + line)
logging.info('Starting logcat %d for device %s', logcat_num,
device_id)
logcat_filename = 'logcat_%s_%03d' % (device_id, logcat_num)
logcat_file = open(os.path.join(base_dir, logcat_filename), 'w')
process = subprocess.Popen([adb_cmd, '-s', device_id,
'logcat', '-v', 'threadtime'],
stdout=logcat_file,
stderr=subprocess.PIPE)
devices[device_id] = (process, logcat_num + 1)
def GetAttachedDevices(adb_cmd):
"""Gets the device list from adb.
We use an alarm in this function to avoid deadlocking from an external
dependency.
Args:
adb_cmd: binary to run adb
Returns:
list of devices or an empty list on timeout
"""
signal.alarm(2)
try:
out, err = subprocess.Popen([adb_cmd, 'devices'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
if err:
logging.warning('adb device error %s', err.strip())
return re.findall('^(\w+)\tdevice$', out, re.MULTILINE)
except TimeoutException:
logging.warning('"adb devices" command timed out')
return []
except (IOError, OSError):
logging.exception('Exception from "adb devices"')
return []
finally:
signal.alarm(0)
def main(base_dir, adb_cmd='adb'):
"""Monitor adb forever. Expects a SIGINT (Ctrl-C) to kill."""
# We create the directory to ensure 'run once' semantics
if os.path.exists(base_dir):
print 'adb_logcat_monitor: %s already exists? Cleaning' % base_dir
shutil.rmtree(base_dir, ignore_errors=True)
os.makedirs(base_dir)
logging.basicConfig(filename=os.path.join(base_dir, 'eventlog'),
level=logging.INFO,
format='%(asctime)-2s %(levelname)-8s %(message)s')
# Set up the alarm for calling 'adb devices'. This is to ensure
# our script doesn't get stuck waiting for a process response
def TimeoutHandler(_, unused_frame):
raise TimeoutException()
signal.signal(signal.SIGALRM, TimeoutHandler)
# Handle SIGTERMs to ensure clean shutdown
def SigtermHandler(_, unused_frame):
raise SigtermError()
signal.signal(signal.SIGTERM, SigtermHandler)
logging.info('Started with pid %d', os.getpid())
pid_file_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
try:
with open(pid_file_path, 'w') as f:
f.write(str(os.getpid()))
while True:
for device_id in GetAttachedDevices(adb_cmd):
if not device_id in devices:
devices[device_id] = (None, 0)
for device in devices:
# This will spawn logcat watchers for any device ever detected
StartLogcatIfNecessary(device, adb_cmd, base_dir)
time.sleep(5)
except SigtermError:
logging.info('Received SIGTERM, shutting down')
except:
logging.exception('Unexpected exception in main.')
finally:
for process, _ in devices.itervalues():
if process:
try:
process.terminate()
except OSError:
pass
os.remove(pid_file_path)
if __name__ == '__main__':
if 2 <= len(sys.argv) <= 3:
print 'adb_logcat_monitor: Initializing'
sys.exit(main(*sys.argv[1:3]))
print 'Usage: %s <base_dir> [<adb_binary_path>]' % sys.argv[0]

View file

@ -0,0 +1,202 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Shutdown adb_logcat_monitor and print accumulated logs.
To test, call './adb_logcat_printer.py <base_dir>' where
<base_dir> contains 'adb logcat -v threadtime' files named as
logcat_<deviceID>_<sequenceNum>
The script will print the files to out, and will combine multiple
logcats from a single device if there is overlap.
Additionally, if a <base_dir>/LOGCAT_MONITOR_PID exists, the script
will attempt to terminate the contained PID by sending a SIGINT and
monitoring for the deletion of the aforementioned file.
"""
import cStringIO
import logging
import os
import re
import signal
import sys
import time
# Set this to debug for more verbose output
LOG_LEVEL = logging.INFO
def CombineLogFiles(list_of_lists, logger):
"""Splices together multiple logcats from the same device.
Args:
list_of_lists: list of pairs (filename, list of timestamped lines)
logger: handler to log events
Returns:
list of lines with duplicates removed
"""
cur_device_log = ['']
for cur_file, cur_file_lines in list_of_lists:
# Ignore files with just the logcat header
if len(cur_file_lines) < 2:
continue
common_index = 0
# Skip this step if list just has empty string
if len(cur_device_log) > 1:
try:
line = cur_device_log[-1]
# Used to make sure we only splice on a timestamped line
if re.match('^\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3} ', line):
common_index = cur_file_lines.index(line)
else:
logger.warning('splice error - no timestamp in "%s"?', line.strip())
except ValueError:
# The last line was valid but wasn't found in the next file
cur_device_log += ['***** POSSIBLE INCOMPLETE LOGCAT *****']
logger.info('Unable to splice %s. Incomplete logcat?', cur_file)
cur_device_log += ['*'*30 + ' %s' % cur_file]
cur_device_log.extend(cur_file_lines[common_index:])
return cur_device_log
def FindLogFiles(base_dir):
"""Search a directory for logcat files.
Args:
base_dir: directory to search
Returns:
Mapping of device_id to a sorted list of file paths for a given device
"""
logcat_filter = re.compile('^logcat_(\w+)_(\d+)$')
# list of tuples (<device_id>, <seq num>, <full file path>)
filtered_list = []
for cur_file in os.listdir(base_dir):
matcher = logcat_filter.match(cur_file)
if matcher:
filtered_list += [(matcher.group(1), int(matcher.group(2)),
os.path.join(base_dir, cur_file))]
filtered_list.sort()
file_map = {}
for device_id, _, cur_file in filtered_list:
if not device_id in file_map:
file_map[device_id] = []
file_map[device_id] += [cur_file]
return file_map
def GetDeviceLogs(log_filenames, logger):
"""Read log files, combine and format.
Args:
log_filenames: mapping of device_id to sorted list of file paths
logger: logger handle for logging events
Returns:
list of formatted device logs, one for each device.
"""
device_logs = []
for device, device_files in log_filenames.iteritems():
logger.debug('%s: %s', device, str(device_files))
device_file_lines = []
for cur_file in device_files:
with open(cur_file) as f:
device_file_lines += [(cur_file, f.read().splitlines())]
combined_lines = CombineLogFiles(device_file_lines, logger)
# Prepend each line with a short unique ID so it's easy to see
# when the device changes. We don't use the start of the device
# ID because it can be the same among devices. Example lines:
# AB324: foo
# AB324: blah
device_logs += [('\n' + device[-5:] + ': ').join(combined_lines)]
return device_logs
def ShutdownLogcatMonitor(base_dir, logger):
"""Attempts to shutdown adb_logcat_monitor and blocks while waiting."""
try:
monitor_pid_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
with open(monitor_pid_path) as f:
monitor_pid = int(f.readline())
logger.info('Sending SIGTERM to %d', monitor_pid)
os.kill(monitor_pid, signal.SIGTERM)
i = 0
while True:
time.sleep(.2)
if not os.path.exists(monitor_pid_path):
return
if not os.path.exists('/proc/%d' % monitor_pid):
logger.warning('Monitor (pid %d) terminated uncleanly?', monitor_pid)
return
logger.info('Waiting for logcat process to terminate.')
i += 1
if i >= 10:
logger.warning('Monitor pid did not terminate. Continuing anyway.')
return
except (ValueError, IOError, OSError):
logger.exception('Error signaling logcat monitor - continuing')
def main(base_dir, output_file):
log_stringio = cStringIO.StringIO()
logger = logging.getLogger('LogcatPrinter')
logger.setLevel(LOG_LEVEL)
sh = logging.StreamHandler(log_stringio)
sh.setFormatter(logging.Formatter('%(asctime)-2s %(levelname)-8s'
' %(message)s'))
logger.addHandler(sh)
try:
# Wait at least 5 seconds after base_dir is created before printing.
#
# The idea is that 'adb logcat > file' output consists of 2 phases:
# 1 Dump all the saved logs to the file
# 2 Stream log messages as they are generated
#
# We want to give enough time for phase 1 to complete. There's no
# good method to tell how long to wait, but it usually only takes a
# second. On most bots, this code path won't occur at all, since
# adb_logcat_monitor.py command will have spawned more than 5 seconds
# prior to called this shell script.
try:
sleep_time = 5 - (time.time() - os.path.getctime(base_dir))
except OSError:
sleep_time = 5
if sleep_time > 0:
logger.warning('Monitor just started? Sleeping %.1fs', sleep_time)
time.sleep(sleep_time)
assert os.path.exists(base_dir), '%s does not exist' % base_dir
ShutdownLogcatMonitor(base_dir, logger)
separator = '\n' + '*' * 80 + '\n\n'
for log in GetDeviceLogs(FindLogFiles(base_dir), logger):
output_file.write(log)
output_file.write(separator)
with open(os.path.join(base_dir, 'eventlog')) as f:
output_file.write('\nLogcat Monitor Event Log\n')
output_file.write(f.read())
except:
logger.exception('Unexpected exception')
logger.info('Done.')
sh.flush()
output_file.write('\nLogcat Printer Event Log\n')
output_file.write(log_stringio.getvalue())
if __name__ == '__main__':
if len(sys.argv) == 1:
print 'Usage: %s <base_dir>' % sys.argv[0]
sys.exit(1)
sys.exit(main(sys.argv[1], sys.stdout))

View file

@ -0,0 +1,14 @@
#!/bin/bash
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
if [ $# -gt 0 ] ; then
INTENT_ARGS='-d "$1"' # e.g. a URL
fi
adb shell am start \
-a android.intent.action.VIEW \
-n org.chromium.content_shell/.ContentShellActivity \
$INTENT_ARGS

View file

@ -0,0 +1,97 @@
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<project name="chromium-jars" default="dist">
<!--
Common ant build file for for chromium_*.jars.
For creating a new chromium_*.jar :
1. Use build/java.gypi action.
The jar will be created as chromium_${PACKAGE_NAME} in
${PRODUCT_DIR}/lib.java.
-->
<description>
Building ${PROJECT_NAME}/ java source code with ant.
</description>
<import file="common.xml"/>
<path id="javac.custom.classpath">
<filelist files="${INPUT_JARS_PATHS}"/>
<pathelement location="${ANDROID_SDK}/android.jar"/>
</path>
<path id="javac.srcdirs.additional">
<filelist files="${ADDITIONAL_SRC_DIRS}"/>
<filelist files="${GENERATED_SRC_DIRS}"/>
</path>
<property-value
name="javac.srcdir"
value="src:${toString:javac.srcdirs.additional}"
/>
<property-location
name="dest.dir"
location="${PRODUCT_DIR}/java/${PACKAGE_NAME}"
check-exists="false"
/>
<target name="init">
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
<mkdir dir="${dest.dir}"/>
<!-- Remove all .class files from dest.dir. This prevents inclusion of
incorrect .class files in the final .jar. For example, if a .java file
was deleted, the .jar should not contain the .class files for that
.java from previous builds.
-->
<delete>
<fileset dir="${dest.dir}" includes="**/*.class"/>
</delete>
</target>
<target name="compile" depends="init" description="Compiles source.">
<fail message="Error: javac.custom.classpath is not set. Please set it to
classpath for javac.">
<condition>
<not><isreference refid="javac.custom.classpath"/></not>
</condition>
</fail>
<echo>
Compiling ${javac.srcdir}, classpath: ${toString:javac.custom.classpath}
</echo>
<javac
srcdir="${javac.srcdir}"
destdir="${dest.dir}"
classpathref="javac.custom.classpath"
debug="true"
includeantruntime="false"
/>
</target>
<target name="dist" depends="compile"
description="Generate chromium_${PACKAGE_NAME}.jar.">
<!-- Create the distribution directory -->
<jar
jarfile="${lib.java.dir}/chromium_${PACKAGE_NAME}.jar"
basedir="${dest.dir}"
/>
<!-- If Gyp thinks this output is stale but Ant doesn't, the modification
time should still be updated. Otherwise, this target will continue to
be rebuilt in future builds.
-->
<touch file="${lib.java.dir}/chromium_${PACKAGE_NAME}.jar"/>
</target>
<target name="clean" description="clean up">
<!-- Delete the appropriate directory trees -->
<delete dir="${dest.dir}"/>
</target>
</project>

View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<project name="chrome_common_defines">
<!-- Common build properties for Chrome for android. -->
<!--
Macro for checking that a property is correctly set. Performs checks for:
1. Property is set and not null.
2. String value of property does not contains any '$' signs.
-->
<macrodef name="check-property-value">
<attribute name="property"/>
<sequential>
<fail message ="Property @{property} is not set.">
<condition>
<or>
<not><isset property="@{property}"/></not>
<length string="${@{property}}" trim="true" when="less" length="1"/>
</or>
</condition>
</fail>
<!--
Check for $ signs. This catches errors when properties are initialized from environment
variables. E.g. if we have <property name="foo" value="${env.bar}" /> but env.bar is
not set then foo will have the literal value of '${env.bar}'.
-->
<fail message="Value checked failed for property: @{property} : ${@{property}}.
Property value contains an uninitialized environment variable.">
<condition>
<contains string="${@{property}}" substring="$"/>
</condition>
</fail>
</sequential>
</macrodef>
<!--
A safe setter for location properties. Checks that a location is not
empty and actually exists. For specifying output directories, location
check can be disabled by specifying check-exists="false".
-->
<macrodef name="property-location">
<attribute name="name"/>
<attribute name="location"/>
<attribute name="check-exists" default="true"/>
<sequential>
<property name="@{name}" location="@{location}"/>
<check-property-value property="@{name}"/>
<fail message="Location specified for @{name} : @{location} does not exist.">
<condition>
<and>
<equals arg1="@{check-exists}" arg2="true"/>
<not><available file="@{location}"/></not>
</and>
</condition>
</fail>
</sequential>
</macrodef>
<!-- A safe setter for property values -->
<macrodef name="property-value">
<attribute name="name"/>
<attribute name="value"/>
<sequential>
<property name="@{name}" value="@{value}"/>
<check-property-value property="@{name}"/>
</sequential>
</macrodef>
<!-- Common environment properties. -->
<property-location name="sdk.dir" location="${ANDROID_SDK_ROOT}"/>
<property-value name="target" value="android-${ANDROID_SDK_VERSION}"/>
<property name="source.dir" location="src"/>
<property-location name="android.gdbserver" location="${ANDROID_GDBSERVER}"/>
<!--
Common directories used by SDK Build, when making changes here
make sure to update gyp files and test scripts constants in
build/android/pylib/constants.py
-->
<!-- Common directory for chromium_*.jars. -->
<property-location name="lib.java.dir" location="${PRODUCT_DIR}/lib.java"/>
<!-- Common directory for test jars. -->
<property-location name="test.lib.java.dir"
location="${PRODUCT_DIR}/test.lib.java"/>
<!-- Common directory for apks. -->
<property-location name="apks.dir" location="${PRODUCT_DIR}/apks"/>
</project>

View file

@ -0,0 +1,284 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<project name="chrome_sdk_overrides" >
<!--
Redefinition of targets used by SDK tools.
Supported version: SDK tools revision 20.
SDK tools do not allow easy way of extending classpaths
for aidl and javac. This file defines targets which can be used to
override targets used by tools.
-->
<target name="-pre-compile">
<!--
Remove all .class files from the output directory. This prevents inclusion of incorrect .class
files in the final apk. For example, if a .java file was deleted, the apk should not contain
the .class files for that .java from previous builds.
-->
<delete>
<fileset dir="${out.classes.absolute.dir}" includes="**/*.class"/>
</delete>
</target>
<!--
Override the -compile target.
This target requires 'javac.custom.classpath' to be set to reference
of classpath to be used for javac. Also accepts custom path for
sources: 'javac.custom.sourcepath'.
-->
<target
name="-compile"
depends="-build-setup, -pre-build, -code-gen, -pre-compile">
<do-only-if-manifest-hasCode elseText="hasCode = false. Skipping..." >
<!-- If javac.srcdirs.additional isn't set, set it to an empty path. -->
<if>
<condition>
<not>
<isreference refid="javac.srcdirs.additional"/>
</not>
</condition>
<then>
<path id="javac.srcdirs.additional"/>
</then>
</if>
<javac
bootclasspathref="project.target.class.path"
classpathref="javac.custom.classpath"
debug="true"
destdir="${out.classes.absolute.dir}"
encoding="${java.encoding}"
extdirs=""
fork="${need.javac.fork}"
includeantruntime="false"
source="${java.source}"
target="${java.target}"
verbose="${verbose}">
<src path="${source.absolute.dir}"/>
<src path="${gen.absolute.dir}"/>
<src>
<path refid="javac.srcdirs.additional"/>
</src>
<compilerarg line="${java.compilerargs}"/>
</javac>
<!--
If the project is instrumented, then instrument the classes
TODO(shashishekhar): Add option to override emma filter.
-->
<if condition="${build.is.instrumented}">
<then>
<echo level="info">
Instrumenting classes from ${out.absolute.dir}/classes...
</echo>
<!-- build the default filter to remove R, Manifest, BuildConfig -->
<getemmafilter
appPackage="${project.app.package}"
filterOut="emma.default.filter"
libraryPackagesRefId="project.library.packages"/>
<!--
Define where the .em file is output.
This may have been setup already if this is a library.
-->
<property name="emma.coverage.absolute.file"
location="${out.absolute.dir}/coverage.em"/>
<!-- It only instruments class files, not any external libs -->
<emma enabled="true">
<instr
instrpath="${out.absolute.dir}/classes"
metadatafile="${emma.coverage.absolute.file}"
mode="overwrite"
outdir="${out.absolute.dir}/classes"
verbosity="${verbosity}">
<filter excludes="${emma.default.filter}"/>
<filter value="${emma.filter}"/>
</instr>
</emma>
</then>
</if>
<!--
If the project needs a test jar then generate a jar containing
all compiled classes and referenced jars.
project.is.testapp is set by Android's ant build system based on the
target's manifest. It is true only for instrumentation apks.
-->
<if condition="${project.is.testapp}">
<then>
<echo level="info">Creating test jar file:
${ant.project.name}-debug.jar</echo>
<property-location name="create.test.jar.file"
location="${CHROMIUM_SRC}/build/android/ant/create-test-jar.js"/>
<script language="javascript" src="${create.test.jar.file}"/>
</then>
</if>
</do-only-if-manifest-hasCode>
</target>
<!--
For debug builds, the Android SDK tools create a key in ~/.android and sign the build with it.
This has caused all kinds of issues. Instead, the debug build should be signed with a key in
build/android/ant. The SDK tools do not provide any support for overriding that behavior and so
instead one must use the hack below.
-->
<!-- Disables automatic signing. -->
<property name="build.is.signing.debug" value="false"/>
<!-- TODO(cjhopman): Remove this property when all gyp files define the CHROMIUM_SRC property. -->
<property name="CHROMIUM_SRC" value="${PRODUCT_DIR}/../.." />
<property name="key.store" value="${CHROMIUM_SRC}/build/android/ant/chromium-debug.keystore"/>
<property name="key.store.password" value="chromium"/>
<property name="key.alias" value="chromiumdebugkey"/>
<property name="key.alias.password" value="chromium"/>
<!-- SDK tools assume that out.packaged.file is signed and name it "...-unaligned" -->
<property name="out.packaged.file"
value="${apks.dir}/${ant.project.name}-debug-unsigned.apk" />
<property name="out.unaligned.file"
value="${apks.dir}/${ant.project.name}-debug-unaligned.apk" />
<!-- By default, the SDK tools build only aligns the APK in the -do-debug target. -->
<target name="-do-debug"
depends="-set-debug-mode, -debug-obfuscation-check, -package, -post-package">
<!-- only create apk if *not* a library project -->
<do-only-if-not-library elseText="Library project: do not create apk..." >
<sequential>
<!-- Signs the APK -->
<echo level="info">Signing final apk...</echo>
<signapk
input="${out.packaged.file}"
output="${out.unaligned.file}"
keystore="${key.store}"
storepass="${key.store.password}"
alias="${key.alias}"
keypass="${key.alias.password}"/>
<!-- Zip aligns the APK -->
<zipalign-helper
in.package="${out.unaligned.file}"
out.package="${out.final.file}" />
<echo level="info">Release Package: ${out.final.file}</echo>
</sequential>
</do-only-if-not-library>
<record-build-info />
</target>
<path id="native.libs.gdbserver">
<fileset file="${android.gdbserver}"/>
</path>
<target name="-post-compile">
<!--
Copy gdbserver to main libs directory if building a non-instrumentation debug apk.
TODO(jrg): For now, Chrome on Android always builds native code
as Release and java/ant as Debug, which means we always install
gdbserver. Resolve this discrepancy, possibly by making this
Release Official build java/ant as Release.
-->
<if>
<condition>
<and>
<equals arg1="${build.target}" arg2="debug"/>
<isfalse value="${project.is.testapp}"/>
</and>
</condition>
<then>
<echo message="Copying gdbserver to the apk to enable native debugging"/>
<copy todir="${out.dir}/libs/${target.abi}">
<path refid="native.libs.gdbserver"/>
</copy>
</then>
</if>
<!-- Package all the compiled .class files into a .jar. -->
<jar
jarfile="${lib.java.dir}/chromium_apk_${PACKAGE_NAME}.jar"
basedir="${out.classes.absolute.dir}"
/>
</target>
<!--
Override obfuscate target to pass javac.custom.classpath to Proguard. SDK tools do not provide
any way to pass custom class paths to Proguard.
-->
<target name="-obfuscate">
<if condition="${proguard.enabled}">
<then>
<property name="obfuscate.absolute.dir" location="${out.absolute.dir}/proguard"/>
<property name="preobfuscate.jar.file" value="${obfuscate.absolute.dir}/original.jar"/>
<property name="obfuscated.jar.file" value="${obfuscate.absolute.dir}/obfuscated.jar"/>
<!-- input for dex will be proguard's output -->
<property name="out.dex.input.absolute.dir" value="${obfuscated.jar.file}"/>
<!-- Add Proguard Tasks -->
<property name="proguard.jar" location="${android.tools.dir}/proguard/lib/proguard.jar"/>
<taskdef name="proguard" classname="proguard.ant.ProGuardTask" classpath="${proguard.jar}"/>
<!-- Set the android classpath Path object into a single property. It'll be
all the jar files separated by a platform path-separator.
Each path must be quoted if it contains spaces.
-->
<pathconvert property="project.target.classpath.value" refid="project.target.class.path">
<firstmatchmapper>
<regexpmapper from='^([^ ]*)( .*)$$' to='"\1\2"'/>
<identitymapper/>
</firstmatchmapper>
</pathconvert>
<!-- Build a path object with all the jar files that must be obfuscated.
This include the project compiled source code and any 3rd party jar
files. -->
<path id="project.all.classes.path">
<pathelement location="${preobfuscate.jar.file}"/>
<path refid="project.all.jars.path"/>
<!-- Pass javac.custom.classpath for apks. -->
<path refid="javac.custom.classpath"/>
</path>
<!-- Set the project jar files Path object into a single property. It'll be
all the jar files separated by a platform path-separator.
Each path must be quoted if it contains spaces.
-->
<pathconvert property="project.all.classes.value" refid="project.all.classes.path">
<firstmatchmapper>
<regexpmapper from='^([^ ]*)( .*)$$' to='"\1\2"'/>
<identitymapper/>
</firstmatchmapper>
</pathconvert>
<!-- Turn the path property ${proguard.config} from an A:B:C property
into a series of includes: -include A -include B -include C
suitable for processing by the ProGuard task. Note - this does
not include the leading '-include "' or the closing '"'; those
are added under the <proguard> call below.
-->
<path id="proguard.configpath">
<pathelement path="${proguard.config}"/>
</path>
<pathconvert pathsep='" -include "' property="proguard.configcmd"
refid="proguard.configpath"/>
<mkdir dir="${obfuscate.absolute.dir}"/>
<delete file="${preobfuscate.jar.file}"/>
<delete file="${obfuscated.jar.file}"/>
<jar basedir="${out.classes.absolute.dir}"
destfile="${preobfuscate.jar.file}"/>
<proguard>
-include "${proguard.configcmd}"
-include "${out.absolute.dir}/proguard.txt"
-injars ${project.all.classes.value}
-outjars "${obfuscated.jar.file}"
-libraryjars ${project.target.classpath.value}
-dump "${obfuscate.absolute.dir}/dump.txt"
-printseeds "${obfuscate.absolute.dir}/seeds.txt"
-printusage "${obfuscate.absolute.dir}/usage.txt"
-printmapping "${obfuscate.absolute.dir}/mapping.txt"
</proguard>
</then>
</if>
</target>
</project>

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_fyi_builder.sh "$@"

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_fyi_tester.sh "$@"

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_main_builder.sh "$@"

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_try_builder.sh "$@"

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_try_compile.sh "$@"

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_try_tester.sh "$@"

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Temporary placeholders to call new buildbot script locations until
# buildbot master config can be pointed to new location.
exec $(dirname $0)/buildbot/bb_webkit_latest_builder.sh "$@"

View file

@ -0,0 +1,6 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#dummy gypi: contents should be discarded due to an enclosing 'conditions:' element.
{}

View file

@ -0,0 +1,43 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides iotop/top style profiling for android.
Usage:
./device_stats_monitor.py --hz=20 --duration=5 --outfile=/tmp/foo
"""
import optparse
import os
import sys
import time
from pylib import android_commands
from pylib import device_stats_monitor
from pylib import test_options_parser
def main(argv):
option_parser = optparse.OptionParser()
option_parser.add_option('--hz', type='int', default=20,
help='Number of samples/sec.')
option_parser.add_option('--duration', type='int', default=5,
help='Seconds to monitor.')
option_parser.add_option('--outfile', default='/tmp/devicestatsmonitor',
help='Location to start output file.')
test_options_parser.AddBuildTypeOption(option_parser)
options, args = option_parser.parse_args(argv)
monitor = device_stats_monitor.DeviceStatsMonitor(
android_commands.AndroidCommands(), options.hz, options.build_type)
monitor.Start()
print 'Waiting for %d seconds while profiling.' % options.duration
time.sleep(options.duration)
url = monitor.StopAndCollect(options.outfile)
print 'View results in browser at %s' % url
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -0,0 +1,170 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A class to keep track of devices across builds and report state."""
import logging
import optparse
import os
import smtplib
import sys
from pylib import buildbot_report
from pylib.android_commands import GetAttachedDevices
from pylib.cmd_helper import GetCmdOutput
def DeviceInfo(serial):
"""Gathers info on a device via various adb calls.
Args:
serial: The serial of the attached device to construct info about.
Returns:
Tuple of device type, build id and report as a string.
"""
def AdbShellCmd(cmd):
return GetCmdOutput('adb -s %s shell %s' % (serial, cmd),
shell=True).strip()
device_type = AdbShellCmd('getprop ro.build.product')
device_build = AdbShellCmd('getprop ro.build.id')
report = ['Device %s (%s)' % (serial, device_type),
' Build: %s (%s)' % (device_build,
AdbShellCmd('getprop ro.build.fingerprint')),
' Battery: %s%%' % AdbShellCmd('dumpsys battery | grep level '
"| awk '{print $2}'"),
' Battery temp: %s' % AdbShellCmd('dumpsys battery'
'| grep temp '
"| awk '{print $2}'"),
' IMEI slice: %s' % AdbShellCmd('dumpsys iphonesubinfo '
'| grep Device'
"| awk '{print $4}'")[-6:],
' Wifi IP: %s' % AdbShellCmd('getprop dhcp.wlan0.ipaddress'),
'']
return device_type, device_build, '\n'.join(report)
def CheckForMissingDevices(options, adb_online_devs):
"""Uses file of previous online devices to detect broken phones.
Args:
options: out_dir parameter of options argument is used as the base
directory to load and update the cache file.
adb_online_devs: A list of serial numbers of the currently visible
and online attached devices.
"""
# TODO(navabi): remove this once the bug that causes different number
# of devices to be detected between calls is fixed.
logger = logging.getLogger()
logger.setLevel(logging.INFO)
out_dir = os.path.abspath(options.out_dir)
def ReadDeviceList(file_name):
devices_path = os.path.join(out_dir, file_name)
devices = []
try:
with open(devices_path) as f:
devices = f.read().splitlines()
except IOError:
# Ignore error, file might not exist
pass
return devices
def WriteDeviceList(file_name, device_list):
path = os.path.join(out_dir, file_name)
if not os.path.exists(out_dir):
os.makedirs(out_dir)
with open(path, 'w') as f:
# Write devices currently visible plus devices previously seen.
f.write('\n'.join(set(device_list)))
last_devices_path = os.path.join(out_dir, '.last_devices')
last_devices = ReadDeviceList('.last_devices')
missing_devs = list(set(last_devices) - set(adb_online_devs))
if missing_devs:
from_address = 'buildbot@chromium.org'
to_address = 'chromium-android-device-alerts@google.com'
bot_name = os.environ['BUILDBOT_BUILDERNAME']
slave_name = os.environ['BUILDBOT_SLAVENAME']
num_online_devs = len(adb_online_devs)
subject = 'Devices offline on %s, %s (%d remaining).' % (slave_name,
bot_name,
num_online_devs)
buildbot_report.PrintWarning()
devices_missing_msg = '%d devices not detected.' % len(missing_devs)
buildbot_report.PrintSummaryText(devices_missing_msg)
# TODO(navabi): Debug by printing both output from GetCmdOutput and
# GetAttachedDevices to compare results.
body = '\n'.join(
['Current online devices: %s' % adb_online_devs,
'%s are no longer visible. Were they removed?\n' % missing_devs,
'SHERIFF: See go/chrome_device_monitor',
'Cache file: %s\n\n' % last_devices_path,
'adb devices: %s' % GetCmdOutput(['adb', 'devices']),
'adb devices(GetAttachedDevices): %s' % GetAttachedDevices()])
print body
# Only send email if the first time a particular device goes offline
last_missing = ReadDeviceList('.last_missing')
new_missing_devs = set(missing_devs) - set(last_missing)
if new_missing_devs:
msg_body = '\r\n'.join(
['From: %s' % from_address,
'To: %s' % to_address,
'Subject: %s' % subject,
'', body])
try:
server = smtplib.SMTP('localhost')
server.sendmail(from_address, [to_address], msg_body)
server.quit()
except Exception as e:
print 'Failed to send alert email. Error: %s' % e
else:
new_devs = set(adb_online_devs) - set(last_devices)
if new_devs and os.path.exists(last_devices_path):
buildbot_report.PrintWarning()
buildbot_report.PrintSummaryText(
'%d new devices detected' % len(new_devs))
print ('New devices detected %s. And now back to your '
'regularly scheduled program.' % list(new_devs))
WriteDeviceList('.last_devices', (adb_online_devs + last_devices))
WriteDeviceList('.last_missing', missing_devs)
def main():
parser = optparse.OptionParser()
parser.add_option('', '--out-dir',
help='Directory where the device path is stored',
default=os.path.join(os.path.dirname(__file__), '..',
'..', 'out'))
options, args = parser.parse_args()
if args:
parser.error('Unknown options %s' % args)
buildbot_report.PrintNamedStep('Device Status Check')
devices = GetAttachedDevices()
types, builds, reports = [], [], []
if devices:
types, builds, reports = zip(*[DeviceInfo(dev) for dev in devices])
unique_types = list(set(types))
unique_builds = list(set(builds))
buildbot_report.PrintMsg('Online devices: %d. Device types %s, builds %s'
% (len(devices), unique_types, unique_builds))
print '\n'.join(reports)
CheckForMissingDevices(options, devices)
if __name__ == '__main__':
sys.exit(main())

View file

@ -0,0 +1,321 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides an interface to start and stop Android emulator.
Assumes system environment ANDROID_NDK_ROOT has been set.
Emulator: The class provides the methods to launch/shutdown the emulator with
the android virtual device named 'avd_armeabi' .
"""
import logging
import os
import signal
import subprocess
import sys
import time
from pylib import android_commands
from pylib import cmd_helper
# adb_interface.py is under ../../third_party/android_testrunner/
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..',
'..', 'third_party', 'android_testrunner'))
import adb_interface
import errors
import run_command
class EmulatorLaunchException(Exception):
"""Emulator failed to launch."""
pass
def _KillAllEmulators():
"""Kill all running emulators that look like ones we started.
There are odd 'sticky' cases where there can be no emulator process
running but a device slot is taken. A little bot trouble and and
we're out of room forever.
"""
emulators = android_commands.GetEmulators()
if not emulators:
return
for emu_name in emulators:
cmd_helper.GetCmdOutput(['adb', '-s', emu_name, 'emu', 'kill'])
logging.info('Emulator killing is async; give a few seconds for all to die.')
for i in range(5):
if not android_commands.GetEmulators():
return
time.sleep(1)
def DeleteAllTempAVDs():
"""Delete all temporary AVDs which are created for tests.
If the test exits abnormally and some temporary AVDs created when testing may
be left in the system. Clean these AVDs.
"""
avds = android_commands.GetAVDs()
if not avds:
return
for avd_name in avds:
if 'run_tests_avd' in avd_name:
cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name]
cmd_helper.GetCmdOutput(cmd)
logging.info('Delete AVD %s' % avd_name)
class PortPool(object):
"""Pool for emulator port starting position that changes over time."""
_port_min = 5554
_port_max = 5585
_port_current_index = 0
@classmethod
def port_range(cls):
"""Return a range of valid ports for emulator use.
The port must be an even number between 5554 and 5584. Sometimes
a killed emulator "hangs on" to a port long enough to prevent
relaunch. This is especially true on slow machines (like a bot).
Cycling through a port start position helps make us resilient."""
ports = range(cls._port_min, cls._port_max, 2)
n = cls._port_current_index
cls._port_current_index = (n + 1) % len(ports)
return ports[n:] + ports[:n]
def _GetAvailablePort():
"""Returns an available TCP port for the console."""
used_ports = []
emulators = android_commands.GetEmulators()
for emulator in emulators:
used_ports.append(emulator.split('-')[1])
for port in PortPool.port_range():
if str(port) not in used_ports:
return port
class Emulator(object):
"""Provides the methods to lanuch/shutdown the emulator.
The emulator has the android virtual device named 'avd_armeabi'.
The emulator could use any even TCP port between 5554 and 5584 for the
console communication, and this port will be part of the device name like
'emulator-5554'. Assume it is always True, as the device name is the id of
emulator managed in this class.
Attributes:
emulator: Path of Android's emulator tool.
popen: Popen object of the running emulator process.
device: Device name of this emulator.
"""
# Signals we listen for to kill the emulator on
_SIGNALS = (signal.SIGINT, signal.SIGHUP)
# Time to wait for an emulator launch, in seconds. This includes
# the time to launch the emulator and a wait-for-device command.
_LAUNCH_TIMEOUT = 120
# Timeout interval of wait-for-device command before bouncing to a a
# process life check.
_WAITFORDEVICE_TIMEOUT = 5
# Time to wait for a "wait for boot complete" (property set on device).
_WAITFORBOOT_TIMEOUT = 300
def __init__(self, new_avd_name, fast_and_loose):
"""Init an Emulator.
Args:
nwe_avd_name: If set, will create a new temporary AVD.
fast_and_loose: Loosen up the rules for reliable running for speed.
Intended for quick testing or re-testing.
"""
try:
android_sdk_root = os.environ['ANDROID_SDK_ROOT']
except KeyError:
logging.critical('The ANDROID_SDK_ROOT must be set to run the test on '
'emulator.')
raise
self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
self.android = os.path.join(android_sdk_root, 'tools', 'android')
self.popen = None
self.device = None
self.default_avd = True
self.fast_and_loose = fast_and_loose
self.abi = 'armeabi-v7a'
self.avd = 'avd_armeabi'
if 'x86' in os.environ.get('TARGET_PRODUCT', ''):
self.abi = 'x86'
self.avd = 'avd_x86'
if new_avd_name:
self.default_avd = False
self.avd = self._CreateAVD(new_avd_name)
def _DeviceName(self):
"""Return our device name."""
port = _GetAvailablePort()
return ('emulator-%d' % port, port)
def _CreateAVD(self, avd_name):
"""Creates an AVD with the given name.
Return avd_name.
"""
avd_command = [
self.android,
'--silent',
'create', 'avd',
'--name', avd_name,
'--abi', self.abi,
'--target', 'android-16',
'-c', '128M',
'--force',
]
avd_process = subprocess.Popen(args=avd_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
avd_process.stdin.write('no\n')
avd_process.wait()
logging.info('Create AVD command: %s', ' '.join(avd_command))
return avd_name
def _DeleteAVD(self):
"""Delete the AVD of this emulator."""
avd_command = [
self.android,
'--silent',
'delete',
'avd',
'--name', self.avd,
]
avd_process = subprocess.Popen(args=avd_command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
logging.info('Delete AVD command: %s', ' '.join(avd_command))
avd_process.wait()
def Launch(self, kill_all_emulators):
"""Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
emulator is ready for use.
If fails, an exception will be raised.
"""
if kill_all_emulators:
_KillAllEmulators() # just to be sure
if not self.fast_and_loose:
self._AggressiveImageCleanup()
(self.device, port) = self._DeviceName()
emulator_command = [
self.emulator,
# Speed up emulator launch by 40%. Really.
'-no-boot-anim',
# The default /data size is 64M.
# That's not enough for 8 unit test bundles and their data.
'-partition-size', '512',
# Enable GPU by default.
'-gpu', 'on',
# Use a familiar name and port.
'-avd', self.avd,
'-port', str(port)]
if not self.fast_and_loose:
emulator_command.extend([
# Wipe the data. We've seen cases where an emulator
# gets 'stuck' if we don't do this (every thousand runs or
# so).
'-wipe-data',
])
logging.info('Emulator launch command: %s', ' '.join(emulator_command))
self.popen = subprocess.Popen(args=emulator_command,
stderr=subprocess.STDOUT)
self._InstallKillHandler()
def _AggressiveImageCleanup(self):
"""Aggressive cleanup of emulator images.
Experimentally it looks like our current emulator use on the bot
leaves image files around in /tmp/android-$USER. If a "random"
name gets reused, we choke with a 'File exists' error.
TODO(jrg): is there a less hacky way to accomplish the same goal?
"""
logging.info('Aggressive Image Cleanup')
emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
if not os.path.exists(emulator_imagedir):
return
for image in os.listdir(emulator_imagedir):
full_name = os.path.join(emulator_imagedir, image)
if 'emulator' in full_name:
logging.info('Deleting emulator image %s', full_name)
os.unlink(full_name)
def ConfirmLaunch(self, wait_for_boot=False):
"""Confirm the emulator launched properly.
Loop on a wait-for-device with a very small timeout. On each
timeout, check the emulator process is still alive.
After confirming a wait-for-device can be successful, make sure
it returns the right answer.
"""
seconds_waited = 0
number_of_waits = 2 # Make sure we can wfd twice
adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device')
while seconds_waited < self._LAUNCH_TIMEOUT:
try:
run_command.RunCommand(adb_cmd,
timeout_time=self._WAITFORDEVICE_TIMEOUT,
retry_count=1)
number_of_waits -= 1
if not number_of_waits:
break
except errors.WaitForResponseTimedOutError as e:
seconds_waited += self._WAITFORDEVICE_TIMEOUT
adb_cmd = "adb -s %s %s" % (self.device, 'kill-server')
run_command.RunCommand(adb_cmd)
self.popen.poll()
if self.popen.returncode != None:
raise EmulatorLaunchException('EMULATOR DIED')
if seconds_waited >= self._LAUNCH_TIMEOUT:
raise EmulatorLaunchException('TIMEOUT with wait-for-device')
logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
if wait_for_boot:
# Now that we checked for obvious problems, wait for a boot complete.
# Waiting for the package manager is sometimes problematic.
a = android_commands.AndroidCommands(self.device)
a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT)
def Shutdown(self):
"""Shuts down the process started by launch."""
if not self.default_avd:
self._DeleteAVD()
if self.popen:
self.popen.poll()
if self.popen.returncode == None:
self.popen.kill()
self.popen = None
def _ShutdownOnSignal(self, signum, frame):
logging.critical('emulator _ShutdownOnSignal')
for sig in self._SIGNALS:
signal.signal(sig, signal.SIG_DFL)
self.Shutdown()
raise KeyboardInterrupt # print a stack
def _InstallKillHandler(self):
"""Install a handler to kill the emulator when we exit unexpectedly."""
for sig in self._SIGNALS:
signal.signal(sig, self._ShutdownOnSignal)
def main(argv):
Emulator(None, True).Launch(True)
if __name__ == '__main__':
main(sys.argv)

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Enables dalvik vm asserts in the android device."""
from pylib import android_commands
import optparse
import sys
def main(argv):
option_parser = optparse.OptionParser()
option_parser.add_option('--enable_asserts', dest='set_asserts',
action='store_true', default=None,
help='Sets the dalvik.vm.enableassertions property to "all"')
option_parser.add_option('--disable_asserts', dest='set_asserts',
action='store_false', default=None,
help='Removes the dalvik.vm.enableassertions property')
options, _ = option_parser.parse_args(argv)
commands = android_commands.AndroidCommands()
if options.set_asserts != None:
if commands.SetJavaAssertsEnabled(options.set_asserts):
commands.Reboot(full_reboot=False)
if __name__ == '__main__':
main(sys.argv)

View file

@ -0,0 +1,133 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Sets up environment for building Chromium on Android. It can either be
# compiled with the Android tree or using the Android SDK/NDK. To build with
# NDK/SDK: ". build/android/envsetup.sh --sdk". Environment variable
# ANDROID_SDK_BUILD=1 will then be defined and used in the rest of the setup to
# specifiy build type.
# When building WebView as part of Android we can't use the SDK. Other builds
# default to using the SDK.
# NOTE(yfriedman): This looks unnecessary but downstream the default value
# should be 0 until all builds switch to SDK/NDK.
if [[ "${CHROME_ANDROID_BUILD_WEBVIEW}" -eq 1 ]]; then
export ANDROID_SDK_BUILD=0
else
export ANDROID_SDK_BUILD=1
fi
# Loop over args in case we add more arguments in the future.
while [ "$1" != "" ]; do
case $1 in
-s | --sdk ) export ANDROID_SDK_BUILD=1 ; shift ;;
* ) shift ; break ;;
esac
done
if [[ "${ANDROID_SDK_BUILD}" -eq 1 ]]; then
echo "Using SDK build"
fi
host_os=$(uname -s | sed -e 's/Linux/linux/;s/Darwin/mac/')
case "${host_os}" in
"linux")
toolchain_dir="linux-x86_64"
;;
"mac")
toolchain_dir="darwin-x86"
;;
*)
echo "Host platform ${host_os} is not supported" >& 2
return 1
esac
CURRENT_DIR="$(readlink -f "$(dirname $BASH_SOURCE)/../../")"
if [[ -z "${CHROME_SRC}" ]]; then
# If $CHROME_SRC was not set, assume current directory is CHROME_SRC.
export CHROME_SRC="${CURRENT_DIR}"
fi
if [[ "${CURRENT_DIR/"${CHROME_SRC}"/}" == "${CURRENT_DIR}" ]]; then
# If current directory is not in $CHROME_SRC, it might be set for other
# source tree. If $CHROME_SRC was set correctly and we are in the correct
# directory, "${CURRENT_DIR/"${CHROME_SRC}"/}" will be "".
# Otherwise, it will equal to "${CURRENT_DIR}"
echo "Warning: Current directory is out of CHROME_SRC, it may not be \
the one you want."
echo "${CHROME_SRC}"
fi
# Android sdk platform version to use
export ANDROID_SDK_VERSION=16
# Source functions script. The file is in the same directory as this script.
. "$(dirname $BASH_SOURCE)"/envsetup_functions.sh
if [[ "${ANDROID_SDK_BUILD}" -eq 1 ]]; then
sdk_build_init
# Sets up environment for building Chromium for Android with source. Expects
# android environment setup and lunch.
elif [[ -z "$ANDROID_BUILD_TOP" || \
-z "$ANDROID_TOOLCHAIN" || \
-z "$ANDROID_PRODUCT_OUT" ]]; then
echo "Android build environment variables must be set."
echo "Please cd to the root of your Android tree and do: "
echo " . build/envsetup.sh"
echo " lunch"
echo "Then try this again."
echo "Or did you mean NDK/SDK build. Run envsetup.sh with --sdk argument."
return 1
elif [[ -n "$CHROME_ANDROID_BUILD_WEBVIEW" ]]; then
webview_build_init
else
non_sdk_build_init
fi
# Workaround for valgrind build
if [[ -n "$CHROME_ANDROID_VALGRIND_BUILD" ]]; then
# arm_thumb=0 is a workaround for https://bugs.kde.org/show_bug.cgi?id=270709
DEFINES+=" arm_thumb=0 release_extra_cflags='-fno-inline\
-fno-omit-frame-pointer -fno-builtin' release_valgrind_build=1\
release_optimize=1"
fi
# Source a bunch of helper functions
. ${CHROME_SRC}/build/android/adb_device_functions.sh
ANDROID_GOMA_WRAPPER=""
if [[ -d $GOMA_DIR ]]; then
ANDROID_GOMA_WRAPPER="$GOMA_DIR/gomacc"
fi
export ANDROID_GOMA_WRAPPER
# Declare Android are cross compile.
export GYP_CROSSCOMPILE=1
export CXX_target="${ANDROID_GOMA_WRAPPER} \
$(echo -n ${ANDROID_TOOLCHAIN}/*-g++)"
# Performs a gyp_chromium run to convert gyp->Makefile for android code.
android_gyp() {
echo "GYP_GENERATORS set to '$GYP_GENERATORS'"
# http://crbug.com/143889.
# In case we are doing a Clang build, we have to unset CC_target and
# CXX_target. Otherwise GYP ends up generating a gcc build (although we set
# 'clang' to 1). This behavior was introduced by
# 54d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83f in GYP.
(
# Fork to avoid side effects on the user's environment variables.
if echo "$GYP_DEFINES" | grep -qE '(clang|asan)'; then
if echo "$CXX_target" | grep -q g++; then
unset CXX_target
fi
fi
"${CHROME_SRC}/build/gyp_chromium" --depth="${CHROME_SRC}" --check "$@"
)
}
# FLOCK needs to be null on system that has no flock
which flock > /dev/null || export FLOCK=

View file

@ -0,0 +1,334 @@
#!/bin/bash
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Defines functions for envsetup.sh which sets up environment for building
# Chromium on Android. The build can be either use the Android NDK/SDK or
# android source tree. Each has a unique init function which calls functions
# prefixed with "common_" that is common for both environment setups.
################################################################################
# Check to make sure the toolchain exists for the NDK version.
################################################################################
common_check_toolchain() {
if [[ ! -d "${ANDROID_TOOLCHAIN}" ]]; then
echo "Can not find Android toolchain in ${ANDROID_TOOLCHAIN}." >& 2
echo "The NDK version might be wrong." >& 2
return 1
fi
}
################################################################################
# Exports environment variables common to both sdk and non-sdk build (e.g. PATH)
# based on CHROME_SRC and ANDROID_TOOLCHAIN, along with DEFINES for GYP_DEFINES.
################################################################################
common_vars_defines() {
# Set toolchain path according to product architecture.
toolchain_arch="arm-linux-androideabi"
if [[ "${TARGET_PRODUCT}" =~ .*x86.* ]]; then
toolchain_arch="x86"
fi
toolchain_version="4.6"
toolchain_target=$(basename \
${ANDROID_NDK_ROOT}/toolchains/${toolchain_arch}-${toolchain_version})
toolchain_path="${ANDROID_NDK_ROOT}/toolchains/${toolchain_target}"\
"/prebuilt/${toolchain_dir}/bin/"
# Set only if not already set.
# Don't override ANDROID_TOOLCHAIN if set by Android configuration env.
export ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN:-${toolchain_path}}
common_check_toolchain
# Add Android SDK/NDK tools to system path.
export PATH=$PATH:${ANDROID_NDK_ROOT}
export PATH=$PATH:${ANDROID_SDK_ROOT}/tools
export PATH=$PATH:${ANDROID_SDK_ROOT}/platform-tools
# This must be set before ANDROID_TOOLCHAIN, so that clang could find the
# gold linker.
# TODO(michaelbai): Remove this path once the gold linker become the default
# linker.
export PATH=$PATH:${CHROME_SRC}/build/android/${toolchain_arch}-gold
# Must have tools like arm-linux-androideabi-gcc on the path for ninja
export PATH=$PATH:${ANDROID_TOOLCHAIN}
# Add Chromium Android development scripts to system path.
# Must be after CHROME_SRC is set.
export PATH=$PATH:${CHROME_SRC}/build/android
# TODO(beverloo): Remove these once all consumers updated to --strip-binary.
export OBJCOPY=$(echo ${ANDROID_TOOLCHAIN}/*-objcopy)
export STRIP=$(echo ${ANDROID_TOOLCHAIN}/*-strip)
# The set of GYP_DEFINES to pass to gyp. Use 'readlink -e' on directories
# to canonicalize them (remove double '/', remove trailing '/', etc).
DEFINES="OS=android"
DEFINES+=" host_os=${host_os}"
if [[ -n "$CHROME_ANDROID_OFFICIAL_BUILD" ]]; then
DEFINES+=" branding=Chrome"
DEFINES+=" buildtype=Official"
# These defines are used by various chrome build scripts to tag the binary's
# version string as 'official' in linux builds (e.g. in
# chrome/trunk/src/chrome/tools/build/version.py).
export OFFICIAL_BUILD=1
export CHROMIUM_BUILD="_google_chrome"
export CHROME_BUILD_TYPE="_official"
# Used by chrome_version_info_posix.cc to display the channel name.
# Valid values: "unstable", "stable", "dev", "beta".
export CHROME_VERSION_EXTRA="beta"
fi
# The order file specifies the order of symbols in the .text section of the
# shared library, libchromeview.so. The file is an order list of section
# names and the library is linked with option
# --section-ordering-file=<orderfile>. The order file is updated by profiling
# startup after compiling with the order_profiling=1 GYP_DEFINES flag.
ORDER_DEFINES="order_text_section=${CHROME_SRC}/orderfiles/orderfile.out"
# The following defines will affect ARM code generation of both C/C++ compiler
# and V8 mksnapshot.
case "${TARGET_PRODUCT}" in
"passion"|"soju"|"sojua"|"sojus"|"yakju"|"mysid"|"nakasi")
DEFINES+=" arm_neon=1 armv7=1 arm_thumb=1"
DEFINES+=" ${ORDER_DEFINES}"
TARGET_ARCH="arm"
;;
"trygon"|"tervigon")
DEFINES+=" arm_neon=0 armv7=1 arm_thumb=1 arm_fpu=vfpv3-d16"
DEFINES+=" ${ORDER_DEFINES}"
TARGET_ARCH="arm"
;;
"full")
DEFINES+=" arm_neon=0 armv7=0 arm_thumb=1 arm_fpu=vfp"
TARGET_ARCH="arm"
;;
*x86*)
# TODO(tedbo): The ia32 build fails on ffmpeg, so we disable it here.
DEFINES+=" use_libffmpeg=0"
host_arch=$(uname -m | sed -e \
's/i.86/ia32/;s/x86_64/x64/;s/amd64/x64/;s/arm.*/arm/;s/i86pc/ia32/')
DEFINES+=" host_arch=${host_arch}"
TARGET_ARCH="x86"
;;
*)
echo "TARGET_PRODUCT: ${TARGET_PRODUCT} is not supported." >& 2
return 1
esac
case "${TARGET_ARCH}" in
"arm")
DEFINES+=" target_arch=arm"
;;
"x86")
DEFINES+=" target_arch=ia32"
;;
*)
echo "TARGET_ARCH: ${TARGET_ARCH} is not supported." >& 2
return 1
esac
DEFINES+=" android_gdbserver=${ANDROID_NDK_ROOT}/prebuilt/\
android-${TARGET_ARCH}/gdbserver/gdbserver"
}
################################################################################
# Exports common GYP variables based on variable DEFINES and CHROME_SRC.
################################################################################
common_gyp_vars() {
export GYP_DEFINES="${DEFINES}"
# Set GYP_GENERATORS to make-android if it's currently unset or null.
export GYP_GENERATORS="${GYP_GENERATORS:-make-android}"
# Use our All target as the default
export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} default_target=All"
# We want to use our version of "all" targets.
export CHROMIUM_GYP_FILE="${CHROME_SRC}/build/all_android.gyp"
}
################################################################################
# Initializes environment variables for NDK/SDK build. Only Android NDK Revision
# 7 on Linux or Mac is offically supported. To run this script, the system
# environment ANDROID_NDK_ROOT must be set to Android NDK's root path. The
# ANDROID_SDK_ROOT only needs to be set to override the default SDK which is in
# the tree under $ROOT/src/third_party/android_tools/sdk.
# TODO(navabi): Add NDK to $ROOT/src/third_party/android_tools/ndk.
# To build Chromium for Android with NDK/SDK follow the steps below:
# > export ANDROID_NDK_ROOT=<android ndk root>
# > export ANDROID_SDK_ROOT=<android sdk root> # to override the default sdk
# > . build/android/envsetup.sh --sdk
# > make
################################################################################
sdk_build_init() {
# If ANDROID_NDK_ROOT is set when envsetup is run, use the ndk pointed to by
# the environment variable. Otherwise, use the default ndk from the tree.
if [[ -z "${ANDROID_NDK_ROOT}" || ! -d "${ANDROID_NDK_ROOT}" ]]; then
export ANDROID_NDK_ROOT="${CHROME_SRC}/third_party/android_tools/ndk/"
fi
# If ANDROID_SDK_ROOT is set when envsetup is run, and if it has the
# right SDK-compatible directory layout, use the sdk pointed to by the
# environment variable. Otherwise, use the default sdk from the tree.
local sdk_suffix=platforms/android-${ANDROID_SDK_VERSION}
if [[ -z "${ANDROID_SDK_ROOT}" || \
! -d "${ANDROID_SDK_ROOT}/${sdk_suffix}" ]]; then
export ANDROID_SDK_ROOT="${CHROME_SRC}/third_party/android_tools/sdk/"
fi
# Makes sure ANDROID_BUILD_TOP is unset if build has option --sdk
unset ANDROID_BUILD_TOP
# Set default target.
export TARGET_PRODUCT="${TARGET_PRODUCT:-trygon}"
# Unset toolchain so that it can be set based on TARGET_PRODUCT.
# This makes it easy to switch between architectures.
unset ANDROID_TOOLCHAIN
common_vars_defines
DEFINES+=" sdk_build=1"
# If we are building NDK/SDK, and in the upstream (open source) tree,
# define a special variable for bringup purposes.
case "${ANDROID_BUILD_TOP-undefined}" in
"undefined")
DEFINES+=" android_upstream_bringup=1"
;;
esac
# Sets android specific directories to NOT_SDK_COMPLIANT. This will allow
# android_gyp to generate make files, but will cause errors when (and only
# when) building targets that depend on these directories.
DEFINES+=" android_src='NOT_SDK_COMPLIANT'"
DEFINES+=" android_product_out=${CHROME_SRC}/out/android"
DEFINES+=" android_lib='NOT_SDK_COMPLIANT'"
DEFINES+=" android_static_lib='NOT_SDK_COMPLIANT'"
DEFINES+=" android_sdk=${ANDROID_SDK_ROOT}/${sdk_suffix}"
DEFINES+=" android_sdk_root=${ANDROID_SDK_ROOT}"
DEFINES+=" android_sdk_tools=${ANDROID_SDK_ROOT}/platform-tools"
DEFINES+=" android_sdk_version=${ANDROID_SDK_VERSION}"
DEFINES+=" android_toolchain=${ANDROID_TOOLCHAIN}"
common_gyp_vars
if [[ -n "$CHROME_ANDROID_BUILD_WEBVIEW" ]]; then
# Can not build WebView with NDK/SDK because it needs the Android build
# system and build inside an Android source tree.
echo "Can not build WebView with NDK/SDK. Requires android source tree." \
>& 2
echo "Try . build/android/envsetup.sh instead." >& 2
return 1
fi
}
################################################################################
# Initializes environment variables for build with android source. This expects
# android environment to be set up along with lunch. To build:
# > . build/envsetup.sh
# > lunch <lunch-type>
# > . build/android/envsetup.sh
# > make
#############################################################################
non_sdk_build_init() {
# We export "TOP" here so that "mmm" can be run to build Java code without
# having to cd to $ANDROID_BUILD_TOP.
export TOP="$ANDROID_BUILD_TOP"
# Set "ANDROID_NDK_ROOT" as checked-in version, if it was not set.
if [[ "${ANDROID_NDK_ROOT}" || ! -d "$ANDROID_NDK_ROOT" ]] ; then
export ANDROID_NDK_ROOT="${CHROME_SRC}/third_party/android_tools/ndk/"
fi
if [[ ! -d "${ANDROID_NDK_ROOT}" ]] ; then
echo "Can not find Android NDK root ${ANDROID_NDK_ROOT}." >& 2
return 1
fi
# We export "ANDROID_SDK_ROOT" for building Java source with the SDK.
export ANDROID_SDK_ROOT=${ANDROID_BUILD_TOP}/prebuilts/sdk/\
${ANDROID_SDK_VERSION}
# Needed by android antfiles when creating apks.
export ANDROID_SDK_HOME=${ANDROID_SDK_ROOT}
# Unset ANDROID_TOOLCHAIN, so it could be set to checked-in 64-bit toolchain.
# in common_vars_defines
unset ANDROID_TOOLCHAIN
common_vars_defines
DEFINES+=" sdk_build=0"
DEFINES+=" android_product_out=${ANDROID_PRODUCT_OUT}"
if [[ -n "$CHROME_ANDROID_BUILD_WEBVIEW" ]]; then
webview_build_init
return
fi
# The non-SDK build currently requires the SDK path to build the framework
# Java aidl files. TODO(steveblock): Investigate avoiding this requirement.
DEFINES+=" android_sdk=${ANDROID_SDK_ROOT}"
DEFINES+=" android_sdk_root=${ANDROID_SDK_ROOT}"
DEFINES+=" android_sdk_tools=${ANDROID_SDK_ROOT}/../tools/linux"
DEFINES+=" android_sdk_version=${ANDROID_SDK_VERSION}"
DEFINES+=" android_toolchain=${ANDROID_TOOLCHAIN}"
common_gyp_vars
}
################################################################################
# To build WebView, we use the Android build system and build inside an Android
# source tree. This method is called from non_sdk_build_init() and adds to the
# settings specified there.
#############################################################################
webview_build_init() {
# For the WebView build we always use the NDK and SDK in the Android tree,
# and we don't touch ANDROID_TOOLCHAIN which is already set by Android.
export ANDROID_NDK_ROOT=${ANDROID_BUILD_TOP}/prebuilts/ndk/8
export ANDROID_SDK_ROOT=${ANDROID_BUILD_TOP}/prebuilts/sdk/\
${ANDROID_SDK_VERSION}
common_vars_defines
# We need to supply SDK paths relative to the top of the Android tree to make
# sure the generated Android makefiles are portable, as they will be checked
# into the Android tree.
ANDROID_SDK=$(python -c \
"import os.path; print os.path.relpath('${ANDROID_SDK_ROOT}', \
'${ANDROID_BUILD_TOP}')")
ANDROID_SDK_TOOLS=$(python -c \
"import os.path; \
print os.path.relpath('${ANDROID_SDK_ROOT}/../tools/linux', \
'${ANDROID_BUILD_TOP}')")
DEFINES+=" android_build_type=1"
DEFINES+=" sdk_build=0"
DEFINES+=" android_src=\$(GYP_ABS_ANDROID_TOP_DIR)"
DEFINES+=" android_product_out=NOT_USED_ON_WEBVIEW"
DEFINES+=" android_upstream_bringup=1"
DEFINES+=" android_sdk=\$(GYP_ABS_ANDROID_TOP_DIR)/${ANDROID_SDK}"
DEFINES+=" android_sdk_root=\$(GYP_ABS_ANDROID_TOP_DIR)/${ANDROID_SDK}"
DEFINES+=" android_sdk_tools=\$(GYP_ABS_ANDROID_TOP_DIR)/${ANDROID_SDK_TOOLS}"
DEFINES+=" android_sdk_version=${ANDROID_SDK_VERSION}"
DEFINES+=" android_toolchain=${ANDROID_TOOLCHAIN}"
export GYP_DEFINES="${DEFINES}"
export GYP_GENERATORS="android"
export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} default_target=All"
export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} limit_to_target_all=1"
export GYP_GENERATOR_FLAGS="${GYP_GENERATOR_FLAGS} auto_regeneration=0"
export CHROMIUM_GYP_FILE="${CHROME_SRC}/android_webview/all_webview.gyp"
}

View file

@ -0,0 +1,171 @@
#!/bin/bash
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Attach gdb to a running android application. Similar to ndk-gdb.
# Run with --annotate=3 if running under emacs (M-x gdb).
#
# By default it is used to debug content shell, if it is used to
# debug other piceces, '-p' and '-l' options are needed.
# For *unittests_apk (like base_unittests_apk), run with:
# "gdb_apk -p org.chromium.native_test -l out/Release/lib.target -r"
# Run a command through adb shell, strip the extra \r from the output
# and return the correct status code to detect failures. This assumes
# that the adb shell command prints a final \n to stdout.
# args: command to run
# Prints the command's stdout on stdout
# Returns the command's status
# Note: the command's stderr is lost
adb_shell () {
local TMPOUT="$(mktemp)"
local LASTLINE RET
local ADB=${ADB:-adb}
# The weird sed rule is to strip the final \r on each output line
# Since 'adb shell' never returns the command's proper exit/status code,
# we force it to print it as '%%<status>' in the temporary output file,
# which we will later strip from it.
$ADB shell $@ ";" echo "%%\$?" 2>/dev/null | sed -e 's![[:cntrl:]]!!g' > $TMPOUT
# Get last line in log, which contains the exit code from the command
LASTLINE=$(sed -e '$!d' $TMPOUT)
# Extract the status code from the end of the line, which must be '%%<code>'
RET=$(echo "$LASTLINE" | awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,RSTART+2); } }')
# Remove the status code from the last line. Note that this may result in an empty line
LASTLINE=$(echo "$LASTLINE" | awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,1,RSTART-1); } }')
# The output itself: all lines except the status code
sed -e '$d' $TMPOUT && echo -n "$LASTLINE"
# Remove temp file
rm -f $TMPOUT
# Exit with the appropriate status
return $RET
}
adb=$(which adb)
if [[ "$adb" = "" ]] ; then
echo "Need adb in your path"
exit 1
fi
usage() {
echo "usage: ${0##*/} [-p package_name] [-l shared_lib_dir] [-g gdb] [-r]"
echo "-p package_name the android APK package to be debugged"
echo "-l shared_lib_dir directory containes native shared library"
echo "-g gdb_args agruments for gdb, eg: -g '-n -write'"
echo "-r the target device is rooted"
}
process_options() {
local OPTNAME OPTIND OPTERR OPTARG
while getopts ":p:l:g:r" OPTNAME; do
case "$OPTNAME" in
p)
package_name="$OPTARG"
;;
l)
shared_lib_dir="$OPTARG"
;;
g)
gdb_args="$OPTARG"
;;
r)
rooted_phone=1
;;
\:)
echo "'-$OPTARG' needs an argument."
usage
exit 1
;;
*)
echo "invalid command line option: $OPTARG"
usage
exit 1
;;
esac
done
if [ $# -ge ${OPTIND} ]; then
eval echo "Unexpected command line argument: \${${OPTIND}}"
usage
exit 1
fi
}
rooted_phone=0
root=$(dirname $0)/../..
package_name=org.chromium.content_shell
shared_lib_dir=$root/out/${BUILDTYPE:-Debug}/lib.target
gdb_args=''
#process options
process_options "$@"
echo "Debug package $package_name"
echo "Assume native shared library is under $shared_lib_dir"
data_dir=/data/data/$package_name
gdb_server_on_device=$data_dir/lib/gdbserver
# Kill any running gdbserver
pid=$(adb shell ps | awk '/gdbserver/ {print $2}')
if [[ "$pid" != "" ]] ; then
if [[ $rooted_phone -eq 1 ]] ; then
adb shell kill $pid
else
adb shell run-as $package_name kill $pid
fi
fi
pid=$(adb_shell ps | awk "/$package_name$/ {print \$2}")
if [[ "$pid" = "" ]] ; then
echo "No $package_name running?"
echo "Try this: adb shell am start -a android.intent.action.VIEW " \
"-n $package_name/.SomethingActivity (Something might be ContentShell)"
exit 2
fi
no_gdb_server=$(adb shell ls $gdb_server_on_device | grep 'No such file')
if [[ "$no_gdb_server" != "" ]] ; then
echo "No gdb server on device at $gdb_server_on_device"
echo "Please install a debug build."
exit 3
fi
if [[ $rooted_phone -eq 1 ]] ; then
adb shell $gdb_server_on_device :4321 --attach $pid &
adb forward tcp:4321 tcp:4321
else
adb shell run-as $package_name lib/gdbserver +debug-socket --attach $pid &
adb forward tcp:4321 localfilesystem:$data_dir/debug-socket
fi
sleep 2
# Pull app_process and C libraries from device if needed
app_process=${shared_lib_dir}/app_process
if [[ ! -f ${app_process} ]] ; then
adb pull /system/bin/app_process ${app_process}
adb pull /system/lib/libc.so ${shared_lib_dir}
fi
# gdb commands
cmdfile=$(mktemp /tmp/gdb_android_XXXXXXXX)
cat >$cmdfile<<EOF
# set solib-absolute-prefix null
set solib-search-path ${shared_lib_dir}
file ${app_process}
target remote :4321
EOF
gdb=$(echo $ANDROID_TOOLCHAIN/../../linux-x86/bin/*gdb)
if [[ ! -f ${gdb} ]] ; then
echo "Wow no gdb in env var ANDROID_TOOLCHAIN which is $ANDROID_TOOLCHAIN"
exit 4
else
echo Using $gdb
fi
# ${gdb} -x $cmdfile $* $app_process
${gdb} -x $cmdfile $gdb_args
rm $cmdfile

View file

@ -0,0 +1,15 @@
#!/bin/bash
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Attach gdb to a running content shell. Redirect to the shell gdb_apk
ROOT=$(cd "$(dirname $0)"; pwd)
if [ $# -gt 0 ]; then
exec ${ROOT}/gdb_apk -r -g "$*"
else
exec ${ROOT}/gdb_apk -r -p org.chromium.content_shell
fi

View file

@ -0,0 +1,29 @@
# List of suppressions
# Android will not support StackTrace.
StackTrace.*
#
# Sometimes this is automatically generated by run_tests.py
VerifyPathControlledByUserTest.Symlinks
# http://crbug.com/138845
MessagePumpLibeventTest.TestWatchingFromBadThread
StringPrintfTest.StringPrintfMisc
StringPrintfTest.StringAppendfString
StringPrintfTest.StringAppendfInt
StringPrintfTest.StringPrintfBounds
ProcessUtilTest.GetAppOutputRestrictedSIGPIPE
# TODO(jrg): Fails on bots. Works locally. Figure out why. 2/6/12
FieldTrialTest.*
# TODO(zhenghao): Fail from build 6102 r123270. http://crbug.com/115612
StackContainer.BufferAlignment
# Failed on bot since it was first introduced.
FileUtilProxyTest.Touch
# Failing because FD remapping not supported in multiprocess_test_android.cc yet.
ProcessUtilTest.FDRemapping
# Flaky?
ScopedJavaRefTest.RefCounts
# Death tests are not supported with apks.
*DeathTest*

View file

@ -0,0 +1,10 @@
# Addtional list of suppressions from emulator
#
# Automatically generated by run_tests.py
PathServiceTest.Get
SharedMemoryTest.OpenClose
StringPrintfTest.StringAppendfInt
StringPrintfTest.StringAppendfString
StringPrintfTest.StringPrintfBounds
StringPrintfTest.StringPrintfMisc
VerifyPathControlledByUserTest.Symlinks

View file

@ -0,0 +1,21 @@
# List of suppressions
AudioRendererHostTest.CreateAndClose
AudioRendererHostTest.CreateAndShutdown
AudioRendererHostTest.CreatePlayAndClose
AudioRendererHostTest.CreatePlayPauseAndClose
AudioRendererHostTest.SetVolume
AudioRendererHostTest.CreatePlayAndShutdown
AudioRendererHostTest.CreatePlayPauseAndShutdown
AudioRendererHostTest.SimulateError
AudioRendererHostTest.SimulateErrorAndClose
# crbug.com/104950
DeviceOrientationProviderTest.ObserverNotRemoved
DeviceOrientationProviderTest.StartFailing
# crbug.com/138930
SkCanvasVideoRendererTest.*
# crbug.com/139095
RenderWidgetTest.OnMsgPaintAtSize
# crbug.com/147549
GamepadProviderTest.PollingAccess
PepperGamepadHostTest.WaitForReply

View file

@ -0,0 +1,14 @@
# Times out
IPCSyncChannelTest.ChattyServer
# MultiProcessTest related failures. These tests fail if DCHECK is enabled.
IPCChannelPosixTest.AdvancedConnected
IPCChannelPosixTest.ResetState
IPCChannelPosixTest.MultiConnection
IPCFuzzingTest.SanityTest
IPCFuzzingTest.MsgBadPayloadArgs
IPCChannelTest.DescriptorTest
IPCChannelTest.ChannelTest
IPCChannelTest.ChannelProxyTest
IPCChannelTest.SendMessageInChannelConnected
SyncSocketTest.SanityTest

View file

@ -0,0 +1,11 @@
# List of suppressions
# crbug.com/136720
CrossProcessNotificationMultiProcessTest.Basic
# Death tests are not supported on APK
# http://crbug.com/138855
CompositeFilterDeathTest.*
# http://crbug.com/138833
AesDecryptorTest.*

View file

@ -0,0 +1,124 @@
# List of suppressions.
# Generated by hand to get net_unittests running initially.
# Likely too aggressive disabling.
DiskCacheBackendTest.*
TransportSecurityStateTest.ParseSidePins*
# TODO(jnd): The following tests are disabled because test server spawner is not
# enabled on Chromium Android. Once the net upstreaming is done, test server
# spawner can be enabled and those tests should pass.
# BUG=135165
X509CertificateTest.*
X509CertificateParseTest.*
HttpNetworkTransactionSpdy2Test.SOCKS4_HTTP_GET
HttpNetworkTransactionSpdy2Test.SOCKS4_SSL_GET
HttpNetworkTransactionSpdy21Test.SOCKS4_HTTP_GET
HttpNetworkTransactionSpdy21Test.SOCKS4_SSL_GET
HttpNetworkTransactionSpdy3Test.SOCKS4_HTTP_GET
HttpNetworkTransactionSpdy3Test.SOCKS4_SSL_GET
HttpNetworkTransactionTest.SOCKS4_HTTP_GET
HttpNetworkTransactionTest.SOCKS4_SSL_GET
HttpNetworkTransactionTest.UploadUnreadableFile
HttpNetworkTransactionTest.UnreadableUploadFileAfterAuthRestart
ProxyResolverJSBindingsTest.MyIpAddress
ProxyScriptFetcherImplTest.*
SOCKSClientSocketTest.*
SSLClientSocketTest.*
PythonUtils.PythonRunTime
URLRequestTestHTTP.*
URLRequestTestFTP.*
HTTPSRequestTest.HTTPSMismatchedTest
HTTPSRequestTest.HTTPSExpiredTest
HTTPSRequestTest.HTTPSPreloadedHSTSTest
HTTPSRequestTest.ClientAuthTest
URLRequestTest.DelayedCookieCallback
URLRequestTest.DoNotSendCookies
URLRequestTest.DoNotSaveCookies
URLRequestTest.DoNotSendCookies_ViaPolicy
URLRequestTest.DoNotSaveCookies_ViaPolicy
URLRequestTest.DoNotSaveEmptyCookies
URLRequestTest.DoNotSendCookies_ViaPolicy_Async
URLRequestTest.DoNotSaveCookies_ViaPolicy_Async
URLRequestTest.DoNotOverrideReferrer
WebSocketJobSpdy2Test.ThrottlingWebSocket
WebSocketJobSpdy2Test.ThrottlingWebSocketSpdyEnabled
WebSocketJobSpdy2Test.ThrottlingSpdy
WebSocketJobSpdy2Test.ThrottlingSpdySpdyEnabled
WebSocketJobSpdy3Test.ThrottlingWebSocket
WebSocketJobSpdy3Test.ThrottlingWebSocketSpdyEnabled
WebSocketJobSpdy3Test.ThrottlingSpdy
WebSocketJobSpdy3Test.ThrottlingSpdySpdyEnabled
WebSocketJobTest.ThrottlingWebSocket
WebSocketJobTest.ThrottlingWebSocketSpdyEnabled
WebSocketJobTest.ThrottlingSpdy
WebSocketJobTest.ThrottlingSpdySpdyEnabled
X509CertificateWeakDigestTest.*
*/X509CertificateWeakDigestTest.*
TransportSecurityStateTest.BogusPinsHeaders
TransportSecurityStateTest.ValidPinsHeadersSHA1
TransportSecurityStateTest.ValidPinsHeadersSHA256
HTTPSRequestTest.ResumeTest
HTTPSRequestTest.SSLSessionCacheShardTest
HTTPSRequestTest.HTTPSErrorsNoClobberTSSTest
HttpNetworkTransactionSpdy3Test.UploadUnreadableFile
HttpNetworkTransactionSpdy2Test.UploadUnreadableFile
HTTPSRequestTest.SSLv3Fallback
HTTPSEVCRLSetTest.FreshCRLSet
HTTPSCRLSetTest.ExpiredCRLSet
URLFetcherTest.SameThreadsTest
URLFetcherTest.DifferentThreadsTest
URLFetcherTest.CancelAll
URLFetcherPostTest.Basic
URLFetcherUploadProgressTest.Basic
URLFetcherDownloadProgressTest.Basic
URLFetcherDownloadProgressCancelTest.CancelWhileProgressReport
URLFetcherHeadersTest.Headers
URLFetcherSocketAddressTest.SocketAddress
URLFetcherStopOnRedirectTest.StopOnRedirect
URLFetcherProtectTest.Overload
URLFetcherProtectTest.ServerUnavailable
URLFetcherProtectTestPassedThrough.ServerUnavailablePropagateResponse
URLFetcherBadHTTPSTest.BadHTTPSTest
URLFetcherCancelTest.ReleasesContext
URLFetcherCancelTest.CancelWhileDelayedStartTaskPending
URLFetcherMultipleAttemptTest.SameData
URLFetcherFileTest.SmallGet
URLFetcherFileTest.LargeGet
URLFetcherFileTest.CantakeOfFile
URLFetcherFileTest.OverwriteExistingFile
URLFetcherFileTest.TryToOverwriteDirectory
URLFetcherFileTest.SmallGetToTempFile
URLFetcherFileTest.LargeGetToTempFile
URLFetcherFileTest.CantakeOfTempFile
URLFetcherEmptyPostTest.Basic
# TODO(jrg): Fails on bots. Works locally. Figure out why. 2/6/12
HttpCache.TypicalGET_ConditionalRequest
HttpCache.RangeGET_OK
HttpCache.RangeGET_SyncOK
HttpCache.RangeGET_Revalidate2
HttpCache.RangeGET_Previous200
HttpCache.RangeGET_Cancel2
# crbug.com/139144
UDPSocketTest.Broadcast
UDPSocketTest.ConnectRandomBind
UDPSocketTest.ClientGetLocalPeerAddresses
# Disabled 4/13/12 by jrg. More CertVerifyProcTest.* tests fail on
# the bot than locally; disabling them all for now.
CertVerifyProcTest.*
# Runs fine locally but not on the bot.
VerifyRoot/CertVerifyProcWeakDigestTest.*
# Relies on TestServer which isn't yet configured upstream.
URLRequestContextBuilderTest.*
# All cert_verify_proc_unittest.cc failing since JNI was enabled.
VerifyIntermediate/CertVerifyProcWeakDigestTest.*
VerifyEndEntity/CertVerifyProcWeakDigestTest.*
VerifyIncompleteIntermediate/CertVerifyProcWeakDigestTest.*
VerifyIncompleteEndEntity/CertVerifyProcWeakDigestTest.*
VerifyMixed/CertVerifyProcWeakDigestTest.*
# Death tests are not supported with apks.
*DeathTest*
# These are death tests and thus also disabled.
PrioritizedDispatcherTest.CancelNull
PrioritizedDispatcherTest.CancelMissing

View file

@ -0,0 +1,4 @@
SyncHttpBridgeTest.*
# crbug.com/144422
OnDiskSyncableDirectory.FailInitialWrite

View file

@ -0,0 +1,44 @@
# List of suppressions
# This file was automatically generated by build/android/run_tests.py
BytesFormattingTest.FormatBytes
CanvasTest.StringSizeEmptyString
CanvasTest.StringWidth
ClipboardTest.RTFTest
FontListTest.FontDescString_FromFont
FontListTest.FontDescString_FromFontVector
FontListTest.FontDescString_FromFontWithNonNormalStyle
FontListTest.Fonts_DeriveFontList
FontListTest.Fonts_DeriveFontListWithSize
FontListTest.Fonts_DescStringWithStyleInFlexibleFormat_RoundTrip
FontListTest.Fonts_FontVector_RoundTrip
FontListTest.Fonts_FromDescString
FontListTest.Fonts_FromDescStringInFlexibleFormat
FontListTest.Fonts_FromDescStringWithStyleInFlexibleFormat
FontListTest.Fonts_FromFont
FontListTest.Fonts_FromFontVector
FontListTest.Fonts_FromFontWithNonNormalStyle
FontListTest.Fonts_GetStyle
FontTest.Ascent
FontTest.AvgCharWidth
FontTest.AvgWidths
FontTest.Height
FontTest.LoadArial
FontTest.LoadArialBold
FontTest.Widths
L10nUtilTest.GetDisplayNameForCountry
L10nUtilTest.GetDisplayNameForLocale
ResourceBundle.DelegateGetFont
TextEliderTest.ElideEmail
TextEliderTest.ElideEmailMoreSpace
TextEliderTest.ElideRectangleText
TextEliderTest.ElideRectangleTextLongWords
TextEliderTest.ElideRectangleTextPunctuation
TextEliderTest.ElideTextLongStrings
TextEliderTest.ElideTextSurrogatePairs
TextEliderTest.ElideTextTruncate
TextEliderTest.TestFileURLEliding
TextEliderTest.TestFilenameEliding
TextEliderTest.TestGeneralEliding
TextEliderTest.TestMoreEliding
TextEliderTest.TestTrailingEllipsisSlashEllipsisHack
TreeNodeIteratorPruneTest.Test

View file

@ -0,0 +1,149 @@
# List of suppressions
# crbug.com/139429
BrowserMainTest.WarmConnectionFieldTrial_Invalid
BrowserMainTest.WarmConnectionFieldTrial_Random
BrowserMainTest.WarmConnectionFieldTrial_WarmestSocket
# crbug.com/139431
ChromePaths.UserCacheDir
# The UDP related tests currently do not work on Android because
# we lack a UDP forwarder tool.
NetworkStatsTestUDP.*
# Missing test resource of 16MB.
HistoryProfileTest.TypicalProfileVersion
# crbug.com/139408
SQLitePersistentCookieStoreTest.TestDontLoadOldSessionCookies
SQLitePersistentCookieStoreTest.PersistIsPersistent
# http://crbug.com/117940
TemplateURLTest.*
TemplateURLPrepopulateDataTest.*
TemplateURLServiceSyncTest.*
SearchHostToURLsMapTest.*
# crbug.com/139427
TemplateURLFetcherTest.*
# crbug.com/139433
AutofillTableTest.AutofillProfile*
AutofillTableTest.UpdateAutofillProfile
# crbug.com/139400
AutofillProfileTest.*
CreditCardTest.SetInfoExpirationMonth
# crbug.com/139398
DownloadItemModelTest.InterruptTooltip
# Tests crashing in the APK
# DEATH test, forking and doing bad stuff, not supported yet.
IncognitoModePrefsDeathTest.GetAvailabilityBadValue
# l10n_util.cc(655)] Check failed: std::string::npos != pos
DownloadItemModelTest.InterruptStatus
# l10n_util.cc(655)] Check failed: std::string::npos != pos
WebsiteSettingsTest.OnSiteDataAccessed
# Tests failing in the APK (mostly failing to create files out of the Application dir).
DiagnosticsModelTest.RunAll
FirstRunTest.RemoveSentinel
GoogleUpdateTest.StatsConsent
URLFixerUpperTest.FixupFile
URLFixerUpperTest.FixupRelativeFile
DownloadPathReservationTrackerTest.CreateDefaultDownloadPath
# crbug.com/139423
ValueStoreFrontendTest.GetExistingData
# crbug.com/139421
ChromeSelectFilePolicyTest.ExpectAsynchronousListenerCall
# http://crbug.com/139033
ChromeDownloadManagerDelegateTest.StartDownload_PromptAlways
# http://crbug.com/139165
KeywordEditorControllerTest.*
# Extension support is limited on Android.
# Some of these can be enabled if we register extension related prefs in
# browser_prefs.cc
ExtensionTest.*
ExtensionAPI.*
ExtensionFileUtil.*
ExtensionPermissionsTest.*
ExtensionUnpackerTest.*
ActiveTabTest.*
ExtensionAppsPromo.*
ComponentLoaderTest.*
ExtensionFromUserScript.*
ExtensionFromWebApp.*
ExtensionIconManagerTest.*
ExtensionServiceTest.*
ExtensionServiceTestSimple.*
ExtensionSourcePriorityTest.*
ExtensionSpecialStoragePolicyTest.*
ExternalPolicyProviderTest.*
MenuManagerTest.*
PageActionControllerTest.*
PermissionsUpdaterTest.*
ImageLoadingTrackerTest.*
ScriptBadgeControllerTest.*
ExtensionSettingsFrontendTest.*
ExtensionSettingsSyncTest.*
ExtensionUpdaterTest.*
UserScriptListenerTest.*
WebApplicationTest.GetShortcutInfoForTab
ExtensionActionIconFactoryTest.*
# crbug.com/139411
AutocompleteProviderTest.*
HistoryContentsProviderBodyOnlyTest.*
HistoryContentsProviderTest.*
HQPOrderingTest.*
SearchProviderTest.*
# Test server and forwarder and not ready yet.
ProtocolHandlerRegistryTest.TestOSRegistrationFailure
ConnectionTesterTest.*
HttpPipeliningCompatibilityClientTest.*
NetworkStatsTestTCP.*
ConnectionTesterTest.RunAllTests
# crbug.com/139418
SQLiteServerBoundCertStoreTest.TestUpgradeV1
SQLiteServerBoundCertStoreTest.TestUpgradeV2
ProfileSyncComponentsFactoryImplTest.*
PermissionsTest.GetWarningMessages_Plugins
ImageOperations.ResizeShouldAverageColors
# crbug.com/138275
PrerenderTest.*
RenderWidgetTest.OnMsgPaintAtSize
# crbug.com/146857
TopSitesTest.Blacklisting
# crbug.com/139643
VariationsUtilTest.DisableAfterInitialization
VariationsUtilTest.AssociateGoogleVariationID
VariationsUtilTest.NoAssociation
# crbug.com/141473
AutofillManagerTest.UpdatePasswordSyncState
AutofillManagerTest.UpdatePasswordGenerationState
# crbug.com/144227
ExtensionIconImageTest.*
# crbug.com/145843
EntropyProviderTest.UseOneTimeRandomizationSHA1
EntropyProviderTest.UseOneTimeRandomizationPermuted
# crbug.com/147500
ManifestTest.RestrictedKeys
# crbug.com/152599
SyncSearchEngineDataTypeControllerTest.*

View file

@ -0,0 +1,253 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides a convenient wrapper for spawning a test lighttpd instance.
Usage:
lighttpd_server PATH_TO_DOC_ROOT
"""
import codecs
import contextlib
import httplib
import os
import random
import shutil
import socket
import subprocess
import sys
import tempfile
import time
from pylib import constants
from pylib import pexpect
class LighttpdServer(object):
"""Wraps lighttpd server, providing robust startup.
Args:
document_root: Path to root of this server's hosted files.
port: TCP port on the _host_ machine that the server will listen on. If
ommitted it will attempt to use 9000, or if unavailable it will find
a free port from 8001 - 8999.
lighttpd_path, lighttpd_module_path: Optional paths to lighttpd binaries.
base_config_path: If supplied this file will replace the built-in default
lighttpd config file.
extra_config_contents: If specified, this string will be appended to the
base config (default built-in, or from base_config_path).
config_path, error_log, access_log: Optional paths where the class should
place temprary files for this session.
"""
def __init__(self, document_root, port=None,
lighttpd_path=None, lighttpd_module_path=None,
base_config_path=None, extra_config_contents=None,
config_path=None, error_log=None, access_log=None):
self.temp_dir = tempfile.mkdtemp(prefix='lighttpd_for_chrome_android')
self.document_root = os.path.abspath(document_root)
self.fixed_port = port
self.port = port or constants.LIGHTTPD_DEFAULT_PORT
self.server_tag = 'LightTPD ' + str(random.randint(111111, 999999))
self.lighttpd_path = lighttpd_path or '/usr/sbin/lighttpd'
self.lighttpd_module_path = lighttpd_module_path or '/usr/lib/lighttpd'
self.base_config_path = base_config_path
self.extra_config_contents = extra_config_contents
self.config_path = config_path or self._Mktmp('config')
self.error_log = error_log or self._Mktmp('error_log')
self.access_log = access_log or self._Mktmp('access_log')
self.pid_file = self._Mktmp('pid_file')
self.process = None
def _Mktmp(self, name):
return os.path.join(self.temp_dir, name)
def _GetRandomPort(self):
# The ports of test server is arranged in constants.py.
return random.randint(constants.LIGHTTPD_RANDOM_PORT_FIRST,
constants.LIGHTTPD_RANDOM_PORT_LAST)
def StartupHttpServer(self):
"""Starts up a http server with specified document root and port."""
# If we want a specific port, make sure no one else is listening on it.
if self.fixed_port:
self._KillProcessListeningOnPort(self.fixed_port)
while True:
if self.base_config_path:
# Read the config
with codecs.open(self.base_config_path, 'r', 'utf-8') as f:
config_contents = f.read()
else:
config_contents = self._GetDefaultBaseConfig()
if self.extra_config_contents:
config_contents += self.extra_config_contents
# Write out the config, filling in placeholders from the members of |self|
with codecs.open(self.config_path, 'w', 'utf-8') as f:
f.write(config_contents % self.__dict__)
if (not os.path.exists(self.lighttpd_path) or
not os.access(self.lighttpd_path, os.X_OK)):
raise EnvironmentError(
'Could not find lighttpd at %s.\n'
'It may need to be installed (e.g. sudo apt-get install lighttpd)'
% self.lighttpd_path)
self.process = pexpect.spawn(self.lighttpd_path,
['-D', '-f', self.config_path,
'-m', self.lighttpd_module_path],
cwd=self.temp_dir)
client_error, server_error = self._TestServerConnection()
if not client_error:
assert int(open(self.pid_file, 'r').read()) == self.process.pid
break
self.process.close()
if self.fixed_port or not 'in use' in server_error:
print 'Client error:', client_error
print 'Server error:', server_error
return False
self.port = self._GetRandomPort()
return True
def ShutdownHttpServer(self):
"""Shuts down our lighttpd processes."""
if self.process:
self.process.terminate()
shutil.rmtree(self.temp_dir, ignore_errors=True)
def _TestServerConnection(self):
# Wait for server to start
server_msg = ''
for timeout in xrange(1, 5):
client_error = None
try:
with contextlib.closing(httplib.HTTPConnection(
'127.0.0.1', self.port, timeout=timeout)) as http:
http.set_debuglevel(timeout > 3)
http.request('HEAD', '/')
r = http.getresponse()
r.read()
if (r.status == 200 and r.reason == 'OK' and
r.getheader('Server') == self.server_tag):
return (None, server_msg)
client_error = ('Bad response: %s %s version %s\n ' %
(r.status, r.reason, r.version) +
'\n '.join([': '.join(h) for h in r.getheaders()]))
except (httplib.HTTPException, socket.error) as client_error:
pass # Probably too quick connecting: try again
# Check for server startup error messages
ix = self.process.expect([pexpect.TIMEOUT, pexpect.EOF, '.+'],
timeout=timeout)
if ix == 2: # stdout spew from the server
server_msg += self.process.match.group(0)
elif ix == 1: # EOF -- server has quit so giveup.
client_error = client_error or 'Server exited'
break
return (client_error or 'Timeout', server_msg)
def _KillProcessListeningOnPort(self, port):
"""Checks if there is a process listening on port number |port| and
terminates it if found.
Args:
port: Port number to check.
"""
if subprocess.call(['fuser', '-kv', '%d/tcp' % port]) == 0:
# Give the process some time to terminate and check that it is gone.
time.sleep(2)
assert subprocess.call(['fuser', '-v', '%d/tcp' % port]) != 0, \
'Unable to kill process listening on port %d.' % port
def _GetDefaultBaseConfig(self):
return """server.tag = "%(server_tag)s"
server.modules = ( "mod_access",
"mod_accesslog",
"mod_alias",
"mod_cgi",
"mod_rewrite" )
# default document root required
#server.document-root = "."
# files to check for if .../ is requested
index-file.names = ( "index.php", "index.pl", "index.cgi",
"index.html", "index.htm", "default.htm" )
# mimetype mapping
mimetype.assign = (
".gif" => "image/gif",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".png" => "image/png",
".svg" => "image/svg+xml",
".css" => "text/css",
".html" => "text/html",
".htm" => "text/html",
".xhtml" => "application/xhtml+xml",
".xhtmlmp" => "application/vnd.wap.xhtml+xml",
".js" => "application/x-javascript",
".log" => "text/plain",
".conf" => "text/plain",
".text" => "text/plain",
".txt" => "text/plain",
".dtd" => "text/xml",
".xml" => "text/xml",
".manifest" => "text/cache-manifest",
)
# Use the "Content-Type" extended attribute to obtain mime type if possible
mimetype.use-xattr = "enable"
##
# which extensions should not be handle via static-file transfer
#
# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi
static-file.exclude-extensions = ( ".php", ".pl", ".cgi" )
server.bind = "127.0.0.1"
server.port = %(port)s
## virtual directory listings
dir-listing.activate = "enable"
#dir-listing.encoding = "iso-8859-2"
#dir-listing.external-css = "style/oldstyle.css"
## enable debugging
#debug.log-request-header = "enable"
#debug.log-response-header = "enable"
#debug.log-request-handling = "enable"
#debug.log-file-not-found = "enable"
#### SSL engine
#ssl.engine = "enable"
#ssl.pemfile = "server.pem"
# Autogenerated test-specific config follows.
cgi.assign = ( ".cgi" => "/usr/bin/env",
".pl" => "/usr/bin/env",
".asis" => "/bin/cat",
".php" => "/usr/bin/php-cgi" )
server.errorlog = "%(error_log)s"
accesslog.filename = "%(access_log)s"
server.upload-dirs = ( "/tmp" )
server.pid-file = "%(pid_file)s"
server.document-root = "%(document_root)s"
"""
def main(argv):
server = LighttpdServer(*argv[1:])
try:
if server.StartupHttpServer():
raw_input('Server running at http://127.0.0.1:%s -'
' press Enter to exit it.' % server.port)
else:
print 'Server exit code:', server.process.exitstatus
finally:
server.ShutdownHttpServer()
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -0,0 +1,4 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Gathers information about APKs."""
import collections
import os
import re
import cmd_helper
class ApkInfo(object):
"""Helper class for inspecting APKs."""
_PROGUARD_PATH = os.path.join(os.environ['ANDROID_SDK_ROOT'],
'tools/proguard/bin/proguard.sh')
if not os.path.exists(_PROGUARD_PATH):
_PROGUARD_PATH = os.path.join(os.environ['ANDROID_BUILD_TOP'],
'external/proguard/bin/proguard.sh')
_PROGUARD_CLASS_RE = re.compile(r'\s*?- Program class:\s*([\S]+)$')
_PROGUARD_METHOD_RE = re.compile(r'\s*?- Method:\s*(\S*)[(].*$')
_PROGUARD_ANNOTATION_RE = re.compile(r'\s*?- Annotation \[L(\S*);\]:$')
_PROGUARD_ANNOTATION_CONST_RE = re.compile(r'\s*?- Constant element value.*$')
_PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$')
_AAPT_PACKAGE_NAME_RE = re.compile(r'package: .*name=\'(\S*)\'')
def __init__(self, apk_path, jar_path):
if not os.path.exists(apk_path):
raise Exception('%s not found, please build it' % apk_path)
self._apk_path = apk_path
if not os.path.exists(jar_path):
raise Exception('%s not found, please build it' % jar_path)
self._jar_path = jar_path
self._annotation_map = collections.defaultdict(list)
self._test_methods = []
self._Initialize()
def _Initialize(self):
proguard_output = cmd_helper.GetCmdOutput([self._PROGUARD_PATH,
'-injars', self._jar_path,
'-dontshrink',
'-dontoptimize',
'-dontobfuscate',
'-dontpreverify',
'-dump',
]).split('\n')
clazz = None
method = None
annotation = None
has_value = False
qualified_method = None
for line in proguard_output:
m = self._PROGUARD_CLASS_RE.match(line)
if m:
clazz = m.group(1).replace('/', '.') # Change package delim.
annotation = None
continue
m = self._PROGUARD_METHOD_RE.match(line)
if m:
method = m.group(1)
annotation = None
qualified_method = clazz + '#' + method
if method.startswith('test') and clazz.endswith('Test'):
self._test_methods += [qualified_method]
continue
m = self._PROGUARD_ANNOTATION_RE.match(line)
if m:
assert qualified_method
annotation = m.group(1).split('/')[-1] # Ignore the annotation package.
self._annotation_map[qualified_method].append(annotation)
has_value = False
continue
if annotation:
assert qualified_method
if not has_value:
m = self._PROGUARD_ANNOTATION_CONST_RE.match(line)
if m:
has_value = True
else:
m = self._PROGUARD_ANNOTATION_VALUE_RE.match(line)
if m:
value = m.group(1)
self._annotation_map[qualified_method].append(
annotation + ':' + value)
has_value = False
def _GetAnnotationMap(self):
return self._annotation_map
def _IsTestMethod(self, test):
class_name, method = test.split('#')
return class_name.endswith('Test') and method.startswith('test')
def GetApkPath(self):
return self._apk_path
def GetPackageName(self):
"""Returns the package name of this APK."""
aapt_output = cmd_helper.GetCmdOutput(
['aapt', 'dump', 'badging', self._apk_path]).split('\n')
for line in aapt_output:
m = self._AAPT_PACKAGE_NAME_RE.match(line)
if m:
return m.group(1)
raise Exception('Failed to determine package name of %s' % self._apk_path)
def GetTestAnnotations(self, test):
"""Returns a list of all annotations for the given |test|. May be empty."""
if not self._IsTestMethod(test):
return []
return self._GetAnnotationMap()[test]
def _AnnotationsMatchFilters(self, annotation_filter_list, annotations):
"""Checks if annotations match any of the filters."""
if not annotation_filter_list:
return True
for annotation_filter in annotation_filter_list:
filters = annotation_filter.split('=')
if len(filters) == 2:
key = filters[0]
value_list = filters[1].split(',')
for value in value_list:
if key + ':' + value in annotations:
return True
elif annotation_filter in annotations:
return True
return False
def GetAnnotatedTests(self, annotation_filter_list):
"""Returns a list of all tests that match the given annotation filters."""
return [test for test, annotations in self._GetAnnotationMap().iteritems()
if self._IsTestMethod(test) and self._AnnotationsMatchFilters(
annotation_filter_list, annotations)]
def GetTestMethods(self):
"""Returns a list of all test methods in this apk as Class#testMethod."""
return self._test_methods
@staticmethod
def IsPythonDrivenTest(test):
return 'pythonDrivenTests' in test

View file

@ -0,0 +1,210 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import contextlib
import httplib
import logging
import os
import tempfile
import time
import android_commands
import constants
from chrome_test_server_spawner import SpawningServer
import constants
from flag_changer import FlagChanger
from forwarder import Forwarder
import lighttpd_server
import ports
from valgrind_tools import CreateTool
# A file on device to store ports of net test server. The format of the file is
# test-spawner-server-port:test-server-port
NET_TEST_SERVER_PORT_INFO_FILE = 'net-test-server-ports'
class BaseTestRunner(object):
"""Base class for running tests on a single device.
A subclass should implement RunTests() with no parameter, so that calling
the Run() method will set up tests, run them and tear them down.
"""
def __init__(self, device, tool, shard_index, build_type):
"""
Args:
device: Tests will run on the device of this ID.
shard_index: Index number of the shard on which the test suite will run.
build_type: 'Release' or 'Debug'.
"""
self.device = device
self.adb = android_commands.AndroidCommands(device=device)
self.tool = CreateTool(tool, self.adb)
self._http_server = None
self._forwarder = None
self._forwarder_device_port = 8000
self.forwarder_base_url = ('http://localhost:%d' %
self._forwarder_device_port)
self.flags = FlagChanger(self.adb)
self.shard_index = shard_index
self.flags.AddFlags(['--disable-fre'])
self._spawning_server = None
self._spawner_forwarder = None
# We will allocate port for test server spawner when calling method
# LaunchChromeTestServerSpawner and allocate port for test server when
# starting it in TestServerThread.
self.test_server_spawner_port = 0
self.test_server_port = 0
self.build_type = build_type
def _PushTestServerPortInfoToDevice(self):
"""Pushes the latest port information to device."""
self.adb.SetFileContents(self.adb.GetExternalStorage() + '/' +
NET_TEST_SERVER_PORT_INFO_FILE,
'%d:%d' % (self.test_server_spawner_port,
self.test_server_port))
def Run(self):
"""Calls subclass functions to set up tests, run them and tear them down.
Returns:
Test results returned from RunTests().
"""
if not self.HasTests():
return True
self.SetUp()
try:
return self.RunTests()
finally:
self.TearDown()
def SetUp(self):
"""Called before tests run."""
pass
def HasTests(self):
"""Whether the test suite has tests to run."""
return True
def RunTests(self):
"""Runs the tests. Need to be overridden."""
raise NotImplementedError
def TearDown(self):
"""Called when tests finish running."""
self.ShutdownHelperToolsForTestSuite()
def CopyTestData(self, test_data_paths, dest_dir):
"""Copies |test_data_paths| list of files/directories to |dest_dir|.
Args:
test_data_paths: A list of files or directories relative to |dest_dir|
which should be copied to the device. The paths must exist in
|CHROME_DIR|.
dest_dir: Absolute path to copy to on the device.
"""
for p in test_data_paths:
self.adb.PushIfNeeded(
os.path.join(constants.CHROME_DIR, p),
os.path.join(dest_dir, p))
def LaunchTestHttpServer(self, document_root, port=None,
extra_config_contents=None):
"""Launches an HTTP server to serve HTTP tests.
Args:
document_root: Document root of the HTTP server.
port: port on which we want to the http server bind.
extra_config_contents: Extra config contents for the HTTP server.
"""
self._http_server = lighttpd_server.LighttpdServer(
document_root, port=port, extra_config_contents=extra_config_contents)
if self._http_server.StartupHttpServer():
logging.info('http server started: http://localhost:%s',
self._http_server.port)
else:
logging.critical('Failed to start http server')
self.StartForwarderForHttpServer()
return (self._forwarder_device_port, self._http_server.port)
def StartForwarder(self, port_pairs):
"""Starts TCP traffic forwarding for the given |port_pairs|.
Args:
host_port_pairs: A list of (device_port, local_port) tuples to forward.
"""
if self._forwarder:
self._forwarder.Close()
self._forwarder = Forwarder(
self.adb, port_pairs, self.tool, '127.0.0.1', self.build_type)
def StartForwarderForHttpServer(self):
"""Starts a forwarder for the HTTP server.
The forwarder forwards HTTP requests and responses between host and device.
"""
self.StartForwarder([(self._forwarder_device_port, self._http_server.port)])
def RestartHttpServerForwarderIfNecessary(self):
"""Restarts the forwarder if it's not open."""
# Checks to see if the http server port is being used. If not forwards the
# request.
# TODO(dtrainor): This is not always reliable because sometimes the port
# will be left open even after the forwarder has been killed.
if not ports.IsDevicePortUsed(self.adb,
self._forwarder_device_port):
self.StartForwarderForHttpServer()
def ShutdownHelperToolsForTestSuite(self):
"""Shuts down the server and the forwarder."""
# Forwarders should be killed before the actual servers they're forwarding
# to as they are clients potentially with open connections and to allow for
# proper hand-shake/shutdown.
if self._forwarder or self._spawner_forwarder:
# Kill all forwarders on the device and then kill the process on the host
# (if it exists)
self.adb.KillAll('device_forwarder')
if self._forwarder:
self._forwarder.Close()
if self._spawner_forwarder:
self._spawner_forwarder.Close()
if self._http_server:
self._http_server.ShutdownHttpServer()
if self._spawning_server:
self._spawning_server.Stop()
self.flags.Restore()
def LaunchChromeTestServerSpawner(self):
"""Launches test server spawner."""
server_ready = False
error_msgs = []
# Try 3 times to launch test spawner server.
for i in xrange(0, 3):
# Do not allocate port for test server here. We will allocate
# different port for individual test in TestServerThread.
self.test_server_spawner_port = ports.AllocateTestServerPort()
self._spawning_server = SpawningServer(self.test_server_spawner_port,
self.adb,
self.tool,
self.build_type)
self._spawning_server.Start()
server_ready, error_msg = ports.IsHttpServerConnectable(
'127.0.0.1', self.test_server_spawner_port, path='/ping',
expected_read='ready')
if server_ready:
break
else:
error_msgs.append(error_msg)
self._spawning_server.Stop()
# Wait for 2 seconds then restart.
time.sleep(2)
if not server_ready:
logging.error(';'.join(error_msgs))
raise Exception('Can not start the test spawner server.')
self._PushTestServerPortInfoToDevice()
self._spawner_forwarder = Forwarder(
self.adb,
[(self.test_server_spawner_port, self.test_server_spawner_port)],
self.tool, '127.0.0.1', self.build_type)

View file

@ -0,0 +1,113 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import multiprocessing
from test_result import TestResults
def _ShardedTestRunnable(test):
"""Standalone function needed by multiprocessing.Pool."""
log_format = '[' + test.device + '] # %(asctime)-15s: %(message)s'
if logging.getLogger().handlers:
logging.getLogger().handlers[0].setFormatter(logging.Formatter(log_format))
else:
logging.basicConfig(format=log_format)
# Handle SystemExit here since python has a bug to exit current process
try:
return test.Run()
except SystemExit:
return TestResults()
def SetTestsContainer(tests_container):
"""Sets tests container.
multiprocessing.Queue can't be pickled across processes, so we need to set
this as a 'global', per process, via multiprocessing.Pool.
"""
BaseTestSharder.tests_container = tests_container
class BaseTestSharder(object):
"""Base class for sharding tests across multiple devices.
Args:
attached_devices: A list of attached devices.
"""
# See more in SetTestsContainer.
tests_container = None
def __init__(self, attached_devices):
self.attached_devices = attached_devices
self.retries = 1
self.tests = []
def CreateShardedTestRunner(self, device, index):
"""Factory function to create a suite-specific test runner.
Args:
device: Device serial where this shard will run
index: Index of this device in the pool.
Returns:
An object of BaseTestRunner type (that can provide a "Run()" method).
"""
pass
def SetupSharding(self, tests):
"""Called before starting the shards."""
pass
def OnTestsCompleted(self, test_runners, test_results):
"""Notifies that we completed the tests."""
pass
def RunShardedTests(self):
"""Runs the tests in all connected devices.
Returns:
A TestResults object.
"""
logging.warning('*' * 80)
logging.warning('Sharding in ' + str(len(self.attached_devices)) +
' devices.')
logging.warning('Note that the output is not synchronized.')
logging.warning('Look for the "Final result" banner in the end.')
logging.warning('*' * 80)
final_results = TestResults()
for retry in xrange(self.retries):
logging.warning('Try %d of %d', retry + 1, self.retries)
self.SetupSharding(self.tests)
test_runners = []
for index, device in enumerate(self.attached_devices):
logging.warning('*' * 80)
logging.warning('Creating shard %d for %s', index, device)
logging.warning('*' * 80)
test_runner = self.CreateShardedTestRunner(device, index)
test_runners += [test_runner]
logging.warning('Starting...')
pool = multiprocessing.Pool(len(self.attached_devices),
SetTestsContainer,
[BaseTestSharder.tests_container])
# map can't handle KeyboardInterrupt exception. It's a python bug.
# So use map_async instead.
async_results = pool.map_async(_ShardedTestRunnable, test_runners)
results_lists = async_results.get(999999)
test_results = TestResults.FromTestResults(results_lists)
if retry == self.retries - 1:
all_passed = final_results.ok + test_results.ok
final_results = test_results
final_results.ok = all_passed
break
else:
final_results.ok += test_results.ok
self.tests = []
for t in test_results.GetAllBroken():
self.tests += [t.name]
if not self.tests:
break
self.OnTestsCompleted(test_runners, final_results)
return final_results

View file

@ -0,0 +1,46 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper functions to print buildbot messages."""
def PrintLink(label, url):
"""Adds a link with name |label| linking to |url| to current buildbot step.
Args:
label: A string with the name of the label.
url: A string of the URL.
"""
print '@@@STEP_LINK@%s@%s@@@' % (label, url)
def PrintMsg(msg):
"""Appends |msg| to the current buildbot step text.
Args:
msg: String to be appended.
"""
print '@@@STEP_TEXT@%s@@@' % msg
def PrintSummaryText(msg):
"""Appends |msg| to main build summary. Visible from waterfall.
Args:
msg: String to be appended.
"""
print '@@@STEP_SUMMARY_TEXT@%s@@@' % msg
def PrintError():
"""Marks the current step as failed."""
print '@@@STEP_FAILURE@@@'
def PrintWarning():
"""Marks the current step with a warning."""
print '@@@STEP_WARNINGS@@@'
def PrintNamedStep(step):
print '@@@BUILD_STEP %s@@@' % step

View file

@ -0,0 +1,402 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A "Test Server Spawner" that handles killing/stopping per-test test servers.
It's used to accept requests from the device to spawn and kill instances of the
chrome test server on the host.
"""
import BaseHTTPServer
import json
import logging
import os
import select
import struct
import subprocess
import threading
import time
import urlparse
import constants
from forwarder import Forwarder
import ports
# Path that are needed to import necessary modules when running testserver.py.
os.environ['PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + ':%s:%s:%s:%s' % (
os.path.join(constants.CHROME_DIR, 'third_party'),
os.path.join(constants.CHROME_DIR, 'third_party', 'tlslite'),
os.path.join(constants.CHROME_DIR, 'third_party', 'pyftpdlib', 'src'),
os.path.join(constants.CHROME_DIR, 'net', 'tools', 'testserver'))
SERVER_TYPES = {
'http': '',
'ftp': '-f',
'sync': '--sync',
'tcpecho': '--tcp-echo',
'udpecho': '--udp-echo',
}
# The timeout (in seconds) of starting up the Python test server.
TEST_SERVER_STARTUP_TIMEOUT = 10
def _CheckPortStatus(port, expected_status):
"""Returns True if port has expected_status.
Args:
port: the port number.
expected_status: boolean of expected status.
Returns:
Returns True if the status is expected. Otherwise returns False.
"""
for timeout in range(1, 5):
if ports.IsHostPortUsed(port) == expected_status:
return True
time.sleep(timeout)
return False
def _GetServerTypeCommandLine(server_type):
"""Returns the command-line by the given server type.
Args:
server_type: the server type to be used (e.g. 'http').
Returns:
A string containing the command-line argument.
"""
if server_type not in SERVER_TYPES:
raise NotImplementedError('Unknown server type: %s' % server_type)
if server_type == 'udpecho':
raise Exception('Please do not run UDP echo tests because we do not have '
'a UDP forwarder tool.')
return SERVER_TYPES[server_type]
class TestServerThread(threading.Thread):
"""A thread to run the test server in a separate process."""
def __init__(self, ready_event, arguments, adb, tool, build_type):
"""Initialize TestServerThread with the following argument.
Args:
ready_event: event which will be set when the test server is ready.
arguments: dictionary of arguments to run the test server.
adb: instance of AndroidCommands.
tool: instance of runtime error detection tool.
build_type: 'Release' or 'Debug'.
"""
threading.Thread.__init__(self)
self.wait_event = threading.Event()
self.stop_flag = False
self.ready_event = ready_event
self.ready_event.clear()
self.arguments = arguments
self.adb = adb
self.tool = tool
self.test_server_process = None
self.is_ready = False
self.host_port = self.arguments['port']
assert isinstance(self.host_port, int)
self._test_server_forwarder = None
# The forwarder device port now is dynamically allocated.
self.forwarder_device_port = 0
# Anonymous pipe in order to get port info from test server.
self.pipe_in = None
self.pipe_out = None
self.command_line = []
self.build_type = build_type
def _WaitToStartAndGetPortFromTestServer(self):
"""Waits for the Python test server to start and gets the port it is using.
The port information is passed by the Python test server with a pipe given
by self.pipe_out. It is written as a result to |self.host_port|.
Returns:
Whether the port used by the test server was successfully fetched.
"""
assert self.host_port == 0 and self.pipe_out and self.pipe_in
(in_fds, _, _) = select.select([self.pipe_in, ], [], [],
TEST_SERVER_STARTUP_TIMEOUT)
if len(in_fds) == 0:
logging.error('Failed to wait to the Python test server to be started.')
return False
# First read the data length as an unsigned 4-byte value. This
# is _not_ using network byte ordering since the Python test server packs
# size as native byte order and all Chromium platforms so far are
# configured to use little-endian.
# TODO(jnd): Change the Python test server and local_test_server_*.cc to
# use a unified byte order (either big-endian or little-endian).
data_length = os.read(self.pipe_in, struct.calcsize('=L'))
if data_length:
(data_length,) = struct.unpack('=L', data_length)
assert data_length
if not data_length:
logging.error('Failed to get length of server data.')
return False
port_json = os.read(self.pipe_in, data_length)
if not port_json:
logging.error('Failed to get server data.')
return False
logging.info('Got port json data: %s', port_json)
port_json = json.loads(port_json)
if port_json.has_key('port') and isinstance(port_json['port'], int):
self.host_port = port_json['port']
return _CheckPortStatus(self.host_port, True)
logging.error('Failed to get port information from the server data.')
return False
def _GenerateCommandLineArguments(self):
"""Generates the command line to run the test server.
Note that all options are processed by following the definitions in
testserver.py.
"""
if self.command_line:
return
# The following arguments must exist.
type_cmd = _GetServerTypeCommandLine(self.arguments['server-type'])
if type_cmd:
self.command_line.append(type_cmd)
self.command_line.append('--port=%d' % self.host_port)
# Use a pipe to get the port given by the instance of Python test server
# if the test does not specify the port.
if self.host_port == 0:
(self.pipe_in, self.pipe_out) = os.pipe()
self.command_line.append('--startup-pipe=%d' % self.pipe_out)
self.command_line.append('--host=%s' % self.arguments['host'])
data_dir = self.arguments['data-dir'] or 'chrome/test/data'
if not os.path.isabs(data_dir):
data_dir = os.path.join(constants.CHROME_DIR, data_dir)
self.command_line.append('--data-dir=%s' % data_dir)
# The following arguments are optional depending on the individual test.
if self.arguments.has_key('log-to-console'):
self.command_line.append('--log-to-console')
if self.arguments.has_key('auth-token'):
self.command_line.append('--auth-token=%s' % self.arguments['auth-token'])
if self.arguments.has_key('https'):
self.command_line.append('--https')
if self.arguments.has_key('cert-and-key-file'):
self.command_line.append('--cert-and-key-file=%s' % os.path.join(
constants.CHROME_DIR, self.arguments['cert-and-key-file']))
if self.arguments.has_key('ocsp'):
self.command_line.append('--ocsp=%s' % self.arguments['ocsp'])
if self.arguments.has_key('https-record-resume'):
self.command_line.append('--https-record-resume')
if self.arguments.has_key('ssl-client-auth'):
self.command_line.append('--ssl-client-auth')
if self.arguments.has_key('tls-intolerant'):
self.command_line.append('--tls-intolerant=%s' %
self.arguments['tls-intolerant'])
if self.arguments.has_key('ssl-client-ca'):
for ca in self.arguments['ssl-client-ca']:
self.command_line.append('--ssl-client-ca=%s' %
os.path.join(constants.CHROME_DIR, ca))
if self.arguments.has_key('ssl-bulk-cipher'):
for bulk_cipher in self.arguments['ssl-bulk-cipher']:
self.command_line.append('--ssl-bulk-cipher=%s' % bulk_cipher)
def run(self):
logging.info('Start running the thread!')
self.wait_event.clear()
self._GenerateCommandLineArguments()
command = [os.path.join(constants.CHROME_DIR, 'net', 'tools',
'testserver', 'testserver.py')] + self.command_line
logging.info('Running: %s', command)
self.process = subprocess.Popen(command)
if self.process:
if self.pipe_out:
self.is_ready = self._WaitToStartAndGetPortFromTestServer()
else:
self.is_ready = _CheckPortStatus(self.host_port, True)
if self.is_ready:
self._test_server_forwarder = Forwarder(
self.adb, [(0, self.host_port)], self.tool, '127.0.0.1',
self.build_type)
# Check whether the forwarder is ready on the device.
self.is_ready = False
device_port = self._test_server_forwarder.DevicePortForHostPort(
self.host_port)
if device_port:
for timeout in range(1, 5):
if ports.IsDevicePortUsed(self.adb, device_port, 'LISTEN'):
self.is_ready = True
self.forwarder_device_port = device_port
break
time.sleep(timeout)
# Wake up the request handler thread.
self.ready_event.set()
# Keep thread running until Stop() gets called.
while not self.stop_flag:
time.sleep(1)
if self.process.poll() is None:
self.process.kill()
if self._test_server_forwarder:
self._test_server_forwarder.Close()
self.process = None
self.is_ready = False
if self.pipe_out:
os.close(self.pipe_in)
os.close(self.pipe_out)
self.pipe_in = None
self.pipe_out = None
logging.info('Test-server has died.')
self.wait_event.set()
def Stop(self):
"""Blocks until the loop has finished.
Note that this must be called in another thread.
"""
if not self.process:
return
self.stop_flag = True
self.wait_event.wait()
class SpawningServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""A handler used to process http GET/POST request."""
def _SendResponse(self, response_code, response_reason, additional_headers,
contents):
"""Generates a response sent to the client from the provided parameters.
Args:
response_code: number of the response status.
response_reason: string of reason description of the response.
additional_headers: dict of additional headers. Each key is the name of
the header, each value is the content of the header.
contents: string of the contents we want to send to client.
"""
self.send_response(response_code, response_reason)
self.send_header('Content-Type', 'text/html')
# Specify the content-length as without it the http(s) response will not
# be completed properly (and the browser keeps expecting data).
self.send_header('Content-Length', len(contents))
for header_name in additional_headers:
self.send_header(header_name, additional_headers[header_name])
self.end_headers()
self.wfile.write(contents)
self.wfile.flush()
def _StartTestServer(self):
"""Starts the test server thread."""
logging.info('Handling request to spawn a test server.')
content_type = self.headers.getheader('content-type')
if content_type != 'application/json':
raise Exception('Bad content-type for start request.')
content_length = self.headers.getheader('content-length')
if not content_length:
content_length = 0
try:
content_length = int(content_length)
except:
raise Exception('Bad content-length for start request.')
logging.info(content_length)
test_server_argument_json = self.rfile.read(content_length)
logging.info(test_server_argument_json)
assert not self.server.test_server_instance
ready_event = threading.Event()
self.server.test_server_instance = TestServerThread(
ready_event,
json.loads(test_server_argument_json),
self.server.adb,
self.server.tool,
self.server.build_type)
self.server.test_server_instance.setDaemon(True)
self.server.test_server_instance.start()
ready_event.wait()
if self.server.test_server_instance.is_ready:
self._SendResponse(200, 'OK', {}, json.dumps(
{'port': self.server.test_server_instance.forwarder_device_port,
'message': 'started'}))
logging.info('Test server is running on port: %d.',
self.server.test_server_instance.host_port)
else:
self.server.test_server_instance.Stop()
self.server.test_server_instance = None
self._SendResponse(500, 'Test Server Error.', {}, '')
logging.info('Encounter problem during starting a test server.')
def _KillTestServer(self):
"""Stops the test server instance."""
# There should only ever be one test server at a time. This may do the
# wrong thing if we try and start multiple test servers.
if not self.server.test_server_instance:
return
port = self.server.test_server_instance.host_port
logging.info('Handling request to kill a test server on port: %d.', port)
self.server.test_server_instance.Stop()
# Make sure the status of test server is correct before sending response.
if _CheckPortStatus(port, False):
self._SendResponse(200, 'OK', {}, 'killed')
logging.info('Test server on port %d is killed', port)
else:
self._SendResponse(500, 'Test Server Error.', {}, '')
logging.info('Encounter problem during killing a test server.')
self.server.test_server_instance = None
def do_POST(self):
parsed_path = urlparse.urlparse(self.path)
action = parsed_path.path
logging.info('Action for POST method is: %s.', action)
if action == '/start':
self._StartTestServer()
else:
self._SendResponse(400, 'Unknown request.', {}, '')
logging.info('Encounter unknown request: %s.', action)
def do_GET(self):
parsed_path = urlparse.urlparse(self.path)
action = parsed_path.path
params = urlparse.parse_qs(parsed_path.query, keep_blank_values=1)
logging.info('Action for GET method is: %s.', action)
for param in params:
logging.info('%s=%s', param, params[param][0])
if action == '/kill':
self._KillTestServer()
elif action == '/ping':
# The ping handler is used to check whether the spawner server is ready
# to serve the requests. We don't need to test the status of the test
# server when handling ping request.
self._SendResponse(200, 'OK', {}, 'ready')
logging.info('Handled ping request and sent response.')
else:
self._SendResponse(400, 'Unknown request', {}, '')
logging.info('Encounter unknown request: %s.', action)
class SpawningServer(object):
"""The class used to start/stop a http server."""
def __init__(self, test_server_spawner_port, adb, tool, build_type):
logging.info('Creating new spawner on port: %d.', test_server_spawner_port)
self.server = BaseHTTPServer.HTTPServer(('', test_server_spawner_port),
SpawningServerRequestHandler)
self.port = test_server_spawner_port
self.server.adb = adb
self.server.tool = tool
self.server.test_server_instance = None
self.server.build_type = build_type
def _Listen(self):
logging.info('Starting test server spawner')
self.server.serve_forever()
def Start(self):
listener_thread = threading.Thread(target=self._Listen)
listener_thread.setDaemon(True)
listener_thread.start()
time.sleep(1)
def Stop(self):
if self.server.test_server_instance:
self.server.test_server_instance.Stop()
self.server.shutdown()

View file

@ -0,0 +1,50 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A wrapper for subprocess to make calling shell commands easier."""
import logging
import subprocess
def RunCmd(args, cwd=None):
"""Opens a subprocess to execute a program and returns its return value.
Args:
args: A string or a sequence of program arguments. The program to execute is
the string or the first item in the args sequence.
cwd: If not None, the subprocess's current directory will be changed to
|cwd| before it's executed.
Returns:
Return code from the command execution.
"""
logging.info(str(args) + ' ' + (cwd or ''))
p = subprocess.Popen(args=args, cwd=cwd)
return p.wait()
def GetCmdOutput(args, cwd=None, shell=False):
"""Open a subprocess to execute a program and returns its output.
Args:
args: A string or a sequence of program arguments. The program to execute is
the string or the first item in the args sequence.
cwd: If not None, the subprocess's current directory will be changed to
|cwd| before it's executed.
shell: Whether to execute args as a shell command.
Returns:
Captures and returns the command's stdout.
Prints the command's stderr to logger (which defaults to stdout).
"""
logging.info(str(args) + ' ' + (cwd or ''))
p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=shell)
stdout, stderr = p.communicate()
if stderr:
logging.critical(stderr)
logging.info(stdout[:4096]) # Truncate output longer than 4k.
return stdout

View file

@ -0,0 +1,47 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Defines a set of constants shared by test runners and other scripts."""
import os
CHROME_PACKAGE = 'com.google.android.apps.chrome'
CHROME_ACTIVITY = 'com.google.android.apps.chrome.Main'
CHROME_TESTS_PACKAGE = 'com.google.android.apps.chrome.tests'
LEGACY_BROWSER_PACKAGE = 'com.google.android.browser'
LEGACY_BROWSER_ACTIVITY = 'com.android.browser.BrowserActivity'
CONTENT_SHELL_PACKAGE = "org.chromium.content_shell"
CONTENT_SHELL_ACTIVITY = "org.chromium.content_shell.ContentShellActivity"
CHROME_SHELL_PACKAGE = 'org.chromium.chrome.browser.test'
CHROMIUM_TEST_SHELL_PACKAGE = 'org.chromium.chrome.testshell'
CHROME_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..'))
# Ports arrangement for various test servers used in Chrome for Android.
# Lighttpd server will attempt to use 9000 as default port, if unavailable it
# will find a free port from 8001 - 8999.
LIGHTTPD_DEFAULT_PORT = 9000
LIGHTTPD_RANDOM_PORT_FIRST = 8001
LIGHTTPD_RANDOM_PORT_LAST = 8999
TEST_SYNC_SERVER_PORT = 9031
# The net test server is started from 10000. Reserve 20000 ports for the all
# test-server based tests should be enough for allocating different port for
# individual test-server based test.
TEST_SERVER_PORT_FIRST = 10000
TEST_SERVER_PORT_LAST = 30000
# A file to record next valid port of test server.
TEST_SERVER_PORT_FILE = '/tmp/test_server_port'
TEST_SERVER_PORT_LOCKFILE = '/tmp/test_server_port.lock'
TEST_EXECUTABLE_DIR = '/data/local/tmp'
# Directories for common java libraries for SDK build.
# These constants are defined in build/android/ant/common.xml
SDK_BUILD_TEST_JAVALIB_DIR = 'test.lib.java'
SDK_BUILD_APKS_DIR = 'apks'
# The directory on the device where perf test output gets saved to.
DEVICE_PERF_OUTPUT_DIR = '/data/data/' + CHROME_PACKAGE + '/files'

View file

@ -0,0 +1,196 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Collect debug info for a test."""
import datetime
import logging
import os
import re
import shutil
import string
import subprocess
import tempfile
import cmd_helper
TOMBSTONE_DIR = '/data/tombstones/'
class GTestDebugInfo(object):
"""A helper class to collect related debug information for a gtest.
Debug info is collected in two steps:
- first, object(s) of this class (one per device), accumulate logs
and screenshots in tempdir.
- once the test has finished, call ZipAndCleanResults to create
a zip containing the logs from all devices, and clean them up.
Args:
adb: ADB interface the tests are using.
device: Serial# of the Android device in which the specified gtest runs.
testsuite_name: Name of the specified gtest.
gtest_filter: Test filter used by the specified gtest.
"""
def __init__(self, adb, device, testsuite_name, gtest_filter):
"""Initializes the DebugInfo class for a specified gtest."""
self.adb = adb
self.device = device
self.testsuite_name = testsuite_name
self.gtest_filter = gtest_filter
self.logcat_process = None
self.has_storage = False
self.log_dir = os.path.join(tempfile.gettempdir(),
'gtest_debug_info',
self.testsuite_name,
self.device)
if not os.path.exists(self.log_dir):
os.makedirs(self.log_dir)
self.log_file_name = os.path.join(self.log_dir,
self._GeneratePrefixName() + '_log.txt')
self.old_crash_files = self._ListCrashFiles()
def _GetSignatureFromGTestFilter(self):
"""Gets a signature from gtest_filter.
Signature is used to identify the tests from which we collect debug
information.
Returns:
A signature string. Returns 'all' if there is no gtest filter.
"""
if not self.gtest_filter:
return 'all'
filename_chars = "-_()%s%s" % (string.ascii_letters, string.digits)
signature = ''.join(c for c in self.gtest_filter if c in filename_chars)
if len(signature) > 64:
# The signature can't be too long, as it'll be part of a file name.
signature = signature[:64]
return signature
def _GeneratePrefixName(self):
"""Generates a prefix name for debug information of the test.
The prefix name consists of the following:
(1) root name of test_suite_base.
(2) device serial number.
(3) prefix of filter signature generate from gtest_filter.
(4) date & time when calling this method.
Returns:
Name of the log file.
"""
return (os.path.splitext(self.testsuite_name)[0] + '_' + self.device + '_' +
self._GetSignatureFromGTestFilter() + '_' +
datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S-%f'))
def StartRecordingLog(self, clear=True, filters=['*:v']):
"""Starts recording logcat output to a file.
This call should come before running test, with calling StopRecordingLog
following the tests.
Args:
clear: True if existing log output should be cleared.
filters: A list of logcat filters to be used.
"""
self.StopRecordingLog()
if clear:
cmd_helper.RunCmd(['adb', '-s', self.device, 'logcat', '-c'])
logging.info('Start dumping log to %s ...', self.log_file_name)
command = 'adb -s %s logcat -v threadtime %s > %s' % (self.device,
' '.join(filters),
self.log_file_name)
self.logcat_process = subprocess.Popen(command, shell=True)
def StopRecordingLog(self):
"""Stops an existing logcat recording subprocess."""
if not self.logcat_process:
return
# Cannot evaluate directly as 0 is a possible value.
if self.logcat_process.poll() is None:
self.logcat_process.kill()
self.logcat_process = None
logging.info('Finish log dump.')
def TakeScreenshot(self, identifier_mark):
"""Takes a screen shot from current specified device.
Args:
identifier_mark: A string to identify the screen shot DebugInfo will take.
It will be part of filename of the screen shot. Empty
string is acceptable.
Returns:
Returns the file name on the host of the screenshot if successful,
None otherwise.
"""
assert isinstance(identifier_mark, str)
screenshot_path = os.path.join(os.getenv('ANDROID_HOST_OUT', ''),
'bin',
'screenshot2')
if not os.path.exists(screenshot_path):
logging.error('Failed to take screen shot from device %s', self.device)
return None
shot_path = os.path.join(self.log_dir, ''.join([self._GeneratePrefixName(),
identifier_mark,
'_screenshot.png']))
re_success = re.compile(re.escape('Success.'), re.MULTILINE)
if re_success.findall(cmd_helper.GetCmdOutput([screenshot_path, '-s',
self.device, shot_path])):
logging.info('Successfully took a screen shot to %s', shot_path)
return shot_path
logging.error('Failed to take screen shot from device %s', self.device)
return None
def _ListCrashFiles(self):
"""Collects crash files from current specified device.
Returns:
A dict of crash files in format {"name": (size, lastmod), ...}.
"""
return self.adb.ListPathContents(TOMBSTONE_DIR)
def ArchiveNewCrashFiles(self):
"""Archives the crash files newly generated until calling this method."""
current_crash_files = self._ListCrashFiles()
files = []
for f in current_crash_files:
if f not in self.old_crash_files:
files += [f]
elif current_crash_files[f] != self.old_crash_files[f]:
# Tombstones dir can only have maximum 10 files, so we need to compare
# size and timestamp information of file if the file exists.
files += [f]
if files:
logging.info('New crash file(s):%s' % ' '.join(files))
for f in files:
self.adb.Adb().Pull(TOMBSTONE_DIR + f,
os.path.join(self.log_dir, f))
@staticmethod
def ZipAndCleanResults(dest_dir, dump_file_name):
"""A helper method to zip all debug information results into a dump file.
Args:
dest_dir: Dir path in where we put the dump file.
dump_file_name: Desired name of the dump file. This method makes sure
'.zip' will be added as ext name.
"""
if not dest_dir or not dump_file_name:
return
cmd_helper.RunCmd(['mkdir', '-p', dest_dir])
log_basename = os.path.basename(dump_file_name)
log_zip_file = os.path.join(dest_dir,
os.path.splitext(log_basename)[0] + '.zip')
logging.info('Zipping debug dumps into %s ...', log_zip_file)
# Add new dumps into the zip file. The zip may exist already if previous
# gtest also dumps the debug information. It's OK since we clean up the old
# dumps in each build step.
log_src_dir = os.path.join(tempfile.gettempdir(), 'gtest_debug_info')
cmd_helper.RunCmd(['zip', '-q', '-r', log_zip_file, log_src_dir])
assert os.path.exists(log_zip_file)
assert os.path.exists(log_src_dir)
shutil.rmtree(log_src_dir)

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<!--
* Copyright (c) 2012 The Chromium Authors. All rights reserved. Use of this
* source code is governed by a BSD-style license that can be found in the
* LICENSE file.
-->
<html>
<head>
<title>Device Stats Monitor</title>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<style>
body {
font-family: sans-serif
}
</style>
</head>
<body>
<h2>Device Stats Monitor</h2>
<ul>
<li>Pass path to trace data via the <code>results</code> querystring param.
<li>Combine charts with the <code>combine</code> querystring param (e.g. <code>&combine=sectors_read,sectors_written</code>).
<li>Use <code>stacked=true</code> to stack combined charts instead of overlaying (default).
</ul>
</body>
<script>
google.load("visualization", "1", {packages:["corechart"]});
/**
* @returns The querystring param value for |name| or an empty string.
*/
function getQuerystringParam(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if (results == null)
return "";
else
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
/**
* @returns An array of keys in |obj| sorted by value.
*/
function sortedKeys(obj) {
var keys = [];
for (var key in obj) {
keys.push(key);
}
keys.sort();
return keys;
}
/**
* Removes by value all params from array.
*/
Array.prototype.remove = function() {
var what, a = arguments, l = a.length, ax;
while (l && this.length) {
what = a[--l];
while ((ax = this.indexOf(what)) != -1) {
this.splice(ax, 1);
}
}
return this;
}
/**
* Displays a new chart.
*
* @param {Number} hz Number of sample per second of the data.
* @param {String} name Name to display on top of chart.
* @param {Number[][]} values Array of value arrays to display.
* @param {Boolean} stacked Whether to display values as stacked.
*/
function displayChart(hz, name, values, units, stacked) {
var data = new google.visualization.DataTable();
data.addColumn('number', 'ms');
var names = name.split(',');
for (var i = 0; i < names.length; i++) {
data.addColumn('number', names[i]);
}
var rows = [];
var interval = 1000.0 / hz;
for (var i = 0; i < values[0].length; i++) {
var row = [i*interval];
for (var j = 0; j < values.length; j++) {
row.push(values[j][i]);
}
rows.push(row);
}
data.addRows(rows);
var options = {
hAxis: {title: 'ms (' + hz + 'hz)'},
isStacked: stacked,
legend: {position: 'top'},
vAxis: {title: units},
};
var elem = document.createElement('DIV');
elem.style = 'width:100%;height:500px';
document.body.appendChild(elem);
var chart = new google.visualization.AreaChart(elem);
chart.draw(data, options);
}
/**
* Displays all charts.
*
* Invoked by the results script. JSONP is used to avoid security
* restrictions on XHRs for file:// URLs.
*/
function display(hz, results, units) {
var combine = getQuerystringParam('combine');
var keys = sortedKeys(results);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var name = key;
var values = [results[key]];
var unit = units[key];
if (combine.indexOf(key) >= 0) {
i--;
name = combine;
values = [];
var combined_keys = combine.split(',');
for (var j = 0; j < combined_keys.length; j++) {
values.push(results[combined_keys[j]]);
keys.remove(combined_keys[j]);
}
}
displayChart(hz, name, values, unit, !!getQuerystringParam('stacked'));
}
}
var resultsPath = getQuerystringParam('results');
if (resultsPath)
document.write("<script src='" + resultsPath + "'></"+"script>");
else
document.write("Please specify results querystring param.");
</script>
</html>

View file

@ -0,0 +1,116 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utilities for iotop/top style profiling for android."""
import collections
import json
import os
import subprocess
import sys
import urllib
import constants
import io_stats_parser
class DeviceStatsMonitor(object):
"""Class for collecting device stats such as IO/CPU usage.
Args:
adb: Instance of AndroidComannds.
hz: Frequency at which to sample device stats.
"""
DEVICE_PATH = constants.TEST_EXECUTABLE_DIR + '/device_stats_monitor'
PROFILE_PATH = (constants.DEVICE_PERF_OUTPUT_DIR +
'/device_stats_monitor.profile')
RESULT_VIEWER_PATH = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'device_stats_monitor.html'))
def __init__(self, adb, hz, build_type):
self._adb = adb
host_path = os.path.abspath(os.path.join(
constants.CHROME_DIR, 'out', build_type, 'device_stats_monitor'))
self._adb.PushIfNeeded(host_path, DeviceStatsMonitor.DEVICE_PATH)
self._hz = hz
def Start(self):
"""Starts device stats monitor on the device."""
self._adb.SetFileContents(DeviceStatsMonitor.PROFILE_PATH, '')
self._process = subprocess.Popen(
['adb', 'shell', '%s --hz=%d %s' % (
DeviceStatsMonitor.DEVICE_PATH, self._hz,
DeviceStatsMonitor.PROFILE_PATH)])
def StopAndCollect(self, output_path):
"""Stops monitoring and saves results.
Args:
output_path: Path to save results.
Returns:
String of URL to load results in browser.
"""
assert self._process
self._adb.KillAll(DeviceStatsMonitor.DEVICE_PATH)
self._process.wait()
profile = self._adb.GetFileContents(DeviceStatsMonitor.PROFILE_PATH)
results = collections.defaultdict(list)
last_io_stats = None
last_cpu_stats = None
for line in profile:
if ' mmcblk0 ' in line:
stats = io_stats_parser.ParseIoStatsLine(line)
if last_io_stats:
results['sectors_read'].append(stats.num_sectors_read -
last_io_stats.num_sectors_read)
results['sectors_written'].append(stats.num_sectors_written -
last_io_stats.num_sectors_written)
last_io_stats = stats
elif line.startswith('cpu '):
stats = self._ParseCpuStatsLine(line)
if last_cpu_stats:
results['user'].append(stats.user - last_cpu_stats.user)
results['nice'].append(stats.nice - last_cpu_stats.nice)
results['system'].append(stats.system - last_cpu_stats.system)
results['idle'].append(stats.idle - last_cpu_stats.idle)
results['iowait'].append(stats.iowait - last_cpu_stats.iowait)
results['irq'].append(stats.irq - last_cpu_stats.irq)
results['softirq'].append(stats.softirq- last_cpu_stats.softirq)
last_cpu_stats = stats
units = {
'sectors_read': 'sectors',
'sectors_written': 'sectors',
'user': 'jiffies',
'nice': 'jiffies',
'system': 'jiffies',
'idle': 'jiffies',
'iowait': 'jiffies',
'irq': 'jiffies',
'softirq': 'jiffies',
}
with open(output_path, 'w') as f:
f.write('display(%d, %s, %s);' % (self._hz, json.dumps(results), units))
return 'file://%s?results=file://%s' % (
DeviceStatsMonitor.RESULT_VIEWER_PATH, urllib.quote(output_path))
@staticmethod
def _ParseCpuStatsLine(line):
"""Parses a line of cpu stats into a CpuStats named tuple."""
# Field definitions: http://www.linuxhowtos.org/System/procstat.htm
cpu_stats = collections.namedtuple('CpuStats',
['device',
'user',
'nice',
'system',
'idle',
'iowait',
'irq',
'softirq',
])
fields = line.split()
return cpu_stats._make([fields[0]] + [int(f) for f in fields[1:8]])

View file

@ -0,0 +1,63 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import android_commands
import constants
import logging
import os
import subprocess
import time
class FakeDns(object):
"""Wrapper class for the fake_dns tool."""
_FAKE_DNS_PATH = constants.TEST_EXECUTABLE_DIR + '/fake_dns'
def __init__(self, adb, build_type):
"""
Args:
adb: the AndroidCommands to use.
build_type: 'Release' or 'Debug'.
"""
self._adb = adb
self._build_type = build_type
self._fake_dns = None
self._original_dns = None
def _PushAndStartFakeDns(self):
"""Starts the fake_dns server that replies all name queries 127.0.0.1.
Returns:
subprocess instance connected to the fake_dns process on the device.
"""
self._adb.PushIfNeeded(
os.path.join(constants.CHROME_DIR, 'out', self._build_type, 'fake_dns'),
FakeDns._FAKE_DNS_PATH)
return subprocess.Popen(
['adb', '-s', self._adb._adb.GetSerialNumber(),
'shell', '%s -D' % FakeDns._FAKE_DNS_PATH])
def SetUp(self):
"""Configures the system to point to a DNS server that replies 127.0.0.1.
This can be used in combination with the forwarder to forward all web
traffic to a replay server.
The TearDown() method will perform all cleanup.
"""
self._adb.RunShellCommand('ip route add 8.8.8.0/24 via 127.0.0.1 dev lo')
self._fake_dns = self._PushAndStartFakeDns()
self._original_dns = self._adb.RunShellCommand('getprop net.dns1')[0]
self._adb.RunShellCommand('setprop net.dns1 127.0.0.1')
time.sleep(2) # Time for server to start and the setprop to take effect.
def TearDown(self):
"""Shuts down the fake_dns."""
if self._fake_dns:
if not self._original_dns or self._original_dns == '127.0.0.1':
logging.warning('Bad original DNS, falling back to Google DNS.')
self._original_dns = '8.8.8.8'
self._adb.RunShellCommand('setprop net.dns1 %s' % self._original_dns)
self._fake_dns.kill()
self._adb.RunShellCommand('ip route del 8.8.8.0/24 via 127.0.0.1 dev lo')

View file

@ -0,0 +1,144 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import constants
import traceback
import warnings
# Location where chrome reads command line flags from
CHROME_COMMAND_FILE = constants.TEST_EXECUTABLE_DIR + '/chrome-command-line'
class FlagChanger(object):
"""Changes the flags Chrome runs with.
There are two different use cases for this file:
* Flags are permanently set by calling Set().
* Flags can be temporarily set for a particular set of unit tests. These
tests should call Restore() to revert the flags to their original state
once the tests have completed.
"""
def __init__(self, android_cmd):
self._android_cmd = android_cmd
# Save the original flags.
self._orig_line = self._android_cmd.GetFileContents(CHROME_COMMAND_FILE)
if self._orig_line:
self._orig_line = self._orig_line[0].strip()
# Parse out the flags into a list to facilitate adding and removing flags.
self._current_flags = self._TokenizeFlags(self._orig_line)
def Get(self):
"""Returns list of current flags."""
return self._current_flags
def Set(self, flags):
"""Replaces all flags on the current command line with the flags given.
Args:
flags: A list of flags to set, eg. ['--single-process'].
"""
if flags:
assert flags[0] != 'chrome'
self._current_flags = flags
self._UpdateCommandLineFile()
def AddFlags(self, flags):
"""Appends flags to the command line if they aren't already there.
Args:
flags: A list of flags to add on, eg. ['--single-process'].
"""
if flags:
assert flags[0] != 'chrome'
# Avoid appending flags that are already present.
for flag in flags:
if flag not in self._current_flags:
self._current_flags.append(flag)
self._UpdateCommandLineFile()
def RemoveFlags(self, flags):
"""Removes flags from the command line, if they exist.
Args:
flags: A list of flags to remove, eg. ['--single-process']. Note that we
expect a complete match when removing flags; if you want to remove
a switch with a value, you must use the exact string used to add
it in the first place.
"""
if flags:
assert flags[0] != 'chrome'
for flag in flags:
if flag in self._current_flags:
self._current_flags.remove(flag)
self._UpdateCommandLineFile()
def Restore(self):
"""Restores the flags to their original state."""
self._current_flags = self._TokenizeFlags(self._orig_line)
self._UpdateCommandLineFile()
def _UpdateCommandLineFile(self):
"""Writes out the command line to the file, or removes it if empty."""
print "Current flags: ", self._current_flags
if self._current_flags:
self._android_cmd.SetFileContents(CHROME_COMMAND_FILE,
'chrome ' +
' '.join(self._current_flags))
else:
self._android_cmd.RunShellCommand('rm ' + CHROME_COMMAND_FILE)
def _TokenizeFlags(self, line):
"""Changes the string containing the command line into a list of flags.
Follows similar logic to CommandLine.java::tokenizeQuotedArguments:
* Flags are split using whitespace, unless the whitespace is within a
pair of quotation marks.
* Unlike the Java version, we keep the quotation marks around switch
values since we need them to re-create the file when new flags are
appended.
Args:
line: A string containing the entire command line. The first token is
assumed to be the program name.
"""
if not line:
return []
tokenized_flags = []
current_flag = ""
within_quotations = False
# Move through the string character by character and build up each flag
# along the way.
for c in line.strip():
if c is '"':
if len(current_flag) > 0 and current_flag[-1] == '\\':
# Last char was a backslash; pop it, and treat this " as a literal.
current_flag = current_flag[0:-1] + '"'
else:
within_quotations = not within_quotations
current_flag += c
elif not within_quotations and (c is ' ' or c is '\t'):
if current_flag is not "":
tokenized_flags.append(current_flag)
current_flag = ""
else:
current_flag += c
# Tack on the last flag.
if not current_flag:
if within_quotations:
warnings.warn("Unterminated quoted string: " + current_flag)
else:
tokenized_flags.append(current_flag)
# Return everything but the program name.
return tokenized_flags[1:]

View file

@ -0,0 +1,198 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import re
import sys
import time
import android_commands
import cmd_helper
import constants
import ports
from pylib import pexpect
class Forwarder(object):
"""Class to manage port forwards from the device to the host."""
_DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder'
# Unix Abstract socket path:
_DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder'
_TIMEOUT_SECS = 30
def __init__(self, adb, port_pairs, tool, host_name, build_type):
"""Forwards TCP ports on the device back to the host.
Works like adb forward, but in reverse.
Args:
adb: Instance of AndroidCommands for talking to the device.
port_pairs: A list of tuples (device_port, host_port) to forward. Note
that you can specify 0 as a device_port, in which case a
port will by dynamically assigned on the device. You can
get the number of the assigned port using the
DevicePortForHostPort method.
tool: Tool class to use to get wrapper, if necessary, for executing the
forwarder (see valgrind_tools.py).
host_name: Address to forward to, must be addressable from the
host machine. Usually use loopback '127.0.0.1'.
build_type: 'Release' or 'Debug'.
Raises:
Exception on failure to forward the port.
"""
self._adb = adb
self._host_to_device_port_map = dict()
self._host_process = None
self._device_process = None
self._adb_forward_process = None
self._host_adb_control_port = ports.AllocateTestServerPort()
if not self._host_adb_control_port:
raise Exception('Failed to allocate a TCP port in the host machine.')
adb.PushIfNeeded(
os.path.join(constants.CHROME_DIR, 'out', build_type,
'device_forwarder'),
Forwarder._DEVICE_FORWARDER_PATH)
self._host_forwarder_path = os.path.join(constants.CHROME_DIR,
'out',
build_type,
'host_forwarder')
forward_string = ['%d:%d:%s' %
(device, host, host_name) for device, host in port_pairs]
logging.info('Forwarding ports: %s', forward_string)
timeout_sec = 5
host_pattern = 'host_forwarder.*' + ' '.join(forward_string)
# TODO(felipeg): Rather than using a blocking kill() here, the device
# forwarder could try to bind the Unix Domain Socket until it succeeds or
# while it fails because the socket is already bound (with appropriate
# timeout handling obviously).
self._KillHostForwarderBlocking(host_pattern, timeout_sec)
self._KillDeviceForwarderBlocking(timeout_sec)
self._adb_forward_process = pexpect.spawn(
'adb', ['-s',
adb._adb.GetSerialNumber(),
'forward',
'tcp:%s' % self._host_adb_control_port,
'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT])
self._device_process = pexpect.spawn(
'adb', ['-s',
adb._adb.GetSerialNumber(),
'shell',
'%s %s -D --adb_sock=%s' % (
tool.GetUtilWrapper(),
Forwarder._DEVICE_FORWARDER_PATH,
Forwarder._DEVICE_ADB_CONTROL_PORT)])
device_success_re = re.compile('Starting Device Forwarder.')
device_failure_re = re.compile('.*:ERROR:(.*)')
index = self._device_process.expect([device_success_re,
device_failure_re,
pexpect.EOF,
pexpect.TIMEOUT],
Forwarder._TIMEOUT_SECS)
if index == 1:
# Failure
error_msg = str(self._device_process.match.group(1))
logging.error(self._device_process.before)
self._CloseProcess()
raise Exception('Failed to start Device Forwarder with Error: %s' %
error_msg)
elif index == 2:
logging.error(self._device_process.before)
self._CloseProcess()
raise Exception('Unexpected EOF while trying to start Device Forwarder.')
elif index == 3:
logging.error(self._device_process.before)
self._CloseProcess()
raise Exception('Timeout while trying start Device Forwarder')
self._host_process = pexpect.spawn(self._host_forwarder_path,
['--adb_port=%s' % (
self._host_adb_control_port)] +
forward_string)
# Read the output of the command to determine which device ports where
# forwarded to which host ports (necessary if
host_success_re = re.compile('Forwarding device port (\d+) to host (\d+):')
host_failure_re = re.compile('Couldn\'t start forwarder server for port '
'spec: (\d+):(\d+)')
for pair in port_pairs:
index = self._host_process.expect([host_success_re,
host_failure_re,
pexpect.EOF,
pexpect.TIMEOUT],
Forwarder._TIMEOUT_SECS)
if index == 0:
# Success
device_port = int(self._host_process.match.group(1))
host_port = int(self._host_process.match.group(2))
self._host_to_device_port_map[host_port] = device_port
logging.info("Forwarding device port: %d to host port: %d." %
(device_port, host_port))
elif index == 1:
# Failure
device_port = int(self._host_process.match.group(1))
host_port = int(self._host_process.match.group(2))
self._CloseProcess()
raise Exception('Failed to forward port %d to %d' % (device_port,
host_port))
elif index == 2:
logging.error(self._host_process.before)
self._CloseProcess()
raise Exception('Unexpected EOF while trying to forward ports %s' %
port_pairs)
elif index == 3:
logging.error(self._host_process.before)
self._CloseProcess()
raise Exception('Timeout while trying to forward ports %s' % port_pairs)
def _KillHostForwarderBlocking(self, host_pattern, timeout_sec):
"""Kills any existing host forwarders using the provided pattern.
Note that this waits until the process terminates.
"""
cmd_helper.RunCmd(['pkill', '-f', host_pattern])
elapsed = 0
wait_period = 0.1
while not cmd_helper.RunCmd(['pgrep', '-f', host_pattern]) and (
elapsed < timeout_sec):
time.sleep(wait_period)
elapsed += wait_period
if elapsed >= timeout_sec:
raise Exception('Timed out while killing ' + host_pattern)
def _KillDeviceForwarderBlocking(self, timeout_sec):
"""Kills any existing device forwarders.
Note that this waits until the process terminates.
"""
processes_killed = self._adb.KillAllBlocking(
'device_forwarder', timeout_sec)
if not processes_killed:
pids = self._adb.ExtractPid('device_forwarder')
if pids:
raise Exception('Timed out while killing device_forwarder')
def _CloseProcess(self):
if self._host_process:
self._host_process.close()
if self._device_process:
self._device_process.close()
if self._adb_forward_process:
self._adb_forward_process.close()
self._host_process = None
self._device_process = None
self._adb_forward_process = None
def DevicePortForHostPort(self, host_port):
"""Get the device port that corresponds to a given host port."""
return self._host_to_device_port_map.get(host_port)
def Close(self):
"""Terminate the forwarder process."""
self._CloseProcess()

View file

@ -0,0 +1,32 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides an interface to communicate with the device via the adb command.
Assumes adb binary is currently on system path.
"""
import collections
def ParseIoStatsLine(line):
"""Parses a line of io stats into a IoStats named tuple."""
# Field definitions: http://www.kernel.org/doc/Documentation/iostats.txt
IoStats = collections.namedtuple('IoStats',
['device',
'num_reads_issued',
'num_reads_merged',
'num_sectors_read',
'ms_spent_reading',
'num_writes_completed',
'num_writes_merged',
'num_sectors_written',
'ms_spent_writing',
'num_ios_in_progress',
'ms_spent_doing_io',
'ms_spent_doing_io_weighted',
])
fields = line.split()
return IoStats._make([fields[2]] + [int(f) for f in fields[3:]])

View file

@ -0,0 +1,27 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This file is imported by python tests ran by run_python_tests.py."""
import os
import android_commands
from run_java_tests import TestRunner
def _GetPackageName(fname):
"""Extracts the package name from the test file path."""
base_root = os.path.join('com', 'google', 'android')
dirname = os.path.dirname(fname)
package = dirname[dirname.rfind(base_root):]
return package.replace(os.sep, '.')
def RunJavaTest(fname, suite, test, ports_to_forward):
device = android_commands.GetAttachedDevices()[0]
package_name = _GetPackageName(fname)
test = package_name + '.' + suite + '#' + test
java_test_runner = TestRunner(False, device, [test], False, False, False,
False, 0, ports_to_forward)
return java_test_runner.Run()

View file

@ -0,0 +1,160 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A helper module for parsing JSON objects from perf tests results."""
import json
def GetAverageRunInfo(json_data, name):
"""Summarizes TraceEvent JSON data for performance metrics.
Example JSON Inputs (More tags can be added but these are required):
Measuring Duration:
[
{ "cat": "Java",
"ts": 10000000000,
"ph": "S",
"name": "TestTrace"
},
{ "cat": "Java",
"ts": 10000004000,
"ph": "F",
"name": "TestTrace"
},
...
]
Measuring Call Frequency (FPS):
[
{ "cat": "Java",
"ts": 10000000000,
"ph": "I",
"name": "TestTraceFPS"
},
{ "cat": "Java",
"ts": 10000004000,
"ph": "I",
"name": "TestTraceFPS"
},
...
]
Args:
json_data: A list of dictonaries each representing a JSON object.
name: The 'name' tag to filter on in the JSON file.
Returns:
A dictionary of result data with the following tags:
min: The minimum value tracked.
max: The maximum value tracked.
average: The average of all the values tracked.
count: The number of times the category/name pair was tracked.
type: The type of tracking ('Instant' for instant tags and 'Span' for
begin/end tags.
category: The passed in category filter.
name: The passed in name filter.
data_points: A list of all of the times used to generate this data.
units: The units for the values being reported.
Raises:
Exception: if entry contains invalid data.
"""
def EntryFilter(entry):
return entry['cat'] == 'Java' and entry['name'] == name
filtered_entries = filter(EntryFilter, json_data)
result = {}
result['min'] = -1
result['max'] = -1
result['average'] = 0
result['count'] = 0
result['type'] = 'Unknown'
result['category'] = 'Java'
result['name'] = name
result['data_points'] = []
result['units'] = ''
total_sum = 0
last_val = 0
val_type = None
for entry in filtered_entries:
if not val_type:
if 'mem' in entry:
val_type = 'mem'
def GetVal(entry):
return entry['mem']
result['units'] = 'kb'
elif 'ts' in entry:
val_type = 'ts'
def GetVal(entry):
return float(entry['ts']) / 1000.0
result['units'] = 'ms'
else:
raise Exception('Entry did not contain valid value info: %s' % entry)
if not val_type in entry:
raise Exception('Entry did not contain expected value type "%s" '
'information: %s' % (val_type, entry))
val = GetVal(entry)
if (entry['ph'] == 'S' and
(result['type'] == 'Unknown' or result['type'] == 'Span')):
result['type'] = 'Span'
last_val = val
elif ((entry['ph'] == 'F' and result['type'] == 'Span') or
(entry['ph'] == 'I' and (result['type'] == 'Unknown' or
result['type'] == 'Instant'))):
if last_val > 0:
delta = val - last_val
if result['min'] == -1 or result['min'] > delta:
result['min'] = delta
if result['max'] == -1 or result['max'] < delta:
result['max'] = delta
total_sum += delta
result['count'] += 1
result['data_points'].append(delta)
if entry['ph'] == 'I':
result['type'] = 'Instant'
last_val = val
if result['count'] > 0: result['average'] = total_sum / result['count']
return result
def GetAverageRunInfoFromJSONString(json_string, name):
"""Returns the results from GetAverageRunInfo using a JSON string.
Args:
json_string: The string containing JSON.
name: The 'name' tag to filter on in the JSON file.
Returns:
See GetAverageRunInfo Returns section.
"""
return GetAverageRunInfo(json.loads(json_string), name)
def GetAverageRunInfoFromFile(json_file, name):
"""Returns the results from GetAverageRunInfo using a JSON file.
Args:
json_file: The path to a JSON file.
name: The 'name' tag to filter on in the JSON file.
Returns:
See GetAverageRunInfo Returns section.
"""
with open(json_file, 'r') as f:
data = f.read()
perf = json.loads(data)
return GetAverageRunInfo(perf, name)

View file

@ -0,0 +1,120 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import re
import android_commands
import math
# Valid values of result type.
RESULT_TYPES = {'unimportant': 'RESULT ',
'default': '*RESULT ',
'informational': ''}
def _EscapePerfResult(s):
"""Escapes |s| for use in a perf result."""
# Colons (:) and equal signs (=) are not allowed, and we chose an arbitrary
# limit of 40 chars.
return re.sub(':|=', '_', s[:40])
def PrintPerfResult(measurement, trace, values, units, result_type='default',
print_to_stdout=True):
"""Prints numerical data to stdout in the format required by perf tests.
The string args may be empty but they must not contain any colons (:) or
equals signs (=).
Args:
measurement: A description of the quantity being measured, e.g. "vm_peak".
trace: A description of the particular data point, e.g. "reference".
values: A list of numeric measured values.
units: A description of the units of measure, e.g. "bytes".
result_type: A tri-state that accepts values of ['unimportant', 'default',
'informational']. 'unimportant' prints RESULT, 'default' prints *RESULT
and 'informational' prints nothing.
print_to_stdout: If True, prints the output in stdout instead of returning
the output to caller.
Returns:
String of the formated perf result.
"""
assert result_type in RESULT_TYPES, 'result type: %s is invalid' % result_type
assert isinstance(values, list)
assert len(values)
assert '/' not in measurement
avg = None
sd = None
if len(values) > 1:
try:
value = '[%s]' % ','.join([str(v) for v in values])
avg = sum([float(v) for v in values]) / len(values)
sqdiffs = [(float(v) - avg) ** 2 for v in values]
variance = sum(sqdiffs) / (len(values) - 1)
sd = math.sqrt(variance)
except ValueError:
value = ", ".join(values)
else:
value = values[0]
trace_name = _EscapePerfResult(trace)
output = '%s%s: %s%s%s %s' % (
RESULT_TYPES[result_type],
_EscapePerfResult(measurement),
trace_name,
# Do not show equal sign if the trace is empty. Usually it happens when
# measurement is enough clear to describe the result.
'= ' if trace_name else '',
value,
units)
if avg:
output += '\nAvg %s: %f%s' % (measurement, avg, units)
if sd:
output += '\nSd %s: %f%s' % (measurement, sd, units)
if print_to_stdout:
print output
return output
class PerfTestSetup(object):
"""Provides methods for setting up a device for perf testing."""
_DROP_CACHES = '/proc/sys/vm/drop_caches'
_SCALING_GOVERNOR = '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor'
def __init__(self, adb):
self._adb = adb
num_cpus = self._adb.GetFileContents('/sys/devices/system/cpu/online',
log_result=False)
assert num_cpus, 'Unable to find /sys/devices/system/cpu/online'
self._num_cpus = int(num_cpus[0].split('-')[-1])
self._original_scaling_governor = None
def DropRamCaches(self):
"""Drops the filesystem ram caches for performance testing."""
if not self._adb.IsRootEnabled():
self._adb.EnableAdbRoot()
self._adb.RunShellCommand('sync')
self._adb.RunShellCommand('echo 3 > ' + PerfTestSetup._DROP_CACHES)
def SetUp(self):
"""Sets up performance tests."""
if not self._original_scaling_governor:
self._original_scaling_governor = self._adb.GetFileContents(
PerfTestSetup._SCALING_GOVERNOR % 0,
log_result=False)[0]
self._SetScalingGovernorInternal('performance')
self.DropRamCaches()
def TearDown(self):
"""Tears down performance tests."""
if self._original_scaling_governor:
self._SetScalingGovernorInternal(self._original_scaling_governor)
self._original_scaling_governor = None
def _SetScalingGovernorInternal(self, value):
for cpu in range(self._num_cpus):
self._adb.RunShellCommand(
('echo %s > ' + PerfTestSetup._SCALING_GOVERNOR) % (value, cpu))

View file

@ -0,0 +1,167 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Functions that deals with local and device ports."""
import contextlib
import fcntl
import httplib
import logging
import os
import re
import socket
import traceback
import cmd_helper
import constants
#The following two methods are used to allocate the port source for various
# types of test servers. Because some net relates tests can be run on shards
# at same time, it's important to have a mechanism to allocate the port process
# safe. In here, we implement the safe port allocation by leveraging flock.
def ResetTestServerPortAllocation():
"""Reset the port allocation to start from TEST_SERVER_PORT_FIRST.
Returns:
Returns True if reset successes. Otherwise returns False.
"""
try:
with open(constants.TEST_SERVER_PORT_FILE, 'w') as fp:
fp.write('%d' % constants.TEST_SERVER_PORT_FIRST)
if os.path.exists(constants.TEST_SERVER_PORT_LOCKFILE):
os.unlink(constants.TEST_SERVER_PORT_LOCKFILE)
return True
except Exception as e:
logging.error(e)
return False
def AllocateTestServerPort():
"""Allocate a port incrementally.
Returns:
Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and
TEST_SERVER_PORT_LAST. Returning 0 means no more valid port can be used.
"""
port = 0
ports_tried = []
try:
fp_lock = open(constants.TEST_SERVER_PORT_LOCKFILE, 'w')
fcntl.flock(fp_lock, fcntl.LOCK_EX)
# Get current valid port and calculate next valid port.
assert os.path.exists(constants.TEST_SERVER_PORT_FILE)
with open(constants.TEST_SERVER_PORT_FILE, 'r+') as fp:
port = int(fp.read())
ports_tried.append(port)
while IsHostPortUsed(port):
port += 1
ports_tried.append(port)
if (port > constants.TEST_SERVER_PORT_LAST or
port < constants.TEST_SERVER_PORT_FIRST):
port = 0
else:
fp.seek(0, os.SEEK_SET)
fp.write('%d' % (port + 1))
except Exception as e:
logging.info(e)
finally:
if fp_lock:
fcntl.flock(fp_lock, fcntl.LOCK_UN)
fp_lock.close()
if port:
logging.info('Allocate port %d for test server.', port)
else:
logging.error('Could not allocate port for test server. '
'List of ports tried: %s', str(ports_tried))
return port
def IsHostPortUsed(host_port):
"""Checks whether the specified host port is used or not.
Uses -n -P to inhibit the conversion of host/port numbers to host/port names.
Args:
host_port: Port on host we want to check.
Returns:
True if the port on host is already used, otherwise returns False.
"""
port_info = '(127\.0\.0\.1)|(localhost)\:%d' % host_port
# TODO(jnd): Find a better way to filter the port.
re_port = re.compile(port_info, re.MULTILINE)
if re_port.findall(cmd_helper.GetCmdOutput(['lsof', '-nPi:%d' % host_port])):
return True
return False
def IsDevicePortUsed(adb, device_port, state=''):
"""Checks whether the specified device port is used or not.
Args:
adb: Instance of AndroidCommands for talking to the device.
device_port: Port on device we want to check.
state: String of the specified state. Default is empty string, which
means any state.
Returns:
True if the port on device is already used, otherwise returns False.
"""
base_url = '127.0.0.1:%d' % device_port
netstat_results = adb.RunShellCommand('netstat', log_result=False)
for single_connect in netstat_results:
# Column 3 is the local address which we want to check with.
connect_results = single_connect.split()
is_state_match = connect_results[5] == state if state else True
if connect_results[3] == base_url and is_state_match:
return True
return False
def IsHttpServerConnectable(host, port, tries=3, command='GET', path='/',
expected_read='', timeout=2):
"""Checks whether the specified http server is ready to serve request or not.
Args:
host: Host name of the HTTP server.
port: Port number of the HTTP server.
tries: How many times we want to test the connection. The default value is
3.
command: The http command we use to connect to HTTP server. The default
command is 'GET'.
path: The path we use when connecting to HTTP server. The default path is
'/'.
expected_read: The content we expect to read from the response. The default
value is ''.
timeout: Timeout (in seconds) for each http connection. The default is 2s.
Returns:
Tuple of (connect status, client error). connect status is a boolean value
to indicate whether the server is connectable. client_error is the error
message the server returns when connect status is false.
"""
assert tries >= 1
for i in xrange(0, tries):
client_error = None
try:
with contextlib.closing(httplib.HTTPConnection(
host, port, timeout=timeout)) as http:
# Output some debug information when we have tried more than 2 times.
http.set_debuglevel(i >= 2)
http.request(command, path)
r = http.getresponse()
content = r.read()
if r.status == 200 and r.reason == 'OK' and content == expected_read:
return (True, '')
client_error = ('Bad response: %s %s version %s\n ' %
(r.status, r.reason, r.version) +
'\n '.join([': '.join(h) for h in r.getheaders()]))
except (httplib.HTTPException, socket.error) as e:
# Probably too quick connecting: try again.
exception_error_msgs = traceback.format_exception_only(type(e), e)
if exception_error_msgs:
client_error = ''.join(exception_error_msgs)
# Only returns last client_error.
return (False, client_error or 'Timeout')

View file

@ -0,0 +1,168 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Base class for Android Python-driven tests.
This test case is intended to serve as the base class for any Python-driven
tests. It is similar to the Python unitttest module in that the user's tests
inherit from this case and add their tests in that case.
When a PythonTestBase object is instantiated, its purpose is to run only one of
its tests. The test runner gives it the name of the test the instance will
run. The test runner calls SetUp with the Android device ID which the test will
run against. The runner runs the test method itself, collecting the result,
and calls TearDown.
Tests can basically do whatever they want in the test methods, such as call
Java tests using _RunJavaTests. Those methods have the advantage of massaging
the Java test results into Python test results.
"""
import logging
import os
import time
import android_commands
import apk_info
from run_java_tests import TestRunner
from test_result import SingleTestResult, TestResults
# aka the parent of com.google.android
BASE_ROOT = 'src' + os.sep
class PythonTestBase(object):
"""Base class for Python-driven tests."""
def __init__(self, test_name):
# test_name must match one of the test methods defined on a subclass which
# inherits from this class.
# It's stored so we can do the attr lookup on demand, allowing this class
# to be pickled, a requirement for the multiprocessing module.
self.test_name = test_name
class_name = self.__class__.__name__
self.qualified_name = class_name + '.' + self.test_name
def SetUp(self, options):
self.options = options
self.shard_index = self.options.shard_index
self.device_id = self.options.device_id
self.adb = android_commands.AndroidCommands(self.device_id)
self.ports_to_forward = []
def TearDown(self):
pass
def Run(self):
logging.warning('Running Python-driven test: %s', self.test_name)
return getattr(self, self.test_name)()
def _RunJavaTest(self, fname, suite, test):
"""Runs a single Java test with a Java TestRunner.
Args:
fname: filename for the test (e.g. foo/bar/baz/tests/FooTest.py)
suite: name of the Java test suite (e.g. FooTest)
test: name of the test method to run (e.g. testFooBar)
Returns:
TestResults object with a single test result.
"""
test = self._ComposeFullTestName(fname, suite, test)
apks = [apk_info.ApkInfo(self.options.test_apk_path,
self.options.test_apk_jar_path)]
java_test_runner = TestRunner(self.options, self.device_id, [test], False,
self.shard_index,
apks,
self.ports_to_forward)
return java_test_runner.Run()
def _RunJavaTests(self, fname, tests):
"""Calls a list of tests and stops at the first test failure.
This method iterates until either it encounters a non-passing test or it
exhausts the list of tests. Then it returns the appropriate Python result.
Args:
fname: filename for the Python test
tests: a list of Java test names which will be run
Returns:
A TestResults object containing a result for this Python test.
"""
start_ms = int(time.time()) * 1000
result = None
for test in tests:
# We're only running one test at a time, so this TestResults object will
# hold only one result.
suite, test_name = test.split('.')
result = self._RunJavaTest(fname, suite, test_name)
# A non-empty list means the test did not pass.
if result.GetAllBroken():
break
duration_ms = int(time.time()) * 1000 - start_ms
# Do something with result.
return self._ProcessResults(result, start_ms, duration_ms)
def _ProcessResults(self, result, start_ms, duration_ms):
"""Translates a Java test result into a Python result for this test.
The TestRunner class that we use under the covers will return a test result
for that specific Java test. However, to make reporting clearer, we have
this method to abstract that detail and instead report that as a failure of
this particular test case while still including the Java stack trace.
Args:
result: TestResults with a single Java test result
start_ms: the time the test started
duration_ms: the length of the test
Returns:
A TestResults object containing a result for this Python test.
"""
test_results = TestResults()
# If our test is in broken, then it crashed/failed.
broken = result.GetAllBroken()
if broken:
# Since we have run only one test, take the first and only item.
single_result = broken[0]
log = single_result.log
if not log:
log = 'No logging information.'
python_result = SingleTestResult(self.qualified_name, start_ms,
duration_ms,
log)
# Figure out where the test belonged. There's probably a cleaner way of
# doing this.
if single_result in result.crashed:
test_results.crashed = [python_result]
elif single_result in result.failed:
test_results.failed = [python_result]
elif single_result in result.unknown:
test_results.unknown = [python_result]
else:
python_result = SingleTestResult(self.qualified_name, start_ms,
duration_ms)
test_results.ok = [python_result]
return test_results
def _ComposeFullTestName(self, fname, suite, test):
package_name = self._GetPackageName(fname)
return package_name + '.' + suite + '#' + test
def _GetPackageName(self, fname):
"""Extracts the package name from the test file path."""
dirname = os.path.dirname(fname)
package = dirname[dirname.rfind(BASE_ROOT) + len(BASE_ROOT):]
return package.replace(os.sep, '.')

View file

@ -0,0 +1,84 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper module for calling python-based tests."""
import logging
import sys
import time
from test_result import TestResults
def CallPythonTest(test, options):
"""Invokes a test function and translates Python exceptions into test results.
This method invokes SetUp()/TearDown() on the test. It is intended to be
resilient to exceptions in SetUp(), the test itself, and TearDown(). Any
Python exception means the test is marked as failed, and the test result will
contain information about the exception.
If SetUp() raises an exception, the test is not run.
If TearDown() raises an exception, the test is treated as a failure. However,
if the test itself raised an exception beforehand, that stack trace will take
precedence whether or not TearDown() also raised an exception.
shard_index is not applicable in single-device scenarios, when test execution
is serial rather than parallel. Tests can use this to bring up servers with
unique port numbers, for example. See also python_test_sharder.
Args:
test: an object which is ostensibly a subclass of PythonTestBase.
options: Options to use for setting up tests.
Returns:
A TestResults object which contains any results produced by the test or, in
the case of a Python exception, the Python exception info.
"""
start_date_ms = int(time.time()) * 1000
failed = False
try:
test.SetUp(options)
except Exception:
failed = True
logging.exception(
'Caught exception while trying to run SetUp() for test: ' +
test.qualified_name)
# Tests whose SetUp() method has failed are likely to fail, or at least
# yield invalid results.
exc_info = sys.exc_info()
return TestResults.FromPythonException(test.qualified_name, start_date_ms,
exc_info)
try:
result = test.Run()
except Exception:
# Setting this lets TearDown() avoid stomping on our stack trace from Run()
# should TearDown() also raise an exception.
failed = True
logging.exception('Caught exception while trying to run test: ' +
test.qualified_name)
exc_info = sys.exc_info()
result = TestResults.FromPythonException(test.qualified_name, start_date_ms,
exc_info)
try:
test.TearDown()
except Exception:
logging.exception(
'Caught exception while trying run TearDown() for test: ' +
test.qualified_name)
if not failed:
# Don't stomp the error during the test if TearDown blows up. This is a
# trade-off: if the test fails, this will mask any problem with TearDown
# until the test is fixed.
exc_info = sys.exc_info()
result = TestResults.FromPythonException(test.qualified_name,
start_date_ms, exc_info)
return result

View file

@ -0,0 +1,203 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Takes care of sharding the python-drive tests in multiple devices."""
import copy
import logging
import multiprocessing
from python_test_caller import CallPythonTest
from run_java_tests import FatalTestException
import sharded_tests_queue
from test_result import TestResults
def SetTestsContainer(tests_container):
"""Sets PythonTestSharder as a top-level field.
PythonTestSharder uses multiprocessing.Pool, which creates a pool of
processes. This is used to initialize each worker in the pool, ensuring that
each worker has access to this shared pool of tests.
The multiprocessing module requires that this be a top-level method.
Args:
tests_container: the container for all the tests.
"""
PythonTestSharder.tests_container = tests_container
def _DefaultRunnable(test_runner):
"""A default runnable for a PythonTestRunner.
Args:
test_runner: A PythonTestRunner which will run tests.
Returns:
The test results.
"""
return test_runner.RunTests()
class PythonTestRunner(object):
"""Thin wrapper around a list of PythonTestBase instances.
This is meant to be a long-lived object which can run multiple Python tests
within its lifetime. Tests will receive the device_id and shard_index.
The shard index affords the ability to create unique port numbers (e.g.
DEFAULT_PORT + shard_index) if the test so wishes.
"""
def __init__(self, options):
"""Constructor.
Args:
options: Options to use for setting up tests.
"""
self.options = options
def RunTests(self):
"""Runs tests from the shared pool of tests, aggregating results.
Returns:
A list of test results for all of the tests which this runner executed.
"""
tests = PythonTestSharder.tests_container
results = []
for t in tests:
res = CallPythonTest(t, self.options)
results.append(res)
return TestResults.FromTestResults(results)
class PythonTestSharder(object):
"""Runs Python tests in parallel on multiple devices.
This is lifted more or less wholesale from BaseTestRunner.
Under the covers, it creates a pool of long-lived PythonTestRunners, which
execute tests from the pool of tests.
Args:
attached_devices: a list of device IDs attached to the host.
available_tests: a list of tests to run which subclass PythonTestBase.
options: Options to use for setting up tests.
Returns:
An aggregated list of test results.
"""
tests_container = None
def __init__(self, attached_devices, available_tests, options):
self.options = options
self.attached_devices = attached_devices
self.retries = options.shard_retries
self.tests = available_tests
def _SetupSharding(self, tests):
"""Creates the shared pool of tests and makes it available to test runners.
Args:
tests: the list of tests which will be consumed by workers.
"""
SetTestsContainer(sharded_tests_queue.ShardedTestsQueue(
len(self.attached_devices), tests))
def RunShardedTests(self):
"""Runs tests in parallel using a pool of workers.
Returns:
A list of test results aggregated from all test runs.
"""
logging.warning('*' * 80)
logging.warning('Sharding in ' + str(len(self.attached_devices)) +
' devices.')
logging.warning('Note that the output is not synchronized.')
logging.warning('Look for the "Final result" banner in the end.')
logging.warning('*' * 80)
all_passed = []
test_results = TestResults()
tests_to_run = self.tests
for retry in xrange(self.retries):
logging.warning('Try %d of %d', retry + 1, self.retries)
self._SetupSharding(self.tests)
test_runners = self._MakeTestRunners(self.attached_devices)
logging.warning('Starting...')
pool = multiprocessing.Pool(len(self.attached_devices),
SetTestsContainer,
[PythonTestSharder.tests_container])
# List of TestResults objects from each test execution.
try:
results_lists = pool.map(_DefaultRunnable, test_runners)
except Exception:
logging.exception('Unable to run tests. Something with the '
'PythonTestRunners has gone wrong.')
raise FatalTestException('PythonTestRunners were unable to run tests.')
test_results = TestResults.FromTestResults(results_lists)
# Accumulate passing results.
all_passed += test_results.ok
# If we have failed tests, map them to tests to retry.
failed_tests = test_results.GetAllBroken()
tests_to_run = self._GetTestsToRetry(self.tests,
failed_tests)
# Bail out early if we have no more tests. This can happen if all tests
# pass before we're out of retries, for example.
if not tests_to_run:
break
final_results = TestResults()
# all_passed has accumulated all passing test results.
# test_results will have the results from the most recent run, which could
# include a variety of failure modes (unknown, crashed, failed, etc).
final_results = test_results
final_results.ok = all_passed
return final_results
def _MakeTestRunners(self, attached_devices):
"""Initialize and return a list of PythonTestRunners.
Args:
attached_devices: list of device IDs attached to host.
Returns:
A list of PythonTestRunners, one for each device.
"""
test_runners = []
for index, device in enumerate(attached_devices):
logging.warning('*' * 80)
logging.warning('Creating shard %d for %s', index, device)
logging.warning('*' * 80)
# Bind the PythonTestRunner to a device & shard index. Give it the
# runnable which it will use to actually execute the tests.
test_options = copy.deepcopy(self.options)
test_options.ensure_value('device_id', device)
test_options.ensure_value('shard_index', index)
test_runner = PythonTestRunner(test_options)
test_runners.append(test_runner)
return test_runners
def _GetTestsToRetry(self, available_tests, failed_tests):
"""Infers a list of tests to retry from failed tests and available tests.
Args:
available_tests: a list of tests which subclass PythonTestBase.
failed_tests: a list of SingleTestResults representing failed tests.
Returns:
A list of test objects which correspond to test names found in
failed_tests, or an empty list if there is no correspondence.
"""
failed_test_names = map(lambda t: t.test_name, failed_tests)
tests_to_retry = [t for t in available_tests
if t.qualified_name in failed_test_names]
return tests_to_retry

View file

@ -0,0 +1,591 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs the Java tests. See more information on run_instrumentation_tests.py."""
import fnmatch
import logging
import os
import re
import shutil
import sys
import time
import android_commands
import apk_info
from base_test_runner import BaseTestRunner
from base_test_sharder import BaseTestSharder, SetTestsContainer
import cmd_helper
import constants
import errors
from forwarder import Forwarder
from json_perf_parser import GetAverageRunInfoFromJSONString
from perf_tests_helper import PrintPerfResult
import sharded_tests_queue
from test_result import SingleTestResult, TestResults
import valgrind_tools
_PERF_TEST_ANNOTATION = 'PerfTest'
class FatalTestException(Exception):
"""A fatal test exception."""
pass
def _TestNameToExpectation(test_name):
# A test name is a Package.Path.Class#testName; convert to what we use in
# the expectation file.
return '.'.join(test_name.replace('#', '.').split('.')[-2:])
def FilterTests(test_names, pattern_list, inclusive):
"""Filters |test_names| using a list of patterns.
Args:
test_names: A list of test names.
pattern_list: A list of patterns.
inclusive: If True, returns the tests that match any pattern. if False,
returns the tests that do not match any pattern.
Returns:
A list of test names.
"""
ret = []
for t in test_names:
has_match = False
for pattern in pattern_list:
has_match = has_match or fnmatch.fnmatch(_TestNameToExpectation(t),
pattern)
if has_match == inclusive:
ret += [t]
return ret
class TestRunner(BaseTestRunner):
"""Responsible for running a series of tests connected to a single device."""
_DEVICE_DATA_DIR = 'chrome/test/data'
_EMMA_JAR = os.path.join(os.environ.get('ANDROID_BUILD_TOP', ''),
'external/emma/lib/emma.jar')
_COVERAGE_MERGED_FILENAME = 'unittest_coverage.es'
_COVERAGE_WEB_ROOT_DIR = os.environ.get('EMMA_WEB_ROOTDIR')
_COVERAGE_FILENAME = 'coverage.ec'
_COVERAGE_RESULT_PATH = ('/data/data/com.google.android.apps.chrome/files/' +
_COVERAGE_FILENAME)
_COVERAGE_META_INFO_PATH = os.path.join(os.environ.get('ANDROID_BUILD_TOP',
''),
'out/target/common/obj/APPS',
'Chrome_intermediates/coverage.em')
_HOSTMACHINE_PERF_OUTPUT_FILE = '/tmp/chrome-profile'
_DEVICE_PERF_OUTPUT_SEARCH_PREFIX = (constants.DEVICE_PERF_OUTPUT_DIR +
'/chrome-profile*')
_DEVICE_HAS_TEST_FILES = {}
def __init__(self, options, device, tests_iter, coverage, shard_index, apks,
ports_to_forward):
"""Create a new TestRunner.
Args:
options: An options object with the following required attributes:
- build_type: 'Release' or 'Debug'.
- install_apk: Re-installs the apk if opted.
- save_perf_json: Whether or not to save the JSON file from UI perf
tests.
- screenshot_failures: Take a screenshot for a test failure
- tool: Name of the Valgrind tool.
- wait_for_debugger: blocks until the debugger is connected.
device: Attached android device.
tests_iter: A list of tests to be run.
coverage: Collects coverage information if opted.
shard_index: shard # for this TestRunner, used to create unique port
numbers.
apks: A list of ApkInfo objects need to be installed. The first element
should be the tests apk, the rests could be the apks used in test.
The default is ChromeTest.apk.
ports_to_forward: A list of port numbers for which to set up forwarders.
Can be optionally requested by a test case.
Raises:
FatalTestException: if coverage metadata is not available.
"""
BaseTestRunner.__init__(
self, device, options.tool, shard_index, options.build_type)
if not apks:
apks = [apk_info.ApkInfo(options.test_apk_path,
options.test_apk_jar_path)]
self.build_type = options.build_type
self.install_apk = options.install_apk
self.save_perf_json = options.save_perf_json
self.screenshot_failures = options.screenshot_failures
self.wait_for_debugger = options.wait_for_debugger
self.tests_iter = tests_iter
self.coverage = coverage
self.apks = apks
self.test_apk = apks[0]
self.instrumentation_class_path = self.test_apk.GetPackageName()
self.ports_to_forward = ports_to_forward
self.test_results = TestResults()
self.forwarder = None
if self.coverage:
if os.path.exists(TestRunner._COVERAGE_MERGED_FILENAME):
os.remove(TestRunner._COVERAGE_MERGED_FILENAME)
if not os.path.exists(TestRunner._COVERAGE_META_INFO_PATH):
raise FatalTestException('FATAL ERROR in ' + sys.argv[0] +
' : Coverage meta info [' +
TestRunner._COVERAGE_META_INFO_PATH +
'] does not exist.')
if (not TestRunner._COVERAGE_WEB_ROOT_DIR or
not os.path.exists(TestRunner._COVERAGE_WEB_ROOT_DIR)):
raise FatalTestException('FATAL ERROR in ' + sys.argv[0] +
' : Path specified in $EMMA_WEB_ROOTDIR [' +
TestRunner._COVERAGE_WEB_ROOT_DIR +
'] does not exist.')
def _GetTestsIter(self):
if not self.tests_iter:
# multiprocessing.Queue can't be pickled across processes if we have it as
# a member set during constructor. Grab one here instead.
self.tests_iter = (BaseTestSharder.tests_container)
assert self.tests_iter
return self.tests_iter
def CopyTestFilesOnce(self):
"""Pushes the test data files to the device. Installs the apk if opted."""
if TestRunner._DEVICE_HAS_TEST_FILES.get(self.device, False):
logging.warning('Already copied test files to device %s, skipping.',
self.device)
return
host_test_files = [
('android_webview/test/data/device_files', 'webview'),
('content/test/data/android/device_files', 'content'),
('chrome/test/data/android/device_files', 'chrome')
]
for (host_src, dst_layer) in host_test_files:
host_test_files_path = constants.CHROME_DIR + '/' + host_src
if os.path.exists(host_test_files_path):
self.adb.PushIfNeeded(host_test_files_path,
self.adb.GetExternalStorage() + '/' +
TestRunner._DEVICE_DATA_DIR + '/' + dst_layer)
if self.install_apk:
for apk in self.apks:
self.adb.ManagedInstall(apk.GetApkPath(),
package_name=apk.GetPackageName())
self.tool.CopyFiles()
TestRunner._DEVICE_HAS_TEST_FILES[self.device] = True
def SaveCoverageData(self, test):
"""Saves the Emma coverage data before it's overwritten by the next test.
Args:
test: the test whose coverage data is collected.
"""
if not self.coverage:
return
if not self.adb.Adb().Pull(TestRunner._COVERAGE_RESULT_PATH,
constants.CHROME_DIR):
logging.error('ERROR: Unable to find file ' +
TestRunner._COVERAGE_RESULT_PATH +
' on the device for test ' + test)
pulled_coverage_file = os.path.join(constants.CHROME_DIR,
TestRunner._COVERAGE_FILENAME)
if os.path.exists(TestRunner._COVERAGE_MERGED_FILENAME):
cmd = ['java', '-classpath', TestRunner._EMMA_JAR, 'emma', 'merge',
'-in', pulled_coverage_file,
'-in', TestRunner._COVERAGE_MERGED_FILENAME,
'-out', TestRunner._COVERAGE_MERGED_FILENAME]
cmd_helper.RunCmd(cmd)
else:
shutil.copy(pulled_coverage_file,
TestRunner._COVERAGE_MERGED_FILENAME)
os.remove(pulled_coverage_file)
def GenerateCoverageReportIfNeeded(self):
"""Uses the Emma to generate a coverage report and a html page."""
if not self.coverage:
return
cmd = ['java', '-classpath', TestRunner._EMMA_JAR,
'emma', 'report', '-r', 'html',
'-in', TestRunner._COVERAGE_MERGED_FILENAME,
'-in', TestRunner._COVERAGE_META_INFO_PATH]
cmd_helper.RunCmd(cmd)
new_dir = os.path.join(TestRunner._COVERAGE_WEB_ROOT_DIR,
time.strftime('Coverage_for_%Y_%m_%d_%a_%H:%M'))
shutil.copytree('coverage', new_dir)
latest_dir = os.path.join(TestRunner._COVERAGE_WEB_ROOT_DIR,
'Latest_Coverage_Run')
if os.path.exists(latest_dir):
shutil.rmtree(latest_dir)
os.mkdir(latest_dir)
webserver_new_index = os.path.join(new_dir, 'index.html')
webserver_new_files = os.path.join(new_dir, '_files')
webserver_latest_index = os.path.join(latest_dir, 'index.html')
webserver_latest_files = os.path.join(latest_dir, '_files')
# Setup new softlinks to last result.
os.symlink(webserver_new_index, webserver_latest_index)
os.symlink(webserver_new_files, webserver_latest_files)
cmd_helper.RunCmd(['chmod', '755', '-R', latest_dir, new_dir])
def _GetInstrumentationArgs(self):
ret = {}
if self.coverage:
ret['coverage'] = 'true'
if self.wait_for_debugger:
ret['debug'] = 'true'
return ret
def _TakeScreenshot(self, test):
"""Takes a screenshot from the device."""
screenshot_tool = os.path.join(constants.CHROME_DIR,
'third_party/android_tools/sdk/tools/monkeyrunner')
screenshot_script = os.path.join(constants.CHROME_DIR,
'build/android/monkeyrunner_screenshot.py')
screenshot_path = os.path.join(constants.CHROME_DIR,
'out_screenshots')
if not os.path.exists(screenshot_path):
os.mkdir(screenshot_path)
screenshot_name = os.path.join(screenshot_path, test + '.png')
logging.info('Taking screenshot named %s', screenshot_name)
cmd_helper.RunCmd([screenshot_tool, screenshot_script,
'--serial', self.device,
'--file', screenshot_name])
def SetUp(self):
"""Sets up the test harness and device before all tests are run."""
super(TestRunner, self).SetUp()
if not self.adb.IsRootEnabled():
logging.warning('Unable to enable java asserts for %s, non rooted device',
self.device)
else:
if self.adb.SetJavaAssertsEnabled(enable=True):
self.adb.Reboot(full_reboot=False)
# We give different default value to launch HTTP server based on shard index
# because it may have race condition when multiple processes are trying to
# launch lighttpd with same port at same time.
http_server_ports = self.LaunchTestHttpServer(
os.path.join(constants.CHROME_DIR),
(constants.LIGHTTPD_RANDOM_PORT_FIRST + self.shard_index))
if self.ports_to_forward:
port_pairs = [(port, port) for port in self.ports_to_forward]
# We need to remember which ports the HTTP server is using, since the
# forwarder will stomp on them otherwise.
port_pairs.append(http_server_ports)
self.forwarder = Forwarder(
self.adb, port_pairs, self.tool, '127.0.0.1', self.build_type)
self.CopyTestFilesOnce()
self.flags.AddFlags(['--enable-test-intents'])
def TearDown(self):
"""Cleans up the test harness and saves outstanding data from test run."""
if self.forwarder:
self.forwarder.Close()
self.GenerateCoverageReportIfNeeded()
super(TestRunner, self).TearDown()
def TestSetup(self, test):
"""Sets up the test harness for running a particular test.
Args:
test: The name of the test that will be run.
"""
self.SetupPerfMonitoringIfNeeded(test)
self._SetupIndividualTestTimeoutScale(test)
self.tool.SetupEnvironment()
# Make sure the forwarder is still running.
self.RestartHttpServerForwarderIfNecessary()
def _IsPerfTest(self, test):
"""Determines whether a test is a performance test.
Args:
test: The name of the test to be checked.
Returns:
Whether the test is annotated as a performance test.
"""
return _PERF_TEST_ANNOTATION in self.test_apk.GetTestAnnotations(test)
def SetupPerfMonitoringIfNeeded(self, test):
"""Sets up performance monitoring if the specified test requires it.
Args:
test: The name of the test to be run.
"""
if not self._IsPerfTest(test):
return
self.adb.Adb().SendCommand('shell rm ' +
TestRunner._DEVICE_PERF_OUTPUT_SEARCH_PREFIX)
self.adb.StartMonitoringLogcat()
def TestTeardown(self, test, test_result):
"""Cleans up the test harness after running a particular test.
Depending on the options of this TestRunner this might handle coverage
tracking or performance tracking. This method will only be called if the
test passed.
Args:
test: The name of the test that was just run.
test_result: result for this test.
"""
self.tool.CleanUpEnvironment()
# The logic below relies on the test passing.
if not test_result or test_result.GetStatusCode():
return
self.TearDownPerfMonitoring(test)
self.SaveCoverageData(test)
def TearDownPerfMonitoring(self, test):
"""Cleans up performance monitoring if the specified test required it.
Args:
test: The name of the test that was just run.
Raises:
FatalTestException: if there's anything wrong with the perf data.
"""
if not self._IsPerfTest(test):
return
raw_test_name = test.split('#')[1]
# Wait and grab annotation data so we can figure out which traces to parse
regex = self.adb.WaitForLogMatch(re.compile('\*\*PERFANNOTATION\(' +
raw_test_name +
'\)\:(.*)'), None)
# If the test is set to run on a specific device type only (IE: only
# tablet or phone) and it is being run on the wrong device, the test
# just quits and does not do anything. The java test harness will still
# print the appropriate annotation for us, but will add --NORUN-- for
# us so we know to ignore the results.
# The --NORUN-- tag is managed by MainActivityTestBase.java
if regex.group(1) != '--NORUN--':
# Obtain the relevant perf data. The data is dumped to a
# JSON formatted file.
json_string = self.adb.GetFileContents(
'/data/data/com.google.android.apps.chrome/files/PerfTestData.txt')
if json_string:
json_string = '\n'.join(json_string)
else:
raise FatalTestException('Perf file does not exist or is empty')
if self.save_perf_json:
json_local_file = '/tmp/chromium-android-perf-json-' + raw_test_name
with open(json_local_file, 'w') as f:
f.write(json_string)
logging.info('Saving Perf UI JSON from test ' +
test + ' to ' + json_local_file)
raw_perf_data = regex.group(1).split(';')
for raw_perf_set in raw_perf_data:
if raw_perf_set:
perf_set = raw_perf_set.split(',')
if len(perf_set) != 3:
raise FatalTestException('Unexpected number of tokens in '
'perf annotation string: ' + raw_perf_set)
# Process the performance data
result = GetAverageRunInfoFromJSONString(json_string, perf_set[0])
PrintPerfResult(perf_set[1], perf_set[2],
[result['average']], result['units'])
def _SetupIndividualTestTimeoutScale(self, test):
timeout_scale = self._GetIndividualTestTimeoutScale(test)
valgrind_tools.SetChromeTimeoutScale(self.adb, timeout_scale)
def _GetIndividualTestTimeoutScale(self, test):
"""Returns the timeout scale for the given |test|."""
annotations = self.apks[0].GetTestAnnotations(test)
timeout_scale = 1
if 'TimeoutScale' in annotations:
for annotation in annotations:
scale_match = re.match('TimeoutScale:([0-9]+)', annotation)
if scale_match:
timeout_scale = int(scale_match.group(1))
if self.wait_for_debugger:
timeout_scale *= 100
return timeout_scale
def _GetIndividualTestTimeoutSecs(self, test):
"""Returns the timeout in seconds for the given |test|."""
annotations = self.apks[0].GetTestAnnotations(test)
if 'Manual' in annotations:
return 600 * 60
if 'External' in annotations:
return 10 * 60
if 'LargeTest' in annotations or _PERF_TEST_ANNOTATION in annotations:
return 5 * 60
if 'MediumTest' in annotations:
return 3 * 60
return 1 * 60
def RunTests(self):
"""Runs the tests, generating the coverage if needed.
Returns:
A TestResults object.
"""
instrumentation_path = (self.instrumentation_class_path +
'/android.test.InstrumentationTestRunner')
instrumentation_args = self._GetInstrumentationArgs()
for test in self._GetTestsIter():
test_result = None
start_date_ms = None
try:
self.TestSetup(test)
start_date_ms = int(time.time()) * 1000
args_with_filter = dict(instrumentation_args)
args_with_filter['class'] = test
# |test_results| is a list that should contain
# a single TestResult object.
logging.warn(args_with_filter)
(test_results, _) = self.adb.Adb().StartInstrumentation(
instrumentation_path=instrumentation_path,
instrumentation_args=args_with_filter,
timeout_time=(self._GetIndividualTestTimeoutSecs(test) *
self._GetIndividualTestTimeoutScale(test) *
self.tool.GetTimeoutScale()))
duration_ms = int(time.time()) * 1000 - start_date_ms
assert len(test_results) == 1
test_result = test_results[0]
status_code = test_result.GetStatusCode()
if status_code:
log = test_result.GetFailureReason()
if not log:
log = 'No information.'
if self.screenshot_failures or log.find('INJECT_EVENTS perm') >= 0:
self._TakeScreenshot(test)
self.test_results.failed += [SingleTestResult(test, start_date_ms,
duration_ms, log)]
else:
result = [SingleTestResult(test, start_date_ms, duration_ms)]
self.test_results.ok += result
# Catch exceptions thrown by StartInstrumentation().
# See ../../third_party/android/testrunner/adb_interface.py
except (errors.WaitForResponseTimedOutError,
errors.DeviceUnresponsiveError,
errors.InstrumentationError), e:
if start_date_ms:
duration_ms = int(time.time()) * 1000 - start_date_ms
else:
start_date_ms = int(time.time()) * 1000
duration_ms = 0
message = str(e)
if not message:
message = 'No information.'
self.test_results.crashed += [SingleTestResult(test, start_date_ms,
duration_ms,
message)]
test_result = None
self.TestTeardown(test, test_result)
return self.test_results
class TestSharder(BaseTestSharder):
"""Responsible for sharding the tests on the connected devices."""
def __init__(self, attached_devices, options, tests, apks):
BaseTestSharder.__init__(self, attached_devices)
self.options = options
self.tests = tests
self.apks = apks
def SetupSharding(self, tests):
"""Called before starting the shards."""
SetTestsContainer(sharded_tests_queue.ShardedTestsQueue(
len(self.attached_devices), tests))
def CreateShardedTestRunner(self, device, index):
"""Creates a sharded test runner.
Args:
device: Device serial where this shard will run.
index: Index of this device in the pool.
Returns:
A TestRunner object.
"""
return TestRunner(self.options, device, None, False, index, self.apks, [])
def DispatchJavaTests(options, apks):
"""Dispatches Java tests onto connected device(s).
If possible, this method will attempt to shard the tests to
all connected devices. Otherwise, dispatch and run tests on one device.
Args:
options: Command line options.
apks: list of APKs to use.
Returns:
A TestResults object holding the results of the Java tests.
Raises:
FatalTestException: when there's no attached the devices.
"""
test_apk = apks[0]
if options.annotation:
available_tests = test_apk.GetAnnotatedTests(options.annotation)
if len(options.annotation) == 1 and options.annotation[0] == 'SmallTest':
tests_without_annotation = [
m for m in
test_apk.GetTestMethods()
if not test_apk.GetTestAnnotations(m) and
not apk_info.ApkInfo.IsPythonDrivenTest(m)]
if tests_without_annotation:
tests_without_annotation.sort()
logging.warning('The following tests do not contain any annotation. '
'Assuming "SmallTest":\n%s',
'\n'.join(tests_without_annotation))
available_tests += tests_without_annotation
else:
available_tests = [m for m in test_apk.GetTestMethods()
if not apk_info.ApkInfo.IsPythonDrivenTest(m)]
coverage = os.environ.get('EMMA_INSTRUMENT') == 'true'
tests = []
if options.test_filter:
# |available_tests| are in adb instrument format: package.path.class#test.
filter_without_hash = options.test_filter.replace('#', '.')
tests = [t for t in available_tests
if filter_without_hash in t.replace('#', '.')]
else:
tests = available_tests
if not tests:
logging.warning('No Java tests to run with current args.')
return TestResults()
tests *= options.number_of_runs
attached_devices = android_commands.GetAttachedDevices()
test_results = TestResults()
if not attached_devices:
raise FatalTestException('You have no devices attached or visible!')
if options.device:
attached_devices = [options.device]
logging.info('Will run: %s', str(tests))
if len(attached_devices) > 1 and (coverage or options.wait_for_debugger):
logging.warning('Coverage / debugger can not be sharded, '
'using first available device')
attached_devices = attached_devices[:1]
sharder = TestSharder(attached_devices, options, tests, apks)
test_results = sharder.RunShardedTests()
return test_results

View file

@ -0,0 +1,207 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs the Python tests (relies on using the Java test runner)."""
import logging
import os
import sys
import types
import android_commands
import apk_info
import constants
import python_test_base
from python_test_caller import CallPythonTest
from python_test_sharder import PythonTestSharder
import run_java_tests
from run_java_tests import FatalTestException
from test_info_collection import TestInfoCollection
from test_result import TestResults
def _GetPythonFiles(root, files):
"""Returns all files from |files| that end in 'Test.py'.
Args:
root: A directory name with python files.
files: A list of file names.
Returns:
A list with all Python driven test file paths.
"""
return [os.path.join(root, f) for f in files if f.endswith('Test.py')]
def _InferImportNameFromFile(python_file):
"""Given a file, infer the import name for that file.
Example: /usr/foo/bar/baz.py -> baz.
Args:
python_file: path to the Python file, ostensibly to import later.
Returns:
The module name for the given file.
"""
return os.path.splitext(os.path.basename(python_file))[0]
def DispatchPythonTests(options):
"""Dispatches the Python tests. If there are multiple devices, use sharding.
Args:
options: command line options.
Returns:
A list of test results.
"""
attached_devices = android_commands.GetAttachedDevices()
if not attached_devices:
raise FatalTestException('You have no devices attached or visible!')
if options.device:
attached_devices = [options.device]
test_collection = TestInfoCollection()
all_tests = _GetAllTests(options.python_test_root, options.official_build)
test_collection.AddTests(all_tests)
test_names = [t.qualified_name for t in all_tests]
logging.debug('All available tests: ' + str(test_names))
available_tests = test_collection.GetAvailableTests(
options.annotation, options.test_filter)
if not available_tests:
logging.warning('No Python tests to run with current args.')
return TestResults()
available_tests *= options.number_of_runs
test_names = [t.qualified_name for t in available_tests]
logging.debug('Final list of tests to run: ' + str(test_names))
# Copy files to each device before running any tests.
for device_id in attached_devices:
logging.debug('Pushing files to device %s', device_id)
apks = [apk_info.ApkInfo(options.test_apk_path, options.test_apk_jar_path)]
test_files_copier = run_java_tests.TestRunner(options, device_id,
None, False, 0, apks, [])
test_files_copier.CopyTestFilesOnce()
# Actually run the tests.
if len(attached_devices) > 1 and options.wait_for_debugger:
logging.warning('Debugger can not be sharded, '
'using first available device')
attached_devices = attached_devices[:1]
logging.debug('Running Python tests')
sharder = PythonTestSharder(attached_devices, available_tests, options)
test_results = sharder.RunShardedTests()
return test_results
def _GetTestModules(python_test_root, is_official_build):
"""Retrieve a sorted list of pythonDrivenTests.
Walks the location of pythonDrivenTests, imports them, and provides the list
of imported modules to the caller.
Args:
python_test_root: the path to walk, looking for pythonDrivenTests
is_official_build: whether to run only those tests marked 'official'
Returns:
A list of Python modules which may have zero or more tests.
"""
# By default run all python tests under pythonDrivenTests.
python_test_file_list = []
for root, _, files in os.walk(python_test_root):
if (root.endswith('pythonDrivenTests')
or (is_official_build
and root.endswith('pythonDrivenTests/official'))):
python_test_file_list += _GetPythonFiles(root, files)
python_test_file_list.sort()
test_module_list = [_GetModuleFromFile(test_file)
for test_file in python_test_file_list]
return test_module_list
def _GetModuleFromFile(python_file):
"""Gets the module associated with a file by importing it.
Args:
python_file: file to import
Returns:
The module object.
"""
sys.path.append(os.path.dirname(python_file))
import_name = _InferImportNameFromFile(python_file)
return __import__(import_name)
def _GetTestsFromClass(test_class):
"""Create a list of test objects for each test method on this class.
Test methods are methods on the class which begin with 'test'.
Args:
test_class: class object which contains zero or more test methods.
Returns:
A list of test objects, each of which is bound to one test.
"""
test_names = [m for m in dir(test_class)
if _IsTestMethod(m, test_class)]
return map(test_class, test_names)
def _GetTestClassesFromModule(test_module):
tests = []
for name in dir(test_module):
attr = getattr(test_module, name)
if _IsTestClass(attr):
tests.extend(_GetTestsFromClass(attr))
return tests
def _IsTestClass(test_class):
return (type(test_class) is types.TypeType and
issubclass(test_class, python_test_base.PythonTestBase) and
test_class is not python_test_base.PythonTestBase)
def _IsTestMethod(attrname, test_case_class):
"""Checks whether this is a valid test method.
Args:
attrname: the method name.
test_case_class: the test case class.
Returns:
True if test_case_class.'attrname' is callable and it starts with 'test';
False otherwise.
"""
attr = getattr(test_case_class, attrname)
return callable(attr) and attrname.startswith('test')
def _GetAllTests(test_root, is_official_build):
"""Retrieve a list of Python test modules and their respective methods.
Args:
test_root: path which contains Python-driven test files
is_official_build: whether this is an official build
Returns:
List of test case objects for all available test methods.
"""
if not test_root:
return []
all_tests = []
test_module_list = _GetTestModules(test_root, is_official_build)
for module in test_module_list:
all_tests.extend(_GetTestClassesFromModule(module))
return all_tests

View file

@ -0,0 +1,26 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper functions common to native, java and python test runners."""
import logging
import os
def GetExpectations(file_name):
"""Returns a list of test names in the |file_name| test expectations file."""
if not file_name or not os.path.exists(file_name):
return []
return [x for x in [x.strip() for x in file(file_name).readlines()]
if x and x[0] != '#']
def SetLogLevel(verbose_count):
"""Sets log level as |verbose_count|."""
log_level = logging.WARNING # Default.
if verbose_count == 1:
log_level = logging.INFO
elif verbose_count >= 2:
log_level = logging.DEBUG
logging.getLogger().setLevel(log_level)

View file

@ -0,0 +1,35 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A module that contains a queue for running sharded tests."""
import multiprocessing
class ShardedTestsQueue(object):
"""A queue for managing pending tests across different runners.
This class should only be used when sharding.
Attributes:
num_devices: an integer; the number of attached Android devices.
tests: a list of tests to be run.
tests_queue: if sharding, a JoinableQueue object that holds tests from
|tests|. Otherwise, a list holding tests.
results_queue: a Queue object to hold TestResults objects.
"""
_STOP_SENTINEL = 'STOP' # sentinel value for iter()
def __init__(self, num_devices, tests):
self.num_devices = num_devices
self.tests_queue = multiprocessing.Queue()
for test in tests:
self.tests_queue.put(test)
for _ in xrange(self.num_devices):
self.tests_queue.put(ShardedTestsQueue._STOP_SENTINEL)
def __iter__(self):
"""Returns an iterator with the test cases."""
return iter(self.tests_queue.get, ShardedTestsQueue._STOP_SENTINEL)

View file

@ -0,0 +1,343 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import glob
import logging
import os
import sys
from base_test_runner import BaseTestRunner
import debug_info
import constants
import perf_tests_helper
import run_tests_helper
from test_package_apk import TestPackageApk
from test_package_executable import TestPackageExecutable
from test_result import TestResults
class SingleTestRunner(BaseTestRunner):
"""Single test suite attached to a single device.
Args:
device: Device to run the tests.
test_suite: A specific test suite to run, empty to run all.
gtest_filter: A gtest_filter flag.
test_arguments: Additional arguments to pass to the test binary.
timeout: Timeout for each test.
rebaseline: Whether or not to run tests in isolation and update the filter.
performance_test: Whether or not performance test(s).
cleanup_test_files: Whether or not to cleanup test files on device.
tool: Name of the Valgrind tool.
shard_index: index number of the shard on which the test suite will run.
dump_debug_info: Whether or not to dump debug information.
build_type: 'Release' or 'Debug'.
"""
def __init__(self, device, test_suite, gtest_filter, test_arguments, timeout,
rebaseline, performance_test, cleanup_test_files, tool_name,
shard_index, dump_debug_info, fast_and_loose, build_type):
BaseTestRunner.__init__(self, device, tool_name, shard_index, build_type)
self._running_on_emulator = self.device.startswith('emulator')
self._gtest_filter = gtest_filter
self._test_arguments = test_arguments
self.test_results = TestResults()
if dump_debug_info:
self.dump_debug_info = debug_info.GTestDebugInfo(self.adb, device,
os.path.basename(test_suite), gtest_filter)
else:
self.dump_debug_info = None
self.fast_and_loose = fast_and_loose
logging.warning('Test suite: ' + test_suite)
if os.path.splitext(test_suite)[1] == '.apk':
self.test_package = TestPackageApk(self.adb, device,
test_suite, timeout, rebaseline, performance_test, cleanup_test_files,
self.tool, self.dump_debug_info)
else:
self.test_package = TestPackageExecutable(
self.adb, device,
test_suite, timeout, rebaseline, performance_test, cleanup_test_files,
self.tool, self.dump_debug_info)
self._performance_test_setup = None
if performance_test:
self._performance_test_setup = perf_tests_helper.PerfTestSetup(self.adb)
def _TestSuiteRequiresMockTestServer(self):
"""Returns True if the test suite requires mock test server."""
return False
# TODO(yfriedman): Disabled because of flakiness.
# (self.test_package.test_suite_basename == 'unit_tests' or
# self.test_package.test_suite_basename == 'net_unittests' or
# False)
def _GetFilterFileName(self):
"""Returns the filename of gtest filter."""
return os.path.join(sys.path[0], 'gtest_filter',
self.test_package.test_suite_basename + '_disabled')
def _GetAdditionalEmulatorFilterName(self):
"""Returns the filename of additional gtest filter for emulator."""
return os.path.join(sys.path[0], 'gtest_filter',
self.test_package.test_suite_basename +
'_emulator_additional_disabled')
def GetDisabledTests(self):
"""Returns a list of disabled tests.
Returns:
A list of disabled tests obtained from gtest_filter/test_suite_disabled.
"""
disabled_tests = run_tests_helper.GetExpectations(self._GetFilterFileName())
if self._running_on_emulator:
# Append emulator's filter file.
disabled_tests.extend(run_tests_helper.GetExpectations(
self._GetAdditionalEmulatorFilterName()))
return disabled_tests
def UpdateFilter(self, failed_tests):
"""Updates test_suite_disabled file with the new filter (deletes if empty).
If running in Emulator, only the failed tests which are not in the normal
filter returned by _GetFilterFileName() are written to emulator's
additional filter file.
Args:
failed_tests: A sorted list of failed tests.
"""
disabled_tests = []
if not self._running_on_emulator:
filter_file_name = self._GetFilterFileName()
else:
filter_file_name = self._GetAdditionalEmulatorFilterName()
disabled_tests.extend(
run_tests_helper.GetExpectations(self._GetFilterFileName()))
logging.info('About to update emulator\'s additional filter (%s).'
% filter_file_name)
new_failed_tests = []
if failed_tests:
for test in failed_tests:
if test.name not in disabled_tests:
new_failed_tests.append(test.name)
if not new_failed_tests:
if os.path.exists(filter_file_name):
os.unlink(filter_file_name)
return
filter_file = file(filter_file_name, 'w')
if self._running_on_emulator:
filter_file.write('# Addtional list of suppressions from emulator\n')
else:
filter_file.write('# List of suppressions\n')
filter_file.write('# This file was automatically generated by %s\n'
% sys.argv[0])
filter_file.write('\n'.join(sorted(new_failed_tests)))
filter_file.write('\n')
filter_file.close()
def GetDataFilesForTestSuite(self):
"""Returns a list of data files/dirs needed by the test suite."""
# Ideally, we'd just push all test data. However, it has >100MB, and a lot
# of the files are not relevant (some are used for browser_tests, others for
# features not supported, etc..).
if self.test_package.test_suite_basename in ['base_unittests',
'sql_unittests',
'unit_tests']:
test_files = [
'base/data/file_util_unittest',
'base/data/json/bom_feff.json',
'chrome/test/data/download-test1.lib',
'chrome/test/data/extensions/bad_magic.crx',
'chrome/test/data/extensions/good.crx',
'chrome/test/data/extensions/icon1.png',
'chrome/test/data/extensions/icon2.png',
'chrome/test/data/extensions/icon3.png',
'chrome/test/data/extensions/allow_silent_upgrade/',
'chrome/test/data/extensions/app/',
'chrome/test/data/extensions/bad/',
'chrome/test/data/extensions/effective_host_permissions/',
'chrome/test/data/extensions/empty_manifest/',
'chrome/test/data/extensions/good/Extensions/',
'chrome/test/data/extensions/manifest_tests/',
'chrome/test/data/extensions/page_action/',
'chrome/test/data/extensions/permissions/',
'chrome/test/data/extensions/script_and_capture/',
'chrome/test/data/extensions/unpacker/',
'chrome/test/data/bookmarks/',
'chrome/test/data/components/',
'chrome/test/data/extensions/json_schema_test.js',
'chrome/test/data/History/',
'chrome/test/data/json_schema_validator/',
'chrome/test/data/pref_service/',
'chrome/test/data/serializer_nested_test.js',
'chrome/test/data/serializer_test.js',
'chrome/test/data/serializer_test_nowhitespace.js',
'chrome/test/data/top_sites/',
'chrome/test/data/web_app_info/',
'chrome/test/data/web_database',
'chrome/test/data/webui/',
'chrome/test/data/zip',
'chrome/third_party/mock4js/',
'content/browser/gpu/software_rendering_list.json',
'net/data/cache_tests/insert_load1',
'net/data/cache_tests/dirty_entry5',
'net/data/ssl/certificates/',
'ui/base/test/data/data_pack_unittest',
]
if self.test_package.test_suite_basename == 'unit_tests':
test_files += ['chrome/test/data/simple_open_search.xml']
# The following are spell check data. Now only list the data under
# third_party/hunspell_dictionaries which are used by unit tests.
old_cwd = os.getcwd()
os.chdir(constants.CHROME_DIR)
test_files += glob.glob('third_party/hunspell_dictionaries/*.bdic')
os.chdir(old_cwd)
return test_files
elif self.test_package.test_suite_basename == 'net_unittests':
return [
'net/data/cache_tests',
'net/data/filter_unittests',
'net/data/ftp',
'net/data/proxy_resolver_v8_unittest',
'net/data/ssl/certificates',
'net/data/url_request_unittest/',
'net/data/proxy_script_fetcher_unittest'
]
elif self.test_package.test_suite_basename == 'ui_tests':
return [
'chrome/test/data/dromaeo',
'chrome/test/data/json2.js',
'chrome/test/data/sunspider',
'chrome/test/data/v8_benchmark',
'chrome/test/perf/sunspider_uitest.js',
'chrome/test/perf/v8_benchmark_uitest.js',
]
elif self.test_package.test_suite_basename == 'page_cycler_tests':
data = [
'tools/page_cycler',
'data/page_cycler',
]
for d in data:
if not os.path.exists(d):
raise Exception('Page cycler data not found.')
return data
elif self.test_package.test_suite_basename == 'webkit_unit_tests':
return [
'third_party/WebKit/Source/WebKit/chromium/tests/data',
]
elif self.test_package.test_suite_basename == 'content_unittests':
return [
'content/test/data/gpu/webgl_conformance_test_expectations.txt',
'net/data/ssl/certificates/',
'webkit/data/dom_storage/webcore_test_database.localstorage',
'third_party/hyphen/hyph_en_US.dic',
]
elif self.test_package.test_suite_basename == 'media_unittests':
return [
'media/test/data',
]
return []
def LaunchHelperToolsForTestSuite(self):
"""Launches helper tools for the test suite.
Sometimes one test may need to run some helper tools first in order to
successfully complete the test.
"""
if self._TestSuiteRequiresMockTestServer():
self.LaunchChromeTestServerSpawner()
def StripAndCopyFiles(self):
"""Strips and copies the required data files for the test suite."""
self.test_package.StripAndCopyExecutable()
self.test_package.PushDataAndPakFiles()
self.tool.CopyFiles()
test_data = self.GetDataFilesForTestSuite()
if test_data and not self.fast_and_loose:
# Make sure SD card is ready.
self.adb.WaitForSdCardReady(20)
for data in test_data:
self.CopyTestData([data], self.adb.GetExternalStorage())
def RunTestsWithFilter(self):
"""Runs a tests via a small, temporary shell script."""
self.test_package.CreateTestRunnerScript(self._gtest_filter,
self._test_arguments)
self.test_results = self.test_package.RunTestsAndListResults()
def RebaselineTests(self):
"""Runs all available tests, restarting in case of failures."""
if self._gtest_filter:
all_tests = set(self._gtest_filter.split(':'))
else:
all_tests = set(self.test_package.GetAllTests())
failed_results = set()
executed_results = set()
while True:
executed_names = set([f.name for f in executed_results])
self._gtest_filter = ':'.join(all_tests - executed_names)
self.RunTestsWithFilter()
failed_results.update(self.test_results.crashed,
self.test_results.failed)
executed_results.update(self.test_results.crashed,
self.test_results.failed,
self.test_results.ok)
executed_names = set([f.name for f in executed_results])
logging.info('*' * 80)
logging.info(self.device)
logging.info('Executed: ' + str(len(executed_names)) + ' of ' +
str(len(all_tests)))
logging.info('Failed so far: ' + str(len(failed_results)) + ' ' +
str([f.name for f in failed_results]))
logging.info('Remaining: ' + str(len(all_tests - executed_names)) + ' ' +
str(all_tests - executed_names))
logging.info('*' * 80)
if executed_names == all_tests:
break
self.test_results = TestResults.FromRun(
ok=list(executed_results - failed_results),
failed=list(failed_results))
def RunTests(self):
"""Runs all tests (in rebaseline mode, runs each test in isolation).
Returns:
A TestResults object.
"""
if self.test_package.rebaseline:
self.RebaselineTests()
else:
if not self._gtest_filter:
self._gtest_filter = ('-' + ':'.join(self.GetDisabledTests()) + ':' +
':'.join(['*.' + x + '*' for x in
self.test_package.GetDisabledPrefixes()]))
self.RunTestsWithFilter()
return self.test_results
def SetUp(self):
"""Sets up necessary test enviroment for the test suite."""
super(SingleTestRunner, self).SetUp()
self.adb.ClearApplicationState(constants.CHROME_PACKAGE)
if self._performance_test_setup:
self._performance_test_setup.SetUp()
if self.dump_debug_info:
self.dump_debug_info.StartRecordingLog(True)
self.StripAndCopyFiles()
self.LaunchHelperToolsForTestSuite()
self.tool.SetupEnvironment()
def TearDown(self):
"""Cleans up the test enviroment for the test suite."""
self.tool.CleanUpEnvironment()
if self.test_package.cleanup_test_files:
self.adb.RemovePushedFiles()
if self.dump_debug_info:
self.dump_debug_info.StopRecordingLog()
if self._performance_test_setup:
self._performance_test_setup.TearDown()
if self.dump_debug_info:
self.dump_debug_info.ArchiveNewCrashFiles()
super(SingleTestRunner, self).TearDown()

View file

@ -0,0 +1,137 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module containing information about the python-driven tests."""
import logging
import os
import tests_annotations
class TestInfo(object):
"""An object containing and representing a test function, plus metadata."""
def __init__(self, runnable, set_up=None, tear_down=None):
# The actual test function/method.
self.runnable = runnable
# Qualified name of test function/method (e.g. FooModule.testBar).
self.qualified_name = self._GetQualifiedName(runnable)
# setUp and teardown functions, if any.
self.set_up = set_up
self.tear_down = tear_down
def _GetQualifiedName(self, runnable):
"""Helper method to infer a runnable's name and module name.
Many filters and lists presuppose a format of module_name.testMethodName.
To make this easy on everyone, we use some reflection magic to infer this
name automatically.
Args:
runnable: the test method to get the qualified name for
Returns:
qualified name for this runnable, incl. module name and method name.
"""
runnable_name = runnable.__name__
# See also tests_annotations.
module_name = os.path.splitext(
os.path.basename(runnable.__globals__['__file__']))[0]
return '.'.join([module_name, runnable_name])
def __str__(self):
return self.qualified_name
class TestInfoCollection(object):
"""A collection of TestInfo objects which facilitates filtering."""
def __init__(self):
"""Initialize a new TestInfoCollection."""
# Master list of all valid tests.
self.all_tests = []
def AddTests(self, test_infos):
"""Adds a set of tests to this collection.
The user may then retrieve them, optionally according to criteria, via
GetAvailableTests().
Args:
test_infos: a list of TestInfos representing test functions/methods.
"""
self.all_tests = test_infos
def GetAvailableTests(self, annotation, name_filter):
"""Get a collection of TestInfos which match the supplied criteria.
Args:
annotation: annotation which tests must match, if any
name_filter: name filter which tests must match, if any
Returns:
List of available tests.
"""
available_tests = self.all_tests
# Filter out tests which match neither the requested annotation, nor the
# requested name filter, if any.
available_tests = [t for t in available_tests if
self._AnnotationIncludesTest(t, annotation)]
if annotation and len(annotation) == 1 and annotation[0] == 'SmallTest':
tests_without_annotation = [
t for t in self.all_tests if
not tests_annotations.AnnotatedFunctions.GetTestAnnotations(
t.qualified_name)]
test_names = [t.qualified_name for t in tests_without_annotation]
logging.warning('The following tests do not contain any annotation. '
'Assuming "SmallTest":\n%s',
'\n'.join(test_names))
available_tests += tests_without_annotation
available_tests = [t for t in available_tests if
self._NameFilterIncludesTest(t, name_filter)]
return available_tests
def _AnnotationIncludesTest(self, test_info, annotation_filter_list):
"""Checks whether a given test represented by test_info matches annotation.
Args:
test_info: TestInfo object representing the test
annotation_filter_list: list of annotation filters to match (e.g. Smoke)
Returns:
True if no annotation was supplied or the test matches; false otherwise.
"""
if not annotation_filter_list:
return True
for annotation_filter in annotation_filter_list:
filters = annotation_filter.split('=')
if len(filters) == 2:
key = filters[0]
value_list = filters[1].split(',')
for value in value_list:
if tests_annotations.AnnotatedFunctions.IsAnnotated(
key + ':' + value, test_info.qualified_name):
return True
elif tests_annotations.AnnotatedFunctions.IsAnnotated(
annotation_filter, test_info.qualified_name):
return True
return False
def _NameFilterIncludesTest(self, test_info, name_filter):
"""Checks whether a name filter matches a given test_info's method name.
This is a case-sensitive, substring comparison: 'Foo' will match methods
Foo.testBar and Bar.testFoo. 'foo' would not match either.
Args:
test_info: TestInfo object representing the test
name_filter: substring to check for in the qualified name of the test
Returns:
True if no name filter supplied or it matches; False otherwise.
"""
return not name_filter or name_filter in test_info.qualified_name

View file

@ -0,0 +1,143 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Parses options for the instrumentation tests."""
import constants
import optparse
import os
import sys
_SDK_OUT_DIR = os.path.join(constants.CHROME_DIR, 'out')
def AddBuildTypeOption(option_parser):
"""Decorates OptionParser with build type option."""
default_build_type = 'Debug'
if 'BUILDTYPE' in os.environ:
default_build_type = os.environ['BUILDTYPE']
option_parser.add_option('--debug', action='store_const', const='Debug',
dest='build_type', default=default_build_type,
help='If set, run test suites under out/Debug. '
'Default is env var BUILDTYPE or Debug')
option_parser.add_option('--release', action='store_const', const='Release',
dest='build_type',
help='If set, run test suites under out/Release. '
'Default is env var BUILDTYPE or Debug.')
def AddInstallAPKOption(option_parser):
"""Decorates OptionParser with apk option used to install the APK."""
option_parser.add_option('--apk',
help=('The name of the apk containing the '
' application (with the .apk extension).'))
option_parser.add_option('--apk_package',
help=('The package name used by the apk containing '
'the application.'))
def AddTestRunnerOptions(option_parser, default_timeout=60):
"""Decorates OptionParser with options applicable to all tests."""
option_parser.add_option('-t', dest='timeout',
help='Timeout to wait for each test',
type='int',
default=default_timeout)
option_parser.add_option('-c', dest='cleanup_test_files',
help='Cleanup test files on the device after run',
action='store_true')
option_parser.add_option('-v',
'--verbose',
dest='verbose_count',
default=0,
action='count',
help='Verbose level (multiple times for more)')
profilers = ['devicestatsmonitor', 'chrometrace', 'dumpheap', 'smaps',
'traceview']
option_parser.add_option('--profiler', dest='profilers', action='append',
choices=profilers,
help='Profiling tool to run during test. '
'Pass multiple times to run multiple profilers. '
'Available profilers: %s' % profilers)
option_parser.add_option('--tool',
dest='tool',
help='Run the test under a tool '
'(use --tool help to list them)')
AddBuildTypeOption(option_parser)
def AddInstrumentationOptions(option_parser):
"""Decorates OptionParser with instrumentation tests options."""
AddTestRunnerOptions(option_parser)
option_parser.add_option('-w', '--wait_debugger', dest='wait_for_debugger',
action='store_true', help='Wait for debugger.')
option_parser.add_option('-I', dest='install_apk', help='Install APK.',
action='store_true')
option_parser.add_option('-f', '--test_filter',
help='Test filter (if not fully qualified, '
'will run all matches).')
option_parser.add_option('-A', '--annotation', dest='annotation_str',
help=('Run only tests with any of the given '
'annotations. '
'An annotation can be either a key or a '
'key-values pair. '
'A test that has no annotation is '
'considered "SmallTest".'))
option_parser.add_option('-j', '--java_only', action='store_true',
help='Run only the Java tests.')
option_parser.add_option('-p', '--python_only', action='store_true',
help='Run only the Python tests.')
option_parser.add_option('-n', '--run_count', type='int',
dest='number_of_runs', default=1,
help=('How many times to run each test, regardless '
'of the result. (Default is 1)'))
option_parser.add_option('--test-apk', dest='test_apk',
help=('The name of the apk containing the tests '
'(without the .apk extension). For SDK '
'builds, the apk name without the debug '
'suffix(for example, ContentShellTest).'))
option_parser.add_option('--screenshot', dest='screenshot_failures',
action='store_true',
help='Capture screenshots of test failures')
option_parser.add_option('--save-perf-json', action='store_true',
help='Saves the JSON file for each UI Perf test.')
option_parser.add_option('--shard_retries', type=int, default=1,
help=('Number of times to retry each failure when '
'sharding.'))
option_parser.add_option('--official-build', help='Run official build tests.')
option_parser.add_option('--device',
help='Serial number of device we should use.')
option_parser.add_option('--python_test_root',
help='Root of the python-driven tests.')
def ValidateInstrumentationOptions(option_parser, options, args):
"""Validate options/arguments and populate options with defaults."""
if len(args) > 1:
option_parser.print_help(sys.stderr)
option_parser.error('Unknown arguments: %s' % args[1:])
if options.java_only and options.python_only:
option_parser.error('Options java_only (-j) and python_only (-p) '
'are mutually exclusive.')
options.run_java_tests = True
options.run_python_tests = True
if options.java_only:
options.run_python_tests = False
elif options.python_only:
options.run_java_tests = False
# In case of SDK Build, the jars and apks have a -debug suffix.
options.test_apk_path = os.path.join(_SDK_OUT_DIR,
options.build_type,
constants.SDK_BUILD_APKS_DIR,
'%s-debug.apk' % options.test_apk)
options.test_apk_jar_path = os.path.join(_SDK_OUT_DIR,
options.build_type,
constants.SDK_BUILD_TEST_JAVALIB_DIR,
'%s-debug.jar' % options.test_apk)
if options.annotation_str:
options.annotation = options.annotation_str.split()
elif options.test_filter:
options.annotation = []
else:
options.annotation = ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest']

View file

@ -0,0 +1,200 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import re
import os
import constants
from perf_tests_helper import PrintPerfResult
from pylib import pexpect
from test_result import BaseTestResult, TestResults
# TODO(bulach): TestPackage, TestPackageExecutable and
# TestPackageApk are a work in progress related to making the native tests
# run as a NDK-app from an APK rather than a stand-alone executable.
class TestPackage(object):
"""A helper base class for both APK and stand-alone executables.
Args:
adb: ADB interface the tests are using.
device: Device to run the tests.
test_suite: A specific test suite to run, empty to run all.
timeout: Timeout for each test.
rebaseline: Whether or not to run tests in isolation and update the filter.
performance_test: Whether or not performance test(s).
cleanup_test_files: Whether or not to cleanup test files on device.
tool: Name of the Valgrind tool.
dump_debug_info: A debug_info object.
"""
def __init__(self, adb, device, test_suite, timeout, rebaseline,
performance_test, cleanup_test_files, tool, dump_debug_info):
self.adb = adb
self.device = device
self.test_suite_full = test_suite
self.test_suite = os.path.splitext(test_suite)[0]
self.test_suite_basename = self._GetTestSuiteBaseName()
self.test_suite_dirname = os.path.dirname(
self.test_suite.split(self.test_suite_basename)[0])
self.rebaseline = rebaseline
self.performance_test = performance_test
self.cleanup_test_files = cleanup_test_files
self.tool = tool
if timeout == 0:
timeout = 60
# On a VM (e.g. chromium buildbots), this timeout is way too small.
if os.environ.get('BUILDBOT_SLAVENAME'):
timeout = timeout * 2
self.timeout = timeout * self.tool.GetTimeoutScale()
self.dump_debug_info = dump_debug_info
def _BeginGetIOStats(self):
"""Gets I/O statistics before running test.
Return:
I/O stats object.The I/O stats object may be None if the test is not
performance test.
"""
initial_io_stats = None
# Try to get the disk I/O statistics for all performance tests.
if self.performance_test and not self.rebaseline:
initial_io_stats = self.adb.GetIoStats()
return initial_io_stats
def _EndGetIOStats(self, initial_io_stats):
"""Gets I/O statistics after running test and calcuate the I/O delta.
Args:
initial_io_stats: I/O stats object got from _BeginGetIOStats.
Return:
String for formated diso I/O statistics.
"""
disk_io = ''
if self.performance_test and initial_io_stats:
final_io_stats = self.adb.GetIoStats()
for stat in final_io_stats:
disk_io += '\n' + PrintPerfResult(stat, stat,
[final_io_stats[stat] -
initial_io_stats[stat]],
stat.split('_')[1],
print_to_stdout=False)
logging.info(disk_io)
return disk_io
def GetDisabledPrefixes(self):
return ['DISABLED_', 'FLAKY_', 'FAILS_']
def _ParseGTestListTests(self, all_tests):
ret = []
current = ''
disabled_prefixes = self.GetDisabledPrefixes()
for test in all_tests:
if not test:
continue
if test[0] != ' ' and not test.endswith('.'):
# Ignore any lines with unexpected format.
continue
if test[0] != ' ' and test.endswith('.'):
current = test
continue
if 'YOU HAVE' in test:
break
test_name = test[2:]
if not any([test_name.startswith(x) for x in disabled_prefixes]):
ret += [current + test_name]
return ret
def PushDataAndPakFiles(self):
external_storage = self.adb.GetExternalStorage()
if (self.test_suite_basename == 'ui_unittests' or
self.test_suite_basename == 'unit_tests'):
self.adb.PushIfNeeded(
self.test_suite_dirname + '/chrome.pak',
external_storage + '/paks/chrome.pak')
self.adb.PushIfNeeded(
self.test_suite_dirname + '/locales/en-US.pak',
external_storage + '/paks/en-US.pak')
if self.test_suite_basename == 'unit_tests':
self.adb.PushIfNeeded(
self.test_suite_dirname + '/resources.pak',
external_storage + '/paks/resources.pak')
self.adb.PushIfNeeded(
self.test_suite_dirname + '/chrome_100_percent.pak',
external_storage + '/paks/chrome_100_percent.pak')
self.adb.PushIfNeeded(self.test_suite_dirname + '/test_data',
external_storage + '/test_data')
if self.test_suite_basename == 'content_unittests':
self.adb.PushIfNeeded(
self.test_suite_dirname + '/content_resources.pak',
external_storage + '/paks/content_resources.pak')
def _WatchTestOutput(self, p):
"""Watches the test output.
Args:
p: the process generating output as created by pexpect.spawn.
"""
ok_tests = []
failed_tests = []
crashed_tests = []
timed_out = False
overall_fail = False
re_run = re.compile('\[ RUN \] ?(.*)\r\n')
# APK tests rely on the PASSED tag.
re_passed = re.compile('\[ PASSED \] ?(.*)\r\n')
# Signal handlers are installed before starting tests
# to output the CRASHED marker when a crash happens.
re_crash = re.compile('\[ CRASHED \](.*)\r\n')
re_fail = re.compile('\[ FAILED \] ?(.*)\r\n')
re_runner_fail = re.compile('\[ RUNNER_FAILED \] ?(.*)\r\n')
re_ok = re.compile('\[ OK \] ?(.*?) .*\r\n')
io_stats_before = self._BeginGetIOStats()
try:
while True:
found = p.expect([re_run, re_passed, re_runner_fail],
timeout=self.timeout)
if found == 1: # matched PASSED.
break
if found == 2: # RUNNER_FAILED
logging.error('RUNNER_FAILED')
overall_fail = True
break
if self.dump_debug_info:
self.dump_debug_info.TakeScreenshot('_Test_Start_Run_')
full_test_name = p.match.group(1).replace('\r', '')
found = p.expect([re_ok, re_fail, re_crash], timeout=self.timeout)
if found == 0: # re_ok
if full_test_name == p.match.group(1).replace('\r', ''):
ok_tests += [BaseTestResult(full_test_name, p.before)]
continue
if found == 2: # re_crash
crashed_tests += [BaseTestResult(full_test_name, p.before)]
overall_fail = True
break
# The test failed.
failed_tests += [BaseTestResult(full_test_name, p.before)]
except pexpect.EOF:
logging.error('Test terminated - EOF')
except pexpect.TIMEOUT:
logging.error('Test terminated after %d second timeout.',
self.timeout)
timed_out = True
finally:
p.close()
if not self.rebaseline:
ok_tests += self._EndGetIOStats(io_stats_before)
ret_code = self._GetGTestReturnCode()
if ret_code:
failed_tests += [BaseTestResult('gtest exit code: %d' % ret_code,
'pexpect.before: %s'
'\npexpect.after: %s'
% (p.before,
p.after))]
# Create TestResults and return
return TestResults.FromRun(ok=ok_tests, failed=failed_tests,
crashed=crashed_tests, timed_out=timed_out,
overall_fail=overall_fail)

View file

@ -0,0 +1,121 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import shlex
import sys
import tempfile
import time
import android_commands
import constants
from test_package import TestPackage
from pylib import pexpect
class TestPackageApk(TestPackage):
"""A helper class for running APK-based native tests.
Args:
adb: ADB interface the tests are using.
device: Device to run the tests.
test_suite: A specific test suite to run, empty to run all.
timeout: Timeout for each test.
rebaseline: Whether or not to run tests in isolation and update the filter.
performance_test: Whether or not performance test(s).
cleanup_test_files: Whether or not to cleanup test files on device.
tool: Name of the Valgrind tool.
dump_debug_info: A debug_info object.
"""
def __init__(self, adb, device, test_suite, timeout, rebaseline,
performance_test, cleanup_test_files, tool,
dump_debug_info):
TestPackage.__init__(self, adb, device, test_suite, timeout,
rebaseline, performance_test, cleanup_test_files,
tool, dump_debug_info)
def _CreateTestRunnerScript(self, options):
command_line_file = tempfile.NamedTemporaryFile()
# GTest expects argv[0] to be the executable path.
command_line_file.write(self.test_suite_basename + ' ' + options)
command_line_file.flush()
self.adb.PushIfNeeded(command_line_file.name,
constants.TEST_EXECUTABLE_DIR +
'/chrome-native-tests-command-line')
def _GetGTestReturnCode(self):
return None
def _GetFifo(self):
# The test.fifo path is determined by:
# testing/android/java/src/org/chromium/native_test/
# ChromeNativeTestActivity.java and
# testing/android/native_test_launcher.cc
return '/data/data/org.chromium.native_test/files/test.fifo'
def _ClearFifo(self):
self.adb.RunShellCommand('rm -f ' + self._GetFifo())
def _WatchFifo(self, timeout, logfile=None):
for i in range(10):
if self.adb.FileExistsOnDevice(self._GetFifo()):
print 'Fifo created...'
break
time.sleep(i)
else:
raise Exception('Unable to find fifo on device %s ' % self._GetFifo())
args = shlex.split(self.adb.Adb()._target_arg)
args += ['shell', 'cat', self._GetFifo()]
return pexpect.spawn('adb', args, timeout=timeout, logfile=logfile)
def GetAllTests(self):
"""Returns a list of all tests available in the test suite."""
self._CreateTestRunnerScript('--gtest_list_tests')
try:
self.tool.SetupEnvironment()
# Clear and start monitoring logcat.
self._ClearFifo()
self.adb.RunShellCommand(
'am start -n '
'org.chromium.native_test/'
'org.chromium.native_test.ChromeNativeTestActivity')
# Wait for native test to complete.
p = self._WatchFifo(timeout=30 * self.tool.GetTimeoutScale())
p.expect("<<ScopedMainEntryLogger")
p.close()
finally:
self.tool.CleanUpEnvironment()
# We need to strip the trailing newline.
content = [line.rstrip() for line in p.before.splitlines()]
ret = self._ParseGTestListTests(content)
return ret
def CreateTestRunnerScript(self, gtest_filter, test_arguments):
self._CreateTestRunnerScript('--gtest_filter=%s %s' % (gtest_filter,
test_arguments))
def RunTestsAndListResults(self):
try:
self.tool.SetupEnvironment()
self._ClearFifo()
self.adb.RunShellCommand(
'am start -n '
'org.chromium.native_test/'
'org.chromium.native_test.ChromeNativeTestActivity')
finally:
self.tool.CleanUpEnvironment()
logfile = android_commands.NewLineNormalizer(sys.stdout)
return self._WatchTestOutput(self._WatchFifo(timeout=10, logfile=logfile))
def StripAndCopyExecutable(self):
# Always uninstall the previous one (by activity name); we don't
# know what was embedded in it.
self.adb.ManagedInstall(self.test_suite_full, False,
package_name='org.chromium.native_test')
def _GetTestSuiteBaseName(self):
"""Returns the base name of the test suite."""
# APK test suite names end with '-debug.apk'
return os.path.basename(self.test_suite).rsplit('-debug', 1)[0]

View file

@ -0,0 +1,167 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import shutil
import sys
import tempfile
import cmd_helper
import constants
from test_package import TestPackage
from pylib import pexpect
class TestPackageExecutable(TestPackage):
"""A helper class for running stand-alone executables."""
_TEST_RUNNER_RET_VAL_FILE = 'gtest_retval'
def __init__(self, adb, device, test_suite, timeout, rebaseline,
performance_test, cleanup_test_files, tool, dump_debug_info,
symbols_dir=None):
"""
Args:
adb: ADB interface the tests are using.
device: Device to run the tests.
test_suite: A specific test suite to run, empty to run all.
timeout: Timeout for each test.
rebaseline: Whether or not to run tests in isolation and update the
filter.
performance_test: Whether or not performance test(s).
cleanup_test_files: Whether or not to cleanup test files on device.
tool: Name of the Valgrind tool.
dump_debug_info: A debug_info object.
symbols_dir: Directory to put the stripped binaries.
"""
TestPackage.__init__(self, adb, device, test_suite, timeout,
rebaseline, performance_test, cleanup_test_files,
tool, dump_debug_info)
self.symbols_dir = symbols_dir
def _GetGTestReturnCode(self):
ret = None
ret_code = 1 # Assume failure if we can't find it
ret_code_file = tempfile.NamedTemporaryFile()
try:
if not self.adb.Adb().Pull(
self.adb.GetExternalStorage() + '/' +
TestPackageExecutable._TEST_RUNNER_RET_VAL_FILE,
ret_code_file.name):
logging.critical('Unable to pull gtest ret val file %s',
ret_code_file.name)
raise ValueError
ret_code = file(ret_code_file.name).read()
ret = int(ret_code)
except ValueError:
logging.critical('Error reading gtest ret val file %s [%s]',
ret_code_file.name, ret_code)
ret = 1
return ret
def _AddNativeCoverageExports(self):
# export GCOV_PREFIX set the path for native coverage results
# export GCOV_PREFIX_STRIP indicates how many initial directory
# names to strip off the hardwired absolute paths.
# This value is calculated in buildbot.sh and
# depends on where the tree is built.
# Ex: /usr/local/google/code/chrome will become
# /code/chrome if GCOV_PREFIX_STRIP=3
try:
depth = os.environ['NATIVE_COVERAGE_DEPTH_STRIP']
except KeyError:
logging.info('NATIVE_COVERAGE_DEPTH_STRIP is not defined: '
'No native coverage.')
return ''
export_string = ('export GCOV_PREFIX="%s/gcov"\n' %
self.adb.GetExternalStorage())
export_string += 'export GCOV_PREFIX_STRIP=%s\n' % depth
return export_string
def GetAllTests(self):
"""Returns a list of all tests available in the test suite."""
all_tests = self.adb.RunShellCommand(
'%s %s/%s --gtest_list_tests' %
(self.tool.GetTestWrapper(),
constants.TEST_EXECUTABLE_DIR,
self.test_suite_basename))
return self._ParseGTestListTests(all_tests)
def CreateTestRunnerScript(self, gtest_filter, test_arguments):
"""Creates a test runner script and pushes to the device.
Args:
gtest_filter: A gtest_filter flag.
test_arguments: Additional arguments to pass to the test binary.
"""
tool_wrapper = self.tool.GetTestWrapper()
sh_script_file = tempfile.NamedTemporaryFile()
# We need to capture the exit status from the script since adb shell won't
# propagate to us.
sh_script_file.write('cd %s\n'
'%s'
'%s %s/%s --gtest_filter=%s %s\n'
'echo $? > %s' %
(constants.TEST_EXECUTABLE_DIR,
self._AddNativeCoverageExports(),
tool_wrapper, constants.TEST_EXECUTABLE_DIR,
self.test_suite_basename,
gtest_filter, test_arguments,
TestPackageExecutable._TEST_RUNNER_RET_VAL_FILE))
sh_script_file.flush()
cmd_helper.RunCmd(['chmod', '+x', sh_script_file.name])
self.adb.PushIfNeeded(
sh_script_file.name,
constants.TEST_EXECUTABLE_DIR + '/chrome_test_runner.sh')
logging.info('Conents of the test runner script: ')
for line in open(sh_script_file.name).readlines():
logging.info(' ' + line.rstrip())
def RunTestsAndListResults(self):
"""Runs all the tests and checks for failures.
Returns:
A TestResults object.
"""
args = ['adb', '-s', self.device, 'shell', 'sh',
constants.TEST_EXECUTABLE_DIR + '/chrome_test_runner.sh']
logging.info(args)
p = pexpect.spawn(args[0], args[1:], logfile=sys.stdout)
return self._WatchTestOutput(p)
def StripAndCopyExecutable(self):
"""Strips and copies the executable to the device."""
if self.tool.NeedsDebugInfo():
target_name = self.test_suite
else:
target_name = self.test_suite + '_' + self.device + '_stripped'
should_strip = True
if os.path.isfile(target_name):
logging.info('Found target file %s' % target_name)
target_mtime = os.stat(target_name).st_mtime
source_mtime = os.stat(self.test_suite).st_mtime
if target_mtime > source_mtime:
logging.info('Target mtime (%d) is newer than source (%d), assuming '
'no change.' % (target_mtime, source_mtime))
should_strip = False
if should_strip:
logging.info('Did not find up-to-date stripped binary. Generating a '
'new one (%s).' % target_name)
# Whenever we generate a stripped binary, copy to the symbols dir. If we
# aren't stripping a new binary, assume it's there.
if self.symbols_dir:
if not os.path.exists(self.symbols_dir):
os.makedirs(self.symbols_dir)
shutil.copy(self.test_suite, self.symbols_dir)
strip = os.environ['STRIP']
cmd_helper.RunCmd([strip, self.test_suite, '-o', target_name])
test_binary = constants.TEST_EXECUTABLE_DIR + '/' + self.test_suite_basename
self.adb.PushIfNeeded(target_name, test_binary)
def _GetTestSuiteBaseName(self):
"""Returns the base name of the test suite."""
return os.path.basename(self.test_suite)

View file

@ -0,0 +1,193 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import logging
import os
import time
import traceback
import buildbot_report
import constants
class BaseTestResult(object):
"""A single result from a unit test."""
def __init__(self, name, log):
self.name = name
self.log = log.replace('\r', '')
class SingleTestResult(BaseTestResult):
"""Result information for a single test.
Args:
full_name: Full name of the test.
start_date: Date in milliseconds when the test began running.
dur: Duration of the test run in milliseconds.
log: An optional string listing any errors.
"""
def __init__(self, full_name, start_date, dur, log=''):
BaseTestResult.__init__(self, full_name, log)
name_pieces = full_name.rsplit('#')
if len(name_pieces) > 1:
self.test_name = name_pieces[1]
self.class_name = name_pieces[0]
else:
self.class_name = full_name
self.test_name = full_name
self.start_date = start_date
self.dur = dur
class TestResults(object):
"""Results of a test run."""
def __init__(self):
self.ok = []
self.failed = []
self.crashed = []
self.unknown = []
self.timed_out = False
self.overall_fail = False
@staticmethod
def FromRun(ok=None, failed=None, crashed=None, timed_out=False,
overall_fail=False):
ret = TestResults()
ret.ok = ok or []
ret.failed = failed or []
ret.crashed = crashed or []
ret.timed_out = timed_out
ret.overall_fail = overall_fail
return ret
@staticmethod
def FromTestResults(results):
"""Combines a list of results in a single TestResults object."""
ret = TestResults()
for t in results:
ret.ok += t.ok
ret.failed += t.failed
ret.crashed += t.crashed
ret.unknown += t.unknown
if t.timed_out:
ret.timed_out = True
if t.overall_fail:
ret.overall_fail = True
return ret
@staticmethod
def FromPythonException(test_name, start_date_ms, exc_info):
"""Constructs a TestResults with exception information for the given test.
Args:
test_name: name of the test which raised an exception.
start_date_ms: the starting time for the test.
exc_info: exception info, ostensibly from sys.exc_info().
Returns:
A TestResults object with a SingleTestResult in the failed list.
"""
exc_type, exc_value, exc_traceback = exc_info
trace_info = ''.join(traceback.format_exception(exc_type, exc_value,
exc_traceback))
log_msg = 'Exception:\n' + trace_info
duration_ms = (int(time.time()) * 1000) - start_date_ms
exc_result = SingleTestResult(
full_name='PythonWrapper#' + test_name,
start_date=start_date_ms,
dur=duration_ms,
log=(str(exc_type) + ' ' + log_msg))
results = TestResults()
results.failed.append(exc_result)
return results
def _Log(self, sorted_list):
for t in sorted_list:
logging.critical(t.name)
if t.log:
logging.critical(t.log)
def GetAllBroken(self):
"""Returns the all broken tests including failed, crashed, unknown."""
return self.failed + self.crashed + self.unknown
def LogFull(self, test_group, test_suite, build_type):
"""Output broken test logs, summarize in a log file and the test output."""
# Output all broken tests or 'passed' if none broken.
logging.critical('*' * 80)
logging.critical('Final result')
if self.failed:
logging.critical('Failed:')
self._Log(sorted(self.failed))
if self.crashed:
logging.critical('Crashed:')
self._Log(sorted(self.crashed))
if self.unknown:
logging.critical('Unknown:')
self._Log(sorted(self.unknown))
if not self.GetAllBroken():
logging.critical('Passed')
logging.critical('*' * 80)
# Summarize in a log file, if tests are running on bots.
if test_group and test_suite and os.environ.get('BUILDBOT_BUILDERNAME'):
log_file_path = os.path.join(constants.CHROME_DIR, 'out',
build_type, 'test_logs')
if not os.path.exists(log_file_path):
os.mkdir(log_file_path)
full_file_name = os.path.join(log_file_path, test_group)
if not os.path.exists(full_file_name):
with open(full_file_name, 'w') as log_file:
print >> log_file, '\n%s results for %s build %s:' % (
test_group, os.environ.get('BUILDBOT_BUILDERNAME'),
os.environ.get('BUILDBOT_BUILDNUMBER'))
log_contents = [' %s result : %d tests ran' % (test_suite,
len(self.ok) +
len(self.failed) +
len(self.crashed) +
len(self.unknown))]
content_pairs = [('passed', len(self.ok)), ('failed', len(self.failed)),
('crashed', len(self.crashed))]
for (result, count) in content_pairs:
if count:
log_contents.append(', %d tests %s' % (count, result))
with open(full_file_name, 'a') as log_file:
print >> log_file, ''.join(log_contents)
content = {'test_group': test_group,
'ok': [t.name for t in self.ok],
'failed': [t.name for t in self.failed],
'crashed': [t.name for t in self.failed],
'unknown': [t.name for t in self.unknown],}
with open(os.path.join(log_file_path, 'results.json'), 'a') as json_file:
print >> json_file, json.dumps(content)
# Summarize in the test output.
summary_string = 'Summary:\n'
summary_string += 'RAN=%d\n' % (len(self.ok) + len(self.failed) +
len(self.crashed) + len(self.unknown))
summary_string += 'PASSED=%d\n' % (len(self.ok))
summary_string += 'FAILED=%d %s\n' % (len(self.failed),
[t.name for t in self.failed])
summary_string += 'CRASHED=%d %s\n' % (len(self.crashed),
[t.name for t in self.crashed])
summary_string += 'UNKNOWN=%d %s\n' % (len(self.unknown),
[t.name for t in self.unknown])
logging.critical(summary_string)
return summary_string
def PrintAnnotation(self):
"""Print buildbot annotations for test results."""
if self.timed_out:
buildbot_report.PrintWarning()
elif self.failed or self.crashed or self.overall_fail:
buildbot_report.PrintError()
else:
print 'Step success!' # No annotation needed

View file

@ -0,0 +1,89 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Annotations for python-driven tests."""
import os
class AnnotatedFunctions(object):
"""A container for annotated methods."""
_ANNOTATED = {}
@staticmethod
def _AddFunction(annotation, function):
"""Adds an annotated to function to our container.
Args:
annotation: the annotation string.
function: the function.
Returns:
The function passed in.
"""
module_name = os.path.splitext(os.path.basename(
function.__globals__['__file__']))[0]
qualified_function_name = '.'.join([module_name, function.func_name])
function_list = AnnotatedFunctions._ANNOTATED.get(annotation, [])
function_list.append(qualified_function_name)
AnnotatedFunctions._ANNOTATED[annotation] = function_list
return function
@staticmethod
def IsAnnotated(annotation, qualified_function_name):
"""True if function name (module.function) contains the annotation.
Args:
annotation: the annotation string.
qualified_function_name: the qualified function name.
Returns:
True if module.function contains the annotation.
"""
return qualified_function_name in AnnotatedFunctions._ANNOTATED.get(
annotation, [])
@staticmethod
def GetTestAnnotations(qualified_function_name):
"""Returns a list containing all annotations for the given function.
Args:
qualified_function_name: the qualified function name.
Returns:
List of all annotations for this function.
"""
return [annotation
for annotation, tests in AnnotatedFunctions._ANNOTATED.iteritems()
if qualified_function_name in tests]
# The following functions are annotations used for the python driven tests.
def Smoke(function):
return AnnotatedFunctions._AddFunction('Smoke', function)
def SmallTest(function):
return AnnotatedFunctions._AddFunction('SmallTest', function)
def MediumTest(function):
return AnnotatedFunctions._AddFunction('MediumTest', function)
def LargeTest(function):
return AnnotatedFunctions._AddFunction('LargeTest', function)
def FlakyTest(function):
return AnnotatedFunctions._AddFunction('FlakyTest', function)
def DisabledTest(function):
return AnnotatedFunctions._AddFunction('DisabledTest', function)
def Feature(feature_list):
def _AddFeatures(function):
for feature in feature_list:
AnnotatedFunctions._AddFunction('Feature' + feature, function)
return AnnotatedFunctions._AddFunction('Feature', function)
return _AddFeatures

View file

@ -0,0 +1,255 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Classes in this file define additional actions that need to be taken to run a
test under some kind of runtime error detection tool.
The interface is intended to be used as follows.
1. For tests that simply run a native process (i.e. no activity is spawned):
Call tool.CopyFiles().
Prepend test command line with tool.GetTestWrapper().
2. For tests that spawn an activity:
Call tool.CopyFiles().
Call tool.SetupEnvironment().
Run the test as usual.
Call tool.CleanUpEnvironment().
"""
import os.path
import sys
from constants import CHROME_DIR
def SetChromeTimeoutScale(adb, scale):
"""Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale."""
path = '/data/local/tmp/chrome_timeout_scale'
if not scale or scale == 1.0:
# Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0
adb.RunShellCommand('rm %s' % path)
else:
adb.SetFileContents(path, '%f' % scale)
class BaseTool(object):
"""A tool that does nothing."""
def GetTestWrapper(self):
"""Returns a string that is to be prepended to the test command line."""
return ''
def GetUtilWrapper(self):
"""Returns the wrapper name for the utilities.
Returns:
A string that is to be prepended to the command line of utility
processes (forwarder, etc.).
"""
return ''
def CopyFiles(self):
"""Copies tool-specific files to the device, create directories, etc."""
pass
def SetupEnvironment(self):
"""Sets up the system environment for a test.
This is a good place to set system properties.
"""
pass
def CleanUpEnvironment(self):
"""Cleans up environment."""
pass
def GetTimeoutScale(self):
"""Returns a multiplier that should be applied to timeout values."""
return 1.0
def NeedsDebugInfo(self):
"""Whether this tool requires debug info.
Returns:
True if this tool can not work with stripped binaries.
"""
return False
class AddressSanitizerTool(BaseTool):
"""AddressSanitizer tool."""
WRAPPER_PATH = '/system/bin/asanwrapper'
def __init__(self, adb):
self._adb = adb
self._wrap_properties = ['wrap.com.google.android.apps.ch',
'wrap.org.chromium.native_test']
def CopyFiles(self):
"""Copies ASan tools to the device."""
files = ['system/lib/libasan_preload.so',
'system/bin/asanwrapper',
'system/bin/asan/app_process',
'system/bin/linker']
android_product_out = os.environ['ANDROID_PRODUCT_OUT']
self._adb.MakeSystemFolderWritable()
for f in files:
self._adb.PushIfNeeded(os.path.join(android_product_out, f),
os.path.join('/', f))
def GetTestWrapper(self):
return AddressSanitizerTool.WRAPPER_PATH
def GetUtilWrapper(self):
"""Returns the wrapper for utilities, such as forwarder.
AddressSanitizer wrapper must be added to all instrumented binaries,
including forwarder and the like. This can be removed if such binaries
were built without instrumentation. """
return AddressSanitizerTool.WRAPPER_PATH
def SetupEnvironment(self):
for prop in self._wrap_properties:
self._adb.RunShellCommand('setprop %s "logwrapper %s"' % (
prop, self.GetTestWrapper()))
SetChromeTimeoutScale(self._adb, self.GetTimeoutScale())
def CleanUpEnvironment(self):
for prop in self._wrap_properties:
self._adb.RunShellCommand('setprop %s ""' % (prop,))
SetChromeTimeoutScale(self._adb, None)
def GetTimeoutScale(self):
# Very slow startup.
return 20.0
class ValgrindTool(BaseTool):
"""Base abstract class for Valgrind tools."""
VG_DIR = '/data/local/tmp/valgrind'
VGLOGS_DIR = '/data/local/tmp/vglogs'
def __init__(self, adb):
self._adb = adb
# exactly 31 chars, SystemProperties::PROP_NAME_MAX
self._wrap_properties = ['wrap.com.google.android.apps.ch',
'wrap.org.chromium.native_test']
def CopyFiles(self):
"""Copies Valgrind tools to the device."""
self._adb.RunShellCommand('rm -r %s; mkdir %s' %
(ValgrindTool.VG_DIR, ValgrindTool.VG_DIR))
self._adb.RunShellCommand('rm -r %s; mkdir %s' %
(ValgrindTool.VGLOGS_DIR,
ValgrindTool.VGLOGS_DIR))
files = self.GetFilesForTool()
for f in files:
self._adb.PushIfNeeded(os.path.join(CHROME_DIR, f),
os.path.join(ValgrindTool.VG_DIR,
os.path.basename(f)))
def SetupEnvironment(self):
"""Sets up device environment."""
self._adb.RunShellCommand('chmod 777 /data/local/tmp')
for prop in self._wrap_properties:
self._adb.RunShellCommand('setprop %s "logwrapper %s"' % (
prop, self.GetTestWrapper()))
SetChromeTimeoutScale(self._adb, self.GetTimeoutScale())
def CleanUpEnvironment(self):
"""Cleans up device environment."""
for prop in self._wrap_properties:
self._adb.RunShellCommand('setprop %s ""' % (prop,))
SetChromeTimeoutScale(self._adb, None)
def GetFilesForTool(self):
"""Returns a list of file names for the tool."""
raise NotImplementedError()
def NeedsDebugInfo(self):
"""Whether this tool requires debug info.
Returns:
True if this tool can not work with stripped binaries.
"""
return True
class MemcheckTool(ValgrindTool):
"""Memcheck tool."""
def __init__(self, adb):
super(MemcheckTool, self).__init__(adb)
def GetFilesForTool(self):
"""Returns a list of file names for the tool."""
return ['tools/valgrind/android/vg-chrome-wrapper.sh',
'tools/valgrind/memcheck/suppressions.txt',
'tools/valgrind/memcheck/suppressions_android.txt']
def GetTestWrapper(self):
"""Returns a string that is to be prepended to the test command line."""
return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper.sh'
def GetTimeoutScale(self):
"""Returns a multiplier that should be applied to timeout values."""
return 30
class TSanTool(ValgrindTool):
"""ThreadSanitizer tool. See http://code.google.com/p/data-race-test ."""
def __init__(self, adb):
super(TSanTool, self).__init__(adb)
def GetFilesForTool(self):
"""Returns a list of file names for the tool."""
return ['tools/valgrind/android/vg-chrome-wrapper-tsan.sh',
'tools/valgrind/tsan/suppressions.txt',
'tools/valgrind/tsan/suppressions_android.txt',
'tools/valgrind/tsan/ignores.txt']
def GetTestWrapper(self):
"""Returns a string that is to be prepended to the test command line."""
return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper-tsan.sh'
def GetTimeoutScale(self):
"""Returns a multiplier that should be applied to timeout values."""
return 30.0
TOOL_REGISTRY = {
'memcheck': lambda x: MemcheckTool(x),
'memcheck-renderer': lambda x: MemcheckTool(x),
'tsan': lambda x: TSanTool(x),
'tsan-renderer': lambda x: TSanTool(x),
'asan': lambda x: AddressSanitizerTool(x),
}
def CreateTool(tool_name, adb):
"""Creates a tool with the specified tool name.
Args:
tool_name: Name of the tool to create.
adb: ADB interface the tool will use.
Returns:
A tool for the specified tool_name.
"""
if not tool_name:
return BaseTool()
ctor = TOOL_REGISTRY.get(tool_name)
if ctor:
return ctor(adb)
else:
print 'Unknown tool %s, available tools: %s' % (
tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
sys.exit(1)

View file

@ -0,0 +1,92 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs both the Python and Java tests."""
import optparse
import sys
import time
from pylib import apk_info
from pylib import buildbot_report
from pylib import ports
from pylib import run_java_tests
from pylib import run_python_tests
from pylib import run_tests_helper
from pylib import test_options_parser
from pylib.test_result import TestResults
def SummarizeResults(java_results, python_results, annotation, build_type):
"""Summarize the results from the various test types.
Args:
java_results: a TestResults object with java test case results.
python_results: a TestResults object with python test case results.
annotation: the annotation used for these results.
build_type: 'Release' or 'Debug'.
Returns:
A tuple (all_results, summary_string, num_failing)
"""
all_results = TestResults.FromTestResults([java_results, python_results])
summary_string = all_results.LogFull('Instrumentation', annotation,
build_type)
num_failing = (len(all_results.failed) + len(all_results.crashed) +
len(all_results.unknown))
return all_results, summary_string, num_failing
def DispatchInstrumentationTests(options):
"""Dispatches the Java and Python instrumentation tests, sharding if possible.
Uses the logging module to print the combined final results and
summary of the Java and Python tests. If the java_only option is set, only
the Java tests run. If the python_only option is set, only the python tests
run. If neither are set, run both Java and Python tests.
Args:
options: command-line options for running the Java and Python tests.
Returns:
An integer representing the number of failing tests.
"""
# Reset the test port allocation. It's important to do it before starting
# to dispatch any tests.
if not ports.ResetTestServerPortAllocation():
raise Exception('Failed to reset test server port.')
start_date = int(time.time() * 1000)
java_results = TestResults()
python_results = TestResults()
if options.run_java_tests:
java_results = run_java_tests.DispatchJavaTests(
options,
[apk_info.ApkInfo(options.test_apk_path, options.test_apk_jar_path)])
if options.run_python_tests:
python_results = run_python_tests.DispatchPythonTests(options)
all_results, summary_string, num_failing = SummarizeResults(
java_results, python_results, options.annotation, options.build_type)
return num_failing
def main(argv):
option_parser = optparse.OptionParser()
test_options_parser.AddInstrumentationOptions(option_parser)
options, args = option_parser.parse_args(argv)
test_options_parser.ValidateInstrumentationOptions(option_parser, options,
args)
run_tests_helper.SetLogLevel(options.verbose_count)
buildbot_report.PrintNamedStep(
'Instrumentation tests: %s - %s' % (', '.join(options.annotation),
options.test_apk))
return DispatchInstrumentationTests(options)
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -0,0 +1,450 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs all the native unit tests.
1. Copy over test binary to /data/local on device.
2. Resources: chrome/unit_tests requires resources (chrome.pak and en-US.pak)
to be deployed to the device. We use the device's $EXTERNAL_STORAGE as the
base dir (which maps to Context.getExternalFilesDir()).
3. Environment:
3.1. chrome/unit_tests requires (via chrome_paths.cc) a directory named:
$EXTERNAL_STORAGE + /chrome/test/data
3.2. page_cycler_tests have following requirements,
3.2.1 the following data on host:
<chrome_src_dir>/tools/page_cycler
<chrome_src_dir>/data/page_cycler
3.2.2. two data directories to store above test data on device named:
$EXTERNAL_STORAGE + /tools/ (for database perf test)
$EXTERNAL_STORAGE + /data/ (for other perf tests)
3.2.3. a http server to serve http perf tests.
The http root is host's <chrome_src_dir>/data/page_cycler/, port 8000.
3.2.4 a tool named forwarder is also required to run on device to
forward the http request/response between host and device.
3.2.5 Chrome is installed on device.
4. Run the binary in the device and stream the log to the host.
4.1. Optionally, filter specific tests.
4.2. Optionally, rebaseline: run the available tests and update the
suppressions file for failures.
4.3. If we're running a single test suite and we have multiple devices
connected, we'll shard the tests.
5. Clean up the device.
Suppressions:
Individual tests in a test binary can be suppressed by listing it in
the gtest_filter directory in a file of the same name as the test binary,
one test per line. Here is an example:
$ cat gtest_filter/base_unittests_disabled
DataPackTest.Load
ReadOnlyFileUtilTest.ContentsEqual
This file is generated by the tests running on devices. If running on emulator,
additonal filter file which lists the tests only failed in emulator will be
loaded. We don't care about the rare testcases which succeeded on emuatlor, but
failed on device.
"""
import fnmatch
import logging
import optparse
import os
import signal
import subprocess
import sys
import time
from pylib import android_commands
from pylib.base_test_sharder import BaseTestSharder
from pylib import buildbot_report
from pylib import constants
from pylib import debug_info
import emulator
from pylib import ports
from pylib import run_tests_helper
from pylib import test_options_parser
from pylib.single_test_runner import SingleTestRunner
from pylib.test_result import BaseTestResult, TestResults
_TEST_SUITES = ['base_unittests',
'content_unittests',
'gpu_unittests',
'ipc_tests',
'media_unittests',
'net_unittests',
'sql_unittests',
'sync_unit_tests',
'ui_unittests',
'unit_tests',
]
def TestSuiteDir(build_type):
"""Return the base directory of test suites."""
return os.path.abspath(os.path.join(constants.CHROME_DIR, 'out', build_type))
def FullyQualifiedTestSuites(exe, option_test_suite, build_type):
"""Return a fully qualified list
Args:
exe: if True, use the executable-based test runner.
option_test_suite: the test_suite specified as an option.
build_type: 'Release' or 'Debug'.
"""
test_suite_dir = TestSuiteDir(build_type)
if option_test_suite:
all_test_suites = [option_test_suite]
else:
all_test_suites = _TEST_SUITES
if exe:
qualified_test_suites = [os.path.join(test_suite_dir, t)
for t in all_test_suites]
else:
# out/(Debug|Release)/$SUITE_apk/$SUITE-debug.apk
qualified_test_suites = [os.path.join(test_suite_dir,
t + '_apk',
t + '-debug.apk')
for t in all_test_suites]
for t, q in zip(all_test_suites, qualified_test_suites):
if not os.path.exists(q):
logging.critical('Test suite %s not found in %s.\n'
'Supported test suites:\n %s\n'
'Ensure it has been built.\n',
t, q, _TEST_SUITES)
return []
return qualified_test_suites
class TimeProfile(object):
"""Class for simple profiling of action, with logging of cost."""
def __init__(self, description):
self._description = description
self.Start()
def Start(self):
self._starttime = time.time()
def Stop(self):
"""Stop profiling and dump a log."""
if self._starttime:
stoptime = time.time()
logging.info('%fsec to perform %s',
stoptime - self._starttime, self._description)
self._starttime = None
class Xvfb(object):
"""Class to start and stop Xvfb if relevant. Nop if not Linux."""
def __init__(self):
self._pid = 0
def _IsLinux(self):
"""Return True if on Linux; else False."""
return sys.platform.startswith('linux')
def Start(self):
"""Start Xvfb and set an appropriate DISPLAY environment. Linux only.
Copied from tools/code_coverage/coverage_posix.py
"""
if not self._IsLinux():
return
proc = subprocess.Popen(['Xvfb', ':9', '-screen', '0', '1024x768x24',
'-ac'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self._pid = proc.pid
if not self._pid:
raise Exception('Could not start Xvfb')
os.environ['DISPLAY'] = ':9'
# Now confirm, giving a chance for it to start if needed.
for _ in range(10):
proc = subprocess.Popen('xdpyinfo >/dev/null', shell=True)
_, retcode = os.waitpid(proc.pid, 0)
if retcode == 0:
break
time.sleep(0.25)
if retcode != 0:
raise Exception('Could not confirm Xvfb happiness')
def Stop(self):
"""Stop Xvfb if needed. Linux only."""
if self._pid:
try:
os.kill(self._pid, signal.SIGKILL)
except:
pass
del os.environ['DISPLAY']
self._pid = 0
class TestSharder(BaseTestSharder):
"""Responsible for sharding the tests on the connected devices."""
def __init__(self, attached_devices, test_suite, gtest_filter,
test_arguments, timeout, rebaseline, performance_test,
cleanup_test_files, tool, log_dump_name, fast_and_loose,
build_type):
BaseTestSharder.__init__(self, attached_devices)
self.test_suite = test_suite
self.test_suite_basename = os.path.basename(test_suite)
self.gtest_filter = gtest_filter or ''
self.test_arguments = test_arguments
self.timeout = timeout
self.rebaseline = rebaseline
self.performance_test = performance_test
self.cleanup_test_files = cleanup_test_files
self.tool = tool
self.log_dump_name = log_dump_name
self.fast_and_loose = fast_and_loose
self.build_type = build_type
test = SingleTestRunner(self.attached_devices[0], test_suite, gtest_filter,
test_arguments, timeout, rebaseline,
performance_test, cleanup_test_files, tool, 0,
not not self.log_dump_name, fast_and_loose,
build_type)
self.tests = []
if not self.gtest_filter:
# No filter has been specified, let's add all tests then.
# The executable/apk needs to be copied before we can call GetAllTests.
test.test_package.StripAndCopyExecutable()
all_tests = test.test_package.GetAllTests()
if not rebaseline:
disabled_list = test.GetDisabledTests()
# Only includes tests that do not have any match in the disabled list.
all_tests = filter(lambda t:
not any([fnmatch.fnmatch(t, disabled_pattern)
for disabled_pattern in disabled_list]),
all_tests)
self.tests = all_tests
def CreateShardedTestRunner(self, device, index):
"""Creates a suite-specific test runner.
Args:
device: Device serial where this shard will run.
index: Index of this device in the pool.
Returns:
A SingleTestRunner object.
"""
device_num = len(self.attached_devices)
shard_size = (len(self.tests) + device_num - 1) / device_num
shard_test_list = self.tests[index * shard_size : (index + 1) * shard_size]
test_filter = ':'.join(shard_test_list) + self.gtest_filter
return SingleTestRunner(device, self.test_suite,
test_filter, self.test_arguments, self.timeout,
self.rebaseline, self.performance_test,
self.cleanup_test_files, self.tool, index,
not not self.log_dump_name, self.fast_and_loose,
self.build_type)
def OnTestsCompleted(self, test_runners, test_results):
"""Notifies that we completed the tests."""
test_results.LogFull('Unit test', os.path.basename(self.test_suite),
self.build_type)
test_results.PrintAnnotation()
if test_results.failed and self.rebaseline:
test_runners[0].UpdateFilter(test_results.failed)
if self.log_dump_name:
# Zip all debug info outputs into a file named by log_dump_name.
debug_info.GTestDebugInfo.ZipAndCleanResults(
os.path.join(TestSuiteDir(self.build_type), 'debug_info_dumps'),
self.log_dump_name)
def _RunATestSuite(options):
"""Run a single test suite.
Helper for Dispatch() to allow stop/restart of the emulator across
test bundles. If using the emulator, we start it on entry and stop
it on exit.
Args:
options: options for running the tests.
Returns:
0 if successful, number of failing tests otherwise.
"""
step_name = os.path.basename(options.test_suite).replace('-debug.apk', '')
buildbot_report.PrintNamedStep(step_name)
attached_devices = []
buildbot_emulators = []
if options.use_emulator:
for n in range(options.emulator_count):
t = TimeProfile('Emulator launch %d' % n)
avd_name = None
if n > 0:
# Creates a temporary AVD for the extra emulators.
avd_name = 'run_tests_avd_%d' % n
buildbot_emulator = emulator.Emulator(avd_name, options.fast_and_loose)
buildbot_emulator.Launch(kill_all_emulators=n == 0)
t.Stop()
buildbot_emulators.append(buildbot_emulator)
attached_devices.append(buildbot_emulator.device)
# Wait for all emulators to boot completed.
map(lambda buildbot_emulator: buildbot_emulator.ConfirmLaunch(True),
buildbot_emulators)
elif options.test_device:
attached_devices = [options.test_device]
else:
attached_devices = android_commands.GetAttachedDevices()
if not attached_devices:
logging.critical('A device must be attached and online.')
buildbot_report.PrintError()
return 1
# Reset the test port allocation. It's important to do it before starting
# to dispatch any tests.
if not ports.ResetTestServerPortAllocation():
raise Exception('Failed to reset test server port.')
if options.performance_test or options.gtest_filter:
# These configuration can't be split in multiple devices.
attached_devices = [attached_devices[0]]
sharder = TestSharder(attached_devices, options.test_suite,
options.gtest_filter, options.test_arguments,
options.timeout, options.rebaseline,
options.performance_test,
options.cleanup_test_files, options.tool,
options.log_dump, options.fast_and_loose,
options.build_type)
test_results = sharder.RunShardedTests()
for buildbot_emulator in buildbot_emulators:
buildbot_emulator.Shutdown()
# Another chance if we timed out? At this point It is safe(r) to
# run fast and loose since we just uploaded all the test data and
# binary.
if test_results.timed_out and options.repeat:
logging.critical('Timed out; repeating in fast_and_loose mode.')
options.fast_and_loose = True
options.repeat -= 1
logging.critical('Repeats left: ' + str(options.repeat))
return _RunATestSuite(options)
return len(test_results.failed)
def Dispatch(options):
"""Dispatches the tests, sharding if possible.
If options.use_emulator is True, all tests will be run in new emulator
instance.
Args:
options: options for running the tests.
Returns:
0 if successful, number of failing tests otherwise.
"""
if options.test_suite == 'help':
ListTestSuites()
return 0
if options.use_xvfb:
xvfb = Xvfb()
xvfb.Start()
all_test_suites = FullyQualifiedTestSuites(options.exe, options.test_suite,
options.build_type)
failures = 0
for suite in all_test_suites:
options.test_suite = suite
failures += _RunATestSuite(options)
if options.use_xvfb:
xvfb.Stop()
return failures
def ListTestSuites():
"""Display a list of available test suites."""
print 'Available test suites are:'
for test_suite in _TEST_SUITES:
print test_suite
def main(argv):
option_parser = optparse.OptionParser()
test_options_parser.AddTestRunnerOptions(option_parser, default_timeout=0)
option_parser.add_option('-s', '--suite', dest='test_suite',
help='Executable name of the test suite to run '
'(use -s help to list them)')
option_parser.add_option('-d', '--device', dest='test_device',
help='Target device the test suite to run ')
option_parser.add_option('-r', dest='rebaseline',
help='Rebaseline and update *testsuite_disabled',
action='store_true')
option_parser.add_option('-f', '--gtest_filter', dest='gtest_filter',
help='gtest filter')
option_parser.add_option('-a', '--test_arguments', dest='test_arguments',
help='Additional arguments to pass to the test')
option_parser.add_option('-p', dest='performance_test',
help='Indicator of performance test',
action='store_true')
option_parser.add_option('-L', dest='log_dump',
help='file name of log dump, which will be put in '
'subfolder debug_info_dumps under the same '
'directory in where the test_suite exists.')
option_parser.add_option('-e', '--emulator', dest='use_emulator',
action='store_true',
help='Run tests in a new instance of emulator')
option_parser.add_option('-n', '--emulator_count',
type='int', default=1,
help='Number of emulators to launch for running the '
'tests.')
option_parser.add_option('-x', '--xvfb', dest='use_xvfb',
action='store_true',
help='Use Xvfb around tests (ignored if not Linux)')
option_parser.add_option('--fast', '--fast_and_loose', dest='fast_and_loose',
action='store_true',
help='Go faster (but be less stable), '
'for quick testing. Example: when tracking down '
'tests that hang to add to the disabled list, '
'there is no need to redeploy the test binary '
'or data to the device again. '
'Don\'t use on bots by default!')
option_parser.add_option('--repeat', dest='repeat', type='int',
default=2,
help='Repeat count on test timeout')
option_parser.add_option('--exit_code', action='store_true',
help='If set, the exit code will be total number '
'of failures.')
option_parser.add_option('--exe', action='store_true',
help='If set, use the exe test runner instead of '
'the APK.')
options, args = option_parser.parse_args(argv)
if len(args) > 1:
print 'Unknown argument:', args[1:]
option_parser.print_usage()
sys.exit(1)
run_tests_helper.SetLogLevel(options.verbose_count)
emulator.DeleteAllTempAVDs()
failed_tests_count = Dispatch(options)
# Failures of individual test suites are communicated by printing a
# STEP_FAILURE message.
# Returning a success exit status also prevents the buildbot from incorrectly
# marking the last suite as failed if there were failures in other suites in
# the batch (this happens because the exit status is a sum of all failures
# from all suites, but the buildbot associates the exit status only with the
# most recent step).
if options.exit_code:
return failed_tests_count
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))