diff options
Diffstat (limited to 'Lib/test/test_inspect.py')
-rw-r--r-- | Lib/test/test_inspect.py | 712 |
1 files changed, 528 insertions, 184 deletions
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 30b1556cbfb..4b7ee4ed682 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -5,16 +5,15 @@ import unittest import inspect import linecache import datetime -from UserList import UserList -from UserDict import UserDict +import collections +import os +import shutil +from os.path import normcase -from test.test_support import run_unittest, check_py3k_warnings +from test.support import run_unittest, TESTFN, DirsOnSysPath -with check_py3k_warnings( - ("tuple parameter unpacking has been removed", SyntaxWarning), - quiet=True): - from test import inspect_fodder as mod - from test import inspect_fodder2 as mod2 +from test import inspect_fodder as mod +from test import inspect_fodder2 as mod2 # C module for test_findsource_binary import unicodedata @@ -33,12 +32,19 @@ modfile = mod.__file__ if modfile.endswith(('c', 'o')): modfile = modfile[:-1] -import __builtin__ +# Normalize file names: on Windows, the case of file names of compiled +# modules depends on the path used to start the python executable. +modfile = normcase(modfile) + +def revise(filename, *args): + return (normcase(filename),) + args + +import builtins try: - 1 // 0 + 1/0 except: - tb = sys.exc_traceback + tb = sys.exc_info()[2] git = mod.StupidGit() @@ -59,12 +65,12 @@ class IsTestBase(unittest.TestCase): self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) def generator_function_example(self): - for i in xrange(2): + for i in range(2): yield i class TestPredicates(IsTestBase): def test_sixteen(self): - count = len(filter(lambda x:x.startswith('is'), dir(inspect))) + count = len([x for x in dir(inspect) if x.startswith('is')]) # This test is here for remember you to update Doc/library/inspect.rst # which claims there are 16 such functions expected = 16 @@ -75,16 +81,15 @@ class TestPredicates(IsTestBase): def test_excluding_predicates(self): self.istest(inspect.isbuiltin, 'sys.exit') self.istest(inspect.isbuiltin, '[].append') - self.istest(inspect.iscode, 'mod.spam.func_code') + self.istest(inspect.iscode, 'mod.spam.__code__') self.istest(inspect.isframe, 'tb.tb_frame') self.istest(inspect.isfunction, 'mod.spam') - self.istest(inspect.ismethod, 'mod.StupidGit.abuse') + self.istest(inspect.isfunction, 'mod.StupidGit.abuse') self.istest(inspect.ismethod, 'git.argue') self.istest(inspect.ismodule, 'mod') self.istest(inspect.istraceback, 'tb') - self.istest(inspect.isdatadescriptor, '__builtin__.file.closed') - self.istest(inspect.isdatadescriptor, '__builtin__.file.softspace') - self.istest(inspect.isgenerator, '(x for x in xrange(2))') + self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory') + self.istest(inspect.isgenerator, '(x for x in range(2))') self.istest(inspect.isgeneratorfunction, 'generator_function_example') if hasattr(types, 'GetSetDescriptorType'): self.istest(inspect.isgetsetdescriptor, @@ -104,9 +109,6 @@ class TestPredicates(IsTestBase): self.istest(inspect.isclass, 'mod.StupidGit') self.assertTrue(inspect.isclass(list)) - class newstyle(object): pass - self.assertTrue(inspect.isclass(newstyle)) - class CustomGetattr(object): def __getattr__(self, attr): return None @@ -125,8 +127,7 @@ class TestPredicates(IsTestBase): def test_isabstract(self): from abc import ABCMeta, abstractmethod - class AbstractClassExample(object): - __metaclass__ = ABCMeta + class AbstractClassExample(metaclass=ABCMeta): @abstractmethod def foo(self): @@ -158,23 +159,23 @@ class TestInterpreterStack(IsTestBase): def test_stack(self): self.assertTrue(len(mod.st) >= 5) - self.assertEqual(mod.st[0][1:], + self.assertEqual(revise(*mod.st[0][1:]), (modfile, 16, 'eggs', [' st = inspect.stack()\n'], 0)) - self.assertEqual(mod.st[1][1:], + self.assertEqual(revise(*mod.st[1][1:]), (modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0)) - self.assertEqual(mod.st[2][1:], + self.assertEqual(revise(*mod.st[2][1:]), (modfile, 43, 'argue', [' spam(a, b, c)\n'], 0)) - self.assertEqual(mod.st[3][1:], + self.assertEqual(revise(*mod.st[3][1:]), (modfile, 39, 'abuse', [' self.argue(a, b, c)\n'], 0)) def test_trace(self): self.assertEqual(len(git.tr), 3) - self.assertEqual(git.tr[0][1:], (modfile, 43, 'argue', - [' spam(a, b, c)\n'], 0)) - self.assertEqual(git.tr[1][1:], (modfile, 9, 'spam', - [' eggs(b + d, c + f)\n'], 0)) - self.assertEqual(git.tr[2][1:], (modfile, 18, 'eggs', - [' q = y // 0\n'], 0)) + self.assertEqual(revise(*git.tr[0][1:]), + (modfile, 43, 'argue', [' spam(a, b, c)\n'], 0)) + self.assertEqual(revise(*git.tr[1][1:]), + (modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0)) + self.assertEqual(revise(*git.tr[2][1:]), + (modfile, 18, 'eggs', [' q = y / 0\n'], 0)) def test_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr) @@ -187,20 +188,20 @@ class TestInterpreterStack(IsTestBase): def test_previous_frame(self): args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) - self.assertEqual(args, ['a', 'b', 'c', 'd', ['e', ['f']]]) + self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f']) self.assertEqual(varargs, 'g') self.assertEqual(varkw, 'h') self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals), - '(a=7, b=8, c=9, d=3, (e=4, (f=5,)), *g=(), **h={})') + '(a=7, b=8, c=9, d=3, e=4, f=5, *g=(), **h={})') class GetSourceBase(unittest.TestCase): # Subclasses must override. - fodderFile = None + fodderModule = None def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - with open(inspect.getsourcefile(self.fodderFile)) as fp: + with open(inspect.getsourcefile(self.fodderModule)) as fp: self.source = fp.read() def sourcerange(self, top, bottom): @@ -212,7 +213,7 @@ class GetSourceBase(unittest.TestCase): self.sourcerange(top, bottom)) class TestRetrievingSourceCode(GetSourceBase): - fodderFile = mod + fodderModule = mod def test_getclasses(self): classes = inspect.getmembers(mod, inspect.isclass) @@ -223,11 +224,13 @@ class TestRetrievingSourceCode(GetSourceBase): ('StupidGit', mod.StupidGit)]) tree = inspect.getclasstree([cls[1] for cls in classes], 1) self.assertEqual(tree, - [(mod.ParrotDroppings, ()), - (mod.StupidGit, ()), - [(mod.MalodorousPervert, (mod.StupidGit,)), - [(mod.FesteringGob, (mod.MalodorousPervert, - mod.ParrotDroppings)) + [(object, ()), + [(mod.ParrotDroppings, (object,)), + (mod.StupidGit, (object,)), + [(mod.MalodorousPervert, (mod.StupidGit,)), + [(mod.FesteringGob, (mod.MalodorousPervert, + mod.ParrotDroppings)) + ] ] ] ]) @@ -264,7 +267,7 @@ class TestRetrievingSourceCode(GetSourceBase): # Do it again (check the caching isn't broken) self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod) # Check a builtin - self.assertEqual(inspect.getmodule(str), sys.modules["__builtin__"]) + self.assertEqual(inspect.getmodule(str), sys.modules["builtins"]) # Check filename override self.assertEqual(inspect.getmodule(None, modfile), mod) @@ -273,13 +276,13 @@ class TestRetrievingSourceCode(GetSourceBase): self.assertSourceEqual(mod.StupidGit, 21, 46) def test_getsourcefile(self): - self.assertEqual(inspect.getsourcefile(mod.spam), modfile) - self.assertEqual(inspect.getsourcefile(git.abuse), modfile) + self.assertEqual(normcase(inspect.getsourcefile(mod.spam)), modfile) + self.assertEqual(normcase(inspect.getsourcefile(git.abuse)), modfile) fn = "_non_existing_filename_used_for_sourcefile_test.py" co = compile("None", fn, "exec") self.assertEqual(inspect.getsourcefile(co), None) linecache.cache[co.co_filename] = (1, None, "None", co.co_filename) - self.assertEqual(inspect.getsourcefile(co), fn) + self.assertEqual(normcase(inspect.getsourcefile(co)), fn) def test_getfile(self): self.assertEqual(inspect.getfile(mod.StupidGit), mod.__file__) @@ -290,8 +293,8 @@ class TestRetrievingSourceCode(GetSourceBase): m = sys.modules[name] = ModuleType(name) m.__file__ = "<string>" # hopefully not a real filename... m.__loader__ = "dummy" # pretend the filename is understood by a loader - exec "def x(): pass" in m.__dict__ - self.assertEqual(inspect.getsourcefile(m.x.func_code), '<string>') + exec("def x(): pass", m.__dict__) + self.assertEqual(inspect.getsourcefile(m.x.__code__), '<string>') del sys.modules[name] inspect.getmodule(compile('a=10','','single')) @@ -307,13 +310,13 @@ class TestRetrievingSourceCode(GetSourceBase): linecache.getlines = monkey try: ns = {} - exec compile(source, fn, 'single') in ns + exec(compile(source, fn, 'single'), ns) inspect.getsource(ns["x"]) finally: linecache.getlines = getlines class TestDecorators(GetSourceBase): - fodderFile = mod2 + fodderModule = mod2 def test_wrapped_decorator(self): self.assertSourceEqual(mod2.wrapped, 14, 17) @@ -322,7 +325,7 @@ class TestDecorators(GetSourceBase): self.assertSourceEqual(mod2.gone, 9, 10) class TestOneliners(GetSourceBase): - fodderFile = mod2 + fodderModule = mod2 def test_oneline_lambda(self): # Test inspect.getsource with a one-line lambda function. self.assertSourceEqual(mod2.oll, 25, 25) @@ -364,7 +367,7 @@ class TestOneliners(GetSourceBase): self.assertSourceEqual(mod2.anonymous, 55, 55) class TestBuggyCases(GetSourceBase): - fodderFile = mod2 + fodderModule = mod2 def test_with_comment(self): self.assertSourceEqual(mod2.with_comment, 58, 59) @@ -404,6 +407,24 @@ class TestBuggyCases(GetSourceBase): self.assertEqual(inspect.findsource(co), (lines,0)) self.assertEqual(inspect.getsource(co), lines[0]) +class TestNoEOL(GetSourceBase): + def __init__(self, *args, **kwargs): + self.tempdir = TESTFN + '_dir' + os.mkdir(self.tempdir) + with open(os.path.join(self.tempdir, + 'inspect_fodder3%spy' % os.extsep), 'w') as f: + f.write("class X:\n pass # No EOL") + with DirsOnSysPath(self.tempdir): + import inspect_fodder3 as mod3 + self.fodderModule = mod3 + GetSourceBase.__init__(self, *args, **kwargs) + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def test_class(self): + self.assertSourceEqual(self.fodderModule.X, 1, 2) + class _BrokenDataDescriptor(object): """ @@ -436,17 +457,6 @@ def attrs_wo_objs(cls): class TestClassesAndFunctions(unittest.TestCase): - def test_classic_mro(self): - # Test classic-class method resolution order. - class A: pass - class B(A): pass - class C(A): pass - class D(B, C): pass - - expected = (D, B, A, C) - got = inspect.getmro(D) - self.assertEqual(expected, got) - def test_newstyle_mro(self): # The same w/ new-class MRO. class A(object): pass @@ -458,9 +468,8 @@ class TestClassesAndFunctions(unittest.TestCase): got = inspect.getmro(D) self.assertEqual(expected, got) - def assertArgSpecEquals(self, routine, args_e, varargs_e = None, - varkw_e = None, defaults_e = None, - formatted = None): + def assertArgSpecEquals(self, routine, args_e, varargs_e=None, + varkw_e=None, defaults_e=None, formatted=None): args, varargs, varkw, defaults = inspect.getargspec(routine) self.assertEqual(args, args_e) self.assertEqual(varargs, varargs_e) @@ -470,13 +479,54 @@ class TestClassesAndFunctions(unittest.TestCase): self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults), formatted) + def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None, + varkw_e=None, defaults_e=None, + kwonlyargs_e=[], kwonlydefaults_e=None, + ann_e={}, formatted=None): + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ + inspect.getfullargspec(routine) + self.assertEqual(args, args_e) + self.assertEqual(varargs, varargs_e) + self.assertEqual(varkw, varkw_e) + self.assertEqual(defaults, defaults_e) + self.assertEqual(kwonlyargs, kwonlyargs_e) + self.assertEqual(kwonlydefaults, kwonlydefaults_e) + self.assertEqual(ann, ann_e) + if formatted is not None: + self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults, + kwonlyargs, kwonlydefaults, ann), + formatted) + def test_getargspec(self): - self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted = '(x, y)') + self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted='(x, y)') self.assertArgSpecEquals(mod.spam, - ['a', 'b', 'c', 'd', ['e', ['f']]], - 'g', 'h', (3, (4, (5,))), - '(a, b, c, d=3, (e, (f,))=(4, (5,)), *g, **h)') + ['a', 'b', 'c', 'd', 'e', 'f'], + 'g', 'h', (3, 4, 5), + '(a, b, c, d=3, e=4, f=5, *g, **h)') + + self.assertRaises(ValueError, self.assertArgSpecEquals, + mod2.keyworded, []) + + self.assertRaises(ValueError, self.assertArgSpecEquals, + mod2.annotated, []) + self.assertRaises(ValueError, self.assertArgSpecEquals, + mod2.keyword_only_arg, []) + + + def test_getfullargspec(self): + self.assertFullArgSpecEquals(mod2.keyworded, [], varargs_e='arg1', + kwonlyargs_e=['arg2'], + kwonlydefaults_e={'arg2':1}, + formatted='(*arg1, arg2=1)') + + self.assertFullArgSpecEquals(mod2.annotated, ['arg1'], + ann_e={'arg1' : list}, + formatted='(arg1: list)') + self.assertFullArgSpecEquals(mod2.keyword_only_arg, [], + kwonlyargs_e=['arg'], + formatted='(*, arg)') + def test_getargspec_method(self): class A(object): @@ -484,28 +534,9 @@ class TestClassesAndFunctions(unittest.TestCase): pass self.assertArgSpecEquals(A.m, ['self']) - def test_getargspec_sublistofone(self): - with check_py3k_warnings( - ("tuple parameter unpacking has been removed", SyntaxWarning), - ("parenthesized argument names are invalid", SyntaxWarning)): - exec 'def sublistOfOne((foo,)): return 1' - self.assertArgSpecEquals(sublistOfOne, [['foo']]) - - exec 'def fakeSublistOfOne((foo)): return 1' - self.assertArgSpecEquals(fakeSublistOfOne, ['foo']) - - - def _classify_test(self, newstyle): - """Helper for testing that classify_class_attrs finds a bunch of - different kinds of attributes on a given class. - """ - if newstyle: - base = object - else: - class base: - pass + def test_classify_newstyle(self): + class A(object): - class A(base): def s(): pass s = staticmethod(s) @@ -528,13 +559,15 @@ class TestClassesAndFunctions(unittest.TestCase): 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') - self.assertIn(('m', 'method', A), attrs, 'missing plain method') + self.assertIn(('m', 'method', A), attrs, + 'missing plain method: %r' % attrs) self.assertIn(('m1', 'method', A), attrs, 'missing plain method') self.assertIn(('datablob', 'data', A), attrs, 'missing data') self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') class B(A): + def m(self): pass attrs = attrs_wo_objs(B) @@ -549,6 +582,7 @@ class TestClassesAndFunctions(unittest.TestCase): class C(A): + def m(self): pass def c(self): pass @@ -563,14 +597,12 @@ class TestClassesAndFunctions(unittest.TestCase): self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') class D(B, C): + def m1(self): pass attrs = attrs_wo_objs(D) self.assertIn(('s', 'static method', A), attrs, 'missing static method') - if newstyle: - self.assertIn(('c', 'method', C), attrs, 'missing plain method') - else: - self.assertIn(('c', 'class method', A), attrs, 'missing class method') + self.assertIn(('c', 'method', C), attrs, 'missing plain method') self.assertIn(('p', 'property', A), attrs, 'missing property') self.assertIn(('m', 'method', B), attrs, 'missing plain method') self.assertIn(('m1', 'method', D), attrs, 'missing plain method') @@ -578,49 +610,53 @@ class TestClassesAndFunctions(unittest.TestCase): self.assertIn(('md', 'method', A), attrs, 'missing method descriptor') self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor') - - def test_classify_oldstyle(self): - """classify_class_attrs finds static methods, class methods, - properties, normal methods, and data attributes on an old-style - class. - """ - self._classify_test(False) - - - def test_classify_newstyle(self): - """Just like test_classify_oldstyle, but for a new-style class. - """ - self._classify_test(True) - def test_classify_builtin_types(self): # Simple sanity check that all built-in types can have their # attributes classified. - for name in dir(__builtin__): - builtin = getattr(__builtin__, name) + for name in dir(__builtins__): + builtin = getattr(__builtins__, name) if isinstance(builtin, type): inspect.classify_class_attrs(builtin) - def test_getmembers_method(self): - # Old-style classes - class B: - def f(self): + def test_getmembers_descriptors(self): + class A(object): + dd = _BrokenDataDescriptor() + md = _BrokenMethodDescriptor() + + def pred_wrapper(pred): + # A quick'n'dirty way to discard standard attributes of new-style + # classes. + class Empty(object): pass + def wrapped(x): + if '__name__' in dir(x) and hasattr(Empty, x.__name__): + return False + return pred(x) + return wrapped - self.assertIn(('f', B.f), inspect.getmembers(B)) - # contrary to spec, ismethod() is also True for unbound methods - # (see #1785) - self.assertIn(('f', B.f), inspect.getmembers(B, inspect.ismethod)) - b = B() - self.assertIn(('f', b.f), inspect.getmembers(b)) - self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod)) + ismethoddescriptor = pred_wrapper(inspect.ismethoddescriptor) + isdatadescriptor = pred_wrapper(inspect.isdatadescriptor) + + self.assertEqual(inspect.getmembers(A, ismethoddescriptor), + [('md', A.__dict__['md'])]) + self.assertEqual(inspect.getmembers(A, isdatadescriptor), + [('dd', A.__dict__['dd'])]) + + class B(A): + pass + + self.assertEqual(inspect.getmembers(B, ismethoddescriptor), + [('md', A.__dict__['md'])]) + self.assertEqual(inspect.getmembers(B, isdatadescriptor), + [('dd', A.__dict__['dd'])]) - # New-style classes - class B(object): + def test_getmembers_method(self): + class B: def f(self): pass self.assertIn(('f', B.f), inspect.getmembers(B)) - self.assertIn(('f', B.f), inspect.getmembers(B, inspect.ismethod)) + self.assertNotIn(('f', B.f), inspect.getmembers(B, inspect.ismethod)) b = B() self.assertIn(('f', b.f), inspect.getmembers(b)) self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod)) @@ -628,9 +664,6 @@ class TestClassesAndFunctions(unittest.TestCase): class TestGetcallargsFunctions(unittest.TestCase): - # tuple parameters are named '.1', '.2', etc. - is_tuplename = re.compile(r'^\.\d+$').match - def assertEqualCallArgs(self, func, call_params_string, locs=None): locs = dict(locs or {}, func=func) r1 = eval('func(%s)' % call_params_string, None, locs) @@ -642,29 +675,25 @@ class TestGetcallargsFunctions(unittest.TestCase): locs = dict(locs or {}, func=func) try: eval('func(%s)' % call_param_string, None, locs) - except Exception, ex1: - pass + except Exception as e: + ex1 = e else: self.fail('Exception not raised') try: eval('inspect.getcallargs(func, %s)' % call_param_string, None, locs) - except Exception, ex2: - pass + except Exception as e: + ex2 = e else: self.fail('Exception not raised') self.assertIs(type(ex1), type(ex2)) self.assertEqual(str(ex1), str(ex2)) + del ex1, ex2 def makeCallable(self, signature): - """Create a function that returns its locals(), excluding the - autogenerated '.1', '.2', etc. tuple param names (if any).""" - with check_py3k_warnings( - ("tuple parameter unpacking has been removed", SyntaxWarning), - quiet=True): - code = ("lambda %s: dict(i for i in locals().items() " - "if not is_tuplename(i[0]))") - return eval(code % signature, {'is_tuplename' : self.is_tuplename}) + """Create a function that returns its locals()""" + code = "lambda %s: locals()" + return eval(code % signature) def test_plain(self): f = self.makeCallable('a, b=1') @@ -683,16 +712,11 @@ class TestGetcallargsFunctions(unittest.TestCase): self.assertEqualCallArgs(f, '2, **{"b":3}') self.assertEqualCallArgs(f, '**{"b":3, "a":2}') # expand UserList / UserDict - self.assertEqualCallArgs(f, '*UserList([2])') - self.assertEqualCallArgs(f, '*UserList([2, 3])') - self.assertEqualCallArgs(f, '**UserDict(a=2)') - self.assertEqualCallArgs(f, '2, **UserDict(b=3)') - self.assertEqualCallArgs(f, 'b=2, **UserDict(a=3)') - # unicode keyword args - self.assertEqualCallArgs(f, '**{u"a":2}') - self.assertEqualCallArgs(f, 'b=3, **{u"a":2}') - self.assertEqualCallArgs(f, '2, **{u"b":3}') - self.assertEqualCallArgs(f, '**{u"b":3, u"a":2}') + self.assertEqualCallArgs(f, '*collections.UserList([2])') + self.assertEqualCallArgs(f, '*collections.UserList([2, 3])') + self.assertEqualCallArgs(f, '**collections.UserDict(a=2)') + self.assertEqualCallArgs(f, '2, **collections.UserDict(b=3)') + self.assertEqualCallArgs(f, 'b=2, **collections.UserDict(a=3)') def test_varargs(self): f = self.makeCallable('a, b=1, *c') @@ -701,7 +725,7 @@ class TestGetcallargsFunctions(unittest.TestCase): self.assertEqualCallArgs(f, '2, 3, 4') self.assertEqualCallArgs(f, '*(2,3,4)') self.assertEqualCallArgs(f, '2, *[3,4]') - self.assertEqualCallArgs(f, '2, 3, *UserList([4])') + self.assertEqualCallArgs(f, '2, 3, *collections.UserList([4])') def test_varkw(self): f = self.makeCallable('a, b=1, **c') @@ -711,13 +735,9 @@ class TestGetcallargsFunctions(unittest.TestCase): self.assertEqualCallArgs(f, 'c=4, **{"a":2, "b":3}') self.assertEqualCallArgs(f, '2, c=4, **{"b":3}') self.assertEqualCallArgs(f, 'b=2, **{"a":3, "c":4}') - self.assertEqualCallArgs(f, '**UserDict(a=2, b=3, c=4)') - self.assertEqualCallArgs(f, '2, c=4, **UserDict(b=3)') - self.assertEqualCallArgs(f, 'b=2, **UserDict(a=3, c=4)') - # unicode keyword args - self.assertEqualCallArgs(f, 'c=4, **{u"a":2, u"b":3}') - self.assertEqualCallArgs(f, '2, c=4, **{u"b":3}') - self.assertEqualCallArgs(f, 'b=2, **{u"a":3, u"c":4}') + self.assertEqualCallArgs(f, '**collections.UserDict(a=2, b=3, c=4)') + self.assertEqualCallArgs(f, '2, c=4, **collections.UserDict(b=3)') + self.assertEqualCallArgs(f, 'b=2, **collections.UserDict(a=3, c=4)') def test_varkw_only(self): # issue11256: @@ -726,29 +746,48 @@ class TestGetcallargsFunctions(unittest.TestCase): self.assertEqualCallArgs(f, 'a=1') self.assertEqualCallArgs(f, 'a=1, b=2') self.assertEqualCallArgs(f, 'c=3, **{"a": 1, "b": 2}') - self.assertEqualCallArgs(f, '**UserDict(a=1, b=2)') - self.assertEqualCallArgs(f, 'c=3, **UserDict(a=1, b=2)') - - def test_tupleargs(self): - f = self.makeCallable('(b,c), (d,(e,f))=(0,[1,2])') - self.assertEqualCallArgs(f, '(2,3)') - self.assertEqualCallArgs(f, '[2,3]') - self.assertEqualCallArgs(f, 'UserList([2,3])') - self.assertEqualCallArgs(f, '(2,3), (4,(5,6))') - self.assertEqualCallArgs(f, '(2,3), (4,[5,6])') - self.assertEqualCallArgs(f, '(2,3), [4,UserList([5,6])]') + self.assertEqualCallArgs(f, '**collections.UserDict(a=1, b=2)') + self.assertEqualCallArgs(f, 'c=3, **collections.UserDict(a=1, b=2)') + + def test_keyword_only(self): + f = self.makeCallable('a=3, *, c, d=2') + self.assertEqualCallArgs(f, 'c=3') + self.assertEqualCallArgs(f, 'c=3, a=3') + self.assertEqualCallArgs(f, 'a=2, c=4') + self.assertEqualCallArgs(f, '4, c=4') + self.assertEqualException(f, '') + self.assertEqualException(f, '3') + self.assertEqualException(f, 'a=3') + self.assertEqualException(f, 'd=4') + + f = self.makeCallable('*, c, d=2') + self.assertEqualCallArgs(f, 'c=3') + self.assertEqualCallArgs(f, 'c=3, d=4') + self.assertEqualCallArgs(f, 'd=4, c=3') def test_multiple_features(self): - f = self.makeCallable('a, b=2, (c,(d,e))=(3,[4,5]), *f, **g') - self.assertEqualCallArgs(f, '2, 3, (4,[5,6]), 7') - self.assertEqualCallArgs(f, '2, 3, *[(4,[5,6]), 7], x=8') + f = self.makeCallable('a, b=2, *f, **g') + self.assertEqualCallArgs(f, '2, 3, 7') + self.assertEqualCallArgs(f, '2, 3, x=8') self.assertEqualCallArgs(f, '2, 3, x=8, *[(4,[5,6]), 7]') self.assertEqualCallArgs(f, '2, x=8, *[3, (4,[5,6]), 7], y=9') self.assertEqualCallArgs(f, 'x=8, *[2, 3, (4,[5,6])], y=9') - self.assertEqualCallArgs(f, 'x=8, *UserList([2, 3, (4,[5,6])]), ' - '**{"y":9, "z":10}') - self.assertEqualCallArgs(f, '2, x=8, *UserList([3, (4,[5,6])]), ' - '**UserDict(y=9, z=10)') + self.assertEqualCallArgs(f, 'x=8, *collections.UserList(' + '[2, 3, (4,[5,6])]), **{"y":9, "z":10}') + self.assertEqualCallArgs(f, '2, x=8, *collections.UserList([3, ' + '(4,[5,6])]), **collections.UserDict(' + 'y=9, z=10)') + + f = self.makeCallable('a, b=2, *f, x, y=99, **g') + self.assertEqualCallArgs(f, '2, 3, x=8') + self.assertEqualCallArgs(f, '2, 3, x=8, *[(4,[5,6]), 7]') + self.assertEqualCallArgs(f, '2, x=8, *[3, (4,[5,6]), 7], y=9, z=10') + self.assertEqualCallArgs(f, 'x=8, *[2, 3, (4,[5,6])], y=9, z=10') + self.assertEqualCallArgs(f, 'x=8, *collections.UserList(' + '[2, 3, (4,[5,6])]), q=0, **{"y":9, "z":10}') + self.assertEqualCallArgs(f, '2, x=8, *collections.UserList([3, ' + '(4,[5,6])]), q=0, **collections.UserDict(' + 'y=9, z=10)') def test_errors(self): f0 = self.makeCallable('') @@ -771,7 +810,8 @@ class TestGetcallargsFunctions(unittest.TestCase): self.assertEqualException(f, '2, 3, 4') self.assertEqualException(f, '1, 2, 3, a=1') self.assertEqualException(f, '2, 3, 4, c=5') - self.assertEqualException(f, '2, 3, 4, a=1, c=5') + # XXX: success of this one depends on dict order + ## self.assertEqualException(f, '2, 3, 4, a=1, c=5') # f got an unexpected keyword argument self.assertEqualException(f, 'c=2') self.assertEqualException(f, '2, c=3') @@ -786,14 +826,13 @@ class TestGetcallargsFunctions(unittest.TestCase): # - for functions and bound methods: unexpected keyword 'c' # - for unbound methods: multiple values for keyword 'a' #self.assertEqualException(f, '1, c=3, a=2') - f = self.makeCallable('(a,b)=(0,1)') - self.assertEqualException(f, '1') - self.assertEqualException(f, '[1]') - self.assertEqualException(f, '(1,2,3)') # issue11256: f3 = self.makeCallable('**c') self.assertEqualException(f3, '1, 2') self.assertEqualException(f3, '1, 2, a=1, b=2') + f4 = self.makeCallable('*, a, b=0') + self.assertEqualException(f3, '1, 2') + self.assertEqualException(f3, '1, 2, a=1, b=2') class TestGetcallargsMethods(TestGetcallargsFunctions): @@ -828,12 +867,317 @@ class TestGetcallargsUnboundMethods(TestGetcallargsMethods): locs = dict(locs or {}, inst=self.inst) return (func, 'inst,' + call_params_string, locs) + +class TestGetattrStatic(unittest.TestCase): + + def test_basic(self): + class Thing(object): + x = object() + + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + self.assertEqual(inspect.getattr_static(thing, 'x', None), Thing.x) + with self.assertRaises(AttributeError): + inspect.getattr_static(thing, 'y') + + self.assertEqual(inspect.getattr_static(thing, 'y', 3), 3) + + def test_inherited(self): + class Thing(object): + x = object() + class OtherThing(Thing): + pass + + something = OtherThing() + self.assertEqual(inspect.getattr_static(something, 'x'), Thing.x) + + def test_instance_attr(self): + class Thing(object): + x = 2 + def __init__(self, x): + self.x = x + thing = Thing(3) + self.assertEqual(inspect.getattr_static(thing, 'x'), 3) + del thing.x + self.assertEqual(inspect.getattr_static(thing, 'x'), 2) + + def test_property(self): + class Thing(object): + @property + def x(self): + raise AttributeError("I'm pretending not to exist") + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + + def test_descriptor_raises_AttributeError(self): + class descriptor(object): + def __get__(*_): + raise AttributeError("I'm pretending not to exist") + desc = descriptor() + class Thing(object): + x = desc + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), desc) + + def test_classAttribute(self): + class Thing(object): + x = object() + + self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.x) + + def test_inherited_classattribute(self): + class Thing(object): + x = object() + class OtherThing(Thing): + pass + + self.assertEqual(inspect.getattr_static(OtherThing, 'x'), Thing.x) + + def test_slots(self): + class Thing(object): + y = 'bar' + __slots__ = ['x'] + def __init__(self): + self.x = 'foo' + thing = Thing() + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + self.assertEqual(inspect.getattr_static(thing, 'y'), 'bar') + + del thing.x + self.assertEqual(inspect.getattr_static(thing, 'x'), Thing.x) + + def test_metaclass(self): + class meta(type): + attr = 'foo' + class Thing(object, metaclass=meta): + pass + self.assertEqual(inspect.getattr_static(Thing, 'attr'), 'foo') + + class sub(meta): + pass + class OtherThing(object, metaclass=sub): + x = 3 + self.assertEqual(inspect.getattr_static(OtherThing, 'attr'), 'foo') + + class OtherOtherThing(OtherThing): + pass + # this test is odd, but it was added as it exposed a bug + self.assertEqual(inspect.getattr_static(OtherOtherThing, 'x'), 3) + + def test_no_dict_no_slots(self): + self.assertEqual(inspect.getattr_static(1, 'foo', None), None) + self.assertNotEqual(inspect.getattr_static('foo', 'lower'), None) + + def test_no_dict_no_slots_instance_member(self): + # returns descriptor + with open(__file__) as handle: + self.assertEqual(inspect.getattr_static(handle, 'name'), type(handle).name) + + def test_inherited_slots(self): + # returns descriptor + class Thing(object): + __slots__ = ['x'] + def __init__(self): + self.x = 'foo' + + class OtherThing(Thing): + pass + # it would be nice if this worked... + # we get the descriptor instead of the instance attribute + self.assertEqual(inspect.getattr_static(OtherThing(), 'x'), Thing.x) + + def test_descriptor(self): + class descriptor(object): + def __get__(self, instance, owner): + return 3 + class Foo(object): + d = descriptor() + + foo = Foo() + + # for a non data descriptor we return the instance attribute + foo.__dict__['d'] = 1 + self.assertEqual(inspect.getattr_static(foo, 'd'), 1) + + # if the descriptor is a data-desciptor we should return the + # descriptor + descriptor.__set__ = lambda s, i, v: None + self.assertEqual(inspect.getattr_static(foo, 'd'), Foo.__dict__['d']) + + + def test_metaclass_with_descriptor(self): + class descriptor(object): + def __get__(self, instance, owner): + return 3 + class meta(type): + d = descriptor() + class Thing(object, metaclass=meta): + pass + self.assertEqual(inspect.getattr_static(Thing, 'd'), meta.__dict__['d']) + + + def test_class_as_property(self): + class Base(object): + foo = 3 + + class Something(Base): + executed = False + @property + def __class__(self): + self.executed = True + return object + + instance = Something() + self.assertEqual(inspect.getattr_static(instance, 'foo'), 3) + self.assertFalse(instance.executed) + self.assertEqual(inspect.getattr_static(Something, 'foo'), 3) + + def test_mro_as_property(self): + class Meta(type): + @property + def __mro__(self): + return (object,) + + class Base(object): + foo = 3 + + class Something(Base, metaclass=Meta): + pass + + self.assertEqual(inspect.getattr_static(Something(), 'foo'), 3) + self.assertEqual(inspect.getattr_static(Something, 'foo'), 3) + + def test_dict_as_property(self): + test = self + test.called = False + + class Foo(dict): + a = 3 + @property + def __dict__(self): + test.called = True + return {} + + foo = Foo() + foo.a = 4 + self.assertEqual(inspect.getattr_static(foo, 'a'), 3) + self.assertFalse(test.called) + + def test_custom_object_dict(self): + test = self + test.called = False + + class Custom(dict): + def get(self, key, default=None): + test.called = True + super().get(key, default) + + class Foo(object): + a = 3 + foo = Foo() + foo.__dict__ = Custom() + self.assertEqual(inspect.getattr_static(foo, 'a'), 3) + self.assertFalse(test.called) + + def test_metaclass_dict_as_property(self): + class Meta(type): + @property + def __dict__(self): + self.executed = True + + class Thing(metaclass=Meta): + executed = False + + def __init__(self): + self.spam = 42 + + instance = Thing() + self.assertEqual(inspect.getattr_static(instance, "spam"), 42) + self.assertFalse(Thing.executed) + + def test_module(self): + sentinel = object() + self.assertIsNot(inspect.getattr_static(sys, "version", sentinel), + sentinel) + + def test_metaclass_with_metaclass_with_dict_as_property(self): + class MetaMeta(type): + @property + def __dict__(self): + self.executed = True + return dict(spam=42) + + class Meta(type, metaclass=MetaMeta): + executed = False + + class Thing(metaclass=Meta): + pass + + with self.assertRaises(AttributeError): + inspect.getattr_static(Thing, "spam") + self.assertFalse(Thing.executed) + +class TestGetGeneratorState(unittest.TestCase): + + def setUp(self): + def number_generator(): + for number in range(5): + yield number + self.generator = number_generator() + + def _generatorstate(self): + return inspect.getgeneratorstate(self.generator) + + def test_created(self): + self.assertEqual(self._generatorstate(), inspect.GEN_CREATED) + + def test_suspended(self): + next(self.generator) + self.assertEqual(self._generatorstate(), inspect.GEN_SUSPENDED) + + def test_closed_after_exhaustion(self): + for i in self.generator: + pass + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + self.generator.throw(RuntimeError) + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + + def test_running(self): + # As mentioned on issue #10220, checking for the RUNNING state only + # makes sense inside the generator itself. + # The following generator checks for this by using the closure's + # reference to self and the generator state checking helper method + def running_check_generator(): + for number in range(5): + self.assertEqual(self._generatorstate(), inspect.GEN_RUNNING) + yield number + self.assertEqual(self._generatorstate(), inspect.GEN_RUNNING) + self.generator = running_check_generator() + # Running up to the first yield + next(self.generator) + # Running after the first yield + next(self.generator) + + def test_easy_debugging(self): + # repr() and str() of a generator state should contain the state name + names = 'GEN_CREATED GEN_RUNNING GEN_SUSPENDED GEN_CLOSED'.split() + for name in names: + state = getattr(inspect, name) + self.assertIn(name, repr(state)) + self.assertIn(name, str(state)) + + def test_main(): run_unittest( TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, - TestGetcallargsUnboundMethods) + TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, + TestNoEOL + ) if __name__ == "__main__": test_main() |