summaryrefslogtreecommitdiff
path: root/tests/testyacc.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/testyacc.py')
-rw-r--r--tests/testyacc.py403
1 files changed, 403 insertions, 0 deletions
diff --git a/tests/testyacc.py b/tests/testyacc.py
new file mode 100644
index 0000000..b488bf7
--- /dev/null
+++ b/tests/testyacc.py
@@ -0,0 +1,403 @@
+# testyacc.py
+
+import unittest
+try:
+ import StringIO
+except ImportError:
+ import io as StringIO
+
+import sys
+import os
+import warnings
+import re
+import platform
+
+sys.tracebacklimit = 0
+
+import ply.yacc
+
+def make_pymodule_path(filename):
+ path = os.path.dirname(filename)
+ file = os.path.basename(filename)
+ mod, ext = os.path.splitext(file)
+
+ if sys.hexversion >= 0x3040000:
+ import importlib.util
+ fullpath = importlib.util.cache_from_source(filename, ext=='.pyc')
+ elif sys.hexversion >= 0x3020000:
+ import imp
+ modname = mod+"."+imp.get_tag()+ext
+ fullpath = os.path.join(path,'__pycache__',modname)
+ else:
+ fullpath = filename
+ return fullpath
+
+def pymodule_out_exists(filename):
+ return os.path.exists(make_pymodule_path(filename))
+
+def pymodule_out_remove(filename):
+ os.remove(make_pymodule_path(filename))
+
+def implementation():
+ if platform.system().startswith("Java"):
+ return "Jython"
+ elif hasattr(sys, "pypy_version_info"):
+ return "PyPy"
+ else:
+ return "CPython"
+
+# Check the output to see if it contains all of a set of expected output lines.
+# This alternate implementation looks weird, but is needed to properly handle
+# some variations in error message order that occurs due to dict hash table
+# randomization that was introduced in Python 3.3
+def check_expected(result, expected):
+ # Normalize 'state n' text to account for randomization effects in Python 3.3
+ expected = re.sub(r' state \d+', 'state <n>', expected)
+ result = re.sub(r' state \d+', 'state <n>', result)
+
+ resultlines = set()
+ for line in result.splitlines():
+ if line.startswith("WARNING: "):
+ line = line[9:]
+ elif line.startswith("ERROR: "):
+ line = line[7:]
+ resultlines.add(line)
+
+ # Selectively remove expected lines from the output
+ for eline in expected.splitlines():
+ resultlines = set(line for line in resultlines if not line.endswith(eline))
+
+ # Return True if no result lines remain
+ return not bool(resultlines)
+
+def run_import(module):
+ code = "import "+module
+ exec(code)
+ del sys.modules[module]
+
+# Tests related to errors and warnings when building parsers
+class YaccErrorWarningTests(unittest.TestCase):
+ def setUp(self):
+ sys.stderr = StringIO.StringIO()
+ sys.stdout = StringIO.StringIO()
+ try:
+ os.remove("parsetab.py")
+ pymodule_out_remove("parsetab.pyc")
+ except OSError:
+ pass
+
+ if sys.hexversion >= 0x3020000:
+ warnings.filterwarnings('ignore', category=ResourceWarning)
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
+
+ def tearDown(self):
+ sys.stderr = sys.__stderr__
+ sys.stdout = sys.__stdout__
+ def test_yacc_badargs(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badargs")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_badargs.py:23: Rule 'p_statement_assign' has too many arguments\n"
+ "yacc_badargs.py:27: Rule 'p_statement_expr' requires an argument\n"
+ ))
+ def test_yacc_badid(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badid")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_badid.py:29: Illegal name 'bad&rule' in rule 'statement'\n"
+ "yacc_badid.py:33: Illegal rule name 'bad&rule'\n"
+ ))
+
+ def test_yacc_badprec(self):
+ try:
+ run_import("yacc_badprec")
+ except ply.yacc.YaccError:
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "precedence must be a list or tuple\n"
+ ))
+ def test_yacc_badprec2(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badprec2")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Bad precedence table\n"
+ ))
+
+ def test_yacc_badprec3(self):
+ run_import("yacc_badprec3")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Precedence already specified for terminal 'MINUS'\n"
+ "Generating LALR tables\n"
+
+ ))
+
+ def test_yacc_badrule(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badrule")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_badrule.py:21: Syntax error. Expected ':'\n"
+ "yacc_badrule.py:25: Syntax error in rule 'statement'\n"
+ "yacc_badrule.py:30: Syntax error. Expected ':'\n"
+ "yacc_badrule.py:39: Syntax error. Expected ':'\n"
+ ))
+
+ def test_yacc_badtok(self):
+ try:
+ run_import("yacc_badtok")
+ except ply.yacc.YaccError:
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "tokens must be a list or tuple\n"))
+
+ def test_yacc_dup(self):
+ run_import("yacc_dup")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_dup.py:24: Function p_statement redefined. Previously defined on line 20\n"
+ "Token 'EQUALS' defined, but not used\n"
+ "There is 1 unused token\n"
+ "Generating LALR tables\n"
+
+ ))
+ def test_yacc_error1(self):
+ try:
+ run_import("yacc_error1")
+ except ply.yacc.YaccError:
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_error1.py:58: p_error() requires 1 argument\n"))
+
+ def test_yacc_error2(self):
+ try:
+ run_import("yacc_error2")
+ except ply.yacc.YaccError:
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_error2.py:58: p_error() requires 1 argument\n"))
+
+ def test_yacc_error3(self):
+ try:
+ run_import("yacc_error3")
+ except ply.yacc.YaccError:
+ e = sys.exc_info()[1]
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "'p_error' defined, but is not a function or method\n"))
+
+ def test_yacc_error4(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_error4")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_error4.py:59: Illegal rule name 'error'. Already defined as a token\n"
+ ))
+
+
+ def test_yacc_error5(self):
+ run_import("yacc_error5")
+ result = sys.stdout.getvalue()
+ self.assertTrue(check_expected(result,
+ "Group at 3:10 to 3:12\n"
+ "Undefined name 'a'\n"
+ "Syntax error at 'b'\n"
+ "Syntax error at 4:18 to 4:22\n"
+ "Assignment Error at 2:5 to 5:27\n"
+ "13\n"
+ ))
+
+ def test_yacc_error6(self):
+ run_import("yacc_error6")
+ result = sys.stdout.getvalue()
+ self.assertTrue(check_expected(result,
+ "a=7\n"
+ "Line 3: Syntax error at '*'\n"
+ "c=21\n"
+ ))
+
+ def test_yacc_error7(self):
+ run_import("yacc_error7")
+ result = sys.stdout.getvalue()
+ self.assertTrue(check_expected(result,
+ "a=7\n"
+ "Line 3: Syntax error at '*'\n"
+ "c=21\n"
+ ))
+
+ def test_yacc_inf(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_inf")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Token 'NUMBER' defined, but not used\n"
+ "There is 1 unused token\n"
+ "Infinite recursion detected for symbol 'statement'\n"
+ "Infinite recursion detected for symbol 'expression'\n"
+ ))
+ def test_yacc_literal(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_literal")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_literal.py:33: Literal token '**' in rule 'expression' may only be a single character\n"
+ ))
+ def test_yacc_misplaced(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_misplaced")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_misplaced.py:29: Misplaced '|'\n"
+ ))
+
+ def test_yacc_missing1(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_missing1")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_missing1.py:21: Symbol 'location' used, but not defined as a token or a rule\n"
+ ))
+
+ def test_yacc_nested(self):
+ run_import("yacc_nested")
+ result = sys.stdout.getvalue()
+ self.assertTrue(check_expected(result,
+ "A\n"
+ "A\n"
+ "A\n",
+ ))
+
+ def test_yacc_nodoc(self):
+ run_import("yacc_nodoc")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_nodoc.py:24: No documentation string specified in function 'p_statement_expr' (ignored)\n"
+ "Generating LALR tables\n"
+ ))
+
+ def test_yacc_noerror(self):
+ run_import("yacc_noerror")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "no p_error() function is defined\n"
+ "Generating LALR tables\n"
+ ))
+
+ def test_yacc_nop(self):
+ run_import("yacc_nop")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_nop.py:24: Possible grammar rule 'statement_expr' defined without p_ prefix\n"
+ "Generating LALR tables\n"
+ ))
+
+ def test_yacc_notfunc(self):
+ run_import("yacc_notfunc")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "'p_statement_assign' not defined as a function\n"
+ "Token 'EQUALS' defined, but not used\n"
+ "There is 1 unused token\n"
+ "Generating LALR tables\n"
+ ))
+ def test_yacc_notok(self):
+ try:
+ run_import("yacc_notok")
+ except ply.yacc.YaccError:
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "No token list is defined\n"))
+
+ def test_yacc_rr(self):
+ run_import("yacc_rr")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Generating LALR tables\n"
+ "1 reduce/reduce conflict\n"
+ "reduce/reduce conflict in state 15 resolved using rule (statement -> NAME EQUALS NUMBER)\n"
+ "rejected rule (expression -> NUMBER) in state 15\n"
+
+ ))
+
+ def test_yacc_rr_unused(self):
+ run_import("yacc_rr_unused")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "no p_error() function is defined\n"
+ "Generating LALR tables\n"
+ "3 reduce/reduce conflicts\n"
+ "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n"
+ "rejected rule (rule4 -> A) in state 1\n"
+ "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n"
+ "rejected rule (rule5 -> A) in state 1\n"
+ "reduce/reduce conflict in state 1 resolved using rule (rule4 -> A)\n"
+ "rejected rule (rule5 -> A) in state 1\n"
+ "Rule (rule5 -> A) is never reduced\n"
+ ))
+
+ def test_yacc_simple(self):
+ run_import("yacc_simple")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Generating LALR tables\n"
+ ))
+
+ def test_yacc_sr(self):
+ run_import("yacc_sr")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Generating LALR tables\n"
+ "20 shift/reduce conflicts\n"
+ ))
+
+ def test_yacc_term1(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_term1")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_term1.py:21: Illegal rule name 'NUMBER'. Already defined as a token\n"
+ ))
+
+ def test_yacc_unicode_literals(self):
+ run_import("yacc_unicode_literals")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Generating LALR tables\n"
+ ))
+
+ def test_yacc_unused(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_unused")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_unused.py:59: Symbol 'COMMA' used, but not defined as a token or a rule\n"
+ "Symbol 'COMMA' is unreachable\n"
+ "Symbol 'exprlist' is unreachable\n"
+ ))
+ def test_yacc_unused_rule(self):
+ run_import("yacc_unused_rule")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_unused_rule.py:59: Rule 'integer' defined, but not used\n"
+ "There is 1 unused rule\n"
+ "Symbol 'integer' is unreachable\n"
+ "Generating LALR tables\n"
+ ))
+
+ def test_yacc_uprec(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_uprec.py:34: Nothing known about the precedence of 'UMINUS'\n"
+ ))
+
+ def test_yacc_uprec2(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec2")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "yacc_uprec2.py:34: Syntax error. Nothing follows %prec\n"
+ ))
+
+ def test_yacc_prec1(self):
+ self.assertRaises(ply.yacc.YaccError,run_import,"yacc_prec1")
+ result = sys.stderr.getvalue()
+ self.assertTrue(check_expected(result,
+ "Precedence rule 'left' defined for unknown symbol '+'\n"
+ "Precedence rule 'left' defined for unknown symbol '*'\n"
+ "Precedence rule 'left' defined for unknown symbol '-'\n"
+ "Precedence rule 'left' defined for unknown symbol '/'\n"
+ ))
+
+unittest.main()