From 924c0898792f4e14eb87a91a8e30d7138fb68be0 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Wed, 21 Oct 2009 14:23:54 -0700 Subject: [PATCH] Refactor runtest test_defs to allow programmatic creation. Previously a test definition could only be created via xml. Also fix some minor lint warnings. --- testrunner/test_defs/abstract_test.py | 113 -------------- testrunner/test_defs/host_test.py | 41 ++--- testrunner/test_defs/instrumentation_test.py | 72 +++++---- testrunner/test_defs/native_test.py | 15 +- testrunner/test_defs/test_defs.py | 20 +-- testrunner/test_defs/test_suite.py | 96 ++++++++++++ testrunner/test_defs/xml_suite_helper.py | 152 +++++++++++++++++++ 7 files changed, 319 insertions(+), 190 deletions(-) delete mode 100644 testrunner/test_defs/abstract_test.py create mode 100644 testrunner/test_defs/test_suite.py create mode 100644 testrunner/test_defs/xml_suite_helper.py diff --git a/testrunner/test_defs/abstract_test.py b/testrunner/test_defs/abstract_test.py deleted file mode 100644 index e0c8db206..000000000 --- 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 4aefa3a7b..81050754c 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 @@ class HostTestSuite(AbstractTestSuite): # 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 @@ class HostTestSuite(AbstractTestSuite): 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 @@ class HostTestSuite(AbstractTestSuite): # -p 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 24b4b88fd..63fd7f25e 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 @@ class InstrumentationTestSuite(AbstractTestSuite): 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 @@ class InstrumentationTestSuite(AbstractTestSuite): 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 @@ class InstrumentationTestSuite(AbstractTestSuite): 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 d250de2b2..ad7352a6a 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 7f23b8958..6d885fa50 100644 --- a/testrunner/test_defs/test_defs.py +++ b/testrunner/test_defs/test_defs.py @@ -24,9 +24,7 @@ import xml.parsers # 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 @@ class TestDefinitions(object): 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 000000000..42a0de152 --- /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 000000000..c2ed1ddbd --- /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