#!/usr/bin/python2.4 # # # Copyright 2009, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Utility to find instrumentation test definitions from file system.""" # python imports import os import re # local imports import android_build import android_manifest import android_mk import instrumentation_test import logger class TestWalker(object): """Finds instrumentation tests from filesystem.""" def FindTests(self, path): """Gets list of Android instrumentation tests found at given path. Tests are created from the <instrumentation> tags found in AndroidManifest.xml files relative to the given path. FindTests will first scan sub-folders of path for tests. If none are found, it will scan the file system upwards until a AndroidManifest.xml is found or the Android build root is reached. Some sample values for path: - a parent directory containing many tests: ie development/samples will return tests for instrumentation's in ApiDemos, ApiDemos/tests, Notepad/tests etc - a java test class file ie ApiDemos/tests/src/../ApiDemosTest.java will return a test for the instrumentation in ApiDemos/tests, with the class name filter set to ApiDemosTest - a java package directory ie ApiDemos/tests/src/com/example/android/apis will return a test for the instrumentation in ApiDemos/tests, with the java package filter set to com.example.android.apis. Args: path: file system path to search Returns: list of test suites that support operations defined by test_suite.AbstractTestSuite """ if not os.path.exists(path): logger.Log('%s does not exist' % path) return [] abspath = os.path.abspath(path) # ensure path is in ANDROID_BUILD_ROOT self._build_top = android_build.GetTop() if not self._IsPathInBuildTree(abspath): logger.Log('%s is not a sub-directory of build root %s' % (path, self._build_top)) return [] # first, assume path is a parent directory, which specifies to run all # tests within this directory tests = self._FindSubTests(abspath, []) if not tests: logger.SilentLog('No tests found within %s, searching upwards' % path) tests = self._FindUpstreamTests(abspath) return tests def _IsPathInBuildTree(self, path): """Return true if given path is within current Android build tree. Args: path: absolute file system path Returns: True if path is within Android build tree """ return os.path.commonprefix([self._build_top, path]) == self._build_top def _MakePathRelativeToBuild(self, path): """Convert given path to one relative to build tree root. Args: path: absolute file system path to convert. Returns: The converted path relative to build tree root. Raises: ValueError: if path is not within build tree """ if not self._IsPathInBuildTree(path): raise ValueError build_path_len = len(self._build_top) + 1 # return string with common build_path removed return path[build_path_len:] def _FindSubTests(self, path, tests, build_path=None): """Recursively finds all tests within given path. Args: path: absolute file system path to check tests: current list of found tests build_path: the parent directory where Android.mk was found Returns: updated list of tests """ if not os.path.isdir(path): return tests filenames = os.listdir(path) # Try to build as much of original path as possible, so # keep track of upper-most parent directory where Android.mk was found # this is also necessary in case of overlapping tests # ie if a test exists at 'foo' directory and 'foo/sub', attempting to # build both 'foo' and 'foo/sub' will fail. if not build_path and filenames.count(android_mk.AndroidMK.FILENAME): build_path = self._MakePathRelativeToBuild(path) if filenames.count(android_manifest.AndroidManifest.FILENAME): # found a manifest! now parse it to find the test definition(s) manifest = android_manifest.AndroidManifest(app_path=path) tests.extend(self._CreateSuitesFromManifest(manifest, build_path)) for filename in filenames: self._FindSubTests(os.path.join(path, filename), tests, build_path) return tests def _FindUpstreamTests(self, path): """Find tests defined upward from given path. Args: path: the location to start searching. If it points to a java class file or java package dir, the appropriate test suite filters will be set Returns: list of test_suite.AbstractTestSuite found, may be empty """ class_name_arg = None package_name = None # if path is java file, populate class name if self._IsJavaFile(path): class_name_arg = self._GetClassNameFromFile(path) logger.SilentLog('Using java test class %s' % class_name_arg) elif self._IsJavaPackage(path): package_name = self._GetPackageNameFromDir(path) logger.SilentLog('Using java package %s' % package_name) manifest = self._FindUpstreamManifest(path) if manifest: logger.SilentLog('Found AndroidManifest at %s' % manifest.GetAppPath()) build_path = self._MakePathRelativeToBuild(manifest.GetAppPath()) return self._CreateSuitesFromManifest(manifest, build_path, class_name=class_name_arg, java_package_name=package_name) def _IsJavaFile(self, path): """Returns true if given file system path is a java file.""" return os.path.isfile(path) and self._IsJavaFileName(path) def _IsJavaFileName(self, filename): """Returns true if given file name is a java file name.""" return os.path.splitext(filename)[1] == '.java' def _IsJavaPackage(self, path): """Returns true if given file path is a java package. Currently assumes if any java file exists in this directory, than it represents a java package. Args: path: file system path of directory to check Returns: True if path is a java package """ if not os.path.isdir(path): return False for file_name in os.listdir(path): if self._IsJavaFileName(file_name): return True return False def _GetClassNameFromFile(self, java_file_path): """Gets the fully qualified java class name from path. Args: java_file_path: file system path of java file Returns: fully qualified java class name or None. """ package_name = self._GetPackageNameFromFile(java_file_path) if package_name: filename = os.path.basename(java_file_path) class_name = os.path.splitext(filename)[0] return '%s.%s' % (package_name, class_name) return None def _GetPackageNameFromDir(self, path): """Gets the java package name associated with given directory path. Caveat: currently just parses defined java package name from first java file found in directory. Args: path: file system path of directory Returns: the java package name or None """ for filename in os.listdir(path): if self._IsJavaFileName(filename): return self._GetPackageNameFromFile(os.path.join(path, filename)) def _GetPackageNameFromFile(self, java_file_path): """Gets the java package name associated with given java file path. Args: java_file_path: file system path of java file Returns: the java package name or None """ logger.SilentLog('Looking for java package name in %s' % java_file_path) re_package = re.compile(r'package\s+(.*);') file_handle = open(java_file_path, 'r') for line in file_handle: match = re_package.match(line) if match: return match.group(1) return None def _FindUpstreamManifest(self, path): """Recursively searches filesystem upwards for a AndroidManifest file. Args: path: file system path to search Returns: the AndroidManifest found or None """ if (os.path.isdir(path) and os.listdir(path).count(android_manifest.AndroidManifest.FILENAME)): return android_manifest.AndroidManifest(app_path=path) dirpath = os.path.dirname(path) if self._IsPathInBuildTree(path): return self._FindUpstreamManifest(dirpath) logger.Log('AndroidManifest.xml not found') return None def _CreateSuitesFromManifest(self, manifest, build_path, class_name=None, java_package_name=None): """Creates TestSuites from a AndroidManifest. Args: manifest: the AndroidManifest build_path: the build path to use for test class_name: optionally, the class filter for the suite java_package_name: optionally, the java package filter for the suite Returns: the list of tests created """ tests = [] for instr_name in manifest.GetInstrumentationNames(): pkg_name = manifest.GetPackageName() if instr_name.find(".") < 0: instr_name = "." + instr_name logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name)) suite = instrumentation_test.InstrumentationTestSuite() suite.SetPackageName(pkg_name) suite.SetBuildPath(build_path) suite.SetRunnerName(instr_name) suite.SetName(pkg_name) suite.SetClassName(class_name) suite.SetJavaPackageFilter(java_package_name) # this is a bit of a hack, assume if 'com.android.cts' is in # package name, this is a cts test # this logic can be removed altogether when cts tests no longer require # custom build steps if suite.GetPackageName().startswith('com.android.cts'): suite.SetSuite('cts') tests.append(suite) return tests