1#! /usr/bin/python
2#
3# Protocol Buffers - Google's data interchange format
4# Copyright 2015 Google Inc.  All rights reserved.
5# https://developers.google.com/protocol-buffers/
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are
9# met:
10#
11#     * Redistributions of source code must retain the above copyright
12# notice, this list of conditions and the following disclaimer.
13#     * Redistributions in binary form must reproduce the above
14# copyright notice, this list of conditions and the following disclaimer
15# in the documentation and/or other materials provided with the
16# distribution.
17#     * Neither the name of Google Inc. nor the names of its
18# contributors may be used to endorse or promote products derived from
19# this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33"""Tests for pddm.py."""
34
35import io
36import unittest
37
38import pddm
39
40
41class TestParsingMacros(unittest.TestCase):
42
43  def testParseEmpty(self):
44    f = io.StringIO(u'')
45    result = pddm.MacroCollection(f)
46    self.assertEqual(len(result._macros), 0)
47
48  def testParseOne(self):
49    f = io.StringIO(u"""PDDM-DEFINE foo( )
50body""")
51    result = pddm.MacroCollection(f)
52    self.assertEqual(len(result._macros), 1)
53    macro = result._macros.get('foo')
54    self.assertIsNotNone(macro)
55    self.assertEquals(macro.name, 'foo')
56    self.assertEquals(macro.args, tuple())
57    self.assertEquals(macro.body, 'body')
58
59  def testParseGeneral(self):
60    # Tests multiple defines, spaces in all places, etc.
61    f = io.StringIO(u"""
62PDDM-DEFINE noArgs( )
63body1
64body2
65
66PDDM-DEFINE-END
67
68PDDM-DEFINE oneArg(foo)
69body3
70PDDM-DEFINE  twoArgs( bar_ , baz )
71body4
72body5""")
73    result = pddm.MacroCollection(f)
74    self.assertEqual(len(result._macros), 3)
75    macro = result._macros.get('noArgs')
76    self.assertIsNotNone(macro)
77    self.assertEquals(macro.name, 'noArgs')
78    self.assertEquals(macro.args, tuple())
79    self.assertEquals(macro.body, 'body1\nbody2\n')
80    macro = result._macros.get('oneArg')
81    self.assertIsNotNone(macro)
82    self.assertEquals(macro.name, 'oneArg')
83    self.assertEquals(macro.args, ('foo',))
84    self.assertEquals(macro.body, 'body3')
85    macro = result._macros.get('twoArgs')
86    self.assertIsNotNone(macro)
87    self.assertEquals(macro.name, 'twoArgs')
88    self.assertEquals(macro.args, ('bar_', 'baz'))
89    self.assertEquals(macro.body, 'body4\nbody5')
90    # Add into existing collection
91    f = io.StringIO(u"""
92PDDM-DEFINE another(a,b,c)
93body1
94body2""")
95    result.ParseInput(f)
96    self.assertEqual(len(result._macros), 4)
97    macro = result._macros.get('another')
98    self.assertIsNotNone(macro)
99    self.assertEquals(macro.name, 'another')
100    self.assertEquals(macro.args, ('a', 'b', 'c'))
101    self.assertEquals(macro.body, 'body1\nbody2')
102
103  def testParseDirectiveIssues(self):
104    test_list = [
105      # Unknown directive
106      (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINED foo\nbaz',
107       'Hit a line with an unknown directive: '),
108      # End without begin
109      (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nPDDM-DEFINE-END\n',
110       'Got DEFINE-END directive without an active macro: '),
111      # Line not in macro block
112      (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nmumble\n',
113       'Hit a line that wasn\'t a directive and no open macro definition: '),
114      # Redefine macro
115      (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE foo(a)\nmumble\n',
116       'Attempt to redefine macro: '),
117    ]
118    for idx, (input_str, expected_prefix) in enumerate(test_list, 1):
119      f = io.StringIO(input_str)
120      try:
121        result = pddm.MacroCollection(f)
122        self.fail('Should throw exception, entry %d' % idx)
123      except pddm.PDDMError as e:
124        self.assertTrue(e.message.startswith(expected_prefix),
125                        'Entry %d failed: %r' % (idx, e))
126
127  def testParseBeginIssues(self):
128    test_list = [
129      # 1. No name
130      (u'PDDM-DEFINE\nmumble',
131       'Failed to parse macro definition: '),
132      # 2. No name (with spaces)
133      (u'PDDM-DEFINE  \nmumble',
134       'Failed to parse macro definition: '),
135      # 3. No open paren
136      (u'PDDM-DEFINE  foo\nmumble',
137       'Failed to parse macro definition: '),
138      # 4. No close paren
139      (u'PDDM-DEFINE foo(\nmumble',
140       'Failed to parse macro definition: '),
141      # 5. No close paren (with args)
142      (u'PDDM-DEFINE foo(a, b\nmumble',
143       'Failed to parse macro definition: '),
144      # 6. No name before args
145      (u'PDDM-DEFINE  (a, b)\nmumble',
146       'Failed to parse macro definition: '),
147      # 7. No name before args
148      (u'PDDM-DEFINE foo bar(a, b)\nmumble',
149       'Failed to parse macro definition: '),
150      # 8. Empty arg name
151      (u'PDDM-DEFINE foo(a, ,b)\nmumble',
152       'Empty arg name in macro definition: '),
153      (u'PDDM-DEFINE foo(a,,b)\nmumble',
154       'Empty arg name in macro definition: '),
155      # 10. Duplicate name
156      (u'PDDM-DEFINE foo(a,b,a,c)\nmumble',
157       'Arg name "a" used more than once in macro definition: '),
158      # 11. Invalid arg name
159      (u'PDDM-DEFINE foo(a b,c)\nmumble',
160       'Invalid arg name "a b" in macro definition: '),
161      (u'PDDM-DEFINE foo(a.b,c)\nmumble',
162       'Invalid arg name "a.b" in macro definition: '),
163      (u'PDDM-DEFINE foo(a-b,c)\nmumble',
164       'Invalid arg name "a-b" in macro definition: '),
165      (u'PDDM-DEFINE foo(a,b,c.)\nmumble',
166       'Invalid arg name "c." in macro definition: '),
167      # 15. Extra stuff after the name
168      (u'PDDM-DEFINE foo(a,c) foo\nmumble',
169       'Failed to parse macro definition: '),
170      (u'PDDM-DEFINE foo(a,c) foo)\nmumble',
171       'Failed to parse macro definition: '),
172    ]
173    for idx, (input_str, expected_prefix) in enumerate(test_list, 1):
174      f = io.StringIO(input_str)
175      try:
176        result = pddm.MacroCollection(f)
177        self.fail('Should throw exception, entry %d' % idx)
178      except pddm.PDDMError as e:
179        self.assertTrue(e.message.startswith(expected_prefix),
180                        'Entry %d failed: %r' % (idx, e))
181
182
183class TestExpandingMacros(unittest.TestCase):
184
185  def testExpandBasics(self):
186    f = io.StringIO(u"""
187PDDM-DEFINE noArgs( )
188body1
189body2
190
191PDDM-DEFINE-END
192
193PDDM-DEFINE oneArg(a)
194body3 a
195
196PDDM-DEFINE-END
197
198PDDM-DEFINE twoArgs(b,c)
199body4 b c
200body5
201PDDM-DEFINE-END
202
203""")
204    mc = pddm.MacroCollection(f)
205    test_list = [
206      (u'noArgs()',
207       'body1\nbody2\n'),
208      (u'oneArg(wee)',
209       'body3 wee\n'),
210      (u'twoArgs(having some, fun)',
211       'body4 having some fun\nbody5'),
212      # One arg, pass empty.
213      (u'oneArg()',
214       'body3 \n'),
215      # Two args, gets empty in each slot.
216      (u'twoArgs(, empty)',
217       'body4  empty\nbody5'),
218      (u'twoArgs(empty, )',
219       'body4 empty \nbody5'),
220      (u'twoArgs(, )',
221       'body4  \nbody5'),
222    ]
223    for idx, (input_str, expected) in enumerate(test_list, 1):
224      result = mc.Expand(input_str)
225      self.assertEqual(result, expected,
226                       'Entry %d --\n       Result: %r\n     Expected: %r' %
227                       (idx, result, expected))
228
229  def testExpandArgOptions(self):
230    f = io.StringIO(u"""
231PDDM-DEFINE bar(a)
232a-a$S-a$l-a$L-a$u-a$U
233PDDM-DEFINE-END
234""")
235    mc = pddm.MacroCollection(f)
236
237    self.assertEqual(mc.Expand('bar(xYz)'), 'xYz-   -xYz-xyz-XYz-XYZ')
238    self.assertEqual(mc.Expand('bar(MnoP)'), 'MnoP-    -mnoP-mnop-MnoP-MNOP')
239    # Test empty
240    self.assertEqual(mc.Expand('bar()'), '-----')
241
242  def testExpandSimpleMacroErrors(self):
243    f = io.StringIO(u"""
244PDDM-DEFINE foo(a, b)
245<a-z>
246PDDM-DEFINE baz(a)
247a - a$z
248""")
249    mc = pddm.MacroCollection(f)
250    test_list = [
251      # 1. Unknown macro
252      (u'bar()',
253       'No macro named "bar".'),
254      (u'bar(a)',
255       'No macro named "bar".'),
256      # 3. Arg mismatch
257      (u'foo()',
258       'Expected 2 args, got: "foo()".'),
259      (u'foo(a b)',
260       'Expected 2 args, got: "foo(a b)".'),
261      (u'foo(a,b,c)',
262       'Expected 2 args, got: "foo(a,b,c)".'),
263      # 6. Unknown option in expansion
264      (u'baz(mumble)',
265       'Unknown arg option "a$z" while expanding "baz(mumble)".'),
266    ]
267    for idx, (input_str, expected_err) in enumerate(test_list, 1):
268      try:
269        result = mc.Expand(input_str)
270        self.fail('Should throw exception, entry %d' % idx)
271      except pddm.PDDMError as e:
272        self.assertEqual(e.message, expected_err,
273                        'Entry %d failed: %r' % (idx, e))
274
275  def testExpandReferences(self):
276    f = io.StringIO(u"""
277PDDM-DEFINE StartIt()
278foo(abc, def)
279foo(ghi, jkl)
280PDDM-DEFINE foo(a, b)
281bar(a, int)
282bar(b, NSString *)
283PDDM-DEFINE bar(n, t)
284- (t)n;
285- (void)set##n$u##:(t)value;
286
287""")
288    mc = pddm.MacroCollection(f)
289    expected = """- (int)abc;
290- (void)setAbc:(int)value;
291
292- (NSString *)def;
293- (void)setDef:(NSString *)value;
294
295- (int)ghi;
296- (void)setGhi:(int)value;
297
298- (NSString *)jkl;
299- (void)setJkl:(NSString *)value;
300"""
301    self.assertEqual(mc.Expand('StartIt()'), expected)
302
303  def testCatchRecursion(self):
304    f = io.StringIO(u"""
305PDDM-DEFINE foo(a, b)
306bar(1, a)
307bar(2, b)
308PDDM-DEFINE bar(x, y)
309foo(x, y)
310""")
311    mc = pddm.MacroCollection(f)
312    try:
313      result = mc.Expand('foo(A,B)')
314      self.fail('Should throw exception, entry %d' % idx)
315    except pddm.PDDMError as e:
316      self.assertEqual(e.message,
317                       'Found macro recusion, invoking "foo(1, A)":\n...while expanding "bar(1, A)".\n...while expanding "foo(A,B)".')
318
319
320class TestParsingSource(unittest.TestCase):
321
322  def testBasicParse(self):
323    test_list = [
324      # 1. no directives
325      (u'a\nb\nc',
326       (3,) ),
327      # 2. One define
328      (u'a\n//%PDDM-DEFINE foo()\n//%body\nc',
329       (1, 2, 1) ),
330      # 3. Two defines
331      (u'a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE bar()\n//%body2\nc',
332       (1, 4, 1) ),
333      # 4. Two defines with ends
334      (u'a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE-END\n'
335       u'//%PDDM-DEFINE bar()\n//%body2\n//%PDDM-DEFINE-END\nc',
336       (1, 6, 1) ),
337      # 5. One expand, one define (that runs to end of file)
338      (u'a\n//%PDDM-EXPAND foo()\nbody\n//%PDDM-EXPAND-END\n'
339       u'//%PDDM-DEFINE bar()\n//%body2\n',
340       (1, 1, 2) ),
341      # 6. One define ended with an expand.
342      (u'a\nb\n//%PDDM-DEFINE bar()\n//%body2\n'
343       u'//%PDDM-EXPAND bar()\nbody2\n//%PDDM-EXPAND-END\n',
344       (2, 2, 1) ),
345      # 7. Two expands (one end), one define.
346      (u'a\n//%PDDM-EXPAND foo(1)\nbody\n//%PDDM-EXPAND foo(2)\nbody2\n//%PDDM-EXPAND-END\n'
347       u'//%PDDM-DEFINE foo()\n//%body2\n',
348       (1, 2, 2) ),
349    ]
350    for idx, (input_str, line_counts) in enumerate(test_list, 1):
351      f = io.StringIO(input_str)
352      sf = pddm.SourceFile(f)
353      sf._ParseFile()
354      self.assertEqual(len(sf._sections), len(line_counts),
355                       'Entry %d -- %d != %d' %
356                       (idx, len(sf._sections), len(line_counts)))
357      for idx2, (sec, expected) in enumerate(zip(sf._sections, line_counts), 1):
358        self.assertEqual(sec.num_lines_captured, expected,
359                         'Entry %d, section %d -- %d != %d' %
360                         (idx, idx2, sec.num_lines_captured, expected))
361
362  def testErrors(self):
363    test_list = [
364      # 1. Directive within expansion
365      (u'//%PDDM-EXPAND a()\n//%PDDM-BOGUS',
366       'Ran into directive ("//%PDDM-BOGUS", line 2) while in "//%PDDM-EXPAND a()".'),
367      (u'//%PDDM-EXPAND a()\n//%PDDM-DEFINE a()\n//%body\n',
368       'Ran into directive ("//%PDDM-DEFINE", line 2) while in "//%PDDM-EXPAND a()".'),
369      # 3. Expansion ran off end of file
370      (u'//%PDDM-EXPAND a()\na\nb\n',
371       'Hit the end of the file while in "//%PDDM-EXPAND a()".'),
372      # 4. Directive within define
373      (u'//%PDDM-DEFINE a()\n//%body\n//%PDDM-BOGUS',
374       'Ran into directive ("//%PDDM-BOGUS", line 3) while in "//%PDDM-DEFINE a()".'),
375      (u'//%PDDM-DEFINE a()\n//%body\n//%PDDM-EXPAND-END a()',
376       'Ran into directive ("//%PDDM-EXPAND-END", line 3) while in "//%PDDM-DEFINE a()".'),
377      # 6. Directives that shouldn't start sections
378      (u'a\n//%PDDM-DEFINE-END a()\n//a\n',
379       'Unexpected line 2: "//%PDDM-DEFINE-END a()".'),
380      (u'a\n//%PDDM-EXPAND-END a()\n//a\n',
381       'Unexpected line 2: "//%PDDM-EXPAND-END a()".'),
382      (u'//%PDDM-BOGUS\n//a\n',
383       'Unexpected line 1: "//%PDDM-BOGUS".'),
384    ]
385    for idx, (input_str, expected_err) in enumerate(test_list, 1):
386      f = io.StringIO(input_str)
387      try:
388        pddm.SourceFile(f)._ParseFile()
389        self.fail('Should throw exception, entry %d' % idx)
390      except pddm.PDDMError as e:
391        self.assertEqual(e.message, expected_err,
392                        'Entry %d failed: %r' % (idx, e))
393
394class TestProcessingSource(unittest.TestCase):
395
396  def testBasics(self):
397    input_str = u"""
398//%PDDM-IMPORT-DEFINES ImportFile
399foo
400//%PDDM-EXPAND mumble(abc)
401//%PDDM-EXPAND-END
402bar
403//%PDDM-EXPAND mumble(def)
404//%PDDM-EXPAND mumble(ghi)
405//%PDDM-EXPAND-END
406baz
407//%PDDM-DEFINE mumble(a_)
408//%a_: getName(a_)
409"""
410    input_str2 = u"""
411//%PDDM-DEFINE getName(x_)
412//%do##x_$u##(int x_);
413
414"""
415    expected = u"""
416//%PDDM-IMPORT-DEFINES ImportFile
417foo
418//%PDDM-EXPAND mumble(abc)
419// This block of code is generated, do not edit it directly.
420
421abc: doAbc(int abc);
422//%PDDM-EXPAND-END mumble(abc)
423bar
424//%PDDM-EXPAND mumble(def)
425// This block of code is generated, do not edit it directly.
426
427def: doDef(int def);
428//%PDDM-EXPAND mumble(ghi)
429// This block of code is generated, do not edit it directly.
430
431ghi: doGhi(int ghi);
432//%PDDM-EXPAND-END (2 expansions)
433baz
434//%PDDM-DEFINE mumble(a_)
435//%a_: getName(a_)
436"""
437    expected_stripped = u"""
438//%PDDM-IMPORT-DEFINES ImportFile
439foo
440//%PDDM-EXPAND mumble(abc)
441//%PDDM-EXPAND-END mumble(abc)
442bar
443//%PDDM-EXPAND mumble(def)
444//%PDDM-EXPAND mumble(ghi)
445//%PDDM-EXPAND-END (2 expansions)
446baz
447//%PDDM-DEFINE mumble(a_)
448//%a_: getName(a_)
449"""
450    def _Resolver(name):
451      self.assertEqual(name, 'ImportFile')
452      return io.StringIO(input_str2)
453    f = io.StringIO(input_str)
454    sf = pddm.SourceFile(f, _Resolver)
455    sf.ProcessContent()
456    self.assertEqual(sf.processed_content, expected)
457    # Feed it through and nothing should change.
458    f2 = io.StringIO(sf.processed_content)
459    sf2 = pddm.SourceFile(f2, _Resolver)
460    sf2.ProcessContent()
461    self.assertEqual(sf2.processed_content, expected)
462    self.assertEqual(sf2.processed_content, sf.processed_content)
463    # Test stripping (with the original input and expanded version).
464    f2 = io.StringIO(input_str)
465    sf2 = pddm.SourceFile(f2)
466    sf2.ProcessContent(strip_expansion=True)
467    self.assertEqual(sf2.processed_content, expected_stripped)
468    f2 = io.StringIO(sf.processed_content)
469    sf2 = pddm.SourceFile(f2, _Resolver)
470    sf2.ProcessContent(strip_expansion=True)
471    self.assertEqual(sf2.processed_content, expected_stripped)
472
473  def testProcessFileWithMacroParseError(self):
474    input_str = u"""
475foo
476//%PDDM-DEFINE mumble(a_)
477//%body
478//%PDDM-DEFINE mumble(x_)
479//%body2
480
481"""
482    f = io.StringIO(input_str)
483    sf = pddm.SourceFile(f)
484    try:
485      sf.ProcessContent()
486      self.fail('Should throw exception, entry %d' % idx)
487    except pddm.PDDMError as e:
488      self.assertEqual(e.message,
489                       'Attempt to redefine macro: "PDDM-DEFINE mumble(x_)"\n'
490                       '...while parsing section that started:\n'
491                       '  Line 3: //%PDDM-DEFINE mumble(a_)')
492
493  def testProcessFileWithExpandError(self):
494    input_str = u"""
495foo
496//%PDDM-DEFINE mumble(a_)
497//%body
498//%PDDM-EXPAND foobar(x_)
499//%PDDM-EXPAND-END
500
501"""
502    f = io.StringIO(input_str)
503    sf = pddm.SourceFile(f)
504    try:
505      sf.ProcessContent()
506      self.fail('Should throw exception, entry %d' % idx)
507    except pddm.PDDMError as e:
508      self.assertEqual(e.message,
509                       'No macro named "foobar".\n'
510                       '...while expanding "foobar(x_)" from the section that'
511                       ' started:\n   Line 5: //%PDDM-EXPAND foobar(x_)')
512
513
514if __name__ == '__main__':
515  unittest.main()
516