~ubuntu-branches/ubuntu/lucid/mago/lucid

« back to all changes in this revision

Viewing changes to mago/cmd/discovery.py

  • Committer: Bazaar Package Importer
  • Author(s): Ara Pulido
  • Date: 2009-08-04 09:21:40 UTC
  • Revision ID: james.westby@ubuntu.com-20090804092140-mnc0rm2tr7smo107
Tags: upstream-0.1
ImportĀ upstreamĀ versionĀ 0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
This module contains the functionality related to the discovery of the test suites
 
3
"""
 
4
import os, re, logging
 
5
import xml.etree.ElementTree as etree
 
6
import imp
 
7
 
 
8
 
 
9
class ApplicationData:
 
10
    """
 
11
    Application description data
 
12
    """
 
13
    name_pattern = r"[a-z0-9][-_a-z0-9+.]*"
 
14
    name_regex = re.compile(name_pattern)
 
15
    whitelist = None
 
16
 
 
17
 
 
18
    def __init__(self, path, filenames):
 
19
        self.path = path
 
20
        self.filenames = filenames
 
21
 
 
22
        self.name = os.path.basename(path)
 
23
 
 
24
 
 
25
    def __eq__(self, other):
 
26
        """
 
27
        Two applications are considered to be equal if they have the
 
28
        same name
 
29
        """
 
30
        return (type(self) == type(other)
 
31
                and self.name == other.name)
 
32
 
 
33
 
 
34
    def name_matches(self):
 
35
        """
 
36
        Return True if the application name
 
37
        honors the expected pattern
 
38
        """
 
39
        return self.name_regex.match(self.name)
 
40
 
 
41
 
 
42
    def suites(self):
 
43
        """
 
44
        Return a generator for all suites
 
45
        """
 
46
        return SuiteData.discover(self)
 
47
 
 
48
 
 
49
    def get_target_directory(self, base_target_directory):
 
50
        """
 
51
        Return application target_directory
 
52
        """
 
53
        return os.path.join(base_target_directory,
 
54
                            self.name)
 
55
 
 
56
    @classmethod
 
57
    def discover(cls, base_dirpaths):
 
58
        """
 
59
        Generator that discovers all applications under
 
60
        a list of top directories
 
61
        """
 
62
        discovered_applications = []
 
63
        for base_dirpath in base_dirpaths:
 
64
            dirpaths = [os.path.join(base_dirpath, d)
 
65
                        for d in os.listdir(base_dirpath)
 
66
                        if os.path.isdir(os.path.join(base_dirpath, d))]
 
67
 
 
68
            for dirpath in dirpaths:
 
69
                try:
 
70
                    filenames = [f
 
71
                                 for f in os.listdir(dirpath)
 
72
                                 if os.path.isfile(os.path.join(dirpath, f))]
 
73
                except OSError:
 
74
                    continue # Permission denied.
 
75
 
 
76
                # Application directories are expected to honor
 
77
                # the specified name pattern
 
78
                app = cls(dirpath, filenames)
 
79
                if not app.name_matches():
 
80
                    logging.debug("Application name %s does not match pattern: %s"
 
81
                                  % (app.name, app.name_pattern))
 
82
                    continue
 
83
 
 
84
                # This check makes sure that the same application
 
85
                # isn't discovered twice. That is to say, when discovering
 
86
                # applications from multiple directories, the test cases 
 
87
                # from the application that is first found will be
 
88
                # executed while the others will be discarded
 
89
                if app in discovered_applications:
 
90
                    logging.debug("Application name %s has been already discovered"
 
91
                                  % app.name)
 
92
                    continue
 
93
 
 
94
                # Return application only if there is no whitelist
 
95
                # or if it matches any of the whitelist names
 
96
                if cls.whitelist and not app.name in cls.whitelist:
 
97
                    logging.debug("Application name %s has not been whitelisted"
 
98
                                  % app.name)
 
99
                    continue
 
100
 
 
101
                # At least one '.xml' file with a 'suite' root tag
 
102
                # should be contained in the directory to be a valid application directory
 
103
                if not any(app.suites()):
 
104
                    logging.debug("Application directory %s does't seem to contain a valid suite file"
 
105
                                  % app.path)
 
106
                    continue
 
107
 
 
108
                discovered_applications.append(app)
 
109
                yield app
 
110
 
 
111
 
 
112
class XmlData:
 
113
    """
 
114
    Common methods for XML test data
 
115
    """
 
116
    @property
 
117
    def name(self):
 
118
        """
 
119
        Return suite name as written in the xml file
 
120
        """
 
121
        return self.root.attrib['name']
 
122
 
 
123
 
 
124
    @property
 
125
    def args(self):
 
126
        """
 
127
        Return suite arguments as a dictionary
 
128
        """
 
129
        args_tag = self.root.find('args')
 
130
        
 
131
        if not args_tag:
 
132
            return {}
 
133
 
 
134
        return dict([(arg.tag.encode('ascii'), (arg.text or '').strip())
 
135
                     for arg in args_tag])
 
136
 
 
137
        
 
138
    @property
 
139
    def description(self):
 
140
        """
 
141
        Return suite description as written in the xml file
 
142
        """
 
143
        return self.root.find('description').text.strip()
 
144
 
 
145
 
 
146
    def add_results(self, results):
 
147
        """
 
148
        Add results to the xml data of the test case
 
149
        to generate later report easily
 
150
        """
 
151
        if not results:
 
152
            return
 
153
 
 
154
        result_tag = etree.SubElement(self.root, 'result')
 
155
        for key, values in results.items():
 
156
            for value in values:
 
157
                new_result_tag = etree.SubElement(result_tag, key)
 
158
                new_result_tag.text = str(value)
 
159
 
 
160
 
 
161
class SuiteData(XmlData):
 
162
    """
 
163
    Suite description data
 
164
    """
 
165
    name_whitelist = None
 
166
    filename_whitelist = None
 
167
 
 
168
 
 
169
    def __init__(self, application, filename):
 
170
        self.application = application
 
171
        self.filename = filename
 
172
        self.fullname = os.path.join(application.path,
 
173
                                     filename)
 
174
 
 
175
        try:
 
176
            self.tree = etree.parse(self.fullname)
 
177
            self.root = self.tree.getroot()
 
178
        except:
 
179
            self.tree = None
 
180
            self.root = None
 
181
 
 
182
 
 
183
    def get_class(self):
 
184
        """
 
185
        Return suite instance from the python module
 
186
        """
 
187
        module_name, class_name = self.root.find('class').text.rsplit('.', 1)
 
188
        logging.debug("Module name: %s", module_name)
 
189
 
 
190
        # Suite file and module are expected to be in the same directory
 
191
        load_args = imp.find_module(
 
192
            module_name, [os.path.dirname(self.fullname)])
 
193
 
 
194
        module = imp.load_module(module_name, *load_args)
 
195
 
 
196
        cls = getattr(module, class_name)
 
197
        return cls(**self.args)
 
198
 
 
199
    
 
200
    def get_log_filename(self, application_target_directory):
 
201
        """
 
202
        Return log filename under application target directory
 
203
        """
 
204
        return os.path.join(
 
205
            application_target_directory, 
 
206
            "%s.log" % os.path.basename(os.path.splitext(self.filename)[0]))
 
207
 
 
208
    def cases(self):
 
209
        """
 
210
        Generator for all test cases in the suite
 
211
        """
 
212
        return CaseData.discover(self)
 
213
 
 
214
 
 
215
    def __eq__(self, other):
 
216
        """
 
217
        A suite is compared against its filename
 
218
        or against its own name (useful for filtering)
 
219
        """
 
220
        if type(other) == str:
 
221
            other_filename, other_ext = os.path.splitext(other)
 
222
            
 
223
            if other_ext:
 
224
                return other == self.filename
 
225
            else:
 
226
                return other_filename == os.path.splitext(self.filename)[0]
 
227
        else:
 
228
            return self.filename == other.filename
 
229
 
 
230
    
 
231
    def has_valid_xml(self):
 
232
        """
 
233
        Return true if xml could be parsed
 
234
        and the root tag is 'suite'
 
235
        """
 
236
        return (self.tree
 
237
                and self.tree.getroot().tag == 'suite')
 
238
 
 
239
 
 
240
    @classmethod
 
241
    def discover(cls, app):
 
242
        """
 
243
        Discover suites inside of an application
 
244
        """
 
245
        # All test suites will be defined by an xml file
 
246
        xml_filenames = (filename
 
247
                         for filename in app.filenames
 
248
                         if filename.endswith('xml'))
 
249
 
 
250
        # Discovered suites must contain a valid xml content
 
251
        # and at least one test cases
 
252
        suites = (suite for suite in (cls(app, filename)
 
253
                                      for filename in xml_filenames)
 
254
                  if suite.has_valid_xml() and any(suite.cases()))
 
255
 
 
256
        
 
257
        # Filter suites using the whitelists provided through the
 
258
        # command line
 
259
        if cls.name_whitelist or cls.filename_whitelist:
 
260
            suites = (suite for suite in suites
 
261
                      if suite.name in cls.name_whitelist
 
262
                      or suite in cls.filename_whitelist)
 
263
 
 
264
        return suites
 
265
 
 
266
 
 
267
class CaseData(XmlData):
 
268
    """
 
269
    Test case description data
 
270
    """
 
271
    whitelist = None
 
272
 
 
273
    def __init__(self, suite, root):
 
274
        self.suite = suite
 
275
        self.root = root
 
276
 
 
277
 
 
278
    @property
 
279
    def methodname(self):
 
280
        """
 
281
        Test method according to xml data
 
282
        """
 
283
        return self.root.find('method').text
 
284
 
 
285
 
 
286
    @classmethod
 
287
    def discover(cls, suite):
 
288
        """
 
289
        Discover all test cases in a suite
 
290
        """
 
291
        cases = (cls(suite, tree)
 
292
                 for tree in suite.tree.findall('case'))
 
293
 
 
294
        if cls.whitelist:
 
295
            cases = (case for case in cases
 
296
                     if case.name in cls.whitelist)
 
297
 
 
298
        return cases
 
299
 
 
300
 
 
301
def discover_applications(top_directories,
 
302
                          filtering_applications,
 
303
                          filtering_suite_names,
 
304
                          filtering_suite_files,
 
305
                          filtering_cases):
 
306
    """
 
307
    Discover all applications and filter them properly
 
308
    """
 
309
    # Configure filtering options
 
310
    ApplicationData.whitelist = filtering_applications
 
311
    SuiteData.name_whitelist = filtering_suite_names
 
312
    SuiteData.filename_whitelist = filtering_suite_files
 
313
    CaseData.whitelist = filtering_cases
 
314
 
 
315
    # Discover all applications under top directories
 
316
    discovered_apps = ApplicationData.discover(top_directories)
 
317
 
 
318
    return discovered_apps