Refactor runtest test_defs to allow programmatic creation.
Previously a test definition could only be created via xml.
Also fix some minor lint warnings.
diff --git a/testrunner/test_defs/abstract_test.py b/testrunner/test_defs/abstract_test.py
deleted file mode 100644
index e0c8db2..0000000
--- a/testrunner/test_defs/abstract_test.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/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
-
-
-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 of xml tag a test suite handles. subclasses must define this.
- TAG_NAME = "unspecified"
-
- _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
index 4aefa3a..8105075 100644
--- a/testrunner/test_defs/host_test.py
+++ b/testrunner/test_defs/host_test.py
@@ -20,23 +20,15 @@
# python imports
import os
-# local imports
-from abstract_test import AbstractTestSuite
import errors
import logger
import run_command
+import test_suite
-class HostTestSuite(AbstractTestSuite):
+class HostTestSuite(test_suite.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"
@@ -54,21 +46,29 @@
# 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 __init__(self):
+ test_suite.AbstractTestSuite.__init__(self)
+ self._jar_name = None
+ self._class_name = None
def GetBuildDependencies(self, options):
"""Override parent to tag on building host libs."""
return self._LIB_BUILD_PATHS
- def GetClass(self):
- return self._GetAttribute(self._CLASS_ATTR)
+ def GetClassName(self):
+ return self._class_name
+
+ def SetClassName(self, class_name):
+ self._class_name = class_name
+ return self
def GetJarName(self):
"""Returns the name of the host jar that contains the tests."""
- return self._GetAttribute(self._JAR_ATTR)
+ return self._jar_name
+
+ def SetJarName(self, jar_name):
+ self._jar_name = jar_name
+ return self
def Run(self, options, adb_interface):
"""Runs the host test.
@@ -77,11 +77,14 @@
Args:
options: command line options for running host tests. Expected member
- fields:
+ fields:
host_lib_path: path to directory that contains host library files
test_data_path: path to directory that contains test data files
preview: if true, do not execute, display commands only
adb_interface: reference to device under test
+
+ Raises:
+ errors.AbortError: if fatal error occurs
"""
# get the serial number of the device under test, so it can be passed to
# hosttestlib.
@@ -100,7 +103,7 @@
# -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,
+ self.GetClassName(), serial_number,
options.test_data_path)
logger.Log(cmd)
if not options.preview:
diff --git a/testrunner/test_defs/instrumentation_test.py b/testrunner/test_defs/instrumentation_test.py
index 24b4b88..63fd7f2 100644
--- a/testrunner/test_defs/instrumentation_test.py
+++ b/testrunner/test_defs/instrumentation_test.py
@@ -21,59 +21,66 @@
import os
# local imports
-from abstract_test import AbstractTestSuite
import coverage
import errors
import logger
+import test_suite
-class InstrumentationTestSuite(AbstractTestSuite):
- """Represents a java instrumentation test suite definition run on Android device."""
+class InstrumentationTestSuite(test_suite.AbstractTestSuite):
+ """Represents a java instrumentation test suite definition run on 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"
+ 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 __init__(self):
+ test_suite.AbstractTestSuite.__init__(self)
+ self._package_name = None
+ self._runner_name = self.DEFAULT_RUNNER
+ self._class_name = None
+ self._target_name = None
def GetPackageName(self):
- return self._GetAttribute(self._PKG_ATTR)
+ return self._package_name
+
+ def SetPackageName(self, package_name):
+ self._package_name = package_name
+ return self
def GetRunnerName(self):
- return self._GetAttribute(self._RUNNER_ATTR)
+ return self._runner_name
+
+ def SetRunnerName(self, runner_name):
+ self._runner_name = runner_name
+ return self
def GetClassName(self):
- return self._GetAttribute(self._CLASS_ATTR)
+ return self._class_name
+
+ def SetClassName(self, class_name):
+ self._class_name = class_name
+ return self
def GetTargetName(self):
"""Retrieve module that this test is targeting.
Used for generating code coverage metrics.
+ Returns:
+ the module target name
"""
- return self._GetAttribute(self._TARGET_ATTR)
+ return self._target_name
+
+ def SetTargetName(self, target_name):
+ self._target_name = target_name
+ return self
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.
@@ -82,6 +89,9 @@
Args:
options: command line options to provide to test run
adb: adb_interface to device under test
+
+ Raises:
+ errors.AbortError: if fatal error occurs
"""
test_class = self.GetClassName()
@@ -122,10 +132,10 @@
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)
+ 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)
@@ -156,11 +166,11 @@
error_count = 0
fail_count = 0
for test_result in test_results:
- if test_result.GetStatusCode() == -1: # error
+ 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
+ elif test_result.GetStatusCode() == -2: # failure
logger.Log("Failure in %s: %s" % (test_result.GetTestName(),
test_result.GetFailureReason()))
fail_count+=1
diff --git a/testrunner/test_defs/native_test.py b/testrunner/test_defs/native_test.py
index d250de2..ad7352a 100644
--- a/testrunner/test_defs/native_test.py
+++ b/testrunner/test_defs/native_test.py
@@ -18,28 +18,19 @@
"""TestSuite for running native Android tests."""
# python imports
-import re
import os
+import re
# local imports
-from abstract_test import AbstractTestSuite
import android_build
import logger
import run_command
+import test_suite
-class NativeTestSuite(AbstractTestSuite):
+class NativeTestSuite(test_suite.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.
diff --git a/testrunner/test_defs/test_defs.py b/testrunner/test_defs/test_defs.py
index 7f23b89..6d885fa 100644
--- a/testrunner/test_defs/test_defs.py
+++ b/testrunner/test_defs/test_defs.py
@@ -24,9 +24,7 @@
# local imports
import errors
import logger
-from instrumentation_test import InstrumentationTestSuite
-from native_test import NativeTestSuite
-from host_test import HostTestSuite
+import xml_suite_helper
class TestDefinitions(object):
@@ -74,21 +72,13 @@
def _ParseDoc(self, doc):
root_element = self._GetRootElement(doc)
+ suite_parser = xml_suite_helper.XmlSuiteParser()
for element in root_element.childNodes:
if element.nodeType != xml.dom.Node.ELEMENT_NODE:
continue
- test_suite = 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)
+ test_suite = suite_parser.Parse(element)
+ if test_suite:
+ self._AddTest(test_suite)
def _GetRootElement(self, doc):
root_elements = doc.getElementsByTagName("test-definitions")
diff --git a/testrunner/test_defs/test_suite.py b/testrunner/test_defs/test_suite.py
new file mode 100644
index 0000000..42a0de1
--- /dev/null
+++ b/testrunner/test_defs/test_suite.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2009, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Abstract Android test suite."""
+
+
+class AbstractTestSuite(object):
+ """Represents a generic test suite definition."""
+
+ def __init__(self):
+ self._name = None
+ self._build_path = None
+ self._build_dependencies = []
+ self._is_continuous = False
+ self._is_cts = False
+ self._description = ''
+ self._extra_build_args = ''
+
+ def GetName(self):
+ return self._name
+
+ def SetName(self, name):
+ self._name = name
+ return self
+
+ def GetBuildPath(self):
+ """Returns the build path of this test, relative to source tree root."""
+ return self._build_path
+
+ def SetBuildPath(self, build_path):
+ self._build_path = build_path
+ return self
+
+ def GetBuildDependencies(self, options):
+ """Returns a list of dependent build paths."""
+ return self._build_dependencies
+
+ def SetBuildDependencies(self, build_dependencies):
+ self._build_dependencies = build_dependencies
+ return self
+
+ def IsContinuous(self):
+ """Returns true if test is part of the continuous test."""
+ return self._is_continuous
+
+ def SetContinuous(self, continuous):
+ self._is_continuous = continuous
+ return self._is_continuous
+
+ def IsCts(self):
+ """Returns true if test is part of the compatibility test suite"""
+ return self._is_cts
+
+ def SetCts(self, cts):
+ self._is_cts = cts
+ return self
+
+ def GetDescription(self):
+ """Returns a description if available, an empty string otherwise."""
+ return self._description
+
+ def SetDescription(self, desc):
+ self._description = desc
+ return self
+
+ def GetExtraBuildArgs(self):
+ """Returns the extra build args if available, an empty string otherwise."""
+ return self._extra_build_args
+
+ def SetExtraBuildArgs(self, build_args):
+ self._extra_build_args = build_args
+ return self
+
+ def Run(self, options, adb):
+ """Runs the test.
+
+ Subclasses must implement this.
+ Args:
+ options: global command line options
+ adb: asdb_interface to device under test
+ """
+ raise NotImplementedError
diff --git a/testrunner/test_defs/xml_suite_helper.py b/testrunner/test_defs/xml_suite_helper.py
new file mode 100644
index 0000000..c2ed1dd
--- /dev/null
+++ b/testrunner/test_defs/xml_suite_helper.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2009, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility to parse suite info from xml."""
+
+# Python imports
+import xml.dom.minidom
+import xml.parsers
+
+# local imports
+import errors
+import logger
+import host_test
+import instrumentation_test
+import native_test
+
+
+class XmlSuiteParser(object):
+ """Parses XML attributes common to all TestSuite's."""
+
+ # common attributes
+ _NAME_ATTR = 'name'
+ _BUILD_ATTR = 'build_path'
+ _CONTINUOUS_ATTR = 'continuous'
+ _CTS_ATTR = 'cts'
+ _DESCRIPTION_ATTR = 'description'
+ _EXTRA_BUILD_ARGS_ATTR = 'extra_build_args'
+
+ def Parse(self, element):
+ """Populates common suite attributes from given suite xml element.
+
+ Args:
+ element: xml node to parse
+ Raises:
+ ParseError if a required attribute is missing.
+ Returns:
+ parsed test suite or None
+ """
+ parser = None
+ if element.nodeName == InstrumentationParser.TAG_NAME:
+ parser = InstrumentationParser()
+ elif element.nodeName == NativeParser.TAG_NAME:
+ parser = NativeParser()
+ elif element.nodeName == HostParser.TAG_NAME:
+ parser = HostParser()
+ else:
+ logger.Log('Unrecognized tag %s found' % element.nodeName)
+ return None
+ test_suite = parser.Parse(element)
+ return test_suite
+
+ def _ParseCommonAttributes(self, suite_element, test_suite):
+ test_suite.SetName(self._ParseAttribute(suite_element, self._NAME_ATTR,
+ True))
+ test_suite.SetBuildPath(self._ParseAttribute(suite_element,
+ self._BUILD_ATTR, True))
+ test_suite.SetContinuous(self._ParseAttribute(suite_element,
+ self._CONTINUOUS_ATTR,
+ False, default_value=False))
+ test_suite.SetCts(self._ParseAttribute(suite_element, self._CTS_ATTR, False,
+ default_value=False))
+ test_suite.SetDescription(self._ParseAttribute(suite_element,
+ self._DESCRIPTION_ATTR,
+ False,
+ default_value=''))
+ test_suite.SetExtraBuildArgs(self._ParseAttribute(
+ suite_element, self._EXTRA_BUILD_ARGS_ATTR, False, default_value=''))
+
+ def _ParseAttribute(self, suite_element, attribute_name, mandatory,
+ default_value=None):
+ if suite_element.hasAttribute(attribute_name):
+ value = suite_element.getAttribute(attribute_name)
+ elif mandatory:
+ error_msg = ('Could not find attribute %s in %s' %
+ (attribute_name, self.TAG_NAME))
+ raise errors.ParseError(msg=error_msg)
+ else:
+ value = default_value
+ return value
+
+
+class InstrumentationParser(XmlSuiteParser):
+ """Parses instrumentation suite attributes from xml."""
+
+ # for legacy reasons, the xml tag name for java (device) tests is 'test'
+ TAG_NAME = 'test'
+
+ _PKG_ATTR = 'package'
+ _RUNNER_ATTR = 'runner'
+ _CLASS_ATTR = 'class'
+ _TARGET_ATTR = 'coverage_target'
+
+ def Parse(self, suite_element):
+ """Creates suite and populate with data from xml element."""
+ suite = instrumentation_test.InstrumentationTestSuite()
+ XmlSuiteParser._ParseCommonAttributes(self, suite_element, suite)
+ suite.SetPackageName(self._ParseAttribute(suite_element, self._PKG_ATTR,
+ True))
+ suite.SetRunnerName(self._ParseAttribute(
+ suite_element, self._RUNNER_ATTR, False,
+ instrumentation_test.InstrumentationTestSuite.DEFAULT_RUNNER))
+ suite.SetClassName(self._ParseAttribute(suite_element, self._CLASS_ATTR,
+ False))
+ suite.SetTargetName(self._ParseAttribute(suite_element, self._TARGET_ATTR,
+ False))
+ return suite
+
+
+class NativeParser(XmlSuiteParser):
+ """Parses native suite attributes from xml."""
+
+ TAG_NAME = 'test-native'
+
+ def Parse(self, suite_element):
+ """Creates suite and populate with data from xml element."""
+ suite = native_test.NativeTestSuite()
+ XmlSuiteParser._ParseCommonAttributes(self, suite_element, suite)
+ return suite
+
+
+class HostParser(XmlSuiteParser):
+ """Parses host suite attributes from xml."""
+
+ TAG_NAME = 'test-host'
+
+ _CLASS_ATTR = 'class'
+ # TODO: consider obsoleting in favor of parsing the Android.mk to find the
+ # jar name
+ _JAR_ATTR = 'jar_name'
+
+ def Parse(self, suite_element):
+ """Creates suite and populate with data from xml element."""
+ suite = host_test.HostTestSuite()
+ XmlSuiteParser._ParseCommonAttributes(self, suite_element, suite)
+ suite.SetClassName(self._ParseAttribute(suite_element, self._CLASS_ATTR,
+ True))
+ suite.SetJarName(self._ParseAttribute(suite_element, self._JAR_ATTR, True))
+ return suite