1# testyacc.py
2
3import unittest
4try:
5    import StringIO
6except ImportError:
7    import io as StringIO
8
9import sys
10import os
11
12sys.path.insert(0,"..")
13sys.tracebacklimit = 0
14
15import ply.yacc
16
17def check_expected(result,expected):
18    resultlines = []
19    for line in result.splitlines():
20        if line.startswith("WARNING: "):
21            line = line[9:]
22        elif line.startswith("ERROR: "):
23            line = line[7:]
24        resultlines.append(line)
25
26    expectedlines = expected.splitlines()
27    if len(resultlines) != len(expectedlines):
28        return False
29    for rline,eline in zip(resultlines,expectedlines):
30        if not rline.endswith(eline):
31            return False
32    return True
33
34def run_import(module):
35    code = "import "+module
36    exec(code)
37    del sys.modules[module]
38
39# Tests related to errors and warnings when building parsers
40class YaccErrorWarningTests(unittest.TestCase):
41    def setUp(self):
42        sys.stderr = StringIO.StringIO()
43        sys.stdout = StringIO.StringIO()
44        try:
45            os.remove("parsetab.py")
46            os.remove("parsetab.pyc")
47        except OSError:
48            pass
49
50    def tearDown(self):
51        sys.stderr = sys.__stderr__
52        sys.stdout = sys.__stdout__
53    def test_yacc_badargs(self):
54        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badargs")
55        result = sys.stderr.getvalue()
56        self.assert_(check_expected(result,
57                                    "yacc_badargs.py:23: Rule 'p_statement_assign' has too many arguments\n"
58                                    "yacc_badargs.py:27: Rule 'p_statement_expr' requires an argument\n"
59                                    ))
60    def test_yacc_badid(self):
61        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badid")
62        result = sys.stderr.getvalue()
63        self.assert_(check_expected(result,
64                                    "yacc_badid.py:32: Illegal name 'bad&rule' in rule 'statement'\n"
65                                    "yacc_badid.py:36: Illegal rule name 'bad&rule'\n"
66                                    ))
67
68    def test_yacc_badprec(self):
69        try:
70            run_import("yacc_badprec")
71        except ply.yacc.YaccError:
72            result = sys.stderr.getvalue()
73            self.assert_(check_expected(result,
74                                        "precedence must be a list or tuple\n"
75                                        ))
76    def test_yacc_badprec2(self):
77        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badprec2")
78        result = sys.stderr.getvalue()
79        self.assert_(check_expected(result,
80                                    "Bad precedence table\n"
81                                    ))
82
83    def test_yacc_badprec3(self):
84        run_import("yacc_badprec3")
85        result = sys.stderr.getvalue()
86        self.assert_(check_expected(result,
87                                    "Precedence already specified for terminal 'MINUS'\n"
88                                    "Generating LALR tables\n"
89
90                                    ))
91
92    def test_yacc_badrule(self):
93        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_badrule")
94        result = sys.stderr.getvalue()
95        self.assert_(check_expected(result,
96                                    "yacc_badrule.py:24: Syntax error. Expected ':'\n"
97                                    "yacc_badrule.py:28: Syntax error in rule 'statement'\n"
98                                    "yacc_badrule.py:33: Syntax error. Expected ':'\n"
99                                    "yacc_badrule.py:42: Syntax error. Expected ':'\n"
100                                    ))
101
102    def test_yacc_badtok(self):
103        try:
104            run_import("yacc_badtok")
105        except ply.yacc.YaccError:
106            result = sys.stderr.getvalue()
107            self.assert_(check_expected(result,
108                                        "tokens must be a list or tuple\n"))
109
110    def test_yacc_dup(self):
111        run_import("yacc_dup")
112        result = sys.stderr.getvalue()
113        self.assert_(check_expected(result,
114                                    "yacc_dup.py:27: Function p_statement redefined. Previously defined on line 23\n"
115                                    "Token 'EQUALS' defined, but not used\n"
116                                    "There is 1 unused token\n"
117                                    "Generating LALR tables\n"
118
119                                    ))
120    def test_yacc_error1(self):
121        try:
122            run_import("yacc_error1")
123        except ply.yacc.YaccError:
124            result = sys.stderr.getvalue()
125            self.assert_(check_expected(result,
126                                        "yacc_error1.py:61: p_error() requires 1 argument\n"))
127
128    def test_yacc_error2(self):
129        try:
130            run_import("yacc_error2")
131        except ply.yacc.YaccError:
132            result = sys.stderr.getvalue()
133            self.assert_(check_expected(result,
134                                        "yacc_error2.py:61: p_error() requires 1 argument\n"))
135
136    def test_yacc_error3(self):
137        try:
138            run_import("yacc_error3")
139        except ply.yacc.YaccError:
140            e = sys.exc_info()[1]
141            result = sys.stderr.getvalue()
142            self.assert_(check_expected(result,
143                                        "'p_error' defined, but is not a function or method\n"))
144
145    def test_yacc_error4(self):
146        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_error4")
147        result = sys.stderr.getvalue()
148        self.assert_(check_expected(result,
149                                    "yacc_error4.py:62: Illegal rule name 'error'. Already defined as a token\n"
150                                    ))
151
152    def test_yacc_inf(self):
153        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_inf")
154        result = sys.stderr.getvalue()
155        self.assert_(check_expected(result,
156                                    "Token 'NUMBER' defined, but not used\n"
157                                    "There is 1 unused token\n"
158                                    "Infinite recursion detected for symbol 'statement'\n"
159                                    "Infinite recursion detected for symbol 'expression'\n"
160                                    ))
161    def test_yacc_literal(self):
162        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_literal")
163        result = sys.stderr.getvalue()
164        self.assert_(check_expected(result,
165                                    "yacc_literal.py:36: Literal token '**' in rule 'expression' may only be a single character\n"
166                                    ))
167    def test_yacc_misplaced(self):
168        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_misplaced")
169        result = sys.stderr.getvalue()
170        self.assert_(check_expected(result,
171                                    "yacc_misplaced.py:32: Misplaced '|'\n"
172                                    ))
173
174    def test_yacc_missing1(self):
175        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_missing1")
176        result = sys.stderr.getvalue()
177        self.assert_(check_expected(result,
178                                    "yacc_missing1.py:24: Symbol 'location' used, but not defined as a token or a rule\n"
179                                    ))
180
181    def test_yacc_nested(self):
182        run_import("yacc_nested")
183        result = sys.stdout.getvalue()
184        self.assert_(check_expected(result,
185                                    "A\n"
186                                    "A\n"
187                                    "A\n",
188                                    ))
189
190    def test_yacc_nodoc(self):
191        run_import("yacc_nodoc")
192        result = sys.stderr.getvalue()
193        self.assert_(check_expected(result,
194                                    "yacc_nodoc.py:27: No documentation string specified in function 'p_statement_expr' (ignored)\n"
195                                    "Generating LALR tables\n"
196                                    ))
197
198    def test_yacc_noerror(self):
199        run_import("yacc_noerror")
200        result = sys.stderr.getvalue()
201        self.assert_(check_expected(result,
202                                    "no p_error() function is defined\n"
203                                    "Generating LALR tables\n"
204                                    ))
205
206    def test_yacc_nop(self):
207        run_import("yacc_nop")
208        result = sys.stderr.getvalue()
209        self.assert_(check_expected(result,
210                                    "yacc_nop.py:27: Possible grammar rule 'statement_expr' defined without p_ prefix\n"
211                                    "Generating LALR tables\n"
212                                    ))
213
214    def test_yacc_notfunc(self):
215        run_import("yacc_notfunc")
216        result = sys.stderr.getvalue()
217        self.assert_(check_expected(result,
218                                    "'p_statement_assign' not defined as a function\n"
219                                    "Token 'EQUALS' defined, but not used\n"
220                                    "There is 1 unused token\n"
221                                    "Generating LALR tables\n"
222                                    ))
223    def test_yacc_notok(self):
224        try:
225            run_import("yacc_notok")
226        except ply.yacc.YaccError:
227            result = sys.stderr.getvalue()
228            self.assert_(check_expected(result,
229                                        "No token list is defined\n"))
230
231    def test_yacc_rr(self):
232        run_import("yacc_rr")
233        result = sys.stderr.getvalue()
234        self.assert_(check_expected(result,
235                                    "Generating LALR tables\n"
236                                    "1 reduce/reduce conflict\n"
237                                    "reduce/reduce conflict in state 15 resolved using rule (statement -> NAME EQUALS NUMBER)\n"
238                                    "rejected rule (expression -> NUMBER) in state 15\n"
239
240                                    ))
241
242    def test_yacc_rr_unused(self):
243        run_import("yacc_rr_unused")
244        result = sys.stderr.getvalue()
245        self.assert_(check_expected(result,
246                                    "no p_error() function is defined\n"
247                                    "Generating LALR tables\n"
248                                    "3 reduce/reduce conflicts\n"
249                                    "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n"
250                                    "rejected rule (rule4 -> A) in state 1\n"
251                                    "reduce/reduce conflict in state 1 resolved using rule (rule3 -> A)\n"
252                                    "rejected rule (rule5 -> A) in state 1\n"
253                                    "reduce/reduce conflict in state 1 resolved using rule (rule4 -> A)\n"
254                                    "rejected rule (rule5 -> A) in state 1\n"
255                                    "Rule (rule5 -> A) is never reduced\n"
256                                    ))
257
258    def test_yacc_simple(self):
259        run_import("yacc_simple")
260        result = sys.stderr.getvalue()
261        self.assert_(check_expected(result,
262                                    "Generating LALR tables\n"
263                                    ))
264    def test_yacc_sr(self):
265        run_import("yacc_sr")
266        result = sys.stderr.getvalue()
267        self.assert_(check_expected(result,
268                                    "Generating LALR tables\n"
269                                    "20 shift/reduce conflicts\n"
270                                    ))
271
272    def test_yacc_term1(self):
273        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_term1")
274        result = sys.stderr.getvalue()
275        self.assert_(check_expected(result,
276                                    "yacc_term1.py:24: Illegal rule name 'NUMBER'. Already defined as a token\n"
277                                    ))
278
279    def test_yacc_unused(self):
280        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_unused")
281        result = sys.stderr.getvalue()
282        self.assert_(check_expected(result,
283                                    "yacc_unused.py:62: Symbol 'COMMA' used, but not defined as a token or a rule\n"
284                                    "Symbol 'COMMA' is unreachable\n"
285                                    "Symbol 'exprlist' is unreachable\n"
286                                    ))
287    def test_yacc_unused_rule(self):
288        run_import("yacc_unused_rule")
289        result = sys.stderr.getvalue()
290        self.assert_(check_expected(result,
291                                    "yacc_unused_rule.py:62: Rule 'integer' defined, but not used\n"
292                                    "There is 1 unused rule\n"
293                                    "Symbol 'integer' is unreachable\n"
294                                    "Generating LALR tables\n"
295                                    ))
296
297    def test_yacc_uprec(self):
298        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec")
299        result = sys.stderr.getvalue()
300        print repr(result)
301        self.assert_(check_expected(result,
302                                    "yacc_uprec.py:37: Nothing known about the precedence of 'UMINUS'\n"
303                                    ))
304
305    def test_yacc_uprec2(self):
306        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_uprec2")
307        result = sys.stderr.getvalue()
308        self.assert_(check_expected(result,
309                                    "yacc_uprec2.py:37: Syntax error. Nothing follows %prec\n"
310                                    ))
311
312    def test_yacc_prec1(self):
313        self.assertRaises(ply.yacc.YaccError,run_import,"yacc_prec1")
314        result = sys.stderr.getvalue()
315        self.assert_(check_expected(result,
316                                    "Precedence rule 'left' defined for unknown symbol '+'\n"
317                                    "Precedence rule 'left' defined for unknown symbol '*'\n"
318                                    "Precedence rule 'left' defined for unknown symbol '-'\n"
319                                    "Precedence rule 'left' defined for unknown symbol '/'\n"
320                                    ))
321
322
323
324unittest.main()
325