• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      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
210def 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
221class 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
256      for instr_name in manifest_parser.GetInstrumentationNames():
257        pkg_name = manifest_parser.GetPackageName()
258        if instr_name.find(".") < 0:
259          instr_name = "." + instr_name
260        logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
261        suite = InstrumentationTestSuite()
262        suite.SetPackageName(pkg_name)
263        suite.SetBuildPath(self.GetBuildPath())
264        suite.SetRunnerName(instr_name)
265        suite.SetName(pkg_name)
266        suite.SetClassName(class_name_arg)
267        suite.SetJavaPackageFilter(java_package_name)
268        # this is a bit of a hack, assume if 'com.android.cts' is in
269        # package name, this is a cts test
270        # this logic can be removed altogether when cts tests no longer require
271        # custom build steps
272        if suite.GetPackageName().startswith('com.android.cts'):
273          suite.SetSuite('cts')
274        tests.append(suite)
275      return tests
276
277    except:
278      logger.Log('Could not find or parse %s at %s' %
279                 (android_manifest.AndroidManifest.FILENAME,
280                  self.GetBuildPath()))
281    return tests
282
283  def _IsJavaFile(self, path):
284    """Returns true if given file system path is a java file."""
285    return os.path.isfile(path) and self._IsJavaFileName(path)
286
287  def _IsJavaFileName(self, filename):
288    """Returns true if given file name is a java file name."""
289    return os.path.splitext(filename)[1] == '.java'
290
291  def _IsJavaPackage(self, path):
292    """Returns true if given file path is a java package.
293
294    Currently assumes if any java file exists in this directory, than it
295    represents a java package.
296
297    Args:
298      path: file system path of directory to check
299
300    Returns:
301      True if path is a java package
302    """
303    if not os.path.isdir(path):
304      return False
305    for file_name in os.listdir(path):
306      if self._IsJavaFileName(file_name):
307        return True
308    return False
309
310  def _GetClassNameFromFile(self, java_file_path):
311    """Gets the fully qualified java class name from path.
312
313    Args:
314      java_file_path: file system path of java file
315
316    Returns:
317      fully qualified java class name or None.
318    """
319    package_name = self._GetPackageNameFromFile(java_file_path)
320    if package_name:
321      filename = os.path.basename(java_file_path)
322      class_name = os.path.splitext(filename)[0]
323      return '%s.%s' % (package_name, class_name)
324    return None
325
326  def _GetPackageNameFromDir(self, path):
327    """Gets the java package name associated with given directory path.
328
329    Caveat: currently just parses defined java package name from first java
330    file found in directory.
331
332    Args:
333      path: file system path of directory
334
335    Returns:
336      the java package name or None
337    """
338    for filename in os.listdir(path):
339      if self._IsJavaFileName(filename):
340        return self._GetPackageNameFromFile(os.path.join(path, filename))
341
342  def _GetPackageNameFromFile(self, java_file_path):
343    """Gets the java package name associated with given java file path.
344
345    Args:
346      java_file_path: file system path of java file
347
348    Returns:
349      the java package name or None
350    """
351    logger.SilentLog('Looking for java package name in %s' % java_file_path)
352    re_package = re.compile(r'package\s+(.*);')
353    file_handle = open(java_file_path, 'r')
354    for line in file_handle:
355      match = re_package.match(line)
356      if match:
357        return match.group(1)
358    return None
359