diff options
| author | Brett Chabot <brettchabot@android.com> | 2009-10-21 17:23:01 -0700 |
|---|---|---|
| committer | Brett Chabot <brettchabot@android.com> | 2009-10-26 12:22:46 -0700 |
| commit | 59b4778dd6758436e7e301323b5eb6c10a3061c9 (patch) | |
| tree | 3147c2bbbf0480d371eec761dca84a95d3d58825 /testrunner/test_defs | |
| parent | cdfea21f9285641d3040d4fb19138873086c9869 (diff) | |
| download | android_development-59b4778dd6758436e7e301323b5eb6c10a3061c9.tar.gz android_development-59b4778dd6758436e7e301323b5eb6c10a3061c9.tar.bz2 android_development-59b4778dd6758436e7e301323b5eb6c10a3061c9.zip | |
Add runtest --path.
This supports specifying a file system path to test(s) to run. The path can be
a java test class file, a java package directory, or a parent directory
containing many tests.
This change allows users to run tests independently from test_defs.xml.
BUG 2133198
Diffstat (limited to 'testrunner/test_defs')
| -rw-r--r-- | testrunner/test_defs/__init__.py | 2 | ||||
| -rwxr-xr-x | testrunner/test_defs/test_walker.py | 296 |
2 files changed, 297 insertions, 1 deletions
diff --git a/testrunner/test_defs/__init__.py b/testrunner/test_defs/__init__.py index c205dcb8c..f397d2a74 100644 --- a/testrunner/test_defs/__init__.py +++ b/testrunner/test_defs/__init__.py @@ -1 +1 @@ -__all__ = ['test_defs'] +__all__ = ['test_defs', 'test_walker'] diff --git a/testrunner/test_defs/test_walker.py b/testrunner/test_defs/test_walker.py new file mode 100755 index 000000000..973cac16f --- /dev/null +++ b/testrunner/test_defs/test_walker.py @@ -0,0 +1,296 @@ +#!/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() + 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 + suite.SetCts(suite.GetPackageName().startswith('com.android.cts')) + tests.append(suite) + return tests + |
