mirror of
https://github.com/github/codeql.git
synced 2025-12-17 09:13:20 +01:00
108 lines
4.3 KiB
Python
108 lines
4.3 KiB
Python
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
|