[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 android_commands
from pylib import constants from pylib import constants
from pylib.instrumentation import apk_info from pylib.utils import apk_helper
from pylib.utils import test_options_parser from pylib.utils import test_options_parser
@ -36,7 +36,7 @@ def main(argv):
raise Exception('Error: no connected devices') raise Exception('Error: no connected devices')
if not options.apk_package: 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)) pool = multiprocessing.Pool(len(devices))
# Send a tuple (apk_path, apk_package, device) per device. # Send a tuple (apk_path, apk_package, device) per device.

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

@ -18,20 +18,19 @@ import sys
import tempfile import tempfile
import time import time
import cmd_helper
import constants
import io_stats_parser import io_stats_parser
try: try:
import pexpect import pexpect
except: except:
pexpect = None pexpect = None
CHROME_SRC = os.path.join( sys.path.append(os.path.join(
os.path.abspath(os.path.dirname(__file__)), '..', '..', '..') constants.CHROME_DIR, 'third_party', 'android_testrunner'))
sys.path.append(os.path.join(CHROME_SRC, 'third_party', 'android_testrunner'))
import adb_interface import adb_interface
import am_instrument_parser
import cmd_helper import errors
import errors # is under ../../../third_party/android_testrunner/errors.py
# Pattern to search for the next whole line of pexpect output and capture it # 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 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): class NewLineNormalizer(object):
"""A file-like object to normalize EOLs to '\n'. """A file-like object to normalize EOLs to '\n'.

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

@ -25,7 +25,7 @@ import time
from pylib import android_commands from pylib import android_commands
from pylib.base.test_result import SingleTestResult, TestResults 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 from pylib.instrumentation import test_runner
@ -75,10 +75,11 @@ class PythonTestBase(object):
TestResults object with a single test result. TestResults object with a single test result.
""" """
test = self._ComposeFullTestName(fname, suite, test) test = self._ComposeFullTestName(fname, suite, test)
apks = [apk_info.ApkInfo(self.options.test_apk_path, test_pkg = test_package.TestPackage(
self.options.test_apk_jar_path)] self.options.test_apk_path, self.options.test_apk_jar_path)
java_test_runner = test_runner.TestRunner(self.options, self.device_id, 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) self.ports_to_forward)
try: try:
java_test_runner.SetUp() java_test_runner.SetUp()

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

@ -12,7 +12,7 @@ import types
from pylib import android_commands from pylib import android_commands
from pylib import constants from pylib import constants
from pylib.base.test_result import TestResults 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 from pylib.instrumentation import test_runner
import python_test_base import python_test_base
@ -84,9 +84,10 @@ def DispatchPythonTests(options):
# Copy files to each device before running any tests. # Copy files to each device before running any tests.
for device_id in attached_devices: for device_id in attached_devices:
logging.debug('Pushing files to device %s', device_id) 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, test_files_copier = test_runner.TestRunner(options, device_id, 0, False,
apks, []) test_pkg, [])
test_files_copier.CopyTestFilesOnce() test_files_copier.CopyTestFilesOnce()
# Actually run the tests. # Actually run the tests.

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

@ -10,12 +10,13 @@ import os
from pylib import android_commands from pylib import android_commands
from pylib.base import shard from pylib.base import shard
from pylib.base import test_result from pylib.base import test_result
from pylib.uiautomator import test_package as uiautomator_package
import apk_info import test_package
import test_runner import test_runner
def Dispatch(options, apks): def Dispatch(options):
"""Dispatches instrumentation tests onto connected device(s). """Dispatches instrumentation tests onto connected device(s).
If possible, this method will attempt to shard the tests to If possible, this method will attempt to shard the tests to
@ -23,7 +24,6 @@ def Dispatch(options, apks):
Args: Args:
options: Command line options. options: Command line options.
apks: list of APKs to use.
Returns: Returns:
A TestResults object holding the results of the Java tests. A TestResults object holding the results of the Java tests.
@ -31,26 +31,33 @@ def Dispatch(options, apks):
Raises: Raises:
Exception: when there are no attached devices. 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. # The default annotation for tests which do not have any sizes annotation.
default_size_annotation = 'SmallTest' default_size_annotation = 'SmallTest'
def _GetTestsMissingAnnotation(test_apk): def _GetTestsMissingAnnotation(test_pkg):
test_size_annotations = frozenset(['Smoke', 'SmallTest', 'MediumTest', test_size_annotations = frozenset(['Smoke', 'SmallTest', 'MediumTest',
'LargeTest', 'EnormousTest', 'FlakyTest', 'LargeTest', 'EnormousTest', 'FlakyTest',
'DisabledTest', 'Manual', 'PerfTest']) 'DisabledTest', 'Manual', 'PerfTest'])
tests_missing_annotations = [] tests_missing_annotations = []
for test_method in test_apk.GetTestMethods(): for test_method in test_pkg.GetTestMethods():
annotations = frozenset(test_apk.GetTestAnnotations(test_method)) annotations = frozenset(test_pkg.GetTestAnnotations(test_method))
if (annotations.isdisjoint(test_size_annotations) and 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) tests_missing_annotations.append(test_method)
return sorted(tests_missing_annotations) return sorted(tests_missing_annotations)
if options.annotation: 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: if options.annotation.count(default_size_annotation) > 0:
tests_missing_annotations = _GetTestsMissingAnnotation(test_apk) tests_missing_annotations = _GetTestsMissingAnnotation(test_pkg)
if tests_missing_annotations: if tests_missing_annotations:
logging.warning('The following tests do not contain any annotation. ' logging.warning('The following tests do not contain any annotation. '
'Assuming "%s":\n%s', 'Assuming "%s":\n%s',
@ -58,8 +65,8 @@ def Dispatch(options, apks):
'\n'.join(tests_missing_annotations)) '\n'.join(tests_missing_annotations))
available_tests += tests_missing_annotations available_tests += tests_missing_annotations
else: else:
available_tests = [m for m in test_apk.GetTestMethods() available_tests = [m for m in test_pkg.GetTestMethods()
if not apk_info.ApkInfo.IsPythonDrivenTest(m)] if not test_pkg.IsPythonDrivenTest(m)]
coverage = os.environ.get('EMMA_INSTRUMENT') == 'true' coverage = os.environ.get('EMMA_INSTRUMENT') == 'true'
tests = [] tests = []
@ -92,7 +99,8 @@ def Dispatch(options, apks):
attached_devices = attached_devices[:1] attached_devices = attached_devices[:1]
def TestRunnerFactory(device, shard_index): 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, return shard.ShardAndRunTests(TestRunnerFactory, attached_devices, tests,
options.build_type) 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 base_test_runner
from pylib.base import test_result from pylib.base import test_result
import apk_info
_PERF_TEST_ANNOTATION = 'PerfTest' _PERF_TEST_ANNOTATION = 'PerfTest'
@ -47,8 +45,8 @@ class TestRunner(base_test_runner.BaseTestRunner):
'/chrome-profile*') '/chrome-profile*')
_DEVICE_HAS_TEST_FILES = {} _DEVICE_HAS_TEST_FILES = {}
def __init__(self, options, device, shard_index, coverage, apks, def __init__(self, options, device, shard_index, coverage, test_pkg,
ports_to_forward): ports_to_forward, is_uiautomator_test=False):
"""Create a new TestRunner. """Create a new TestRunner.
Args: Args:
@ -64,34 +62,30 @@ class TestRunner(base_test_runner.BaseTestRunner):
device: Attached android device. device: Attached android device.
shard_index: Shard index. shard_index: Shard index.
coverage: Collects coverage information if opted. coverage: Collects coverage information if opted.
apks: A list of ApkInfo objects need to be installed. The first element test_pkg: A TestPackage object.
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. ports_to_forward: A list of port numbers for which to set up forwarders.
Can be optionally requested by a test case. Can be optionally requested by a test case.
is_uiautomator_test: Whether this is a uiautomator test.
Raises: Raises:
Exception: if coverage metadata is not available. Exception: if coverage metadata is not available.
""" """
super(TestRunner, self).__init__(device, options.tool, options.build_type) super(TestRunner, self).__init__(device, options.tool, options.build_type)
self._lighttp_port = constants.LIGHTTPD_RANDOM_PORT_FIRST + shard_index 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.build_type = options.build_type
self.install_apk = options.install_apk
self.test_data = options.test_data self.test_data = options.test_data
self.save_perf_json = options.save_perf_json self.save_perf_json = options.save_perf_json
self.screenshot_failures = options.screenshot_failures self.screenshot_failures = options.screenshot_failures
self.wait_for_debugger = options.wait_for_debugger self.wait_for_debugger = options.wait_for_debugger
self.disable_assertions = options.disable_assertions self.disable_assertions = options.disable_assertions
self.coverage = coverage self.coverage = coverage
self.apks = apks self.test_pkg = test_pkg
self.test_apk = apks[0]
self.instrumentation_class_path = self.test_apk.GetPackageName()
self.ports_to_forward = ports_to_forward 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 self.forwarder = None
@ -125,10 +119,11 @@ class TestRunner(base_test_runner.BaseTestRunner):
self.adb.PushIfNeeded(host_test_files_path, self.adb.PushIfNeeded(host_test_files_path,
self.adb.GetExternalStorage() + '/' + self.adb.GetExternalStorage() + '/' +
TestRunner._DEVICE_DATA_DIR + '/' + dst_layer) TestRunner._DEVICE_DATA_DIR + '/' + dst_layer)
if self.install_apk: if self.is_uiautomator_test:
for apk in self.apks: self.test_pkg.Install(self.adb)
self.adb.ManagedInstall(apk.GetApkPath(), elif self.install_apk:
package_name=apk.GetPackageName()) self.test_pkg.Install(self.adb)
self.tool.CopyFiles() self.tool.CopyFiles()
TestRunner._DEVICE_HAS_TEST_FILES[self.device] = True TestRunner._DEVICE_HAS_TEST_FILES[self.device] = True
@ -253,7 +248,7 @@ class TestRunner(base_test_runner.BaseTestRunner):
Returns: Returns:
Whether the test is annotated as a performance test. 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): def SetupPerfMonitoringIfNeeded(self, test):
"""Sets up performance monitoring if the specified test requires it. """Sets up performance monitoring if the specified test requires it.
@ -352,7 +347,7 @@ class TestRunner(base_test_runner.BaseTestRunner):
def _GetIndividualTestTimeoutScale(self, test): def _GetIndividualTestTimeoutScale(self, test):
"""Returns the timeout scale for the given |test|.""" """Returns the timeout scale for the given |test|."""
annotations = self.apks[0].GetTestAnnotations(test) annotations = self.test_pkg.GetTestAnnotations(test)
timeout_scale = 1 timeout_scale = 1
if 'TimeoutScale' in annotations: if 'TimeoutScale' in annotations:
for annotation in annotations: for annotation in annotations:
@ -365,7 +360,7 @@ class TestRunner(base_test_runner.BaseTestRunner):
def _GetIndividualTestTimeoutSecs(self, test): def _GetIndividualTestTimeoutSecs(self, test):
"""Returns the timeout in seconds for the given |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: if 'Manual' in annotations:
return 600 * 60 return 600 * 60
if 'External' in annotations: if 'External' in annotations:
@ -382,29 +377,31 @@ class TestRunner(base_test_runner.BaseTestRunner):
Returns: Returns:
A test_result.TestResults object. A test_result.TestResults object.
""" """
instrumentation_path = (self.instrumentation_class_path +
'/android.test.InstrumentationTestRunner')
instrumentation_args = self._GetInstrumentationArgs()
raw_result = None raw_result = None
start_date_ms = None start_date_ms = None
test_results = test_result.TestResults() test_results = test_result.TestResults()
timeout=(self._GetIndividualTestTimeoutSecs(test) *
self._GetIndividualTestTimeoutScale(test) *
self.tool.GetTimeoutScale())
try: try:
self.TestSetup(test) self.TestSetup(test)
start_date_ms = int(time.time()) * 1000 start_date_ms = int(time.time()) * 1000
args_with_filter = dict(instrumentation_args)
args_with_filter['class'] = test if self.is_uiautomator_test:
# |raw_results| is a list that should contain self.adb.ClearApplicationState(self.package_name)
# a single TestResult object. # TODO(frankf): Stop-gap solution. Should use annotations.
logging.warn(args_with_filter) if 'FirstRun' in test:
(raw_results, _) = self.adb.Adb().StartInstrumentation( self.flags.RemoveFlags(['--disable-fre'])
instrumentation_path=instrumentation_path, else:
instrumentation_args=args_with_filter, self.flags.AddFlags(['--disable-fre'])
timeout_time=(self._GetIndividualTestTimeoutSecs(test) * raw_result = self.adb.RunUIAutomatorTest(
self._GetIndividualTestTimeoutScale(test) * test, self.test_pkg.GetPackageName(), timeout)
self.tool.GetTimeoutScale())) else:
raw_result = self.adb.RunInstrumentationTest(
test, self.test_pkg.GetPackageName(),
self._GetInstrumentationArgs(), timeout)
duration_ms = int(time.time()) * 1000 - start_date_ms duration_ms = int(time.time()) * 1000 - start_date_ms
assert len(raw_results) == 1
raw_result = raw_results[0]
status_code = raw_result.GetStatusCode() status_code = raw_result.GetStatusCode()
if status_code: if status_code:
log = raw_result.GetFailureReason() 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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Gathers information about APKs.""" """Module containing utilities for jar packages."""
import collections import collections
import logging import logging
@ -17,22 +17,9 @@ from pylib import constants
# If you change the cached output of proguard, increment this number # If you change the cached output of proguard, increment this number
PICKLE_FORMAT_VERSION = 1 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 TestPackageJar(object):
class ApkInfo(object): def __init__(self, jar_path):
"""Helper class for inspecting APKs."""
def __init__(self, apk_path, jar_path):
sdk_root = os.getenv('ANDROID_SDK_ROOT', constants.ANDROID_SDK_ROOT) sdk_root = os.getenv('ANDROID_SDK_ROOT', constants.ANDROID_SDK_ROOT)
self._PROGUARD_PATH = os.path.join(sdk_root, self._PROGUARD_PATH = os.path.join(sdk_root,
'tools/proguard/bin/proguard.sh') 'tools/proguard/bin/proguard.sh')
@ -46,18 +33,12 @@ class ApkInfo(object):
re.compile(r'\s*?- Constant element value.*$')) re.compile(r'\s*?- Constant element value.*$'))
self._PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$') 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): if not os.path.exists(jar_path):
raise Exception('%s not found, please build it' % jar_path) raise Exception('%s not found, please build it' % jar_path)
self._jar_path = jar_path self._jar_path = jar_path
self._annotation_map = collections.defaultdict(list) self._annotation_map = collections.defaultdict(list)
self._pickled_proguard_name = self._jar_path + '-proguard.pickle' self._pickled_proguard_name = self._jar_path + '-proguard.pickle'
self._test_methods = [] self._test_methods = []
self._Initialize()
def _Initialize(self):
if not self._GetCachedProguardData(): if not self._GetCachedProguardData():
self._GetProguardData() self._GetProguardData()
@ -145,13 +126,6 @@ class ApkInfo(object):
class_name, method = test.split('#') class_name, method = test.split('#')
return class_name.endswith('Test') and method.startswith('test') 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): def GetTestAnnotations(self, test):
"""Returns a list of all annotations for the given |test|. May be empty.""" """Returns a list of all annotations for the given |test|. May be empty."""
if not self._IsTestMethod(test): if not self._IsTestMethod(test):

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

@ -28,6 +28,7 @@ def AddBuildTypeOption(option_parser):
help='If set, run test suites under out/Release. ' help='If set, run test suites under out/Release. '
'Default is env var BUILDTYPE or Debug.') 'Default is env var BUILDTYPE or Debug.')
def AddInstallAPKOption(option_parser): def AddInstallAPKOption(option_parser):
"""Decorates OptionParser with apk option used to install the APK.""" """Decorates OptionParser with apk option used to install the APK."""
AddBuildTypeOption(option_parser) AddBuildTypeOption(option_parser)
@ -127,14 +128,12 @@ def AddGTestOptions(option_parser):
'the APK.') 'the APK.')
def AddInstrumentationOptions(option_parser): def AddCommonInstrumentationOptions(option_parser):
"""Decorates OptionParser with instrumentation tests options.""" """Decorates OptionParser with base instrumentation tests options."""
AddTestRunnerOptions(option_parser) AddTestRunnerOptions(option_parser)
option_parser.add_option('-w', '--wait_debugger', dest='wait_for_debugger', option_parser.add_option('-w', '--wait_debugger', dest='wait_for_debugger',
action='store_true', help='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', option_parser.add_option('-f', '--test_filter',
help='Test filter (if not fully qualified, ' help='Test filter (if not fully qualified, '
'will run all matches).') 'will run all matches).')
@ -153,11 +152,6 @@ def AddInstrumentationOptions(option_parser):
dest='number_of_runs', default=1, dest='number_of_runs', default=1,
help=('How many times to run each test, regardless ' help=('How many times to run each test, regardless '
'of the result. (Default is 1)')) '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', option_parser.add_option('--screenshot', dest='screenshot_failures',
action='store_true', action='store_true',
help='Capture screenshots of test failures') help='Capture screenshots of test failures')
@ -193,17 +187,44 @@ def AddInstrumentationOptions(option_parser):
'directory, and <source> is relative to the ' 'directory, and <source> is relative to the '
'chromium build directory.')) '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: if len(args) > 1:
option_parser.print_help(sys.stderr) option_parser.print_help(sys.stderr)
option_parser.error('Unknown arguments: %s' % args[1:]) option_parser.error('Unknown arguments: %s' % args[1:])
if options.java_only and options.python_only: if options.java_only and options.python_only:
option_parser.error('Options java_only (-j) and python_only (-p) ' option_parser.error('Options java_only (-j) and python_only (-p) '
'are mutually exclusive.') 'are mutually exclusive.')
if not options.test_apk:
option_parser.error('--test-apk must be specified.')
options.run_java_tests = True options.run_java_tests = True
options.run_python_tests = True options.run_python_tests = True
if options.java_only: if options.java_only:
@ -211,6 +232,21 @@ def ValidateInstrumentationOptions(option_parser, options, args):
elif options.python_only: elif options.python_only:
options.run_java_tests = False 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): if os.path.exists(options.test_apk):
# The APK is fully qualified, assume the JAR lives along side. # The APK is fully qualified, assume the JAR lives along side.
options.test_apk_path = options.test_apk 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( options.test_apk_jar_path = os.path.join(
_SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_TEST_JAVALIB_DIR, _SDK_OUT_DIR, options.build_type, constants.SDK_BUILD_TEST_JAVALIB_DIR,
'%s.jar' % options.test_apk) '%s.jar' % options.test_apk)
if options.annotation_str:
options.annotation = options.annotation_str.split()
elif options.test_filter: def ValidateUIAutomatorOptions(option_parser, options, args):
options.annotation = [] """Validate uiautomator options/arguments."""
else: ValidateCommonInstrumentationOptions(option_parser, options, args)
options.annotation = ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest']
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 # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
"""Runs both the Python and Java tests.""" """Runs both the Python and Java instrumentation tests."""
import optparse import optparse
import os import os
@ -16,7 +16,6 @@ from pylib import constants
from pylib import ports from pylib import ports
from pylib.base import test_result from pylib.base import test_result
from pylib.host_driven import run_python_tests from pylib.host_driven import run_python_tests
from pylib.instrumentation import apk_info
from pylib.instrumentation import dispatch from pylib.instrumentation import dispatch
from pylib.utils import run_tests_helper from pylib.utils import run_tests_helper
from pylib.utils import test_options_parser from pylib.utils import test_options_parser
@ -42,14 +41,12 @@ def DispatchInstrumentationTests(options):
if not ports.ResetTestServerPortAllocation(): if not ports.ResetTestServerPortAllocation():
raise Exception('Failed to reset test server port.') raise Exception('Failed to reset test server port.')
start_date = int(time.time() * 1000)
java_results = test_result.TestResults() java_results = test_result.TestResults()
python_results = test_result.TestResults() python_results = test_result.TestResults()
if options.run_java_tests: if options.run_java_tests:
java_results = dispatch.Dispatch( java_results = dispatch.Dispatch(options)
options,
[apk_info.ApkInfo(options.test_apk_path, options.test_apk_jar_path)])
if options.run_python_tests: if options.run_python_tests:
python_results = run_python_tests.DispatchPythonTests(options) 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))