diff options
Diffstat (limited to 'Lib/test/test_inspect.py')
| -rw-r--r-- | Lib/test/test_inspect.py | 1090 |
1 files changed, 985 insertions, 105 deletions
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 5cbec9b..1a124b5 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1,22 +1,35 @@ -import re -import sys -import types -import unittest +import collections +import datetime +import functools +import importlib import inspect +import io import linecache -import datetime -import collections import os -import shutil from os.path import normcase +import _pickle +import re +import shutil +import sys +import types +import textwrap +import unicodedata +import unittest +import unittest.mock -from test.support import run_unittest, TESTFN, DirsOnSysPath +try: + from concurrent.futures import ThreadPoolExecutor +except ImportError: + ThreadPoolExecutor = None +from test.support import run_unittest, TESTFN, DirsOnSysPath, cpython_only +from test.support import MISSING_C_DOCSTRINGS +from test.script_helper import assert_python_ok, assert_python_failure from test import inspect_fodder as mod from test import inspect_fodder2 as mod2 -# C module for test_findsource_binary -import unicodedata +from test.test_import import _ready_to_import + # Functions tested in this suite: # ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode, @@ -63,6 +76,10 @@ def generator_function_example(self): for i in range(2): yield i +class EqualsToAll: + def __eq__(self, other): + return True + class TestPredicates(IsTestBase): def test_sixteen(self): count = len([x for x in dir(inspect) if x.startswith('is')]) @@ -120,7 +137,6 @@ class TestPredicates(IsTestBase): def test_get_slot_members(self): class C(object): __slots__ = ("a", "b") - x = C() x.a = 42 members = dict(inspect.getmembers(x)) @@ -310,6 +326,16 @@ class TestRetrievingSourceCode(GetSourceBase): def test_getfile(self): self.assertEqual(inspect.getfile(mod.StupidGit), mod.__file__) + def test_getfile_class_without_module(self): + class CM(type): + @property + def __module__(cls): + raise AttributeError + class C(metaclass=CM): + pass + with self.assertRaises(TypeError): + inspect.getfile(C) + def test_getmodule_recursion(self): from types import ModuleType name = '__inspect_dummy' @@ -413,19 +439,20 @@ class TestBuggyCases(GetSourceBase): def test_method_in_dynamic_class(self): self.assertSourceEqual(mod2.method_in_dynamic_class, 95, 97) - @unittest.skipIf( - not hasattr(unicodedata, '__file__') or - unicodedata.__file__[-4:] in (".pyc", ".pyo"), - "unicodedata is not an external binary module") + # This should not skip for CPython, but might on a repackaged python where + # unicodedata is not an external module, or on pypy. + @unittest.skipIf(not hasattr(unicodedata, '__file__') or + unicodedata.__file__.endswith('.py'), + "unicodedata is not an external binary module") def test_findsource_binary(self): - self.assertRaises(IOError, inspect.getsource, unicodedata) - self.assertRaises(IOError, inspect.findsource, unicodedata) + self.assertRaises(OSError, inspect.getsource, unicodedata) + self.assertRaises(OSError, inspect.findsource, unicodedata) def test_findsource_code_in_linecache(self): lines = ["x=1"] co = compile(lines[0], "_dynamically_created_file", "exec") - self.assertRaises(IOError, inspect.findsource, co) - self.assertRaises(IOError, inspect.getsource, co) + self.assertRaises(OSError, inspect.findsource, co) + self.assertRaises(OSError, inspect.getsource, co) linecache.cache[co.co_filename] = (1, None, lines, co.co_filename) try: self.assertEqual(inspect.findsource(co), (lines,0)) @@ -463,13 +490,13 @@ class _BrokenDataDescriptor(object): A broken data descriptor. See bug #1785. """ def __get__(*args): - raise AssertionError("should not __get__ data descriptors") + raise AttributeError("broken data descriptor") def __set__(*args): raise RuntimeError def __getattr__(*args): - raise AssertionError("should not __getattr__ data descriptors") + raise AttributeError("broken data descriptor") class _BrokenMethodDescriptor(object): @@ -477,10 +504,10 @@ class _BrokenMethodDescriptor(object): A broken method descriptor. See bug #1785. """ def __get__(*args): - raise AssertionError("should not __get__ method descriptors") + raise AttributeError("broken method descriptor") def __getattr__(*args): - raise AssertionError("should not __getattr__ method descriptors") + raise AttributeError("broken method descriptor") # Helper for testing classify_class_attrs. @@ -559,6 +586,96 @@ class TestClassesAndFunctions(unittest.TestCase): kwonlyargs_e=['arg'], formatted='(*, arg)') + def test_argspec_api_ignores_wrapped(self): + # Issue 20684: low level introspection API must ignore __wrapped__ + @functools.wraps(mod.spam) + def ham(x, y): + pass + # Basic check + self.assertArgSpecEquals(ham, ['x', 'y'], formatted='(x, y)') + self.assertFullArgSpecEquals(ham, ['x', 'y'], formatted='(x, y)') + self.assertFullArgSpecEquals(functools.partial(ham), + ['x', 'y'], formatted='(x, y)') + # Other variants + def check_method(f): + self.assertArgSpecEquals(f, ['self', 'x', 'y'], + formatted='(self, x, y)') + class C: + @functools.wraps(mod.spam) + def ham(self, x, y): + pass + pham = functools.partialmethod(ham) + @functools.wraps(mod.spam) + def __call__(self, x, y): + pass + check_method(C()) + check_method(C.ham) + check_method(C().ham) + check_method(C.pham) + check_method(C().pham) + + class C_new: + @functools.wraps(mod.spam) + def __new__(self, x, y): + pass + check_method(C_new) + + class C_init: + @functools.wraps(mod.spam) + def __init__(self, x, y): + pass + check_method(C_init) + + def test_getfullargspec_signature_attr(self): + def test(): + pass + spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY) + test.__signature__ = inspect.Signature(parameters=(spam_param,)) + + self.assertFullArgSpecEquals(test, args_e=['spam'], formatted='(spam)') + + def test_getfullargspec_signature_annos(self): + def test(a:'spam') -> 'ham': pass + spec = inspect.getfullargspec(test) + self.assertEqual(test.__annotations__, spec.annotations) + + def test(): pass + spec = inspect.getfullargspec(test) + self.assertEqual(test.__annotations__, spec.annotations) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullargspec_builtin_methods(self): + self.assertFullArgSpecEquals(_pickle.Pickler.dump, + args_e=['self', 'obj'], formatted='(self, obj)') + + self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump, + args_e=['self', 'obj'], formatted='(self, obj)') + + self.assertFullArgSpecEquals( + os.stat, + args_e=['path'], + kwonlyargs_e=['dir_fd', 'follow_symlinks'], + kwonlydefaults_e={'dir_fd': None, 'follow_symlinks': True}, + formatted='(path, *, dir_fd=None, follow_symlinks=True)') + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullagrspec_builtin_func(self): + import _testcapi + builtin = _testcapi.docstring_with_signature_with_defaults + spec = inspect.getfullargspec(builtin) + self.assertEqual(spec.defaults[0], 'avocado') + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_getfullagrspec_builtin_func_no_signature(self): + import _testcapi + builtin = _testcapi.docstring_no_signature + with self.assertRaises(TypeError): + inspect.getfullargspec(builtin) def test_getargspec_method(self): class A(object): @@ -588,6 +705,10 @@ class TestClassesAndFunctions(unittest.TestCase): md = _BrokenMethodDescriptor() attrs = attrs_wo_objs(A) + + self.assertIn(('__new__', 'method', object), attrs, 'missing __new__') + self.assertIn(('__init__', 'method', object), attrs, 'missing __init__') + self.assertIn(('s', 'static method', A), attrs, 'missing static method') self.assertIn(('c', 'class method', A), attrs, 'missing class method') self.assertIn(('p', 'property', A), attrs, 'missing property') @@ -650,6 +771,103 @@ class TestClassesAndFunctions(unittest.TestCase): if isinstance(builtin, type): inspect.classify_class_attrs(builtin) + def test_classify_DynamicClassAttribute(self): + class Meta(type): + def __getattr__(self, name): + if name == 'ham': + return 'spam' + return super().__getattr__(name) + class VA(metaclass=Meta): + @types.DynamicClassAttribute + def ham(self): + return 'eggs' + should_find_dca = inspect.Attribute('ham', 'data', VA, VA.__dict__['ham']) + self.assertIn(should_find_dca, inspect.classify_class_attrs(VA)) + should_find_ga = inspect.Attribute('ham', 'data', Meta, 'spam') + self.assertIn(should_find_ga, inspect.classify_class_attrs(VA)) + + def test_classify_overrides_bool(self): + class NoBool(object): + def __eq__(self, other): + return NoBool() + + def __bool__(self): + raise NotImplementedError( + "This object does not specify a boolean value") + + class HasNB(object): + dd = NoBool() + + should_find_attr = inspect.Attribute('dd', 'data', HasNB, HasNB.dd) + self.assertIn(should_find_attr, inspect.classify_class_attrs(HasNB)) + + def test_classify_metaclass_class_attribute(self): + class Meta(type): + fish = 'slap' + def __dir__(self): + return ['__class__', '__module__', '__name__', 'fish'] + class Class(metaclass=Meta): + pass + should_find = inspect.Attribute('fish', 'data', Meta, 'slap') + self.assertIn(should_find, inspect.classify_class_attrs(Class)) + + def test_classify_VirtualAttribute(self): + class Meta(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'BOOM'] + def __getattr__(self, name): + if name =='BOOM': + return 42 + return super().__getattr(name) + class Class(metaclass=Meta): + pass + should_find = inspect.Attribute('BOOM', 'data', Meta, 42) + self.assertIn(should_find, inspect.classify_class_attrs(Class)) + + def test_classify_VirtualAttribute_multi_classes(self): + class Meta1(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'one'] + def __getattr__(self, name): + if name =='one': + return 1 + return super().__getattr__(name) + class Meta2(type): + def __dir__(cls): + return ['__class__', '__module__', '__name__', 'two'] + def __getattr__(self, name): + if name =='two': + return 2 + return super().__getattr__(name) + class Meta3(Meta1, Meta2): + def __dir__(cls): + return list(sorted(set(['__class__', '__module__', '__name__', 'three'] + + Meta1.__dir__(cls) + Meta2.__dir__(cls)))) + def __getattr__(self, name): + if name =='three': + return 3 + return super().__getattr__(name) + class Class1(metaclass=Meta1): + pass + class Class2(Class1, metaclass=Meta3): + pass + + should_find1 = inspect.Attribute('one', 'data', Meta1, 1) + should_find2 = inspect.Attribute('two', 'data', Meta2, 2) + should_find3 = inspect.Attribute('three', 'data', Meta3, 3) + cca = inspect.classify_class_attrs(Class2) + for sf in (should_find1, should_find2, should_find3): + self.assertIn(sf, cca) + + def test_classify_class_attrs_with_buggy_dir(self): + class M(type): + def __dir__(cls): + return ['__class__', '__name__', 'missing'] + class C(metaclass=M): + pass + attrs = [a[0] for a in inspect.classify_class_attrs(C)] + self.assertNotIn('missing', attrs) + def test_getmembers_descriptors(self): class A(object): dd = _BrokenDataDescriptor() @@ -693,6 +911,28 @@ class TestClassesAndFunctions(unittest.TestCase): self.assertIn(('f', b.f), inspect.getmembers(b)) self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod)) + def test_getmembers_VirtualAttribute(self): + class M(type): + def __getattr__(cls, name): + if name == 'eggs': + return 'scrambled' + return super().__getattr__(name) + class A(metaclass=M): + @types.DynamicClassAttribute + def eggs(self): + return 'spam' + self.assertIn(('eggs', 'scrambled'), inspect.getmembers(A)) + self.assertIn(('eggs', 'spam'), inspect.getmembers(A())) + + def test_getmembers_with_buggy_dir(self): + class M(type): + def __dir__(cls): + return ['__class__', '__name__', 'missing'] + class C(metaclass=M): + pass + attrs = [a[0] for a in inspect.getmembers(C)] + self.assertNotIn('missing', attrs) + _global_ref = object() class TestGetClosureVars(unittest.TestCase): @@ -989,6 +1229,20 @@ class TestGetcallargsFunctions(unittest.TestCase): self.assertEqualException(f3, '1, 2') self.assertEqualException(f3, '1, 2, a=1, b=2') + # issue #20816: getcallargs() fails to iterate over non-existent + # kwonlydefaults and raises a wrong TypeError + def f5(*, a): pass + with self.assertRaisesRegex(TypeError, + 'missing 1 required keyword-only'): + inspect.getcallargs(f5) + + + # issue20817: + def f6(a, b, c): + pass + with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"): + inspect.getcallargs(f6) + class TestGetcallargsMethods(TestGetcallargsFunctions): def setUp(self): @@ -1080,6 +1334,15 @@ class TestGetattrStatic(unittest.TestCase): self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.x) + def test_classVirtualAttribute(self): + class Thing(object): + @types.DynamicClassAttribute + def x(self): + return self._x + _x = object() + + self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.__dict__['x']) + def test_inherited_classattribute(self): class Thing(object): x = object() @@ -1390,11 +1653,13 @@ class TestSignatureObject(unittest.TestCase): self.assertEqual(str(S()), '()') - def test(po, pk, *args, ko, **kwargs): + def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs): pass sig = inspect.signature(test) po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY) + pod = sig.parameters['pod'].replace(kind=P.POSITIONAL_ONLY) pk = sig.parameters['pk'] + pkd = sig.parameters['pkd'] args = sig.parameters['args'] ko = sig.parameters['ko'] kwargs = sig.parameters['kwargs'] @@ -1417,6 +1682,15 @@ class TestSignatureObject(unittest.TestCase): with self.assertRaisesRegex(ValueError, 'duplicate parameter name'): S((po, pk, args, kwargs2, ko)) + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((pod, po)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((po, pkd, pk)) + + with self.assertRaisesRegex(ValueError, 'follows default argument'): + S((pkd, pk)) + def test_signature_immutability(self): def test(a): pass @@ -1461,19 +1735,100 @@ class TestSignatureObject(unittest.TestCase): ('kwargs', ..., int, "var_keyword")), ...)) - def test_signature_on_builtin_function(self): - with self.assertRaisesRegex(ValueError, 'not supported by signature'): - inspect.signature(type) - with self.assertRaisesRegex(ValueError, 'not supported by signature'): - # support for 'wrapper_descriptor' - inspect.signature(type.__call__) - with self.assertRaisesRegex(ValueError, 'not supported by signature'): - # support for 'method-wrapper' - inspect.signature(min.__call__) - with self.assertRaisesRegex(ValueError, - 'no signature found for builtin function'): - # support for 'method-wrapper' - inspect.signature(min) + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_builtins(self): + import _testcapi + + def test_unbound_method(o): + """Use this to test unbound methods (things that should have a self)""" + signature = inspect.signature(o) + self.assertTrue(isinstance(signature, inspect.Signature)) + self.assertEqual(list(signature.parameters.values())[0].name, 'self') + return signature + + def test_callable(o): + """Use this to test bound methods or normal callables (things that don't expect self)""" + signature = inspect.signature(o) + self.assertTrue(isinstance(signature, inspect.Signature)) + if signature.parameters: + self.assertNotEqual(list(signature.parameters.values())[0].name, 'self') + return signature + + signature = test_callable(_testcapi.docstring_with_signature_with_defaults) + def p(name): return signature.parameters[name].default + self.assertEqual(p('s'), 'avocado') + self.assertEqual(p('b'), b'bytes') + self.assertEqual(p('d'), 3.14) + self.assertEqual(p('i'), 35) + self.assertEqual(p('n'), None) + self.assertEqual(p('t'), True) + self.assertEqual(p('f'), False) + self.assertEqual(p('local'), 3) + self.assertEqual(p('sys'), sys.maxsize) + self.assertEqual(p('exp'), sys.maxsize - 1) + + test_callable(object) + + # normal method + # (PyMethodDescr_Type, "method_descriptor") + test_unbound_method(_pickle.Pickler.dump) + d = _pickle.Pickler(io.StringIO()) + test_callable(d.dump) + + # static method + test_callable(str.maketrans) + test_callable('abc'.maketrans) + + # class method + test_callable(dict.fromkeys) + test_callable({}.fromkeys) + + # wrapper around slot (PyWrapperDescr_Type, "wrapper_descriptor") + test_unbound_method(type.__call__) + test_unbound_method(int.__add__) + test_callable((3).__add__) + + # _PyMethodWrapper_Type + # support for 'method-wrapper' + test_callable(min.__call__) + + # This doesn't work now. + # (We don't have a valid signature for "type" in 3.4) + with self.assertRaisesRegex(ValueError, "no signature found"): + class ThisWorksNow: + __call__ = type + test_callable(ThisWorksNow()) + + # Regression test for issue #20786 + test_unbound_method(dict.__delitem__) + test_unbound_method(property.__delete__) + + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_decorated_builtins(self): + import _testcapi + func = _testcapi.docstring_with_signature_with_defaults + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(*args, **kwargs) + return wrapper + + decorated_func = decorator(func) + + self.assertEqual(inspect.signature(func), + inspect.signature(decorated_func)) + + @cpython_only + def test_signature_on_builtins_no_signature(self): + import _testcapi + with self.assertRaisesRegex(ValueError, 'no signature found for builtin'): + inspect.signature(_testcapi.docstring_no_signature) def test_signature_on_non_function(self): with self.assertRaisesRegex(TypeError, 'is not a callable object'): @@ -1482,14 +1837,121 @@ class TestSignatureObject(unittest.TestCase): with self.assertRaisesRegex(TypeError, 'is not a Python function'): inspect.Signature.from_function(42) + def test_signature_from_builtin_errors(self): + with self.assertRaisesRegex(TypeError, 'is not a Python builtin'): + inspect.Signature.from_builtin(42) + + def test_signature_from_functionlike_object(self): + def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs): + pass + + class funclike: + # Has to be callable, and have correct + # __code__, __annotations__, __defaults__, __name__, + # and __kwdefaults__ attributes + + def __init__(self, func): + self.__name__ = func.__name__ + self.__code__ = func.__code__ + self.__annotations__ = func.__annotations__ + self.__defaults__ = func.__defaults__ + self.__kwdefaults__ = func.__kwdefaults__ + self.func = func + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + sig_func = inspect.Signature.from_function(func) + + sig_funclike = inspect.Signature.from_function(funclike(func)) + self.assertEqual(sig_funclike, sig_func) + + sig_funclike = inspect.signature(funclike(func)) + self.assertEqual(sig_funclike, sig_func) + + # If object is not a duck type of function, then + # signature will try to get a signature for its '__call__' + # method + fl = funclike(func) + del fl.__defaults__ + self.assertEqual(self.signature(fl), + ((('args', ..., ..., "var_positional"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + # Test with cython-like builtins: + _orig_isdesc = inspect.ismethoddescriptor + def _isdesc(obj): + if hasattr(obj, '_builtinmock'): + return True + return _orig_isdesc(obj) + + with unittest.mock.patch('inspect.ismethoddescriptor', _isdesc): + builtin_func = funclike(func) + # Make sure that our mock setup is working + self.assertFalse(inspect.ismethoddescriptor(builtin_func)) + builtin_func._builtinmock = True + self.assertTrue(inspect.ismethoddescriptor(builtin_func)) + self.assertEqual(inspect.signature(builtin_func), sig_func) + + def test_signature_functionlike_class(self): + # We only want to duck type function-like objects, + # not classes. + + def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs): + pass + + class funclike: + def __init__(self, marker): + pass + + __name__ = func.__name__ + __code__ = func.__code__ + __annotations__ = func.__annotations__ + __defaults__ = func.__defaults__ + __kwdefaults__ = func.__kwdefaults__ + + with self.assertRaisesRegex(TypeError, 'is not a Python function'): + inspect.Signature.from_function(funclike) + + self.assertEqual(str(inspect.signature(funclike)), '(marker)') + def test_signature_on_method(self): class Test: - def foo(self, arg1, arg2=1) -> int: + def __init__(*args): + pass + def m1(self, arg1, arg2=1) -> int: + pass + def m2(*args): + pass + def __call__(*, a): pass - meth = Test().foo + self.assertEqual(self.signature(Test().m1), + ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "positional_or_keyword")), + int)) - self.assertEqual(self.signature(meth), + self.assertEqual(self.signature(Test().m2), + ((('args', ..., ..., "var_positional"),), + ...)) + + self.assertEqual(self.signature(Test), + ((('args', ..., ..., "var_positional"),), + ...)) + + with self.assertRaisesRegex(ValueError, 'invalid method signature'): + self.signature(Test()) + + def test_signature_wrapped_bound_method(self): + # Issue 24298 + class Test: + def m1(self, arg1, arg2=1) -> int: + pass + @functools.wraps(Test().m1) + def m1d(*args, **kwargs): + pass + self.assertEqual(self.signature(m1d), ((('arg1', ..., ..., "positional_or_keyword"), ('arg2', 1, ..., "positional_or_keyword")), int)) @@ -1533,6 +1995,8 @@ class TestSignatureObject(unittest.TestCase): def test_signature_on_partial(self): from functools import partial + Parameter = inspect.Parameter + def test(): pass @@ -1568,15 +2032,22 @@ class TestSignatureObject(unittest.TestCase): self.assertEqual(self.signature(partial(test, b=1, c=2)), ((('a', ..., ..., "positional_or_keyword"), - ('b', 1, ..., "positional_or_keyword"), + ('b', 1, ..., "keyword_only"), ('c', 2, ..., "keyword_only"), ('d', ..., ..., "keyword_only")), ...)) self.assertEqual(self.signature(partial(test, 0, b=1, c=2)), - ((('b', 1, ..., "positional_or_keyword"), + ((('b', 1, ..., "keyword_only"), ('c', 2, ..., "keyword_only"), - ('d', ..., ..., "keyword_only"),), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, a=1)), + ((('a', 1, ..., "keyword_only"), + ('b', ..., ..., "keyword_only"), + ('c', ..., ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), ...)) def test(a, *args, b, **kwargs): @@ -1588,13 +2059,18 @@ class TestSignatureObject(unittest.TestCase): ('kwargs', ..., ..., "var_keyword")), ...)) + self.assertEqual(self.signature(partial(test, a=1)), + ((('a', 1, ..., "keyword_only"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + self.assertEqual(self.signature(partial(test, 1, 2, 3)), ((('args', ..., ..., "var_positional"), ('b', ..., ..., "keyword_only"), ('kwargs', ..., ..., "var_keyword")), ...)) - self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)), ((('args', ..., ..., "var_positional"), ('b', ..., ..., "keyword_only"), @@ -1641,7 +2117,7 @@ class TestSignatureObject(unittest.TestCase): return a _foo = partial(partial(foo, a=10), a=20) self.assertEqual(self.signature(_foo), - ((('a', 20, ..., "positional_or_keyword"),), + ((('a', 20, ..., "keyword_only"),), ...)) # check that we don't have any side-effects in signature(), # and the partial object is still functioning @@ -1650,42 +2126,119 @@ class TestSignatureObject(unittest.TestCase): def foo(a, b, c): return a, b, c _foo = partial(partial(foo, 1, b=20), b=30) + self.assertEqual(self.signature(_foo), - ((('b', 30, ..., "positional_or_keyword"), - ('c', ..., ..., "positional_or_keyword")), + ((('b', 30, ..., "keyword_only"), + ('c', ..., ..., "keyword_only")), ...)) self.assertEqual(_foo(c=10), (1, 30, 10)) - _foo = partial(_foo, 2) # now 'b' has two values - - # positional and keyword - with self.assertRaisesRegex(ValueError, "has incorrect arguments"): - inspect.signature(_foo) def foo(a, b, c, *, d): return a, b, c, d _foo = partial(partial(foo, d=20, c=20), b=10, d=30) self.assertEqual(self.signature(_foo), ((('a', ..., ..., "positional_or_keyword"), - ('b', 10, ..., "positional_or_keyword"), - ('c', 20, ..., "positional_or_keyword"), - ('d', 30, ..., "keyword_only")), + ('b', 10, ..., "keyword_only"), + ('c', 20, ..., "keyword_only"), + ('d', 30, ..., "keyword_only"), + ), ...)) ba = inspect.signature(_foo).bind(a=200, b=11) self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30)) def foo(a=1, b=2, c=3): return a, b, c - _foo = partial(foo, a=10, c=13) - ba = inspect.signature(_foo).bind(11) + _foo = partial(foo, c=13) # (a=1, b=2, *, c=13) + + ba = inspect.signature(_foo).bind(a=11) self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 2, 13)) + ba = inspect.signature(_foo).bind(11, 12) self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13)) + ba = inspect.signature(_foo).bind(11, b=12) self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13)) + ba = inspect.signature(_foo).bind(b=12) - self.assertEqual(_foo(*ba.args, **ba.kwargs), (10, 12, 13)) - _foo = partial(_foo, b=10) - ba = inspect.signature(_foo).bind(12, 14) - self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 14, 13)) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (1, 12, 13)) + + _foo = partial(_foo, b=10, c=20) + ba = inspect.signature(_foo).bind(12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 10, 20)) + + + def foo(a, b, c, d, **kwargs): + pass + sig = inspect.signature(foo) + params = sig.parameters.copy() + params['a'] = params['a'].replace(kind=Parameter.POSITIONAL_ONLY) + params['b'] = params['b'].replace(kind=Parameter.POSITIONAL_ONLY) + foo.__signature__ = inspect.Signature(params.values()) + sig = inspect.signature(foo) + self.assertEqual(str(sig), '(a, b, /, c, d, **kwargs)') + + self.assertEqual(self.signature(partial(foo, 1)), + ((('b', ..., ..., 'positional_only'), + ('c', ..., ..., 'positional_or_keyword'), + ('d', ..., ..., 'positional_or_keyword'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, 2)), + ((('c', ..., ..., 'positional_or_keyword'), + ('d', ..., ..., 'positional_or_keyword'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, 2, 3)), + ((('d', ..., ..., 'positional_or_keyword'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, 2, c=3)), + ((('c', 3, ..., 'keyword_only'), + ('d', ..., ..., 'keyword_only'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + self.assertEqual(self.signature(partial(foo, 1, c=3)), + ((('b', ..., ..., 'positional_only'), + ('c', 3, ..., 'keyword_only'), + ('d', ..., ..., 'keyword_only'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + def test_signature_on_partialmethod(self): + from functools import partialmethod + + class Spam: + def test(): + pass + ham = partialmethod(test) + + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(Spam.ham) + + class Spam: + def test(it, a, *, c) -> 'spam': + pass + ham = partialmethod(test, c=1) + + self.assertEqual(self.signature(Spam.ham), + ((('it', ..., ..., 'positional_or_keyword'), + ('a', ..., ..., 'positional_or_keyword'), + ('c', 1, ..., 'keyword_only')), + 'spam')) + + self.assertEqual(self.signature(Spam().ham), + ((('a', ..., ..., 'positional_or_keyword'), + ('c', 1, ..., 'keyword_only')), + 'spam')) + + def test_signature_on_fake_partialmethod(self): + def foo(a): pass + foo._partialmethod = 'spam' + self.assertEqual(str(inspect.signature(foo)), '(a)') def test_signature_on_decorated(self): import functools @@ -1736,6 +2289,17 @@ class TestSignatureObject(unittest.TestCase): ((('b', ..., ..., "positional_or_keyword"),), ...)) + # Test we handle __signature__ partway down the wrapper stack + def wrapped_foo_call(): + pass + wrapped_foo_call.__wrapped__ = Foo.__call__ + + self.assertEqual(self.signature(wrapped_foo_call), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + def test_signature_on_class(self): class C: def __init__(self, a): @@ -1817,6 +2381,49 @@ class TestSignatureObject(unittest.TestCase): ('bar', 2, ..., "keyword_only")), ...)) + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_class_without_init(self): + # Test classes without user-defined __init__ or __new__ + class C: pass + self.assertEqual(str(inspect.signature(C)), '()') + class D(C): pass + self.assertEqual(str(inspect.signature(D)), '()') + + # Test meta-classes without user-defined __init__ or __new__ + class C(type): pass + class D(C): pass + with self.assertRaisesRegex(ValueError, "callable.*is not supported"): + self.assertEqual(inspect.signature(C), None) + with self.assertRaisesRegex(ValueError, "callable.*is not supported"): + self.assertEqual(inspect.signature(D), None) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_on_builtin_class(self): + self.assertEqual(str(inspect.signature(_pickle.Pickler)), + '(file, protocol=None, fix_imports=True)') + + class P(_pickle.Pickler): pass + class EmptyTrait: pass + class P2(EmptyTrait, P): pass + self.assertEqual(str(inspect.signature(P)), + '(file, protocol=None, fix_imports=True)') + self.assertEqual(str(inspect.signature(P2)), + '(file, protocol=None, fix_imports=True)') + + class P3(P2): + def __init__(self, spam): + pass + self.assertEqual(str(inspect.signature(P3)), '(spam)') + + class MetaP(type): + def __call__(cls, foo, bar): + pass + class P4(P2, metaclass=MetaP): + pass + self.assertEqual(str(inspect.signature(P4)), '(foo, bar)') + def test_signature_on_callable_objects(self): class Foo: def __call__(self, a): @@ -1838,18 +2445,16 @@ class TestSignatureObject(unittest.TestCase): ((('a', ..., ..., "positional_or_keyword"),), ...)) - class ToFail: - __call__ = type - with self.assertRaisesRegex(ValueError, "not supported by signature"): - inspect.signature(ToFail()) - - class Wrapped: pass Wrapped.__wrapped__ = lambda a: None self.assertEqual(self.signature(Wrapped), ((('a', ..., ..., "positional_or_keyword"),), ...)) + # wrapper loop: + Wrapped.__wrapped__ = Wrapped + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + self.signature(Wrapped) def test_signature_on_lambdas(self): self.assertEqual(self.signature((lambda a=10: a)), @@ -1858,47 +2463,62 @@ class TestSignatureObject(unittest.TestCase): def test_signature_equality(self): def foo(a, *, b:int) -> float: pass - self.assertNotEqual(inspect.signature(foo), 42) + self.assertFalse(inspect.signature(foo) == 42) + self.assertTrue(inspect.signature(foo) != 42) + self.assertTrue(inspect.signature(foo) == EqualsToAll()) + self.assertFalse(inspect.signature(foo) != EqualsToAll()) def bar(a, *, b:int) -> float: pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) def bar(a, *, b:int) -> int: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) def bar(a, *, b:int): pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) def bar(a, *, b:int=42) -> float: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) def bar(a, *, c) -> float: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) def bar(a, b:int) -> float: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) def spam(b:int, a) -> float: pass - self.assertNotEqual(inspect.signature(spam), inspect.signature(bar)) + self.assertFalse(inspect.signature(spam) == inspect.signature(bar)) + self.assertTrue(inspect.signature(spam) != inspect.signature(bar)) def foo(*, a, b, c): pass def bar(*, c, b, a): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) def foo(*, a=1, b, c): pass def bar(*, c, b, a=1): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) def foo(pos, *, a=1, b, c): pass def bar(pos, *, c, b, a=1): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) def foo(pos, *, a, b, c): pass def bar(pos, *, c, b, a=1): pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) def foo(pos, *args, a=42, b, c, **kwargs:int): pass def bar(pos, *args, c, b, a=42, **kwargs:int): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) def test_signature_unhashable(self): def foo(a): pass @@ -1923,6 +2543,7 @@ class TestSignatureObject(unittest.TestCase): def test_signature_str_positional_only(self): P = inspect.Parameter + S = inspect.Signature def test(a_po, *, b, **kwargs): return a_po, kwargs @@ -1933,14 +2554,20 @@ class TestSignatureObject(unittest.TestCase): test.__signature__ = sig.replace(parameters=new_params) self.assertEqual(str(inspect.signature(test)), - '(<a_po>, *, b, **kwargs)') + '(a_po, /, *, b, **kwargs)') - sig = inspect.signature(test) - new_params = list(sig.parameters.values()) - new_params[0] = new_params[0].replace(name=None) - test.__signature__ = sig.replace(parameters=new_params) - self.assertEqual(str(inspect.signature(test)), - '(<0>, *, b, **kwargs)') + self.assertEqual(str(S(parameters=[P('foo', P.POSITIONAL_ONLY)])), + '(foo, /)') + + self.assertEqual(str(S(parameters=[ + P('foo', P.POSITIONAL_ONLY), + P('bar', P.VAR_KEYWORD)])), + '(foo, /, **bar)') + + self.assertEqual(str(S(parameters=[ + P('foo', P.POSITIONAL_ONLY), + P('bar', P.VAR_POSITIONAL)])), + '(foo, /, *bar)') def test_signature_replace_anno(self): def test() -> 42: @@ -1955,6 +2582,22 @@ class TestSignatureObject(unittest.TestCase): self.assertEqual(sig.return_annotation, 42) self.assertEqual(sig, inspect.signature(test)) + def test_signature_on_mangled_parameters(self): + class Spam: + def foo(self, __p1:1=2, *, __p2:2=3): + pass + class Ham(Spam): + pass + + self.assertEqual(self.signature(Spam.foo), + ((('self', ..., ..., "positional_or_keyword"), + ('_Spam__p1', 2, 1, "positional_or_keyword"), + ('_Spam__p2', 3, 2, "keyword_only")), + ...)) + + self.assertEqual(self.signature(Spam.foo), + self.signature(Ham.foo)) + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): @@ -1979,10 +2622,13 @@ class TestParameterObject(unittest.TestCase): with self.assertRaisesRegex(ValueError, 'not a valid parameter name'): inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD) - with self.assertRaisesRegex(ValueError, - 'non-positional-only parameter'): + with self.assertRaisesRegex(TypeError, 'name must be a str'): inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD) + with self.assertRaisesRegex(ValueError, + 'is not a valid parameter name'): + inspect.Parameter('$', kind=inspect.Parameter.VAR_KEYWORD) + with self.assertRaisesRegex(ValueError, 'cannot have default values'): inspect.Parameter('a', default=42, kind=inspect.Parameter.VAR_KEYWORD) @@ -2002,11 +2648,17 @@ class TestParameterObject(unittest.TestCase): P = inspect.Parameter p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY) - self.assertEqual(p, p) - self.assertNotEqual(p, 42) + self.assertTrue(p == p) + self.assertFalse(p != p) + self.assertFalse(p == 42) + self.assertTrue(p != 42) + self.assertTrue(p == EqualsToAll()) + self.assertFalse(p != EqualsToAll()) - self.assertEqual(p, P('foo', default=42, - kind=inspect.Parameter.KEYWORD_ONLY)) + self.assertTrue(p == P('foo', default=42, + kind=inspect.Parameter.KEYWORD_ONLY)) + self.assertFalse(p != P('foo', default=42, + kind=inspect.Parameter.KEYWORD_ONLY)) def test_signature_parameter_unhashable(self): p = inspect.Parameter('foo', default=42, @@ -2031,7 +2683,8 @@ class TestParameterObject(unittest.TestCase): self.assertEqual(p2.name, 'bar') self.assertNotEqual(p2, p) - with self.assertRaisesRegex(ValueError, 'not a valid parameter name'): + with self.assertRaisesRegex(ValueError, + 'name is a required attribute'): p2 = p2.replace(name=p2.empty) p2 = p2.replace(name='foo', default=None) @@ -2053,14 +2706,11 @@ class TestParameterObject(unittest.TestCase): self.assertEqual(p2, p) def test_signature_parameter_positional_only(self): - p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY) - self.assertEqual(str(p), '<>') - - p = p.replace(name='1') - self.assertEqual(str(p), '<1>') + with self.assertRaisesRegex(TypeError, 'name must be a str'): + inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY) def test_signature_parameter_immutability(self): - p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY) + p = inspect.Parameter('spam', kind=inspect.Parameter.KEYWORD_ONLY) with self.assertRaises(AttributeError): p.foo = 'bar' @@ -2258,6 +2908,15 @@ class TestSignatureBind(unittest.TestCase): self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6), (1, 2, 4, 5, 6, {})) + self.assertEqual(self.call(test, 1, 2), + (1, 2, 3, 42, 50, {})) + + self.assertEqual(self.call(test, 1, 2, foo=4, bar=5), + (1, 2, 3, 4, 5, {})) + + with self.assertRaisesRegex(TypeError, "but was passed as a keyword"): + self.call(test, 1, 2, foo=4, bar=5, c_po=10) + with self.assertRaisesRegex(TypeError, "parameter is positional only"): self.call(test, 1, 2, c_po=4) @@ -2274,6 +2933,22 @@ class TestSignatureBind(unittest.TestCase): ba = sig.bind(1, self=2, b=3) self.assertEqual(ba.args, (1, 2, 3)) + def test_signature_bind_vararg_name(self): + def test(a, *args): + return a, args + sig = inspect.signature(test) + + with self.assertRaisesRegex(TypeError, "too many keyword arguments"): + sig.bind(a=0, args=1) + + def test(*args, **kwargs): + return args, kwargs + self.assertEqual(self.call(test, args=1), ((), {'args': 1})) + + sig = inspect.signature(test) + ba = sig.bind(args=1) + self.assertEqual(ba.arguments, {'kwargs': {'args': 1}}) + class TestBoundArguments(unittest.TestCase): def test_signature_bound_arguments_unhashable(self): @@ -2286,19 +2961,223 @@ class TestBoundArguments(unittest.TestCase): def test_signature_bound_arguments_equality(self): def foo(a): pass ba = inspect.signature(foo).bind(1) - self.assertEqual(ba, ba) + self.assertTrue(ba == ba) + self.assertFalse(ba != ba) + self.assertTrue(ba == EqualsToAll()) + self.assertFalse(ba != EqualsToAll()) ba2 = inspect.signature(foo).bind(1) - self.assertEqual(ba, ba2) + self.assertTrue(ba == ba2) + self.assertFalse(ba != ba2) ba3 = inspect.signature(foo).bind(2) - self.assertNotEqual(ba, ba3) + self.assertFalse(ba == ba3) + self.assertTrue(ba != ba3) ba3.arguments['a'] = 1 - self.assertEqual(ba, ba3) + self.assertTrue(ba == ba3) + self.assertFalse(ba != ba3) def bar(b): pass ba4 = inspect.signature(bar).bind(1) - self.assertNotEqual(ba, ba4) + self.assertFalse(ba == ba4) + self.assertTrue(ba != ba4) + + +class TestSignaturePrivateHelpers(unittest.TestCase): + def test_signature_get_bound_param(self): + getter = inspect._signature_get_bound_param + + self.assertEqual(getter('($self)'), 'self') + self.assertEqual(getter('($self, obj)'), 'self') + self.assertEqual(getter('($cls, /, obj)'), 'cls') + + def _strip_non_python_syntax(self, input, + clean_signature, self_parameter, last_positional_only): + computed_clean_signature, \ + computed_self_parameter, \ + computed_last_positional_only = \ + inspect._signature_strip_non_python_syntax(input) + self.assertEqual(computed_clean_signature, clean_signature) + self.assertEqual(computed_self_parameter, self_parameter) + self.assertEqual(computed_last_positional_only, last_positional_only) + + def test_signature_strip_non_python_syntax(self): + self._strip_non_python_syntax( + "($module, /, path, mode, *, dir_fd=None, " + + "effective_ids=False,\n follow_symlinks=True)", + "(module, path, mode, *, dir_fd=None, " + + "effective_ids=False, follow_symlinks=True)", + 0, + 0) + + self._strip_non_python_syntax( + "($module, word, salt, /)", + "(module, word, salt)", + 0, + 2) + + self._strip_non_python_syntax( + "(x, y=None, z=None, /)", + "(x, y=None, z=None)", + None, + 2) + + self._strip_non_python_syntax( + "(x, y=None, z=None)", + "(x, y=None, z=None)", + None, + None) + + self._strip_non_python_syntax( + "(x,\n y=None,\n z = None )", + "(x, y=None, z=None)", + None, + None) + + self._strip_non_python_syntax( + "", + "", + None, + None) + + self._strip_non_python_syntax( + None, + None, + None, + None) + + +class TestUnwrap(unittest.TestCase): + + def test_unwrap_one(self): + def func(a, b): + return a + b + wrapper = functools.lru_cache(maxsize=20)(func) + self.assertIs(inspect.unwrap(wrapper), func) + + def test_unwrap_several(self): + def func(a, b): + return a + b + wrapper = func + for __ in range(10): + @functools.wraps(wrapper) + def wrapper(): + pass + self.assertIsNot(wrapper.__wrapped__, func) + self.assertIs(inspect.unwrap(wrapper), func) + + def test_stop(self): + def func1(a, b): + return a + b + @functools.wraps(func1) + def func2(): + pass + @functools.wraps(func2) + def wrapper(): + pass + func2.stop_here = 1 + unwrapped = inspect.unwrap(wrapper, + stop=(lambda f: hasattr(f, "stop_here"))) + self.assertIs(unwrapped, func2) + + def test_cycle(self): + def func1(): pass + func1.__wrapped__ = func1 + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func1) + + def func2(): pass + func2.__wrapped__ = func1 + func1.__wrapped__ = func2 + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func1) + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func2) + + def test_unhashable(self): + def func(): pass + func.__wrapped__ = None + class C: + __hash__ = None + __wrapped__ = func + self.assertIsNone(inspect.unwrap(C())) + +class TestMain(unittest.TestCase): + def test_only_source(self): + module = importlib.import_module('unittest') + rc, out, err = assert_python_ok('-m', 'inspect', + 'unittest') + lines = out.decode().splitlines() + # ignore the final newline + self.assertEqual(lines[:-1], inspect.getsource(module).splitlines()) + self.assertEqual(err, b'') + + def test_custom_getattr(self): + def foo(): + pass + foo.__signature__ = 42 + with self.assertRaises(TypeError): + inspect.signature(foo) + + @unittest.skipIf(ThreadPoolExecutor is None, + 'threads required to test __qualname__ for source files') + def test_qualname_source(self): + rc, out, err = assert_python_ok('-m', 'inspect', + 'concurrent.futures:ThreadPoolExecutor') + lines = out.decode().splitlines() + # ignore the final newline + self.assertEqual(lines[:-1], + inspect.getsource(ThreadPoolExecutor).splitlines()) + self.assertEqual(err, b'') + + def test_builtins(self): + module = importlib.import_module('unittest') + _, out, err = assert_python_failure('-m', 'inspect', + 'sys') + lines = err.decode().splitlines() + self.assertEqual(lines, ["Can't get info for builtin modules."]) + + def test_details(self): + module = importlib.import_module('unittest') + rc, out, err = assert_python_ok('-m', 'inspect', + 'unittest', '--details') + output = out.decode() + # Just a quick sanity check on the output + self.assertIn(module.__name__, output) + self.assertIn(module.__file__, output) + if not sys.flags.optimize: + self.assertIn(module.__cached__, output) + self.assertEqual(err, b'') + + +class TestReload(unittest.TestCase): + + src_before = textwrap.dedent("""\ +def foo(): + print("Bla") + """) + + src_after = textwrap.dedent("""\ +def foo(): + print("Oh no!") + """) + + def assertInspectEqual(self, path, source): + inspected_src = inspect.getsource(source) + with open(path) as src: + self.assertEqual( + src.read().splitlines(True), + inspected_src.splitlines(True) + ) + + def test_getsource_reload(self): + # see issue 1218234 + with _ready_to_import('reload_bug', self.src_before) as (name, path): + module = importlib.import_module(name) + self.assertInspectEqual(path, module) + with open(path, 'w') as src: + src.write(self.src_after) + self.assertInspectEqual(path, module) def test_main(): @@ -2308,7 +3187,8 @@ def test_main(): TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject, - TestBoundArguments, TestGetClosureVars + TestBoundArguments, TestSignaturePrivateHelpers, TestGetClosureVars, + TestUnwrap, TestMain, TestReload ) if __name__ == "__main__": |
