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