https://github.com/python/cpython/commit/c20318bf09e59946f805ace2bd15f70524009edc
commit: c20318bf09e59946f805ace2bd15f70524009edc
branch: main
author: Terry Jan Reedy <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-08-23T01:38:23-04:00
summary:

gh-155648: In IDLE tests, call unittest.main without exit arg (#156249)

* gh-155648: In IDLE tests, call unittest.main without exit arg

DD bug 73: In idlelib.idle_test, test_xyz.py files should end with

if __name__ == '__main__':
    unittest.main(verbosity=2)

The default exit is True. This need not and should not be added.
5 files add the confusing equivalent exit=2 ("why the weird value?"),

4 files add exit=False. This is nonsensical when there is nothing more to run;
main will immediately exit anyway. When running a test file from an IDLE editor,
this argument has no visible effect. However, a Claude-based
bug finder claims that in other circumstances (such as a program running
the test in a shell), the good test may falsely fail. Even if this is
not true, it can only confuse a reader.

(The only place in idlelib for exit=False is in idlelib/abc.py files where the 
unittest
is followed by an htest. The default exit=True exits the process, skipping the 
htest.)

As part of editing the discussion of this in idle_test/htest.py, I clarified 
other things.

files:
M Lib/idlelib/idle_test/README.txt
M Lib/idlelib/idle_test/htest.py
M Lib/idlelib/idle_test/test_delegator.py
M Lib/idlelib/idle_test/test_format.py
M Lib/idlelib/idle_test/test_history.py
M Lib/idlelib/idle_test/test_pathbrowser.py
M Lib/idlelib/idle_test/test_query.py
M Lib/idlelib/idle_test/test_search.py
M Lib/idlelib/idle_test/test_searchbase.py
M Lib/idlelib/idle_test/test_text.py
M Lib/idlelib/idle_test/test_undo.py

diff --git a/Lib/idlelib/idle_test/README.txt b/Lib/idlelib/idle_test/README.txt
index cacd06db873d039..242de2225248178 100644
--- a/Lib/idlelib/idle_test/README.txt
+++ b/Lib/idlelib/idle_test/README.txt
@@ -33,9 +33,9 @@ insert the import and main lines before the htest lines.
 
 if __name__ == "__main__":
     from unittest import main
-    main('idlelib.idle_test.test_abc', verbosity=2, exit=False)
+    main('idlelib.idle_test.test_abc', verbosity=2)
 
-The ', exit=False' is only needed if an htest follows.
+Add ', exit=False' to the main call if and only if an htest follows.
 
 
 
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
index 778e5c3d84e4963..0bd0378fbfa07a9 100644
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -1,48 +1,51 @@
 """Run human tests of Idle's window, dialog, and popup widgets.
 
-run(*tests) Create a master Tk() htest window.  Within that, run each
-callable in tests after finding the matching test spec in this file.  If
-tests is empty, run an htest for each spec dict in this file after
-finding the matching callable in the module named in the spec.  Close
-the master window to end testing.
-
-In a tested module, let X be a global name bound to a callable (class or
-function) whose .__name__ attribute is also X (the usual situation). The
-first parameter of X must be 'parent' or 'master'.  When called, the
-first argument will be the root window.  X must create a child
-Toplevel(parent/master) (or subclass thereof).  The Toplevel may be a
-test widget or dialog, in which case the callable is the corresponding
-class.  Or the Toplevel may contain the widget to be tested or set up a
-context in which a test widget is invoked.  In this latter case, the
-callable is a wrapper function that sets up the Toplevel and other
-objects.  Wrapper function names, such as _editor_window', should start
-with '_' and be lowercase.
-
+The main function, `run(*tests)`, is defined at the end of this file.
+Argument `tests` is a possibly empty tuple of callables defined in some
+idlelib.abc module (or possibly modules).  Its steps:
+1. Create a master Tk() htest window.  Within that window ...
+2a. If tuple `tests` is not empty, run was likely called from one
+    module.  Run each callable in `tests` after finding the matching
+    callable_spec test spec in this file.
+2b. If tests is empty, run was likely called from this file.
+    Run an htest for each spec dict in this file after finding the
+    matching callable in the module named in the spec.
+3. Close the master window to end testing.
+
+In a tested module, let X be a global name bound to a callable (class
+or function) whose .__name__ attribute (its `class` or `def` definition
+name) is also X.  X must expect exactly 1 positional argument, a
+parent toplevel window. Run passes the htest window.  X must create a
+child Toplevel(parent/master).  The callable may be either a runtime
+object or a wrapper function written just for the test.  In the latter
+case, its name should start with '_' and be lowercase (such as '_ttt').
 
 End the module with
-
+```
 if __name__ == '__main__':
-    <run unittest.main with 'exit=False'>
+    from unittest import main
+    main("idlelib.idle_test.test_xyz", verbosity=2, exit=False)
+
     from idlelib.idle_test.htest import run
-    run(callable)  # There could be multiple comma-separated callables.
+    run(callable)
+```
+Replace 'xyz' as appropriate and 'callable' with the callable name or
+comma-separated names (multiple names is rare).  'exit=False' is needed
+for the htest to run.
 
 To have wrapper functions ignored by coverage reports, tag the def
-header like so: "def _wrapper(parent):  # htest #".  Use the same tag
-for htest lines in widget code.  Make sure that the 'if __name__' line
-matches the above.  Then have make sure that .coveragerc includes the
-following:
-
+header like so: "def _wrapper(root):  # htest #".  Use the same tag
+for htest-only lines in the main code. To ignore the 'if __name__'
+statement, match the example above.  Add the below to coveragerc.
+```
 [report]
 exclude_lines =
     .*# htest #
     if __name__ == .__main__.:
-
-(The "." instead of "'" is intentional and necessary.)
-
+```
 
 To run any X, this file must contain a matching instance of the
 following template, with X.__name__ prepended to '_spec'.
-When all tests are run, the prefix is use to get X.
 
 callable_spec = {
     'file': '',
@@ -51,11 +54,10 @@
     }
 
 file (no .py): run() imports file.py.
-kwds: augmented with {'parent':root} and passed to X as **kwds.
+kwds: run() augments with {'parent':root} and passes to X as **kwds.
 title: an example kwd; some widgets need this, delete line if not.
 msg: master window hints about testing the widget.
 
-
 TODO test these modules and classes:
   autocomplete_w.AutoCompleteWindow
   debugger.Debugger
diff --git a/Lib/idlelib/idle_test/test_delegator.py 
b/Lib/idlelib/idle_test/test_delegator.py
index 922416297a42e02..c4273deee7ffbdd 100644
--- a/Lib/idlelib/idle_test/test_delegator.py
+++ b/Lib/idlelib/idle_test/test_delegator.py
@@ -41,4 +41,4 @@ def test_mydel(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=2)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_format.py 
b/Lib/idlelib/idle_test/test_format.py
index e5e903688597aa7..6550e9765f290d3 100644
--- a/Lib/idlelib/idle_test/test_format.py
+++ b/Lib/idlelib/idle_test/test_format.py
@@ -665,4 +665,4 @@ def test_rstrip_end(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=2)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_history.py 
b/Lib/idlelib/idle_test/test_history.py
index 675396514447514..e1031579c3d8210 100644
--- a/Lib/idlelib/idle_test/test_history.py
+++ b/Lib/idlelib/idle_test/test_history.py
@@ -169,4 +169,4 @@ def test_history_prev_next(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=2)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_pathbrowser.py 
b/Lib/idlelib/idle_test/test_pathbrowser.py
index 13d8b9e1ba9572a..a198978d5c1ef72 100644
--- a/Lib/idlelib/idle_test/test_pathbrowser.py
+++ b/Lib/idlelib/idle_test/test_pathbrowser.py
@@ -83,4 +83,4 @@ def test_PathBrowserTreeItem(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=False)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_query.py 
b/Lib/idlelib/idle_test/test_query.py
index a6ef858a8c954a2..58c173723a5adac 100644
--- a/Lib/idlelib/idle_test/test_query.py
+++ b/Lib/idlelib/idle_test/test_query.py
@@ -448,4 +448,4 @@ def test_click_args(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=False)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_search.py 
b/Lib/idlelib/idle_test/test_search.py
index de703c195cd2290..2b0a9d483bfc0ff 100644
--- a/Lib/idlelib/idle_test/test_search.py
+++ b/Lib/idlelib/idle_test/test_search.py
@@ -77,4 +77,4 @@ def test_find_selection(self):
         text.delete('2.0', 'end')
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=2)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_searchbase.py 
b/Lib/idlelib/idle_test/test_searchbase.py
index 8c9c410ebaf47c0..1780cab6527dd94 100644
--- a/Lib/idlelib/idle_test/test_searchbase.py
+++ b/Lib/idlelib/idle_test/test_searchbase.py
@@ -157,4 +157,4 @@ def test_create_command_buttons(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=2)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_text.py 
b/Lib/idlelib/idle_test/test_text.py
index 43a9ba02c3d3c9a..8ee1c9f2d768131 100644
--- a/Lib/idlelib/idle_test/test_text.py
+++ b/Lib/idlelib/idle_test/test_text.py
@@ -233,4 +233,4 @@ def setUp(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=False)
+    unittest.main(verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_undo.py 
b/Lib/idlelib/idle_test/test_undo.py
index beb5b582039f884..0488a2c9809b48c 100644
--- a/Lib/idlelib/idle_test/test_undo.py
+++ b/Lib/idlelib/idle_test/test_undo.py
@@ -132,4 +132,4 @@ def test_addcmd(self):
 
 
 if __name__ == '__main__':
-    unittest.main(verbosity=2, exit=False)
+    unittest.main(verbosity=2)

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to