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=""] |
| 41 | [continuous] |
| 42 | /> |
| 43 | <test ... |
| 44 | </test-definitions> |
| 45 | |
| 46 | TODO: add format checking. |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 47 | """ |
| 48 | |
| 49 | # tag/attribute constants |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 50 | _TEST_TAG_NAME = "test" |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 51 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 52 | def __init__(self): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 53 | # dictionary of test name to tests |
| 54 | self._testname_map = {} |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 55 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 56 | def __iter__(self): |
| 57 | return iter(self._testname_map.values()) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 58 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 59 | def Parse(self, file_path): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 60 | """Parse the test suite data from from given file path. |
| 61 | |
| 62 | Args: |
| 63 | file_path: absolute file path to parse |
| 64 | Raises: |
| 65 | ParseError if file_path cannot be parsed |
| 66 | """ |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 67 | try: |
| 68 | doc = xml.dom.minidom.parse(file_path) |
| 69 | except IOError: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 70 | 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] | 71 | raise errors.ParseError |
| 72 | except xml.parsers.expat.ExpatError: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 73 | logger.Log("Error Parsing xml file: %s " % file_path) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 74 | raise errors.ParseError |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 75 | self._ParseDoc(doc) |
| 76 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 77 | def ParseString(self, xml_string): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 78 | """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] | 79 | doc = xml.dom.minidom.parseString(xml_string) |
| 80 | # TODO: catch exceptions and raise ParseError |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 81 | return self._ParseDoc(doc) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 82 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 83 | def _ParseDoc(self, doc): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 84 | suite_elements = doc.getElementsByTagName(self._TEST_TAG_NAME) |
| 85 | |
| 86 | for suite_element in suite_elements: |
| 87 | test = self._ParseTestSuite(suite_element) |
| 88 | self._AddTest(test) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 89 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 90 | def _ParseTestSuite(self, suite_element): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 91 | """Parse the suite element. |
| 92 | |
| 93 | Returns: |
| 94 | a TestSuite object, populated with parsed data |
| 95 | """ |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 96 | test = TestSuite(suite_element) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 97 | return test |
| 98 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 99 | def _AddTest(self, test): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 100 | """Adds a test to this TestManifest. |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 101 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 102 | If a test already exists with the same name, it overrides it. |
| 103 | |
| 104 | Args: |
| 105 | test: TestSuite to add |
| 106 | """ |
| 107 | self._testname_map[test.GetName()] = test |
| 108 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 109 | def GetTests(self): |
| 110 | return self._testname_map.values() |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 111 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 112 | def GetContinuousTests(self): |
| 113 | con_tests = [] |
| 114 | for test in self.GetTests(): |
| 115 | if test.IsContinuous(): |
| 116 | con_tests.append(test) |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 117 | return con_tests |
| 118 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 119 | def GetTest(self, name): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 120 | return self._testname_map.get(name, None) |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 121 | |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 122 | class TestSuite(object): |
| 123 | """Represents one test suite definition parsed from xml.""" |
| 124 | |
| 125 | _NAME_ATTR = "name" |
| 126 | _PKG_ATTR = "package" |
| 127 | _RUNNER_ATTR = "runner" |
| 128 | _CLASS_ATTR = "class" |
| 129 | _TARGET_ATTR = "coverage_target" |
| 130 | _BUILD_ATTR = "build_path" |
| 131 | _CONTINUOUS_ATTR = "continuous" |
| 132 | |
| 133 | _DEFAULT_RUNNER = "android.test.InstrumentationTestRunner" |
| 134 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 135 | def __init__(self, suite_element): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 136 | """Populates this instance's data from given suite xml element.""" |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 137 | self._name = suite_element.getAttribute(self._NAME_ATTR) |
| 138 | self._package = suite_element.getAttribute(self._PKG_ATTR) |
| 139 | if suite_element.hasAttribute(self._RUNNER_ATTR): |
| 140 | self._runner = suite_element.getAttribute(self._RUNNER_ATTR) |
| 141 | else: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 142 | self._runner = self._DEFAULT_RUNNER |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 143 | if suite_element.hasAttribute(self._CLASS_ATTR): |
| 144 | self._class = suite_element.getAttribute(self._CLASS_ATTR) |
| 145 | else: |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 146 | self._class = None |
| 147 | if suite_element.hasAttribute(self._TARGET_ATTR): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 148 | self._target_name = suite_element.getAttribute(self._TARGET_ATTR) |
| 149 | else: |
| 150 | self._target_name = None |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 151 | if suite_element.hasAttribute(self._BUILD_ATTR): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 152 | self._build_path = suite_element.getAttribute(self._BUILD_ATTR) |
| 153 | else: |
| 154 | self._build_path = None |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 155 | if suite_element.hasAttribute(self._CONTINUOUS_ATTR): |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 156 | self._continuous = suite_element.getAttribute(self._CONTINUOUS_ATTR) |
| 157 | else: |
| 158 | self._continuous = False |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 159 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 160 | def GetName(self): |
| 161 | return self._name |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 162 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 163 | def GetPackageName(self): |
| 164 | return self._package |
| 165 | |
| 166 | def GetRunnerName(self): |
| 167 | return self._runner |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 168 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 169 | def GetClassName(self): |
| 170 | return self._class |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 171 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 172 | def GetTargetName(self): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 173 | """Retrieve module that this test is targeting. |
| 174 | |
| 175 | Used for generating code coverage metrics. |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 176 | """ |
| 177 | return self._target_name |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 178 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 179 | def GetBuildPath(self): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 180 | """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] | 181 | return self._build_path |
| 182 | |
| 183 | def IsContinuous(self): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 184 | """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] | 185 | return self._continuous |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 186 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 187 | def Parse(file_path): |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 188 | """Parses out a TestDefinitions from given path to xml file. |
| 189 | |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 190 | Args: |
| 191 | file_path: string absolute file path |
The Android Open Source Project | 2b83cbd | 2009-03-05 17:04:45 -0800 | [diff] [blame^] | 192 | Returns: |
| 193 | a TestDefinitions object containing data parsed from file_path |
The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 194 | Raises: |
| 195 | ParseError if xml format is not recognized |
| 196 | """ |
| 197 | tests_result = TestDefinitions() |
| 198 | tests_result.Parse(file_path) |
| 199 | return tests_result |