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