summaryrefslogtreecommitdiffstats
path: root/testrunner
diff options
context:
space:
mode:
authorBrett Chabot <brettchabot@google.com>2009-06-29 16:37:41 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-06-29 16:37:41 -0700
commitf03d30eeea7ece108e1a4b4f89edd665bf30fe7a (patch)
tree77e8fa0a34d0043431c7623bdbf9ec59bcc80d55 /testrunner
parentdec4454c5f3546934dfe80bb05532320cc4a82fc (diff)
parent764d3fa70d42a79e2ee999b790e69fc55f12bf61 (diff)
downloadandroid_development-f03d30eeea7ece108e1a4b4f89edd665bf30fe7a.tar.gz
android_development-f03d30eeea7ece108e1a4b4f89edd665bf30fe7a.tar.bz2
android_development-f03d30eeea7ece108e1a4b4f89edd665bf30fe7a.zip
am 764d3fa7: Add support for running host java tests to runtest.
Merge commit '764d3fa70d42a79e2ee999b790e69fc55f12bf61' * commit '764d3fa70d42a79e2ee999b790e69fc55f12bf61': Add support for running host java tests to runtest.
Diffstat (limited to 'testrunner')
-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 1520c43ea..7267221e6 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"
@@ -515,7 +455,7 @@ 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" />
<test-native name="libskia"
build_path="external/skia/tests"
@@ -525,13 +465,20 @@ Native 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