Issue #12510: Revise and triple # of calltip tests, with an eye to unittest

use. Make the get_entity 'method' a module function as it did not use 'self'.
Delete buggy _find_constructor function that is not needed, at least in 3.x.
Revise get_argspec so all tests pass.  Add and fix NEWS entries.
This commit is contained in:
Terry Jan Reedy 2012-06-07 19:41:04 -04:00
parent 29471de459
commit 2a2ce4f673
3 changed files with 137 additions and 75 deletions

View File

@ -100,52 +100,53 @@ class CallTips:
return rpcclt.remotecall("exec", "get_the_calltip", return rpcclt.remotecall("exec", "get_the_calltip",
(expression,), {}) (expression,), {})
else: else:
entity = self.get_entity(expression) return get_argspec(get_entity(expression))
return get_argspec(entity)
def get_entity(self, expression): def get_entity(expression):
"""Return the object corresponding to expression evaluated """Return the object corresponding to expression evaluated
in a namespace spanning sys.modules and __main.dict__. in a namespace spanning sys.modules and __main.dict__.
""" """
if expression: if expression:
namespace = sys.modules.copy() namespace = sys.modules.copy()
namespace.update(__main__.__dict__) namespace.update(__main__.__dict__)
try: try:
return eval(expression, namespace) return eval(expression, namespace)
except BaseException: except BaseException:
# An uncaught exception closes idle, and eval can raise any # An uncaught exception closes idle, and eval can raise any
# exception, especially if user classes are involved. # exception, especially if user classes are involved.
return None return None
def _find_constructor(class_ob): # The following are used in both get_argspec and tests
"Find the nearest __init__() in the class tree." _self_pat = re.compile('self\,?\s*')
try: _default_callable_argspec = "No docstring, see docs."
return class_ob.__init__.__func__
except AttributeError:
for base in class_ob.__bases__:
init = _find_constructor(base)
if init:
return init
return None
def get_argspec(ob): def get_argspec(ob):
"""Get a string describing the arguments for the given object, '''Return a string describing the arguments and return of a callable object.
only if it is callable."""
For Python-coded functions and methods, the first line is introspected.
Delete 'self' parameter for classes (.__init__) and bound methods.
The last line is the first line of the doc string. For builtins, this typically
includes the arguments in addition to the return value.
'''
argspec = "" argspec = ""
if ob is not None and hasattr(ob, '__call__'): if hasattr(ob, '__call__'):
if isinstance(ob, type): if isinstance(ob, type):
fob = _find_constructor(ob) fob = getattr(ob, '__init__', None)
if fob is None: elif isinstance(ob.__call__, types.MethodType):
fob = lambda: None fob = ob.__call__
elif isinstance(ob, types.MethodType):
fob = ob.__func__
else: else:
fob = ob fob = ob
if isinstance(fob, (types.FunctionType, types.LambdaType)): if isinstance(fob, (types.FunctionType, types.MethodType)):
argspec = inspect.formatargspec(*inspect.getfullargspec(fob)) argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
pat = re.compile('self\,?\s*') if (isinstance(ob, (type, types.MethodType)) or
argspec = pat.sub("", argspec) isinstance(ob.__call__, types.MethodType)):
doc = getattr(ob, "__doc__", "") argspec = _self_pat.sub("", argspec)
if isinstance(ob.__call__, types.MethodType):
doc = ob.__call__.__doc__
else:
doc = getattr(ob, "__doc__", "")
if doc: if doc:
doc = doc.lstrip() doc = doc.lstrip()
pos = doc.find("\n") pos = doc.find("\n")
@ -154,13 +155,16 @@ def get_argspec(ob):
if argspec: if argspec:
argspec += "\n" argspec += "\n"
argspec += doc[:pos] argspec += doc[:pos]
if not argspec:
argspec = _default_callable_argspec
return argspec return argspec
################################################# #################################################
# #
# Test code # Test code tests CallTips.fetch_tip, get_entity, and get_argspec
#
def main(): def main():
# Putting expected in docstrings results in doubled tips for test
def t1(): "()" def t1(): "()"
def t2(a, b=None): "(a, b=None)" def t2(a, b=None): "(a, b=None)"
def t3(a, *args): "(a, *args)" def t3(a, *args): "(a, *args)"
@ -170,39 +174,88 @@ def main():
class TC(object): class TC(object):
"(ai=None, *b)" "(ai=None, *b)"
def __init__(self, ai=None, *b): "(ai=None, *b)" def __init__(self, ai=None, *b): "(self, ai=None, *b)"
def t1(self): "()" def t1(self): "(self)"
def t2(self, ai, b=None): "(ai, b=None)" def t2(self, ai, b=None): "(self, ai, b=None)"
def t3(self, ai, *args): "(ai, *args)" def t3(self, ai, *args): "(self, ai, *args)"
def t4(self, *args): "(*args)" def t4(self, *args): "(self, *args)"
def t5(self, ai, *args): "(ai, *args)" def t5(self, ai, *args): "(self, ai, *args)"
def t6(self, ai, b=None, *args, **kw): "(ai, b=None, *args, **kw)" def t6(self, ai, b=None, *args, **kw): "(self, ai, b=None, *args, **kw)"
@classmethod
__main__.__dict__.update(locals()) def cm(cls, a): "(cls, a)"
@staticmethod
def test(tests): def sm(b): "(b)"
ct = CallTips() def __call__(self, ci): "(ci)"
failed=[]
for t in tests:
expected = t.__doc__ + "\n" + t.__doc__
name = t.__name__
# exercise fetch_tip(), not just get_argspec()
try:
qualified_name = "%s.%s" % (t.__self__.__class__.__name__, name)
except AttributeError:
qualified_name = name
argspec = ct.fetch_tip(qualified_name)
if argspec != expected:
failed.append(t)
fmt = "%s - expected %s, but got %s"
print(fmt % (t.__name__, expected, get_argspec(t)))
print("%d of %d tests failed" % (len(failed), len(tests)))
tc = TC() tc = TC()
tests = (t1, t2, t3, t4, t5, t6,
TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6)
test(tests) # Python classes that inherit builtin methods
class Int(int): "Int(x[, base]) -> integer"
class List(list): "List() -> new empty list"
# Simulate builtin with no docstring for default argspec test
class SB: __call__ = None
__main__.__dict__.update(locals()) # required for get_entity eval()
num_tests = num_fail = 0
tip = CallTips().fetch_tip
def test(expression, expected):
nonlocal num_tests, num_fail
num_tests += 1
argspec = tip(expression)
if argspec != expected:
num_fail += 1
fmt = "%s - expected\n%r\n - but got\n%r"
print(fmt % (expression, expected, argspec))
def test_builtins():
# if first line of a possibly multiline compiled docstring changes,
# must change corresponding test string
test('int', "int(x[, base]) -> integer")
test('Int', Int.__doc__)
test('types.MethodType', "method(function, instance)")
test('list', "list() -> new empty list")
test('List', List.__doc__)
test('list.__new__',
'T.__new__(S, ...) -> a new object with type S, a subtype of T')
test('list.__init__',
'x.__init__(...) initializes x; see help(type(x)) for signature')
append_doc = "L.append(object) -> None -- append object to end"
test('list.append', append_doc)
test('[].append', append_doc)
test('List.append', append_doc)
test('SB()', _default_callable_argspec)
def test_funcs():
for func in (t1, t2, t3, t4, t5, t6, TC,):
fdoc = func.__doc__
test(func.__name__, fdoc + "\n" + fdoc)
for func in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.cm, TC.sm):
fdoc = func.__doc__
test('TC.'+func.__name__, fdoc + "\n" + fdoc)
def test_methods():
for func in (tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6):
fdoc = func.__doc__
test('tc.'+func.__name__, _self_pat.sub("", fdoc) + "\n" + fdoc)
fdoc = tc.__call__.__doc__
test('tc', fdoc + "\n" + fdoc)
def test_non_callables():
# expression evaluates, but not to a callable
for expr in ('0', '0.0' 'num_tests', b'num_tests', '[]', '{}'):
test(expr, '')
# expression does not evaluate, but raises an exception
for expr in ('1a', 'xyx', 'num_tests.xyz', '[int][1]', '{0:int}[1]'):
test(expr, '')
test_builtins()
test_funcs()
test_non_callables()
test_methods()
print("%d of %d tests failed" % (num_fail, num_tests))
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -1,6 +1,14 @@
What's New in IDLE 3.2.4? What's New in IDLE 3.2.4?
========================= =========================
- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE.
Erroneous tool tips have been corrected. Default added for callables.
- Issue10365: File open dialog now works instead of crashing even when
parent window is closed while dialog is open.
- Issue 14876: use user-selected font for highlight configuration.
- Issue #14937: Perform auto-completion of filenames in strings even for - Issue #14937: Perform auto-completion of filenames in strings even for
non-ASCII filenames. Likewise for identifiers. non-ASCII filenames. Likewise for identifiers.

View File

@ -89,14 +89,15 @@ Library
- Issue #14443: Tell rpmbuild to use the correct version of Python in - Issue #14443: Tell rpmbuild to use the correct version of Python in
bdist_rpm. Initial patch by Ross Lagerwall. bdist_rpm. Initial patch by Ross Lagerwall.
- Issue14929: Stop Idle 3.x from closing on Unicode decode errors when grepping. - Issue #14929: Stop Idle 3.x from closing on Unicode decode errors when
Patch by Roger Serwy. grepping. Patch by Roger Serwy.
- Issue12510: Attempting to get invalid tooltip no longer closes Idle. - Issue #12510: Attempting to get invalid tooltip no longer closes Idle.
Original patch by Roger Serwy. Other tooltipss have been corrected or improved and the number of tests
has been tripled. Original patch by Roger Serwy.
- Issue #10365: File open dialog now works instead of crashing - Issue #10365: File open dialog now works instead of crashing even when
even when parent window is closed. Patch by Roger Serwy. the parent window is closed before the dialog. Patch by Roger Serwy.
- Issue #14876: Use user-selected font for highlight configuration. - Issue #14876: Use user-selected font for highlight configuration.