test_oinspect.py
382 lines
| 10.6 KiB
| text/x-python
|
PythonLexer
|
r3051 | """Tests for the object inspection functionality. | ||
""" | ||||
#----------------------------------------------------------------------------- | ||||
|
r5390 | # Copyright (C) 2010-2011 The IPython Development Team. | ||
|
r3051 | # | ||
# Distributed under the terms of the BSD License. | ||||
# | ||||
# The full license is in the file COPYING.txt, distributed with this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
from __future__ import print_function | ||||
# Stdlib imports | ||||
|
r7290 | import os | ||
|
r7434 | import re | ||
|
r3051 | |||
# Third-party imports | ||||
import nose.tools as nt | ||||
# Our own imports | ||||
from .. import oinspect | ||||
|
r6995 | from IPython.core.magic import (Magics, magics_class, line_magic, | ||
cell_magic, line_cell_magic, | ||||
register_line_magic, register_cell_magic, | ||||
register_line_cell_magic) | ||||
|
r20813 | from decorator import decorator | ||
|
r15335 | from IPython.testing.decorators import skipif | ||
|
r20699 | from IPython.testing.tools import AssertPrints | ||
|
r20578 | from IPython.utils.path import compress_user | ||
|
r4760 | from IPython.utils import py3compat | ||
|
r3051 | |||
|
r7432 | |||
|
r3051 | #----------------------------------------------------------------------------- | ||
# Globals and constants | ||||
#----------------------------------------------------------------------------- | ||||
inspector = oinspect.Inspector() | ||||
|
r6995 | ip = get_ipython() | ||
|
r3051 | |||
#----------------------------------------------------------------------------- | ||||
# Local utilities | ||||
#----------------------------------------------------------------------------- | ||||
|
r7290 | # WARNING: since this test checks the line number where a function is | ||
# defined, if any code is inserted above, the following line will need to be | ||||
# updated. Do NOT insert any whitespace between the next line and the function | ||||
# definition below. | ||||
|
r20699 | THIS_LINE_NUMBER = 51 # Put here the actual number of this line | ||
|
r7290 | def test_find_source_lines(): | ||
nt.assert_equal(oinspect.find_source_lines(test_find_source_lines), | ||||
THIS_LINE_NUMBER+1) | ||||
|
r7434 | # A couple of utilities to ensure these tests work the same from a source or a | ||
# binary install | ||||
def pyfile(fname): | ||||
|
r7452 | return os.path.normcase(re.sub('.py[co]$', '.py', fname)) | ||
|
r7434 | |||
def match_pyfiles(f1, f2): | ||||
|
r7452 | nt.assert_equal(pyfile(f1), pyfile(f2)) | ||
|
r7434 | |||
|
r7290 | def test_find_file(): | ||
|
r7434 | match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) | ||
|
r7290 | |||
|
r7432 | def test_find_file_decorated1(): | ||
@decorator | ||||
def noop1(f): | ||||
def wrapper(): | ||||
return f(*a, **kw) | ||||
return wrapper | ||||
@noop1 | ||||
def f(x): | ||||
"My docstring" | ||||
|
r7434 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) | ||
|
r7432 | nt.assert_equal(f.__doc__, "My docstring") | ||
def test_find_file_decorated2(): | ||||
@decorator | ||||
def noop2(f, *a, **kw): | ||||
return f(*a, **kw) | ||||
@noop2 | ||||
def f(x): | ||||
"My docstring 2" | ||||
|
r7434 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) | ||
|
r7432 | nt.assert_equal(f.__doc__, "My docstring 2") | ||
|
r7290 | def test_find_file_magic(): | ||
run = ip.find_line_magic('run') | ||||
nt.assert_not_equal(oinspect.find_file(run), None) | ||||
|
r3051 | # A few generic objects we can then inspect in the tests below | ||
class Call(object): | ||||
"""This is the class docstring.""" | ||||
def __init__(self, x, y=1): | ||||
"""This is the constructor docstring.""" | ||||
def __call__(self, *a, **kw): | ||||
"""This is the call docstring.""" | ||||
def method(self, x, z=2): | ||||
"""Some method's docstring""" | ||||
|
r4872 | |||
|
r15362 | class SimpleClass(object): | ||
def method(self, x, z=2): | ||||
"""Some method's docstring""" | ||||
|
r6995 | |||
|
r3867 | class OldStyle: | ||
"""An old-style class for testing.""" | ||||
pass | ||||
|
r3051 | |||
|
r6995 | |||
|
r3051 | def f(x, y=2, *a, **kw): | ||
"""A simple function.""" | ||||
|
r6995 | |||
|
r3051 | def g(y, z=3, *a, **kw): | ||
pass # no docstring | ||||
|
r6995 | @register_line_magic | ||
def lmagic(line): | ||||
"A line magic" | ||||
@register_cell_magic | ||||
def cmagic(line, cell): | ||||
"A cell magic" | ||||
@register_line_cell_magic | ||||
def lcmagic(line, cell=None): | ||||
"A line/cell magic" | ||||
@magics_class | ||||
class SimpleMagics(Magics): | ||||
@line_magic | ||||
def Clmagic(self, cline): | ||||
"A class-based line magic" | ||||
@cell_magic | ||||
def Ccmagic(self, cline, ccell): | ||||
"A class-based cell magic" | ||||
@line_cell_magic | ||||
def Clcmagic(self, cline, ccell=None): | ||||
"A class-based line/cell magic" | ||||
|
r11058 | class Awkward(object): | ||
def __getattr__(self, name): | ||||
raise Exception(name) | ||||
|
r3051 | def check_calltip(obj, name, call, docstring): | ||
"""Generic check pattern all calltip tests will use""" | ||||
info = inspector.info(obj, name) | ||||
call_line, ds = oinspect.call_tip(info) | ||||
nt.assert_equal(call_line, call) | ||||
|
r4872 | nt.assert_equal(ds, docstring) | ||
|
r3051 | |||
#----------------------------------------------------------------------------- | ||||
# Tests | ||||
#----------------------------------------------------------------------------- | ||||
def test_calltip_class(): | ||||
check_calltip(Call, 'Call', 'Call(x, y=1)', Call.__init__.__doc__) | ||||
def test_calltip_instance(): | ||||
c = Call(1) | ||||
check_calltip(c, 'c', 'c(*a, **kw)', c.__call__.__doc__) | ||||
def test_calltip_method(): | ||||
c = Call(1) | ||||
check_calltip(c.method, 'c.method', 'c.method(x, z=2)', c.method.__doc__) | ||||
def test_calltip_function(): | ||||
check_calltip(f, 'f', 'f(x, y=2, *a, **kw)', f.__doc__) | ||||
def test_calltip_function2(): | ||||
check_calltip(g, 'g', 'g(y, z=3, *a, **kw)', '<no docstring>') | ||||
def test_calltip_builtin(): | ||||
check_calltip(sum, 'sum', None, sum.__doc__) | ||||
|
r4872 | |||
|
r6995 | |||
def test_calltip_line_magic(): | ||||
check_calltip(lmagic, 'lmagic', 'lmagic(line)', "A line magic") | ||||
def test_calltip_cell_magic(): | ||||
check_calltip(cmagic, 'cmagic', 'cmagic(line, cell)', "A cell magic") | ||||
|
r11124 | def test_calltip_line_cell_magic(): | ||
|
r6995 | check_calltip(lcmagic, 'lcmagic', 'lcmagic(line, cell=None)', | ||
"A line/cell magic") | ||||
def test_class_magics(): | ||||
cm = SimpleMagics(ip) | ||||
ip.register_magics(cm) | ||||
check_calltip(cm.Clmagic, 'Clmagic', 'Clmagic(cline)', | ||||
"A class-based line magic") | ||||
check_calltip(cm.Ccmagic, 'Ccmagic', 'Ccmagic(cline, ccell)', | ||||
"A class-based cell magic") | ||||
check_calltip(cm.Clcmagic, 'Clcmagic', 'Clcmagic(cline, ccell=None)', | ||||
"A class-based line/cell magic") | ||||
|
r3859 | def test_info(): | ||
"Check that Inspector.info fills out various fields as expected." | ||||
i = inspector.info(Call, oname='Call') | ||||
nt.assert_equal(i['type_name'], 'type') | ||||
|
r4760 | expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'> | ||
nt.assert_equal(i['base_class'], expted_class) | ||||
|
r3859 | nt.assert_equal(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'>") | ||
fname = __file__ | ||||
if fname.endswith(".pyc"): | ||||
fname = fname[:-1] | ||||
|
r4105 | # case-insensitive comparison needed on some filesystems | ||
# e.g. Windows: | ||||
|
r20826 | nt.assert_equal(i['file'].lower(), compress_user(fname).lower()) | ||
|
r15711 | nt.assert_equal(i['definition'], None) | ||
|
r3859 | nt.assert_equal(i['docstring'], Call.__doc__) | ||
|
r3869 | nt.assert_equal(i['source'], None) | ||
|
r3859 | nt.assert_true(i['isclass']) | ||
nt.assert_equal(i['init_definition'], "Call(self, x, y=1)\n") | ||||
nt.assert_equal(i['init_docstring'], Call.__init__.__doc__) | ||||
|
r4872 | |||
|
r3859 | i = inspector.info(Call, detail_level=1) | ||
|
r3869 | nt.assert_not_equal(i['source'], None) | ||
nt.assert_equal(i['docstring'], None) | ||||
|
r4872 | |||
|
r3859 | c = Call(1) | ||
c.__doc__ = "Modified instance docstring" | ||||
i = inspector.info(c) | ||||
nt.assert_equal(i['type_name'], 'Call') | ||||
nt.assert_equal(i['docstring'], "Modified instance docstring") | ||||
nt.assert_equal(i['class_docstring'], Call.__doc__) | ||||
nt.assert_equal(i['init_docstring'], Call.__init__.__doc__) | ||||
|
r15711 | nt.assert_equal(i['call_docstring'], Call.__call__.__doc__) | ||
|
r4872 | |||
|
r3867 | # Test old-style classes, which for example may not have an __init__ method. | ||
|
r4760 | if not py3compat.PY3: | ||
i = inspector.info(OldStyle) | ||||
nt.assert_equal(i['type_name'], 'classobj') | ||||
|
r4872 | |||
|
r4760 | i = inspector.info(OldStyle()) | ||
nt.assert_equal(i['type_name'], 'instance') | ||||
nt.assert_equal(i['docstring'], OldStyle.__doc__) | ||||
|
r5538 | |||
|
r11058 | def test_info_awkward(): | ||
# Just test that this doesn't throw an error. | ||||
i = inspector.info(Awkward()) | ||||
|
r15362 | def test_calldef_none(): | ||
# We should ignore __call__ for all of these. | ||||
for obj in [f, SimpleClass().method, any, str.upper]: | ||||
print(obj) | ||||
i = inspector.info(obj) | ||||
nt.assert_is(i['call_def'], None) | ||||
|
r15335 | if py3compat.PY3: | ||
exec("def f_kwarg(pos, *, kwonly): pass") | ||||
@skipif(not py3compat.PY3) | ||||
def test_definition_kwonlyargs(): | ||||
i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore | ||||
nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)\n") | ||||
|
r5538 | def test_getdoc(): | ||
class A(object): | ||||
"""standard docstring""" | ||||
pass | ||||
class B(object): | ||||
"""standard docstring""" | ||||
def getdoc(self): | ||||
return "custom docstring" | ||||
|
r5573 | class C(object): | ||
"""standard docstring""" | ||||
def getdoc(self): | ||||
return None | ||||
|
r5538 | a = A() | ||
b = B() | ||||
|
r5573 | c = C() | ||
|
r5538 | |||
nt.assert_equal(oinspect.getdoc(a), "standard docstring") | ||||
nt.assert_equal(oinspect.getdoc(b), "custom docstring") | ||||
|
r5573 | nt.assert_equal(oinspect.getdoc(c), "standard docstring") | ||
|
r7460 | |||
|
r17023 | |||
def test_empty_property_has_no_source(): | ||||
i = inspector.info(property(), detail_level=1) | ||||
nt.assert_is(i['source'], None) | ||||
def test_property_sources(): | ||||
import zlib | ||||
class A(object): | ||||
@property | ||||
def foo(self): | ||||
return 'bar' | ||||
foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) | ||||
id = property(id) | ||||
compress = property(zlib.compress) | ||||
i = inspector.info(A.foo, detail_level=1) | ||||
nt.assert_in('def foo(self):', i['source']) | ||||
nt.assert_in('lambda self, v:', i['source']) | ||||
i = inspector.info(A.id, detail_level=1) | ||||
nt.assert_in('fget = <function id>', i['source']) | ||||
i = inspector.info(A.compress, detail_level=1) | ||||
nt.assert_in('fget = <function zlib.compress>', i['source']) | ||||
def test_property_docstring_is_in_info_for_detail_level_0(): | ||||
class A(object): | ||||
@property | ||||
def foobar(): | ||||
"""This is `foobar` property.""" | ||||
pass | ||||
ip.user_ns['a_obj'] = A() | ||||
nt.assert_equals( | ||||
'This is `foobar` property.', | ||||
ip.object_inspect('a_obj.foobar', detail_level=0)['docstring']) | ||||
ip.user_ns['a_cls'] = A | ||||
nt.assert_equals( | ||||
'This is `foobar` property.', | ||||
ip.object_inspect('a_cls.foobar', detail_level=0)['docstring']) | ||||
|
r7460 | def test_pdef(): | ||
# See gh-1914 | ||||
def foo(): pass | ||||
inspector.pdef(foo, 'foo') | ||||
|
r11067 | |||
def test_pinfo_nonascii(): | ||||
# See gh-1177 | ||||
from . import nonascii2 | ||||
ip.user_ns['nonascii2'] = nonascii2 | ||||
ip._inspect('pinfo', 'nonascii2', detail_level=1) | ||||
|
r20699 | |||
def test_pinfo_magic(): | ||||
with AssertPrints('Docstring:'): | ||||
ip._inspect('pinfo', 'lsmagic', detail_level=0) | ||||
with AssertPrints('Source:'): | ||||
ip._inspect('pinfo', 'lsmagic', detail_level=1) | ||||