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