• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2.4
2#
3#
4# Copyright 2009, 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"""Utility to find instrumentation test definitions from file system."""
19
20# python imports
21import os
22import re
23
24# local imports
25import android_build
26import android_manifest
27import android_mk
28import instrumentation_test
29import logger
30
31
32class TestWalker(object):
33  """Finds instrumentation tests from filesystem."""
34
35  def FindTests(self, path):
36    """Gets list of Android instrumentation tests found at given path.
37
38    Tests are created from the <instrumentation> tags found in
39    AndroidManifest.xml files relative to the given path.
40
41    FindTests will first scan sub-folders of path for tests. If none are found,
42    it will scan the file system upwards until a AndroidManifest.xml is found
43    or the Android build root is reached.
44
45    Some sample values for path:
46    - a parent directory containing many tests:
47    ie development/samples will return tests for instrumentation's in ApiDemos,
48    ApiDemos/tests, Notepad/tests etc
49    - a java test class file
50    ie ApiDemos/tests/src/../ApiDemosTest.java will return a test for
51    the instrumentation in ApiDemos/tests, with the class name filter set to
52    ApiDemosTest
53    - a java package directory
54    ie ApiDemos/tests/src/com/example/android/apis will return a test for
55    the instrumentation in ApiDemos/tests, with the java package filter set
56    to com.example.android.apis.
57
58    Args:
59      path: file system path to search
60
61    Returns:
62      list of test suites that support operations defined by
63      test_suite.AbstractTestSuite
64    """
65    if not os.path.exists(path):
66      logger.Log('%s does not exist' % path)
67      return []
68    abspath = os.path.abspath(path)
69    # ensure path is in ANDROID_BUILD_ROOT
70    self._build_top = android_build.GetTop()
71    if not self._IsPathInBuildTree(abspath):
72      logger.Log('%s is not a sub-directory of build root %s' %
73                 (path, self._build_top))
74      return []
75
76    # first, assume path is a parent directory, which specifies to run all
77    # tests within this directory
78    tests = self._FindSubTests(abspath, [])
79    if not tests:
80      logger.SilentLog('No tests found within %s, searching upwards' % path)
81      tests = self._FindUpstreamTests(abspath)
82    return tests
83
84  def _IsPathInBuildTree(self, path):
85    """Return true if given path is within current Android build tree.
86
87    Args:
88      path: absolute file system path
89
90    Returns:
91      True if path is within Android build tree
92    """
93    return os.path.commonprefix([self._build_top, path]) == self._build_top
94
95  def _MakePathRelativeToBuild(self, path):
96    """Convert given path to one relative to build tree root.
97
98    Args:
99      path: absolute file system path to convert.
100
101    Returns:
102      The converted path relative to build tree root.
103
104    Raises:
105      ValueError: if path is not within build tree
106    """
107    if not self._IsPathInBuildTree(path):
108      raise ValueError
109    build_path_len = len(self._build_top) + 1
110    # return string with common build_path removed
111    return path[build_path_len:]
112
113  def _FindSubTests(self, path, tests, build_path=None):
114    """Recursively finds all tests within given path.
115
116    Args:
117      path: absolute file system path to check
118      tests: current list of found tests
119      build_path: the parent directory where Android.mk was found
120
121    Returns:
122      updated list of tests
123    """
124    if not os.path.isdir(path):
125      return tests
126    filenames = os.listdir(path)
127    # Try to build as much of original path as possible, so
128    # keep track of upper-most parent directory where Android.mk was found
129    # this is also necessary in case of overlapping tests
130    # ie if a test exists at 'foo' directory  and 'foo/sub', attempting to
131    # build both 'foo' and 'foo/sub' will fail.
132    if not build_path and filenames.count(android_mk.AndroidMK.FILENAME):
133      build_path = self._MakePathRelativeToBuild(path)
134    if filenames.count(android_manifest.AndroidManifest.FILENAME):
135      # found a manifest! now parse it to find the test definition(s)
136      manifest = android_manifest.AndroidManifest(app_path=path)
137      tests.extend(self._CreateSuitesFromManifest(manifest, build_path))
138    for filename in filenames:
139      self._FindSubTests(os.path.join(path, filename), tests, build_path)
140    return tests
141
142  def _FindUpstreamTests(self, path):
143    """Find tests defined upward from given path.
144
145    Args:
146      path: the location to start searching. If it points to a java class file
147        or java package dir, the appropriate test suite filters will be set
148
149    Returns:
150      list of test_suite.AbstractTestSuite found, may be empty
151    """
152    class_name_arg = None
153    package_name = None
154    # if path is java file, populate class name
155    if self._IsJavaFile(path):
156      class_name_arg = self._GetClassNameFromFile(path)
157      logger.SilentLog('Using java test class %s' % class_name_arg)
158    elif self._IsJavaPackage(path):
159      package_name = self._GetPackageNameFromDir(path)
160      logger.SilentLog('Using java package %s' % package_name)
161    manifest = self._FindUpstreamManifest(path)
162    if manifest:
163      logger.SilentLog('Found AndroidManifest at %s' % manifest.GetAppPath())
164      build_path = self._MakePathRelativeToBuild(manifest.GetAppPath())
165      return self._CreateSuitesFromManifest(manifest,
166                                            build_path,
167                                            class_name=class_name_arg,
168                                            java_package_name=package_name)
169
170  def _IsJavaFile(self, path):
171    """Returns true if given file system path is a java file."""
172    return os.path.isfile(path) and self._IsJavaFileName(path)
173
174  def _IsJavaFileName(self, filename):
175    """Returns true if given file name is a java file name."""
176    return os.path.splitext(filename)[1] == '.java'
177
178  def _IsJavaPackage(self, path):
179    """Returns true if given file path is a java package.
180
181    Currently assumes if any java file exists in this directory, than it
182    represents a java package.
183
184    Args:
185      path: file system path of directory to check
186
187    Returns:
188      True if path is a java package
189    """
190    if not os.path.isdir(path):
191      return False
192    for file_name in os.listdir(path):
193      if self._IsJavaFileName(file_name):
194        return True
195    return False
196
197  def _GetClassNameFromFile(self, java_file_path):
198    """Gets the fully qualified java class name from path.
199
200    Args:
201      java_file_path: file system path of java file
202
203    Returns:
204      fully qualified java class name or None.
205    """
206    package_name = self._GetPackageNameFromFile(java_file_path)
207    if package_name:
208      filename = os.path.basename(java_file_path)
209      class_name = os.path.splitext(filename)[0]
210      return '%s.%s' % (package_name, class_name)
211    return None
212
213  def _GetPackageNameFromDir(self, path):
214    """Gets the java package name associated with given directory path.
215
216    Caveat: currently just parses defined java package name from first java
217    file found in directory.
218
219    Args:
220      path: file system path of directory
221
222    Returns:
223      the java package name or None
224    """
225    for filename in os.listdir(path):
226      if self._IsJavaFileName(filename):
227        return self._GetPackageNameFromFile(os.path.join(path, filename))
228
229  def _GetPackageNameFromFile(self, java_file_path):
230    """Gets the java package name associated with given java file path.
231
232    Args:
233      java_file_path: file system path of java file
234
235    Returns:
236      the java package name or None
237    """
238    logger.SilentLog('Looking for java package name in %s' % java_file_path)
239    re_package = re.compile(r'package\s+(.*);')
240    file_handle = open(java_file_path, 'r')
241    for line in file_handle:
242      match = re_package.match(line)
243      if match:
244        return match.group(1)
245    return None
246
247  def _FindUpstreamManifest(self, path):
248    """Recursively searches filesystem upwards for a AndroidManifest file.
249
250    Args:
251      path: file system path to search
252
253    Returns:
254      the AndroidManifest found or None
255    """
256    if (os.path.isdir(path) and
257        os.listdir(path).count(android_manifest.AndroidManifest.FILENAME)):
258      return android_manifest.AndroidManifest(app_path=path)
259    dirpath = os.path.dirname(path)
260    if self._IsPathInBuildTree(path):
261      return self._FindUpstreamManifest(dirpath)
262    logger.Log('AndroidManifest.xml not found')
263    return None
264
265  def _CreateSuitesFromManifest(self, manifest, build_path, class_name=None,
266                                java_package_name=None):
267    """Creates TestSuites from a AndroidManifest.
268
269    Args:
270      manifest: the AndroidManifest
271      build_path: the build path to use for test
272      class_name: optionally, the class filter for the suite
273      java_package_name: optionally, the java package filter for the suite
274
275    Returns:
276      the list of tests created
277    """
278    tests = []
279    for instr_name in manifest.GetInstrumentationNames():
280      pkg_name = manifest.GetPackageName()
281      if instr_name.find(".") < 0:
282        instr_name = "." + instr_name
283      logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
284      suite = instrumentation_test.InstrumentationTestSuite()
285      suite.SetPackageName(pkg_name)
286      suite.SetBuildPath(build_path)
287      suite.SetRunnerName(instr_name)
288      suite.SetName(pkg_name)
289      suite.SetClassName(class_name)
290      suite.SetJavaPackageFilter(java_package_name)
291      # this is a bit of a hack, assume if 'com.android.cts' is in
292      # package name, this is a cts test
293      # this logic can be removed altogether when cts tests no longer require
294      # custom build steps
295      if suite.GetPackageName().startswith('com.android.cts'):
296        suite.SetSuite('cts')
297      tests.append(suite)
298    return tests
299