blob: 4ef6923a76808fb71b0eb1be47b53a8c78fcb2e2 [file] [log] [blame]
Brett Chabot59b47782009-10-21 17:23:01 -07001#!/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
Brett Chabot12c6d3f2010-07-29 15:27:30 -0700119 build_path: the parent directory where Android.mk that builds sub-folders
120 was found
Brett Chabot59b47782009-10-21 17:23:01 -0700121
122 Returns:
123 updated list of tests
124 """
125 if not os.path.isdir(path):
126 return tests
127 filenames = os.listdir(path)
Brett Chabot59b47782009-10-21 17:23:01 -0700128 if filenames.count(android_manifest.AndroidManifest.FILENAME):
129 # found a manifest! now parse it to find the test definition(s)
130 manifest = android_manifest.AndroidManifest(app_path=path)
Brett Chabot12c6d3f2010-07-29 15:27:30 -0700131 if not build_path:
132 # haven't found a parent makefile which builds this dir. Use current
133 # dir as build path
134 tests.extend(self._CreateSuitesFromManifest(
135 manifest, self._MakePathRelativeToBuild(path)))
136 else:
137 tests.extend(self._CreateSuitesFromManifest(manifest, build_path))
138 # Try to build as much of original path as possible, so
139 # keep track of upper-most parent directory where Android.mk was found that
140 # has rule to build sub-directory makefiles
141 # this is also necessary in case of overlapping tests
142 # ie if a test exists at 'foo' directory and 'foo/sub', attempting to
143 # build both 'foo' and 'foo/sub' will fail.
144 if filenames.count(android_mk.AndroidMK.FILENAME):
145 android_mk_parser = android_mk.AndroidMK(app_path=path)
146 if android_mk_parser.HasInclude('call all-makefiles-under,$(LOCAL_PATH)'):
147 # found rule to build sub-directories. The parent path can be used,
148 # or if not set, use current path
149 if not build_path:
150 build_path = self._MakePathRelativeToBuild(path)
151 else:
152 build_path = None
Brett Chabot59b47782009-10-21 17:23:01 -0700153 for filename in filenames:
154 self._FindSubTests(os.path.join(path, filename), tests, build_path)
155 return tests
156
157 def _FindUpstreamTests(self, path):
158 """Find tests defined upward from given path.
159
160 Args:
161 path: the location to start searching. If it points to a java class file
162 or java package dir, the appropriate test suite filters will be set
163
164 Returns:
165 list of test_suite.AbstractTestSuite found, may be empty
166 """
167 class_name_arg = None
168 package_name = None
169 # if path is java file, populate class name
170 if self._IsJavaFile(path):
171 class_name_arg = self._GetClassNameFromFile(path)
172 logger.SilentLog('Using java test class %s' % class_name_arg)
173 elif self._IsJavaPackage(path):
174 package_name = self._GetPackageNameFromDir(path)
175 logger.SilentLog('Using java package %s' % package_name)
176 manifest = self._FindUpstreamManifest(path)
177 if manifest:
178 logger.SilentLog('Found AndroidManifest at %s' % manifest.GetAppPath())
179 build_path = self._MakePathRelativeToBuild(manifest.GetAppPath())
180 return self._CreateSuitesFromManifest(manifest,
181 build_path,
182 class_name=class_name_arg,
183 java_package_name=package_name)
184
185 def _IsJavaFile(self, path):
186 """Returns true if given file system path is a java file."""
187 return os.path.isfile(path) and self._IsJavaFileName(path)
188
189 def _IsJavaFileName(self, filename):
190 """Returns true if given file name is a java file name."""
191 return os.path.splitext(filename)[1] == '.java'
192
193 def _IsJavaPackage(self, path):
194 """Returns true if given file path is a java package.
195
196 Currently assumes if any java file exists in this directory, than it
197 represents a java package.
198
199 Args:
200 path: file system path of directory to check
201
202 Returns:
203 True if path is a java package
204 """
205 if not os.path.isdir(path):
206 return False
207 for file_name in os.listdir(path):
208 if self._IsJavaFileName(file_name):
209 return True
210 return False
211
212 def _GetClassNameFromFile(self, java_file_path):
213 """Gets the fully qualified java class name from path.
214
215 Args:
216 java_file_path: file system path of java file
217
218 Returns:
219 fully qualified java class name or None.
220 """
221 package_name = self._GetPackageNameFromFile(java_file_path)
222 if package_name:
223 filename = os.path.basename(java_file_path)
224 class_name = os.path.splitext(filename)[0]
225 return '%s.%s' % (package_name, class_name)
226 return None
227
228 def _GetPackageNameFromDir(self, path):
229 """Gets the java package name associated with given directory path.
230
231 Caveat: currently just parses defined java package name from first java
232 file found in directory.
233
234 Args:
235 path: file system path of directory
236
237 Returns:
238 the java package name or None
239 """
240 for filename in os.listdir(path):
241 if self._IsJavaFileName(filename):
242 return self._GetPackageNameFromFile(os.path.join(path, filename))
243
244 def _GetPackageNameFromFile(self, java_file_path):
245 """Gets the java package name associated with given java file path.
246
247 Args:
248 java_file_path: file system path of java file
249
250 Returns:
251 the java package name or None
252 """
253 logger.SilentLog('Looking for java package name in %s' % java_file_path)
254 re_package = re.compile(r'package\s+(.*);')
255 file_handle = open(java_file_path, 'r')
256 for line in file_handle:
257 match = re_package.match(line)
258 if match:
259 return match.group(1)
260 return None
261
262 def _FindUpstreamManifest(self, path):
263 """Recursively searches filesystem upwards for a AndroidManifest file.
264
265 Args:
266 path: file system path to search
267
268 Returns:
269 the AndroidManifest found or None
270 """
271 if (os.path.isdir(path) and
272 os.listdir(path).count(android_manifest.AndroidManifest.FILENAME)):
273 return android_manifest.AndroidManifest(app_path=path)
274 dirpath = os.path.dirname(path)
275 if self._IsPathInBuildTree(path):
276 return self._FindUpstreamManifest(dirpath)
277 logger.Log('AndroidManifest.xml not found')
278 return None
279
280 def _CreateSuitesFromManifest(self, manifest, build_path, class_name=None,
281 java_package_name=None):
282 """Creates TestSuites from a AndroidManifest.
283
284 Args:
285 manifest: the AndroidManifest
286 build_path: the build path to use for test
287 class_name: optionally, the class filter for the suite
288 java_package_name: optionally, the java package filter for the suite
289
290 Returns:
291 the list of tests created
292 """
293 tests = []
294 for instr_name in manifest.GetInstrumentationNames():
295 pkg_name = manifest.GetPackageName()
Fred Quintana75a73dc2010-03-16 16:27:03 -0700296 if instr_name.find(".") < 0:
297 instr_name = "." + instr_name
Brett Chabot59b47782009-10-21 17:23:01 -0700298 logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
299 suite = instrumentation_test.InstrumentationTestSuite()
300 suite.SetPackageName(pkg_name)
301 suite.SetBuildPath(build_path)
302 suite.SetRunnerName(instr_name)
303 suite.SetName(pkg_name)
304 suite.SetClassName(class_name)
305 suite.SetJavaPackageFilter(java_package_name)
306 # this is a bit of a hack, assume if 'com.android.cts' is in
307 # package name, this is a cts test
308 # this logic can be removed altogether when cts tests no longer require
309 # custom build steps
Brett Chabot4a5d9f12010-02-18 20:01:11 -0800310 if suite.GetPackageName().startswith('com.android.cts'):
311 suite.SetSuite('cts')
Brett Chabot59b47782009-10-21 17:23:01 -0700312 tests.append(suite)
313 return tests