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