| #! /usr/bin/env python3 | |
| # | |
| # Protocol Buffers - Google's data interchange format | |
| # Copyright 2015 Google Inc. All rights reserved. | |
| # | |
| # Use of this source code is governed by a BSD-style | |
| # license that can be found in the LICENSE file or at | |
| # https://developers.google.com/open-source/licenses/bsd | |
| """Tests for pddm.py.""" | |
| import io | |
| import unittest | |
| from objectivec.DevTools import pddm | |
| class TestParsingMacros(unittest.TestCase): | |
| def testParseEmpty(self): | |
| f = io.StringIO('') | |
| result = pddm.MacroCollection(f) | |
| self.assertEqual(len(result._macros), 0) | |
| def testParseOne(self): | |
| f = io.StringIO("""PDDM-DEFINE foo( ) | |
| body""") | |
| result = pddm.MacroCollection(f) | |
| self.assertEqual(len(result._macros), 1) | |
| macro = result._macros.get('foo') | |
| self.assertIsNotNone(macro) | |
| self.assertEqual(macro.name, 'foo') | |
| self.assertEqual(macro.args, tuple()) | |
| self.assertEqual(macro.body, 'body') | |
| def testParseGeneral(self): | |
| # Tests multiple defines, spaces in all places, etc. | |
| f = io.StringIO(""" | |
| 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.assertEqual(macro.name, 'noArgs') | |
| self.assertEqual(macro.args, tuple()) | |
| self.assertEqual(macro.body, 'body1\nbody2\n') | |
| macro = result._macros.get('oneArg') | |
| self.assertIsNotNone(macro) | |
| self.assertEqual(macro.name, 'oneArg') | |
| self.assertEqual(macro.args, ('foo',)) | |
| self.assertEqual(macro.body, 'body3') | |
| macro = result._macros.get('twoArgs') | |
| self.assertIsNotNone(macro) | |
| self.assertEqual(macro.name, 'twoArgs') | |
| self.assertEqual(macro.args, ('bar_', 'baz')) | |
| self.assertEqual(macro.body, 'body4\nbody5') | |
| # Add into existing collection | |
| f = io.StringIO(""" | |
| 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.assertEqual(macro.name, 'another') | |
| self.assertEqual(macro.args, ('a', 'b', 'c')) | |
| self.assertEqual(macro.body, 'body1\nbody2') | |
| def testParseDirectiveIssues(self): | |
| test_list = [ | |
| # Unknown directive | |
| ('PDDM-DEFINE foo()\nbody\nPDDM-DEFINED foo\nbaz', | |
| 'Hit a line with an unknown directive: '), | |
| # End without begin | |
| ('PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nPDDM-DEFINE-END\n', | |
| 'Got DEFINE-END directive without an active macro: '), | |
| # Line not in macro block | |
| ('PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nmumble\n', | |
| 'Hit a line that wasn\'t a directive and no open macro definition: '), | |
| # Redefine macro | |
| ('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 | |
| ('PDDM-DEFINE\nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 2. No name (with spaces) | |
| ('PDDM-DEFINE \nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 3. No open paren | |
| ('PDDM-DEFINE foo\nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 4. No close paren | |
| ('PDDM-DEFINE foo(\nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 5. No close paren (with args) | |
| ('PDDM-DEFINE foo(a, b\nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 6. No name before args | |
| ('PDDM-DEFINE (a, b)\nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 7. No name before args | |
| ('PDDM-DEFINE foo bar(a, b)\nmumble', | |
| 'Failed to parse macro definition: '), | |
| # 8. Empty arg name | |
| ('PDDM-DEFINE foo(a, ,b)\nmumble', | |
| 'Empty arg name in macro definition: '), | |
| ('PDDM-DEFINE foo(a,,b)\nmumble', | |
| 'Empty arg name in macro definition: '), | |
| # 10. Duplicate name | |
| ('PDDM-DEFINE foo(a,b,a,c)\nmumble', | |
| 'Arg name "a" used more than once in macro definition: '), | |
| # 11. Invalid arg name | |
| ('PDDM-DEFINE foo(a b,c)\nmumble', | |
| 'Invalid arg name "a b" in macro definition: '), | |
| ('PDDM-DEFINE foo(a.b,c)\nmumble', | |
| 'Invalid arg name "a.b" in macro definition: '), | |
| ('PDDM-DEFINE foo(a-b,c)\nmumble', | |
| 'Invalid arg name "a-b" in macro definition: '), | |
| ('PDDM-DEFINE foo(a,b,c.)\nmumble', | |
| 'Invalid arg name "c." in macro definition: '), | |
| # 15. Extra stuff after the name | |
| ('PDDM-DEFINE foo(a,c) foo\nmumble', | |
| 'Failed to parse macro definition: '), | |
| ('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(""" | |
| 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 = [ | |
| ('noArgs()', | |
| 'body1\nbody2\n'), | |
| ('oneArg(wee)', | |
| 'body3 wee\n'), | |
| ('twoArgs(having some, fun)', | |
| 'body4 having some fun\nbody5'), | |
| # One arg, pass empty. | |
| ('oneArg()', | |
| 'body3 \n'), | |
| # Two args, gets empty in each slot. | |
| ('twoArgs(, empty)', | |
| 'body4 empty\nbody5'), | |
| ('twoArgs(empty, )', | |
| 'body4 empty \nbody5'), | |
| ('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(""" | |
| 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(""" | |
| PDDM-DEFINE foo(a, b) | |
| <a-z> | |
| PDDM-DEFINE baz(a) | |
| a - a$z | |
| """) | |
| mc = pddm.MacroCollection(f) | |
| test_list = [ | |
| # 1. Unknown macro | |
| ('bar()', | |
| 'No macro named "bar".'), | |
| ('bar(a)', | |
| 'No macro named "bar".'), | |
| # 3. Arg mismatch | |
| ('foo()', | |
| 'Expected 2 args, got: "foo()".'), | |
| ('foo(a b)', | |
| 'Expected 2 args, got: "foo(a b)".'), | |
| ('foo(a,b,c)', | |
| 'Expected 2 args, got: "foo(a,b,c)".'), | |
| # 6. Unknown option in expansion | |
| ('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(""" | |
| 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(""" | |
| 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! Test failed to catch recursion.') | |
| except pddm.PDDMError as e: | |
| self.assertEqual(e.message, | |
| 'Found macro recursion, 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 | |
| ('a\nb\nc', | |
| (3,) ), | |
| # 2. One define | |
| ('a\n//%PDDM-DEFINE foo()\n//%body\nc', | |
| (1, 2, 1) ), | |
| # 3. Two defines | |
| ('a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE bar()\n//%body2\nc', | |
| (1, 4, 1) ), | |
| # 4. Two defines with ends | |
| ('a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE-END\n' | |
| '//%PDDM-DEFINE bar()\n//%body2\n//%PDDM-DEFINE-END\nc', | |
| (1, 6, 1) ), | |
| # 5. One expand, one define (that runs to end of file) | |
| ('a\n//%PDDM-EXPAND foo()\nbody\n//%PDDM-EXPAND-END\n' | |
| '//%PDDM-DEFINE bar()\n//%body2\n', | |
| (1, 1, 2) ), | |
| # 6. One define ended with an expand. | |
| ('a\nb\n//%PDDM-DEFINE bar()\n//%body2\n' | |
| '//%PDDM-EXPAND bar()\nbody2\n//%PDDM-EXPAND-END\n', | |
| (2, 2, 1) ), | |
| # 7. Two expands (one end), one define. | |
| ('a\n//%PDDM-EXPAND foo(1)\nbody\n//%PDDM-EXPAND foo(2)\nbody2\n//%PDDM-EXPAND-END\n' | |
| '//%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 | |
| ('//%PDDM-EXPAND a()\n//%PDDM-BOGUS', | |
| 'Ran into directive ("//%PDDM-BOGUS", line 2) while in "//%PDDM-EXPAND a()".'), | |
| ('//%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 | |
| ('//%PDDM-EXPAND a()\na\nb\n', | |
| 'Hit the end of the file while in "//%PDDM-EXPAND a()".'), | |
| # 4. Directive within define | |
| ('//%PDDM-DEFINE a()\n//%body\n//%PDDM-BOGUS', | |
| 'Ran into directive ("//%PDDM-BOGUS", line 3) while in "//%PDDM-DEFINE a()".'), | |
| ('//%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 | |
| ('a\n//%PDDM-DEFINE-END a()\n//a\n', | |
| 'Unexpected line 2: "//%PDDM-DEFINE-END a()".'), | |
| ('a\n//%PDDM-EXPAND-END a()\n//a\n', | |
| 'Unexpected line 2: "//%PDDM-EXPAND-END a()".'), | |
| ('//%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): | |
| self.maxDiff = None | |
| input_str = """ | |
| //%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 = """ | |
| //%PDDM-DEFINE getName(x_) | |
| //%do##x_$u##(int x_); | |
| """ | |
| expected = """ | |
| //%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 = """ | |
| //%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 = """ | |
| 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! Test failed to catch macro parsing error.') | |
| 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 = """ | |
| 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! Test failed to catch expand error.') | |
| 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() |