The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 1 | #!/usr/bin/python2.4 |
| 2 | # |
| 3 | # |
| 4 | # Copyright 2008, The Android Open Source Project |
| 5 | # |
| 6 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | # you may not use this file except in compliance with the License. |
| 8 | # You may obtain a copy of the License at |
| 9 | # |
| 10 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | # |
| 12 | # Unless required by applicable law or agreed to in writing, software |
| 13 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | # See the License for the specific language governing permissions and |
| 16 | # limitations under the License. |
| 17 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 18 | """Parser for test definition xml files.""" |
| 19 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 20 | # Python imports |
| 21 | import xml.dom.minidom |
| 22 | import xml.parsers |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 23 | |
| 24 | # local imports |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 25 | import errors |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 26 | import logger |
| 27 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 28 | |
| 29 | class TestDefinitions(object): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 30 | """Accessor for a test definitions xml file data. |
| 31 | |
| 32 | Expected format is: |
| 33 | <test-definitions> |
| 34 | <test |
| 35 | name="" |
| 36 | package="" |
| 37 | [runner=""] |
| 38 | [class=""] |
| 39 | [coverage_target=""] |
| 40 | [build_path=""] |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 41 | [continuous=false] |
| 42 | [description=""] |
| 43 | /> |
| 44 | <test-native |
| 45 | name="" |
| 46 | build_path="" |
| 47 | [continuous=false] |
| 48 | [description=""] |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 49 | /> |
| 50 | <test ... |
| 51 | </test-definitions> |
| 52 | |
| 53 | TODO: add format checking. |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 54 | """ |
| 55 | |
| 56 | # tag/attribute constants |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 57 | _TEST_TAG_NAME = "test" |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 58 | _TEST_NATIVE_TAG_NAME = "test-native" |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 59 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 60 | def __init__(self): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 61 | # dictionary of test name to tests |
| 62 | self._testname_map = {} |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 63 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 64 | def __iter__(self): |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 65 | ordered_list = [] |
| 66 | for k in sorted(self._testname_map): |
| 67 | ordered_list.append(self._testname_map[k]) |
| 68 | return iter(ordered_list) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 69 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 70 | def Parse(self, file_path): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 71 | """Parse the test suite data from from given file path. |
| 72 | |
| 73 | Args: |
| 74 | file_path: absolute file path to parse |
| 75 | Raises: |
| 76 | ParseError if file_path cannot be parsed |
| 77 | """ |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 78 | try: |
| 79 | doc = xml.dom.minidom.parse(file_path) |
| 80 | except IOError: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 81 | logger.Log("test file %s does not exist" % file_path) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 82 | raise errors.ParseError |
| 83 | except xml.parsers.expat.ExpatError: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 84 | logger.Log("Error Parsing xml file: %s " % file_path) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 85 | raise errors.ParseError |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 86 | self._ParseDoc(doc) |
| 87 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 88 | def ParseString(self, xml_string): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 89 | """Alternate parse method that accepts a string of the xml data.""" |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 90 | doc = xml.dom.minidom.parseString(xml_string) |
| 91 | # TODO: catch exceptions and raise ParseError |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 92 | return self._ParseDoc(doc) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 93 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 94 | def _ParseDoc(self, doc): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 95 | suite_elements = doc.getElementsByTagName(self._TEST_TAG_NAME) |
| 96 | |
| 97 | for suite_element in suite_elements: |
| 98 | test = self._ParseTestSuite(suite_element) |
| 99 | self._AddTest(test) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 100 | |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 101 | suite_elements = doc.getElementsByTagName(self._TEST_NATIVE_TAG_NAME) |
| 102 | |
| 103 | for suite_element in suite_elements: |
| 104 | test = self._ParseNativeTestSuite(suite_element) |
| 105 | self._AddTest(test) |
| 106 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 107 | def _ParseTestSuite(self, suite_element): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 108 | """Parse the suite element. |
| 109 | |
| 110 | Returns: |
| 111 | a TestSuite object, populated with parsed data |
| 112 | """ |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 113 | test = TestSuite(suite_element) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 114 | return test |
| 115 | |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 116 | def _ParseNativeTestSuite(self, suite_element): |
| 117 | """Parse the native test element. |
| 118 | |
| 119 | Returns: |
| 120 | a TestSuite object, populated with parsed data |
| 121 | Raises: |
| 122 | ParseError if some required attribute is missing. |
| 123 | """ |
| 124 | test = TestSuite(suite_element, native=True) |
| 125 | return test |
| 126 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 127 | def _AddTest(self, test): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 128 | """Adds a test to this TestManifest. |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 129 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 130 | If a test already exists with the same name, it overrides it. |
| 131 | |
| 132 | Args: |
| 133 | test: TestSuite to add |
| 134 | """ |
| 135 | self._testname_map[test.GetName()] = test |
| 136 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 137 | def GetTests(self): |
| 138 | return self._testname_map.values() |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 139 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 140 | def GetContinuousTests(self): |
| 141 | con_tests = [] |
| 142 | for test in self.GetTests(): |
| 143 | if test.IsContinuous(): |
| 144 | con_tests.append(test) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 145 | return con_tests |
| 146 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 147 | def GetTest(self, name): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 148 | return self._testname_map.get(name, None) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 149 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 150 | class TestSuite(object): |
| 151 | """Represents one test suite definition parsed from xml.""" |
| 152 | |
| 153 | _NAME_ATTR = "name" |
| 154 | _PKG_ATTR = "package" |
| 155 | _RUNNER_ATTR = "runner" |
| 156 | _CLASS_ATTR = "class" |
| 157 | _TARGET_ATTR = "coverage_target" |
| 158 | _BUILD_ATTR = "build_path" |
| 159 | _CONTINUOUS_ATTR = "continuous" |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 160 | _DESCRIPTION_ATTR = "description" |
Niko Catania | a6dc2ab | 2009-04-03 14:12:46 -0700 | [diff] [blame] | 161 | _EXTRA_MAKE_ARGS_ATTR = "extra_make_args" |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 162 | |
| 163 | _DEFAULT_RUNNER = "android.test.InstrumentationTestRunner" |
| 164 | |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 165 | def __init__(self, suite_element, native=False): |
| 166 | """Populates this instance's data from given suite xml element. |
| 167 | Raises: |
| 168 | ParseError if some required attribute is missing. |
| 169 | """ |
| 170 | self._native = native |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 171 | self._name = suite_element.getAttribute(self._NAME_ATTR) |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 172 | |
| 173 | if self._native: |
| 174 | # For native runs, _BUILD_ATTR is required |
| 175 | if not suite_element.hasAttribute(self._BUILD_ATTR): |
| 176 | logger.Log("Error: %s is missing required build_path attribute" % |
| 177 | self._name) |
| 178 | raise errors.ParseError |
| 179 | else: |
| 180 | self._package = suite_element.getAttribute(self._PKG_ATTR) |
| 181 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 182 | if suite_element.hasAttribute(self._RUNNER_ATTR): |
| 183 | self._runner = suite_element.getAttribute(self._RUNNER_ATTR) |
| 184 | else: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 185 | self._runner = self._DEFAULT_RUNNER |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 186 | if suite_element.hasAttribute(self._CLASS_ATTR): |
| 187 | self._class = suite_element.getAttribute(self._CLASS_ATTR) |
| 188 | else: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 189 | self._class = None |
| 190 | if suite_element.hasAttribute(self._TARGET_ATTR): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 191 | self._target_name = suite_element.getAttribute(self._TARGET_ATTR) |
| 192 | else: |
| 193 | self._target_name = None |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 194 | if suite_element.hasAttribute(self._BUILD_ATTR): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 195 | self._build_path = suite_element.getAttribute(self._BUILD_ATTR) |
| 196 | else: |
| 197 | self._build_path = None |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 198 | if suite_element.hasAttribute(self._CONTINUOUS_ATTR): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 199 | self._continuous = suite_element.getAttribute(self._CONTINUOUS_ATTR) |
| 200 | else: |
| 201 | self._continuous = False |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 202 | if suite_element.hasAttribute(self._DESCRIPTION_ATTR): |
| 203 | self._description = suite_element.getAttribute(self._DESCRIPTION_ATTR) |
| 204 | else: |
| 205 | self._description = "" |
Niko Catania | a6dc2ab | 2009-04-03 14:12:46 -0700 | [diff] [blame] | 206 | if suite_element.hasAttribute(self._EXTRA_MAKE_ARGS_ATTR): |
| 207 | self._extra_make_args = suite_element.getAttribute( |
| 208 | self._EXTRA_MAKE_ARGS_ATTR) |
| 209 | else: |
| 210 | self._extra_make_args = "" |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 211 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 212 | def GetName(self): |
| 213 | return self._name |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 214 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 215 | def GetPackageName(self): |
| 216 | return self._package |
| 217 | |
| 218 | def GetRunnerName(self): |
| 219 | return self._runner |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 220 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 221 | def GetClassName(self): |
| 222 | return self._class |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 223 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 224 | def GetTargetName(self): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 225 | """Retrieve module that this test is targeting. |
| 226 | |
| 227 | Used for generating code coverage metrics. |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 228 | """ |
| 229 | return self._target_name |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 230 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 231 | def GetBuildPath(self): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 232 | """Returns the build path of this test, relative to source tree root.""" |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 233 | return self._build_path |
| 234 | |
| 235 | def IsContinuous(self): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 236 | """Returns true if test is flagged as being part of the continuous tests""" |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 237 | return self._continuous |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 238 | |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 239 | def IsNative(self): |
| 240 | """Returns true if test is a native one.""" |
| 241 | return self._native |
| 242 | |
| 243 | def GetDescription(self): |
Niko Catania | a6dc2ab | 2009-04-03 14:12:46 -0700 | [diff] [blame] | 244 | """Returns a description if available, an empty string otherwise.""" |
Niko Catania | 2e990b9 | 2009-04-02 16:52:26 -0700 | [diff] [blame] | 245 | return self._description |
| 246 | |
Niko Catania | a6dc2ab | 2009-04-03 14:12:46 -0700 | [diff] [blame] | 247 | def GetExtraMakeArgs(self): |
| 248 | """Returns the extra make args if available, an empty string otherwise.""" |
| 249 | return self._extra_make_args |
| 250 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 251 | def Parse(file_path): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 252 | """Parses out a TestDefinitions from given path to xml file. |
| 253 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 254 | Args: |
| 255 | file_path: string absolute file path |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame] | 256 | Returns: |
| 257 | a TestDefinitions object containing data parsed from file_path |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 258 | Raises: |
| 259 | ParseError if xml format is not recognized |
| 260 | """ |
| 261 | tests_result = TestDefinitions() |
| 262 | tests_result.Parse(file_path) |
| 263 | return tests_result |