1#!/usr/bin/python2.4 2 3# Copyright (C) 2009 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Utility classes for CTS.""" 18 19import re 20import xml.dom.minidom as minidom 21 22 23class TestPackage(object): 24 """This class represents a test package. 25 26 Each test package consists of one or more suites, each containing one or more subsuites and/or 27 one or more test cases. Each test case contains one or more tests. 28 29 The package structure is currently stored using Python dictionaries and lists. Translation 30 to XML is done via a DOM tree that gets created on demand. 31 32 TODO: Instead of using an internal data structure, using a DOM tree directly would increase 33 the usability. For example, one could easily create an instance by parsing an existing XML. 34 """ 35 36 class TestSuite(object): 37 """A test suite.""" 38 39 def __init__(self, is_root=False): 40 self.is_root = is_root 41 self.test_cases = {} 42 self.test_suites = {} 43 44 def Add(self, names): 45 if len(names) == 2: 46 # names contains the names of the test case and the test 47 test_case = self.test_cases.setdefault(names[0], []) 48 test_case.append(names[1]) 49 else: 50 sub_suite = self.test_suites.setdefault(names[0], TestPackage.TestSuite()) 51 sub_suite.Add(names[1:]) 52 53 def WriteDescription(self, doc, parent): 54 """Recursively append all suites and testcases to the parent tag.""" 55 for (suite_name, suite) in self.test_suites.iteritems(): 56 child = doc.createElement('TestSuite') 57 child.setAttribute('name', suite_name) 58 parent.appendChild(child) 59 # recurse into child suites 60 suite.WriteDescription(doc, child) 61 for (case_name, test_list) in self.test_cases.iteritems(): 62 child = doc.createElement('TestCase') 63 child.setAttribute('name', case_name) 64 parent.appendChild(child) 65 for test_name in test_list: 66 test = doc.createElement('Test') 67 test.setAttribute('name', test_name) 68 child.appendChild(test) 69 70 def __init__(self, package_name, app_package_name=''): 71 self.encoding = 'UTF-8' 72 self.attributes = {'name': package_name, 'AndroidFramework': 'Android 1.0', 73 'version': '1.0', 'targetNameSpace': '', 'targetBinaryName': '', 74 'jarPath': '', 'appPackageName': app_package_name} 75 self.root_suite = self.TestSuite(is_root=True) 76 77 def AddTest(self, name): 78 """Add a test to the package. 79 80 Test names are given in the form "testSuiteName.testSuiteName.TestCaseName.testName". 81 Test suites can be nested to any depth. 82 83 Args: 84 name: The name of the test to add. 85 """ 86 parts = name.split('.') 87 self.root_suite.Add(parts) 88 89 def AddAttribute(self, name, value): 90 """Add an attribute to the test package itself.""" 91 self.attributes[name] = value 92 93 def GetDocument(self): 94 """Returns a minidom Document representing the test package structure.""" 95 doc = minidom.Document() 96 package = doc.createElement('TestPackage') 97 for (attr, value) in self.attributes.iteritems(): 98 package.setAttribute(attr, value) 99 self.root_suite.WriteDescription(doc, package) 100 doc.appendChild(package) 101 return doc 102 103 def WriteDescription(self, writer): 104 """Write the description as XML to the given writer.""" 105 doc = self.GetDocument() 106 doc.writexml(writer, addindent=' ', newl='\n', encoding=self.encoding) 107 doc.unlink() 108 109 110class TestPlan(object): 111 """A CTS test plan generator.""" 112 113 def __init__(self, all_packages): 114 """Instantiate a test plan with a list of available package names. 115 116 Args: 117 all_packages: The full list of available packages. Subsequent calls to Exclude() and 118 Include() select from the packages given here. 119 """ 120 self.all_packages = all_packages 121 self.map = None 122 123 self.includedTestsMap = {} 124 self.excludedTestsMap = {} 125 126 127 def IncludeTests(self, package, test_list): 128 """Include only specific tests in this plan. 129 130 package The package that contains the tests. e.g. android.mypackage 131 This package should must be included via Include. 132 test_list A list of tests with methods to be included. e.g. 133 ['TestClass#testA', 'TestClass#testB'] 134 """ 135 packaged_test_list = [] 136 for test in test_list: 137 packaged_test_list.append(test) 138 139 if package in self.includedTestsMap: 140 self.includedTestsMap[package] += packaged_test_list 141 else: 142 self.includedTestsMap[package] = packaged_test_list 143 144 145 def ExcludeTests(self, package, test_list): 146 """Exclude specific tests from this plan. 147 148 package The package that contains the tests. e.g. android.mypackage 149 This package should must be included via Include. 150 test_list A list of tests with methods to be excluded. e.g. 151 ['TestClass#testA', 'TestClass#textB'] 152 """ 153 packaged_test_list = [] 154 for test in test_list: 155 packaged_test_list.append(test) 156 if package in self.excludedTestsMap: 157 self.excludedTestsMap[package] += packaged_test_list 158 else: 159 self.excludedTestsMap[package] = packaged_test_list 160 161 162 def Exclude(self, pattern): 163 """Exclude all packages matching the given regular expression from the plan. 164 165 If this is the first call to Exclude() or Include(), all packages are selected before applying 166 the exclusion. 167 168 Args: 169 pattern: A regular expression selecting the package names to exclude. 170 """ 171 if not self.map: 172 self.Include('.*') 173 exp = re.compile(pattern) 174 for package in self.all_packages: 175 if exp.match(package): 176 self.map[package] = False 177 178 def Include(self, pattern): 179 """Include all packages matching the given regular expressions in the plan. 180 181 Args: 182 pattern: A regular expression selecting the package names to include. 183 """ 184 if not self.map: 185 self.map = {} 186 for package in self.all_packages: 187 self.map[package] = False 188 exp = re.compile(pattern) 189 for package in self.all_packages: 190 if exp.match(package): 191 self.map[package] = True 192 193 def Write(self, file_name): 194 """Write the test plan to the given file. 195 196 Requires Include() or Exclude() to be called prior to calling this method. 197 198 Args: 199 file_name: The name of the file into which the test plan should be written. 200 """ 201 doc = minidom.Document() 202 plan = doc.createElement('TestPlan') 203 plan.setAttribute('version', '1.0') 204 doc.appendChild(plan) 205 for package in self.all_packages: 206 if self.map[package]: 207 entry = doc.createElement('Entry') 208 entry.setAttribute('name', package) 209 if package in self.excludedTestsMap: 210 entry.setAttribute('exclude', ';'.join(self.excludedTestsMap[package])) 211 if package in self.includedTestsMap: 212 entry.setAttribute('include', ';'.join(self.includedTestsMap[package])) 213 plan.appendChild(entry) 214 stream = open(file_name, 'w') 215 doc.writexml(stream, addindent=' ', newl='\n', encoding='UTF-8') 216 stream.close() 217 218 219class XmlFile(object): 220 """This class parses Xml files and allows reading attribute values by tag and attribute name.""" 221 222 def __init__(self, path): 223 """Instantiate the class using the manifest file denoted by path.""" 224 self.doc = minidom.parse(path) 225 226 def GetAndroidAttr(self, tag, attr_name): 227 """Get the value of the given attribute in the first matching tag. 228 229 Args: 230 tag: The name of the tag to search. 231 attr_name: An attribute name in the android manifest namespace. 232 233 Returns: 234 The value of the given attribute in the first matching tag. 235 """ 236 element = self.doc.getElementsByTagName(tag)[0] 237 return element.getAttributeNS('http://schemas.android.com/apk/res/android', attr_name) 238 239 def GetAttr(self, tag, attr_name): 240 """Return the value of the given attribute in the first matching tag. 241 242 Args: 243 tag: The name of the tag to search. 244 attr_name: An attribute name in the default namespace. 245 246 Returns: 247 The value of the given attribute in the first matching tag. 248 """ 249 element = self.doc.getElementsByTagName(tag)[0] 250 return element.getAttribute(attr_name) 251