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