mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-30 04:17:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
139
media/webrtc/trunk/build/android/adb_device_functions.sh
Normal file
139
media/webrtc/trunk/build/android/adb_device_functions.sh
Normal 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
|
||||
)
|
||||
}
|
||||
24
media/webrtc/trunk/build/android/adb_kill_content_shell
Normal file
24
media/webrtc/trunk/build/android/adb_kill_content_shell
Normal 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
|
||||
155
media/webrtc/trunk/build/android/adb_logcat_monitor.py
Normal file
155
media/webrtc/trunk/build/android/adb_logcat_monitor.py
Normal 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]
|
||||
202
media/webrtc/trunk/build/android/adb_logcat_printer.py
Normal file
202
media/webrtc/trunk/build/android/adb_logcat_printer.py
Normal 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))
|
||||
14
media/webrtc/trunk/build/android/adb_run_content_shell
Normal file
14
media/webrtc/trunk/build/android/adb_run_content_shell
Normal 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
|
||||
97
media/webrtc/trunk/build/android/ant/chromium-jars.xml
Normal file
97
media/webrtc/trunk/build/android/ant/chromium-jars.xml
Normal 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>
|
||||
90
media/webrtc/trunk/build/android/ant/common.xml
Normal file
90
media/webrtc/trunk/build/android/ant/common.xml
Normal 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>
|
||||
284
media/webrtc/trunk/build/android/ant/sdk-targets.xml
Normal file
284
media/webrtc/trunk/build/android/ant/sdk-targets.xml
Normal 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>
|
||||
9
media/webrtc/trunk/build/android/buildbot_fyi_builder.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_fyi_builder.sh
Normal 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 "$@"
|
||||
9
media/webrtc/trunk/build/android/buildbot_fyi_tester.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_fyi_tester.sh
Normal 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 "$@"
|
||||
9
media/webrtc/trunk/build/android/buildbot_main.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_main.sh
Normal 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 "$@"
|
||||
9
media/webrtc/trunk/build/android/buildbot_try_builder.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_try_builder.sh
Normal 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 "$@"
|
||||
9
media/webrtc/trunk/build/android/buildbot_try_compile.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_try_compile.sh
Normal 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 "$@"
|
||||
9
media/webrtc/trunk/build/android/buildbot_try_tester.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_try_tester.sh
Normal 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 "$@"
|
||||
9
media/webrtc/trunk/build/android/buildbot_webkit_main.sh
Normal file
9
media/webrtc/trunk/build/android/buildbot_webkit_main.sh
Normal 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 "$@"
|
||||
6
media/webrtc/trunk/build/android/cpufeatures.gypi
Normal file
6
media/webrtc/trunk/build/android/cpufeatures.gypi
Normal 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.
|
||||
{}
|
||||
43
media/webrtc/trunk/build/android/device_stats_monitor.py
Normal file
43
media/webrtc/trunk/build/android/device_stats_monitor.py
Normal 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))
|
||||
170
media/webrtc/trunk/build/android/device_status_check.py
Normal file
170
media/webrtc/trunk/build/android/device_status_check.py
Normal 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())
|
||||
321
media/webrtc/trunk/build/android/emulator.py
Normal file
321
media/webrtc/trunk/build/android/emulator.py
Normal 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)
|
||||
31
media/webrtc/trunk/build/android/enable_asserts.py
Normal file
31
media/webrtc/trunk/build/android/enable_asserts.py
Normal 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)
|
||||
133
media/webrtc/trunk/build/android/envsetup.sh
Normal file
133
media/webrtc/trunk/build/android/envsetup.sh
Normal 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=
|
||||
334
media/webrtc/trunk/build/android/envsetup_functions.sh
Normal file
334
media/webrtc/trunk/build/android/envsetup_functions.sh
Normal 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"
|
||||
}
|
||||
171
media/webrtc/trunk/build/android/gdb_apk
Normal file
171
media/webrtc/trunk/build/android/gdb_apk
Normal 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
|
||||
15
media/webrtc/trunk/build/android/gdb_content_shell
Normal file
15
media/webrtc/trunk/build/android/gdb_content_shell
Normal 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
|
||||
|
|
@ -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*
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.*
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
SyncHttpBridgeTest.*
|
||||
|
||||
# crbug.com/144422
|
||||
OnDiskSyncableDirectory.FailInitialWrite
|
||||
|
|
@ -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
|
||||
|
|
@ -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.*
|
||||
253
media/webrtc/trunk/build/android/lighttpd_server.py
Normal file
253
media/webrtc/trunk/build/android/lighttpd_server.py
Normal 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))
|
||||
4
media/webrtc/trunk/build/android/pylib/__init__.py
Normal file
4
media/webrtc/trunk/build/android/pylib/__init__.py
Normal 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.
|
||||
|
||||
1071
media/webrtc/trunk/build/android/pylib/android_commands.py
Normal file
1071
media/webrtc/trunk/build/android/pylib/android_commands.py
Normal file
File diff suppressed because it is too large
Load diff
142
media/webrtc/trunk/build/android/pylib/apk_info.py
Normal file
142
media/webrtc/trunk/build/android/pylib/apk_info.py
Normal 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
|
||||
210
media/webrtc/trunk/build/android/pylib/base_test_runner.py
Normal file
210
media/webrtc/trunk/build/android/pylib/base_test_runner.py
Normal 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)
|
||||
113
media/webrtc/trunk/build/android/pylib/base_test_sharder.py
Normal file
113
media/webrtc/trunk/build/android/pylib/base_test_sharder.py
Normal 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
|
||||
46
media/webrtc/trunk/build/android/pylib/buildbot_report.py
Normal file
46
media/webrtc/trunk/build/android/pylib/buildbot_report.py
Normal 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
|
||||
|
|
@ -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()
|
||||
50
media/webrtc/trunk/build/android/pylib/cmd_helper.py
Normal file
50
media/webrtc/trunk/build/android/pylib/cmd_helper.py
Normal 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
|
||||
47
media/webrtc/trunk/build/android/pylib/constants.py
Normal file
47
media/webrtc/trunk/build/android/pylib/constants.py
Normal 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'
|
||||
196
media/webrtc/trunk/build/android/pylib/debug_info.py
Normal file
196
media/webrtc/trunk/build/android/pylib/debug_info.py
Normal 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)
|
||||
143
media/webrtc/trunk/build/android/pylib/device_stats_monitor.html
Normal file
143
media/webrtc/trunk/build/android/pylib/device_stats_monitor.html
Normal 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>
|
||||
116
media/webrtc/trunk/build/android/pylib/device_stats_monitor.py
Normal file
116
media/webrtc/trunk/build/android/pylib/device_stats_monitor.py
Normal 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]])
|
||||
63
media/webrtc/trunk/build/android/pylib/fake_dns.py
Normal file
63
media/webrtc/trunk/build/android/pylib/fake_dns.py
Normal 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')
|
||||
144
media/webrtc/trunk/build/android/pylib/flag_changer.py
Normal file
144
media/webrtc/trunk/build/android/pylib/flag_changer.py
Normal 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:]
|
||||
198
media/webrtc/trunk/build/android/pylib/forwarder.py
Normal file
198
media/webrtc/trunk/build/android/pylib/forwarder.py
Normal 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()
|
||||
32
media/webrtc/trunk/build/android/pylib/io_stats_parser.py
Normal file
32
media/webrtc/trunk/build/android/pylib/io_stats_parser.py
Normal 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:]])
|
||||
|
|
@ -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()
|
||||
160
media/webrtc/trunk/build/android/pylib/json_perf_parser.py
Normal file
160
media/webrtc/trunk/build/android/pylib/json_perf_parser.py
Normal 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)
|
||||
120
media/webrtc/trunk/build/android/pylib/perf_tests_helper.py
Normal file
120
media/webrtc/trunk/build/android/pylib/perf_tests_helper.py
Normal 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))
|
||||
167
media/webrtc/trunk/build/android/pylib/ports.py
Normal file
167
media/webrtc/trunk/build/android/pylib/ports.py
Normal 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')
|
||||
168
media/webrtc/trunk/build/android/pylib/python_test_base.py
Normal file
168
media/webrtc/trunk/build/android/pylib/python_test_base.py
Normal 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, '.')
|
||||
84
media/webrtc/trunk/build/android/pylib/python_test_caller.py
Normal file
84
media/webrtc/trunk/build/android/pylib/python_test_caller.py
Normal 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
|
||||
203
media/webrtc/trunk/build/android/pylib/python_test_sharder.py
Normal file
203
media/webrtc/trunk/build/android/pylib/python_test_sharder.py
Normal 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
|
||||
591
media/webrtc/trunk/build/android/pylib/run_java_tests.py
Normal file
591
media/webrtc/trunk/build/android/pylib/run_java_tests.py
Normal 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
|
||||
207
media/webrtc/trunk/build/android/pylib/run_python_tests.py
Normal file
207
media/webrtc/trunk/build/android/pylib/run_python_tests.py
Normal 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
|
||||
26
media/webrtc/trunk/build/android/pylib/run_tests_helper.py
Normal file
26
media/webrtc/trunk/build/android/pylib/run_tests_helper.py
Normal 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)
|
||||
|
|
@ -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)
|
||||
343
media/webrtc/trunk/build/android/pylib/single_test_runner.py
Normal file
343
media/webrtc/trunk/build/android/pylib/single_test_runner.py
Normal 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()
|
||||
137
media/webrtc/trunk/build/android/pylib/test_info_collection.py
Normal file
137
media/webrtc/trunk/build/android/pylib/test_info_collection.py
Normal 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
|
||||
143
media/webrtc/trunk/build/android/pylib/test_options_parser.py
Normal file
143
media/webrtc/trunk/build/android/pylib/test_options_parser.py
Normal 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']
|
||||
200
media/webrtc/trunk/build/android/pylib/test_package.py
Normal file
200
media/webrtc/trunk/build/android/pylib/test_package.py
Normal 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)
|
||||
121
media/webrtc/trunk/build/android/pylib/test_package_apk.py
Normal file
121
media/webrtc/trunk/build/android/pylib/test_package_apk.py
Normal 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]
|
||||
|
|
@ -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)
|
||||
193
media/webrtc/trunk/build/android/pylib/test_result.py
Normal file
193
media/webrtc/trunk/build/android/pylib/test_result.py
Normal 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
|
||||
89
media/webrtc/trunk/build/android/pylib/tests_annotations.py
Normal file
89
media/webrtc/trunk/build/android/pylib/tests_annotations.py
Normal 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
|
||||
255
media/webrtc/trunk/build/android/pylib/valgrind_tools.py
Normal file
255
media/webrtc/trunk/build/android/pylib/valgrind_tools.py
Normal 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)
|
||||
|
|
@ -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))
|
||||
450
media/webrtc/trunk/build/android/run_tests.py
Normal file
450
media/webrtc/trunk/build/android/run_tests.py
Normal 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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue