code/source: remove compiling functions

A lot of complex code that isn't used anymore outside of tests after
the previous commit.
This commit is contained in:
Ran Benita 2020-07-01 20:20:12 +03:00
parent 9640c9c9eb
commit ef39115001
4 changed files with 56 additions and 290 deletions

View File

@ -7,7 +7,6 @@ from .code import getfslineno
from .code import getrawcode from .code import getrawcode
from .code import Traceback from .code import Traceback
from .code import TracebackEntry from .code import TracebackEntry
from .source import compile_ as compile
from .source import Source from .source import Source
__all__ = [ __all__ = [
@ -19,6 +18,5 @@ __all__ = [
"getrawcode", "getrawcode",
"Traceback", "Traceback",
"TracebackEntry", "TracebackEntry",
"compile",
"Source", "Source",
] ]

View File

@ -1,13 +1,9 @@
import ast import ast
import inspect import inspect
import linecache
import sys
import textwrap import textwrap
import tokenize import tokenize
import warnings import warnings
from bisect import bisect_right from bisect import bisect_right
from types import CodeType
from types import FrameType
from typing import Iterable from typing import Iterable
from typing import Iterator from typing import Iterator
from typing import List from typing import List
@ -15,13 +11,7 @@ from typing import Optional
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
import py
from _pytest.compat import overload from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
from typing_extensions import Literal
class Source: class Source:
@ -30,8 +20,6 @@ class Source:
When using Source(...), the source lines are deindented. When using Source(...), the source lines are deindented.
""" """
_compilecounter = 0
def __init__(self, obj: object = None) -> None: def __init__(self, obj: object = None) -> None:
if not obj: if not obj:
self.lines = [] # type: List[str] self.lines = [] # type: List[str]
@ -122,124 +110,6 @@ class Source:
def __str__(self) -> str: def __str__(self) -> str:
return "\n".join(self.lines) return "\n".join(self.lines)
@overload
def compile(
self,
filename: Optional[str] = ...,
mode: str = ...,
flag: "Literal[0]" = ...,
dont_inherit: int = ...,
_genframe: Optional[FrameType] = ...,
) -> CodeType:
raise NotImplementedError()
@overload # noqa: F811
def compile( # noqa: F811
self,
filename: Optional[str] = ...,
mode: str = ...,
flag: int = ...,
dont_inherit: int = ...,
_genframe: Optional[FrameType] = ...,
) -> Union[CodeType, ast.AST]:
raise NotImplementedError()
def compile( # noqa: F811
self,
filename: Optional[str] = None,
mode: str = "exec",
flag: int = 0,
dont_inherit: int = 0,
_genframe: Optional[FrameType] = None,
) -> Union[CodeType, ast.AST]:
""" return compiled code object. if filename is None
invent an artificial filename which displays
the source/line position of the caller frame.
"""
if not filename or py.path.local(filename).check(file=0):
if _genframe is None:
_genframe = sys._getframe(1) # the caller
fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
base = "<%d-codegen " % self._compilecounter
self.__class__._compilecounter += 1
if not filename:
filename = base + "%s:%d>" % (fn, lineno)
else:
filename = base + "%r %s:%d>" % (filename, fn, lineno)
source = "\n".join(self.lines) + "\n"
try:
co = compile(source, filename, mode, flag)
except SyntaxError as ex:
# re-represent syntax errors from parsing python strings
msglines = self.lines[: ex.lineno]
if ex.offset:
msglines.append(" " * ex.offset + "^")
msglines.append("(code was compiled probably from here: %s)" % filename)
newex = SyntaxError("\n".join(msglines))
newex.offset = ex.offset
newex.lineno = ex.lineno
newex.text = ex.text
raise newex from ex
else:
if flag & ast.PyCF_ONLY_AST:
assert isinstance(co, ast.AST)
return co
assert isinstance(co, CodeType)
lines = [(x + "\n") for x in self.lines]
# Type ignored because linecache.cache is private.
linecache.cache[filename] = (1, None, lines, filename) # type: ignore
return co
#
# public API shortcut functions
#
@overload
def compile_(
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = ...,
mode: str = ...,
flags: "Literal[0]" = ...,
dont_inherit: int = ...,
) -> CodeType:
raise NotImplementedError()
@overload # noqa: F811
def compile_( # noqa: F811
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = ...,
mode: str = ...,
flags: int = ...,
dont_inherit: int = ...,
) -> Union[CodeType, ast.AST]:
raise NotImplementedError()
def compile_( # noqa: F811
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = None,
mode: str = "exec",
flags: int = 0,
dont_inherit: int = 0,
) -> Union[CodeType, ast.AST]:
""" compile the given source to a raw code object,
and maintain an internal cache which allows later
retrieval of the source code for the code object
and any recursively created code objects.
"""
if isinstance(source, ast.AST):
# XXX should Source support having AST?
assert filename is not None
co = compile(source, filename, mode, flags, dont_inherit)
assert isinstance(co, (CodeType, ast.AST))
return co
_genframe = sys._getframe(1) # the caller
s = Source(source)
return s.compile(filename, mode, flags, _genframe=_genframe)
# #
# helper functions # helper functions

View File

@ -127,24 +127,28 @@ class TestTraceback_f_g_h:
assert s.endswith("raise ValueError") assert s.endswith("raise ValueError")
def test_traceback_entry_getsource_in_construct(self): def test_traceback_entry_getsource_in_construct(self):
source = _pytest._code.Source( def xyz():
"""\ try:
def xyz(): raise ValueError
try: except somenoname: # type: ignore[name-defined] # noqa: F821
raise ValueError pass
except somenoname:
pass
xyz()
"""
)
try: try:
exec(source.compile()) xyz()
except NameError: except NameError:
tb = _pytest._code.ExceptionInfo.from_current().traceback excinfo = _pytest._code.ExceptionInfo.from_current()
print(tb[-1].getsource()) else:
s = str(tb[-1].getsource()) assert False, "did not raise NameError"
assert s.startswith("def xyz():\n try:")
assert s.strip().endswith("except somenoname:") tb = excinfo.traceback
source = tb[-1].getsource()
assert source is not None
assert source.deindent().lines == [
"def xyz():",
" try:",
" raise ValueError",
" except somenoname: # type: ignore[name-defined] # noqa: F821",
]
def test_traceback_cut(self): def test_traceback_cut(self):
co = _pytest._code.Code(f) co = _pytest._code.Code(f)
@ -445,16 +449,6 @@ class TestFormattedExcinfo:
return importasmod return importasmod
def excinfo_from_exec(self, source):
source = _pytest._code.Source(source).strip()
try:
exec(source.compile())
except KeyboardInterrupt:
raise
except BaseException:
return _pytest._code.ExceptionInfo.from_current()
assert 0, "did not raise"
def test_repr_source(self): def test_repr_source(self):
pr = FormattedExcinfo() pr = FormattedExcinfo()
source = _pytest._code.Source( source = _pytest._code.Source(
@ -471,19 +465,29 @@ class TestFormattedExcinfo:
def test_repr_source_excinfo(self) -> None: def test_repr_source_excinfo(self) -> None:
""" check if indentation is right """ """ check if indentation is right """
pr = FormattedExcinfo() try:
excinfo = self.excinfo_from_exec(
""" def f():
def f(): 1 / 0
assert 0
f() f()
"""
) except BaseException:
excinfo = _pytest._code.ExceptionInfo.from_current()
else:
assert 0, "did not raise"
pr = FormattedExcinfo() pr = FormattedExcinfo()
source = pr._getentrysource(excinfo.traceback[-1]) source = pr._getentrysource(excinfo.traceback[-1])
assert source is not None assert source is not None
lines = pr.get_source(source, 1, excinfo) lines = pr.get_source(source, 1, excinfo)
assert lines == [" def f():", "> assert 0", "E AssertionError"] for line in lines:
print(line)
assert lines == [
" def f():",
"> 1 / 0",
"E ZeroDivisionError: division by zero",
]
def test_repr_source_not_existing(self): def test_repr_source_not_existing(self):
pr = FormattedExcinfo() pr = FormattedExcinfo()

View File

@ -3,6 +3,7 @@
# or redundant on purpose and can't be disable on a line-by-line basis # or redundant on purpose and can't be disable on a line-by-line basis
import ast import ast
import inspect import inspect
import linecache
import sys import sys
import textwrap import textwrap
from types import CodeType from types import CodeType
@ -33,14 +34,6 @@ def test_source_str_function() -> None:
assert str(x) == "\n3" assert str(x) == "\n3"
def test_unicode() -> None:
x = Source("4")
assert str(x) == "4"
co = _pytest._code.compile('"å"', mode="eval")
val = eval(co)
assert isinstance(val, str)
def test_source_from_function() -> None: def test_source_from_function() -> None:
source = _pytest._code.Source(test_source_str_function) source = _pytest._code.Source(test_source_str_function)
assert str(source).startswith("def test_source_str_function() -> None:") assert str(source).startswith("def test_source_str_function() -> None:")
@ -83,15 +76,6 @@ def test_source_strip_multiline() -> None:
assert source2.lines == [" hello"] assert source2.lines == [" hello"]
def test_syntaxerror_rerepresentation() -> None:
ex = pytest.raises(SyntaxError, _pytest._code.compile, "xyz xyz")
assert ex is not None
assert ex.value.lineno == 1
assert ex.value.offset in {5, 7} # cpython: 7, pypy3.6 7.1.1: 5
assert ex.value.text
assert ex.value.text.rstrip("\n") == "xyz xyz"
class TestAccesses: class TestAccesses:
def setup_class(self) -> None: def setup_class(self) -> None:
self.source = Source( self.source = Source(
@ -124,7 +108,7 @@ class TestAccesses:
assert len(values) == 4 assert len(values) == 4
class TestSourceParsingAndCompiling: class TestSourceParsing:
def setup_class(self) -> None: def setup_class(self) -> None:
self.source = Source( self.source = Source(
"""\ """\
@ -135,39 +119,6 @@ class TestSourceParsingAndCompiling:
""" """
).strip() ).strip()
def test_compile(self) -> None:
co = _pytest._code.compile("x=3")
d = {} # type: Dict[str, Any]
exec(co, d)
assert d["x"] == 3
def test_compile_and_getsource_simple(self) -> None:
co = _pytest._code.compile("x=3")
exec(co)
source = _pytest._code.Source(co)
assert str(source) == "x=3"
def test_compile_and_getsource_through_same_function(self) -> None:
def gensource(source):
return _pytest._code.compile(source)
co1 = gensource(
"""
def f():
raise KeyError()
"""
)
co2 = gensource(
"""
def f():
raise ValueError()
"""
)
source1 = inspect.getsource(co1)
assert "KeyError" in source1
source2 = inspect.getsource(co2)
assert "ValueError" in source2
def test_getstatement(self) -> None: def test_getstatement(self) -> None:
# print str(self.source) # print str(self.source)
ass = str(self.source[1:]) ass = str(self.source[1:])
@ -264,44 +215,6 @@ class TestSourceParsingAndCompiling:
source = Source(":") source = Source(":")
pytest.raises(SyntaxError, lambda: source.getstatementrange(0)) pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
def test_compile_to_ast(self) -> None:
source = Source("x = 4")
mod = source.compile(flag=ast.PyCF_ONLY_AST)
assert isinstance(mod, ast.Module)
compile(mod, "<filename>", "exec")
def test_compile_and_getsource(self) -> None:
co = self.source.compile()
exec(co, globals())
f(7) # type: ignore
excinfo = pytest.raises(AssertionError, f, 6) # type: ignore
assert excinfo is not None
frame = excinfo.traceback[-1].frame
assert isinstance(frame.code.fullsource, Source)
stmt = frame.code.fullsource.getstatement(frame.lineno)
assert str(stmt).strip().startswith("assert")
@pytest.mark.parametrize("name", ["", None, "my"])
def test_compilefuncs_and_path_sanity(self, name: Optional[str]) -> None:
def check(comp, name) -> None:
co = comp(self.source, name)
if not name:
expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2) # type: ignore
else:
expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2) # type: ignore
fn = co.co_filename
assert fn.endswith(expected)
mycode = _pytest._code.Code(self.test_compilefuncs_and_path_sanity)
mylineno = mycode.firstlineno
mypath = mycode.path
for comp in _pytest._code.compile, _pytest._code.Source.compile:
check(comp, name)
def test_offsetless_synerr(self):
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode="eval")
def test_getstartingblock_singleline() -> None: def test_getstartingblock_singleline() -> None:
class A: class A:
@ -331,18 +244,16 @@ def test_getline_finally() -> None:
def test_getfuncsource_dynamic() -> None: def test_getfuncsource_dynamic() -> None:
source = """ def f():
def f(): raise ValueError
raise ValueError
def g(): pass def g():
""" pass
co = _pytest._code.compile(source)
exec(co, globals()) f_source = _pytest._code.Source(f)
f_source = _pytest._code.Source(f) # type: ignore g_source = _pytest._code.Source(g)
g_source = _pytest._code.Source(g) # type: ignore
assert str(f_source).strip() == "def f():\n raise ValueError" assert str(f_source).strip() == "def f():\n raise ValueError"
assert str(g_source).strip() == "def g(): pass" assert str(g_source).strip() == "def g():\n pass"
def test_getfuncsource_with_multine_string() -> None: def test_getfuncsource_with_multine_string() -> None:
@ -405,23 +316,6 @@ def test_getsource_fallback() -> None:
assert str(src) == expected assert str(src) == expected
def test_idem_compile_and_getsource() -> None:
from _pytest._code.source import getsource
expected = "def x(): pass"
co = _pytest._code.compile(expected)
src = getsource(co)
assert str(src) == expected
def test_compile_ast() -> None:
# We don't necessarily want to support this.
# This test was added just for coverage.
stmt = ast.parse("def x(): pass")
co = _pytest._code.compile(stmt, filename="foo.py")
assert isinstance(co, CodeType)
def test_findsource_fallback() -> None: def test_findsource_fallback() -> None:
from _pytest._code.source import findsource from _pytest._code.source import findsource
@ -431,15 +325,15 @@ def test_findsource_fallback() -> None:
assert src[lineno] == " def x():" assert src[lineno] == " def x():"
def test_findsource() -> None: def test_findsource(monkeypatch) -> None:
from _pytest._code.source import findsource from _pytest._code.source import findsource
co = _pytest._code.compile( filename = "<pytest-test_findsource>"
"""if 1: lines = ["if 1:\n", " def x():\n", " pass\n"]
def x(): co = compile("".join(lines), filename, "exec")
pass
""" # Type ignored because linecache.cache is private.
) monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) # type: ignore[attr-defined]
src, lineno = findsource(co) src, lineno = findsource(co)
assert src is not None assert src is not None