summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrett Chabot <brettchabot@google.com>2009-06-25 17:57:31 -0700
committerBrett Chabot <brettchabot@google.com>2009-06-26 14:31:52 -0700
commit764d3fa70d42a79e2ee999b790e69fc55f12bf61 (patch)
tree99033717a6c33ed44ea70edf3d04918f91e5550e
parente860ea067609d5cd33bce04a0536600961aceb4a (diff)
downloadandroid_development-764d3fa70d42a79e2ee999b790e69fc55f12bf61.tar.gz
android_development-764d3fa70d42a79e2ee999b790e69fc55f12bf61.tar.bz2
android_development-764d3fa70d42a79e2ee999b790e69fc55f12bf61.zip
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.
-rw-r--r--testrunner/__init__.py1
-rwxr-xr-xtestrunner/adb_interface.py16
-rw-r--r--testrunner/android_build.py41
-rwxr-xr-xtestrunner/coverage.py61
-rwxr-xr-xtestrunner/errors.py31
-rwxr-xr-xtestrunner/runtest.py226
-rw-r--r--testrunner/test_defs.py281
-rw-r--r--testrunner/test_defs.xml85
-rw-r--r--testrunner/test_defs.xsd136
-rw-r--r--testrunner/test_defs/__init__.py1
-rw-r--r--testrunner/test_defs/abstract_test.py111
-rw-r--r--testrunner/test_defs/host_test.py107
-rw-r--r--testrunner/test_defs/instrumentation_test.py169
-rw-r--r--testrunner/test_defs/native_test.py153
-rw-r--r--testrunner/test_defs/test_defs.py146
15 files changed, 928 insertions, 637 deletions
diff --git a/testrunner/__init__.py b/testrunner/__init__.py
new file mode 100644
index 000000000..69ee92b04
--- /dev/null
+++ b/testrunner/__init__.py
@@ -0,0 +1 @@
+__all__ = ['adb_interface', 'android_build', 'errors', 'logger', 'run_command']
diff --git a/testrunner/adb_interface.py b/testrunner/adb_interface.py
index 429bc27d7..33191f7d5 100755
--- a/testrunner/adb_interface.py
+++ b/testrunner/adb_interface.py
@@ -306,7 +306,7 @@ class AdbInterface:
attempts = 0
wait_period = 5
while not pm_found and (attempts*wait_period) < wait_time:
- # assume the 'adb shell pm path android' command will always
+ # assume the 'adb shell pm path android' command will always
# return 'package: something' in the success case
output = self.SendShellCommand("pm path android", retry_count=1)
if "package:" in output:
@@ -357,12 +357,12 @@ class AdbInterface:
def Sync(self, retry_count=3):
"""Perform a adb sync.
-
+
Blocks until device package manager is responding.
-
+
Args:
retry_count: number of times to retry sync before failing
-
+
Raises:
WaitForResponseTimedOutError if package manager does not respond
AbortError if unrecoverable error occurred
@@ -375,12 +375,12 @@ class AdbInterface:
error = e
output = e.msg
if "Read-only file system" in output:
- logger.SilentLog(output)
+ logger.SilentLog(output)
logger.Log("Remounting read-only filesystem")
self.SendCommand("remount")
output = self.SendCommand("sync", retry_count=retry_count)
elif "No space left on device" in output:
- logger.SilentLog(output)
+ logger.SilentLog(output)
logger.Log("Restarting device runtime")
self.SendShellCommand("stop", retry_count=retry_count)
output = self.SendCommand("sync", retry_count=retry_count)
@@ -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()
+
diff --git a/testrunner/android_build.py b/testrunner/android_build.py
index 976f2bb1f..37ddf9e65 100644
--- a/testrunner/android_build.py
+++ b/testrunner/android_build.py
@@ -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
diff --git a/testrunner/coverage.py b/testrunner/coverage.py
index c80eea0a4..eb46f1f49 100755
--- a/testrunner/coverage.py
+++ b/testrunner/coverage.py
@@ -3,16 +3,16 @@
#
# 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
+# 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
+# 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
+# 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.
"""Utilities for generating code coverage reports for Android tests."""
@@ -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,16 +71,18 @@ class CoverageGenerator(object):
Returns:
True if device can support code coverage. False otherwise.
"""
- output = self._adb.SendShellCommand("cat init.rc | grep BOOTCLASSPATH | "
- "grep emma.jar")
- if len(output) > 0:
- return True
- else:
- 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"
- " to add emma.jar")
- return False
+ try:
+ output = self._adb.SendShellCommand("cat init.rc | grep BOOTCLASSPATH | "
+ "grep emma.jar")
+ if len(output) > 0:
+ return True
+ 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"
+ " to add emma.jar")
+ return False
def ExtractReport(self, test_suite,
device_coverage_path,
@@ -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
diff --git a/testrunner/errors.py b/testrunner/errors.py
index c04fd012c..e163dd45b 100755
--- a/testrunner/errors.py
+++ b/testrunner/errors.py
@@ -3,41 +3,44 @@
#
# 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
+# 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
+# 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
+# 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.
"""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."""
class DeviceUnresponsiveError(Exception):
"""Device is unresponsive to command."""
-
+
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."""
diff --git a/testrunner/runtest.py b/testrunner/runtest.py
index 03eddbf70..34f979fc0 100755
--- a/testrunner/runtest.py
+++ b/testrunner/runtest.py
@@ -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:
diff --git a/testrunner/test_defs.py b/testrunner/test_defs.py
deleted file mode 100644
index 0542a053d..000000000
--- a/testrunner/test_defs.py
+++ /dev/null
@@ -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
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 08fe0e850..eaaec94b7 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -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.
-
-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
+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.
+
+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>
diff --git a/testrunner/test_defs.xsd b/testrunner/test_defs.xsd
index f9647795f..65c032fde 100644
--- a/testrunner/test_defs.xsd
+++ b/testrunner/test_defs.xsd
@@ -1,37 +1,135 @@
<?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"
- elementFormDefault="qualified">
+ targetNamespace="http://schemas.android.com/testrunner/test_defs/1.0"
+ xmlns="http://schemas.android.com/testrunner/test_defs/1.0"
+ elementFormDefault="qualified">
<xs:element name="test-definitions">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
- <xs:element name="test" type="javaTestType"/>
- <xs:element name="test-native" type="nativeTestType"/>
+ <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>
+ <!-- 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" />
+
+ <!-- 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: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"/>
+ <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>
diff --git a/testrunner/test_defs/__init__.py b/testrunner/test_defs/__init__.py
new file mode 100644
index 000000000..c205dcb8c
--- /dev/null
+++ b/testrunner/test_defs/__init__.py
@@ -0,0 +1 @@
+__all__ = ['test_defs']
diff --git a/testrunner/test_defs/abstract_test.py b/testrunner/test_defs/abstract_test.py
new file mode 100644
index 000000000..7c4d63dde
--- /dev/null
+++ b/testrunner/test_defs/abstract_test.py
@@ -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
diff --git a/testrunner/test_defs/host_test.py b/testrunner/test_defs/host_test.py
new file mode 100644
index 000000000..4aefa3a7b
--- /dev/null
+++ b/testrunner/test_defs/host_test.py
@@ -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)
diff --git a/testrunner/test_defs/instrumentation_test.py b/testrunner/test_defs/instrumentation_test.py
new file mode 100644
index 000000000..fcc9b42c8
--- /dev/null
+++ b/testrunner/test_defs/instrumentation_test.py
@@ -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))
diff --git a/testrunner/test_defs/native_test.py b/testrunner/test_defs/native_test.py
new file mode 100644
index 000000000..1e79872ff
--- /dev/null
+++ b/testrunner/test_defs/native_test.py
@@ -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
diff --git a/testrunner/test_defs/test_defs.py b/testrunner/test_defs/test_defs.py
new file mode 100644
index 000000000..7f23b8958
--- /dev/null
+++ b/testrunner/test_defs/test_defs.py
@@ -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