Files
android_development/testrunner/test_defs/instrumentation_test.py
Brett Chabot 7454171f17 Coverage and unbundling changes for runtest.
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
2012-08-31 18:55:15 -07:00

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