1# testyacc.py 2 3import unittest 4try: 5 import StringIO 6except ImportError: 7 import io as StringIO 8 9import sys 10import os 11import warnings 12import re 13import platform 14 15sys.path.insert(0,"..") 16sys.tracebacklimit = 0 17 18import ply.yacc 19 20def make_pymodule_path(filename): 21 path = os.path.dirname(filename) 22 file = os.path.basename(filename) 23 mod, ext = os.path.splitext(file) 24 25 if sys.hexversion >= 0x3040000: 26 import importlib.util 27 fullpath = importlib.util.cache_from_source(filename, ext=='.pyc') 28 elif sys.hexversion >= 0x3020000: 29 import imp 30 modname = mod+"."+imp.get_tag()+ext 31 fullpath = os.path.join(path,'__pycache__',modname) 32 else: 33 fullpath = filename 34 return fullpath 35 36def pymodule_out_exists(filename): 37 return os.path.exists(make_pymodule_path(filename)) 38 39def pymodule_out_remove(filename): 40 os.remove(make_pymodule_path(filename)) 41 42def implementation(): 43 if platform.system().startswith("Java"): 44 return "Jython" 45 elif hasattr(sys, "pypy_version_info"): 46 return "PyPy" 47 else: 48 return "CPython" 49 50# Check the output to see if it contains all of a set of expected output lines. 51# This alternate implementation looks weird, but is needed to properly handle 52# some variations in error message order that occurs due to dict hash table 53# randomization that was introduced in Python 3.3 54def check_expected(result, expected): 55 # Normalize 'state n' text to account for randomization effects in Python 3.3 56 expected = re.sub(r' state \d+', 'state <n>', expected) 57 result = re.sub(r' state \d+', 'state <n>', result) 58 59 resultlines = set() 60 for line in result.splitlines(): 61 if line.startswith("WARNING: "): 62 line = line[9:] 63 elif line.startswith("ERROR: "): 64 line = line[7:] 65 resultlines.add(line) 66 67 # Selectively remove expected lines from the output 68 for eline in expected.splitlines(): 69 resultlines = set(line for line in resultlines if not line.endswith(eline)) 70 71 # Return True if no result lines remain 72 return not bool(resultlines) 73 74def run_import(module): 75 code = "import "+module 76 exec(code) 77 del sys.modules[module] 78 79# Tests related to errors and warnings when building parsers 80class YaccErrorWarningTests(unittest.TestCase): 81 def setUp(self): 82 sys.stderr = StringIO.StringIO() 83 sys.stdout = StringIO.StringIO() 84 try: 85 os.remove("parsetab.py") 86 pymodule_out_remove("parsetab.pyc") 87 except OSError: 88 pass 89 90 if sys.hexversion >= 0x3020000: 91 warnings.filterwarnings('ignore', category=ResourceWarning) 92 warnings.filterwarnings('ignore', category=DeprecationWarning) 93 94 def tearDown(self): 95 sys.stderr = sys.__stderr__ 96 sys.stdout = sys.__stdout__ 97 def test_yacc_badargs(self): 98 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badargs") 99 result = sys.stderr.getvalue() 100 self.assert_(check_expected(result, 101 "yacc_badargs.py:23: Rule 'p_statement_assign' has too many arguments\n" 102 "yacc_badargs.py:27: Rule 'p_statement_expr' requires an argument\n" 103 )) 104 def test_yacc_badid(self): 105 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badid") 106 result = sys.stderr.getvalue() 107 self.assert_(check_expected(result, 108 "yacc_badid.py:32: Illegal name 'bad&rule' in rule 'statement'\n" 109 "yacc_badid.py:36: Illegal rule name 'bad&rule'\n" 110 )) 111 112 def test_yacc_badprec(self): 113 try: 114 run_import("yacc_badprec") 115 except ply.yacc.YaccError: 116 result = sys.stderr.getvalue() 117 self.assert_(check_expected(result, 118 "precedence must be a list or tuple\n" 119 )) 120 def test_yacc_badprec2(self): 121 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badprec2") 122 result = sys.stderr.getvalue() 123 self.assert_(check_expected(result, 124 "Bad precedence table\n" 125 )) 126 127 def test_yacc_badprec3(self): 128 run_import("yacc_badprec3") 129 result = sys.stderr.getvalue() 130 self.assert_(check_expected(result, 131 "Precedence already specified for terminal 'MINUS'\n" 132 "Generating LALR tables\n" 133 134 )) 135 136 def test_yacc_badrule(self): 137 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badrule") 138 result = sys.stderr.getvalue() 139 self.assert_(check_expected(result, 140 "yacc_badrule.py:24: Syntax error. Expected ':'\n" 141 "yacc_badrule.py:28: Syntax error in rule 'statement'\n" 142 "yacc_badrule.py:33: Syntax error. Expected ':'\n" 143 "yacc_badrule.py:42: Syntax error. Expected ':'\n" 144 )) 145 146 def test_yacc_badtok(self): 147 try: 148 run_import("yacc_badtok") 149 except ply.yacc.YaccError: 150 result = sys.stderr.getvalue() 151 self.assert_(check_expected(result, 152 "tokens must be a list or tuple\n")) 153 154 def test_yacc_dup(self): 155 run_import("yacc_dup") 156 result = sys.stderr.getvalue() 157 self.assert_(check_expected(result, 158 "yacc_dup.py:27: Function p_statement redefined. Previously defined on line 23\n" 159 "Token 'EQUALS' defined, but not used\n" 160 "There is 1 unused token\n" 161 "Generating LALR tables\n" 162 163 )) 164 def test_yacc_error1(self): 165 try: 166 run_import("yacc_error1") 167 except ply.yacc.YaccError: 168 result = sys.stderr.getvalue() 169 self.assert_(check_expected(result, 170 "yacc_error1.py:61: p_error() requires 1 argument\n")) 171 172 def test_yacc_error2(self): 173 try: 174 run_import("yacc_error2") 175 except ply.yacc.YaccError: 176 result = sys.stderr.getvalue() 177 self.assert_(check_expected(result, 178 "yacc_error2.py:61: p_error() requires 1 argument\n")) 179 180 def test_yacc_error3(self): 181 try: 182 run_import("yacc_error3") 183 except ply.yacc.YaccError: 184 e = sys.exc_info()[1] 185 result = sys.stderr.getvalue() 186 self.assert_(check_expected(result, 187 "'p_error' defined, but is not a function or method\n")) 188 189 def test_yacc_error4(self): 190 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_error4") 191 result = sys.stderr.getvalue() 192 self.assert_(check_expected(result, 193 "yacc_error4.py:62: Illegal rule name 'error'. Already defined as a token\n" 194 )) 195 196 197 def test_yacc_error5(self): 198 run_import("yacc_error5") 199 result = sys.stdout.getvalue() 200 self.assert_(check_expected(result, 201 "Group at 3:10 to 3:12\n" 202 "Undefined name 'a'\n" 203 "Syntax error at 'b'\n" 204 "Syntax error at 4:18 to 4:22\n" 205 "Assignment Error at 2:5 to 5:27\n" 206 "13\n" 207 )) 208 209 def test_yacc_error6(self): 210 run_import("yacc_error6") 211 result = sys.stdout.getvalue() 212 self.assert_(check_expected(result, 213 "a=7\n" 214 "Line 3: Syntax error at '*'\n" 215 "c=21\n" 216 )) 217 218 def test_yacc_error7(self): 219 run_import("yacc_error7") 220 result = sys.stdout.getvalue() 221 self.assert_(check_expected(result, 222 "a=7\n" 223 "Line 3: Syntax error at '*'\n" 224 "c=21\n" 225 )) 226 227 def test_yacc_inf(self): 228 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_inf") 229 result = sys.stderr.getvalue() 230 self.assert_(check_expected(result, 231 "Token 'NUMBER' defined, but not used\n" 232 "There is 1 unused token\n" 233 "Infinite recursion detected for symbol 'statement'\n" 234 "Infinite recursion detected for symbol 'expression'\n" 235 )) 236 def test_yacc_literal(self): 237 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_literal") 238 result = sys.stderr.getvalue() 239 self.assert_(check_expected(result, 240 "yacc_literal.py:36: Literal token '**' in rule 'expression' may only be a single character\n" 241 )) 242 def test_yacc_misplaced(self): 243 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_misplaced") 244 result = sys.stderr.getvalue() 245 self.assert_(check_expected(result, 246 "yacc_misplaced.py:32: Misplaced '|'\n" 247 )) 248 249 def test_yacc_missing1(self): 250 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_missing1") 251 result = sys.stderr.getvalue() 252 self.assert_(check_expected(result, 253 "yacc_missing1.py:24: Symbol 'location' used, but not defined as a token or a rule\n" 254 )) 255 256 def test_yacc_nested(self): 257 run_import("yacc_nested") 258 result = sys.stdout.getvalue() 259 self.assert_(check_expected(result, 260 "A\n" 261 "A\n" 262 "A\n", 263 )) 264 265 def test_yacc_nodoc(self): 266 run_import("yacc_nodoc") 267 result = sys.stderr.getvalue() 268 self.assert_(check_expected(result, 269 "yacc_nodoc.py:27: No documentation string specified in function 'p_statement_expr' (ignored)\n" 270 "Generating LALR tables\n" 271 )) 272 273 def test_yacc_noerror(self): 274 run_import("yacc_noerror") 275 result = sys.stderr.getvalue() 276 self.assert_(check_expected(result, 277 "no p_error() function is defined\n" 278 "Generating LALR tables\n" 279 )) 280 281 def test_yacc_nop(self): 282 run_import("yacc_nop") 283 result = sys.stderr.getvalue() 284 self.assert_(check_expected(result, 285 "yacc_nop.py:27: Possible grammar rule 'statement_expr' defined without p_ prefix\n" 286 "Generating LALR tables\n" 287 )) 288 289 def test_yacc_notfunc(self): 290 run_import("yacc_notfunc") 291 result = sys.stderr.getvalue() 292 self.assert_(check_expected(result, 293 "'p_statement_assign' not defined as a function\n" 294 "Token 'EQUALS' defined, but not used\n" 295 "There is 1 unused token\n" 296 "Generating LALR tables\n" 297 )) 298 def test_yacc_notok(self): 299 try: 300 run_import("yacc_notok") 301 except ply.yacc.YaccError: 302 result = sys.stderr.getvalue() 303 self.assert_(check_expected(result, 304 "No token list is defined\n")) 305 306 def test_yacc_rr(self): 307 run_import("yacc_rr") 308 result = sys.stderr.getvalue() 309 self.assert_(check_expected(result, 310 "Generating LALR tables\n" 311 "1 reduce/reduce conflict\n" 312 "reduce/reduce conflict in state 15 resolved using rule (statement -> NAME EQUALS NUMBER)\n" 313 "rejected rule (expression -> NUMBER) in state 15\n" 314 315 )) 316 317 def test_yacc_rr_unused(self): 318 run_import("yacc_rr_unused") 319 result = sys.stderr.getvalue() 320 self.assert_(check_expected(result, 321 "no p_error() function is defined\n" 322 "Generating LALR tables\n" 323 "3 reduce/reduce conflicts\n" 324 "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n" 325 "rejected rule (rule4 -> A) in state 1\n" 326 "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n" 327 "rejected rule (rule5 -> A) in state 1\n" 328 "reduce/reduce conflict in state 1 resolved using rule (rule4 -> A)\n" 329 "rejected rule (rule5 -> A) in state 1\n" 330 "Rule (rule5 -> A) is never reduced\n" 331 )) 332 333 def test_yacc_simple(self): 334 run_import("yacc_simple") 335 result = sys.stderr.getvalue() 336 self.assert_(check_expected(result, 337 "Generating LALR tables\n" 338 )) 339 340 def test_yacc_sr(self): 341 run_import("yacc_sr") 342 result = sys.stderr.getvalue() 343 self.assert_(check_expected(result, 344 "Generating LALR tables\n" 345 "20 shift/reduce conflicts\n" 346 )) 347 348 def test_yacc_term1(self): 349 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_term1") 350 result = sys.stderr.getvalue() 351 self.assert_(check_expected(result, 352 "yacc_term1.py:24: Illegal rule name 'NUMBER'. Already defined as a token\n" 353 )) 354 355 def test_yacc_unicode_literals(self): 356 run_import("yacc_unicode_literals") 357 result = sys.stderr.getvalue() 358 self.assert_(check_expected(result, 359 "Generating LALR tables\n" 360 )) 361 362 def test_yacc_unused(self): 363 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_unused") 364 result = sys.stderr.getvalue() 365 self.assert_(check_expected(result, 366 "yacc_unused.py:62: Symbol 'COMMA' used, but not defined as a token or a rule\n" 367 "Symbol 'COMMA' is unreachable\n" 368 "Symbol 'exprlist' is unreachable\n" 369 )) 370 def test_yacc_unused_rule(self): 371 run_import("yacc_unused_rule") 372 result = sys.stderr.getvalue() 373 self.assert_(check_expected(result, 374 "yacc_unused_rule.py:62: Rule 'integer' defined, but not used\n" 375 "There is 1 unused rule\n" 376 "Symbol 'integer' is unreachable\n" 377 "Generating LALR tables\n" 378 )) 379 380 def test_yacc_uprec(self): 381 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec") 382 result = sys.stderr.getvalue() 383 self.assert_(check_expected(result, 384 "yacc_uprec.py:37: Nothing known about the precedence of 'UMINUS'\n" 385 )) 386 387 def test_yacc_uprec2(self): 388 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec2") 389 result = sys.stderr.getvalue() 390 self.assert_(check_expected(result, 391 "yacc_uprec2.py:37: Syntax error. Nothing follows %prec\n" 392 )) 393 394 def test_yacc_prec1(self): 395 self.assertRaises(ply.yacc.YaccError,run_import,"yacc_prec1") 396 result = sys.stderr.getvalue() 397 self.assert_(check_expected(result, 398 "Precedence rule 'left' defined for unknown symbol '+'\n" 399 "Precedence rule 'left' defined for unknown symbol '*'\n" 400 "Precedence rule 'left' defined for unknown symbol '-'\n" 401 "Precedence rule 'left' defined for unknown symbol '/'\n" 402 )) 403 404 def test_pkg_test1(self): 405 from pkg_test1 import parser 406 self.assertTrue(os.path.exists('pkg_test1/parsing/parsetab.py')) 407 self.assertTrue(os.path.exists('pkg_test1/parsing/lextab.py')) 408 self.assertTrue(os.path.exists('pkg_test1/parsing/parser.out')) 409 r = parser.parse('3+4+5') 410 self.assertEqual(r, 12) 411 412 def test_pkg_test2(self): 413 from pkg_test2 import parser 414 self.assertTrue(os.path.exists('pkg_test2/parsing/calcparsetab.py')) 415 self.assertTrue(os.path.exists('pkg_test2/parsing/calclextab.py')) 416 self.assertTrue(os.path.exists('pkg_test2/parsing/parser.out')) 417 r = parser.parse('3+4+5') 418 self.assertEqual(r, 12) 419 420 def test_pkg_test3(self): 421 from pkg_test3 import parser 422 self.assertTrue(os.path.exists('pkg_test3/generated/parsetab.py')) 423 self.assertTrue(os.path.exists('pkg_test3/generated/lextab.py')) 424 self.assertTrue(os.path.exists('pkg_test3/generated/parser.out')) 425 r = parser.parse('3+4+5') 426 self.assertEqual(r, 12) 427 428 def test_pkg_test4(self): 429 from pkg_test4 import parser 430 self.assertFalse(os.path.exists('pkg_test4/parsing/parsetab.py')) 431 self.assertFalse(os.path.exists('pkg_test4/parsing/lextab.py')) 432 self.assertFalse(os.path.exists('pkg_test4/parsing/parser.out')) 433 r = parser.parse('3+4+5') 434 self.assertEqual(r, 12) 435 436 def test_pkg_test5(self): 437 from pkg_test5 import parser 438 self.assertTrue(os.path.exists('pkg_test5/parsing/parsetab.py')) 439 self.assertTrue(os.path.exists('pkg_test5/parsing/lextab.py')) 440 self.assertTrue(os.path.exists('pkg_test5/parsing/parser.out')) 441 r = parser.parse('3+4+5') 442 self.assertEqual(r, 12) 443 444 def test_pkg_test6(self): 445 from pkg_test6 import parser 446 self.assertTrue(os.path.exists('pkg_test6/parsing/parsetab.py')) 447 self.assertTrue(os.path.exists('pkg_test6/parsing/lextab.py')) 448 self.assertTrue(os.path.exists('pkg_test6/parsing/parser.out')) 449 r = parser.parse('3+4+5') 450 self.assertEqual(r, 12) 451 452unittest.main() 453