-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathtest_parser.py
More file actions
107 lines (97 loc) · 4.29 KB
/
test_parser.py
File metadata and controls
107 lines (97 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import sys
import os.path
import shutil
import unittest
import pytest
import warnings
from tests import test_utils
from semmle.python.parser.dump_ast import old_parser, AstDumper, StdoutLogger
from semmle.python.parser.tsg_parser import parse as new_parser
import subprocess
class ParserTest(unittest.TestCase):
def __init__(self, name):
super(ParserTest, self).__init__(name)
self.test_folder = os.path.join(os.path.dirname(__file__), "parser")
self.maxDiff = None
@pytest.fixture(autouse=True)
def capsys(self, capsys):
self.capsys = capsys
def compare_parses(self, filename, logger):
pyfile = os.path.join(self.test_folder, filename)
stem = filename[:-3]
oldfile = os.path.join(self.test_folder, stem + ".old")
newfile = os.path.join(self.test_folder, stem + ".new")
old_error = False
new_error = False
try:
old_ast = old_parser(pyfile, logger)
with open(oldfile, "w") as old:
AstDumper(old).visit(old_ast)
except SyntaxError:
old_error = True
try:
new_ast = new_parser(pyfile, logger)
with open(newfile, "w") as new:
AstDumper(new).visit(new_ast)
except SyntaxError:
new_error = True
if old_error or new_error:
raise Exception("Parser error: old_error={}, new_error={}".format(old_error, new_error))
try:
diff = subprocess.check_output(["git", "diff", "--patience", "--no-index", oldfile, newfile])
except subprocess.CalledProcessError as e:
diff = e.output
if diff:
pytest.fail(diff.decode("utf-8"))
self.assertEqual(self.capsys.readouterr().err, "")
os.remove(oldfile)
os.remove(newfile)
def compare_expected(self, filename, logger, new=True ):
if sys.version_info.major < 3:
return
pyfile = os.path.join(self.test_folder, filename)
stem = filename[:-3]
expected = os.path.join(self.test_folder, stem + ".expected")
actual = os.path.join(self.test_folder, stem + ".actual")
parser = new_parser if new else old_parser
with warnings.catch_warnings():
# The test case `b"this is not a unicode escape because we are in a
# bytestring: \N{AMPERSAND}"`` in strings_new.py gives a DeprecationWarning,
# however we are actually testing the parser behavior on such bad code, so
# we can't just "fix" the code. You would think we could use the Python
# warning filter to ignore this specific warning, but that doesn't work --
# furthermore, using `error::DeprecationWarning` makes the *output* of the
# test change :O
#
# This was the best solution I could come up with that _both_ allows pytest
# to error on normal deprecation warnings, but also allows this one case to
# exist.
if filename == "strings_new.py":
warnings.simplefilter("ignore", DeprecationWarning)
ast = parser(pyfile, logger)
with open(actual, "w") as actual_file:
AstDumper(actual_file).visit(ast)
try:
diff = subprocess.check_output(["git", "diff", "--patience", "--no-index", expected, actual])
except subprocess.CalledProcessError as e:
diff = e.output
if diff:
pytest.fail(diff.decode("utf-8"))
self.assertEqual(self.capsys.readouterr().err, "")
os.remove(actual)
def setup_tests():
test_folder = os.path.join(os.path.dirname(__file__), "parser")
with StdoutLogger() as logger:
for file in os.listdir(test_folder):
if file.endswith(".py"):
stem = file[:-3]
test_name = "test_" + stem
if stem.endswith("_new"):
test_func = lambda self, file=file: self.compare_expected(file, logger, new=True)
elif stem.endswith("_old"):
test_func = lambda self, file=file: self.compare_expected(file, logger, new=False)
else:
test_func = lambda self, file=file: self.compare_parses(file, logger)
setattr(ParserTest, test_name, test_func)
setup_tests()
del setup_tests