blob: 948d53e78a9aeaa9e32abee558f870bc5e28599b [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 []
Brian Carlstrom90218ac2010-04-09 16:29:17 -070068 realpath = os.path.realpath(path)
Brett Chabot59b47782009-10-21 17:23:01 -070069 # ensure path is in ANDROID_BUILD_ROOT
Brian Carlstrom90218ac2010-04-09 16:29:17 -070070 self._build_top = os.path.realpath(android_build.GetTop())
71 if not self._IsPathInBuildTree(realpath):
Brett Chabot59b47782009-10-21 17:23:01 -070072 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
Brian Carlstrom90218ac2010-04-09 16:29:17 -070078 tests = self._FindSubTests(realpath, [])
Brett Chabot59b47782009-10-21 17:23:01 -070079 if not tests:
80 logger.SilentLog('No tests found within %s, searching upwards' % path)
Brian Carlstrom90218ac2010-04-09 16:29:17 -070081 tests = self._FindUpstreamTests(realpath)
Brett Chabot59b47782009-10-21 17:23:01 -070082 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()
Fred Quintana75a73dc2010-03-16 16:27:03 -0700281 if instr_name.find(".") < 0:
282 instr_name = "." + instr_name
Brett Chabot59b47782009-10-21 17:23:01 -0700283 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
Brett Chabot4a5d9f12010-02-18 20:01:11 -0800295 if suite.GetPackageName().startswith('com.android.cts'):
296 suite.SetSuite('cts')
Brett Chabot59b47782009-10-21 17:23:01 -0700297 tests.append(suite)
298 return tests