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             user=options.user)
    159       except errors.InstrumentationError, errors.DeviceUnresponsiveError:
    160         return
    161       self._PrintTestResults(test_results)
    162       device_coverage_path = status_map.get("coverageFilePath", None)
    163       if device_coverage_path is None:
    164         logger.Log("Error: could not find coverage data on device")
    165         return
    166 
    167       coverage_file = coverage_gen.ExtractReport(
    168           self.GetName(), coverage_target, device_coverage_path,
    169           test_qualifier=options.test_size)
    170       if coverage_file is not None:
    171         logger.Log("Coverage report generated at %s" % coverage_file)
    172 
    173     else:
    174       self._CheckInstrumentationInstalled(adb)
    175       adb.StartInstrumentationNoResults(package_name=self.GetPackageName(),
    176                                         runner_name=self.GetRunnerName(),
    177                                         raw_mode=options.raw_mode,
    178                                         instrumentation_args=
    179                                         instrumentation_args,
    180                                         user=options.user)
    181 
    182   def _CheckInstrumentationInstalled(self, adb):
    183     if not adb.IsInstrumentationInstalled(self.GetPackageName(),
    184                                           self.GetRunnerName()):
    185       msg=("Could not find instrumentation %s/%s on device. Try forcing a "
    186            "rebuild by updating a source file, and re-executing runtest." %
    187            (self.GetPackageName(), self.GetRunnerName()))
    188       raise errors.AbortError(msg=msg)
    189 
    190   def _PrintTestResults(self, test_results):
    191     """Prints a summary of test result data to stdout.
    192 
    193     Args:
    194       test_results: a list of am_instrument_parser.TestResult
    195     """
    196     total_count = 0
    197     error_count = 0
    198     fail_count = 0
    199     for test_result in test_results:
    200       if test_result.GetStatusCode() == -1:  # error
    201         logger.Log("Error in %s: %s" % (test_result.GetTestName(),
    202                                         test_result.GetFailureReason()))
    203         error_count+=1
    204       elif test_result.GetStatusCode() == -2:  # failure
    205         logger.Log("Failure in %s: %s" % (test_result.GetTestName(),
    206                                           test_result.GetFailureReason()))
    207         fail_count+=1
    208       total_count+=1
    209     logger.Log("Tests run: %d, Failures: %d, Errors: %d" %
    210                (total_count, fail_count, error_count))
    211 
    212 def HasInstrumentationTest(path):
    213   """Determine if given path defines an instrumentation test.
    214 
    215   Args:
    216     path: file system path to instrumentation test.
    217   """
    218   manifest_parser = android_manifest.CreateAndroidManifest(path)
    219   if manifest_parser:
    220     return manifest_parser.GetInstrumentationNames()
    221   return False
    222 
    223 class InstrumentationTestFactory(test_suite.AbstractTestFactory):
    224   """A factory for creating InstrumentationTestSuites"""
    225 
    226   def __init__(self, test_root_path, build_path):
    227     test_suite.AbstractTestFactory.__init__(self, test_root_path,
    228                                             build_path)
    229 
    230   def CreateTests(self, sub_tests_path=None):
    231     """Create tests found in test_path.
    232 
    233     Will create a single InstrumentationTestSuite based on info found in
    234     AndroidManifest.xml found at build_path. Will set additional filters if
    235     test_path refers to a java package or java class.
    236     """
    237     tests = []
    238     class_name_arg = None
    239     java_package_name = None
    240     if sub_tests_path:
    241       # if path is java file, populate class name
    242       if self._IsJavaFile(sub_tests_path):
    243         class_name_arg = self._GetClassNameFromFile(sub_tests_path)
    244         logger.SilentLog('Using java test class %s' % class_name_arg)
    245       elif self._IsJavaPackage(sub_tests_path):
    246         java_package_name = self._GetPackageNameFromDir(sub_tests_path)
    247         logger.SilentLog('Using java package %s' % java_package_name)
    248     try:
    249       manifest_parser = android_manifest.AndroidManifest(app_path=
    250                                                          self.GetTestsRootPath())
    251       instrs = manifest_parser.GetInstrumentationNames()
    252       if not instrs:
    253         logger.Log('Could not find instrumentation declarations in %s at %s' %
    254                    (android_manifest.AndroidManifest.FILENAME,
    255                     self.GetBuildPath()))
    256         return tests
    257       elif len(instrs) > 1:
    258         logger.Log("Found multiple instrumentation declarations in %s/%s. "
    259                    "Only using first declared." %
    260                    (self.GetBuildPath(),
    261                     android_manifest.AndroidManifest.FILENAME))
    262       instr_name = manifest_parser.GetInstrumentationNames()[0]
    263       # escape inner class names
    264       instr_name = instr_name.replace('$', '\$')
    265       pkg_name = manifest_parser.GetPackageName()
    266       if instr_name.find(".") < 0:
    267         instr_name = "." + instr_name
    268       logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
    269       suite = InstrumentationTestSuite()
    270       suite.SetPackageName(pkg_name)
    271       suite.SetBuildPath(self.GetBuildPath())
    272       suite.SetRunnerName(instr_name)
    273       suite.SetName(pkg_name)
    274       suite.SetClassName(class_name_arg)
    275       suite.SetJavaPackageFilter(java_package_name)
    276       tests.append(suite)
    277       return tests
    278 
    279     except:
    280       logger.Log('Could not find or parse %s at %s' %
    281                  (android_manifest.AndroidManifest.FILENAME,
    282                   self.GetBuildPath()))
    283     return tests
    284 
    285   def _IsJavaFile(self, path):
    286     """Returns true if given file system path is a java file."""
    287     return os.path.isfile(path) and self._IsJavaFileName(path)
    288 
    289   def _IsJavaFileName(self, filename):
    290     """Returns true if given file name is a java file name."""
    291     return os.path.splitext(filename)[1] == '.java'
    292 
    293   def _IsJavaPackage(self, path):
    294     """Returns true if given file path is a java package.
    295 
    296     Currently assumes if any java file exists in this directory, than it
    297     represents a java package.
    298 
    299     Args:
    300       path: file system path of directory to check
    301 
    302     Returns:
    303       True if path is a java package
    304     """
    305     if not os.path.isdir(path):
    306       return False
    307     for file_name in os.listdir(path):
    308       if self._IsJavaFileName(file_name):
    309         return True
    310     return False
    311 
    312   def _GetClassNameFromFile(self, java_file_path):
    313     """Gets the fully qualified java class name from path.
    314 
    315     Args:
    316       java_file_path: file system path of java file
    317 
    318     Returns:
    319       fully qualified java class name or None.
    320     """
    321     package_name = self._GetPackageNameFromFile(java_file_path)
    322     if package_name:
    323       filename = os.path.basename(java_file_path)
    324       class_name = os.path.splitext(filename)[0]
    325       return '%s.%s' % (package_name, class_name)
    326     return None
    327 
    328   def _GetPackageNameFromDir(self, path):
    329     """Gets the java package name associated with given directory path.
    330 
    331     Caveat: currently just parses defined java package name from first java
    332     file found in directory.
    333 
    334     Args:
    335       path: file system path of directory
    336 
    337     Returns:
    338       the java package name or None
    339     """
    340     for filename in os.listdir(path):
    341       if self._IsJavaFileName(filename):
    342         return self._GetPackageNameFromFile(os.path.join(path, filename))
    343 
    344   def _GetPackageNameFromFile(self, java_file_path):
    345     """Gets the java package name associated with given java file path.
    346 
    347     Args:
    348       java_file_path: file system path of java file
    349 
    350     Returns:
    351       the java package name or None
    352     """
    353     logger.SilentLog('Looking for java package name in %s' % java_file_path)
    354     re_package = re.compile(r'package\s+(.*);')
    355     file_handle = open(java_file_path, 'r')
    356     for line in file_handle:
    357       match = re_package.match(line)
    358       if match:
    359         return match.group(1)
    360     return None
    361