Add type annotations to _pytest._code.code

This commit is contained in:
Ran Benita
2019-11-15 23:02:55 +02:00
parent 562d4811d5
commit eaa34a9df0
4 changed files with 299 additions and 214 deletions

View File

@@ -4,13 +4,16 @@
import ast
import inspect
import sys
from typing import Any
from typing import Dict
from typing import Optional
import _pytest._code
import pytest
from _pytest._code import Source
def test_source_str_function():
def test_source_str_function() -> None:
x = Source("3")
assert str(x) == "3"
@@ -25,7 +28,7 @@ def test_source_str_function():
assert str(x) == "\n3"
def test_unicode():
def test_unicode() -> None:
x = Source("4")
assert str(x) == "4"
co = _pytest._code.compile('"å"', mode="eval")
@@ -33,12 +36,12 @@ def test_unicode():
assert isinstance(val, str)
def test_source_from_function():
def test_source_from_function() -> None:
source = _pytest._code.Source(test_source_str_function)
assert str(source).startswith("def test_source_str_function():")
assert str(source).startswith("def test_source_str_function() -> None:")
def test_source_from_method():
def test_source_from_method() -> None:
class TestClass:
def test_method(self):
pass
@@ -47,13 +50,13 @@ def test_source_from_method():
assert source.lines == ["def test_method(self):", " pass"]
def test_source_from_lines():
def test_source_from_lines() -> None:
lines = ["a \n", "b\n", "c"]
source = _pytest._code.Source(lines)
assert source.lines == ["a ", "b", "c"]
def test_source_from_inner_function():
def test_source_from_inner_function() -> None:
def f():
pass
@@ -63,7 +66,7 @@ def test_source_from_inner_function():
assert str(source).startswith("def f():")
def test_source_putaround_simple():
def test_source_putaround_simple() -> None:
source = Source("raise ValueError")
source = source.putaround(
"try:",
@@ -85,7 +88,7 @@ else:
)
def test_source_putaround():
def test_source_putaround() -> None:
source = Source()
source = source.putaround(
"""
@@ -96,28 +99,29 @@ def test_source_putaround():
assert str(source).strip() == "if 1:\n x=1"
def test_source_strips():
def test_source_strips() -> None:
source = Source("")
assert source == Source()
assert str(source) == ""
assert source.strip() == source
def test_source_strip_multiline():
def test_source_strip_multiline() -> None:
source = Source()
source.lines = ["", " hello", " "]
source2 = source.strip()
assert source2.lines == [" hello"]
def test_syntaxerror_rerepresentation():
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.strip(), "x x"
assert ex.value.text == "xyz xyz\n"
def test_isparseable():
def test_isparseable() -> None:
assert Source("hello").isparseable()
assert Source("if 1:\n pass").isparseable()
assert Source(" \nif 1:\n pass").isparseable()
@@ -127,7 +131,7 @@ def test_isparseable():
class TestAccesses:
def setup_class(self):
def setup_class(self) -> None:
self.source = Source(
"""\
def f(x):
@@ -137,26 +141,26 @@ class TestAccesses:
"""
)
def test_getrange(self):
def test_getrange(self) -> None:
x = self.source[0:2]
assert x.isparseable()
assert len(x.lines) == 2
assert str(x) == "def f(x):\n pass"
def test_getline(self):
def test_getline(self) -> None:
x = self.source[0]
assert x == "def f(x):"
def test_len(self):
def test_len(self) -> None:
assert len(self.source) == 4
def test_iter(self):
def test_iter(self) -> None:
values = [x for x in self.source]
assert len(values) == 4
class TestSourceParsingAndCompiling:
def setup_class(self):
def setup_class(self) -> None:
self.source = Source(
"""\
def f(x):
@@ -166,19 +170,19 @@ class TestSourceParsingAndCompiling:
"""
).strip()
def test_compile(self):
def test_compile(self) -> None:
co = _pytest._code.compile("x=3")
d = {}
d = {} # type: Dict[str, Any]
exec(co, d)
assert d["x"] == 3
def test_compile_and_getsource_simple(self):
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):
def test_compile_and_getsource_through_same_function(self) -> None:
def gensource(source):
return _pytest._code.compile(source)
@@ -199,7 +203,7 @@ class TestSourceParsingAndCompiling:
source2 = inspect.getsource(co2)
assert "ValueError" in source2
def test_getstatement(self):
def test_getstatement(self) -> None:
# print str(self.source)
ass = str(self.source[1:])
for i in range(1, 4):
@@ -208,7 +212,7 @@ class TestSourceParsingAndCompiling:
# x = s.deindent()
assert str(s) == ass
def test_getstatementrange_triple_quoted(self):
def test_getstatementrange_triple_quoted(self) -> None:
# print str(self.source)
source = Source(
"""hello('''
@@ -219,7 +223,7 @@ class TestSourceParsingAndCompiling:
s = source.getstatement(1)
assert s == str(source)
def test_getstatementrange_within_constructs(self):
def test_getstatementrange_within_constructs(self) -> None:
source = Source(
"""\
try:
@@ -241,7 +245,7 @@ class TestSourceParsingAndCompiling:
# assert source.getstatementrange(5) == (0, 7)
assert source.getstatementrange(6) == (6, 7)
def test_getstatementrange_bug(self):
def test_getstatementrange_bug(self) -> None:
source = Source(
"""\
try:
@@ -255,7 +259,7 @@ class TestSourceParsingAndCompiling:
assert len(source) == 6
assert source.getstatementrange(2) == (1, 4)
def test_getstatementrange_bug2(self):
def test_getstatementrange_bug2(self) -> None:
source = Source(
"""\
assert (
@@ -272,7 +276,7 @@ class TestSourceParsingAndCompiling:
assert len(source) == 9
assert source.getstatementrange(5) == (0, 9)
def test_getstatementrange_ast_issue58(self):
def test_getstatementrange_ast_issue58(self) -> None:
source = Source(
"""\
@@ -286,38 +290,44 @@ class TestSourceParsingAndCompiling:
assert getstatement(2, source).lines == source.lines[2:3]
assert getstatement(3, source).lines == source.lines[3:4]
def test_getstatementrange_out_of_bounds_py3(self):
def test_getstatementrange_out_of_bounds_py3(self) -> None:
source = Source("if xxx:\n from .collections import something")
r = source.getstatementrange(1)
assert r == (1, 2)
def test_getstatementrange_with_syntaxerror_issue7(self):
def test_getstatementrange_with_syntaxerror_issue7(self) -> None:
source = Source(":")
pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
def test_compile_to_ast(self):
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):
def test_compile_and_getsource(self) -> None:
co = self.source.compile()
exec(co, globals())
f(7)
excinfo = pytest.raises(AssertionError, f, 6)
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):
def test_compilefuncs_and_path_sanity(self, name: Optional[str]) -> None:
def check(comp, name):
co = comp(self.source, name)
if not name:
expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2)
expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2) # type: ignore
else:
expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2)
expected = "codegen %r %s:%d>" % (
name,
mypath, # type: ignore
mylineno + 2 + 2, # type: ignore
) # type: ignore
fn = co.co_filename
assert fn.endswith(expected)
@@ -332,9 +342,9 @@ class TestSourceParsingAndCompiling:
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode="eval")
def test_getstartingblock_singleline():
def test_getstartingblock_singleline() -> None:
class A:
def __init__(self, *args):
def __init__(self, *args) -> None:
frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement
@@ -344,22 +354,22 @@ def test_getstartingblock_singleline():
assert len(values) == 1
def test_getline_finally():
def c():
def test_getline_finally() -> None:
def c() -> None:
pass
with pytest.raises(TypeError) as excinfo:
teardown = None
try:
c(1)
c(1) # type: ignore
finally:
if teardown:
teardown()
source = excinfo.traceback[-1].statement
assert str(source).strip() == "c(1)"
assert str(source).strip() == "c(1) # type: ignore"
def test_getfuncsource_dynamic():
def test_getfuncsource_dynamic() -> None:
source = """
def f():
raise ValueError
@@ -368,11 +378,13 @@ def test_getfuncsource_dynamic():
"""
co = _pytest._code.compile(source)
exec(co, globals())
assert str(_pytest._code.Source(f)).strip() == "def f():\n raise ValueError"
assert str(_pytest._code.Source(g)).strip() == "def g(): pass"
f_source = _pytest._code.Source(f) # type: ignore
g_source = _pytest._code.Source(g) # type: ignore
assert str(f_source).strip() == "def f():\n raise ValueError"
assert str(g_source).strip() == "def g(): pass"
def test_getfuncsource_with_multine_string():
def test_getfuncsource_with_multine_string() -> None:
def f():
c = """while True:
pass
@@ -387,7 +399,7 @@ def test_getfuncsource_with_multine_string():
assert str(_pytest._code.Source(f)) == expected.rstrip()
def test_deindent():
def test_deindent() -> None:
from _pytest._code.source import deindent as deindent
assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"]
@@ -401,7 +413,7 @@ def test_deindent():
assert lines == ["def f():", " def g():", " pass"]
def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot):
def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot) -> None:
# this test fails because the implicit inspect.getsource(A) below
# does not return the "x = 1" last line.
source = _pytest._code.Source(
@@ -423,7 +435,7 @@ if True:
pass
def test_getsource_fallback():
def test_getsource_fallback() -> None:
from _pytest._code.source import getsource
expected = """def x():
@@ -432,7 +444,7 @@ def test_getsource_fallback():
assert src == expected
def test_idem_compile_and_getsource():
def test_idem_compile_and_getsource() -> None:
from _pytest._code.source import getsource
expected = "def x(): pass"
@@ -441,15 +453,16 @@ def test_idem_compile_and_getsource():
assert src == expected
def test_findsource_fallback():
def test_findsource_fallback() -> None:
from _pytest._code.source import findsource
src, lineno = findsource(x)
assert src is not None
assert "test_findsource_simple" in str(src)
assert src[lineno] == " def x():"
def test_findsource():
def test_findsource() -> None:
from _pytest._code.source import findsource
co = _pytest._code.compile(
@@ -460,19 +473,21 @@ def test_findsource():
)
src, lineno = findsource(co)
assert src is not None
assert "if 1:" in str(src)
d = {}
d = {} # type: Dict[str, Any]
eval(co, d)
src, lineno = findsource(d["x"])
assert src is not None
assert "if 1:" in str(src)
assert src[lineno] == " def x():"
def test_getfslineno():
def test_getfslineno() -> None:
from _pytest._code import getfslineno
def f(x):
def f(x) -> None:
pass
fspath, lineno = getfslineno(f)
@@ -498,40 +513,40 @@ def test_getfslineno():
assert getfslineno(B)[1] == -1
def test_code_of_object_instance_with_call():
def test_code_of_object_instance_with_call() -> None:
class A:
pass
pytest.raises(TypeError, lambda: _pytest._code.Source(A()))
class WithCall:
def __call__(self):
def __call__(self) -> None:
pass
code = _pytest._code.Code(WithCall())
assert "pass" in str(code.source())
class Hello:
def __call__(self):
def __call__(self) -> None:
pass
pytest.raises(TypeError, lambda: _pytest._code.Code(Hello))
def getstatement(lineno, source):
def getstatement(lineno: int, source) -> Source:
from _pytest._code.source import getstatementrange_ast
source = _pytest._code.Source(source, deindent=False)
ast, start, end = getstatementrange_ast(lineno, source)
return source[start:end]
src = _pytest._code.Source(source, deindent=False)
ast, start, end = getstatementrange_ast(lineno, src)
return src[start:end]
def test_oneline():
def test_oneline() -> None:
source = getstatement(0, "raise ValueError")
assert str(source) == "raise ValueError"
def test_comment_and_no_newline_at_end():
def test_comment_and_no_newline_at_end() -> None:
from _pytest._code.source import getstatementrange_ast
source = Source(
@@ -545,12 +560,12 @@ def test_comment_and_no_newline_at_end():
assert end == 2
def test_oneline_and_comment():
def test_oneline_and_comment() -> None:
source = getstatement(0, "raise ValueError\n#hello")
assert str(source) == "raise ValueError"
def test_comments():
def test_comments() -> None:
source = '''def test():
"comment 1"
x = 1
@@ -576,7 +591,7 @@ comment 4
assert str(getstatement(line, source)) == '"""\ncomment 4\n"""'
def test_comment_in_statement():
def test_comment_in_statement() -> None:
source = """test(foo=1,
# comment 1
bar=2)
@@ -588,17 +603,17 @@ def test_comment_in_statement():
)
def test_single_line_else():
def test_single_line_else() -> None:
source = getstatement(1, "if False: 2\nelse: 3")
assert str(source) == "else: 3"
def test_single_line_finally():
def test_single_line_finally() -> None:
source = getstatement(1, "try: 1\nfinally: 3")
assert str(source) == "finally: 3"
def test_issue55():
def test_issue55() -> None:
source = (
"def round_trip(dinp):\n assert 1 == dinp\n"
'def test_rt():\n round_trip("""\n""")\n'
@@ -607,7 +622,7 @@ def test_issue55():
assert str(s) == ' round_trip("""\n""")'
def test_multiline():
def test_multiline() -> None:
source = getstatement(
0,
"""\
@@ -621,7 +636,7 @@ x = 3
class TestTry:
def setup_class(self):
def setup_class(self) -> None:
self.source = """\
try:
raise ValueError
@@ -631,25 +646,25 @@ else:
raise KeyError()
"""
def test_body(self):
def test_body(self) -> None:
source = getstatement(1, self.source)
assert str(source) == " raise ValueError"
def test_except_line(self):
def test_except_line(self) -> None:
source = getstatement(2, self.source)
assert str(source) == "except Something:"
def test_except_body(self):
def test_except_body(self) -> None:
source = getstatement(3, self.source)
assert str(source) == " raise IndexError(1)"
def test_else(self):
def test_else(self) -> None:
source = getstatement(5, self.source)
assert str(source) == " raise KeyError()"
class TestTryFinally:
def setup_class(self):
def setup_class(self) -> None:
self.source = """\
try:
raise ValueError
@@ -657,17 +672,17 @@ finally:
raise IndexError(1)
"""
def test_body(self):
def test_body(self) -> None:
source = getstatement(1, self.source)
assert str(source) == " raise ValueError"
def test_finally(self):
def test_finally(self) -> None:
source = getstatement(3, self.source)
assert str(source) == " raise IndexError(1)"
class TestIf:
def setup_class(self):
def setup_class(self) -> None:
self.source = """\
if 1:
y = 3
@@ -677,24 +692,24 @@ else:
y = 7
"""
def test_body(self):
def test_body(self) -> None:
source = getstatement(1, self.source)
assert str(source) == " y = 3"
def test_elif_clause(self):
def test_elif_clause(self) -> None:
source = getstatement(2, self.source)
assert str(source) == "elif False:"
def test_elif(self):
def test_elif(self) -> None:
source = getstatement(3, self.source)
assert str(source) == " y = 5"
def test_else(self):
def test_else(self) -> None:
source = getstatement(5, self.source)
assert str(source) == " y = 7"
def test_semicolon():
def test_semicolon() -> None:
s = """\
hello ; pytest.skip()
"""
@@ -702,7 +717,7 @@ hello ; pytest.skip()
assert str(source) == s.strip()
def test_def_online():
def test_def_online() -> None:
s = """\
def func(): raise ValueError(42)
@@ -713,7 +728,7 @@ def something():
assert str(source) == "def func(): raise ValueError(42)"
def XXX_test_expression_multiline():
def XXX_test_expression_multiline() -> None:
source = """\
something
'''
@@ -722,7 +737,7 @@ something
assert str(result) == "'''\n'''"
def test_getstartingblock_multiline():
def test_getstartingblock_multiline() -> None:
class A:
def __init__(self, *args):
frame = sys._getframe(1)