upload android base code part7

This commit is contained in:
August 2018-08-08 18:09:17 +08:00
parent 4e516ec6ed
commit 841ae54672
25229 changed files with 1709508 additions and 0 deletions

View file

@ -0,0 +1 @@
__all__ = ['test_defs', 'test_walker']

View file

@ -0,0 +1,122 @@
#!/usr/bin/python
#
#
# Copyright 2011, 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.
"""TestSuite for running C/C++ Android tests using gtest framework."""
# python imports
import os
import re
# local imports
import logger
import run_command
import test_suite
class GTestSuite(test_suite.AbstractTestSuite):
"""A test suite for running gtest on device."""
def __init__(self):
test_suite.AbstractTestSuite.__init__(self)
self._target_exec_path = None
def GetTargetExecPath(self):
"""Get the target path to gtest executable."""
return self._target_exec_path
def SetTargetExecPath(self, path):
self._target_exec_path = path
return self
def Run(self, options, adb):
"""Run the provided gtest test suite.
Args:
options: command line options
adb: adb interface
"""
shell_cmd = adb.PreviewShellCommand(self.GetTargetExecPath())
logger.Log(shell_cmd)
if not options.preview:
# gtest will log to test results to stdout, so no need to do any
# extra processing
run_command.RunCommand(shell_cmd, return_output=False)
class GTestFactory(test_suite.AbstractTestFactory):
def __init__(self, test_root_path, build_path):
test_suite.AbstractTestFactory.__init__(self, test_root_path,
build_path)
def CreateTests(self, sub_tests_path=None):
"""Create tests found in sub_tests_path.
Looks for test files matching a pattern, and assumes each one is a separate
binary on target.
Test files must match one of the following pattern:
- test_*.[c|cc|cpp]
- *_test.[c|cc|cpp]
- *_unittest.[c|cc|cpp]
- *Tests.[cc|cpp]
"""
if not sub_tests_path:
sub_tests_path = self.GetTestRootPath()
test_file_list = []
if os.path.isfile(sub_tests_path):
self._EvaluateFile(test_file_list, os.path.basename(sub_tests_path))
else:
os.path.walk(sub_tests_path, self._CollectTestSources, test_file_list)
# TODO: obtain this from makefile instead of hardcoding
target_root_path = os.path.join('/data', 'nativetest')
test_suites = []
for test_file in test_file_list:
logger.SilentLog('Creating gtest suite for file %s' % test_file)
suite = GTestSuite()
suite.SetBuildPath(self.GetBuildPath())
# expect tests in /data/nativetest/test_file/test_file
suite.SetTargetExecPath(os.path.join(target_root_path, test_file, test_file))
test_suites.append(suite)
return test_suites
def _CollectTestSources(self, test_list, dirname, files):
"""For each directory, find tests source file and add them to the list.
Test files must match one of the following pattern:
- test_*.[cc|cpp]
- *_test.[cc|cpp]
- *_unittest.[cc|cpp]
- *Tests.[cc|cpp]
This method is a callback for os.path.walk.
Args:
test_list: Where new tests should be inserted.
dirname: Current directory.
files: List of files in the current directory.
"""
for f in files:
self._EvaluateFile(test_list, f)
def _EvaluateFile(self, test_list, file):
(name, ext) = os.path.splitext(file)
if ext == ".cc" or ext == ".cpp" or ext == ".c":
if re.search("_test$|_test_$|_unittest$|_unittest_$|^test_|Tests$", name):
logger.SilentLog("Found native test file %s" % file)
test_list.append(name)

View file

@ -0,0 +1,108 @@
#!/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.
"""Parser for test definition xml files."""
# python imports
import os
import errors
import logger
import run_command
import test_suite
class HostTestSuite(test_suite.AbstractTestSuite):
"""A test suite for running hosttestlib java tests."""
_JUNIT_JAR_NAME = "junit.jar"
_HOSTTESTLIB_NAME = "hosttestlib.jar"
_DDMLIB_NAME = "ddmlib-prebuilt.jar"
_TRADEFED_NAME = "tradefed-prebuilt.jar"
_lib_names = [_JUNIT_JAR_NAME, _HOSTTESTLIB_NAME, _DDMLIB_NAME, _TRADEFED_NAME]
_JUNIT_BUILD_PATH = os.path.join("external", "junit")
_HOSTTESTLIB_BUILD_PATH = os.path.join("development", "tools", "hosttestlib")
_LIB_BUILD_PATHS = [_JUNIT_BUILD_PATH, _HOSTTESTLIB_BUILD_PATH ]
# main class for running host tests
# TODO: should other runners be supported, and make runner an attribute of
# the test suite?
_TEST_RUNNER = "com.android.hosttest.DeviceTestRunner"
def __init__(self):
test_suite.AbstractTestSuite.__init__(self)
self._jar_name = None
self._class_name = None
def GetBuildDependencies(self, options):
"""Override parent to tag on building host libs."""
return self._LIB_BUILD_PATHS
def GetClassName(self):
return self._class_name
def SetClassName(self, class_name):
self._class_name = class_name
return self
def GetJarName(self):
"""Returns the name of the host jar that contains the tests."""
return self._jar_name
def SetJarName(self, jar_name):
self._jar_name = jar_name
return self
def Run(self, options, adb_interface):
"""Runs the host test.
Results will be displayed on stdout. Assumes 'java' is on system path.
Args:
options: command line options for running host tests. Expected member
fields:
host_lib_path: path to directory that contains host library files
test_data_path: path to directory that contains test data files
preview: if true, do not execute, display commands only
adb_interface: reference to device under test
Raises:
errors.AbortError: if fatal error occurs
"""
# get the serial number of the device under test, so it can be passed to
# hosttestlib.
serial_number = adb_interface.GetSerialNumber()
self._lib_names.append(self.GetJarName())
# gather all the host jars that are needed to run tests
full_lib_paths = []
for lib in self._lib_names:
path = os.path.join(options.host_lib_path, lib)
# make sure jar file exists on host
if not os.path.exists(path):
raise errors.AbortError(msg="Could not find jar %s" % path)
full_lib_paths.append(path)
# java -cp <libs> <runner class> <test suite class> -s <device serial>
# -p <test data path>
cmd = "java -cp %s %s %s -s %s -p %s" % (":".join(full_lib_paths),
self._TEST_RUNNER,
self.GetClassName(), serial_number,
options.test_data_path)
logger.Log(cmd)
if not options.preview:
run_command.RunOnce(cmd, return_output=False)

View file

@ -0,0 +1,360 @@
#!/usr/bin/python2.4
#
#
# Copyright 2008, 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.
"""TestSuite definition for Android instrumentation tests."""
import os
import re
# local imports
import android_manifest
from coverage import coverage
import errors
import logger
import test_suite
class InstrumentationTestSuite(test_suite.AbstractTestSuite):
"""Represents a java instrumentation test suite definition run on device."""
DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
def __init__(self):
test_suite.AbstractTestSuite.__init__(self)
self._package_name = None
self._runner_name = self.DEFAULT_RUNNER
self._class_name = None
self._target_name = None
self._java_package = None
def GetPackageName(self):
return self._package_name
def SetPackageName(self, package_name):
self._package_name = package_name
return self
def GetRunnerName(self):
return self._runner_name
def SetRunnerName(self, runner_name):
self._runner_name = runner_name
return self
def GetClassName(self):
return self._class_name
def SetClassName(self, class_name):
self._class_name = class_name
return self
def GetJavaPackageFilter(self):
return self._java_package
def SetJavaPackageFilter(self, java_package_name):
"""Configure the suite to only run tests in given java package."""
self._java_package = java_package_name
return self
def GetTargetName(self):
"""Retrieve module that this test is targeting.
Used for generating code coverage metrics.
Returns:
the module target name
"""
return self._target_name
def SetTargetName(self, target_name):
self._target_name = target_name
return self
def GetBuildDependencies(self, options):
if options.coverage_target_path:
return [options.coverage_target_path]
return []
def Run(self, options, adb):
"""Run the provided test suite.
Builds up an adb instrument command using provided input arguments.
Args:
options: command line options to provide to test run
adb: adb_interface to device under test
Raises:
errors.AbortError: if fatal error occurs
"""
test_class = self.GetClassName()
if options.test_class is not None:
test_class = options.test_class.lstrip()
if test_class.startswith("."):
test_class = self.GetPackageName() + test_class
if options.test_method is not None:
test_class = "%s#%s" % (test_class, options.test_method)
test_package = self.GetJavaPackageFilter()
if options.test_package:
test_package = options.test_package
if test_class and test_package:
logger.Log('Error: both class and java package options are specified')
instrumentation_args = {}
if test_class is not None:
instrumentation_args["class"] = test_class
if test_package:
instrumentation_args["package"] = test_package
if options.test_size:
instrumentation_args["size"] = options.test_size
if options.wait_for_debugger:
instrumentation_args["debug"] = "true"
if options.suite_assign_mode:
instrumentation_args["suiteAssignment"] = "true"
if options.coverage:
instrumentation_args["coverage"] = "true"
if options.test_annotation:
instrumentation_args["annotation"] = options.test_annotation
if options.test_not_annotation:
instrumentation_args["notAnnotation"] = options.test_not_annotation
if options.preview:
adb_cmd = adb.PreviewInstrumentationCommand(
package_name=self.GetPackageName(),
runner_name=self.GetRunnerName(),
raw_mode=options.raw_mode,
instrumentation_args=instrumentation_args)
logger.Log(adb_cmd)
elif options.coverage:
coverage_gen = coverage.CoverageGenerator(adb)
if options.coverage_target_path:
coverage_target = coverage_gen.GetCoverageTargetForPath(options.coverage_target_path)
elif self.GetTargetName():
coverage_target = coverage_gen.GetCoverageTarget(self.GetTargetName())
self._CheckInstrumentationInstalled(adb)
# need to parse test output to determine path to coverage file
logger.Log("Running in coverage mode, suppressing test output")
try:
(test_results, status_map) = adb.StartInstrumentationForPackage(
package_name=self.GetPackageName(),
runner_name=self.GetRunnerName(),
timeout_time=60*60,
instrumentation_args=instrumentation_args,
user=options.user)
except errors.InstrumentationError, errors.DeviceUnresponsiveError:
return
self._PrintTestResults(test_results)
device_coverage_path = status_map.get("coverageFilePath", None)
if device_coverage_path is None:
logger.Log("Error: could not find coverage data on device")
return
coverage_file = coverage_gen.ExtractReport(
self.GetName(), coverage_target, device_coverage_path,
test_qualifier=options.test_size)
if coverage_file is not None:
logger.Log("Coverage report generated at %s" % coverage_file)
else:
self._CheckInstrumentationInstalled(adb)
adb.StartInstrumentationNoResults(package_name=self.GetPackageName(),
runner_name=self.GetRunnerName(),
raw_mode=options.raw_mode,
instrumentation_args=
instrumentation_args,
user=options.user)
def _CheckInstrumentationInstalled(self, adb):
if not adb.IsInstrumentationInstalled(self.GetPackageName(),
self.GetRunnerName()):
msg=("Could not find instrumentation %s/%s on device. Try forcing a "
"rebuild by updating a source file, and re-executing runtest." %
(self.GetPackageName(), self.GetRunnerName()))
raise errors.AbortError(msg=msg)
def _PrintTestResults(self, test_results):
"""Prints a summary of test result data to stdout.
Args:
test_results: a list of am_instrument_parser.TestResult
"""
total_count = 0
error_count = 0
fail_count = 0
for test_result in test_results:
if test_result.GetStatusCode() == -1: # error
logger.Log("Error in %s: %s" % (test_result.GetTestName(),
test_result.GetFailureReason()))
error_count+=1
elif test_result.GetStatusCode() == -2: # failure
logger.Log("Failure in %s: %s" % (test_result.GetTestName(),
test_result.GetFailureReason()))
fail_count+=1
total_count+=1
logger.Log("Tests run: %d, Failures: %d, Errors: %d" %
(total_count, fail_count, error_count))
def HasInstrumentationTest(path):
"""Determine if given path defines an instrumentation test.
Args:
path: file system path to instrumentation test.
"""
manifest_parser = android_manifest.CreateAndroidManifest(path)
if manifest_parser:
return manifest_parser.GetInstrumentationNames()
return False
class InstrumentationTestFactory(test_suite.AbstractTestFactory):
"""A factory for creating InstrumentationTestSuites"""
def __init__(self, test_root_path, build_path):
test_suite.AbstractTestFactory.__init__(self, test_root_path,
build_path)
def CreateTests(self, sub_tests_path=None):
"""Create tests found in test_path.
Will create a single InstrumentationTestSuite based on info found in
AndroidManifest.xml found at build_path. Will set additional filters if
test_path refers to a java package or java class.
"""
tests = []
class_name_arg = None
java_package_name = None
if sub_tests_path:
# if path is java file, populate class name
if self._IsJavaFile(sub_tests_path):
class_name_arg = self._GetClassNameFromFile(sub_tests_path)
logger.SilentLog('Using java test class %s' % class_name_arg)
elif self._IsJavaPackage(sub_tests_path):
java_package_name = self._GetPackageNameFromDir(sub_tests_path)
logger.SilentLog('Using java package %s' % java_package_name)
try:
manifest_parser = android_manifest.AndroidManifest(app_path=
self.GetTestsRootPath())
instrs = manifest_parser.GetInstrumentationNames()
if not instrs:
logger.Log('Could not find instrumentation declarations in %s at %s' %
(android_manifest.AndroidManifest.FILENAME,
self.GetBuildPath()))
return tests
elif len(instrs) > 1:
logger.Log("Found multiple instrumentation declarations in %s/%s. "
"Only using first declared." %
(self.GetBuildPath(),
android_manifest.AndroidManifest.FILENAME))
instr_name = manifest_parser.GetInstrumentationNames()[0]
# escape inner class names
instr_name = instr_name.replace('$', '\$')
pkg_name = manifest_parser.GetPackageName()
if instr_name.find(".") < 0:
instr_name = "." + instr_name
logger.SilentLog('Found instrumentation %s/%s' % (pkg_name, instr_name))
suite = InstrumentationTestSuite()
suite.SetPackageName(pkg_name)
suite.SetBuildPath(self.GetBuildPath())
suite.SetRunnerName(instr_name)
suite.SetName(pkg_name)
suite.SetClassName(class_name_arg)
suite.SetJavaPackageFilter(java_package_name)
tests.append(suite)
return tests
except:
logger.Log('Could not find or parse %s at %s' %
(android_manifest.AndroidManifest.FILENAME,
self.GetBuildPath()))
return tests
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

View file

@ -0,0 +1,185 @@
#!/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.
"""TestSuite for running native Android tests."""
# python imports
import os
import re
# local imports
import android_build
import logger
import run_command
import test_suite
class NativeTestSuite(test_suite.AbstractTestSuite):
"""A test suite for running native aka C/C++ tests on device."""
def Run(self, options, adb):
"""Run the provided *native* test suite.
The test_suite must contain a build path where the native test
files are. Subdirectories are automatically scanned as well.
Each test's name must have a .cc or .cpp extension and match one
of the following patterns:
- test_*
- *_test.[cc|cpp]
- *_unittest.[cc|cpp]
A successful test must return 0. Any other value will be considered
as an error.
Args:
options: command line options
adb: adb interface
"""
# find all test files, convert unicode names to ascii, take the basename
# and drop the .cc/.cpp extension.
source_list = []
build_path = os.path.join(android_build.GetTop(), self.GetBuildPath())
os.path.walk(build_path, self._CollectTestSources, source_list)
logger.SilentLog("Tests source %s" % source_list)
# Host tests are under out/host/<os>-<arch>/bin.
host_list = self._FilterOutMissing(android_build.GetHostBin(), source_list)
logger.SilentLog("Host tests %s" % host_list)
# Target tests are under $ANDROID_PRODUCT_OUT/data/nativetest.
target_list = self._FilterOutMissing(android_build.GetTargetNativeTestPath(),
source_list)
logger.SilentLog("Target tests %s" % target_list)
# Run on the host
logger.Log("\nRunning on host")
for f in host_list:
if run_command.RunHostCommand(f) != 0:
logger.Log("%s... failed" % f)
else:
if run_command.HasValgrind():
if run_command.RunHostCommand(f, valgrind=True) == 0:
logger.Log("%s... ok\t\t[valgrind: ok]" % f)
else:
logger.Log("%s... ok\t\t[valgrind: failed]" % f)
else:
logger.Log("%s... ok\t\t[valgrind: missing]" % f)
# Run on the device
logger.Log("\nRunning on target")
for f in target_list:
full_path = os.path.join(os.sep, "data", "nativetest", f)
# Single quotes are needed to prevent the shell splitting it.
output = adb.SendShellCommand("'%s 2>&1;echo -n exit code:$?'" %
"(cd /sdcard;%s)" % full_path,
int(options.timeout))
success = output.endswith("exit code:0")
logger.Log("%s... %s" % (f, success and "ok" or "failed"))
# Print the captured output when the test failed.
if not success or options.verbose:
pos = output.rfind("exit code")
output = output[0:pos]
logger.Log(output)
# Cleanup
adb.SendShellCommand("rm %s" % full_path)
def _CollectTestSources(self, test_list, dirname, files):
"""For each directory, find tests source file and add them to the list.
Test files must match one of the following pattern:
- test_*.[cc|cpp]
- *_test.[cc|cpp]
- *_unittest.[cc|cpp]
This method is a callback for os.path.walk.
Args:
test_list: Where new tests should be inserted.
dirname: Current directory.
files: List of files in the current directory.
"""
for f in files:
(name, ext) = os.path.splitext(f)
if ext == ".cc" or ext == ".cpp" or ext == ".c":
if re.search("_test$|_test_$|_unittest$|_unittest_$|^test_", name):
logger.SilentLog("Found %s" % f)
test_list.append(str(os.path.join(dirname, f)))
def _FilterOutMissing(self, path, sources):
"""Filter out from the sources list missing tests.
Sometimes some test source are not built for the target, i.e there
is no binary corresponding to the source file. We need to filter
these out.
Args:
path: Where the binaries should be.
sources: List of tests source path.
Returns:
A list of relative paths to the test binaries built from the sources.
"""
binaries = []
for f in sources:
binary = os.path.basename(f)
binary = os.path.splitext(binary)[0]
found = self._FindFileRecursively(path, binary)
if found:
binary = os.path.relpath(os.path.abspath(found),
os.path.abspath(path))
binaries.append(binary)
return binaries
def _FindFileRecursively(self, path, match):
"""Finds the first executable binary in a given path that matches the name.
Args:
path: Where to search for binaries. Can be nested directories.
binary: Which binary to search for.
Returns:
first matched file in the path or None if none is found.
"""
for root, dirs, files in os.walk(path):
for f in files:
if f == match:
return os.path.join(root, f)
for d in dirs:
found = self._FindFileRecursively(os.path.join(root, d), match)
if found:
return found
return None
def _RunHostCommand(self, binary, valgrind=False):
"""Run a command on the host (opt using valgrind).
Runs the host binary and returns the exit code.
If successfull, the output (stdout and stderr) are discarded,
but printed in case of error.
The command can be run under valgrind in which case all the
output are always discarded.
Args:
binary: basename of the file to be run. It is expected to be under
$ANDROID_HOST_OUT/bin.
valgrind: If True the command will be run under valgrind.
Returns:
The command exit code (int)
"""
full_path = os.path.join(android_build.GetHostBin(), binary)
return run_command.RunHostCommand(full_path, valgrind=valgrind)

View file

@ -0,0 +1,132 @@
#!/usr/bin/python2.4
#
#
# Copyright 2008, 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.
"""Parser for test definition xml files."""
# Python imports
import xml.dom.minidom
import xml.parsers
# local imports
import errors
import logger
import xml_suite_helper
class TestDefinitions(object):
"""Accessor for a test definitions xml file data.
See test_defs.xsd for expected format.
"""
def __init__(self):
# dictionary of test name to tests
self._testname_map = {}
def __iter__(self):
ordered_list = []
for k in sorted(self._testname_map):
ordered_list.append(self._testname_map[k])
return iter(ordered_list)
def Parse(self, file_path):
"""Parse the test suite data from from given file path.
Args:
file_path: absolute file path to parse
Raises:
ParseError if file_path cannot be parsed
"""
try:
doc = xml.dom.minidom.parse(file_path)
self._ParseDoc(doc)
except IOError:
logger.Log("test file %s does not exist" % file_path)
raise errors.ParseError
except xml.parsers.expat.ExpatError:
logger.Log("Error Parsing xml file: %s " % file_path)
raise errors.ParseError
except errors.ParseError, e:
logger.Log("Error Parsing xml file: %s Reason: %s" % (file_path, e.msg))
raise e
def ParseString(self, xml_string):
"""Alternate parse method that accepts a string of the xml data."""
doc = xml.dom.minidom.parseString(xml_string)
# TODO: catch exceptions and raise ParseError
return self._ParseDoc(doc)
def _ParseDoc(self, doc):
root_element = self._GetRootElement(doc)
suite_parser = xml_suite_helper.XmlSuiteParser()
for element in root_element.childNodes:
if element.nodeType != xml.dom.Node.ELEMENT_NODE:
continue
test_suite = suite_parser.Parse(element)
if test_suite:
self._AddTest(test_suite)
def _GetRootElement(self, doc):
root_elements = doc.getElementsByTagName("test-definitions")
if len(root_elements) != 1:
error_msg = "expected 1 and only one test-definitions tag"
raise errors.ParseError(msg=error_msg)
return root_elements[0]
def _AddTest(self, test):
"""Adds a test to this TestManifest.
If a test already exists with the same name, it overrides it.
Args:
test: TestSuite to add
"""
if self.GetTest(test.GetName()) is not None:
logger.SilentLog("Overriding test definition %s" % test.GetName())
self._testname_map[test.GetName()] = test
def GetTests(self):
return self._testname_map.values()
def GetContinuousTests(self):
con_tests = []
for test in self.GetTests():
if test.IsContinuous():
con_tests.append(test)
return con_tests
def GetTestsInSuite(self, suite):
"""Return list of tests in given suite."""
return [t for t in self.GetTests() if t.GetSuite() == suite]
def GetTest(self, name):
return self._testname_map.get(name, None)
def Parse(file_path):
"""Parses out a TestDefinitions from given path to xml file.
Args:
file_path: string absolute file path
Returns:
a TestDefinitions object containing data parsed from file_path
Raises:
ParseError if xml format is not recognized
"""
tests_result = TestDefinitions()
tests_result.Parse(file_path)
return tests_result

View file

@ -0,0 +1,151 @@
#!/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.
"""Abstract Android test suite."""
class AbstractTestSuite(object):
"""Represents a generic test suite definition.
TODO: rename this as AbstractTestDef.
"""
def __init__(self):
self._name = None
self._build_path = None
self._build_dependencies = []
self._is_continuous = False
self._suite = None
self._description = ''
self._extra_build_args = ''
self._is_full_make = False
self._is_granted_permissions = True
def GetName(self):
return self._name
def SetName(self, name):
self._name = name
return self
def GetBuildPath(self):
"""Returns the build path of this test, relative to source tree root."""
return self._build_path
def SetBuildPath(self, build_path):
self._build_path = build_path
return self
def GetBuildDependencies(self, options):
"""Returns a list of dependent build paths."""
return self._build_dependencies
def SetBuildDependencies(self, build_dependencies):
self._build_dependencies = build_dependencies
return self
def IsContinuous(self):
"""Returns true if test is part of the continuous test."""
return self._is_continuous
def SetContinuous(self, continuous):
self._is_continuous = continuous
return self._is_continuous
def IsGrantedPermissions(self):
"""Return true if the test should be granted runtime permissions on install."""
return self._is_granted_permissions
def SetIsGrantedPermissions(self, is_granted_permissions):
self._is_granted_permissions = is_granted_permissions
return self._is_granted_permissions
def GetSuite(self):
"""Returns the name of test' suite, or None."""
return self._suite
def SetSuite(self, suite):
self._suite = suite
return self
def GetDescription(self):
"""Returns a description if available, an empty string otherwise."""
return self._description
def SetDescription(self, desc):
self._description = desc
return self
def GetExtraBuildArgs(self):
"""Returns the extra build args if available, an empty string otherwise."""
return self._extra_build_args
def SetExtraBuildArgs(self, build_args):
self._extra_build_args = build_args
return self
def IsFullMake(self):
return self._is_full_make
def SetIsFullMake(self, full_make):
self._is_full_make = full_make
return self
def Run(self, options, adb):
"""Runs the test.
Subclasses must implement this.
Args:
options: global command line options
adb: asdb_interface to device under test
"""
raise NotImplementedError
class AbstractTestFactory(object):
"""generic test suite factory."""
def __init__(self, test_root_path, build_path):
"""Creates a test suite factory.
Args:
test_root_path: the filesystem path to the tests build directory
upstream_build_path: filesystem path for the directory
to build when running tests, relative to the source tree root.
"""
self._test_root_path = test_root_path
self._build_path = build_path
def GetBuildPath(self):
return self._build_path
def GetTestsRootPath(self):
return self._test_root_path
def CreateTests(self, sub_tests_path=None):
"""Creates the tests at given test_path.
Subclasses must implement this.
Args:
sub_tests_path: the child path of test_root_path containing the tests to
run. If unspecified will be set to test_root_path.
Returns:
an array of AbstractTestSuite, or empty AbstractTestSuite if no tests
were defined
"""
raise NotImplementedError

View file

@ -0,0 +1,263 @@
#!/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
# local imports
import android_build
import android_mk
import gtest
import instrumentation_test
import logger
class TestWalker(object):
"""Finds Android tests from filesystem."""
def FindTests(self, path):
"""Gets list of Android tests found at given path.
Tests are created from info found in Android.mk and AndroidManifest.xml
files relative to the given path.
Currently supported tests are:
- Android application tests run via instrumentation
- native C/C++ tests using GTest framework. (note Android.mk must follow
expected GTest template)
FindTests will first scan sub-folders of path for tests. If none are found,
it will scan the file system upwards until a valid test Android.mk 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.
TODO: add GTest examples
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 []
realpath = os.path.realpath(path)
# ensure path is in ANDROID_BUILD_ROOT
self._build_top = os.path.realpath(android_build.GetTop())
if not self._IsPathInBuildTree(realpath):
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(realpath, [])
if not tests:
logger.SilentLog('No tests found within %s, searching upwards' % path)
tests = self._FindUpstreamTests(realpath)
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, upstream_build_path=None):
"""Recursively finds all tests within given path.
Args:
path: absolute file system path to check
tests: current list of found tests
upstream_build_path: the parent directory where Android.mk that builds
sub-folders was found
Returns:
updated list of tests
"""
if not os.path.isdir(path):
return tests
android_mk_parser = android_mk.CreateAndroidMK(path)
if android_mk_parser:
build_rel_path = self._MakePathRelativeToBuild(path)
if not upstream_build_path:
# haven't found a parent makefile which builds this dir. Use current
# dir as build path
tests.extend(self._CreateSuites(
android_mk_parser, path, build_rel_path))
else:
tests.extend(self._CreateSuites(android_mk_parser, path,
upstream_build_path))
# TODO: remove this logic, and rely on caller to collapse build
# paths via make_tree
# Try to build as much of original path as possible, so
# keep track of upper-most parent directory where Android.mk was found
# that has rule to build sub-directory makefiles.
# 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 android_mk_parser.IncludesMakefilesUnder():
# found rule to build sub-directories. The parent path can be used,
# or if not set, use current path
if not upstream_build_path:
upstream_build_path = self._MakePathRelativeToBuild(path)
else:
upstream_build_path = None
for filename in os.listdir(path):
self._FindSubTests(os.path.join(path, filename), tests,
upstream_build_path)
return tests
def _FindUpstreamTests(self, path):
"""Find tests defined upward from given path.
Args:
path: the location to start searching.
Returns:
list of test_suite.AbstractTestSuite found, may be empty
"""
factory = self._FindUpstreamTestFactory(path)
if factory:
return factory.CreateTests(sub_tests_path=path)
else:
return []
def _GetTestFactory(self, android_mk_parser, path, build_path):
"""Get the test factory for given makefile.
If given path is a valid tests build path, will return the TestFactory
for creating tests.
Args:
android_mk_parser: the android mk to evaluate
path: the filesystem path of the makefile
build_path: filesystem path for the directory
to build when running tests, relative to source root.
Returns:
the TestFactory or None if path is not a valid tests build path
"""
if android_mk_parser.HasGTest():
return gtest.GTestFactory(path, build_path)
elif instrumentation_test.HasInstrumentationTest(path):
return instrumentation_test.InstrumentationTestFactory(path,
build_path)
else:
# somewhat unusual, but will continue searching
logger.SilentLog('Found makefile at %s, but did not detect any tests.'
% path)
return None
def _GetTestFactoryForPath(self, path):
"""Get the test factory for given path.
If given path is a valid tests build path, will return the TestFactory
for creating tests.
Args:
path: the filesystem path to evaluate
Returns:
the TestFactory or None if path is not a valid tests build path
"""
android_mk_parser = android_mk.CreateAndroidMK(path)
if android_mk_parser:
build_path = self._MakePathRelativeToBuild(path)
return self._GetTestFactory(android_mk_parser, path, build_path)
else:
return None
def _FindUpstreamTestFactory(self, path):
"""Recursively searches filesystem upwards for a test factory.
Args:
path: file system path to search
Returns:
the TestFactory found or None
"""
factory = self._GetTestFactoryForPath(path)
if factory:
return factory
dirpath = os.path.dirname(path)
if self._IsPathInBuildTree(path):
return self._FindUpstreamTestFactory(dirpath)
logger.Log('A tests Android.mk was not found')
return None
def _CreateSuites(self, android_mk_parser, path, upstream_build_path):
"""Creates TestSuites from a AndroidMK.
Args:
android_mk_parser: the AndroidMK
path: absolute file system path of the makefile to evaluate
upstream_build_path: the build path to use for test. This can be
different than the 'path', in cases where an upstream makefile
is being used.
Returns:
the list of tests created
"""
factory = self._GetTestFactory(android_mk_parser, path,
build_path=upstream_build_path)
if factory:
return factory.CreateTests(path)
else:
return []

View file

@ -0,0 +1,162 @@
#!/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 parse suite info from xml."""
# Python imports
import xml.dom.minidom
import xml.parsers
# local imports
import errors
import logger
import host_test
import instrumentation_test
import native_test
class XmlSuiteParser(object):
"""Parses XML attributes common to all TestSuite's."""
# common attributes
_NAME_ATTR = 'name'
_BUILD_ATTR = 'build_path'
_CONTINUOUS_ATTR = 'continuous'
_SUITE_ATTR = 'suite'
_DESCRIPTION_ATTR = 'description'
_EXTRA_BUILD_ARGS_ATTR = 'extra_build_args'
_FULL_MAKE_ATTR = 'full_make'
_GRANTED_PERMISSIONS_ATTR = 'granted_permissions'
def Parse(self, element):
"""Populates common suite attributes from given suite xml element.
Args:
element: xml node to parse
Raises:
ParseError if a required attribute is missing.
Returns:
parsed test suite or None
"""
parser = None
if element.nodeName == InstrumentationParser.TAG_NAME:
parser = InstrumentationParser()
elif element.nodeName == NativeParser.TAG_NAME:
parser = NativeParser()
elif element.nodeName == HostParser.TAG_NAME:
parser = HostParser()
else:
logger.Log('Unrecognized tag %s found' % element.nodeName)
return None
test_suite = parser.Parse(element)
return test_suite
def _ParseCommonAttributes(self, suite_element, test_suite):
test_suite.SetName(self._ParseAttribute(suite_element, self._NAME_ATTR,
True))
test_suite.SetBuildPath(self._ParseAttribute(suite_element,
self._BUILD_ATTR, True))
test_suite.SetContinuous(self._ParseAttribute(suite_element,
self._CONTINUOUS_ATTR,
False, default_value=False))
test_suite.SetIsGrantedPermissions(self._ParseAttribute(suite_element,
self._GRANTED_PERMISSIONS_ATTR,
False, default_value=True))
test_suite.SetSuite(self._ParseAttribute(suite_element, self._SUITE_ATTR, False,
default_value=None))
test_suite.SetDescription(self._ParseAttribute(suite_element,
self._DESCRIPTION_ATTR,
False,
default_value=''))
test_suite.SetExtraBuildArgs(self._ParseAttribute(
suite_element, self._EXTRA_BUILD_ARGS_ATTR, False, default_value=''))
test_suite.SetIsFullMake(self._ParseAttribute(
suite_element, self._FULL_MAKE_ATTR, False, default_value=False))
def _ParseAttribute(self, suite_element, attribute_name, mandatory,
default_value=None):
if suite_element.hasAttribute(attribute_name):
value = suite_element.getAttribute(attribute_name)
if default_value in (True, False):
value = value.lower() == "true"
elif mandatory:
error_msg = ('Could not find attribute %s in %s' %
(attribute_name, self.TAG_NAME))
raise errors.ParseError(msg=error_msg)
else:
value = default_value
return value
class InstrumentationParser(XmlSuiteParser):
"""Parses instrumentation suite attributes from xml."""
# for legacy reasons, the xml tag name for java (device) tests is 'test'
TAG_NAME = 'test'
_PKG_ATTR = 'package'
_RUNNER_ATTR = 'runner'
_CLASS_ATTR = 'class'
_TARGET_ATTR = 'coverage_target'
def Parse(self, suite_element):
"""Creates suite and populate with data from xml element."""
suite = instrumentation_test.InstrumentationTestSuite()
XmlSuiteParser._ParseCommonAttributes(self, suite_element, suite)
suite.SetPackageName(self._ParseAttribute(suite_element, self._PKG_ATTR,
True))
suite.SetRunnerName(self._ParseAttribute(
suite_element, self._RUNNER_ATTR, False,
instrumentation_test.InstrumentationTestSuite.DEFAULT_RUNNER))
suite.SetClassName(self._ParseAttribute(suite_element, self._CLASS_ATTR,
False))
suite.SetTargetName(self._ParseAttribute(suite_element, self._TARGET_ATTR,
False))
return suite
class NativeParser(XmlSuiteParser):
"""Parses native suite attributes from xml."""
TAG_NAME = 'test-native'
def Parse(self, suite_element):
"""Creates suite and populate with data from xml element."""
suite = native_test.NativeTestSuite()
XmlSuiteParser._ParseCommonAttributes(self, suite_element, suite)
return suite
class HostParser(XmlSuiteParser):
"""Parses host suite attributes from xml."""
TAG_NAME = 'test-host'
_CLASS_ATTR = 'class'
# TODO: consider obsoleting in favor of parsing the Android.mk to find the
# jar name
_JAR_ATTR = 'jar_name'
def Parse(self, suite_element):
"""Creates suite and populate with data from xml element."""
suite = host_test.HostTestSuite()
XmlSuiteParser._ParseCommonAttributes(self, suite_element, suite)
suite.SetClassName(self._ParseAttribute(suite_element, self._CLASS_ATTR,
True))
suite.SetJarName(self._ParseAttribute(suite_element, self._JAR_ATTR, True))
return suite