[Android] Enable running uiautomator tests.

- uiautomator uses instrumentation runner for results reporting.
- Separate the concept of test jar (for progaurd consumption) from test
  apk
- clear the app before every uiautomator test

BUG=162742
NOTRY=True

Review URL: https://chromiumcodereview.appspot.com/12921004

git-svn-id: http://src.chromium.org/svn/trunk/src/build@189343 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
frankf@chromium.org 2013-03-20 18:06:53 +00:00
Родитель d52c1fbe79
Коммит 8a450f5010
15 изменённых файлов: 517 добавлений и 123 удалений

Просмотреть файл

@ -11,7 +11,7 @@ import sys
from pylib import android_commands
from pylib import constants
from pylib.instrumentation import apk_info
from pylib.utils import apk_helper
from pylib.utils import test_options_parser
@ -36,7 +36,7 @@ def main(argv):
raise Exception('Error: no connected devices')
if not options.apk_package:
options.apk_package = apk_info.GetPackageNameForApk(options.apk)
options.apk_package = apk_helper.GetPackageName(options.apk)
pool = multiprocessing.Pool(len(devices))
# Send a tuple (apk_path, apk_package, device) per device.

Просмотреть файл

@ -18,20 +18,19 @@ import sys
import tempfile
import time
import cmd_helper
import constants
import io_stats_parser
try:
import pexpect
except:
pexpect = None
CHROME_SRC = os.path.join(
os.path.abspath(os.path.dirname(__file__)), '..', '..', '..')
sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner'))
sys.path.append(os.path.join(
constants.CHROME_DIR, 'third_party', 'android_testrunner'))
import adb_interface
import cmd_helper
import errors # is under ../../../third_party/android_testrunner/errors.py
import am_instrument_parser
import errors
# Pattern to search for the next whole line of pexpect output and capture it
@ -1214,6 +1213,50 @@ class AndroidCommands(object):
"""
self._util_wrapper = util_wrapper
def RunInstrumentationTest(self, test, test_package, instr_args, timeout):
"""Runs a single instrumentation test.
Args:
test: Test class/method.
test_package: Package name of test apk.
instr_args: Extra key/value to pass to am instrument.
timeout: Timeout time in seconds.
Returns:
An instance of am_instrument_parser.TestResult object.
"""
instrumentation_path = ('%s/android.test.InstrumentationTestRunner' %
test_package)
args_with_filter = dict(instr_args)
args_with_filter['class'] = test
logging.info(args_with_filter)
(raw_results, _) = self._adb.StartInstrumentation(
instrumentation_path=instrumentation_path,
instrumentation_args=args_with_filter,
timeout_time=timeout)
assert len(raw_results) == 1
return raw_results[0]
def RunUIAutomatorTest(self, test, test_package, timeout):
"""Runs a single uiautomator test.
Args:
test: Test class/method.
test_package: Name of the test jar.
timeout: Timeout time in seconds.
Returns:
An instance of am_instrument_parser.TestResult object.
"""
cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
logging.info('>>> $' + cmd)
output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
# uiautomator doesn't fully conform to the instrumenation test runner
# convention and doesn't terminate with INSTRUMENTATION_CODE.
# Just assume the first result is valid.
(test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
return test_results[0]
class NewLineNormalizer(object):
"""A file-like object to normalize EOLs to '\n'.

Просмотреть файл

@ -25,7 +25,7 @@ import time
from pylib import android_commands
from pylib.base.test_result import SingleTestResult, TestResults
from pylib.instrumentation import apk_info
from pylib.instrumentation import test_package
from pylib.instrumentation import test_runner
@ -75,10 +75,11 @@ class PythonTestBase(object):
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)]
test_pkg = test_package.TestPackage(
self.options.test_apk_path, self.options.test_apk_jar_path)
java_test_runner = test_runner.TestRunner(self.options, self.device_id,
self.shard_index, False, apks,
self.shard_index, False,
test_pkg,
self.ports_to_forward)
try:
java_test_runner.SetUp()

Просмотреть файл

@ -12,7 +12,7 @@ import types
from pylib import android_commands
from pylib import constants
from pylib.base.test_result import TestResults
from pylib.instrumentation import apk_info
from pylib.instrumentation import test_package
from pylib.instrumentation import test_runner
import python_test_base
@ -84,9 +84,10 @@ def DispatchPythonTests(options):
# 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_pkg = test_package.TestPackage(options.test_apk_path,
options.test_apk_jar_path)
test_files_copier = test_runner.TestRunner(options, device_id, 0, False,
apks, [])
test_pkg, [])
test_files_copier.CopyTestFilesOnce()
# Actually run the tests.

Просмотреть файл

@ -10,12 +10,13 @@ import os
from pylib import android_commands
from pylib.base import shard
from pylib.base import test_result
from pylib.uiautomator import test_package as uiautomator_package
import apk_info
import test_package
import test_runner
def Dispatch(options, apks):
def Dispatch(options):
"""Dispatches instrumentation tests onto connected device(s).
If possible, this method will attempt to shard the tests to
@ -23,7 +24,6 @@ def Dispatch(options, apks):
Args:
options: Command line options.
apks: list of APKs to use.
Returns:
A TestResults object holding the results of the Java tests.
@ -31,26 +31,33 @@ def Dispatch(options, apks):
Raises:
Exception: when there are no attached devices.
"""
test_apk = apks[0]
is_uiautomator_test = False
if hasattr(options, 'uiautomator_jar'):
test_pkg = uiautomator_package.TestPackage(
options.uiautomator_jar, options.uiautomator_info_jar)
is_uiautomator_test = True
else:
test_pkg = test_package.TestPackage(options.test_apk_path,
options.test_apk_jar_path)
# The default annotation for tests which do not have any sizes annotation.
default_size_annotation = 'SmallTest'
def _GetTestsMissingAnnotation(test_apk):
def _GetTestsMissingAnnotation(test_pkg):
test_size_annotations = frozenset(['Smoke', 'SmallTest', 'MediumTest',
'LargeTest', 'EnormousTest', 'FlakyTest',
'DisabledTest', 'Manual', 'PerfTest'])
tests_missing_annotations = []
for test_method in test_apk.GetTestMethods():
annotations = frozenset(test_apk.GetTestAnnotations(test_method))
for test_method in test_pkg.GetTestMethods():
annotations = frozenset(test_pkg.GetTestAnnotations(test_method))
if (annotations.isdisjoint(test_size_annotations) and
not apk_info.ApkInfo.IsPythonDrivenTest(test_method)):
not test_pkg.IsPythonDrivenTest(test_method)):
tests_missing_annotations.append(test_method)
return sorted(tests_missing_annotations)
if options.annotation:
available_tests = test_apk.GetAnnotatedTests(options.annotation)
available_tests = test_pkg.GetAnnotatedTests(options.annotation)
if options.annotation.count(default_size_annotation) > 0:
tests_missing_annotations = _GetTestsMissingAnnotation(test_apk)
tests_missing_annotations = _GetTestsMissingAnnotation(test_pkg)
if tests_missing_annotations:
logging.warning('The following tests do not contain any annotation. '
'Assuming "%s":\n%s',
@ -58,8 +65,8 @@ def Dispatch(options, apks):
'\n'.join(tests_missing_annotations))
available_tests += tests_missing_annotations
else:
available_tests = [m for m in test_apk.GetTestMethods()
if not apk_info.ApkInfo.IsPythonDrivenTest(m)]
available_tests = [m for m in test_pkg.GetTestMethods()
if not test_pkg.IsPythonDrivenTest(m)]
coverage = os.environ.get('EMMA_INSTRUMENT') == 'true'
tests = []
@ -92,7 +99,8 @@ def Dispatch(options, apks):
attached_devices = attached_devices[:1]
def TestRunnerFactory(device, shard_index):
return test_runner.TestRunner(options, device, shard_index, False, apks, [])
return test_runner.TestRunner(
options, device, shard_index, False, test_pkg, [], is_uiautomator_test)
return shard.ShardAndRunTests(TestRunnerFactory, attached_devices, tests,
options.build_type)

Просмотреть файл

@ -0,0 +1,163 @@
# Copyright (c) 2013 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 class for instrumenation test jar."""
import collections
import logging
import os
import pickle
import re
from pylib import cmd_helper
from pylib import constants
# If you change the cached output of proguard, increment this number
PICKLE_FORMAT_VERSION = 1
class TestJar(object):
def __init__(self, jar_path):
sdk_root = os.getenv('ANDROID_SDK_ROOT', constants.ANDROID_SDK_ROOT)
self._PROGUARD_PATH = os.path.join(sdk_root,
'tools/proguard/bin/proguard.sh')
if not os.path.exists(self._PROGUARD_PATH):
self._PROGUARD_PATH = os.path.join(os.environ['ANDROID_BUILD_TOP'],
'external/proguard/bin/proguard.sh')
self._PROGUARD_CLASS_RE = re.compile(r'\s*?- Program class:\s*([\S]+)$')
self._PROGUARD_METHOD_RE = re.compile(r'\s*?- Method:\s*(\S*)[(].*$')
self._PROGUARD_ANNOTATION_RE = re.compile(r'\s*?- Annotation \[L(\S*);\]:$')
self._PROGUARD_ANNOTATION_CONST_RE = (
re.compile(r'\s*?- Constant element value.*$'))
self._PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$')
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._pickled_proguard_name = self._jar_path + '-proguard.pickle'
self._test_methods = []
if not self._GetCachedProguardData():
self._GetProguardData()
def _GetCachedProguardData(self):
if (os.path.exists(self._pickled_proguard_name) and
(os.path.getmtime(self._pickled_proguard_name) >
os.path.getmtime(self._jar_path))):
logging.info('Loading cached proguard output from %s',
self._pickled_proguard_name)
try:
with open(self._pickled_proguard_name, 'r') as r:
d = pickle.loads(r.read())
if d['VERSION'] == PICKLE_FORMAT_VERSION:
self._annotation_map = d['ANNOTATION_MAP']
self._test_methods = d['TEST_METHODS']
return True
except:
logging.warning('PICKLE_FORMAT_VERSION has changed, ignoring cache')
return False
def _GetProguardData(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
if not qualified_method:
# Ignore non-method annotations.
continue
m = self._PROGUARD_ANNOTATION_RE.match(line)
if m:
annotation = m.group(1).split('/')[-1] # Ignore the annotation package.
self._annotation_map[qualified_method].append(annotation)
has_value = False
continue
if annotation:
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
logging.info('Storing proguard output to %s', self._pickled_proguard_name)
d = {'VERSION': PICKLE_FORMAT_VERSION,
'ANNOTATION_MAP': self._annotation_map,
'TEST_METHODS': self._test_methods}
with open(self._pickled_proguard_name, 'w') as f:
f.write(pickle.dumps(d))
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 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

Просмотреть файл

@ -0,0 +1,31 @@
# Copyright (c) 2013 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.
"""Class representing instrumentation test apk and jar."""
import os
from pylib.utils import apk_helper
import test_jar
class TestPackage(test_jar.TestJar):
def __init__(self, apk_path, jar_path):
test_jar.TestJar.__init__(self, jar_path)
if not os.path.exists(apk_path):
raise Exception('%s not found, please build it' % apk_path)
self._apk_path = apk_path
def GetApkPath(self):
return self._apk_path
def GetPackageName(self):
"""Returns the package name of this APK."""
return apk_helper.GetPackageName(self._apk_path)
# Override.
def Install(self, adb):
adb.ManagedInstall(self.GetApkPath(), package_name=self.GetPackageName())

Просмотреть файл

@ -21,8 +21,6 @@ from pylib import valgrind_tools
from pylib.base import base_test_runner
from pylib.base import test_result
import apk_info
_PERF_TEST_ANNOTATION = 'PerfTest'
@ -47,8 +45,8 @@ class TestRunner(base_test_runner.BaseTestRunner):
'/chrome-profile*')
_DEVICE_HAS_TEST_FILES = {}
def __init__(self, options, device, shard_index, coverage, apks,
ports_to_forward):
def __init__(self, options, device, shard_index, coverage, test_pkg,
ports_to_forward, is_uiautomator_test=False):
"""Create a new TestRunner.
Args:
@ -64,34 +62,30 @@ class TestRunner(base_test_runner.BaseTestRunner):
device: Attached android device.
shard_index: Shard index.
coverage: Collects coverage information if opted.
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.
test_pkg: A TestPackage object.
ports_to_forward: A list of port numbers for which to set up forwarders.
Can be optionally requested by a test case.
is_uiautomator_test: Whether this is a uiautomator test.
Raises:
Exception: if coverage metadata is not available.
"""
super(TestRunner, self).__init__(device, options.tool, options.build_type)
self._lighttp_port = constants.LIGHTTPD_RANDOM_PORT_FIRST + shard_index
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.test_data = options.test_data
self.save_perf_json = options.save_perf_json
self.screenshot_failures = options.screenshot_failures
self.wait_for_debugger = options.wait_for_debugger
self.disable_assertions = options.disable_assertions
self.coverage = coverage
self.apks = apks
self.test_apk = apks[0]
self.instrumentation_class_path = self.test_apk.GetPackageName()
self.test_pkg = test_pkg
self.ports_to_forward = ports_to_forward
self.is_uiautomator_test = is_uiautomator_test
if self.is_uiautomator_test:
self.package_name = options.package_name
else:
self.install_apk = options.install_apk
self.forwarder = None
@ -125,10 +119,11 @@ class TestRunner(base_test_runner.BaseTestRunner):
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())
if self.is_uiautomator_test:
self.test_pkg.Install(self.adb)
elif self.install_apk:
self.test_pkg.Install(self.adb)
self.tool.CopyFiles()
TestRunner._DEVICE_HAS_TEST_FILES[self.device] = True
@ -253,7 +248,7 @@ class TestRunner(base_test_runner.BaseTestRunner):
Returns:
Whether the test is annotated as a performance test.
"""
return _PERF_TEST_ANNOTATION in self.test_apk.GetTestAnnotations(test)
return _PERF_TEST_ANNOTATION in self.test_pkg.GetTestAnnotations(test)
def SetupPerfMonitoringIfNeeded(self, test):
"""Sets up performance monitoring if the specified test requires it.
@ -352,7 +347,7 @@ class TestRunner(base_test_runner.BaseTestRunner):
def _GetIndividualTestTimeoutScale(self, test):
"""Returns the timeout scale for the given |test|."""
annotations = self.apks[0].GetTestAnnotations(test)
annotations = self.test_pkg.GetTestAnnotations(test)
timeout_scale = 1
if 'TimeoutScale' in annotations:
for annotation in annotations:
@ -365,7 +360,7 @@ class TestRunner(base_test_runner.BaseTestRunner):
def _GetIndividualTestTimeoutSecs(self, test):
"""Returns the timeout in seconds for the given |test|."""
annotations = self.apks[0].GetTestAnnotations(test)
annotations = self.test_pkg.GetTestAnnotations(test)
if 'Manual' in annotations:
return 600 * 60
if 'External' in annotations:
@ -382,29 +377,31 @@ class TestRunner(base_test_runner.BaseTestRunner):
Returns:
A test_result.TestResults object.
"""
instrumentation_path = (self.instrumentation_class_path +
'/android.test.InstrumentationTestRunner')
instrumentation_args = self._GetInstrumentationArgs()
raw_result = None
start_date_ms = None
test_results = test_result.TestResults()
timeout=(self._GetIndividualTestTimeoutSecs(test) *
self._GetIndividualTestTimeoutScale(test) *
self.tool.GetTimeoutScale())
try:
self.TestSetup(test)
start_date_ms = int(time.time()) * 1000
args_with_filter = dict(instrumentation_args)
args_with_filter['class'] = test
# |raw_results| is a list that should contain
# a single TestResult object.
logging.warn(args_with_filter)
(raw_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()))
if self.is_uiautomator_test:
self.adb.ClearApplicationState(self.package_name)
# TODO(frankf): Stop-gap solution. Should use annotations.
if 'FirstRun' in test:
self.flags.RemoveFlags(['--disable-fre'])
else:
self.flags.AddFlags(['--disable-fre'])
raw_result = self.adb.RunUIAutomatorTest(
test, self.test_pkg.GetPackageName(), timeout)
else:
raw_result = self.adb.RunInstrumentationTest(
test, self.test_pkg.GetPackageName(),
self._GetInstrumentationArgs(), timeout)
duration_ms = int(time.time()) * 1000 - start_date_ms
assert len(raw_results) == 1
raw_result = raw_results[0]
status_code = raw_result.GetStatusCode()
if status_code:
log = raw_result.GetFailureReason()

Просмотреть файл

@ -0,0 +1,4 @@
# Copyright (c) 2013 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.

Просмотреть файл

@ -0,0 +1,27 @@
# Copyright (c) 2013 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.
"""Class representing uiautomator test package."""
import os
from pylib import constants
from pylib.instrumentation import test_jar
class TestPackage(test_jar.TestJar):
def __init__(self, jar_path, jar_info_path):
test_jar.TestJar.__init__(self, jar_info_path)
if not os.path.exists(jar_path):
raise Exception('%s not found, please build it' % jar_path)
self._jar_path = jar_path
def GetPackageName(self):
"""Returns the JAR named that is installed on the device."""
return os.path.basename(self._jar_path)
# Override.
def Install(self, adb):
adb.PushIfNeeded(self._jar_path, constants.TEST_EXECUTABLE_DIR)

Просмотреть файл

@ -0,0 +1,21 @@
# Copyright (c) 2013 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 utilities for apk packages."""
import re
from pylib import cmd_helper
def GetPackageName(apk_path):
"""Returns the package name of the apk."""
aapt_output = cmd_helper.GetCmdOutput(
['aapt', 'dump', 'badging', apk_path]).split('\n')
package_name_re = re.compile(r'package: .*name=\'(\S*)\'')
for line in aapt_output:
m = package_name_re.match(line)
if m:
return m.group(1)
raise Exception('Failed to determine package name of %s' % apk_path)

Просмотреть файл

@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Gathers information about APKs."""
"""Module containing utilities for jar packages."""
import collections
import logging
@ -17,22 +17,9 @@ from pylib import constants
# If you change the cached output of proguard, increment this number
PICKLE_FORMAT_VERSION = 1
def GetPackageNameForApk(apk_path):
"""Returns the package name of the apk file."""
aapt_output = cmd_helper.GetCmdOutput(
['aapt', 'dump', 'badging', apk_path]).split('\n')
package_name_re = re.compile(r'package: .*name=\'(\S*)\'')
for line in aapt_output:
m = package_name_re.match(line)
if m:
return m.group(1)
raise Exception('Failed to determine package name of %s' % apk_path)
class ApkInfo(object):
"""Helper class for inspecting APKs."""
def __init__(self, apk_path, jar_path):
class TestPackageJar(object):
def __init__(self, jar_path):
sdk_root = os.getenv('ANDROID_SDK_ROOT', constants.ANDROID_SDK_ROOT)
self._PROGUARD_PATH = os.path.join(sdk_root,
'tools/proguard/bin/proguard.sh')
@ -46,18 +33,12 @@ class ApkInfo(object):
re.compile(r'\s*?- Constant element value.*$'))
self._PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$')
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._pickled_proguard_name = self._jar_path + '-proguard.pickle'
self._test_methods = []
self._Initialize()
def _Initialize(self):
if not self._GetCachedProguardData():
self._GetProguardData()
@ -145,13 +126,6 @@ class ApkInfo(object):
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."""
return GetPackageNameForApk(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):

Просмотреть файл

@ -28,6 +28,7 @@ def AddBuildTypeOption(option_parser):
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."""
AddBuildTypeOption(option_parser)
@ -127,14 +128,12 @@ def AddGTestOptions(option_parser):
'the APK.')
def AddInstrumentationOptions(option_parser):
"""Decorates OptionParser with instrumentation tests options."""
def AddCommonInstrumentationOptions(option_parser):
"""Decorates OptionParser with base 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).')
@ -153,11 +152,6 @@ def AddInstrumentationOptions(option_parser):
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')
@ -193,17 +187,44 @@ def AddInstrumentationOptions(option_parser):
'directory, and <source> is relative to the '
'chromium build directory.'))
def ValidateInstrumentationOptions(option_parser, options, args):
"""Validate options/arguments and populate options with defaults."""
def AddInstrumentationOptions(option_parser):
"""Decorates OptionParser with instrumentation tests options."""
AddCommonInstrumentationOptions(option_parser)
option_parser.add_option('-I', dest='install_apk',
help='Install APK.', action='store_true')
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).'))
def AddUIAutomatorOptions(option_parser):
"""Decorates OptionParser with uiautomator tests options."""
AddCommonInstrumentationOptions(option_parser)
option_parser.add_option(
'--package-name',
help=('The package name used by the apk containing the application.'))
option_parser.add_option(
'--uiautomator-jar',
help=('Path to the uiautomator jar to be installed on the device.'))
option_parser.add_option(
'--uiautomator-info-jar',
help=('Path to the uiautomator jar for use by proguard.'))
def ValidateCommonInstrumentationOptions(option_parser, options, args):
"""Validate common 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.')
if not options.test_apk:
option_parser.error('--test-apk must be specified.')
options.run_java_tests = True
options.run_python_tests = True
if options.java_only:
@ -211,6 +232,21 @@ def ValidateInstrumentationOptions(option_parser, options, args):
elif options.python_only:
options.run_java_tests = False
if options.annotation_str:
options.annotation = options.annotation_str.split()
elif options.test_filter:
options.annotation = []
else:
options.annotation = ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest']
def ValidateInstrumentationOptions(option_parser, options, args):
"""Validate options/arguments and populate options with defaults."""
ValidateCommonInstrumentationOptions(option_parser, options, args)
if not options.test_apk:
option_parser.error('--test-apk must be specified.')
if os.path.exists(options.test_apk):
# The APK is fully qualified, assume the JAR lives along side.
options.test_apk_path = options.test_apk
@ -224,9 +260,18 @@ def ValidateInstrumentationOptions(option_parser, options, args):
options.test_apk_jar_path = os.path.join(
_SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_TEST_JAVALIB_DIR,
'%s.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']
def ValidateUIAutomatorOptions(option_parser, options, args):
"""Validate uiautomator options/arguments."""
ValidateCommonInstrumentationOptions(option_parser, options, args)
if not options.package_name:
option_parser.error('--package-name must be specified.')
if not options.uiautomator_jar:
option_parser.error('--uiautomator-jar must be specified.')
if not options.uiautomator_info_jar:
option_parser.error('--uiautomator-info-jar must be specified.')

Просмотреть файл

@ -4,7 +4,7 @@
# 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."""
"""Runs both the Python and Java instrumentation tests."""
import optparse
import os
@ -16,7 +16,6 @@ from pylib import constants
from pylib import ports
from pylib.base import test_result
from pylib.host_driven import run_python_tests
from pylib.instrumentation import apk_info
from pylib.instrumentation import dispatch
from pylib.utils import run_tests_helper
from pylib.utils import test_options_parser
@ -42,14 +41,12 @@ def DispatchInstrumentationTests(options):
if not ports.ResetTestServerPortAllocation():
raise Exception('Failed to reset test server port.')
start_date = int(time.time() * 1000)
java_results = test_result.TestResults()
python_results = test_result.TestResults()
if options.run_java_tests:
java_results = dispatch.Dispatch(
options,
[apk_info.ApkInfo(options.test_apk_path, options.test_apk_jar_path)])
java_results = dispatch.Dispatch(options)
if options.run_python_tests:
python_results = run_python_tests.DispatchPythonTests(options)

Просмотреть файл

@ -0,0 +1,82 @@
#!/usr/bin/env python
#
# Copyright (c) 2013 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 UIAutomator tests."""
import optparse
import os
import sys
import time
from pylib import buildbot_report
from pylib import constants
from pylib import ports
from pylib.base import test_result
from pylib.host_driven import run_python_tests
from pylib.instrumentation import dispatch
from pylib.utils import run_tests_helper
from pylib.utils import test_options_parser
def DispatchUIAutomatorTests(options):
"""Dispatches the UIAutomator 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 broken tests.
"""
if not options.keep_test_server_ports:
# 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.')
java_results = test_result.TestResults()
python_results = test_result.TestResults()
if options.run_java_tests:
java_results = dispatch.Dispatch(options)
if options.run_python_tests:
python_results = run_python_tests.DispatchPythonTests(options)
all_results = test_result.TestResults.FromTestResults([java_results,
python_results])
all_results.LogFull(
test_type='UIAutomator',
test_package=os.path.basename(options.uiautomator_jar),
annotation=options.annotation,
build_type=options.build_type,
flakiness_server=options.flakiness_dashboard_server)
return len(all_results.GetAllBroken())
def main(argv):
option_parser = optparse.OptionParser()
test_options_parser.AddUIAutomatorOptions(option_parser)
options, args = option_parser.parse_args(argv)
test_options_parser.ValidateUIAutomatorOptions(option_parser, options, args)
run_tests_helper.SetLogLevel(options.verbose_count)
ret = 1
try:
ret = DispatchUIAutomatorTests(options)
finally:
buildbot_report.PrintStepResultIfNeeded(options, ret)
return ret
if __name__ == '__main__':
sys.exit(main(sys.argv))