Home | History | Annotate | Download | only in test_defs
      1 #!/usr/bin/python2.4
      2 #
      3 #
      4 # Copyright 2008, The Android Open Source Project
      5 #
      6 # Licensed under the Apache License, Version 2.0 (the "License");
      7 # you may not use this file except in compliance with the License.
      8 # You may obtain a copy of the License at
      9 #
     10 #     http://www.apache.org/licenses/LICENSE-2.0
     11 #
     12 # Unless required by applicable law or agreed to in writing, software
     13 # distributed under the License is distributed on an "AS IS" BASIS,
     14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 # See the License for the specific language governing permissions and
     16 # limitations under the License.
     17 
     18 """TestSuite definition for Android instrumentation tests."""
     19 
     20 import os
     21 import re
     22 
     23 # local imports
     24 import android_manifest
     25 from coverage import coverage
     26 import errors
     27 import logger
     28 import test_suite
     29 
     30 
     31 class InstrumentationTestSuite(test_suite.AbstractTestSuite):
     32   """Represents a java instrumentation test suite definition run on device."""
     33 
     34   DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
     35 
     36   def __init__(self):
     37     test_suite.AbstractTestSuite.__init__(self)
     38     self._package_name = None
     39     self._runner_name = self.DEFAULT_RUNNER
     40     self._class_name = None
     41     self._target_name = None
     42     self._java_package = None
     43 
     44   def GetPackageName(self):
     45     return self._package_name
     46 
     47   def SetPackageName(self, package_name):
     48     self._package_name = package_name
     49     return self
     50 
     51   def GetRunnerName(self):
     52     return self._runner_name
     53 
     54   def SetRunnerName(self, runner_name):
     55     self._runner_name = runner_name
     56     return self
     57 
     58   def GetClassName(self):
     59     return self._class_name
     60 
     61   def SetClassName(self, class_name):
     62     self._class_name = class_name
     63     return self
     64 
     65   def GetJavaPackageFilter(self):
     66     return self._java_package
     67 
     68   def SetJavaPackageFilter(self, java_package_name):
     69     """Configure the suite to only run tests in given java package."""
     70     self._java_package = java_package_name
     71     return self
     72 
     73   def GetTargetName(self):
     74     """Retrieve module that this test is targeting.
     75 
     76     Used for generating code coverage metrics.
     77     Returns:
     78       the module target name
     79     """
     80     return self._target_name
     81 
     82   def SetTargetName(self, target_name):
     83     self._target_name = target_name
     84     return self
     85 
     86   def GetBuildDependencies(self, options):
     87     if options.coverage_target_path:
     88       return [options.coverage_target_path]
     89     return []
     90 
     91   def Run(self, options, adb):
     92     """Run the provided test suite.
     93 
     94     Builds up an adb instrument command using provided input arguments.
     95 
     96     Args:
     97       options: command line options to provide to test run
     98       adb: adb_interface to device under test
     99 
    100     Raises:
    101       errors.AbortError: if fatal error occurs
    102     """
    103 
    104     test_class = self.GetClassName()
    105     if options.test_class is not None:
    106       test_class = options.test_class.lstrip()
    107       if test_class.startswith("."):
    108         test_class = self.GetPackageName() + test_class
    109     if options.test_method is not None:
    110       test_class = "%s#%s" % (test_class, options.test_method)
    111 
    112     test_package = self.GetJavaPackageFilter()
    113     if options.test_package:
    114       test_package = options.test_package
    115 
    116     if test_class and test_package:
    117       logger.Log('Error: both class and java package options are specified')
    118 
    119     instrumentation_args = {}
    120     if test_class is not None:
    121       instrumentation_args["class"] = test_class
    122     if test_package:
    123       instrumentation_args["package"] = test_package
    124     if options.test_size:
    125       instrumentation_args["size"] = options.test_size
    126     if options.wait_for_debugger:
    127       instrumentation_args["debug"] = "true"
    128     if options.suite_assign_mode:
    129       instrumentation_args["suiteAssignment"] = "true"
    130     if options.coverage:
    131       instrumentation_args["coverage"] = "true"
    132     if options.test_annotation:
    133       instrumentation_args["annotation"] = options.test_annotation
    134     if options.test_not_annotation:
    135       instrumentation_args["notAnnotation"] = options.test_not_annotation
    136     if options.preview:
    137       adb_cmd = adb.PreviewInstrumentationCommand(
    138           package_name=self.GetPackageName(),
    139           runner_name=self.GetRunnerName(),
    140           raw_mode=options.raw_mode,
    141           instrumentation_args=instrumentation_args)
    142       logger.Log(adb_cmd)
    143     elif options.coverage:
    144       coverage_gen = coverage.CoverageGenerator(adb)
    145       if options.coverage_target_path:
    146         coverage_target = coverage_gen.GetCoverageTargetForPath(options.coverage_target_path)
    147       elif self.GetTargetName():
    148         coverage_target = coverage_gen.GetCoverageTarget(self.GetTargetName())
    149       self._CheckInstrumentationInstalled(adb)
    150       # need to parse test output to determine path to coverage file
    151       logger.Log("Running in coverage mode, suppressing test output")
    152       try:
    153         (test_results, status_map) = adb.StartInstrumentationForPackage(
    154             package_name=self.GetPackageName(),
    155             runner_name=self.GetRunnerName(),
    156             timeout_time=60*60,
    157             instrumentation_args=instrumentation_args)
    158       except errors.InstrumentationError, errors.DeviceUnresponsiveError:
    159         return
    160       self._PrintTestResults(test_results)
    161       device_coverage_path = status_map.get("coverageFilePath", None)
    162       if device_coverage_path is None:
    163         logger.Log("Error: could not find coverage data on device")
    164         return
    165 
    166       coverage_file = coverage_gen.ExtractReport(
    167           self.GetName(), coverage_target, device_coverage_path,
    168           test_qualifier=options.test_size)
    169       if coverage_file is not None:
    170         logger.Log("Coverage report generated at %s" % coverage_file)
    171 
    172     else:
    173       self._CheckInstrumentationInstalled(adb)
    174       adb.StartInstrumentationNoResults(package_name=self.GetPackageName(),
    175                                         runner_name=self.GetRunnerName(),
    176                                         raw_mode=options.raw_mode,
    177                                         instrumentation_args=
    178                                         instrumentation_args)
    179 
    180   def _CheckInstrumentationInstalled(self, adb):
    181     if not adb.IsInstrumentationInstalled(self.GetPackageName(),
    182                                           self.GetRunnerName()):
    183       msg=("Could not find instrumentation %s/%s on device. Try forcing a "
    184            "rebuild by updating a source file, and re-executing runtest." %
    185            (self.GetPackageName(), self.GetRunnerName()))
    186       raise errors.AbortError(msg=msg)
    187 
    188   def _PrintTestResults(self, test_results):
    189     """Prints a summary of test result data to stdout.
    190 
    191     Args:
    192       test_results: a list of am_instrument_parser.TestResult
    193     """
    194     total_count = 0
    195     error_count = 0
    196     fail_count = 0
    197     for test_result in test_results:
    198       if test_result.GetStatusCode() == -1:  # error
    199         logger.Log("Error in %s: %s" % (test_result.GetTestName(),
    200                                         test_result.GetFailureReason()))
    201         error_count+=1
    202       elif test_result.GetStatusCode() == -2:  # failure
    203         logger.Log("Failure in %s: %s" % (test_result.GetTestName(),
    204                                           test_result.GetFailureReason()))
    205         fail_count+=1
    206       total_count+=1
    207     logger.Log("Tests run: %d, Failures: %d, Errors: %d" %
    208                (total_count, fail_count, error_count))
    209 
    210 def HasInstrumentationTest(path):
    211   """Determine if given path defines an instrumentation test.
    212 
    213   Args:
    214     path: file system path to instrumentation test.
    215   """
    216   manifest_parser = android_manifest.CreateAndroidManifest(path)
    217   if manifest_parser:
    218     return manifest_parser.GetInstrumentationNames()
    219   return False
    220 
    221 class InstrumentationTestFactory(test_suite.AbstractTestFactory):
    222   """A factory for creating InstrumentationTestSuites"""
    223 
    224   def __init__(self, test_root_path, build_path):
    225     test_suite.AbstractTestFactory.__init__(self, test_root_path,
    226                                             build_path)
    227 
    228   def CreateTests(self, sub_tests_path=None):
    229     """Create tests found in test_path.
    230 
    231     Will create a single InstrumentationTestSuite based on info found in
    232     AndroidManifest.xml found at build_path. Will set additional filters if
    233     test_path refers to a java package or java class.
    234     """
    235     tests = []
    236     class_name_arg = None
    237     java_package_name = None
    238     if sub_tests_path:
    239       # if path is java file, populate class name
    240       if self._IsJavaFile(sub_tests_path):
    241         class_name_arg = self._GetClassNameFromFile(sub_tests_path)
    242         logger.SilentLog('Using java test class %s' % class_name_arg)
    243       elif self._IsJavaPackage(sub_tests_path):
    244         java_package_name = self._GetPackageNameFromDir(sub_tests_path)
    245         logger.SilentLog('Using java package %s' % java_package_name)
    246     try:
    247       manifest_parser = android_manifest.AndroidManifest(app_path=
    248                                                          self.GetTestsRootPath())
    249       instrs = manifest_parser.GetInstrumentationNames()
    250       if not instrs:
    251         logger.Log('Could not find instrumentation declarations in %s at %s' %
    252                    (android_manifest.AndroidManifest.FILENAME,
    253                     self.GetBuildPath()))
    254         return tests
    255       elif len(instrs) > 1:
    256         logger.Log("Found multiple instrumentation declarations in %s/%s. "
    257                    "Only using first declared." %
    258                    (self.GetBuildPath(),
    259                     android_manifest.AndroidManifest.FILENAME))
    260       instr_name = manifest_parser.GetInstrumentationNames()[0]
    261       # escape inner class names
    262       instr_name = instr_name.replace('$', '\$')
    263       pkg_name = manifest_parser.GetPackageName()
    264       if instr_name.find(".") < 0:
    265         instr_name = "." + instr_name
    266       logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
    267       suite = InstrumentationTestSuite()
    268       suite.SetPackageName(pkg_name)
    269       suite.SetBuildPath(self.GetBuildPath())
    270       suite.SetRunnerName(instr_name)
    271       suite.SetName(pkg_name)
    272       suite.SetClassName(class_name_arg)
    273       suite.SetJavaPackageFilter(java_package_name)
    274       # this is a bit of a hack, assume if 'com.android.cts' is in
    275       # package name, this is a cts test
    276       # this logic can be removed altogether when cts tests no longer require
    277       # custom build steps
    278       if suite.GetPackageName().startswith('com.android.cts'):
    279         suite.SetSuite('cts')
    280       tests.append(suite)
    281       return tests
    282 
    283     except:
    284       logger.Log('Could not find or parse %s at %s' %
    285                  (android_manifest.AndroidManifest.FILENAME,
    286                   self.GetBuildPath()))
    287     return tests
    288 
    289   def _IsJavaFile(self, path):
    290     """Returns true if given file system path is a java file."""
    291     return os.path.isfile(path) and self._IsJavaFileName(path)
    292 
    293   def _IsJavaFileName(self, filename):
    294     """Returns true if given file name is a java file name."""
    295     return os.path.splitext(filename)[1] == '.java'
    296 
    297   def _IsJavaPackage(self, path):
    298     """Returns true if given file path is a java package.
    299 
    300     Currently assumes if any java file exists in this directory, than it
    301     represents a java package.
    302 
    303     Args:
    304       path: file system path of directory to check
    305 
    306     Returns:
    307       True if path is a java package
    308     """
    309     if not os.path.isdir(path):
    310       return False
    311     for file_name in os.listdir(path):
    312       if self._IsJavaFileName(file_name):
    313         return True
    314     return False
    315 
    316   def _GetClassNameFromFile(self, java_file_path):
    317     """Gets the fully qualified java class name from path.
    318 
    319     Args:
    320       java_file_path: file system path of java file
    321 
    322     Returns:
    323       fully qualified java class name or None.
    324     """
    325     package_name = self._GetPackageNameFromFile(java_file_path)
    326     if package_name:
    327       filename = os.path.basename(java_file_path)
    328       class_name = os.path.splitext(filename)[0]
    329       return '%s.%s' % (package_name, class_name)
    330     return None
    331 
    332   def _GetPackageNameFromDir(self, path):
    333     """Gets the java package name associated with given directory path.
    334 
    335     Caveat: currently just parses defined java package name from first java
    336     file found in directory.
    337 
    338     Args:
    339       path: file system path of directory
    340 
    341     Returns:
    342       the java package name or None
    343     """
    344     for filename in os.listdir(path):
    345       if self._IsJavaFileName(filename):
    346         return self._GetPackageNameFromFile(os.path.join(path, filename))
    347 
    348   def _GetPackageNameFromFile(self, java_file_path):
    349     """Gets the java package name associated with given java file path.
    350 
    351     Args:
    352       java_file_path: file system path of java file
    353 
    354     Returns:
    355       the java package name or None
    356     """
    357     logger.SilentLog('Looking for java package name in %s' % java_file_path)
    358     re_package = re.compile(r'package\s+(.*);')
    359     file_handle = open(java_file_path, 'r')
    360     for line in file_handle:
    361       match = re_package.match(line)
    362       if match:
    363         return match.group(1)
    364     return None
    365