365b14cdba2e5722de14202b9c31a5ff9fed08a4
[tools/itest-core.git] / itest / loader.py
1 import os
2 import logging
3
4 try:
5     import unittest2 as unittest
6 except ImportError:
7     import unittest
8 from jinja2 import Environment, FileSystemLoader
9
10 from itest import xmlparser
11 from itest.conf import settings
12 from itest.case import TestCase
13
14 log = logging.getLogger(os.path.splitext(os.path.basename(__file__))[0])
15
16
17 def load_case(sel):
18     '''
19     Load tests from a single test select pattern `sel`
20     '''
21     suiteClass = unittest.TestSuite
22     def _is_test(ret):
23         return isinstance(ret, suiteClass) or \
24             isinstance(ret, TestCase)
25
26     suite = suiteClass()
27     stack = [sel]
28     while stack:
29         sel = stack.pop()
30         for pattern in suite_patterns.all():
31             if callable(pattern):
32                 pattern = pattern()
33
34             ret = pattern.load(sel)
35             if not ret:
36                 continue
37
38             if _is_test(ret):
39                 suite.addTest(ret)
40             elif isinstance(ret, list):
41                 stack.extend(ret)
42             else:
43                 stack.append(ret)
44             break
45
46     return suite
47
48
49 class TestLoader(unittest.TestLoader):
50
51     def loadTestsFromModule(self, _module, _use_load_tests=True):
52         if settings.env_root:
53             return load_case(settings.env_root)
54         return self.suiteClass()
55
56     def loadTestsFromName(self, name, module=None):
57         return load_case(name)
58
59
60 class AliasPattern(object):
61     '''dict key of settings.SUITES is alias for its value'''
62
63     def load(self, sel):
64         if sel in settings.SUITES:
65             return settings.SUITES[sel]
66
67
68 class FilePattern(object):
69     '''test from file name'''
70
71     def load(self, name):
72         if not os.path.isfile(name):
73             return
74
75         template_dirs = [os.path.abspath(os.path.dirname(name))]
76         if settings.cases_dir:
77             template_dirs.append(settings.cases_dir)
78         jinja2_env = Environment(loader=FileSystemLoader(template_dirs))
79         template = jinja2_env.get_template(os.path.basename(name))
80         text = template.render()
81
82         if isinstance(text, unicode):
83             text = text.encode('utf8')
84             # template returns unicode
85             # but xml parser only accepts str
86             # And we can only assume it's utf8 here
87
88         data = xmlparser.Parser().parse(text)
89         if not data:
90             raise Exception("Can't load test case from %s" % name)
91         return TestCase(os.path.abspath(name), data)
92
93
94 class DirPattern(object):
95     '''find all tests recursively in a dir'''
96
97     def load(self, top):
98         if os.path.isdir(top):
99             return list(self._walk(top))
100
101     def _walk(self, top):
102         for current, _dirs, nondirs in os.walk(top):
103             for name in nondirs:
104                 if name.endswith('.case'):
105                     yield os.path.join(current, name)
106
107
108 class ComponentPattern(object):
109     '''tests from a component name'''
110
111     _components = None
112
113     @staticmethod
114     def guess_components():
115         if not settings.env_root:
116             return ()
117         comp = []
118         for base in os.listdir(settings.cases_dir):
119             full = os.path.join(settings.cases_dir, base)
120             if os.path.isdir(full):
121                 comp.append(base)
122         return set(comp)
123
124     @classmethod
125     def is_component(cls, comp):
126         if cls._components is None:
127             cls._components = cls.guess_components()
128         return comp in cls._components
129
130     def load(self, comp):
131         if self.is_component(comp):
132             return os.path.join(settings.cases_dir, comp)
133
134
135 class InversePattern(object):
136     '''string starts with "!" is the inverse of string[1:]'''
137
138     def load(self, sel):
139         if sel.startswith('!'):
140             comp = sel[1:]
141             comps = ComponentPattern.guess_components()
142             if ComponentPattern.is_component(comp):
143                 return [c for c in comps if c != comp]
144             # if the keyword isn't a component name, then it is useless
145             return list(comps)
146
147
148 class IntersectionPattern(object):
149     '''use && load intersection set of many parts'''
150
151     loader_class = TestLoader
152
153     def load(self, sel):
154         if sel.find('&&') <= 0:
155             return
156
157         def intersection(many):
158             inter = None
159             for each in many:
160                 if inter is None:
161                     inter = set(each)
162                 else:
163                     inter.intersection_update(each)
164             return inter
165
166         loader = self.loader_class()
167         many = [load_case(part) for part in sel.split('&&')]
168
169         return loader.suiteClass(intersection(many))
170
171
172 class _SuitePatternRegister(object):
173
174     def __init__(self):
175         self._patterns = []
176
177     def register(self, cls):
178         self._patterns.append(cls)
179
180     def all(self):
181         return self._patterns
182
183
184 def register_default_patterns():
185     for pattern in (AliasPattern,
186                     FilePattern,
187                     DirPattern,
188                     IntersectionPattern,
189                     ComponentPattern,
190                     InversePattern,
191                     ):
192         suite_patterns.register(pattern)
193
194 suite_patterns = _SuitePatternRegister()
195 register_default_patterns()