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      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
212def 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
223class 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