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