##// END OF EJS Templates
Add test for ?? to show docstring if no source code available #10534
ryan thielke -
Show More
@@ -1,409 +1,434 b''
1 1 """Tests for the object inspection functionality.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from inspect import Signature, Parameter
9 9 import os
10 10 import re
11 11 import sys
12 12
13 13 import nose.tools as nt
14 14
15 15 from .. import oinspect
16 16 from IPython.core.magic import (Magics, magics_class, line_magic,
17 17 cell_magic, line_cell_magic,
18 18 register_line_magic, register_cell_magic,
19 19 register_line_cell_magic)
20 20 from decorator import decorator
21 21 from IPython import get_ipython
22 22 from IPython.testing.decorators import skipif
23 23 from IPython.testing.tools import AssertPrints, AssertNotPrints
24 24 from IPython.utils.path import compress_user
25 25 from IPython.utils import py3compat
26 26
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Globals and constants
30 30 #-----------------------------------------------------------------------------
31 31
32 32 inspector = oinspect.Inspector()
33 33 ip = get_ipython()
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Local utilities
37 37 #-----------------------------------------------------------------------------
38 38
39 39 # WARNING: since this test checks the line number where a function is
40 40 # defined, if any code is inserted above, the following line will need to be
41 41 # updated. Do NOT insert any whitespace between the next line and the function
42 42 # definition below.
43 43 THIS_LINE_NUMBER = 43 # Put here the actual number of this line
44 44
45 45 from unittest import TestCase
46 46
47 47 class Test(TestCase):
48 48
49 49 def test_find_source_lines(self):
50 50 self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines),
51 51 THIS_LINE_NUMBER+6)
52 52
53 53
54 54 # A couple of utilities to ensure these tests work the same from a source or a
55 55 # binary install
56 56 def pyfile(fname):
57 57 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
58 58
59 59
60 60 def match_pyfiles(f1, f2):
61 61 nt.assert_equal(pyfile(f1), pyfile(f2))
62 62
63 63
64 64 def test_find_file():
65 65 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
66 66
67 67
68 68 def test_find_file_decorated1():
69 69
70 70 @decorator
71 71 def noop1(f):
72 72 def wrapper(*a, **kw):
73 73 return f(*a, **kw)
74 74 return wrapper
75 75
76 76 @noop1
77 77 def f(x):
78 78 "My docstring"
79 79
80 80 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
81 81 nt.assert_equal(f.__doc__, "My docstring")
82 82
83 83
84 84 def test_find_file_decorated2():
85 85
86 86 @decorator
87 87 def noop2(f, *a, **kw):
88 88 return f(*a, **kw)
89 89
90 90 @noop2
91 91 @noop2
92 92 @noop2
93 93 def f(x):
94 94 "My docstring 2"
95 95
96 96 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
97 97 nt.assert_equal(f.__doc__, "My docstring 2")
98 98
99 99
100 100 def test_find_file_magic():
101 101 run = ip.find_line_magic('run')
102 102 nt.assert_not_equal(oinspect.find_file(run), None)
103 103
104 104
105 105 # A few generic objects we can then inspect in the tests below
106 106
107 107 class Call(object):
108 108 """This is the class docstring."""
109 109
110 110 def __init__(self, x, y=1):
111 111 """This is the constructor docstring."""
112 112
113 113 def __call__(self, *a, **kw):
114 114 """This is the call docstring."""
115 115
116 116 def method(self, x, z=2):
117 117 """Some method's docstring"""
118 118
119 119 class HasSignature(object):
120 120 """This is the class docstring."""
121 121 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
122 122
123 123 def __init__(self, *args):
124 124 """This is the init docstring"""
125 125
126 126
127 127 class SimpleClass(object):
128 128 def method(self, x, z=2):
129 129 """Some method's docstring"""
130 130
131 131
132 132 class OldStyle:
133 133 """An old-style class for testing."""
134 134 pass
135 135
136 136
137 137 def f(x, y=2, *a, **kw):
138 138 """A simple function."""
139 139
140 140
141 141 def g(y, z=3, *a, **kw):
142 142 pass # no docstring
143 143
144 144
145 145 @register_line_magic
146 146 def lmagic(line):
147 147 "A line magic"
148 148
149 149
150 150 @register_cell_magic
151 151 def cmagic(line, cell):
152 152 "A cell magic"
153 153
154 154
155 155 @register_line_cell_magic
156 156 def lcmagic(line, cell=None):
157 157 "A line/cell magic"
158 158
159 159
160 160 @magics_class
161 161 class SimpleMagics(Magics):
162 162 @line_magic
163 163 def Clmagic(self, cline):
164 164 "A class-based line magic"
165 165
166 166 @cell_magic
167 167 def Ccmagic(self, cline, ccell):
168 168 "A class-based cell magic"
169 169
170 170 @line_cell_magic
171 171 def Clcmagic(self, cline, ccell=None):
172 172 "A class-based line/cell magic"
173 173
174 174
175 175 class Awkward(object):
176 176 def __getattr__(self, name):
177 177 raise Exception(name)
178 178
179 179 class NoBoolCall:
180 180 """
181 181 callable with `__bool__` raising should still be inspect-able.
182 182 """
183 183
184 184 def __call__(self):
185 185 """does nothing"""
186 186 pass
187 187
188 188 def __bool__(self):
189 189 """just raise NotImplemented"""
190 190 raise NotImplementedError('Must be implemented')
191 191
192 192
193 193 class SerialLiar(object):
194 194 """Attribute accesses always get another copy of the same class.
195 195
196 196 unittest.mock.call does something similar, but it's not ideal for testing
197 197 as the failure mode is to eat all your RAM. This gives up after 10k levels.
198 198 """
199 199 def __init__(self, max_fibbing_twig, lies_told=0):
200 200 if lies_told > 10000:
201 201 raise RuntimeError('Nose too long, honesty is the best policy')
202 202 self.max_fibbing_twig = max_fibbing_twig
203 203 self.lies_told = lies_told
204 204 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
205 205
206 206 def __getattr__(self, item):
207 207 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
208 208
209 209 #-----------------------------------------------------------------------------
210 210 # Tests
211 211 #-----------------------------------------------------------------------------
212 212
213 213 def test_info():
214 214 "Check that Inspector.info fills out various fields as expected."
215 215 i = inspector.info(Call, oname='Call')
216 216 nt.assert_equal(i['type_name'], 'type')
217 217 expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
218 218 nt.assert_equal(i['base_class'], expted_class)
219 219 nt.assert_regex(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>")
220 220 fname = __file__
221 221 if fname.endswith(".pyc"):
222 222 fname = fname[:-1]
223 223 # case-insensitive comparison needed on some filesystems
224 224 # e.g. Windows:
225 225 nt.assert_equal(i['file'].lower(), compress_user(fname).lower())
226 226 nt.assert_equal(i['definition'], None)
227 227 nt.assert_equal(i['docstring'], Call.__doc__)
228 228 nt.assert_equal(i['source'], None)
229 229 nt.assert_true(i['isclass'])
230 230 nt.assert_equal(i['init_definition'], "Call(x, y=1)")
231 231 nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
232 232
233 233 i = inspector.info(Call, detail_level=1)
234 234 nt.assert_not_equal(i['source'], None)
235 235 nt.assert_equal(i['docstring'], None)
236 236
237 237 c = Call(1)
238 238 c.__doc__ = "Modified instance docstring"
239 239 i = inspector.info(c)
240 240 nt.assert_equal(i['type_name'], 'Call')
241 241 nt.assert_equal(i['docstring'], "Modified instance docstring")
242 242 nt.assert_equal(i['class_docstring'], Call.__doc__)
243 243 nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
244 244 nt.assert_equal(i['call_docstring'], Call.__call__.__doc__)
245 245
246 246 def test_class_signature():
247 247 info = inspector.info(HasSignature, 'HasSignature')
248 248 nt.assert_equal(info['init_definition'], "HasSignature(test)")
249 249 nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__)
250 250
251 251 def test_info_awkward():
252 252 # Just test that this doesn't throw an error.
253 253 inspector.info(Awkward())
254 254
255 255 def test_bool_raise():
256 256 inspector.info(NoBoolCall())
257 257
258 258 def test_info_serialliar():
259 259 fib_tracker = [0]
260 260 inspector.info(SerialLiar(fib_tracker))
261 261
262 262 # Nested attribute access should be cut off at 100 levels deep to avoid
263 263 # infinite loops: https://github.com/ipython/ipython/issues/9122
264 264 nt.assert_less(fib_tracker[0], 9000)
265 265
266 266 def test_calldef_none():
267 267 # We should ignore __call__ for all of these.
268 268 for obj in [f, SimpleClass().method, any, str.upper]:
269 269 print(obj)
270 270 i = inspector.info(obj)
271 271 nt.assert_is(i['call_def'], None)
272 272
273 273 def f_kwarg(pos, *, kwonly):
274 274 pass
275 275
276 276 def test_definition_kwonlyargs():
277 277 i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore
278 278 nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)")
279 279
280 280 def test_getdoc():
281 281 class A(object):
282 282 """standard docstring"""
283 283 pass
284 284
285 285 class B(object):
286 286 """standard docstring"""
287 287 def getdoc(self):
288 288 return "custom docstring"
289 289
290 290 class C(object):
291 291 """standard docstring"""
292 292 def getdoc(self):
293 293 return None
294 294
295 295 a = A()
296 296 b = B()
297 297 c = C()
298 298
299 299 nt.assert_equal(oinspect.getdoc(a), "standard docstring")
300 300 nt.assert_equal(oinspect.getdoc(b), "custom docstring")
301 301 nt.assert_equal(oinspect.getdoc(c), "standard docstring")
302 302
303 303
304 304 def test_empty_property_has_no_source():
305 305 i = inspector.info(property(), detail_level=1)
306 306 nt.assert_is(i['source'], None)
307 307
308 308
309 309 def test_property_sources():
310 310 import zlib
311 311
312 312 class A(object):
313 313 @property
314 314 def foo(self):
315 315 return 'bar'
316 316
317 317 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
318 318
319 319 id = property(id)
320 320 compress = property(zlib.compress)
321 321
322 322 i = inspector.info(A.foo, detail_level=1)
323 323 nt.assert_in('def foo(self):', i['source'])
324 324 nt.assert_in('lambda self, v:', i['source'])
325 325
326 326 i = inspector.info(A.id, detail_level=1)
327 327 nt.assert_in('fget = <function id>', i['source'])
328 328
329 329 i = inspector.info(A.compress, detail_level=1)
330 330 nt.assert_in('fget = <function zlib.compress>', i['source'])
331 331
332 332
333 333 def test_property_docstring_is_in_info_for_detail_level_0():
334 334 class A(object):
335 335 @property
336 336 def foobar(self):
337 337 """This is `foobar` property."""
338 338 pass
339 339
340 340 ip.user_ns['a_obj'] = A()
341 341 nt.assert_equal(
342 342 'This is `foobar` property.',
343 343 ip.object_inspect('a_obj.foobar', detail_level=0)['docstring'])
344 344
345 345 ip.user_ns['a_cls'] = A
346 346 nt.assert_equal(
347 347 'This is `foobar` property.',
348 348 ip.object_inspect('a_cls.foobar', detail_level=0)['docstring'])
349 349
350 350
351 351 def test_pdef():
352 352 # See gh-1914
353 353 def foo(): pass
354 354 inspector.pdef(foo, 'foo')
355 355
356 356
357 357 def test_pinfo_nonascii():
358 358 # See gh-1177
359 359 from . import nonascii2
360 360 ip.user_ns['nonascii2'] = nonascii2
361 361 ip._inspect('pinfo', 'nonascii2', detail_level=1)
362 362
363 363
364 364 def test_pinfo_docstring_no_source():
365 365 """Docstring should be included with detail_level=1 if there is no source"""
366 366 with AssertPrints('Docstring:'):
367 367 ip._inspect('pinfo', 'str.format', detail_level=0)
368 368 with AssertPrints('Docstring:'):
369 369 ip._inspect('pinfo', 'str.format', detail_level=1)
370 370
371 371
372 372 def test_pinfo_no_docstring_if_source():
373 373 """Docstring should not be included with detail_level=1 if source is found"""
374 374 def foo():
375 375 """foo has a docstring"""
376 376
377 377 ip.user_ns['foo'] = foo
378 378
379 379 with AssertPrints('Docstring:'):
380 380 ip._inspect('pinfo', 'foo', detail_level=0)
381 381 with AssertPrints('Source:'):
382 382 ip._inspect('pinfo', 'foo', detail_level=1)
383 383 with AssertNotPrints('Docstring:'):
384 384 ip._inspect('pinfo', 'foo', detail_level=1)
385 385
386 386
387 def test_pinfo_docstring_if_detail_and_no_source():
388 """ Docstring should be displayed if source info not available """
389 obj_def = '''class Foo(object):
390 """ This is a docstring for Foo """
391 def bar(self):
392 """ This is a docstring for Foo.bar """
393 pass
394 '''
395
396 ip.run_cell(obj_def)
397 ip.run_cell('foo = Foo()')
398
399 with AssertNotPrints("Source:"):
400 with AssertPrints('Docstring:'):
401 ip._inspect('pinfo', 'foo', detail_level=0)
402 with AssertPrints('Docstring:'):
403 ip._inspect('pinfo', 'foo', detail_level=1)
404 with AssertPrints('Docstring:'):
405 ip._inspect('pinfo', 'foo.bar', detail_level=0)
406
407 with AssertNotPrints('Docstring:'):
408 with AssertPrints('Source:'):
409 ip._inspect('pinfo', 'foo.bar', detail_level=1)
410
411
387 412 def test_pinfo_magic():
388 413 with AssertPrints('Docstring:'):
389 414 ip._inspect('pinfo', 'lsmagic', detail_level=0)
390 415
391 416 with AssertPrints('Source:'):
392 417 ip._inspect('pinfo', 'lsmagic', detail_level=1)
393 418
394 419
395 420 def test_init_colors():
396 421 # ensure colors are not present in signature info
397 422 info = inspector.info(HasSignature)
398 423 init_def = info['init_definition']
399 424 nt.assert_not_in('[0m', init_def)
400 425
401 426
402 427 def test_builtin_init():
403 428 info = inspector.info(list)
404 429 init_def = info['init_definition']
405 430 # Python < 3.4 can't get init definition from builtins,
406 431 # but still exercise the inspection in case of error-raising bugs.
407 432 if sys.version_info >= (3,4):
408 433 nt.assert_is_not_none(init_def)
409 434
General Comments 0
You need to be logged in to leave comments. Login now