Coverage for idle_test/test_calltip.py: 91%

230 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1"Test calltip, coverage 76%" 

2 

3from idlelib import calltip 

4import unittest 

5from unittest.mock import Mock 

6import textwrap 

7import types 

8import re 

9from idlelib.idle_test.mock_tk import Text 

10 

11 

12# Test Class TC is used in multiple get_argspec test methods 

13class TC: 

14 'doc' 

15 tip = "(ai=None, *b)" 

16 def __init__(self, ai=None, *b): 'doc' 

17 __init__.tip = "(self, ai=None, *b)" 

18 def t1(self): 'doc' 18 ↛ exitline 18 didn't return from function 't1'

19 t1.tip = "(self)" 

20 def t2(self, ai, b=None): 'doc' 20 ↛ exitline 20 didn't return from function 't2'

21 t2.tip = "(self, ai, b=None)" 

22 def t3(self, ai, *args): 'doc' 22 ↛ exitline 22 didn't return from function 't3'

23 t3.tip = "(self, ai, *args)" 

24 def t4(self, *args): 'doc' 24 ↛ exitline 24 didn't return from function 't4'

25 t4.tip = "(self, *args)" 

26 def t5(self, ai, b=None, *args, **kw): 'doc' 26 ↛ exitline 26 didn't return from function 't5'

27 t5.tip = "(self, ai, b=None, *args, **kw)" 

28 def t6(no, self): 'doc' 28 ↛ exitline 28 didn't return from function 't6'

29 t6.tip = "(no, self)" 

30 def __call__(self, ci): 'doc' 30 ↛ exitline 30 didn't return from function '__call__'

31 __call__.tip = "(self, ci)" 

32 def nd(self): pass # No doc. 32 ↛ exitline 32 didn't return from function 'nd'

33 # attaching .tip to wrapped methods does not work 

34 @classmethod 

35 def cm(cls, a): 'doc' 35 ↛ exitline 35 didn't return from function 'cm'

36 @staticmethod 

37 def sm(b): 'doc' 37 ↛ exitline 37 didn't return from function 'sm'

38 

39 

40tc = TC() 

41default_tip = calltip._default_callable_argspec 

42get_spec = calltip.get_argspec 

43 

44 

45class Get_argspecTest(unittest.TestCase): 

46 # The get_spec function must return a string, even if blank. 

47 # Test a variety of objects to be sure that none cause it to raise 

48 # (quite aside from getting as correct an answer as possible). 

49 # The tests of builtins may break if inspect or the docstrings change, 

50 # but a red buildbot is better than a user crash (as has happened). 

51 # For a simple mismatch, change the expected output to the actual. 

52 

53 def test_builtins(self): 

54 

55 def tiptest(obj, out): 1f

56 self.assertEqual(get_spec(obj), out) 1f

57 

58 # Python class that inherits builtin methods 

59 class List(list): "List() doc" 1f

60 

61 # Simulate builtin with no docstring for default tip test 

62 class SB: __call__ = None 1f

63 

64 if List.__doc__ is not None: 64 ↛ 68line 64 didn't jump to line 68, because the condition on line 64 was never false1f

65 tiptest(List, 1f

66 f'(iterable=(), /)' 

67 f'\n{List.__doc__}') 

68 tiptest(list.__new__, 1f

69 '(*args, **kwargs)\n' 

70 'Create and return a new object. ' 

71 'See help(type) for accurate signature.') 

72 tiptest(list.__init__, 1f

73 '(self, /, *args, **kwargs)\n' 

74 'Initialize self. See help(type(self)) for accurate signature.') 

75 append_doc = "\nAppend object to the end of the list." 1f

76 tiptest(list.append, '(self, object, /)' + append_doc) 1f

77 tiptest(List.append, '(self, object, /)' + append_doc) 1f

78 tiptest([].append, '(object, /)' + append_doc) 1f

79 

80 tiptest(types.MethodType, 1f

81 '(function, instance, /)\n' 

82 'Create a bound instance method object.') 

83 tiptest(SB(), default_tip) 1f

84 

85 p = re.compile('') 1f

86 tiptest(re.sub, '''\ 1f

87(pattern, repl, string, count=0, flags=0) 

88Return the string obtained by replacing the leftmost 

89non-overlapping occurrences of the pattern in string by the 

90replacement repl. repl can be either a string or a callable; 

91if a string, backslash escapes in it are processed. If it is 

92a callable, it's passed the Match object and must return''') 

93 tiptest(p.sub, '''\ 1f

94(repl, string, count=0) 

95Return the string obtained by replacing the leftmost \ 

96non-overlapping occurrences o...''') 

97 

98 def test_signature_wrap(self): 

99 if textwrap.TextWrapper.__doc__ is not None: 99 ↛ exitline 99 didn't return from function 'test_signature_wrap', because the condition on line 99 was never false1s

100 self.assertEqual(get_spec(textwrap.TextWrapper), '''\ 1s

101(width=70, initial_indent='', subsequent_indent='', expand_tabs=True, 

102 replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, 

103 drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, 

104 placeholder=' [...]') 

105Object for wrapping/filling text. The public interface consists of 

106the wrap() and fill() methods; the other methods are just there for 

107subclasses to override in order to tweak the default behaviour. 

108If you want to completely replace the main wrapping algorithm, 

109you\'ll probably have to override _wrap_chunks().''') 

110 

111 def test_properly_formatted(self): 

112 

113 def foo(s='a'*100): 1h

114 pass 

115 

116 def bar(s='a'*100): 1h

117 """Hello Guido""" 

118 pass 

119 

120 def baz(s='a'*100, z='b'*100): 1h

121 pass 

122 

123 indent = calltip._INDENT 1h

124 

125 sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ 1h

126 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ 

127 "aaaaaaaaaa')" 

128 sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ 1h

129 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ 

130 "aaaaaaaaaa')\nHello Guido" 

131 sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ 1h

132 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ 

133 "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\ 

134 "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\ 

135 "bbbbbbbbbbbbbbbbbbbbbb')" 

136 

137 for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]: 1h

138 with self.subTest(func=func, doc=doc): 1h

139 self.assertEqual(get_spec(func), doc) 1h

140 

141 def test_docline_truncation(self): 

142 def f(): pass 142 ↛ exitline 142 didn't return from function 'f'1p

143 f.__doc__ = 'a'*300 1p

144 self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}") 1p

145 

146 def test_multiline_docstring(self): 

147 # Test fewer lines than max. 

148 self.assertEqual(get_spec(range), 1n

149 "range(stop) -> range object\n" 

150 "range(start, stop[, step]) -> range object") 

151 

152 # Test max lines 

153 self.assertEqual(get_spec(bytes), '''\ 1n

154bytes(iterable_of_ints) -> bytes 

155bytes(string, encoding[, errors]) -> bytes 

156bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer 

157bytes(int) -> bytes object of size given by the parameter initialized with null bytes 

158bytes() -> empty bytes object''') 

159 

160 # Test more than max lines 

161 def f(): pass 161 ↛ exitline 161 didn't return from function 'f'1n

162 f.__doc__ = 'a\n' * 15 1n

163 self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES) 1n

164 

165 def test_functions(self): 

166 def t1(): 'doc' 166 ↛ exitline 166 didn't return from function 't1'1g

167 t1.tip = "()" 1g

168 def t2(a, b=None): 'doc' 168 ↛ exitline 168 didn't return from function 't2'1g

169 t2.tip = "(a, b=None)" 1g

170 def t3(a, *args): 'doc' 170 ↛ exitline 170 didn't return from function 't3'1g

171 t3.tip = "(a, *args)" 1g

172 def t4(*args): 'doc' 172 ↛ exitline 172 didn't return from function 't4'1g

173 t4.tip = "(*args)" 1g

174 def t5(a, b=None, *args, **kw): 'doc' 174 ↛ exitline 174 didn't return from function 't5'1g

175 t5.tip = "(a, b=None, *args, **kw)" 1g

176 

177 doc = '\ndoc' if t1.__doc__ is not None else '' 1g

178 for func in (t1, t2, t3, t4, t5, TC): 1g

179 with self.subTest(func=func): 1g

180 self.assertEqual(get_spec(func), func.tip + doc) 1g

181 

182 def test_methods(self): 

183 doc = '\ndoc' if TC.__doc__ is not None else '' 1l

184 for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__): 1l

185 with self.subTest(meth=meth): 1l

186 self.assertEqual(get_spec(meth), meth.tip + doc) 1l

187 self.assertEqual(get_spec(TC.cm), "(a)" + doc) 1l

188 self.assertEqual(get_spec(TC.sm), "(b)" + doc) 1l

189 

190 def test_bound_methods(self): 

191 # test that first parameter is correctly removed from argspec 

192 doc = '\ndoc' if TC.__doc__ is not None else '' 1o

193 for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), 1o

194 (tc.t6, "(self)"), (tc.__call__, '(ci)'), 

195 (tc, '(ci)'), (TC.cm, "(a)"),): 

196 with self.subTest(meth=meth, mtip=mtip): 1o

197 self.assertEqual(get_spec(meth), mtip + doc) 1o

198 

199 def test_starred_parameter(self): 

200 # test that starred first parameter is *not* removed from argspec 

201 class C: 1m

202 def m1(*args): pass 202 ↛ exitline 202 didn't return from function 'm1'1m

203 c = C() 1m

204 for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),): 1m

205 with self.subTest(meth=meth, mtip=mtip): 1m

206 self.assertEqual(get_spec(meth), mtip) 1m

207 

208 def test_invalid_method_get_spec(self): 

209 class C: 1j

210 def m2(**kwargs): pass 210 ↛ exitline 210 didn't return from function 'm2'1j

211 class Test: 1j

212 def __call__(*, a): pass 212 ↛ exitline 212 didn't return from function '__call__'1j

213 

214 mtip = calltip._invalid_method 1j

215 self.assertEqual(get_spec(C().m2), mtip) 1j

216 self.assertEqual(get_spec(Test()), mtip) 1j

217 

218 def test_non_ascii_name(self): 

219 # test that re works to delete a first parameter name that 

220 # includes non-ascii chars, such as various forms of A. 

221 uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)" 1t

222 assert calltip._first_param.sub('', uni) == '(a)' 1t

223 

224 def test_no_docstring(self): 

225 for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")): 1q

226 with self.subTest(meth=meth, mtip=mtip): 1q

227 self.assertEqual(get_spec(meth), mtip) 1q

228 

229 def test_buggy_getattr_class(self): 

230 class NoCall: 1i

231 def __getattr__(self, name): # Not invoked for class attribute. 1i

232 raise IndexError # Bug. 1i

233 class CallA(NoCall): 1i

234 def __call__(self, ci): # Bug does not matter. 1i

235 pass 

236 class CallB(NoCall): 1i

237 def __call__(oui, a, b, c): # Non-standard 'self'. 1i

238 pass 

239 

240 for meth, mtip in ((NoCall, default_tip), (CallA, default_tip), 1i

241 (NoCall(), ''), (CallA(), '(ci)'), 

242 (CallB(), '(a, b, c)')): 

243 with self.subTest(meth=meth, mtip=mtip): 1i

244 self.assertEqual(get_spec(meth), mtip) 1i

245 

246 def test_metaclass_class(self): # Failure case for issue 38689. 

247 class Type(type): # Type() requires 3 type args, returns class. 1k

248 __class__ = property({}.__getitem__, {}.__setitem__) 1k

249 class Object(metaclass=Type): 1k

250 __slots__ = '__class__' 1k

251 for meth, mtip in ((Type, get_spec(type)), (Object, default_tip), 1k

252 (Object(), '')): 

253 with self.subTest(meth=meth, mtip=mtip): 1k

254 self.assertEqual(get_spec(meth), mtip) 1k

255 

256 def test_non_callables(self): 

257 for obj in (0, 0.0, '0', b'0', [], {}): 1r

258 with self.subTest(obj=obj): 1r

259 self.assertEqual(get_spec(obj), '') 1r

260 

261 

262class Get_entityTest(unittest.TestCase): 

263 def test_bad_entity(self): 

264 self.assertIsNone(calltip.get_entity('1/0')) 1u

265 def test_good_entity(self): 

266 self.assertIs(calltip.get_entity('int'), int) 1v

267 

268 

269# Test the 9 Calltip methods. 

270# open_calltip is about half the code; the others are fairly trivial. 

271# The default mocks are what are needed for open_calltip. 

272 

273class mock_Shell: 

274 "Return mock sufficient to pass to hyperparser." 

275 def __init__(self, text): 

276 text.tag_prevrange = Mock(return_value=None) 

277 self.text = text 

278 self.prompt_last_line = ">>> " 

279 self.indentwidth = 4 

280 self.tabwidth = 8 

281 

282 

283class mock_TipWindow: 

284 def __init__(self): 

285 pass 1cedb

286 

287 def showtip(self, text, parenleft, parenright): 

288 self.args = parenleft, parenright 1cedb

289 self.parenline, self.parencol = map(int, parenleft.split('.')) 1cedb

290 

291 

292class WrappedCalltip(calltip.Calltip): 

293 def _make_tk_calltip_window(self): 

294 return mock_TipWindow() 1cedb

295 

296 def remove_calltip_window(self, event=None): 

297 if self.active_calltip: # Setup to None. 1cedb

298 self.active_calltip = None 1cedb

299 self.tips_removed += 1 # Setup to 0. 1cedb

300 

301 def fetch_tip(self, expression): 

302 return 'tip' 1cedb

303 

304 

305class CalltipTest(unittest.TestCase): 

306 

307 @classmethod 

308 def setUpClass(cls): 

309 cls.text = Text() 

310 cls.ct = WrappedCalltip(mock_Shell(cls.text)) 

311 

312 def setUp(self): 

313 self.text.delete('1.0', 'end') # Insert and call 

314 self.ct.active_calltip = None 

315 # Test .active_calltip, +args 

316 self.ct.tips_removed = 0 

317 

318 def open_close(self, testfunc): 

319 # Open-close template with testfunc called in between. 

320 opentip = self.ct.open_calltip 1cedb

321 self.text.insert(1.0, 'f(') 1cedb

322 opentip(False) 1cedb

323 self.tip = self.ct.active_calltip 1cedb

324 testfunc(self) ### 1cedb

325 self.text.insert('insert', ')') 1cedb

326 opentip(False) 1cedb

327 self.assertIsNone(self.ct.active_calltip, None) 1cedb

328 

329 def test_open_close(self): 

330 def args(self): 1e

331 self.assertEqual(self.tip.args, ('1.1', '1.end')) 1e

332 self.open_close(args) 1e

333 

334 def test_repeated_force(self): 

335 def force(self): 1d

336 for char in 'abc': 1d

337 self.text.insert('insert', 'a') 1d

338 self.ct.open_calltip(True) 1d

339 self.ct.open_calltip(True) 1d

340 self.assertIs(self.ct.active_calltip, self.tip) 1d

341 self.open_close(force) 1d

342 

343 def test_repeated_parens(self): 

344 def parens(self): 1b

345 for context in "a", "'": 1b

346 with self.subTest(context=context): 1b

347 self.text.insert('insert', context) 1b

348 for char in '(()())': 1b

349 self.text.insert('insert', char) 1b

350 self.assertIs(self.ct.active_calltip, self.tip) 1b

351 self.text.insert('insert', "'") 1b

352 self.open_close(parens) 1b

353 

354 def test_comment_parens(self): 

355 def comment(self): 1c

356 self.text.insert('insert', "# ") 1c

357 for char in '(()())': 1c

358 self.text.insert('insert', char) 1c

359 self.assertIs(self.ct.active_calltip, self.tip) 1c

360 self.text.insert('insert', "\n") 1c

361 self.open_close(comment) 1c

362 

363 

364if __name__ == '__main__': 364 ↛ 365line 364 didn't jump to line 365, because the condition on line 364 was never true

365 unittest.main(verbosity=2)