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