| #!/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 |
| from coverage 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): |
| if options.coverage_target_path: |
| return [options.coverage_target_path] |
| 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) |
| if options.coverage_target_path: |
| coverage_target = coverage_gen.GetCoverageTargetForPath(options.coverage_target_path) |
| elif self.GetTargetName(): |
| coverage_target = coverage_gen.GetCoverageTarget(self.GetTargetName()) |
| self._CheckInstrumentationInstalled(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.GetName(), coverage_target, device_coverage_path, |
| test_qualifier=options.test_size) |
| if coverage_file is not None: |
| logger.Log("Coverage report generated at %s" % coverage_file) |
| |
| else: |
| self._CheckInstrumentationInstalled(adb) |
| adb.StartInstrumentationNoResults(package_name=self.GetPackageName(), |
| runner_name=self.GetRunnerName(), |
| raw_mode=options.raw_mode, |
| instrumentation_args= |
| instrumentation_args) |
| |
| def _CheckInstrumentationInstalled(self, adb): |
| if not adb.IsInstrumentationInstalled(self.GetPackageName(), |
| self.GetRunnerName()): |
| msg=("Could not find instrumentation %s/%s on device. Try forcing a " |
| "rebuild by updating a source file, and re-executing runtest." % |
| (self.GetPackageName(), self.GetRunnerName())) |
| raise errors.AbortError(msg=msg) |
| |
| 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 |