Stop using 'adb sync' in runtest. It was unreliable even when working with full platform builds, and doesn't work at all for unbundled apps. Instead, use output from build command to find produced artifacts and use 'adb install' where possible. However, note that this approach won't sync previously built artifacts to device. Also adjust to build system support for code coverage. Its no longer required to build libcore to get code coverage. Change-Id: I9c5d37897c9570d2d29db3ec82f5c53e60a8f485
342 lines
11 KiB
Python
342 lines
11 KiB
Python
#!/usr/bin/python2.4
|
|
#
|
|
#
|
|
# Copyright 2008, The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""TestSuite definition for Android instrumentation tests."""
|
|
|
|
import os
|
|
import re
|
|
|
|
# local imports
|
|
import android_manifest
|
|
import coverage
|
|
import errors
|
|
import logger
|
|
import test_suite
|
|
|
|
|
|
class InstrumentationTestSuite(test_suite.AbstractTestSuite):
|
|
"""Represents a java instrumentation test suite definition run on device."""
|
|
|
|
DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
|
|
|
|
def __init__(self):
|
|
test_suite.AbstractTestSuite.__init__(self)
|
|
self._package_name = None
|
|
self._runner_name = self.DEFAULT_RUNNER
|
|
self._class_name = None
|
|
self._target_name = None
|
|
self._java_package = None
|
|
|
|
def GetPackageName(self):
|
|
return self._package_name
|
|
|
|
def SetPackageName(self, package_name):
|
|
self._package_name = package_name
|
|
return self
|
|
|
|
def GetRunnerName(self):
|
|
return self._runner_name
|
|
|
|
def SetRunnerName(self, runner_name):
|
|
self._runner_name = runner_name
|
|
return self
|
|
|
|
def GetClassName(self):
|
|
return self._class_name
|
|
|
|
def SetClassName(self, class_name):
|
|
self._class_name = class_name
|
|
return self
|
|
|
|
def GetJavaPackageFilter(self):
|
|
return self._java_package
|
|
|
|
def SetJavaPackageFilter(self, java_package_name):
|
|
"""Configure the suite to only run tests in given java package."""
|
|
self._java_package = java_package_name
|
|
return self
|
|
|
|
def GetTargetName(self):
|
|
"""Retrieve module that this test is targeting.
|
|
|
|
Used for generating code coverage metrics.
|
|
Returns:
|
|
the module target name
|
|
"""
|
|
return self._target_name
|
|
|
|
def SetTargetName(self, target_name):
|
|
self._target_name = target_name
|
|
return self
|
|
|
|
def GetBuildDependencies(self, options):
|
|
return []
|
|
|
|
def Run(self, options, adb):
|
|
"""Run the provided test suite.
|
|
|
|
Builds up an adb instrument command using provided input arguments.
|
|
|
|
Args:
|
|
options: command line options to provide to test run
|
|
adb: adb_interface to device under test
|
|
|
|
Raises:
|
|
errors.AbortError: if fatal error occurs
|
|
"""
|
|
|
|
test_class = self.GetClassName()
|
|
if options.test_class is not None:
|
|
test_class = options.test_class.lstrip()
|
|
if test_class.startswith("."):
|
|
test_class = self.GetPackageName() + test_class
|
|
if options.test_method is not None:
|
|
test_class = "%s#%s" % (test_class, options.test_method)
|
|
|
|
test_package = self.GetJavaPackageFilter()
|
|
if options.test_package:
|
|
test_package = options.test_package
|
|
|
|
if test_class and test_package:
|
|
logger.Log('Error: both class and java package options are specified')
|
|
|
|
instrumentation_args = {}
|
|
if test_class is not None:
|
|
instrumentation_args["class"] = test_class
|
|
if test_package:
|
|
instrumentation_args["package"] = test_package
|
|
if options.test_size:
|
|
instrumentation_args["size"] = options.test_size
|
|
if options.wait_for_debugger:
|
|
instrumentation_args["debug"] = "true"
|
|
if options.suite_assign_mode:
|
|
instrumentation_args["suiteAssignment"] = "true"
|
|
if options.coverage:
|
|
instrumentation_args["coverage"] = "true"
|
|
if options.test_annotation:
|
|
instrumentation_args["annotation"] = options.test_annotation
|
|
if options.test_not_annotation:
|
|
instrumentation_args["notAnnotation"] = options.test_not_annotation
|
|
if options.preview:
|
|
adb_cmd = adb.PreviewInstrumentationCommand(
|
|
package_name=self.GetPackageName(),
|
|
runner_name=self.GetRunnerName(),
|
|
raw_mode=options.raw_mode,
|
|
instrumentation_args=instrumentation_args)
|
|
logger.Log(adb_cmd)
|
|
elif options.coverage:
|
|
coverage_gen = coverage.CoverageGenerator(adb)
|
|
# need to parse test output to determine path to coverage file
|
|
logger.Log("Running in coverage mode, suppressing test output")
|
|
try:
|
|
(test_results, status_map) = adb.StartInstrumentationForPackage(
|
|
package_name=self.GetPackageName(),
|
|
runner_name=self.GetRunnerName(),
|
|
timeout_time=60*60,
|
|
instrumentation_args=instrumentation_args)
|
|
except errors.InstrumentationError, errors.DeviceUnresponsiveError:
|
|
return
|
|
self._PrintTestResults(test_results)
|
|
device_coverage_path = status_map.get("coverageFilePath", None)
|
|
if device_coverage_path is None:
|
|
logger.Log("Error: could not find coverage data on device")
|
|
return
|
|
|
|
coverage_file = coverage_gen.ExtractReport(
|
|
self, device_coverage_path, test_qualifier=options.test_size)
|
|
if coverage_file is not None:
|
|
logger.Log("Coverage report generated at %s" % coverage_file)
|
|
else:
|
|
adb.StartInstrumentationNoResults(
|
|
package_name=self.GetPackageName(),
|
|
runner_name=self.GetRunnerName(),
|
|
raw_mode=options.raw_mode,
|
|
instrumentation_args=instrumentation_args)
|
|
|
|
def _PrintTestResults(self, test_results):
|
|
"""Prints a summary of test result data to stdout.
|
|
|
|
Args:
|
|
test_results: a list of am_instrument_parser.TestResult
|
|
"""
|
|
total_count = 0
|
|
error_count = 0
|
|
fail_count = 0
|
|
for test_result in test_results:
|
|
if test_result.GetStatusCode() == -1: # error
|
|
logger.Log("Error in %s: %s" % (test_result.GetTestName(),
|
|
test_result.GetFailureReason()))
|
|
error_count+=1
|
|
elif test_result.GetStatusCode() == -2: # failure
|
|
logger.Log("Failure in %s: %s" % (test_result.GetTestName(),
|
|
test_result.GetFailureReason()))
|
|
fail_count+=1
|
|
total_count+=1
|
|
logger.Log("Tests run: %d, Failures: %d, Errors: %d" %
|
|
(total_count, fail_count, error_count))
|
|
|
|
|
|
def HasInstrumentationTest(path):
|
|
"""Determine if given path defines an instrumentation test.
|
|
|
|
Args:
|
|
path: file system path to instrumentation test.
|
|
"""
|
|
manifest_parser = android_manifest.CreateAndroidManifest(path)
|
|
if manifest_parser:
|
|
return manifest_parser.GetInstrumentationNames()
|
|
return False
|
|
|
|
class InstrumentationTestFactory(test_suite.AbstractTestFactory):
|
|
"""A factory for creating InstrumentationTestSuites"""
|
|
|
|
def __init__(self, test_root_path, build_path):
|
|
test_suite.AbstractTestFactory.__init__(self, test_root_path,
|
|
build_path)
|
|
|
|
def CreateTests(self, sub_tests_path=None):
|
|
"""Create tests found in test_path.
|
|
|
|
Will create a single InstrumentationTestSuite based on info found in
|
|
AndroidManifest.xml found at build_path. Will set additional filters if
|
|
test_path refers to a java package or java class.
|
|
"""
|
|
tests = []
|
|
class_name_arg = None
|
|
java_package_name = None
|
|
if sub_tests_path:
|
|
# if path is java file, populate class name
|
|
if self._IsJavaFile(sub_tests_path):
|
|
class_name_arg = self._GetClassNameFromFile(sub_tests_path)
|
|
logger.SilentLog('Using java test class %s' % class_name_arg)
|
|
elif self._IsJavaPackage(sub_tests_path):
|
|
java_package_name = self._GetPackageNameFromDir(sub_tests_path)
|
|
logger.SilentLog('Using java package %s' % java_package_name)
|
|
try:
|
|
manifest_parser = android_manifest.AndroidManifest(app_path=
|
|
self.GetTestsRootPath())
|
|
instrs = manifest_parser.GetInstrumentationNames()
|
|
if not instrs:
|
|
logger.Log('Could not find instrumentation declarations in %s at %s' %
|
|
(android_manifest.AndroidManifest.FILENAME,
|
|
self.GetBuildPath()))
|
|
return tests
|
|
|
|
for instr_name in manifest_parser.GetInstrumentationNames():
|
|
pkg_name = manifest_parser.GetPackageName()
|
|
if instr_name.find(".") < 0:
|
|
instr_name = "." + instr_name
|
|
logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
|
|
suite = InstrumentationTestSuite()
|
|
suite.SetPackageName(pkg_name)
|
|
suite.SetBuildPath(self.GetBuildPath())
|
|
suite.SetRunnerName(instr_name)
|
|
suite.SetName(pkg_name)
|
|
suite.SetClassName(class_name_arg)
|
|
suite.SetJavaPackageFilter(java_package_name)
|
|
# this is a bit of a hack, assume if 'com.android.cts' is in
|
|
# package name, this is a cts test
|
|
# this logic can be removed altogether when cts tests no longer require
|
|
# custom build steps
|
|
if suite.GetPackageName().startswith('com.android.cts'):
|
|
suite.SetSuite('cts')
|
|
tests.append(suite)
|
|
return tests
|
|
|
|
except:
|
|
logger.Log('Could not find or parse %s at %s' %
|
|
(android_manifest.AndroidManifest.FILENAME,
|
|
self.GetBuildPath()))
|
|
return tests
|
|
|
|
def _IsJavaFile(self, path):
|
|
"""Returns true if given file system path is a java file."""
|
|
return os.path.isfile(path) and self._IsJavaFileName(path)
|
|
|
|
def _IsJavaFileName(self, filename):
|
|
"""Returns true if given file name is a java file name."""
|
|
return os.path.splitext(filename)[1] == '.java'
|
|
|
|
def _IsJavaPackage(self, path):
|
|
"""Returns true if given file path is a java package.
|
|
|
|
Currently assumes if any java file exists in this directory, than it
|
|
represents a java package.
|
|
|
|
Args:
|
|
path: file system path of directory to check
|
|
|
|
Returns:
|
|
True if path is a java package
|
|
"""
|
|
if not os.path.isdir(path):
|
|
return False
|
|
for file_name in os.listdir(path):
|
|
if self._IsJavaFileName(file_name):
|
|
return True
|
|
return False
|
|
|
|
def _GetClassNameFromFile(self, java_file_path):
|
|
"""Gets the fully qualified java class name from path.
|
|
|
|
Args:
|
|
java_file_path: file system path of java file
|
|
|
|
Returns:
|
|
fully qualified java class name or None.
|
|
"""
|
|
package_name = self._GetPackageNameFromFile(java_file_path)
|
|
if package_name:
|
|
filename = os.path.basename(java_file_path)
|
|
class_name = os.path.splitext(filename)[0]
|
|
return '%s.%s' % (package_name, class_name)
|
|
return None
|
|
|
|
def _GetPackageNameFromDir(self, path):
|
|
"""Gets the java package name associated with given directory path.
|
|
|
|
Caveat: currently just parses defined java package name from first java
|
|
file found in directory.
|
|
|
|
Args:
|
|
path: file system path of directory
|
|
|
|
Returns:
|
|
the java package name or None
|
|
"""
|
|
for filename in os.listdir(path):
|
|
if self._IsJavaFileName(filename):
|
|
return self._GetPackageNameFromFile(os.path.join(path, filename))
|
|
|
|
def _GetPackageNameFromFile(self, java_file_path):
|
|
"""Gets the java package name associated with given java file path.
|
|
|
|
Args:
|
|
java_file_path: file system path of java file
|
|
|
|
Returns:
|
|
the java package name or None
|
|
"""
|
|
logger.SilentLog('Looking for java package name in %s' % java_file_path)
|
|
re_package = re.compile(r'package\s+(.*);')
|
|
file_handle = open(java_file_path, 'r')
|
|
for line in file_handle:
|
|
match = re_package.match(line)
|
|
if match:
|
|
return match.group(1)
|
|
return None
|