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 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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue