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 21 import os 22 23 # local imports 24 import android_build 25 import android_mk 26 import gtest 27 import instrumentation_test 28 import logger 29 30 31 class TestWalker(object): 32 """Finds Android tests from filesystem.""" 33 34 def FindTests(self, path): 35 """Gets list of Android tests found at given path. 36 37 Tests are created from info found in Android.mk and AndroidManifest.xml 38 files relative to the given path. 39 40 Currently supported tests are: 41 - Android application tests run via instrumentation 42 - native C/C++ tests using GTest framework. (note Android.mk must follow 43 expected GTest template) 44 45 FindTests will first scan sub-folders of path for tests. If none are found, 46 it will scan the file system upwards until a valid test Android.mk is found 47 or the Android build root is reached. 48 49 Some sample values for path: 50 - a parent directory containing many tests: 51 ie development/samples will return tests for instrumentation's in ApiDemos, 52 ApiDemos/tests, Notepad/tests etc 53 - a java test class file 54 ie ApiDemos/tests/src/../ApiDemosTest.java will return a test for 55 the instrumentation in ApiDemos/tests, with the class name filter set to 56 ApiDemosTest 57 - a java package directory 58 ie ApiDemos/tests/src/com/example/android/apis will return a test for 59 the instrumentation in ApiDemos/tests, with the java package filter set 60 to com.example.android.apis. 61 62 TODO: add GTest examples 63 64 Args: 65 path: file system path to search 66 67 Returns: 68 list of test suites that support operations defined by 69 test_suite.AbstractTestSuite 70 """ 71 if not os.path.exists(path): 72 logger.Log('%s does not exist' % path) 73 return [] 74 realpath = os.path.realpath(path) 75 # ensure path is in ANDROID_BUILD_ROOT 76 self._build_top = os.path.realpath(android_build.GetTop()) 77 if not self._IsPathInBuildTree(realpath): 78 logger.Log('%s is not a sub-directory of build root %s' % 79 (path, self._build_top)) 80 return [] 81 82 # first, assume path is a parent directory, which specifies to run all 83 # tests within this directory 84 tests = self._FindSubTests(realpath, []) 85 if not tests: 86 logger.SilentLog('No tests found within %s, searching upwards' % path) 87 tests = self._FindUpstreamTests(realpath) 88 return tests 89 90 def _IsPathInBuildTree(self, path): 91 """Return true if given path is within current Android build tree. 92 93 Args: 94 path: absolute file system path 95 96 Returns: 97 True if path is within Android build tree 98 """ 99 return os.path.commonprefix([self._build_top, path]) == self._build_top 100 101 def _MakePathRelativeToBuild(self, path): 102 """Convert given path to one relative to build tree root. 103 104 Args: 105 path: absolute file system path to convert. 106 107 Returns: 108 The converted path relative to build tree root. 109 110 Raises: 111 ValueError: if path is not within build tree 112 """ 113 if not self._IsPathInBuildTree(path): 114 raise ValueError 115 build_path_len = len(self._build_top) + 1 116 # return string with common build_path removed 117 return path[build_path_len:] 118 119 def _FindSubTests(self, path, tests, upstream_build_path=None): 120 """Recursively finds all tests within given path. 121 122 Args: 123 path: absolute file system path to check 124 tests: current list of found tests 125 upstream_build_path: the parent directory where Android.mk that builds 126 sub-folders was found 127 128 Returns: 129 updated list of tests 130 """ 131 if not os.path.isdir(path): 132 return tests 133 android_mk_parser = android_mk.CreateAndroidMK(path) 134 if android_mk_parser: 135 build_rel_path = self._MakePathRelativeToBuild(path) 136 if not upstream_build_path: 137 # haven't found a parent makefile which builds this dir. Use current 138 # dir as build path 139 tests.extend(self._CreateSuites( 140 android_mk_parser, path, build_rel_path)) 141 else: 142 tests.extend(self._CreateSuites(android_mk_parser, path, 143 upstream_build_path)) 144 # TODO: remove this logic, and rely on caller to collapse build 145 # paths via make_tree 146 147 # Try to build as much of original path as possible, so 148 # keep track of upper-most parent directory where Android.mk was found 149 # that has rule to build sub-directory makefiles. 150 # this is also necessary in case of overlapping tests 151 # ie if a test exists at 'foo' directory and 'foo/sub', attempting to 152 # build both 'foo' and 'foo/sub' will fail. 153 154 if android_mk_parser.IncludesMakefilesUnder(): 155 # found rule to build sub-directories. The parent path can be used, 156 # or if not set, use current path 157 if not upstream_build_path: 158 upstream_build_path = self._MakePathRelativeToBuild(path) 159 else: 160 upstream_build_path = None 161 for filename in os.listdir(path): 162 self._FindSubTests(os.path.join(path, filename), tests, 163 upstream_build_path) 164 return tests 165 166 def _FindUpstreamTests(self, path): 167 """Find tests defined upward from given path. 168 169 Args: 170 path: the location to start searching. 171 172 Returns: 173 list of test_suite.AbstractTestSuite found, may be empty 174 """ 175 factory = self._FindUpstreamTestFactory(path) 176 if factory: 177 return factory.CreateTests(sub_tests_path=path) 178 else: 179 return [] 180 181 def _GetTestFactory(self, android_mk_parser, path, build_path): 182 """Get the test factory for given makefile. 183 184 If given path is a valid tests build path, will return the TestFactory 185 for creating tests. 186 187 Args: 188 android_mk_parser: the android mk to evaluate 189 path: the filesystem path of the makefile 190 build_path: filesystem path for the directory 191 to build when running tests, relative to source root. 192 193 Returns: 194 the TestFactory or None if path is not a valid tests build path 195 """ 196 if android_mk_parser.HasGTest(): 197 return gtest.GTestFactory(path, build_path) 198 elif instrumentation_test.HasInstrumentationTest(path): 199 return instrumentation_test.InstrumentationTestFactory(path, 200 build_path) 201 else: 202 # somewhat unusual, but will continue searching 203 logger.SilentLog('Found makefile at %s, but did not detect any tests.' 204 % path) 205 206 return None 207 208 def _GetTestFactoryForPath(self, path): 209 """Get the test factory for given path. 210 211 If given path is a valid tests build path, will return the TestFactory 212 for creating tests. 213 214 Args: 215 path: the filesystem path to evaluate 216 217 Returns: 218 the TestFactory or None if path is not a valid tests build path 219 """ 220 android_mk_parser = android_mk.CreateAndroidMK(path) 221 if android_mk_parser: 222 build_path = self._MakePathRelativeToBuild(path) 223 return self._GetTestFactory(android_mk_parser, path, build_path) 224 else: 225 return None 226 227 def _FindUpstreamTestFactory(self, path): 228 """Recursively searches filesystem upwards for a test factory. 229 230 Args: 231 path: file system path to search 232 233 Returns: 234 the TestFactory found or None 235 """ 236 factory = self._GetTestFactoryForPath(path) 237 if factory: 238 return factory 239 dirpath = os.path.dirname(path) 240 if self._IsPathInBuildTree(path): 241 return self._FindUpstreamTestFactory(dirpath) 242 logger.Log('A tests Android.mk was not found') 243 return None 244 245 def _CreateSuites(self, android_mk_parser, path, upstream_build_path): 246 """Creates TestSuites from a AndroidMK. 247 248 Args: 249 android_mk_parser: the AndroidMK 250 path: absolute file system path of the makefile to evaluate 251 upstream_build_path: the build path to use for test. This can be 252 different than the 'path', in cases where an upstream makefile 253 is being used. 254 255 Returns: 256 the list of tests created 257 """ 258 factory = self._GetTestFactory(android_mk_parser, path, 259 build_path=upstream_build_path) 260 if factory: 261 return factory.CreateTests(path) 262 else: 263 return [] 264