• 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      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