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 def Exclude(self, pattern): 124 """Exclude all packages matching the given regular expression from the plan. 125 126 If this is the first call to Exclude() or Include(), all packages are selected before applying 127 the exclusion. 128 129 Args: 130 pattern: A regular expression selecting the package names to exclude. 131 """ 132 if not self.map: 133 self.Include('.*') 134 exp = re.compile(pattern) 135 for package in self.all_packages: 136 if exp.match(package): 137 self.map[package] = False 138 139 def Include(self, pattern): 140 """Include all packages matching the given regular expressions in the plan. 141 142 Args: 143 pattern: A regular expression selecting the package names to include. 144 """ 145 if not self.map: 146 self.map = {} 147 for package in self.all_packages: 148 self.map[package] = False 149 exp = re.compile(pattern) 150 for package in self.all_packages: 151 if exp.match(package): 152 self.map[package] = True 153 154 def Write(self, file_name): 155 """Write the test plan to the given file. 156 157 Requires Include() or Exclude() to be called prior to calling this method. 158 159 Args: 160 file_name: The name of the file into which the test plan should be written. 161 """ 162 doc = minidom.Document() 163 plan = doc.createElement('TestPlan') 164 plan.setAttribute('version', '1.0') 165 doc.appendChild(plan) 166 for package in self.all_packages: 167 if self.map[package]: 168 entry = doc.createElement('Entry') 169 entry.setAttribute('uri', package) 170 plan.appendChild(entry) 171 stream = open(file_name, 'w') 172 doc.writexml(stream, addindent=' ', newl='\n', encoding='UTF-8') 173 stream.close() 174 175 176class XmlFile(object): 177 """This class parses Xml files and allows reading attribute values by tag and attribute name.""" 178 179 def __init__(self, path): 180 """Instantiate the class using the manifest file denoted by path.""" 181 self.doc = minidom.parse(path) 182 183 def GetAndroidAttr(self, tag, attr_name): 184 """Get the value of the given attribute in the first matching tag. 185 186 Args: 187 tag: The name of the tag to search. 188 attr_name: An attribute name in the android manifest namespace. 189 190 Returns: 191 The value of the given attribute in the first matching tag. 192 """ 193 element = self.doc.getElementsByTagName(tag)[0] 194 return element.getAttributeNS('http://schemas.android.com/apk/res/android', attr_name) 195 196 def GetAttr(self, tag, attr_name): 197 """Return the value of the given attribute in the first matching tag. 198 199 Args: 200 tag: The name of the tag to search. 201 attr_name: An attribute name in the default namespace. 202 203 Returns: 204 The value of the given attribute in the first matching tag. 205 """ 206 element = self.doc.getElementsByTagName(tag)[0] 207 return element.getAttribute(attr_name) 208