giscanner: add AnnotationParser tests
[platform/upstream/gobject-introspection.git] / tests / scanner / annotationparser / test_parser.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2012 Dieter Verfaillie <dieterv@optionexplicit.be>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19 #
20
21
22 '''
23 test_parser.py
24
25 Tests ensuring the "parse tree" built by annotationparser.py
26 continues to function correctly.
27 '''
28
29
30 import difflib
31 import os
32 import xml.etree.ElementTree as etree
33 import unittest
34
35 from giscanner.annotationparser import AnnotationParser
36 from giscanner.ast import Namespace
37 from giscanner.message import MessageLogger
38
39
40 def parsed2tree(docblock):
41     parsed = ''
42
43     if docblock is not None:
44         parsed += '<docblock>\n'
45
46         parsed += '  <identifier>\n'
47         # An identifier name is always required, but we can't trust our
48         # own parser to ensure this when testing so fall back to an empty
49         # string when no name has been parsed...
50         parsed += '    <name>%s</name>\n' % (docblock.name or '', )
51         if docblock.options.values:
52             parsed += '    <annotations>\n'
53             for key, value in docblock.options.values:
54                 parsed += '      <annotation>\n'
55                 parsed += '        <name>%s</name>\n' % (key, )
56                 if value is not None:
57                     options = value.all()
58                     parsed += '        <options>\n'
59                     for option in options:
60                         parsed += '          <option>\n'
61                         parsed += '            <name>%s</name>\n' % (option, )
62                         if options[option] is not None:
63                             parsed += '            <value>%s</value>\n' % (options[option], )
64                         parsed += '          </option>\n'
65                     parsed += '        </options>\n'
66                 parsed += '      </annotation>\n'
67             parsed += '    </annotations>\n'
68         parsed += '  </identifier>\n'
69
70         if docblock.params:
71             parsed += '  <parameters>\n'
72             for param_name in docblock.params:
73                 param = docblock.params.get(param_name)
74                 parsed += '    <parameter>\n'
75                 parsed += '      <name>%s</name>\n' % (param_name, )
76                 if param.options.values:
77                     parsed += '      <annotations>\n'
78                     for key, value in param.options.values:
79                         parsed += '        <annotation>\n'
80                         parsed += '          <name>%s</name>\n' % (key, )
81                         if value is not None:
82                             options = value.all()
83                             parsed += '          <options>\n'
84                             for option in options:
85                                 parsed += '            <option>\n'
86                                 parsed += '              <name>%s</name>\n' % (option, )
87                                 if options[option] is not None:
88                                     parsed += '              <value>%s</value>\n' % (options[option], )
89                                 parsed += '            </option>\n'
90                             parsed += '          </options>\n'
91                         parsed += '        </annotation>\n'
92                     parsed += '      </annotations>\n'
93                 if param.comment or param.value:
94                     parsed += '      <description>%s</description>\n' % (param.comment or param.value, )
95                 parsed += '    </parameter>\n'
96             parsed += '  </parameters>\n'
97
98         if docblock.comment or docblock.value:
99             parsed += '  <description>%s</description>\n' % (docblock.comment or docblock.value, )
100
101         if docblock.tags:
102             parsed += '  <tags>\n'
103             for tag_name in docblock.tags:
104                 tag = docblock.tags.get(tag_name)
105                 parsed += '    <tag>\n'
106                 parsed += '      <name>%s</name>\n' % (tag_name, )
107                 if tag.options.values:
108                     parsed += '      <annotations>\n'
109                     for key, value in tag.options.values:
110                         parsed += '        <annotation>\n'
111                         parsed += '          <name>%s</name>\n' %(key, )
112                         if value is not None:
113                             options = value.all()
114                             parsed += '          <options>\n'
115                             for option in options:
116                                 parsed += '            <option>\n'
117                                 parsed += '              <name>%s</name>\n' % (option, )
118                                 if options[option] is not None:
119                                     parsed += '              <value>%s</value>\n' % (options[option], )
120                                 parsed += '            </option>\n'
121                             parsed += '          </options>\n'
122                         parsed += '        </annotation>\n'
123                     parsed += '      </annotations>\n'
124                 if tag.comment or tag.value:
125                     parsed += '      <description>%s</description>\n' % (tag.comment or tag.value, )
126                 parsed += '    </tag>\n'
127             parsed += '  </tags>\n'
128
129         parsed += '<docblock>'
130
131     return parsed
132
133 def expected2tree(docblock):
134     # Note: this sucks, but we can't rely on etree.tostring() to generate useable output :(
135
136     expected = ''
137
138     if docblock is not None:
139         expected += '<docblock>\n'
140
141         if docblock.find('identifier') is not None:
142             expected += '  <identifier>\n'
143             # Expecting an identifier name is required, don't bother checking if it's there or not
144             expected += '    <name>%s</name>\n' % (docblock.find('identifier/name').text, )
145             annotations = docblock.find('identifier/annotations')
146             if annotations is not None:
147                 expected += '    <annotations>\n'
148                 for annotation in annotations.iterfind('annotation'):
149                     expected += '      <annotation>\n'
150                     expected += '        <name>%s</name>\n' % (annotation.find('name').text, )
151                     if annotation.find('options') is not None:
152                         expected += '        <options>\n'
153                         for option in annotation.iterfind('options/option'):
154                             expected += '          <option>\n'
155                             expected += '            <name>%s</name>\n' % (option.find('name').text, )
156                             if option.find('value') is not None:
157                                 expected += '            <value>%s</value>\n' % (option.find('value').text, )
158                             expected += '          </option>\n'
159                         expected += '        </options>\n'
160                     expected += '      </annotation>\n'
161                 expected += '    </annotations>\n'
162             expected += '  </identifier>\n'
163
164         parameters = docblock.find('parameters')
165         if parameters is not None:
166             expected += '  <parameters>\n'
167             for parameter in parameters.iterfind('parameter'):
168                 expected += '    <parameter>\n'
169                 expected += '      <name>%s</name>\n' % (parameter.find('name').text, )
170                 annotations = parameter.find('annotations')
171                 if annotations is not None:
172                     expected += '      <annotations>\n'
173                     for annotation in parameter.iterfind('annotations/annotation'):
174                         expected += '        <annotation>\n'
175                         expected += '          <name>%s</name>\n' % (annotation.find('name').text, )
176                         if annotation.find('options') is not None:
177                             expected += '          <options>\n'
178                             for option in annotation.iterfind('options/option'):
179                                 expected += '            <option>\n'
180                                 expected += '              <name>%s</name>\n' % (option.find('name').text, )
181                                 if option.find('value') is not None:
182                                     expected += '              <value>%s</value>\n' % (option.find('value').text, )
183                                 expected += '            </option>\n'
184                             expected += '          </options>\n'
185                         expected += '        </annotation>\n'
186                     expected += '      </annotations>\n'
187                 if parameter.find('description') is not None:
188                     expected += '      <description>%s</description>\n' % (parameter.find('description').text, )
189                 expected += '    </parameter>\n'
190             expected += '  </parameters>\n'
191
192         description = docblock.find('description')
193         if description is not None:
194             expected += '  <description>%s</description>\n' % (description.text, )
195
196         tags = docblock.find('tags')
197         if tags is not None:
198             expected += '  <tags>\n'
199             for tag in tags.iterfind('tag'):
200                 expected += '    <tag>\n'
201                 expected += '      <name>%s</name>\n' % (tag.find('name').text, )
202                 annotations = tag.find('annotations')
203                 if annotations is not None:
204                     expected += '      <annotations>\n'
205                     for annotation in tag.iterfind('annotations/annotation'):
206                         expected += '        <annotation>\n'
207                         expected += '          <name>%s</name>\n' % (annotation.find('name').text, )
208                         if annotation.find('options') is not None:
209                             expected += '          <options>\n'
210                             for option in annotation.iterfind('options/option'):
211                                 expected += '            <option>\n'
212                                 expected += '              <name>%s</name>\n' % (option.find('name').text, )
213                                 if option.find('value') is not None:
214                                     expected += '              <value>%s</value>\n' % (option.find('value').text, )
215                                 expected += '            </option>\n'
216                             expected += '          </options>\n'
217                         expected += '        </annotation>\n'
218                     expected += '      </annotations>\n'
219                 if tag.find('description') is not None:
220                     expected += '      <description>%s</description>\n' % (tag.find('description').text, )
221                 expected += '    </tag>\n'
222             expected += '  </tags>\n'
223
224         expected += '<docblock>'
225
226     return expected
227
228
229 def create_tests(tests_dir, tests_file):
230     tests_name = os.path.relpath(tests_file[:-4], tests_dir)
231     tests_name = tests_name.replace('/', '.').replace('\\', '.')
232
233     tests_tree = etree.parse(tests_file).getroot()
234
235     fix_cdata_elements = tests_tree.findall('test/commentblock')
236     fix_cdata_elements += tests_tree.findall('.//description')
237
238     for element in fix_cdata_elements:
239         if element.text:
240             element.text = element.text.replace('{{?', '<!')
241             element.text = element.text.replace('}}', '>')
242
243     for counter, test in enumerate(tests_tree.findall('test')):
244         test_name = 'test_%s.%03d' % (tests_name, counter + 1)
245         test_method = TestCommentBlock.__create_test__(test)
246         setattr(TestCommentBlock, test_name, test_method)
247
248
249 class TestCommentBlock(unittest.TestCase):
250     @classmethod
251     def __create_test__(cls, testcase):
252         def do_test(self):
253             # Parse GTK-Doc comment block
254             commentblock = testcase.find('commentblock').text
255             parsed_docblock = AnnotationParser().parse_comment_block((commentblock, 'test.c', 1))
256             parsed_tree = parsed2tree(parsed_docblock).split('\n')
257
258             # Get expected output
259             expected_docblock = testcase.find('docblock')
260             expected_tree = expected2tree(expected_docblock).split('\n')
261
262             # Construct a meaningful message
263             msg = 'Parsed DocBlock object tree does not match expected output:\n\n'
264             msg += '%s\n\n' % (commentblock, )
265
266             diff = difflib.unified_diff(expected_tree, parsed_tree,
267                                         'Expected DocBlock', 'Parsed DocBlock',
268                                         n=max(len(expected_tree), len(parsed_tree)),
269                                         lineterm='')
270             for line in diff:
271                 msg += '%s\n' % (line, )
272
273             # Compare parsed with expected DocBlock tree
274             self.assertEqual(parsed_tree, expected_tree, msg)
275
276         return do_test
277
278
279 if __name__ == '__main__':
280     # Initialize message logger
281     # TODO: at some point it might be a good idea to test warnings emitted
282     # by annotationparser here, instead of having them in tests/warn/annotationparser.h?
283     namespace = Namespace('Test', '1.0')
284     logger = MessageLogger.get(namespace=namespace)
285     logger.enable_warnings(False)
286
287     # Load test cases from disc
288     tests_dir = os.path.dirname(os.path.abspath(__file__))
289
290     for dirpath, dirnames, filenames in os.walk(tests_dir):
291         for filename in filenames:
292             tests_file = os.path.join(dirpath, filename)
293             if os.path.basename(tests_file).endswith('.xml'):
294                 create_tests(tests_dir, tests_file)
295
296     # Run test suite
297     unittest.main()