[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:
Родитель
d52c1fbe79
Коммит
8a450f5010
|
@ -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))
|
Загрузка…
Ссылка в новой задаче