• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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