Coverage for idle_test/test_squeezer.py: 49%

277 statements  

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

1"Test squeezer, coverage 95%" 

2 

3from textwrap import dedent 

4from tkinter import Text, Tk 

5import unittest 

6from unittest.mock import Mock, NonCallableMagicMock, patch, sentinel, ANY 

7from test.support import requires 

8 

9from idlelib.config import idleConf 

10from idlelib.percolator import Percolator 

11from idlelib.squeezer import count_lines_with_wrapping, ExpandingButton, \ 

12 Squeezer 

13from idlelib import macosx 

14from idlelib.textview import view_text 

15from idlelib.tooltip import Hovertip 

16 

17SENTINEL_VALUE = sentinel.SENTINEL_VALUE 

18 

19 

20def get_test_tk_root(test_instance): 

21 """Helper for tests: Create a root Tk object.""" 

22 requires('gui') 1nopqrsijgkl

23 root = Tk() 

24 root.withdraw() 

25 

26 def cleanup_root(): 

27 root.update_idletasks() 

28 root.destroy() 

29 test_instance.addCleanup(cleanup_root) 

30 

31 return root 

32 

33 

34class CountLinesTest(unittest.TestCase): 

35 """Tests for the count_lines_with_wrapping function.""" 

36 def check(self, expected, text, linewidth): 

37 return self.assertEqual( 1mth

38 expected, 

39 count_lines_with_wrapping(text, linewidth), 

40 ) 

41 

42 def test_count_empty(self): 

43 """Test with an empty string.""" 

44 self.assertEqual(count_lines_with_wrapping(""), 0) 1u

45 

46 def test_count_begins_with_empty_line(self): 

47 """Test with a string which begins with a newline.""" 

48 self.assertEqual(count_lines_with_wrapping("\ntext"), 2) 1v

49 

50 def test_count_ends_with_empty_line(self): 

51 """Test with a string which ends with a newline.""" 

52 self.assertEqual(count_lines_with_wrapping("text\n"), 1) 1w

53 

54 def test_count_several_lines(self): 

55 """Test with several lines of text.""" 

56 self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3) 1x

57 

58 def test_empty_lines(self): 

59 self.check(expected=1, text='\n', linewidth=80) 1m

60 self.check(expected=2, text='\n\n', linewidth=80) 1m

61 self.check(expected=10, text='\n' * 10, linewidth=80) 1m

62 

63 def test_long_line(self): 

64 self.check(expected=3, text='a' * 200, linewidth=80) 1t

65 self.check(expected=3, text='a' * 200 + '\n', linewidth=80) 1t

66 

67 def test_several_lines_different_lengths(self): 

68 text = dedent("""\ 1h

69 13 characters 

70 43 is the number of characters on this line 

71 

72 7 chars 

73 13 characters""") 

74 self.check(expected=5, text=text, linewidth=80) 1h

75 self.check(expected=5, text=text + '\n', linewidth=80) 1h

76 self.check(expected=6, text=text, linewidth=40) 1h

77 self.check(expected=7, text=text, linewidth=20) 1h

78 self.check(expected=11, text=text, linewidth=10) 1h

79 

80 

81class SqueezerTest(unittest.TestCase): 

82 """Tests for the Squeezer class.""" 

83 def make_mock_editor_window(self, with_text_widget=False): 

84 """Create a mock EditorWindow instance.""" 

85 editwin = NonCallableMagicMock() 1iefjgkldcb

86 editwin.width = 80 1iefjgkldcb

87 

88 if with_text_widget: 1iefjgkldcb

89 editwin.root = get_test_tk_root(self) 1ijgkl

90 text_widget = self.make_text_widget(root=editwin.root) 

91 editwin.text = editwin.per.bottom = text_widget 

92 

93 return editwin 1efdcb

94 

95 def make_squeezer_instance(self, editor_window=None): 

96 """Create an actual Squeezer instance with a mock EditorWindow.""" 

97 if editor_window is None: 97 ↛ 98line 97 didn't jump to line 98, because the condition on line 97 was never true1efdcb

98 editor_window = self.make_mock_editor_window() 

99 squeezer = Squeezer(editor_window) 1efdcb

100 return squeezer 1efdcb

101 

102 def make_text_widget(self, root=None): 

103 if root is None: 

104 root = get_test_tk_root(self) 

105 text_widget = Text(root) 

106 text_widget["font"] = ('Courier', 10) 

107 text_widget.mark_set("iomark", "1.0") 

108 return text_widget 

109 

110 def set_idleconf_option_with_cleanup(self, configType, section, option, value): 

111 prev_val = idleConf.GetOption(configType, section, option) 

112 idleConf.SetOption(configType, section, option, value) 

113 self.addCleanup(idleConf.SetOption, 

114 configType, section, option, prev_val) 

115 

116 def test_count_lines(self): 

117 """Test Squeezer.count_lines() with various inputs.""" 

118 editwin = self.make_mock_editor_window() 1e

119 squeezer = self.make_squeezer_instance(editwin) 1e

120 

121 for text_code, line_width, expected in [ 1e

122 (r"'\n'", 80, 1), 

123 (r"'\n' * 3", 80, 3), 

124 (r"'a' * 40 + '\n'", 80, 1), 

125 (r"'a' * 80 + '\n'", 80, 1), 

126 (r"'a' * 200 + '\n'", 80, 3), 

127 (r"'aa\t' * 20", 80, 2), 

128 (r"'aa\t' * 21", 80, 3), 

129 (r"'aa\t' * 20", 40, 4), 

130 ]: 

131 with self.subTest(text_code=text_code, 1e

132 line_width=line_width, 

133 expected=expected): 

134 text = eval(text_code) 1e

135 with patch.object(editwin, 'width', line_width): 1e

136 self.assertEqual(squeezer.count_lines(text), expected) 1e

137 

138 def test_init(self): 

139 """Test the creation of Squeezer instances.""" 

140 editwin = self.make_mock_editor_window() 1f

141 squeezer = self.make_squeezer_instance(editwin) 1f

142 self.assertIs(squeezer.editwin, editwin) 1f

143 self.assertEqual(squeezer.expandingbuttons, []) 1f

144 

145 def test_write_no_tags(self): 

146 """Test Squeezer's overriding of the EditorWindow's write() method.""" 

147 editwin = self.make_mock_editor_window() 1d

148 for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: 1d

149 editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) 1d

150 squeezer = self.make_squeezer_instance(editwin) 1d

151 

152 self.assertEqual(squeezer.editwin.write(text, ()), SENTINEL_VALUE) 1d

153 self.assertEqual(orig_write.call_count, 1) 1d

154 orig_write.assert_called_with(text, ()) 1d

155 self.assertEqual(len(squeezer.expandingbuttons), 0) 1d

156 

157 def test_write_not_stdout(self): 

158 """Test Squeezer's overriding of the EditorWindow's write() method.""" 

159 for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: 1c

160 editwin = self.make_mock_editor_window() 1c

161 editwin.write.return_value = SENTINEL_VALUE 1c

162 orig_write = editwin.write 1c

163 squeezer = self.make_squeezer_instance(editwin) 1c

164 

165 self.assertEqual(squeezer.editwin.write(text, "stderr"), 1c

166 SENTINEL_VALUE) 

167 self.assertEqual(orig_write.call_count, 1) 1c

168 orig_write.assert_called_with(text, "stderr") 1c

169 self.assertEqual(len(squeezer.expandingbuttons), 0) 1c

170 

171 def test_write_stdout(self): 

172 """Test Squeezer's overriding of the EditorWindow's write() method.""" 

173 editwin = self.make_mock_editor_window() 1b

174 

175 for text in ['', 'TEXT']: 1b

176 editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) 1b

177 squeezer = self.make_squeezer_instance(editwin) 1b

178 squeezer.auto_squeeze_min_lines = 50 1b

179 

180 self.assertEqual(squeezer.editwin.write(text, "stdout"), 1b

181 SENTINEL_VALUE) 

182 self.assertEqual(orig_write.call_count, 1) 1b

183 orig_write.assert_called_with(text, "stdout") 1b

184 self.assertEqual(len(squeezer.expandingbuttons), 0) 1b

185 

186 for text in ['LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: 1b

187 editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) 1b

188 squeezer = self.make_squeezer_instance(editwin) 1b

189 squeezer.auto_squeeze_min_lines = 50 1b

190 

191 self.assertEqual(squeezer.editwin.write(text, "stdout"), None) 1b

192 self.assertEqual(orig_write.call_count, 0) 1b

193 self.assertEqual(len(squeezer.expandingbuttons), 1) 1b

194 

195 def test_auto_squeeze(self): 

196 """Test that the auto-squeezing creates an ExpandingButton properly.""" 

197 editwin = self.make_mock_editor_window(with_text_widget=True) 1i

198 text_widget = editwin.text 

199 squeezer = self.make_squeezer_instance(editwin) 

200 squeezer.auto_squeeze_min_lines = 5 

201 squeezer.count_lines = Mock(return_value=6) 

202 

203 editwin.write('TEXT\n'*6, "stdout") 

204 self.assertEqual(text_widget.get('1.0', 'end'), '\n') 

205 self.assertEqual(len(squeezer.expandingbuttons), 1) 

206 

207 def test_squeeze_current_text(self): 

208 """Test the squeeze_current_text method.""" 

209 # Squeezing text should work for both stdout and stderr. 

210 for tag_name in ["stdout", "stderr"]: 210 ↛ exitline 210 didn't return from function 'test_squeeze_current_text', because the loop on line 210 didn't complete1g

211 editwin = self.make_mock_editor_window(with_text_widget=True) 1g

212 text_widget = editwin.text 

213 squeezer = self.make_squeezer_instance(editwin) 

214 squeezer.count_lines = Mock(return_value=6) 

215 

216 # Prepare some text in the Text widget. 

217 text_widget.insert("1.0", "SOME\nTEXT\n", tag_name) 

218 text_widget.mark_set("insert", "1.0") 

219 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') 

220 

221 self.assertEqual(len(squeezer.expandingbuttons), 0) 

222 

223 # Test squeezing the current text. 

224 retval = squeezer.squeeze_current_text() 

225 self.assertEqual(retval, "break") 

226 self.assertEqual(text_widget.get('1.0', 'end'), '\n\n') 

227 self.assertEqual(len(squeezer.expandingbuttons), 1) 

228 self.assertEqual(squeezer.expandingbuttons[0].s, 'SOME\nTEXT') 

229 

230 # Test that expanding the squeezed text works and afterwards 

231 # the Text widget contains the original text. 

232 squeezer.expandingbuttons[0].expand() 

233 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') 

234 self.assertEqual(len(squeezer.expandingbuttons), 0) 

235 

236 def test_squeeze_current_text_no_allowed_tags(self): 

237 """Test that the event doesn't squeeze text without a relevant tag.""" 

238 editwin = self.make_mock_editor_window(with_text_widget=True) 1k

239 text_widget = editwin.text 

240 squeezer = self.make_squeezer_instance(editwin) 

241 squeezer.count_lines = Mock(return_value=6) 

242 

243 # Prepare some text in the Text widget. 

244 text_widget.insert("1.0", "SOME\nTEXT\n", "TAG") 

245 text_widget.mark_set("insert", "1.0") 

246 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') 

247 

248 self.assertEqual(len(squeezer.expandingbuttons), 0) 

249 

250 # Test squeezing the current text. 

251 retval = squeezer.squeeze_current_text() 

252 self.assertEqual(retval, "break") 

253 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n') 

254 self.assertEqual(len(squeezer.expandingbuttons), 0) 

255 

256 def test_squeeze_text_before_existing_squeezed_text(self): 

257 """Test squeezing text before existing squeezed text.""" 

258 editwin = self.make_mock_editor_window(with_text_widget=True) 1l

259 text_widget = editwin.text 

260 squeezer = self.make_squeezer_instance(editwin) 

261 squeezer.count_lines = Mock(return_value=6) 

262 

263 # Prepare some text in the Text widget and squeeze it. 

264 text_widget.insert("1.0", "SOME\nTEXT\n", "stdout") 

265 text_widget.mark_set("insert", "1.0") 

266 squeezer.squeeze_current_text() 

267 self.assertEqual(len(squeezer.expandingbuttons), 1) 

268 

269 # Test squeezing the current text. 

270 text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout") 

271 text_widget.mark_set("insert", "1.0") 

272 retval = squeezer.squeeze_current_text() 

273 self.assertEqual(retval, "break") 

274 self.assertEqual(text_widget.get('1.0', 'end'), '\n\n\n') 

275 self.assertEqual(len(squeezer.expandingbuttons), 2) 

276 self.assertTrue(text_widget.compare( 

277 squeezer.expandingbuttons[0], 

278 '<', 

279 squeezer.expandingbuttons[1], 

280 )) 

281 

282 def test_reload(self): 

283 """Test the reload() class-method.""" 

284 editwin = self.make_mock_editor_window(with_text_widget=True) 1j

285 squeezer = self.make_squeezer_instance(editwin) 

286 

287 orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines 

288 

289 # Increase auto-squeeze-min-lines. 

290 new_auto_squeeze_min_lines = orig_auto_squeeze_min_lines + 10 

291 self.set_idleconf_option_with_cleanup( 

292 'main', 'PyShell', 'auto-squeeze-min-lines', 

293 str(new_auto_squeeze_min_lines)) 

294 

295 Squeezer.reload() 

296 self.assertEqual(squeezer.auto_squeeze_min_lines, 

297 new_auto_squeeze_min_lines) 

298 

299 def test_reload_no_squeezer_instances(self): 

300 """Test that Squeezer.reload() runs without any instances existing.""" 

301 Squeezer.reload() 1y

302 

303 

304class ExpandingButtonTest(unittest.TestCase): 

305 """Tests for the ExpandingButton class.""" 

306 # In these tests the squeezer instance is a mock, but actual tkinter 

307 # Text and Button instances are created. 

308 def make_mock_squeezer(self): 

309 """Helper for tests: Create a mock Squeezer object.""" 

310 root = get_test_tk_root(self) 1nopqrs

311 squeezer = Mock() 

312 squeezer.editwin.text = Text(root) 

313 squeezer.editwin.per = Percolator(squeezer.editwin.text) 

314 self.addCleanup(squeezer.editwin.per.close) 

315 

316 # Set default values for the configuration settings. 

317 squeezer.auto_squeeze_min_lines = 50 

318 return squeezer 

319 

320 @patch('idlelib.squeezer.Hovertip', autospec=Hovertip) 

321 def test_init(self, MockHovertip): 

322 """Test the simplest creation of an ExpandingButton.""" 

323 squeezer = self.make_mock_squeezer() 1q

324 text_widget = squeezer.editwin.text 

325 

326 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) 

327 self.assertEqual(expandingbutton.s, 'TEXT') 

328 

329 # Check that the underlying tkinter.Button is properly configured. 

330 self.assertEqual(expandingbutton.master, text_widget) 

331 self.assertTrue('50 lines' in expandingbutton.cget('text')) 

332 

333 # Check that the text widget still contains no text. 

334 self.assertEqual(text_widget.get('1.0', 'end'), '\n') 

335 

336 # Check that the mouse events are bound. 

337 self.assertIn('<Double-Button-1>', expandingbutton.bind()) 

338 right_button_code = '<Button-%s>' % ('2' if macosx.isAquaTk() else '3') 

339 self.assertIn(right_button_code, expandingbutton.bind()) 

340 

341 # Check that ToolTip was called once, with appropriate values. 

342 self.assertEqual(MockHovertip.call_count, 1) 

343 MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY) 

344 

345 # Check that 'right-click' appears in the tooltip text. 

346 tooltip_text = MockHovertip.call_args[0][1] 

347 self.assertIn('right-click', tooltip_text.lower()) 

348 

349 def test_expand(self): 

350 """Test the expand event.""" 

351 squeezer = self.make_mock_squeezer() 1o

352 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) 

353 

354 # Insert the button into the text widget 

355 # (this is normally done by the Squeezer class). 

356 text_widget = squeezer.editwin.text 

357 text_widget.window_create("1.0", window=expandingbutton) 

358 

359 # trigger the expand event 

360 retval = expandingbutton.expand(event=Mock()) 

361 self.assertEqual(retval, None) 

362 

363 # Check that the text was inserted into the text widget. 

364 self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n') 

365 

366 # Check that the 'TAGS' tag was set on the inserted text. 

367 text_end_index = text_widget.index('end-1c') 

368 self.assertEqual(text_widget.get('1.0', text_end_index), 'TEXT') 

369 self.assertEqual(text_widget.tag_nextrange('TAGS', '1.0'), 

370 ('1.0', text_end_index)) 

371 

372 # Check that the button removed itself from squeezer.expandingbuttons. 

373 self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1) 

374 squeezer.expandingbuttons.remove.assert_called_with(expandingbutton) 

375 

376 def test_expand_dangerous_oupput(self): 

377 """Test that expanding very long output asks user for confirmation.""" 

378 squeezer = self.make_mock_squeezer() 1p

379 text = 'a' * 10**5 

380 expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer) 

381 expandingbutton.set_is_dangerous() 

382 self.assertTrue(expandingbutton.is_dangerous) 

383 

384 # Insert the button into the text widget 

385 # (this is normally done by the Squeezer class). 

386 text_widget = expandingbutton.text 

387 text_widget.window_create("1.0", window=expandingbutton) 

388 

389 # Patch the message box module to always return False. 

390 with patch('idlelib.squeezer.messagebox') as mock_msgbox: 

391 mock_msgbox.askokcancel.return_value = False 

392 mock_msgbox.askyesno.return_value = False 

393 # Trigger the expand event. 

394 retval = expandingbutton.expand(event=Mock()) 

395 

396 # Check that the event chain was broken and no text was inserted. 

397 self.assertEqual(retval, 'break') 

398 self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '') 

399 

400 # Patch the message box module to always return True. 

401 with patch('idlelib.squeezer.messagebox') as mock_msgbox: 

402 mock_msgbox.askokcancel.return_value = True 

403 mock_msgbox.askyesno.return_value = True 

404 # Trigger the expand event. 

405 retval = expandingbutton.expand(event=Mock()) 

406 

407 # Check that the event chain wasn't broken and the text was inserted. 

408 self.assertEqual(retval, None) 

409 self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text) 

410 

411 def test_copy(self): 

412 """Test the copy event.""" 

413 # Testing with the actual clipboard proved problematic, so this 

414 # test replaces the clipboard manipulation functions with mocks 

415 # and checks that they are called appropriately. 

416 squeezer = self.make_mock_squeezer() 1n

417 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) 

418 expandingbutton.clipboard_clear = Mock() 

419 expandingbutton.clipboard_append = Mock() 

420 

421 # Trigger the copy event. 

422 retval = expandingbutton.copy(event=Mock()) 

423 self.assertEqual(retval, None) 

424 

425 # Vheck that the expanding button called clipboard_clear() and 

426 # clipboard_append('TEXT') once each. 

427 self.assertEqual(expandingbutton.clipboard_clear.call_count, 1) 

428 self.assertEqual(expandingbutton.clipboard_append.call_count, 1) 

429 expandingbutton.clipboard_append.assert_called_with('TEXT') 

430 

431 def test_view(self): 

432 """Test the view event.""" 

433 squeezer = self.make_mock_squeezer() 1s

434 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) 

435 expandingbutton.selection_own = Mock() 

436 

437 with patch('idlelib.squeezer.view_text', autospec=view_text)\ 

438 as mock_view_text: 

439 # Trigger the view event. 

440 expandingbutton.view(event=Mock()) 

441 

442 # Check that the expanding button called view_text. 

443 self.assertEqual(mock_view_text.call_count, 1) 

444 

445 # Check that the proper text was passed. 

446 self.assertEqual(mock_view_text.call_args[0][2], 'TEXT') 

447 

448 def test_rmenu(self): 

449 """Test the context menu.""" 

450 squeezer = self.make_mock_squeezer() 1r

451 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) 

452 with patch('tkinter.Menu') as mock_Menu: 

453 mock_menu = Mock() 

454 mock_Menu.return_value = mock_menu 

455 mock_event = Mock() 

456 mock_event.x = 10 

457 mock_event.y = 10 

458 expandingbutton.context_menu_event(event=mock_event) 

459 self.assertEqual(mock_menu.add_command.call_count, 

460 len(expandingbutton.rmenu_specs)) 

461 for label, *data in expandingbutton.rmenu_specs: 

462 mock_menu.add_command.assert_any_call(label=label, command=ANY) 

463 

464 

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

466 unittest.main(verbosity=2)