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:
parent
9640c9c9eb
commit
ef39115001
|
@ -7,7 +7,6 @@ from .code import getfslineno
|
|||
from .code import getrawcode
|
||||
from .code import Traceback
|
||||
from .code import TracebackEntry
|
||||
from .source import compile_ as compile
|
||||
from .source import Source
|
||||
|
||||
__all__ = [
|
||||
|
@ -19,6 +18,5 @@ __all__ = [
|
|||
"getrawcode",
|
||||
"Traceback",
|
||||
"TracebackEntry",
|
||||
"compile",
|
||||
"Source",
|
||||
]
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import ast
|
||||
import inspect
|
||||
import linecache
|
||||
import sys
|
||||
import textwrap
|
||||
import tokenize
|
||||
import warnings
|
||||
from bisect import bisect_right
|
||||
from types import CodeType
|
||||
from types import FrameType
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
|
@ -15,13 +11,7 @@ from typing import Optional
|
|||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import py
|
||||
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Literal
|
||||
|
||||
|
||||
class Source:
|
||||
|
@ -30,8 +20,6 @@ class Source:
|
|||
When using Source(...), the source lines are deindented.
|
||||
"""
|
||||
|
||||
_compilecounter = 0
|
||||
|
||||
def __init__(self, obj: object = None) -> None:
|
||||
if not obj:
|
||||
self.lines = [] # type: List[str]
|
||||
|
@ -122,124 +110,6 @@ class Source:
|
|||
def __str__(self) -> str:
|
||||
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
|
||||
|
|
|
@ -127,24 +127,28 @@ class TestTraceback_f_g_h:
|
|||
assert s.endswith("raise ValueError")
|
||||
|
||||
def test_traceback_entry_getsource_in_construct(self):
|
||||
source = _pytest._code.Source(
|
||||
"""\
|
||||
def xyz():
|
||||
try:
|
||||
raise ValueError
|
||||
except somenoname:
|
||||
pass
|
||||
xyz()
|
||||
"""
|
||||
)
|
||||
def xyz():
|
||||
try:
|
||||
raise ValueError
|
||||
except somenoname: # type: ignore[name-defined] # noqa: F821
|
||||
pass
|
||||
|
||||
try:
|
||||
exec(source.compile())
|
||||
xyz()
|
||||
except NameError:
|
||||
tb = _pytest._code.ExceptionInfo.from_current().traceback
|
||||
print(tb[-1].getsource())
|
||||
s = str(tb[-1].getsource())
|
||||
assert s.startswith("def xyz():\n try:")
|
||||
assert s.strip().endswith("except somenoname:")
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
else:
|
||||
assert False, "did not raise NameError"
|
||||
|
||||
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):
|
||||
co = _pytest._code.Code(f)
|
||||
|
@ -445,16 +449,6 @@ class TestFormattedExcinfo:
|
|||
|
||||
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):
|
||||
pr = FormattedExcinfo()
|
||||
source = _pytest._code.Source(
|
||||
|
@ -471,19 +465,29 @@ class TestFormattedExcinfo:
|
|||
|
||||
def test_repr_source_excinfo(self) -> None:
|
||||
""" check if indentation is right """
|
||||
pr = FormattedExcinfo()
|
||||
excinfo = self.excinfo_from_exec(
|
||||
"""
|
||||
def f():
|
||||
assert 0
|
||||
f()
|
||||
"""
|
||||
)
|
||||
try:
|
||||
|
||||
def f():
|
||||
1 / 0
|
||||
|
||||
f()
|
||||
|
||||
except BaseException:
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
else:
|
||||
assert 0, "did not raise"
|
||||
|
||||
pr = FormattedExcinfo()
|
||||
source = pr._getentrysource(excinfo.traceback[-1])
|
||||
assert source is not None
|
||||
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):
|
||||
pr = FormattedExcinfo()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# or redundant on purpose and can't be disable on a line-by-line basis
|
||||
import ast
|
||||
import inspect
|
||||
import linecache
|
||||
import sys
|
||||
import textwrap
|
||||
from types import CodeType
|
||||
|
@ -33,14 +34,6 @@ def test_source_str_function() -> None:
|
|||
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:
|
||||
source = _pytest._code.Source(test_source_str_function)
|
||||
assert str(source).startswith("def test_source_str_function() -> None:")
|
||||
|
@ -83,15 +76,6 @@ def test_source_strip_multiline() -> None:
|
|||
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:
|
||||
def setup_class(self) -> None:
|
||||
self.source = Source(
|
||||
|
@ -124,7 +108,7 @@ class TestAccesses:
|
|||
assert len(values) == 4
|
||||
|
||||
|
||||
class TestSourceParsingAndCompiling:
|
||||
class TestSourceParsing:
|
||||
def setup_class(self) -> None:
|
||||
self.source = Source(
|
||||
"""\
|
||||
|
@ -135,39 +119,6 @@ class TestSourceParsingAndCompiling:
|
|||
"""
|
||||
).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:
|
||||
# print str(self.source)
|
||||
ass = str(self.source[1:])
|
||||
|
@ -264,44 +215,6 @@ class TestSourceParsingAndCompiling:
|
|||
source = Source(":")
|
||||
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:
|
||||
class A:
|
||||
|
@ -331,18 +244,16 @@ def test_getline_finally() -> None:
|
|||
|
||||
|
||||
def test_getfuncsource_dynamic() -> None:
|
||||
source = """
|
||||
def f():
|
||||
raise ValueError
|
||||
def f():
|
||||
raise ValueError
|
||||
|
||||
def g(): pass
|
||||
"""
|
||||
co = _pytest._code.compile(source)
|
||||
exec(co, globals())
|
||||
f_source = _pytest._code.Source(f) # type: ignore
|
||||
g_source = _pytest._code.Source(g) # type: ignore
|
||||
def g():
|
||||
pass
|
||||
|
||||
f_source = _pytest._code.Source(f)
|
||||
g_source = _pytest._code.Source(g)
|
||||
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:
|
||||
|
@ -405,23 +316,6 @@ def test_getsource_fallback() -> None:
|
|||
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:
|
||||
from _pytest._code.source import findsource
|
||||
|
||||
|
@ -431,15 +325,15 @@ def test_findsource_fallback() -> None:
|
|||
assert src[lineno] == " def x():"
|
||||
|
||||
|
||||
def test_findsource() -> None:
|
||||
def test_findsource(monkeypatch) -> None:
|
||||
from _pytest._code.source import findsource
|
||||
|
||||
co = _pytest._code.compile(
|
||||
"""if 1:
|
||||
def x():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
filename = "<pytest-test_findsource>"
|
||||
lines = ["if 1:\n", " def x():\n", " pass\n"]
|
||||
co = compile("".join(lines), filename, "exec")
|
||||
|
||||
# Type ignored because linecache.cache is private.
|
||||
monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) # type: ignore[attr-defined]
|
||||
|
||||
src, lineno = findsource(co)
|
||||
assert src is not None
|
||||
|
|
Loading…
Reference in New Issue