Coverage for idle_test/test_format.py: 54%
376 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
1"Test format, coverage 99%."
3from idlelib import format as ft
4import unittest
5from unittest import mock
6from test.support import requires
7from tkinter import Tk, Text
8from idlelib.editor import EditorWindow
9from idlelib.idle_test.mock_idle import Editor as MockEditor
12class Is_Get_Test(unittest.TestCase):
13 """Test the is_ and get_ functions"""
14 test_comment = '# This is a comment'
15 test_nocomment = 'This is not a comment'
16 trailingws_comment = '# This is a comment '
17 leadingws_comment = ' # This is a comment'
18 leadingws_nocomment = ' This is not a comment'
20 def test_is_all_white(self):
21 self.assertTrue(ft.is_all_white('')) 1k
22 self.assertTrue(ft.is_all_white('\t\n\r\f\v')) 1k
23 self.assertFalse(ft.is_all_white(self.test_comment)) 1k
25 def test_get_indent(self):
26 Equal = self.assertEqual 1i
27 Equal(ft.get_indent(self.test_comment), '') 1i
28 Equal(ft.get_indent(self.trailingws_comment), '') 1i
29 Equal(ft.get_indent(self.leadingws_comment), ' ') 1i
30 Equal(ft.get_indent(self.leadingws_nocomment), ' ') 1i
32 def test_get_comment_header(self):
33 Equal = self.assertEqual 1h
34 # Test comment strings
35 Equal(ft.get_comment_header(self.test_comment), '#') 1h
36 Equal(ft.get_comment_header(self.trailingws_comment), '#') 1h
37 Equal(ft.get_comment_header(self.leadingws_comment), ' #') 1h
38 # Test non-comment strings
39 Equal(ft.get_comment_header(self.leadingws_nocomment), ' ') 1h
40 Equal(ft.get_comment_header(self.test_nocomment), '') 1h
43class FindTest(unittest.TestCase):
44 """Test the find_paragraph function in paragraph module.
46 Using the runcase() function, find_paragraph() is called with 'mark' set at
47 multiple indexes before and inside the test paragraph.
49 It appears that code with the same indentation as a quoted string is grouped
50 as part of the same paragraph, which is probably incorrect behavior.
51 """
53 @classmethod
54 def setUpClass(cls):
55 from idlelib.idle_test.mock_tk import Text
56 cls.text = Text()
58 def runcase(self, inserttext, stopline, expected):
59 # Check that find_paragraph returns the expected paragraph when
60 # the mark index is set to beginning, middle, end of each line
61 # up to but not including the stop line
62 text = self.text 1bc
63 text.insert('1.0', inserttext) 1bc
64 for line in range(1, stopline): 1bc
65 linelength = int(text.index("%d.end" % line).split('.')[1]) 1bc
66 for col in (0, linelength//2, linelength): 1bc
67 tempindex = "%d.%d" % (line, col) 1bc
68 self.assertEqual(ft.find_paragraph(text, tempindex), expected) 1bc
69 text.delete('1.0', 'end') 1bc
71 def test_find_comment(self):
72 comment = ( 1b
73 "# Comment block with no blank lines before\n"
74 "# Comment line\n"
75 "\n")
76 self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58])) 1b
78 comment = ( 1b
79 "\n"
80 "# Comment block with whitespace line before and after\n"
81 "# Comment line\n"
82 "\n")
83 self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70])) 1b
85 comment = ( 1b
86 "\n"
87 " # Indented comment block with whitespace before and after\n"
88 " # Comment line\n"
89 "\n")
90 self.runcase(comment, 4, ('2.0', '4.0', ' #', comment[1:82])) 1b
92 comment = ( 1b
93 "\n"
94 "# Single line comment\n"
95 "\n")
96 self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23])) 1b
98 comment = ( 1b
99 "\n"
100 " # Single line comment with leading whitespace\n"
101 "\n")
102 self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:51])) 1b
104 comment = ( 1b
105 "\n"
106 "# Comment immediately followed by code\n"
107 "x = 42\n"
108 "\n")
109 self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40])) 1b
111 comment = ( 1b
112 "\n"
113 " # Indented comment immediately followed by code\n"
114 "x = 42\n"
115 "\n")
116 self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:53])) 1b
118 comment = ( 1b
119 "\n"
120 "# Comment immediately followed by indented code\n"
121 " x = 42\n"
122 "\n")
123 self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49])) 1b
125 def test_find_paragraph(self):
126 teststring = ( 1c
127 '"""String with no blank lines before\n'
128 'String line\n'
129 '"""\n'
130 '\n')
131 self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53])) 1c
133 teststring = ( 1c
134 "\n"
135 '"""String with whitespace line before and after\n'
136 'String line.\n'
137 '"""\n'
138 '\n')
139 self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66])) 1c
141 teststring = ( 1c
142 '\n'
143 ' """Indented string with whitespace before and after\n'
144 ' Comment string.\n'
145 ' """\n'
146 '\n')
147 self.runcase(teststring, 5, ('2.0', '5.0', ' ', teststring[1:85])) 1c
149 teststring = ( 1c
150 '\n'
151 '"""Single line string."""\n'
152 '\n')
153 self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27])) 1c
155 teststring = ( 1c
156 '\n'
157 ' """Single line string with leading whitespace."""\n'
158 '\n')
159 self.runcase(teststring, 3, ('2.0', '3.0', ' ', teststring[1:55])) 1c
162class ReformatFunctionTest(unittest.TestCase):
163 """Test the reformat_paragraph function without the editor window."""
165 def test_reformat_paragraph(self):
166 Equal = self.assertEqual 1e
167 reform = ft.reformat_paragraph 1e
168 hw = "O hello world" 1e
169 Equal(reform(' ', 1), ' ') 1e
170 Equal(reform("Hello world", 20), "Hello world") 1e
172 # Test without leading newline
173 Equal(reform(hw, 1), "O\nhello\nworld") 1e
174 Equal(reform(hw, 6), "O\nhello\nworld") 1e
175 Equal(reform(hw, 7), "O hello\nworld") 1e
176 Equal(reform(hw, 12), "O hello\nworld") 1e
177 Equal(reform(hw, 13), "O hello world") 1e
179 # Test with leading newline
180 hw = "\nO hello world" 1e
181 Equal(reform(hw, 1), "\nO\nhello\nworld") 1e
182 Equal(reform(hw, 6), "\nO\nhello\nworld") 1e
183 Equal(reform(hw, 7), "\nO hello\nworld") 1e
184 Equal(reform(hw, 12), "\nO hello\nworld") 1e
185 Equal(reform(hw, 13), "\nO hello world") 1e
188class ReformatCommentTest(unittest.TestCase):
189 """Test the reformat_comment function without the editor window."""
191 def test_reformat_comment(self):
192 Equal = self.assertEqual 1g
194 # reformat_comment formats to a minimum of 20 characters
195 test_string = ( 1g
196 " \"\"\"this is a test of a reformat for a triple quoted string"
197 " will it reformat to less than 70 characters for me?\"\"\"")
198 result = ft.reformat_comment(test_string, 70, " ") 1g
199 expected = ( 1g
200 " \"\"\"this is a test of a reformat for a triple quoted string will it\n"
201 " reformat to less than 70 characters for me?\"\"\"")
202 Equal(result, expected) 1g
204 test_comment = ( 1g
205 "# this is a test of a reformat for a triple quoted string will "
206 "it reformat to less than 70 characters for me?")
207 result = ft.reformat_comment(test_comment, 70, "#") 1g
208 expected = ( 1g
209 "# this is a test of a reformat for a triple quoted string will it\n"
210 "# reformat to less than 70 characters for me?")
211 Equal(result, expected) 1g
214class FormatClassTest(unittest.TestCase):
215 def test_init_close(self):
216 instance = ft.FormatParagraph('editor') 1j
217 self.assertEqual(instance.editwin, 'editor') 1j
218 instance.close() 1j
219 self.assertEqual(instance.editwin, None) 1j
222# For testing format_paragraph_event, Initialize FormatParagraph with
223# a mock Editor with .text and .get_selection_indices. The text must
224# be a Text wrapper that adds two methods
226# A real EditorWindow creates unneeded, time-consuming baggage and
227# sometimes emits shutdown warnings like this:
228# "warning: callback failed in WindowList <class '_tkinter.TclError'>
229# : invalid command name ".55131368.windows".
230# Calling EditorWindow._close in tearDownClass prevents this but causes
231# other problems (windows left open).
233class TextWrapper:
234 def __init__(self, master):
235 self.text = Text(master=master)
236 def __getattr__(self, name):
237 return getattr(self.text, name)
238 def undo_block_start(self): pass 238 ↛ exitline 238 didn't return from function 'undo_block_start'
239 def undo_block_stop(self): pass 239 ↛ exitline 239 didn't return from function 'undo_block_stop'
241class Editor:
242 def __init__(self, root):
243 self.text = TextWrapper(root)
244 get_selection_indices = EditorWindow. get_selection_indices
246class FormatEventTest(unittest.TestCase):
247 """Test the formatting of text inside a Text widget.
249 This is done with FormatParagraph.format.paragraph_event,
250 which calls functions in the module as appropriate.
251 """
252 test_string = (
253 " '''this is a test of a reformat for a triple "
254 "quoted string will it reformat to less than 70 "
255 "characters for me?'''\n")
256 multiline_test_string = (
257 " '''The first line is under the max width.\n"
258 " The second line's length is way over the max width. It goes "
259 "on and on until it is over 100 characters long.\n"
260 " Same thing with the third line. It is also way over the max "
261 "width, but FormatParagraph will fix it.\n"
262 " '''\n")
263 multiline_test_comment = (
264 "# The first line is under the max width.\n"
265 "# The second line's length is way over the max width. It goes on "
266 "and on until it is over 100 characters long.\n"
267 "# Same thing with the third line. It is also way over the max "
268 "width, but FormatParagraph will fix it.\n"
269 "# The fourth line is short like the first line.")
271 @classmethod
272 def setUpClass(cls):
273 requires('gui')
274 cls.root = Tk()
275 cls.root.withdraw()
276 editor = Editor(root=cls.root)
277 cls.text = editor.text.text # Test code does not need the wrapper.
278 cls.formatter = ft.FormatParagraph(editor).format_paragraph_event
279 # Sets the insert mark just after the re-wrapped and inserted text.
281 @classmethod
282 def tearDownClass(cls):
283 del cls.text, cls.formatter
284 cls.root.update_idletasks()
285 cls.root.destroy()
286 del cls.root
288 def test_short_line(self):
289 self.text.insert('1.0', "Short line\n")
290 self.formatter("Dummy")
291 self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
292 self.text.delete('1.0', 'end')
294 def test_long_line(self):
295 text = self.text
297 # Set cursor ('insert' mark) to '1.0', within text.
298 text.insert('1.0', self.test_string)
299 text.mark_set('insert', '1.0')
300 self.formatter('ParameterDoesNothing', limit=70)
301 result = text.get('1.0', 'insert')
302 # find function includes \n
303 expected = (
304" '''this is a test of a reformat for a triple quoted string will it\n"
305" reformat to less than 70 characters for me?'''\n") # yes
306 self.assertEqual(result, expected)
307 text.delete('1.0', 'end')
309 # Select from 1.11 to line end.
310 text.insert('1.0', self.test_string)
311 text.tag_add('sel', '1.11', '1.end')
312 self.formatter('ParameterDoesNothing', limit=70)
313 result = text.get('1.0', 'insert')
314 # selection excludes \n
315 expected = (
316" '''this is a test of a reformat for a triple quoted string will it reformat\n"
317" to less than 70 characters for me?'''") # no
318 self.assertEqual(result, expected)
319 text.delete('1.0', 'end')
321 def test_multiple_lines(self):
322 text = self.text
323 # Select 2 long lines.
324 text.insert('1.0', self.multiline_test_string)
325 text.tag_add('sel', '2.0', '4.0')
326 self.formatter('ParameterDoesNothing', limit=70)
327 result = text.get('2.0', 'insert')
328 expected = (
329" The second line's length is way over the max width. It goes on and\n"
330" on until it is over 100 characters long. Same thing with the third\n"
331" line. It is also way over the max width, but FormatParagraph will\n"
332" fix it.\n")
333 self.assertEqual(result, expected)
334 text.delete('1.0', 'end')
336 def test_comment_block(self):
337 text = self.text
339 # Set cursor ('insert') to '1.0', within block.
340 text.insert('1.0', self.multiline_test_comment)
341 self.formatter('ParameterDoesNothing', limit=70)
342 result = text.get('1.0', 'insert')
343 expected = (
344"# The first line is under the max width. The second line's length is\n"
345"# way over the max width. It goes on and on until it is over 100\n"
346"# characters long. Same thing with the third line. It is also way over\n"
347"# the max width, but FormatParagraph will fix it. The fourth line is\n"
348"# short like the first line.\n")
349 self.assertEqual(result, expected)
350 text.delete('1.0', 'end')
352 # Select line 2, verify line 1 unaffected.
353 text.insert('1.0', self.multiline_test_comment)
354 text.tag_add('sel', '2.0', '3.0')
355 self.formatter('ParameterDoesNothing', limit=70)
356 result = text.get('1.0', 'insert')
357 expected = (
358"# The first line is under the max width.\n"
359"# The second line's length is way over the max width. It goes on and\n"
360"# on until it is over 100 characters long.\n")
361 self.assertEqual(result, expected)
362 text.delete('1.0', 'end')
364# The following block worked with EditorWindow but fails with the mock.
365# Lines 2 and 3 get pasted together even though the previous block left
366# the previous line alone. More investigation is needed.
367## # Select lines 3 and 4
368## text.insert('1.0', self.multiline_test_comment)
369## text.tag_add('sel', '3.0', '5.0')
370## self.formatter('ParameterDoesNothing')
371## result = text.get('3.0', 'insert')
372## expected = (
373##"# Same thing with the third line. It is also way over the max width,\n"
374##"# but FormatParagraph will fix it. The fourth line is short like the\n"
375##"# first line.\n")
376## self.assertEqual(result, expected)
377## text.delete('1.0', 'end')
380class DummyEditwin:
381 def __init__(self, root, text):
382 self.root = root 1df
383 self.text = text 1df
384 self.indentwidth = 4 1df
385 self.tabwidth = 4 1df
386 self.usetabs = False 1df
387 self.context_use_ps1 = True 1df
389 _make_blanks = EditorWindow._make_blanks
390 get_selection_indices = EditorWindow.get_selection_indices
393class FormatRegionTest(unittest.TestCase):
395 @classmethod
396 def setUpClass(cls):
397 requires('gui')
398 cls.root = Tk()
399 cls.root.withdraw()
400 cls.text = Text(cls.root)
401 cls.text.undo_block_start = mock.Mock()
402 cls.text.undo_block_stop = mock.Mock()
403 cls.editor = DummyEditwin(cls.root, cls.text)
404 cls.formatter = ft.FormatRegion(cls.editor)
406 @classmethod
407 def tearDownClass(cls):
408 del cls.text, cls.formatter, cls.editor
409 cls.root.update_idletasks()
410 cls.root.destroy()
411 del cls.root
413 def setUp(self):
414 self.text.insert('1.0', self.code_sample)
416 def tearDown(self):
417 self.text.delete('1.0', 'end')
419 code_sample = """\
420# WS line needed for test.
421class C1:
422 # Class comment.
423 def __init__(self, a, b):
424 self.a = a
425 self.b = b
427 def compare(self):
428 if a > b:
429 return a
430 elif a < b:
431 return b
432 else:
433 return None
434"""
436 def test_get_region(self):
437 get = self.formatter.get_region
438 text = self.text
439 eq = self.assertEqual
441 # Add selection.
442 text.tag_add('sel', '7.0', '10.0')
443 expected_lines = ['',
444 ' def compare(self):',
445 ' if a > b:',
446 '']
447 eq(get(), ('7.0', '10.0', '\n'.join(expected_lines), expected_lines))
449 # Remove selection.
450 text.tag_remove('sel', '1.0', 'end')
451 eq(get(), ('15.0', '16.0', '\n', ['', '']))
453 def test_set_region(self):
454 set_ = self.formatter.set_region
455 text = self.text
456 eq = self.assertEqual
458 save_bell = text.bell
459 text.bell = mock.Mock()
460 line6 = self.code_sample.splitlines()[5]
461 line10 = self.code_sample.splitlines()[9]
463 text.tag_add('sel', '6.0', '11.0')
464 head, tail, chars, lines = self.formatter.get_region()
466 # No changes.
467 set_(head, tail, chars, lines)
468 text.bell.assert_called_once()
469 eq(text.get('6.0', '11.0'), chars)
470 eq(text.get('sel.first', 'sel.last'), chars)
471 text.tag_remove('sel', '1.0', 'end')
473 # Alter selected lines by changing lines and adding a newline.
474 newstring = 'added line 1\n\n\n\n'
475 newlines = newstring.split('\n')
476 set_('7.0', '10.0', chars, newlines)
477 # Selection changed.
478 eq(text.get('sel.first', 'sel.last'), newstring)
479 # Additional line added, so last index is changed.
480 eq(text.get('7.0', '11.0'), newstring)
481 # Before and after lines unchanged.
482 eq(text.get('6.0', '7.0-1c'), line6)
483 eq(text.get('11.0', '12.0-1c'), line10)
484 text.tag_remove('sel', '1.0', 'end')
486 text.bell = save_bell
488 def test_indent_region_event(self):
489 indent = self.formatter.indent_region_event
490 text = self.text
491 eq = self.assertEqual
493 text.tag_add('sel', '7.0', '10.0')
494 indent()
495 # Blank lines aren't affected by indent.
496 eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
498 def test_dedent_region_event(self):
499 dedent = self.formatter.dedent_region_event
500 text = self.text
501 eq = self.assertEqual
503 text.tag_add('sel', '7.0', '10.0')
504 dedent()
505 # Blank lines aren't affected by dedent.
506 eq(text.get('7.0', '10.0'), ('\ndef compare(self):\n if a > b:\n'))
508 def test_comment_region_event(self):
509 comment = self.formatter.comment_region_event
510 text = self.text
511 eq = self.assertEqual
513 text.tag_add('sel', '7.0', '10.0')
514 comment()
515 eq(text.get('7.0', '10.0'), ('##\n## def compare(self):\n## if a > b:\n'))
517 def test_uncomment_region_event(self):
518 comment = self.formatter.comment_region_event
519 uncomment = self.formatter.uncomment_region_event
520 text = self.text
521 eq = self.assertEqual
523 text.tag_add('sel', '7.0', '10.0')
524 comment()
525 uncomment()
526 eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
528 # Only remove comments at the beginning of a line.
529 text.tag_remove('sel', '1.0', 'end')
530 text.tag_add('sel', '3.0', '4.0')
531 uncomment()
532 eq(text.get('3.0', '3.end'), (' # Class comment.'))
534 self.formatter.set_region('3.0', '4.0', '', ['# Class comment.', ''])
535 uncomment()
536 eq(text.get('3.0', '3.end'), (' Class comment.'))
538 @mock.patch.object(ft.FormatRegion, "_asktabwidth")
539 def test_tabify_region_event(self, _asktabwidth):
540 tabify = self.formatter.tabify_region_event
541 text = self.text
542 eq = self.assertEqual
544 text.tag_add('sel', '7.0', '10.0')
545 # No tabwidth selected.
546 _asktabwidth.return_value = None
547 self.assertIsNone(tabify())
549 _asktabwidth.return_value = 3
550 self.assertIsNotNone(tabify())
551 eq(text.get('7.0', '10.0'), ('\n\t def compare(self):\n\t\t if a > b:\n'))
553 @mock.patch.object(ft.FormatRegion, "_asktabwidth")
554 def test_untabify_region_event(self, _asktabwidth):
555 untabify = self.formatter.untabify_region_event
556 text = self.text
557 eq = self.assertEqual
559 text.tag_add('sel', '7.0', '10.0')
560 # No tabwidth selected.
561 _asktabwidth.return_value = None
562 self.assertIsNone(untabify())
564 _asktabwidth.return_value = 2
565 self.formatter.tabify_region_event()
566 _asktabwidth.return_value = 3
567 self.assertIsNotNone(untabify())
568 eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
570 @mock.patch.object(ft, "askinteger")
571 def test_ask_tabwidth(self, askinteger):
572 ask = self.formatter._asktabwidth
573 askinteger.return_value = 10
574 self.assertEqual(ask(), 10)
577class IndentsTest(unittest.TestCase):
579 @mock.patch.object(ft, "askyesno")
580 def test_toggle_tabs(self, askyesno):
581 editor = DummyEditwin(None, None) # usetabs == False. 1f
582 indents = ft.Indents(editor) 1f
583 askyesno.return_value = True 1f
585 indents.toggle_tabs_event(None) 1f
586 self.assertEqual(editor.usetabs, True) 1f
587 self.assertEqual(editor.indentwidth, 8) 1f
589 indents.toggle_tabs_event(None) 1f
590 self.assertEqual(editor.usetabs, False) 1f
591 self.assertEqual(editor.indentwidth, 8) 1f
593 @mock.patch.object(ft, "askinteger")
594 def test_change_indentwidth(self, askinteger):
595 editor = DummyEditwin(None, None) # indentwidth == 4. 1d
596 indents = ft.Indents(editor) 1d
598 askinteger.return_value = None 1d
599 indents.change_indentwidth_event(None) 1d
600 self.assertEqual(editor.indentwidth, 4) 1d
602 askinteger.return_value = 3 1d
603 indents.change_indentwidth_event(None) 1d
604 self.assertEqual(editor.indentwidth, 3) 1d
606 askinteger.return_value = 5 1d
607 editor.usetabs = True 1d
608 indents.change_indentwidth_event(None) 1d
609 self.assertEqual(editor.indentwidth, 3) 1d
612class RstripTest(unittest.TestCase):
614 @classmethod
615 def setUpClass(cls):
616 requires('gui')
617 cls.root = Tk()
618 cls.root.withdraw()
619 cls.text = Text(cls.root)
620 cls.editor = MockEditor(text=cls.text)
621 cls.do_rstrip = ft.Rstrip(cls.editor).do_rstrip
623 @classmethod
624 def tearDownClass(cls):
625 del cls.text, cls.do_rstrip, cls.editor
626 cls.root.update_idletasks()
627 cls.root.destroy()
628 del cls.root
630 def tearDown(self):
631 self.text.delete('1.0', 'end-1c')
633 def test_rstrip_lines(self):
634 original = (
635 "Line with an ending tab \n"
636 "Line ending in 5 spaces \n"
637 "Linewithnospaces\n"
638 " indented line\n"
639 " indented line with trailing space \n"
640 " \n")
641 stripped = (
642 "Line with an ending tab\n"
643 "Line ending in 5 spaces\n"
644 "Linewithnospaces\n"
645 " indented line\n"
646 " indented line with trailing space\n")
648 self.text.insert('1.0', original)
649 self.do_rstrip()
650 self.assertEqual(self.text.get('1.0', 'insert'), stripped)
652 def test_rstrip_end(self):
653 text = self.text
654 for code in ('', '\n', '\n\n\n'):
655 with self.subTest(code=code):
656 text.insert('1.0', code)
657 self.do_rstrip()
658 self.assertEqual(text.get('1.0','end-1c'), '')
659 for code in ('a\n', 'a\n\n', 'a\n\n\n'):
660 with self.subTest(code=code):
661 text.delete('1.0', 'end-1c')
662 text.insert('1.0', code)
663 self.do_rstrip()
664 self.assertEqual(text.get('1.0','end-1c'), 'a\n')
667if __name__ == '__main__': 667 ↛ 668line 667 didn't jump to line 668, because the condition on line 667 was never true
668 unittest.main(verbosity=2, exit=2)