Add support for running host java tests to runtest.

With this change, also refactored runtest as follows: Modified the
test suite schema and python implementation to have an inheritance
structure.  Each test type has its own python module, which will
also handle the logic of running the test.
This commit is contained in:
Brett Chabot
2009-06-25 17:57:31 -07:00
parent e860ea0676
commit 764d3fa70d
15 changed files with 928 additions and 637 deletions

1
testrunner/__init__.py Normal file
View File

@@ -0,0 +1 @@
__all__ = ['adb_interface', 'android_build', 'errors', 'logger', 'run_command']

View File

@@ -392,3 +392,7 @@ class AdbInterface:
self.WaitForDevicePm()
return output
def GetSerialNumber(self):
"""Returns the serial number of the targeted device."""
return self.SendCommand("get-serialno").strip()

View File

@@ -133,3 +133,44 @@ def GetTargetSystemBin():
logger.Log("Error: Target system bin path could not be found")
raise errors.AbortError
return path
def GetHostLibraryPath():
"""Returns the full pathname to the host java library output directory.
Typically $ANDROID_BUILD_TOP/out/host/<host_os>/framework.
Assumes build environment has been properly configured by envsetup &
lunch/choosecombo.
Returns:
The absolute file path of the Android host java library directory.
Raises:
AbortError: if Android host java library directory could not be found.
"""
(_, _, os_arch) = GetHostOsArch()
path = os.path.join(GetTop(), "out", "host", os_arch, "framework")
if not os.path.exists(path):
logger.Log("Error: Host library path could not be found %s" % path)
raise errors.AbortError
return path
def GetTestAppPath():
"""Returns the full pathname to the test app build output directory.
Typically $ANDROID_PRODUCT_OUT/data/app
Assumes build environment has been properly configured by envsetup &
lunch/choosecombo.
Returns:
The absolute file path of the Android test app build directory.
Raises:
AbortError: if Android host java library directory could not be found.
"""
path = os.path.join(GetProductOut(), "data", "app")
if not os.path.exists(path):
logger.Log("Error: app path could not be found %s" % path)
raise errors.AbortError
return path

View File

@@ -37,12 +37,8 @@ class CoverageGenerator(object):
coverage results for a pre-defined set of tests and targets
"""
# environment variable to enable emma builds in Android build system
_EMMA_BUILD_FLAG = "EMMA_INSTRUMENT"
# build path to Emma target Makefile
_EMMA_BUILD_PATH = os.path.join("external", "emma")
# path to EMMA host jar, relative to Android build root
_EMMA_JAR = os.path.join(_EMMA_BUILD_PATH, "lib", "emma.jar")
_EMMA_JAR = os.path.join("external", "emma", "lib", "emma.jar")
_TEST_COVERAGE_EXT = "ec"
# root path of generated coverage report files, relative to Android build root
_COVERAGE_REPORT_PATH = os.path.join("out", "emma")
@@ -58,19 +54,14 @@ class CoverageGenerator(object):
_TARGET_INTERMEDIATES_BASE_PATH = os.path.join("out", "target", "common",
"obj")
def __init__(self, android_root_path, adb_interface):
self._root_path = android_root_path
def __init__(self, adb_interface):
self._root_path = android_build.GetTop()
self._output_root_path = os.path.join(self._root_path,
self._COVERAGE_REPORT_PATH)
self._emma_jar_path = os.path.join(self._root_path, self._EMMA_JAR)
self._adb = adb_interface
self._targets_manifest = self._ReadTargets()
def EnableCoverageBuild(self):
"""Enable building an Android target with code coverage instrumentation."""
os.environ[self._EMMA_BUILD_FLAG] = "true"
#TODO: can emma.jar automagically be added to bootclasspath here?
def TestDeviceCoverageSupport(self):
"""Check if device has support for generating code coverage metrics.
@@ -80,11 +71,13 @@ class CoverageGenerator(object):
Returns:
True if device can support code coverage. False otherwise.
"""
try:
output = self._adb.SendShellCommand("cat init.rc | grep BOOTCLASSPATH | "
"grep emma.jar")
if len(output) > 0:
return True
else:
except errors.AbortError:
pass
logger.Log("Error: Targeted device does not have emma.jar on its "
"BOOTCLASSPATH.")
logger.Log("Modify the BOOTCLASSPATH entry in system/core/rootdir/init.rc"
@@ -259,9 +252,6 @@ class CoverageGenerator(object):
coverage_files = glob.glob(file_pattern)
return coverage_files
def GetEmmaBuildPath(self):
return self._EMMA_BUILD_PATH
def _ReadTargets(self):
"""Parses the set of coverage target data.
@@ -310,6 +300,11 @@ class CoverageGenerator(object):
self._CombineTargetCoverage()
def EnableCoverageBuild():
"""Enable building an Android target with code coverage instrumentation."""
os.environ["EMMA_INSTRUMENT"] = "true"
def Run():
"""Does coverage operations based on command line args."""
# TODO: do we want to support combining coverage for a single target

View File

@@ -18,6 +18,12 @@
"""Defines common exception classes for this package."""
class MsgException(Exception):
"""Generic exception with an optional string msg."""
def __init__(self, msg=""):
self.msg = msg
class WaitForResponseTimedOutError(Exception):
"""We sent a command and had to wait too long for response."""
@@ -30,14 +36,11 @@ class InstrumentationError(Exception):
"""Failed to run instrumentation."""
class AbortError(Exception):
class AbortError(MsgException):
"""Generic exception that indicates a fatal error has occurred and program
execution should be aborted."""
def __init__(self, msg=""):
self.msg = msg
class ParseError(Exception):
class ParseError(MsgException):
"""Raised when xml data to parse has unrecognized format."""

View File

@@ -34,7 +34,7 @@ import coverage
import errors
import logger
import run_command
import test_defs
from test_defs import test_defs
class TestRunner(object):
@@ -154,8 +154,8 @@ class TestRunner(object):
self._known_tests = self._ReadTests()
self._coverage_gen = coverage.CoverageGenerator(
android_root_path=self._root_path, adb_interface=self._adb)
self._options.host_lib_path = android_build.GetHostLibraryPath()
self._options.test_data_path = android_build.GetTestAppPath()
def _ReadTests(self):
"""Parses the set of test definition data.
@@ -196,18 +196,14 @@ class TestRunner(object):
if target_set:
if self._options.coverage:
self._coverage_gen.EnableCoverageBuild()
self._AddBuildTargetPath(self._coverage_gen.GetEmmaBuildPath(),
target_set)
coverage.EnableCoverageBuild()
target_build_string = " ".join(list(target_set))
extra_args_string = " ".join(list(extra_args_set))
# log the user-friendly equivalent make command, so developers can
# replicate this step
logger.Log("mmm %s %s" % (target_build_string, extra_args_string))
# mmm cannot be used from python, so perform a similiar operation using
# mmm cannot be used from python, so perform a similar operation using
# ONE_SHOT_MAKEFILE
cmd = 'ONE_SHOT_MAKEFILE="%s" make -C "%s" files %s' % (
target_build_string, self._root_path, extra_args_string)
logger.Log(cmd)
if self._options.preview:
# in preview mode, just display to the user what command would have been
@@ -221,7 +217,9 @@ class TestRunner(object):
def _AddBuildTarget(self, test_suite, target_set, extra_args_set):
build_dir = test_suite.GetBuildPath()
if self._AddBuildTargetPath(build_dir, target_set):
extra_args_set.add(test_suite.GetExtraMakeArgs())
extra_args_set.add(test_suite.GetExtraBuildArgs())
for path in test_suite.GetBuildDependencies(self._options):
self._AddBuildTargetPath(path, target_set)
def _AddBuildTargetPath(self, build_dir, target_set):
if build_dir is not None:
@@ -249,206 +247,6 @@ class TestRunner(object):
tests.append(test)
return tests
def _RunTest(self, test_suite):
"""Run the provided test suite.
Builds up an adb instrument command using provided input arguments.
Args:
test_suite: TestSuite to run
"""
test_class = test_suite.GetClassName()
if self._options.test_class is not None:
test_class = self._options.test_class.lstrip()
if test_class.startswith("."):
test_class = test_suite.GetPackageName() + test_class
if self._options.test_method is not None:
test_class = "%s#%s" % (test_class, self._options.test_method)
instrumentation_args = {}
if test_class is not None:
instrumentation_args["class"] = test_class
if self._options.test_package:
instrumentation_args["package"] = self._options.test_package
if self._options.test_size:
instrumentation_args["size"] = self._options.test_size
if self._options.wait_for_debugger:
instrumentation_args["debug"] = "true"
if self._options.suite_assign_mode:
instrumentation_args["suiteAssignment"] = "true"
if self._options.coverage:
instrumentation_args["coverage"] = "true"
if self._options.preview:
adb_cmd = self._adb.PreviewInstrumentationCommand(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
raw_mode=self._options.raw_mode,
instrumentation_args=instrumentation_args)
logger.Log(adb_cmd)
elif self._options.coverage:
self._adb.WaitForInstrumentation(test_suite.GetPackageName(),
test_suite.GetRunnerName())
# 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) = self._adb.StartInstrumentationForPackage(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
timeout_time=60*60,
instrumentation_args=instrumentation_args)
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 = self._coverage_gen.ExtractReport(test_suite, device_coverage_path)
if coverage_file is not None:
logger.Log("Coverage report generated at %s" % coverage_file)
else:
self._adb.WaitForInstrumentation(test_suite.GetPackageName(),
test_suite.GetRunnerName())
self._adb.StartInstrumentationNoResults(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
raw_mode=self._options.raw_mode,
instrumentation_args=instrumentation_args)
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 _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":
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 test binaries built from the sources.
"""
binaries = []
for f in sources:
binary = os.path.basename(f)
binary = os.path.splitext(binary)[0]
full_path = os.path.join(path, binary)
if os.path.exists(full_path):
binaries.append(binary)
return binaries
def _RunNativeTest(self, test_suite):
"""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:
test_suite: TestSuite to run
"""
# find all test files, convert unicode names to ascii, take the basename
# and drop the .cc/.cpp extension.
source_list = []
build_path = test_suite.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/system/bin.
target_list = self._FilterOutMissing(android_build.GetTargetSystemBin(),
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, "system", "bin", f)
# Single quotes are needed to prevent the shell splitting it.
output = self._adb.SendShellCommand("'%s 2>&1;echo -n exit code:$?'" %
full_path,
int(self._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 self._options.verbose:
pos = output.rfind("exit code")
output = output[0:pos]
logger.Log(output)
# Cleanup
self._adb.SendShellCommand("rm %s" % full_path)
def RunTests(self):
"""Main entry method - executes the tests according to command line args."""
try:
@@ -462,10 +260,8 @@ class TestRunner(object):
self._DoBuild()
for test_suite in self._GetTestsToRun():
if test_suite.IsNative():
self._RunNativeTest(test_suite)
else:
self._RunTest(test_suite)
test_suite.Run(self._options, self._adb)
except KeyboardInterrupt:
logger.Log("Exiting...")
except errors.AbortError, e:

View File

@@ -1,281 +0,0 @@
#!/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
class TestDefinitions(object):
"""Accessor for a test definitions xml file data.
Expected format is:
<test-definitions>
<test
name=""
package=""
[runner=""]
[class=""]
[coverage_target=""]
[build_path=""]
[continuous=false]
[description=""]
/>
<test-native
name=""
build_path=""
[continuous=false]
[description=""]
/>
<test ...
</test-definitions>
TODO: add format checking.
"""
# tag/attribute constants
_TEST_TAG_NAME = "test"
_TEST_NATIVE_TAG_NAME = "test-native"
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)
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
self._ParseDoc(doc)
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):
suite_elements = doc.getElementsByTagName(self._TEST_TAG_NAME)
for suite_element in suite_elements:
test = self._ParseTestSuite(suite_element)
self._AddTest(test)
suite_elements = doc.getElementsByTagName(self._TEST_NATIVE_TAG_NAME)
for suite_element in suite_elements:
test = self._ParseNativeTestSuite(suite_element)
self._AddTest(test)
def _ParseTestSuite(self, suite_element):
"""Parse the suite element.
Returns:
a TestSuite object, populated with parsed data
"""
test = TestSuite(suite_element)
return test
def _ParseNativeTestSuite(self, suite_element):
"""Parse the native test element.
Returns:
a TestSuite object, populated with parsed data
Raises:
ParseError if some required attribute is missing.
"""
test = TestSuite(suite_element, native=True)
return test
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
"""
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 GetCtsTests(self):
"""Return list of cts tests."""
cts_tests = []
for test in self.GetTests():
if test.IsCts():
cts_tests.append(test)
return cts_tests
def GetTest(self, name):
return self._testname_map.get(name, None)
class TestSuite(object):
"""Represents one test suite definition parsed from xml."""
_NAME_ATTR = "name"
_PKG_ATTR = "package"
_RUNNER_ATTR = "runner"
_CLASS_ATTR = "class"
_TARGET_ATTR = "coverage_target"
_BUILD_ATTR = "build_path"
_CONTINUOUS_ATTR = "continuous"
_CTS_ATTR = "cts"
_DESCRIPTION_ATTR = "description"
_EXTRA_MAKE_ARGS_ATTR = "extra_make_args"
_DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
def __init__(self, suite_element, native=False):
"""Populates this instance's data from given suite xml element.
Raises:
ParseError if some required attribute is missing.
"""
self._native = native
self._name = suite_element.getAttribute(self._NAME_ATTR)
if self._native:
# For native runs, _BUILD_ATTR is required
if not suite_element.hasAttribute(self._BUILD_ATTR):
logger.Log("Error: %s is missing required build_path attribute" %
self._name)
raise errors.ParseError
else:
self._package = suite_element.getAttribute(self._PKG_ATTR)
if suite_element.hasAttribute(self._RUNNER_ATTR):
self._runner = suite_element.getAttribute(self._RUNNER_ATTR)
else:
self._runner = self._DEFAULT_RUNNER
if suite_element.hasAttribute(self._CLASS_ATTR):
self._class = suite_element.getAttribute(self._CLASS_ATTR)
else:
self._class = None
if suite_element.hasAttribute(self._TARGET_ATTR):
self._target_name = suite_element.getAttribute(self._TARGET_ATTR)
else:
self._target_name = None
if suite_element.hasAttribute(self._BUILD_ATTR):
self._build_path = suite_element.getAttribute(self._BUILD_ATTR)
else:
self._build_path = None
if suite_element.hasAttribute(self._CONTINUOUS_ATTR):
self._continuous = suite_element.getAttribute(self._CONTINUOUS_ATTR)
else:
self._continuous = False
if suite_element.hasAttribute(self._CTS_ATTR):
self._cts = suite_element.getAttribute(self._CTS_ATTR)
else:
self._cts = False
if suite_element.hasAttribute(self._DESCRIPTION_ATTR):
self._description = suite_element.getAttribute(self._DESCRIPTION_ATTR)
else:
self._description = ""
if suite_element.hasAttribute(self._EXTRA_MAKE_ARGS_ATTR):
self._extra_make_args = suite_element.getAttribute(
self._EXTRA_MAKE_ARGS_ATTR)
else:
self._extra_make_args = ""
def GetName(self):
return self._name
def GetPackageName(self):
return self._package
def GetRunnerName(self):
return self._runner
def GetClassName(self):
return self._class
def GetTargetName(self):
"""Retrieve module that this test is targeting.
Used for generating code coverage metrics.
"""
return self._target_name
def GetBuildPath(self):
"""Returns the build path of this test, relative to source tree root."""
return self._build_path
def IsContinuous(self):
"""Returns true if test is flagged as being part of the continuous tests"""
return self._continuous
def IsCts(self):
"""Returns true if test is part of the compatibility test suite"""
return self._cts
def IsNative(self):
"""Returns true if test is a native one."""
return self._native
def GetDescription(self):
"""Returns a description if available, an empty string otherwise."""
return self._description
def GetExtraMakeArgs(self):
"""Returns the extra make args if available, an empty string otherwise."""
return self._extra_make_args
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

@@ -17,72 +17,12 @@
<!--
This file contains standard test definitions for the Android platform
Java tests are defined by <test> tags and native ones (C/C++) are defined by
<test-native> tags.
The following test types are supported:
- On device Java instrumentation tests are defined by <test> tags.
- native ones (C/C++) are defined by <test-native> tags.
- host java tests are defined by <test-host> tags.
JAVA/application tests:
=======================
The java <test> element has the following attributes
name package [class runner build_path coverage_target continuous description]
Where:
name: Self-descriptive name used to uniquely identify the test
build_path: File system path, relative to Android build root, to this
package's Android.mk file. If omitted, build/sync step for this test will
be skipped.
package: Android application package that contains the tests
class: Optional. Fully qualified Java test class to run.
runner: Fully qualified InstrumentationTestRunner to execute. If omitted,
will default to android.test.InstrumentationTestRunner.
coverage_target: Build name of Android package this test targets - these
targets are defined in the coverage_targets.xml file. Used as basis for
code coverage metrics. If omitted, code coverage will not be supported for
this test.
continuous: Optional boolean. Default is false. Set to true if tests are known
to be reliable, and should be included in a continuous test system. false if
they are under development.
cts: Optional boolean. Default is false. Set to true if test is included in
compatibility test suite.
description: Optional string. Default is empty. Short description (typically
less than 60 characters) about this test.
These attributes map to the following commands:
(if class is defined)
adb shell am instrument -w <package>/<runner>
(else)
adb shell am instrument -w -e class <class> <package>/<runner>
Native tests:
=============
The <test-native> element has the following attributes
name build_path [continuous description extra_make_args]
Where:
name: Self-descriptive name used to uniquely identify the test
build_path: File system path, relative to Android build root, to this
package's Android.mk file. By convention the name of a test should match:
- test_*.[cc|cpp]
- *_test.[cc|cpp]
- *_unittest.[cc|cpp]
continuous: Optional boolean. Default is false. Set to true if tests are known
to be reliable, and should be included in a continuous test system.
false if they are under development.
description: Optional string. Default is empty. Short description (typically
less than 60 characters) about this test.
extra_make_args: Optional string. Default is empty. Some test module require
extra make arguments to build. This string is append to the make command.
These attributes map to the following commands:
make <build_path>/Android.mk <extra_make_args>
adb sync
for test_prog in <tests built>; do
adb shell "/system/bin/${test_prog} >/dev/null 2>&1;echo \$?"
adb shell "rm /system/bin/${test_prog}"
done
See test_defs.xsd for more information.
-->
<test-definitions xmlns="http://schemas.android.com/testrunner/test_defs/1.0"
@@ -504,19 +444,26 @@ Native tests:
<test-native name="libstdcpp"
build_path="system/extras/tests/bionic/libstdc++"
description="Bionic libstdc++."
extra_make_args="BIONIC_TESTS=1" />
extra_build_args="BIONIC_TESTS=1" />
<!-- Android STL tests -->
<test-native name="astl"
build_path="external/astl/tests"
description="Android STL."
extra_make_args="ASTL_TESTS=1" />
extra_build_args="ASTL_TESTS=1" />
<!-- pending patch 820
<test-native name="gtest"
build_path="external/gtest"
description="Google test."
extra_make_args="GTEST_TESTS=1" />
extra_build_args="GTEST_TESTS=1" />
-->
<!-- host java tests -->
<test-host name="cts-appinstall"
build_path="cts/tests/install-tests"
class="com.android.cts.install.InstallTests"
jar_name="CtsInstallTests.jar"
cts="true" />
</test-definitions>

View File

@@ -1,4 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 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.
-->
<!-- Contains the schema definition for Android test definitions xml -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.android.com/testrunner/test_defs/1.0"
xmlns="http://schemas.android.com/testrunner/test_defs/1.0"
@@ -10,28 +27,109 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="test" type="javaTestType" />
<xs:element name="test-native" type="nativeTestType" />
<xs:element name="test-host" type="hostTestType" />
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="javaTestType">
<!-- Generic, abstract test definition. Contains attributes common to all
test types. -->
<xs:complexType name="testType">
<!-- Self-descriptive name used to uniquely identify the test. -->
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="package" type="xs:string" use="required"/>
<xs:attribute name="build_path" type="xs:string" use="optional"/>
<xs:attribute name="class" type="xs:string" use="optional"/>
<xs:attribute name="runner" type="xs:string" use="optional"
default="android.test.InstrumentationTestRunner"/>
<xs:attribute name="coverage_target" type="xs:string" use="optional"/>
<xs:attribute name="continuous" type="xs:boolean" use="optional" default="false"/>
<xs:attribute name="cts" type="xs:boolean" use="optional" default="false"/>
<!-- File system path, relative to Android build root, to this
package's Android.mk file. -->
<xs:attribute name="build_path" type="xs:string" use="required" />
<!-- Include test in continuous test system. -->
<xs:attribute name="continuous" type="xs:boolean" use="optional"
default="false" />
<!-- Include test in compatibility test suite. -->
<xs:attribute name="cts" type="xs:boolean" use="optional"
default="false" />
<!-- Short description (typically less than 60 characters) about this
test. -->
<xs:attribute name="description" type="xs:string" use="optional" />
<!-- Extra arguments to append to build command when building this
test. -->
<xs:attribute name="extra_build_args" type="xs:string"
use="optional" />
</xs:complexType>
<!-- Java on device instrumentation test.
The test attributes map to the following commands:
(if class is defined)
adb shell am instrument -w <package>/<runner>
(else)
adb shell am instrument -w -e class <class> <package>/<runner>
-->
<xs:complexType name="javaTestType">
<xs:complexContent>
<xs:extension base="testType">
<!-- Android application package that contains the tests. -->
<xs:attribute name="package" type="xs:string" use="required" />
<!-- Fully qualified Java test class to run. -->
<xs:attribute name="class" type="xs:string" use="optional" />
<!-- Fully qualified InstrumentationTestRunner to execute. -->
<xs:attribute name="runner" type="xs:string" use="optional"
default="android.test.InstrumentationTestRunner" />
<!-- Build name of Android package this test targets. These
targets are defined in the coverage_targets.xml file. Used as
basis for code coverage metrics. If omitted, code coverage will
not be supported for this test. -->
<xs:attribute name="coverage_target" type="xs:string"
use="optional" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- Native (C/C++) on device tests.
The native test attributes map to the following commands:
make <build_path>/Android.mk <extra_build_args>
adb sync
for test_prog in <tests built>; do
adb shell "/system/bin/${test_prog} >/dev/null 2>&1;echo \$?"
adb shell "rm /system/bin/${test_prog}"
done
-->
<xs:complexType name="nativeTestType">
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="build_path" type="xs:string" use="required"/>
<xs:attribute name="extra_make_args" type="xs:string" use="optional"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="continuous" type="xs:boolean" use="optional" default="false"/>
<xs:complexContent>
<xs:extension base="testType" />
<!-- no additional attributes -->
</xs:complexContent>
</xs:complexType>
<!-- Host java tests.
Uses hosttestlib to execute tests on host. Maps to following command:
java -cp <libs>:jar_name com.android.hosttest.DeviceTestRunner \
<class> -s <device serial> -p <app build path>
-->
<xs:complexType name="hostTestType">
<xs:complexContent>
<xs:extension base="testType">
<!-- The test class to run. Must extend DeviceTestSuite, and
implement a public static suite() method that returns a Test to
run. -->
<xs:attribute name="class" type="xs:string" use="required" />
<!-- built jar name of host library that includes the tests. -->
<xs:attribute name="jar_name" type="xs:string" use="required" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

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

View File

@@ -0,0 +1,111 @@
#!/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."""
# Python imports
import xml.dom.minidom
import xml.parsers
# local imports
import errors
import logger
class AbstractTestSuite(object):
"""Represents a generic test suite definition parsed from xml.
This class will parse the XML attributes common to all TestSuite's.
"""
_NAME_ATTR = "name"
_BUILD_ATTR = "build_path"
_CONTINUOUS_ATTR = "continuous"
_CTS_ATTR = "cts"
_DESCRIPTION_ATTR = "description"
_EXTRA_BUILD_ARGS_ATTR = "extra_build_args"
def __init__(self):
self._attr_map = {}
def Parse(self, suite_element):
"""Populates this instance's data from given suite xml element.
Raises:
ParseError if a required attribute is missing.
"""
# parse name first so it can be used for error reporting
self._ParseAttribute(suite_element, self._NAME_ATTR, True)
self._ParseAttribute(suite_element, self._BUILD_ATTR, True)
self._ParseAttribute(suite_element, self._CONTINUOUS_ATTR, False,
default_value=False)
self._ParseAttribute(suite_element, self._CTS_ATTR, False,
default_value=False)
self._ParseAttribute(suite_element, self._DESCRIPTION_ATTR, False,
default_value="")
self._ParseAttribute(suite_element, self._EXTRA_BUILD_ARGS_ATTR, False,
default_value="")
def _ParseAttribute(self, suite_element, attribute_name, mandatory,
default_value=None):
if suite_element.hasAttribute(attribute_name):
self._attr_map[attribute_name] = \
suite_element.getAttribute(attribute_name)
elif mandatory:
error_msg = ("Could not find attribute %s in %s %s" %
(attribute_name, self.TAG_NAME, self.GetName()))
raise errors.ParseError(msg=error_msg)
else:
self._attr_map[attribute_name] = default_value
def GetName(self):
return self._GetAttribute(self._NAME_ATTR)
def GetBuildPath(self):
"""Returns the build path of this test, relative to source tree root."""
return self._GetAttribute(self._BUILD_ATTR)
def GetBuildDependencies(self, options):
"""Returns a list of dependent build paths."""
return []
def IsContinuous(self):
"""Returns true if test is flagged as being part of the continuous tests"""
return self._GetAttribute(self._CONTINUOUS_ATTR)
def IsCts(self):
"""Returns true if test is part of the compatibility test suite"""
return self._GetAttribute(self._CTS_ATTR)
def GetDescription(self):
"""Returns a description if available, an empty string otherwise."""
return self._GetAttribute(self._DESCRIPTION_ATTR)
def GetExtraBuildArgs(self):
"""Returns the extra build args if available, an empty string otherwise."""
return self._GetAttribute(self._EXTRA_BUILD_ARGS_ATTR)
def _GetAttribute(self, attribute_name):
return self._attr_map.get(attribute_name)
def Run(self, options, adb):
"""Runs the test.
Subclasses must implement this.
Args:
options: global command line options
"""
raise NotImplementedError

View File

@@ -0,0 +1,107 @@
#!/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
# local imports
from abstract_test import AbstractTestSuite
import errors
import logger
import run_command
class HostTestSuite(AbstractTestSuite):
"""A test suite for running hosttestlib java tests."""
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"
_JUNIT_JAR_NAME = "junit.jar"
_HOSTTESTLIB_NAME = "hosttestlib.jar"
_DDMLIB_NAME = "ddmlib.jar"
_lib_names = [_JUNIT_JAR_NAME, _HOSTTESTLIB_NAME, _DDMLIB_NAME]
_JUNIT_BUILD_PATH = os.path.join("external", "junit")
_HOSTTESTLIB_BUILD_PATH = os.path.join("development", "tools", "hosttestlib")
_DDMLIB_BUILD_PATH = os.path.join("development", "tools", "ddms", "libs",
"ddmlib")
_LIB_BUILD_PATHS = [_JUNIT_BUILD_PATH, _HOSTTESTLIB_BUILD_PATH,
_DDMLIB_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 Parse(self, suite_element):
super(HostTestSuite, self).Parse(suite_element)
self._ParseAttribute(suite_element, self._CLASS_ATTR, True)
self._ParseAttribute(suite_element, self._JAR_ATTR, True)
def GetBuildDependencies(self, options):
"""Override parent to tag on building host libs."""
return self._LIB_BUILD_PATHS
def GetClass(self):
return self._GetAttribute(self._CLASS_ATTR)
def GetJarName(self):
"""Returns the name of the host jar that contains the tests."""
return self._GetAttribute(self._JAR_ATTR)
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
"""
# 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.GetClass(), 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,169 @@
#!/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."""
# python imports
import os
# local imports
from abstract_test import AbstractTestSuite
import coverage
import errors
import logger
class InstrumentationTestSuite(AbstractTestSuite):
"""Represents a java instrumentation test suite definition run on Android device."""
# 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"
_DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
# build path to Emma target Makefile
_EMMA_BUILD_PATH = os.path.join("external", "emma")
def _GetTagName(self):
return self._TAG_NAME
def GetPackageName(self):
return self._GetAttribute(self._PKG_ATTR)
def GetRunnerName(self):
return self._GetAttribute(self._RUNNER_ATTR)
def GetClassName(self):
return self._GetAttribute(self._CLASS_ATTR)
def GetTargetName(self):
"""Retrieve module that this test is targeting.
Used for generating code coverage metrics.
"""
return self._GetAttribute(self._TARGET_ATTR)
def GetBuildDependencies(self, options):
if options.coverage:
return [self._EMMA_BUILD_PATH]
return []
def Parse(self, suite_element):
super(InstrumentationTestSuite, self).Parse(suite_element)
self._ParseAttribute(suite_element, self._PKG_ATTR, True)
self._ParseAttribute(suite_element, self._RUNNER_ATTR, False, self._DEFAULT_RUNNER)
self._ParseAttribute(suite_element, self._CLASS_ATTR, False)
self._ParseAttribute(suite_element, self._TARGET_ATTR, False)
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
"""
test_class = self.GetClassName()
if options.test_class is not None:
test_class = options.test_class.lstrip()
if test_class.startswith("."):
test_class = test_suite.GetPackageName() + test_class
if options.test_method is not None:
test_class = "%s#%s" % (test_class, options.test_method)
instrumentation_args = {}
if test_class is not None:
instrumentation_args["class"] = test_class
if options.test_package:
instrumentation_args["package"] = options.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.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 not coverage_gen.TestDeviceCoverageSupport():
raise errors.AbortError
adb.WaitForInstrumentation(self.GetPackageName(),
self.GetRunnerName())
# 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)
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, device_coverage_path)
if coverage_file is not None:
logger.Log("Coverage report generated at %s" % coverage_file)
else:
adb.WaitForInstrumentation(self.GetPackageName(),
self.GetRunnerName())
adb.StartInstrumentationNoResults(
package_name=self.GetPackageName(),
runner_name=self.GetRunnerName(),
raw_mode=options.raw_mode,
instrumentation_args=instrumentation_args)
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))

View File

@@ -0,0 +1,153 @@
#!/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 for running native Android tests."""
# python imports
import os
# local imports
from abstract_test import AbstractTestSuite
import android_build
import errors
import logger
import run_command
class NativeTestSuite(AbstractTestSuite):
"""A test suite for running native aka C/C++ tests on device."""
TAG_NAME = "test-native"
def _GetTagName(self):
return self._TAG_NAME
def Parse(self, suite_element):
super(NativeTestSuite, self).Parse(suite_element)
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 = 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/system/bin.
target_list = self._FilterOutMissing(android_build.GetTargetSystemBin(),
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, "system", "bin", f)
# Single quotes are needed to prevent the shell splitting it.
output = self._adb.SendShellCommand("'%s 2>&1;echo -n exit code:$?'" %
full_path,
int(self._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":
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 test binaries built from the sources.
"""
binaries = []
for f in sources:
binary = os.path.basename(f)
binary = os.path.splitext(binary)[0]
full_path = os.path.join(path, binary)
if os.path.exists(full_path):
binaries.append(binary)
return binaries

View File

@@ -0,0 +1,146 @@
#!/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
from instrumentation_test import InstrumentationTestSuite
from native_test import NativeTestSuite
from host_test import HostTestSuite
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)
for element in root_element.childNodes:
if element.nodeType != xml.dom.Node.ELEMENT_NODE:
continue
test_suite = None
if element.nodeName == InstrumentationTestSuite.TAG_NAME:
test_suite = InstrumentationTestSuite()
elif element.nodeName == NativeTestSuite.TAG_NAME:
test_suite = NativeTestSuite()
elif element.nodeName == HostTestSuite.TAG_NAME:
test_suite = HostTestSuite()
else:
logger.Log("Unrecognized tag %s found" % element.nodeName)
continue
test_suite.Parse(element)
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.Log("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 GetCtsTests(self):
"""Return list of cts tests."""
cts_tests = []
for test in self.GetTests():
if test.IsCts():
cts_tests.append(test)
return cts_tests
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