1#!/usr/bin/env python
2# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import glob
7import os
8import unittest
9
10from idl_lexer import IDLLexer
11from idl_parser import IDLParser, ParseFile
12
13
14def ParseCommentTest(comment):
15  comment = comment.strip()
16  comments = comment.split(None, 1)
17  return comments[0], comments[1]
18
19
20class WebIDLParser(unittest.TestCase):
21
22  def setUp(self):
23    self.parser = IDLParser(IDLLexer(), mute_error=True)
24    test_dir = os.path.abspath(
25        os.path.join(os.path.dirname(__file__), 'test_parser'))
26    self.filenames = glob.glob('%s/*_web.idl' % test_dir)
27
28  def _TestNode(self, node, filepath):
29    comments = node.GetListOf('SpecialComment')
30    for comment in comments:
31      check, value = ParseCommentTest(comment.GetName())
32      if check == 'ERROR':
33        msg = node.GetLogLine('Expecting\n\t%s\nbut found \n\t%s\n' % (
34            value, str(node)))
35        self.assertEqual(value, node.GetName() ,msg)
36
37      if check == 'TREE':
38        quick = '\n'.join(node.Tree())
39        lineno = node.GetProperty('LINENO')
40        msg = 'Mismatched tree at line %d in %s:' % (lineno, filepath)
41        msg += '\n\n[EXPECTED]\n%s\n\n[ACTUAL]\n%s\n' % (value, quick)
42        self.assertEqual(value, quick, msg)
43
44  def testExpectedNodes(self):
45    for filename in self.filenames:
46      filenode = ParseFile(self.parser, filename)
47      children = filenode.GetChildren()
48      self.assertTrue(len(children) > 2, 'Expecting children in %s.' %
49          filename)
50
51      for node in filenode.GetChildren():
52        self._TestNode(node, filename)
53
54
55class TestIncludes(unittest.TestCase):
56
57  def setUp(self):
58    self.parser = IDLParser(IDLLexer(), mute_error=True)
59
60  def _ParseIncludes(self, idl_text):
61    filenode = self.parser.ParseText(filename='', data=idl_text)
62    self.assertEqual(1, len(filenode.GetChildren()))
63    return filenode.GetChildren()[0]
64
65  def testAIncludesB(self):
66    idl_text = 'A includes B;'
67    includes_node = self._ParseIncludes(idl_text)
68    self.assertEqual('Includes(A)', str(includes_node))
69    reference_node = includes_node.GetProperty('REFERENCE')
70    self.assertEqual('B', str(reference_node))
71
72  def testBIncludesC(self):
73    idl_text = 'B includes C;'
74    includes_node = self._ParseIncludes(idl_text)
75    self.assertEqual('Includes(B)', str(includes_node))
76    reference_node = includes_node.GetProperty('REFERENCE')
77    self.assertEqual('C', str(reference_node))
78
79  def testUnexpectedSemicolon(self):
80    idl_text = 'A includes;'
81    node = self._ParseIncludes(idl_text)
82    self.assertEqual('Error', node.GetClass())
83    error_message = node.GetName()
84    self.assertEqual('Unexpected ";" after keyword "includes".',
85        error_message)
86
87  def testUnexpectedIncludes(self):
88    idl_text = 'includes C;'
89    node = self._ParseIncludes(idl_text)
90    self.assertEqual('Error', node.GetClass())
91    error_message = node.GetName()
92    self.assertEqual('Unexpected includes.',
93        error_message)
94
95  def testUnexpectedIncludesAfterBracket(self):
96    idl_text = '[foo] includes B;'
97    node = self._ParseIncludes(idl_text)
98    self.assertEqual('Error', node.GetClass())
99    error_message = node.GetName()
100    self.assertEqual('Unexpected keyword "includes" after "]".',
101        error_message)
102
103
104class TestEnums(unittest.TestCase):
105
106  def setUp(self):
107    self.parser = IDLParser(IDLLexer(), mute_error=True)
108
109  def _ParseEnums(self, idl_text):
110    filenode = self.parser.ParseText(filename='', data=idl_text)
111    self.assertEqual(1, len(filenode.GetChildren()))
112    return filenode.GetChildren()[0]
113
114  def testBasic(self):
115    idl_text = 'enum MealType {"rice","noodles","other"};'
116    node = self._ParseEnums(idl_text)
117    children = node.GetChildren()
118    self.assertEqual('Enum', node.GetClass())
119    self.assertEqual(3, len(children))
120    self.assertEqual('EnumItem', children[0].GetClass())
121    self.assertEqual('rice', children[0].GetName())
122    self.assertEqual('EnumItem', children[1].GetClass())
123    self.assertEqual('noodles', children[1].GetName())
124    self.assertEqual('EnumItem', children[2].GetClass())
125    self.assertEqual('other', children[2].GetName())
126
127  def testErrorMissingName(self):
128    idl_text = 'enum {"rice","noodles","other"};'
129    node = self._ParseEnums(idl_text)
130    self.assertEqual('Error', node.GetClass())
131    error_message = node.GetName()
132    self.assertEqual('Enum missing name.', error_message)
133
134  def testTrailingCommaIsAllowed(self):
135    idl_text = 'enum TrailingComma {"rice","noodles","other",};'
136    node = self._ParseEnums(idl_text)
137    children = node.GetChildren()
138    self.assertEqual('Enum', node.GetClass())
139    self.assertEqual(3, len(children))
140    self.assertEqual('EnumItem', children[0].GetClass())
141    self.assertEqual('rice', children[0].GetName())
142    self.assertEqual('EnumItem', children[1].GetClass())
143    self.assertEqual('noodles', children[1].GetName())
144    self.assertEqual('EnumItem', children[2].GetClass())
145    self.assertEqual('other', children[2].GetName())
146
147  def testErrorMissingCommaBetweenIdentifiers(self):
148    idl_text = 'enum MissingComma {"rice" "noodles","other"};'
149    node = self._ParseEnums(idl_text)
150    self.assertEqual('Error', node.GetClass())
151    error_message = node.GetName()
152    self.assertEqual('Unexpected string "noodles" after string "rice".',
153        error_message)
154
155  def testErrorExtraCommaBetweenIdentifiers(self):
156    idl_text = 'enum ExtraComma {"rice","noodles",,"other"};'
157    node = self._ParseEnums(idl_text)
158    self.assertEqual('Error', node.GetClass())
159    error_message = node.GetName()
160    self.assertEqual('Unexpected "," after ",".', error_message)
161
162  def testErrorUnexpectedKeyword(self):
163    idl_text = 'enum TestEnum {interface,"noodles","other"};'
164    node = self._ParseEnums(idl_text)
165    self.assertEqual('Error', node.GetClass())
166    error_message = node.GetName()
167    self.assertEqual('Unexpected keyword "interface" after "{".',
168        error_message)
169
170  def testErrorUnexpectedIdentifier(self):
171    idl_text = 'enum TestEnum {somename,"noodles","other"};'
172    node = self._ParseEnums(idl_text)
173    self.assertEqual('Error', node.GetClass())
174    error_message = node.GetName()
175    self.assertEqual('Unexpected identifier "somename" after "{".',
176        error_message)
177
178class TestExtendedAttribute(unittest.TestCase):
179  def setUp(self):
180    self.parser = IDLParser(IDLLexer(), mute_error=True)
181
182  def _ParseIdlWithExtendedAttributes(self, extended_attribute_text):
183    idl_text = extended_attribute_text + ' enum MealType {"rice"};'
184    filenode = self.parser.ParseText(filename='', data=idl_text)
185    self.assertEqual(1, len(filenode.GetChildren()))
186    node = filenode.GetChildren()[0]
187    self.assertEqual('Enum', node.GetClass())
188    children = node.GetChildren()
189    self.assertEqual(2, len(children))
190    self.assertEqual('EnumItem', children[0].GetClass())
191    self.assertEqual('rice', children[0].GetName())
192    return children[1]
193
194  def testNoArguments(self):
195    extended_attribute_text = '[Replacable]'
196    attributes = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
197    self.assertEqual('ExtAttributes', attributes.GetClass())
198    self.assertEqual(1, len(attributes.GetChildren()) )
199    attribute = attributes.GetChildren()[0]
200    self.assertEqual('ExtAttribute', attribute.GetClass())
201    self.assertEqual('Replacable', attribute.GetName())
202
203  def testArgumentList(self):
204    extended_attribute_text = '[Constructor(double x, double y)]'
205    attributes = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
206    self.assertEqual('ExtAttributes', attributes.GetClass())
207    self.assertEqual(1, len(attributes.GetChildren()))
208    attribute = attributes.GetChildren()[0]
209    self.assertEqual('ExtAttribute', attribute.GetClass())
210    self.assertEqual('Constructor', attribute.GetName())
211    self.assertEqual('Arguments', attribute.GetChildren()[0].GetClass())
212    arguments = attributes.GetChildren()[0].GetChildren()[0]
213    self.assertEqual(2, len(arguments.GetChildren()))
214    self.assertEqual('Argument', arguments.GetChildren()[0].GetClass())
215    self.assertEqual('x', arguments.GetChildren()[0].GetName())
216    self.assertEqual('Argument', arguments.GetChildren()[1].GetClass())
217    self.assertEqual('y', arguments.GetChildren()[1].GetName())
218
219  def testNamedArgumentList(self):
220    extended_attribute_text = '[NamedConstructor=Image(DOMString src)]'
221    attributes = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
222    self.assertEqual('ExtAttributes', attributes.GetClass())
223    self.assertEqual(1, len(attributes.GetChildren()))
224    attribute = attributes.GetChildren()[0]
225    self.assertEqual('ExtAttribute', attribute.GetClass())
226    self.assertEqual('NamedConstructor',attribute.GetName())
227    self.assertEqual(1, len(attribute.GetChildren()))
228    self.assertEqual('Call', attribute.GetChildren()[0].GetClass())
229    self.assertEqual('Image', attribute.GetChildren()[0].GetName())
230    arguments = attribute.GetChildren()[0].GetChildren()[0]
231    self.assertEqual('Arguments', arguments.GetClass())
232    self.assertEqual(1, len(arguments.GetChildren()))
233    self.assertEqual('Argument', arguments.GetChildren()[0].GetClass())
234    self.assertEqual('src', arguments.GetChildren()[0].GetName())
235    argument = arguments.GetChildren()[0]
236    self.assertEqual(1, len(argument.GetChildren()))
237    self.assertEqual('Type', argument.GetChildren()[0].GetClass())
238    arg = argument.GetChildren()[0]
239    self.assertEqual(1, len(arg.GetChildren()))
240    argType = arg.GetChildren()[0]
241    self.assertEqual('StringType', argType.GetClass())
242    self.assertEqual('DOMString', argType.GetName())
243
244  def testIdentifier(self):
245    extended_attribute_text = '[PutForwards=name]'
246    attributes = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
247    self.assertEqual('ExtAttributes', attributes.GetClass())
248    self.assertEqual(1, len(attributes.GetChildren()))
249    attribute = attributes.GetChildren()[0]
250    self.assertEqual('ExtAttribute', attribute.GetClass())
251    self.assertEqual('PutForwards', attribute.GetName())
252    identifier = attribute.GetProperty('VALUE')
253    self.assertEqual('name', identifier)
254
255  def testIdentifierList(self):
256    extended_attribute_text = '[Exposed=(Window,Worker)]'
257    attributes = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
258    self.assertEqual('ExtAttributes', attributes.GetClass())
259    self.assertEqual(1, len(attributes.GetChildren()))
260    attribute = attributes.GetChildren()[0]
261    self.assertEqual('ExtAttribute', attribute.GetClass())
262    self.assertEqual('Exposed', attribute.GetName())
263    identifierList = attribute.GetProperty('VALUE')
264    self.assertEqual(2, len(identifierList))
265    self.assertEqual('Window', identifierList[0])
266    self.assertEqual('Worker', identifierList[1])
267
268  def testCombinationOfExtendedAttributes(self):
269    extended_attribute_text = '[Replacable, Exposed=(Window,Worker)]'
270    attributes = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
271    self.assertEqual('ExtAttributes', attributes.GetClass())
272    self.assertEqual(2, len(attributes.GetChildren()))
273    attribute0 = attributes.GetChildren()[0]
274    self.assertEqual('ExtAttribute', attribute0.GetClass())
275    self.assertEqual('Replacable', attribute0.GetName())
276    attribute1 = attributes.GetChildren()[1]
277    self.assertEqual('ExtAttribute', attribute1.GetClass())
278    self.assertEqual('Exposed', attribute1.GetName())
279    identifierList = attribute1.GetProperty('VALUE')
280    self.assertEqual(2, len(identifierList))
281    self.assertEqual('Window', identifierList[0])
282    self.assertEqual('Worker', identifierList[1])
283
284  def testErrorTrailingComma(self):
285    extended_attribute_text = '[Replacable, Exposed=(Window,Worker),]'
286    error = self._ParseIdlWithExtendedAttributes(extended_attribute_text)
287    self.assertEqual('Error', error.GetClass())
288    error_message = error.GetName()
289    self.assertEqual('Unexpected "]" after ",".', error_message)
290    self.assertEqual('ExtendedAttributeList', error.GetProperty('PROD'))
291
292  def testErrorMultipleExtendedAttributes(self):
293    extended_attribute_text = '[Attribute1][Attribute2]'
294    idl_text = extended_attribute_text + ' enum MealType {"rice"};'
295    filenode = self.parser.ParseText(filename='', data=idl_text)
296    self.assertEqual(1, len(filenode.GetChildren()))
297    node = filenode.GetChildren()[0]
298    self.assertEqual('Error', node.GetClass())
299    self.assertEqual('Unexpected "[" after "]".', node.GetName())
300    self.assertEqual('Definition', node.GetProperty('PROD'))
301    children = node.GetChildren()
302    self.assertEqual(1, len(children))
303    attributes = children[0]
304    self.assertEqual('ExtAttributes', attributes.GetClass())
305    self.assertEqual(1, len(attributes.GetChildren()))
306    attribute = attributes.GetChildren()[0]
307    self.assertEqual('ExtAttribute', attribute.GetClass())
308    self.assertEqual('Attribute1', attribute.GetName())
309
310
311class TestDefaultValue(unittest.TestCase):
312  def setUp(self):
313    self.parser = IDLParser(IDLLexer(), mute_error=True)
314
315  def _ParseDefaultValue(self, default_value_text):
316    idl_text = 'interface I { void hello(' + default_value_text + '); };'
317    filenode = self.parser.ParseText(filename='', data=idl_text)
318    self.assertEqual(1, len(filenode.GetChildren()))
319    node = filenode.GetChildren()[0]
320    self.assertEqual('Interface', node.GetClass())
321    self.assertEqual('I', node.GetName())
322    children = node.GetChildren()
323    self.assertEqual(1, len(children))
324    operation = children[0]
325    self.assertEqual('Operation', operation.GetClass())
326    self.assertEqual('hello', operation.GetName())
327    self.assertEqual(2, len(operation.GetChildren()))
328    arguments = operation.GetChildren()[0]
329    self.assertEqual('Arguments', arguments.GetClass())
330    self.assertEqual(1, len(arguments.GetChildren()))
331    argument = arguments.GetChildren()[0]
332    return_type = operation.GetChildren()[1]
333    self._CheckTypeNode(return_type, 'PrimitiveType', 'void')
334    return argument
335
336  def _CheckTypeNode(self, type_node, expected_class, expected_name):
337    self.assertEqual('Type', type_node.GetClass())
338    self.assertEqual(1, len(type_node.GetChildren()))
339    type_detail = type_node.GetChildren()[0]
340    class_name = type_detail.GetClass()
341    name = type_detail.GetName()
342    self.assertEqual(expected_class, class_name)
343    self.assertEqual(expected_name, name)
344
345  def _CheckArgumentNode(self, argument, expected_class, expected_name):
346    class_name = argument.GetClass()
347    name = argument.GetName()
348    self.assertEqual(expected_class, class_name)
349    self.assertEqual(expected_name, name)
350
351  def _CheckDefaultValue(self, default_value, expected_type, expected_value):
352    self.assertEqual('Default', default_value.GetClass())
353    self.assertEqual(expected_type, default_value.GetProperty('TYPE'))
354    self.assertEqual(expected_value, default_value.GetProperty('VALUE'))
355
356  def testDefaultValueDOMString(self):
357    default_value_text = 'optional DOMString arg = "foo"'
358    argument = self._ParseDefaultValue(default_value_text)
359    self._CheckArgumentNode(argument, 'Argument', 'arg')
360    argument_type = argument.GetChildren()[0]
361    self._CheckTypeNode(argument_type, 'StringType', 'DOMString')
362    default_value = argument.GetChildren()[1]
363    self._CheckDefaultValue(default_value, 'DOMString', 'foo')
364
365  def testDefaultValueInteger(self):
366    default_value_text = 'optional long arg = 10'
367    argument = self._ParseDefaultValue(default_value_text)
368    self._CheckArgumentNode(argument, 'Argument', 'arg')
369    argument_type = argument.GetChildren()[0]
370    self._CheckTypeNode(argument_type, 'PrimitiveType', 'long')
371    default_value = argument.GetChildren()[1]
372    self._CheckDefaultValue(default_value, 'integer', '10')
373
374  def testDefaultValueFloat(self):
375    default_value_text = 'optional float arg = 1.5'
376    argument = self._ParseDefaultValue(default_value_text)
377    self._CheckArgumentNode(argument, 'Argument', 'arg')
378    argument_type = argument.GetChildren()[0]
379    self._CheckTypeNode(argument_type, 'PrimitiveType', 'float')
380    default_value = argument.GetChildren()[1]
381    self._CheckDefaultValue(default_value, 'float', '1.5')
382
383  def testDefaultValueBoolean(self):
384    default_value_text = 'optional boolean arg = true'
385    argument = self._ParseDefaultValue(default_value_text)
386    self._CheckArgumentNode(argument, 'Argument', 'arg')
387    argument_type = argument.GetChildren()[0]
388    self._CheckTypeNode(argument_type, 'PrimitiveType', 'boolean')
389    default_value = argument.GetChildren()[1]
390    self._CheckDefaultValue(default_value, 'boolean', True)
391
392  def testDefaultValueNull(self):
393    # Node is a nullable type
394    default_value_text = 'optional Node arg = null'
395    argument = self._ParseDefaultValue(default_value_text)
396    self._CheckArgumentNode(argument, 'Argument', 'arg')
397    argument_type = argument.GetChildren()[0]
398    self._CheckTypeNode(argument_type, 'Typeref', 'Node')
399    default_value = argument.GetChildren()[1]
400    self._CheckDefaultValue(default_value, 'NULL', 'NULL')
401
402if __name__ == '__main__':
403  unittest.main(verbosity=2)
404