text: #! /usr/bin/python
  #
  # Protocol Buffers - Google's data interchange format
  # Copyright 2015 Google Inc.  All rights reserved.
  # https://developers.google.com/protocol-buffers/
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are
  # met:
  #
  #     * Redistributions of source code must retain the above copyright
  # notice, this list of conditions and the following disclaimer.
  #     * Redistributions in binary form must reproduce the above
  # copyright notice, this list of conditions and the following disclaimer
  # in the documentation and/or other materials provided with the
  # distribution.
  #     * Neither the name of Google Inc. nor the names of its
  # contributors may be used to endorse or promote products derived from
  # this software without specific prior written permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  """Tests for pddm.py."""
  
  import io
  import unittest
  
  import pddm
  
  
  class TestParsingMacros(unittest.TestCase):
  
    def testParseEmpty(self):
      f = io.StringIO(u'')
      result = pddm.MacroCollection(f)
      self.assertEqual(len(result._macros), 0)
  
    def testParseOne(self):
      f = io.StringIO(u"""PDDM-DEFINE foo( )
  body""")
      result = pddm.MacroCollection(f)
      self.assertEqual(len(result._macros), 1)
      macro = result._macros.get('foo')
      self.assertIsNotNone(macro)
      self.assertEquals(macro.name, 'foo')
      self.assertEquals(macro.args, tuple())
      self.assertEquals(macro.body, 'body')
  
    def testParseGeneral(self):
      # Tests multiple defines, spaces in all places, etc.
      f = io.StringIO(u"""
  PDDM-DEFINE noArgs( )
  body1
  body2
  
  PDDM-DEFINE-END
  
  PDDM-DEFINE oneArg(foo)
  body3
  PDDM-DEFINE  twoArgs( bar_ , baz )
  body4
  body5""")
      result = pddm.MacroCollection(f)
      self.assertEqual(len(result._macros), 3)
      macro = result._macros.get('noArgs')
      self.assertIsNotNone(macro)
      self.assertEquals(macro.name, 'noArgs')
      self.assertEquals(macro.args, tuple())
      self.assertEquals(macro.body, 'body1\nbody2\n')
      macro = result._macros.get('oneArg')
      self.assertIsNotNone(macro)
      self.assertEquals(macro.name, 'oneArg')
      self.assertEquals(macro.args, ('foo',))
      self.assertEquals(macro.body, 'body3')
      macro = result._macros.get('twoArgs')
      self.assertIsNotNone(macro)
      self.assertEquals(macro.name, 'twoArgs')
      self.assertEquals(macro.args, ('bar_', 'baz'))
      self.assertEquals(macro.body, 'body4\nbody5')
      # Add into existing collection
      f = io.StringIO(u"""
  PDDM-DEFINE another(a,b,c)
  body1
  body2""")
      result.ParseInput(f)
      self.assertEqual(len(result._macros), 4)
      macro = result._macros.get('another')
      self.assertIsNotNone(macro)
      self.assertEquals(macro.name, 'another')
      self.assertEquals(macro.args, ('a', 'b', 'c'))
      self.assertEquals(macro.body, 'body1\nbody2')
  
    def testParseDirectiveIssues(self):
      test_list = [
        # Unknown directive
        (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINED foo\nbaz',
         'Hit a line with an unknown directive: '),
        # End without begin
        (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nPDDM-DEFINE-END\n',
         'Got DEFINE-END directive without an active macro: '),
        # Line not in macro block
        (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nmumble\n',
         'Hit a line that wasn\'t a directive and no open macro definition: '),
        # Redefine macro
        (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE foo(a)\nmumble\n',
         'Attempt to redefine macro: '),
      ]
      for idx, (input_str, expected_prefix) in enumerate(test_list, 1):
        f = io.StringIO(input_str)
        try:
          result = pddm.MacroCollection(f)
          self.fail('Should throw exception, entry %d' % idx)
        except pddm.PDDMError as e:
          self.assertTrue(e.message.startswith(expected_prefix),
                          'Entry %d failed: %r' % (idx, e))
  
    def testParseBeginIssues(self):
      test_list = [
        # 1. No name
        (u'PDDM-DEFINE\nmumble',
         'Failed to parse macro definition: '),
        # 2. No name (with spaces)
        (u'PDDM-DEFINE  \nmumble',
         'Failed to parse macro definition: '),
        # 3. No open paren
        (u'PDDM-DEFINE  foo\nmumble',
         'Failed to parse macro definition: '),
        # 4. No close paren
        (u'PDDM-DEFINE foo(\nmumble',
         'Failed to parse macro definition: '),
        # 5. No close paren (with args)
        (u'PDDM-DEFINE foo(a, b\nmumble',
         'Failed to parse macro definition: '),
        # 6. No name before args
        (u'PDDM-DEFINE  (a, b)\nmumble',
         'Failed to parse macro definition: '),
        # 7. No name before args
        (u'PDDM-DEFINE foo bar(a, b)\nmumble',
         'Failed to parse macro definition: '),
        # 8. Empty arg name
        (u'PDDM-DEFINE foo(a, ,b)\nmumble',
         'Empty arg name in macro definition: '),
        (u'PDDM-DEFINE foo(a,,b)\nmumble',
         'Empty arg name in macro definition: '),
        # 10. Duplicate name
        (u'PDDM-DEFINE foo(a,b,a,c)\nmumble',
         'Arg name "a" used more than once in macro definition: '),
        # 11. Invalid arg name
        (u'PDDM-DEFINE foo(a b,c)\nmumble',
         'Invalid arg name "a b" in macro definition: '),
        (u'PDDM-DEFINE foo(a.b,c)\nmumble',
         'Invalid arg name "a.b" in macro definition: '),
        (u'PDDM-DEFINE foo(a-b,c)\nmumble',
         'Invalid arg name "a-b" in macro definition: '),
        (u'PDDM-DEFINE foo(a,b,c.)\nmumble',
         'Invalid arg name "c." in macro definition: '),
        # 15. Extra stuff after the name
        (u'PDDM-DEFINE foo(a,c) foo\nmumble',
         'Failed to parse macro definition: '),
        (u'PDDM-DEFINE foo(a,c) foo)\nmumble',
         'Failed to parse macro definition: '),
      ]
      for idx, (input_str, expected_prefix) in enumerate(test_list, 1):
        f = io.StringIO(input_str)
        try:
          result = pddm.MacroCollection(f)
          self.fail('Should throw exception, entry %d' % idx)
        except pddm.PDDMError as e:
          self.assertTrue(e.message.startswith(expected_prefix),
                          'Entry %d failed: %r' % (idx, e))
  
  
  class TestExpandingMacros(unittest.TestCase):
  
    def testExpandBasics(self):
      f = io.StringIO(u"""
  PDDM-DEFINE noArgs( )
  body1
  body2
  
  PDDM-DEFINE-END
  
  PDDM-DEFINE oneArg(a)
  body3 a
  
  PDDM-DEFINE-END
  
  PDDM-DEFINE twoArgs(b,c)
  body4 b c
  body5
  PDDM-DEFINE-END
  
  """)
      mc = pddm.MacroCollection(f)
      test_list = [
        (u'noArgs()',
         'body1\nbody2\n'),
        (u'oneArg(wee)',
         'body3 wee\n'),
        (u'twoArgs(having some, fun)',
         'body4 having some fun\nbody5'),
        # One arg, pass empty.
        (u'oneArg()',
         'body3 \n'),
        # Two args, gets empty in each slot.
        (u'twoArgs(, empty)',
         'body4  empty\nbody5'),
        (u'twoArgs(empty, )',
         'body4 empty \nbody5'),
        (u'twoArgs(, )',
         'body4  \nbody5'),
      ]
      for idx, (input_str, expected) in enumerate(test_list, 1):
        result = mc.Expand(input_str)
        self.assertEqual(result, expected,
                         'Entry %d --\n       Result: %r\n     Expected: %r' %
                         (idx, result, expected))
  
    def testExpandArgOptions(self):
      f = io.StringIO(u"""
  PDDM-DEFINE bar(a)
  a-a$S-a$l-a$L-a$u-a$U
  PDDM-DEFINE-END
  """)
      mc = pddm.MacroCollection(f)
  
      self.assertEqual(mc.Expand('bar(xYz)'), 'xYz-   -xYz-xyz-XYz-XYZ')
      self.assertEqual(mc.Expand('bar(MnoP)'), 'MnoP-    -mnoP-mnop-MnoP-MNOP')
      # Test empty
      self.assertEqual(mc.Expand('bar()'), '-----')
  
    def testExpandSimpleMacroErrors(self):
      f = io.StringIO(u"""
  PDDM-DEFINE foo(a, b)
  <a-z>
  PDDM-DEFINE baz(a)
  a - a$z
  """)
      mc = pddm.MacroCollection(f)
      test_list = [
        # 1. Unknown macro
        (u'bar()',
         'No macro named "bar".'),
        (u'bar(a)',
         'No macro named "bar".'),
        # 3. Arg mismatch
        (u'foo()',
         'Expected 2 args, got: "foo()".'),
        (u'foo(a b)',
         'Expected 2 args, got: "foo(a b)".'),
        (u'foo(a,b,c)',
         'Expected 2 args, got: "foo(a,b,c)".'),
        # 6. Unknown option in expansion
        (u'baz(mumble)',
         'Unknown arg option "a$z" while expanding "baz(mumble)".'),
      ]
      for idx, (input_str, expected_err) in enumerate(test_list, 1):
        try:
          result = mc.Expand(input_str)
          self.fail('Should throw exception, entry %d' % idx)
        except pddm.PDDMError as e:
          self.assertEqual(e.message, expected_err,
                          'Entry %d failed: %r' % (idx, e))
  
    def testExpandReferences(self):
      f = io.StringIO(u"""
  PDDM-DEFINE StartIt()
  foo(abc, def)
  foo(ghi, jkl)
  PDDM-DEFINE foo(a, b)
  bar(a, int)
  bar(b, NSString *)
  PDDM-DEFINE bar(n, t)
  - (t)n;
  - (void)set##n$u##:(t)value;
  
  """)
      mc = pddm.MacroCollection(f)
      expected = """- (int)abc;
  - (void)setAbc:(int)value;
  
  - (NSString *)def;
  - (void)setDef:(NSString *)value;
  
  - (int)ghi;
  - (void)setGhi:(int)value;
  
  - (NSString *)jkl;
  - (void)setJkl:(NSString *)value;
  """
      self.assertEqual(mc.Expand('StartIt()'), expected)
  
    def testCatchRecursion(self):
      f = io.StringIO(u"""
  PDDM-DEFINE foo(a, b)
  bar(1, a)
  bar(2, b)
  PDDM-DEFINE bar(x, y)
  foo(x, y)
  """)
      mc = pddm.MacroCollection(f)
      try:
        result = mc.Expand('foo(A,B)')
        self.fail('Should throw exception, entry %d' % idx)
      except pddm.PDDMError as e:
        self.assertEqual(e.message,
                         'Found macro recusion, invoking "foo(1, A)":\n...while expanding "bar(1, A)".\n...while expanding "foo(A,B)".')
  
  
  class TestParsingSource(unittest.TestCase):
  
    def testBasicParse(self):
      test_list = [
        # 1. no directives
        (u'a\nb\nc',
         (3,) ),
        # 2. One define
        (u'a\n//%PDDM-DEFINE foo()\n//%body\nc',
         (1, 2, 1) ),
        # 3. Two defines
        (u'a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE bar()\n//%body2\nc',
         (1, 4, 1) ),
        # 4. Two defines with ends
        (u'a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE-END\n'
         u'//%PDDM-DEFINE bar()\n//%body2\n//%PDDM-DEFINE-END\nc',
         (1, 6, 1) ),
        # 5. One expand, one define (that runs to end of file)
        (u'a\n//%PDDM-EXPAND foo()\nbody\n//%PDDM-EXPAND-END\n'
         u'//%PDDM-DEFINE bar()\n//%body2\n',
         (1, 1, 2) ),
        # 6. One define ended with an expand.
        (u'a\nb\n//%PDDM-DEFINE bar()\n//%body2\n'
         u'//%PDDM-EXPAND bar()\nbody2\n//%PDDM-EXPAND-END\n',
         (2, 2, 1) ),
        # 7. Two expands (one end), one define.
        (u'a\n//%PDDM-EXPAND foo(1)\nbody\n//%PDDM-EXPAND foo(2)\nbody2\n//%PDDM-EXPAND-END\n'
         u'//%PDDM-DEFINE foo()\n//%body2\n',
         (1, 2, 2) ),
      ]
      for idx, (input_str, line_counts) in enumerate(test_list, 1):
        f = io.StringIO(input_str)
        sf = pddm.SourceFile(f)
        sf._ParseFile()
        self.assertEqual(len(sf._sections), len(line_counts),
                         'Entry %d -- %d != %d' %
                         (idx, len(sf._sections), len(line_counts)))
        for idx2, (sec, expected) in enumerate(zip(sf._sections, line_counts), 1):
          self.assertEqual(sec.num_lines_captured, expected,
                           'Entry %d, section %d -- %d != %d' %
                           (idx, idx2, sec.num_lines_captured, expected))
  
    def testErrors(self):
      test_list = [
        # 1. Directive within expansion
        (u'//%PDDM-EXPAND a()\n//%PDDM-BOGUS',
         'Ran into directive ("//%PDDM-BOGUS", line 2) while in "//%PDDM-EXPAND a()".'),
        (u'//%PDDM-EXPAND a()\n//%PDDM-DEFINE a()\n//%body\n',
         'Ran into directive ("//%PDDM-DEFINE", line 2) while in "//%PDDM-EXPAND a()".'),
        # 3. Expansion ran off end of file
        (u'//%PDDM-EXPAND a()\na\nb\n',
         'Hit the end of the file while in "//%PDDM-EXPAND a()".'),
        # 4. Directive within define
        (u'//%PDDM-DEFINE a()\n//%body\n//%PDDM-BOGUS',
         'Ran into directive ("//%PDDM-BOGUS", line 3) while in "//%PDDM-DEFINE a()".'),
        (u'//%PDDM-DEFINE a()\n//%body\n//%PDDM-EXPAND-END a()',
         'Ran into directive ("//%PDDM-EXPAND-END", line 3) while in "//%PDDM-DEFINE a()".'),
        # 6. Directives that shouldn't start sections
        (u'a\n//%PDDM-DEFINE-END a()\n//a\n',
         'Unexpected line 2: "//%PDDM-DEFINE-END a()".'),
        (u'a\n//%PDDM-EXPAND-END a()\n//a\n',
         'Unexpected line 2: "//%PDDM-EXPAND-END a()".'),
        (u'//%PDDM-BOGUS\n//a\n',
         'Unexpected line 1: "//%PDDM-BOGUS".'),
      ]
      for idx, (input_str, expected_err) in enumerate(test_list, 1):
        f = io.StringIO(input_str)
        try:
          pddm.SourceFile(f)._ParseFile()
          self.fail('Should throw exception, entry %d' % idx)
        except pddm.PDDMError as e:
          self.assertEqual(e.message, expected_err,
                          'Entry %d failed: %r' % (idx, e))
  
  class TestProcessingSource(unittest.TestCase):
  
    def testBasics(self):
      input_str = u"""
  //%PDDM-IMPORT-DEFINES ImportFile
  foo
  //%PDDM-EXPAND mumble(abc)
  //%PDDM-EXPAND-END
  bar
  //%PDDM-EXPAND mumble(def)
  //%PDDM-EXPAND mumble(ghi)
  //%PDDM-EXPAND-END
  baz
  //%PDDM-DEFINE mumble(a_)
  //%a_: getName(a_)
  """
      input_str2 = u"""
  //%PDDM-DEFINE getName(x_)
  //%do##x_$u##(int x_);
  
  """
      expected = u"""
  //%PDDM-IMPORT-DEFINES ImportFile
  foo
  //%PDDM-EXPAND mumble(abc)
  // This block of code is generated, do not edit it directly.
  
  abc: doAbc(int abc);
  //%PDDM-EXPAND-END mumble(abc)
  bar
  //%PDDM-EXPAND mumble(def)
  // This block of code is generated, do not edit it directly.
  
  def: doDef(int def);
  //%PDDM-EXPAND mumble(ghi)
  // This block of code is generated, do not edit it directly.
  
  ghi: doGhi(int ghi);
  //%PDDM-EXPAND-END (2 expansions)
  baz
  //%PDDM-DEFINE mumble(a_)
  //%a_: getName(a_)
  """
      expected_stripped = u"""
  //%PDDM-IMPORT-DEFINES ImportFile
  foo
  //%PDDM-EXPAND mumble(abc)
  //%PDDM-EXPAND-END mumble(abc)
  bar
  //%PDDM-EXPAND mumble(def)
  //%PDDM-EXPAND mumble(ghi)
  //%PDDM-EXPAND-END (2 expansions)
  baz
  //%PDDM-DEFINE mumble(a_)
  //%a_: getName(a_)
  """
      def _Resolver(name):
        self.assertEqual(name, 'ImportFile')
        return io.StringIO(input_str2)
      f = io.StringIO(input_str)
      sf = pddm.SourceFile(f, _Resolver)
      sf.ProcessContent()
      self.assertEqual(sf.processed_content, expected)
      # Feed it through and nothing should change.
      f2 = io.StringIO(sf.processed_content)
      sf2 = pddm.SourceFile(f2, _Resolver)
      sf2.ProcessContent()
      self.assertEqual(sf2.processed_content, expected)
      self.assertEqual(sf2.processed_content, sf.processed_content)
      # Test stripping (with the original input and expanded version).
      f2 = io.StringIO(input_str)
      sf2 = pddm.SourceFile(f2)
      sf2.ProcessContent(strip_expansion=True)
      self.assertEqual(sf2.processed_content, expected_stripped)
      f2 = io.StringIO(sf.processed_content)
      sf2 = pddm.SourceFile(f2, _Resolver)
      sf2.ProcessContent(strip_expansion=True)
      self.assertEqual(sf2.processed_content, expected_stripped)
  
    def testProcessFileWithMacroParseError(self):
      input_str = u"""
  foo
  //%PDDM-DEFINE mumble(a_)
  //%body
  //%PDDM-DEFINE mumble(x_)
  //%body2
  
  """
      f = io.StringIO(input_str)
      sf = pddm.SourceFile(f)
      try:
        sf.ProcessContent()
        self.fail('Should throw exception, entry %d' % idx)
      except pddm.PDDMError as e:
        self.assertEqual(e.message,
                         'Attempt to redefine macro: "PDDM-DEFINE mumble(x_)"\n'
                         '...while parsing section that started:\n'
                         '  Line 3: //%PDDM-DEFINE mumble(a_)')
  
    def testProcessFileWithExpandError(self):
      input_str = u"""
  foo
  //%PDDM-DEFINE mumble(a_)
  //%body
  //%PDDM-EXPAND foobar(x_)
  //%PDDM-EXPAND-END
  
  """
      f = io.StringIO(input_str)
      sf = pddm.SourceFile(f)
      try:
        sf.ProcessContent()
        self.fail('Should throw exception, entry %d' % idx)
      except pddm.PDDMError as e:
        self.assertEqual(e.message,
                         'No macro named "foobar".\n'
                         '...while expanding "foobar(x_)" from the section that'
                         ' started:\n   Line 5: //%PDDM-EXPAND foobar(x_)')
  
  
  if __name__ == '__main__':
    unittest.main()
  