Home | History | Annotate | Download | only in cts
      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 
     19 import re
     20 import xml.dom.minidom as minidom
     21 
     22 
     23 class 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 
    110 class 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 
    219 class 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