From 5e5f1a36972c424a057a792f9de84d1bdb029b56 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Thu, 23 May 2024 20:07:35 +0800 Subject: [PATCH 01/14] Fix high DPI causes Tools/unittestgui(PyUnit) window blurry on Windows --- Misc/ACKS | 1 + .../Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst | 1 + Tools/unittestgui/unittestgui.py | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst diff --git a/Misc/ACKS b/Misc/ACKS index eaa7453aaade3e..9a08fd59607544 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1096,6 +1096,7 @@ Ivan Levkivskyi Ben Lewis William Lewis Akira Li +Jia Hao Li Robert Li Xuanji Li Zekun Li diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst b/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst new file mode 100644 index 00000000000000..6a3538fc1e5a7f --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst @@ -0,0 +1 @@ +Fix high DPI causes Tools/unittestgui(PyUnit) window blurry on Windows \ No newline at end of file diff --git a/Tools/unittestgui/unittestgui.py b/Tools/unittestgui/unittestgui.py index 4fc5930adbbfc7..70fff31754eb37 100755 --- a/Tools/unittestgui/unittestgui.py +++ b/Tools/unittestgui/unittestgui.py @@ -39,6 +39,9 @@ from tkinter import simpledialog +if sys.platform == 'win32': + from idlelib.util import fix_win_hidpi + fix_win_hidpi() ############################################################################## From ab9781ae11532f8ab4772820fde1e4a38497a4f4 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Thu, 23 May 2024 20:16:07 +0800 Subject: [PATCH 02/14] lint --- .../Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst b/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst index 6a3538fc1e5a7f..09d10d5af183f1 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst @@ -1 +1,2 @@ -Fix high DPI causes Tools/unittestgui(PyUnit) window blurry on Windows \ No newline at end of file +Fix high DPI causes Tools/unittestgui(PyUnit) window blurry on Windows +Patch by Wulian233 From 570dc55b79ddef1242cf75ec55e8a1b49f0483f0 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sat, 25 May 2024 17:36:07 +0800 Subject: [PATCH 03/14] Update README.rst --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 44b020e0c7ed5f..a4f8104fa20cbc 100644 --- a/README.rst +++ b/README.rst @@ -136,7 +136,7 @@ What's New ---------- We have a comprehensive overview of the changes in the `What's New in Python -3.13 `_ document. For a more +3.14 `_ document. For a more detailed change log, read `Misc/NEWS `_, but a full accounting of changes can only be gleaned from the `commit history @@ -149,7 +149,7 @@ entitled "Installing multiple versions". Documentation ------------- -`Documentation for Python 3.13 `_ is online, +`Documentation for Python 3.14 `_ is online, updated daily. It can also be downloaded in many formats for faster access. The documentation @@ -200,15 +200,15 @@ intend to install multiple versions using the same prefix you must decide which version (if any) is your "primary" version. Install that version using ``make install``. Install all other versions using ``make altinstall``. -For example, if you want to install Python 2.7, 3.6, and 3.13 with 3.13 being the -primary version, you would execute ``make install`` in your 3.13 build directory +For example, if you want to install Python 2.7, 3.6, and 3.14 with 3.14 being the +primary version, you would execute ``make install`` in your 3.14 build directory and ``make altinstall`` in the others. Release Schedule ---------------- -See :pep:`719` for Python 3.13 release details. +See :pep:`745` for Python 3.14 release details. Copyright and License Information From d7651deceb511bec956b2dee00842c647e47e943 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sat, 25 May 2024 17:53:13 +0800 Subject: [PATCH 04/14] Update layout.html --- Doc/tools/templates/layout.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/tools/templates/layout.html b/Doc/tools/templates/layout.html index e931147813ae03..6d0c9c07bf61db 100644 --- a/Doc/tools/templates/layout.html +++ b/Doc/tools/templates/layout.html @@ -61,7 +61,13 @@ // since all the "active, built and not hidden" versions will be shown automatically. let versions = config.versions.active.concat([ { - slug: "dev (3.13)", + slug: "dev (3.14)", + urls: { + documentation: "https://docs.python.org/3.14/", + } + }, + { + slug: "3.13", urls: { documentation: "https://docs.python.org/3.13/", } From d46ec136d150ed1a56113b8fe2d790eade0c8a6e Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sat, 25 May 2024 18:33:47 +0800 Subject: [PATCH 05/14] Update help.html --- Lib/idlelib/help.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html index 827d230b54e159..988eb80d1cbbfc 100644 --- a/Lib/idlelib/help.html +++ b/Lib/idlelib/help.html @@ -5,7 +5,7 @@ - IDLE — Python 3.13.0a2 documentation + IDLE — Python 3.14.0a0 documentation @@ -18,7 +18,7 @@ @@ -184,7 +184,7 @@

Navigation

  • - 3.13.0a2 Documentation » + 3.14.0a0 Documentation »
  • @@ -1136,7 +1136,7 @@

    Navigation

  • - 3.13.0a2 Documentation » + 3.14.0a0 Documentation »
  • @@ -1180,7 +1180,7 @@

    Navigation



    - Last updated on Jan 17, 2024 (06:57 UTC). + Last updated on May 25, 2024 (10:33 UTC). Found a bug?
    From 8b6477c23d47472e06fbfe09807237ac5afb3e17 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sat, 25 May 2024 18:38:46 +0800 Subject: [PATCH 06/14] Delete my last PR files --- Tools/unittestgui/unittestgui.py | 480 ------------------------------- 1 file changed, 480 deletions(-) delete mode 100755 Tools/unittestgui/unittestgui.py diff --git a/Tools/unittestgui/unittestgui.py b/Tools/unittestgui/unittestgui.py deleted file mode 100755 index 70fff31754eb37..00000000000000 --- a/Tools/unittestgui/unittestgui.py +++ /dev/null @@ -1,480 +0,0 @@ -#!/usr/bin/env python3 -""" -GUI framework and application for use with Python unit testing framework. -Execute tests written using the framework provided by the 'unittest' module. - -Updated for unittest test discovery by Mark Roddy and Python 3 -support by Brian Curtin. - -Based on the original by Steve Purcell, from: - - http://pyunit.sourceforge.net/ - -Copyright (c) 1999, 2000, 2001 Steve Purcell -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -""" - -__author__ = "Steve Purcell (stephen_purcell@yahoo.com)" - -import sys -import traceback -import unittest - -import tkinter as tk -from tkinter import messagebox -from tkinter import filedialog -from tkinter import simpledialog - - -if sys.platform == 'win32': - from idlelib.util import fix_win_hidpi - fix_win_hidpi() - - -############################################################################## -# GUI framework classes -############################################################################## - -class BaseGUITestRunner(object): - """Subclass this class to create a GUI TestRunner that uses a specific - windowing toolkit. The class takes care of running tests in the correct - manner, and making callbacks to the derived class to obtain information - or signal that events have occurred. - """ - def __init__(self, *args, **kwargs): - self.currentResult = None - self.running = 0 - self.__rollbackImporter = RollbackImporter() - self.test_suite = None - - #test discovery variables - self.directory_to_read = '' - self.top_level_dir = '' - self.test_file_glob_pattern = 'test*.py' - - self.initGUI(*args, **kwargs) - - def errorDialog(self, title, message): - "Override to display an error arising from GUI usage" - pass - - def getDirectoryToDiscover(self): - "Override to prompt user for directory to perform test discovery" - pass - - def runClicked(self): - "To be called in response to user choosing to run a test" - if self.running: return - if not self.test_suite: - self.errorDialog("Test Discovery", "You discover some tests first!") - return - self.currentResult = GUITestResult(self) - self.totalTests = self.test_suite.countTestCases() - self.running = 1 - self.notifyRunning() - self.test_suite.run(self.currentResult) - self.running = 0 - self.notifyStopped() - - def stopClicked(self): - "To be called in response to user stopping the running of a test" - if self.currentResult: - self.currentResult.stop() - - def discoverClicked(self): - self.__rollbackImporter.rollbackImports() - directory = self.getDirectoryToDiscover() - if not directory: - return - self.directory_to_read = directory - try: - # Explicitly use 'None' value if no top level directory is - # specified (indicated by empty string) as discover() explicitly - # checks for a 'None' to determine if no tld has been specified - top_level_dir = self.top_level_dir or None - tests = unittest.defaultTestLoader.discover(directory, self.test_file_glob_pattern, top_level_dir) - self.test_suite = tests - except: - exc_type, exc_value, exc_tb = sys.exc_info() - traceback.print_exception(*sys.exc_info()) - self.errorDialog("Unable to run test '%s'" % directory, - "Error loading specified test: %s, %s" % (exc_type, exc_value)) - return - self.notifyTestsDiscovered(self.test_suite) - - # Required callbacks - - def notifyTestsDiscovered(self, test_suite): - "Override to display information about the suite of discovered tests" - pass - - def notifyRunning(self): - "Override to set GUI in 'running' mode, enabling 'stop' button etc." - pass - - def notifyStopped(self): - "Override to set GUI in 'stopped' mode, enabling 'run' button etc." - pass - - def notifyTestFailed(self, test, err): - "Override to indicate that a test has just failed" - pass - - def notifyTestErrored(self, test, err): - "Override to indicate that a test has just errored" - pass - - def notifyTestSkipped(self, test, reason): - "Override to indicate that test was skipped" - pass - - def notifyTestFailedExpectedly(self, test, err): - "Override to indicate that test has just failed expectedly" - pass - - def notifyTestStarted(self, test): - "Override to indicate that a test is about to run" - pass - - def notifyTestFinished(self, test): - """Override to indicate that a test has finished (it may already have - failed or errored)""" - pass - - -class GUITestResult(unittest.TestResult): - """A TestResult that makes callbacks to its associated GUI TestRunner. - Used by BaseGUITestRunner. Need not be created directly. - """ - def __init__(self, callback): - unittest.TestResult.__init__(self) - self.callback = callback - - def addError(self, test, err): - unittest.TestResult.addError(self, test, err) - self.callback.notifyTestErrored(test, err) - - def addFailure(self, test, err): - unittest.TestResult.addFailure(self, test, err) - self.callback.notifyTestFailed(test, err) - - def addSkip(self, test, reason): - super(GUITestResult,self).addSkip(test, reason) - self.callback.notifyTestSkipped(test, reason) - - def addExpectedFailure(self, test, err): - super(GUITestResult,self).addExpectedFailure(test, err) - self.callback.notifyTestFailedExpectedly(test, err) - - def stopTest(self, test): - unittest.TestResult.stopTest(self, test) - self.callback.notifyTestFinished(test) - - def startTest(self, test): - unittest.TestResult.startTest(self, test) - self.callback.notifyTestStarted(test) - - -class RollbackImporter: - """This tricky little class is used to make sure that modules under test - will be reloaded the next time they are imported. - """ - def __init__(self): - self.previousModules = sys.modules.copy() - - def rollbackImports(self): - for modname in sys.modules.copy().keys(): - if not modname in self.previousModules: - # Force reload when modname next imported - del(sys.modules[modname]) - - -############################################################################## -# Tkinter GUI -############################################################################## - -class DiscoverSettingsDialog(simpledialog.Dialog): - """ - Dialog box for prompting test discovery settings - """ - - def __init__(self, master, top_level_dir, test_file_glob_pattern, *args, **kwargs): - self.top_level_dir = top_level_dir - self.dirVar = tk.StringVar() - self.dirVar.set(top_level_dir) - - self.test_file_glob_pattern = test_file_glob_pattern - self.testPatternVar = tk.StringVar() - self.testPatternVar.set(test_file_glob_pattern) - - simpledialog.Dialog.__init__(self, master, title="Discover Settings", - *args, **kwargs) - - def body(self, master): - tk.Label(master, text="Top Level Directory").grid(row=0) - self.e1 = tk.Entry(master, textvariable=self.dirVar) - self.e1.grid(row = 0, column=1) - tk.Button(master, text="...", - command=lambda: self.selectDirClicked(master)).grid(row=0,column=3) - - tk.Label(master, text="Test File Pattern").grid(row=1) - self.e2 = tk.Entry(master, textvariable = self.testPatternVar) - self.e2.grid(row = 1, column=1) - return None - - def selectDirClicked(self, master): - dir_path = filedialog.askdirectory(parent=master) - if dir_path: - self.dirVar.set(dir_path) - - def apply(self): - self.top_level_dir = self.dirVar.get() - self.test_file_glob_pattern = self.testPatternVar.get() - -class TkTestRunner(BaseGUITestRunner): - """An implementation of BaseGUITestRunner using Tkinter. - """ - def initGUI(self, root, initialTestName): - """Set up the GUI inside the given root window. The test name entry - field will be pre-filled with the given initialTestName. - """ - self.root = root - - self.statusVar = tk.StringVar() - self.statusVar.set("Idle") - - #tk vars for tracking counts of test result types - self.runCountVar = tk.IntVar() - self.failCountVar = tk.IntVar() - self.errorCountVar = tk.IntVar() - self.skipCountVar = tk.IntVar() - self.expectFailCountVar = tk.IntVar() - self.remainingCountVar = tk.IntVar() - - self.top = tk.Frame() - self.top.pack(fill=tk.BOTH, expand=1) - self.createWidgets() - - def getDirectoryToDiscover(self): - return filedialog.askdirectory() - - def settingsClicked(self): - d = DiscoverSettingsDialog(self.top, self.top_level_dir, self.test_file_glob_pattern) - self.top_level_dir = d.top_level_dir - self.test_file_glob_pattern = d.test_file_glob_pattern - - def notifyTestsDiscovered(self, test_suite): - discovered = test_suite.countTestCases() - self.runCountVar.set(0) - self.failCountVar.set(0) - self.errorCountVar.set(0) - self.remainingCountVar.set(discovered) - self.progressBar.setProgressFraction(0.0) - self.errorListbox.delete(0, tk.END) - self.statusVar.set("Discovering tests from %s. Found: %s" % - (self.directory_to_read, discovered)) - self.stopGoButton['state'] = tk.NORMAL - - def createWidgets(self): - """Creates and packs the various widgets. - - Why is it that GUI code always ends up looking a mess, despite all the - best intentions to keep it tidy? Answers on a postcard, please. - """ - # Status bar - statusFrame = tk.Frame(self.top, relief=tk.SUNKEN, borderwidth=2) - statusFrame.pack(anchor=tk.SW, fill=tk.X, side=tk.BOTTOM) - tk.Label(statusFrame, width=1, textvariable=self.statusVar).pack(side=tk.TOP, fill=tk.X) - - # Area to enter name of test to run - leftFrame = tk.Frame(self.top, borderwidth=3) - leftFrame.pack(fill=tk.BOTH, side=tk.LEFT, anchor=tk.NW, expand=1) - suiteNameFrame = tk.Frame(leftFrame, borderwidth=3) - suiteNameFrame.pack(fill=tk.X) - - # Progress bar - progressFrame = tk.Frame(leftFrame, relief=tk.GROOVE, borderwidth=2) - progressFrame.pack(fill=tk.X, expand=0, anchor=tk.NW) - tk.Label(progressFrame, text="Progress:").pack(anchor=tk.W) - self.progressBar = ProgressBar(progressFrame, relief=tk.SUNKEN, - borderwidth=2) - self.progressBar.pack(fill=tk.X, expand=1) - - - # Area with buttons to start/stop tests and quit - buttonFrame = tk.Frame(self.top, borderwidth=3) - buttonFrame.pack(side=tk.LEFT, anchor=tk.NW, fill=tk.Y) - - tk.Button(buttonFrame, text="Discover Tests", - command=self.discoverClicked).pack(fill=tk.X) - - - self.stopGoButton = tk.Button(buttonFrame, text="Start", - command=self.runClicked, state=tk.DISABLED) - self.stopGoButton.pack(fill=tk.X) - - tk.Button(buttonFrame, text="Close", - command=self.top.quit).pack(side=tk.BOTTOM, fill=tk.X) - tk.Button(buttonFrame, text="Settings", - command=self.settingsClicked).pack(side=tk.BOTTOM, fill=tk.X) - - # Area with labels reporting results - for label, var in (('Run:', self.runCountVar), - ('Failures:', self.failCountVar), - ('Errors:', self.errorCountVar), - ('Skipped:', self.skipCountVar), - ('Expected Failures:', self.expectFailCountVar), - ('Remaining:', self.remainingCountVar), - ): - tk.Label(progressFrame, text=label).pack(side=tk.LEFT) - tk.Label(progressFrame, textvariable=var, - foreground="blue").pack(side=tk.LEFT, fill=tk.X, - expand=1, anchor=tk.W) - - # List box showing errors and failures - tk.Label(leftFrame, text="Failures and errors:").pack(anchor=tk.W) - listFrame = tk.Frame(leftFrame, relief=tk.SUNKEN, borderwidth=2) - listFrame.pack(fill=tk.BOTH, anchor=tk.NW, expand=1) - self.errorListbox = tk.Listbox(listFrame, foreground='red', - selectmode=tk.SINGLE, - selectborderwidth=0) - self.errorListbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, - anchor=tk.NW) - listScroll = tk.Scrollbar(listFrame, command=self.errorListbox.yview) - listScroll.pack(side=tk.LEFT, fill=tk.Y, anchor=tk.N) - self.errorListbox.bind("", - lambda e, self=self: self.showSelectedError()) - self.errorListbox.configure(yscrollcommand=listScroll.set) - - def errorDialog(self, title, message): - messagebox.showerror(parent=self.root, title=title, - message=message) - - def notifyRunning(self): - self.runCountVar.set(0) - self.failCountVar.set(0) - self.errorCountVar.set(0) - self.remainingCountVar.set(self.totalTests) - self.errorInfo = [] - while self.errorListbox.size(): - self.errorListbox.delete(0) - #Stopping seems not to work, so simply disable the start button - #self.stopGoButton.config(command=self.stopClicked, text="Stop") - self.stopGoButton.config(state=tk.DISABLED) - self.progressBar.setProgressFraction(0.0) - self.top.update_idletasks() - - def notifyStopped(self): - self.stopGoButton.config(state=tk.DISABLED) - #self.stopGoButton.config(command=self.runClicked, text="Start") - self.statusVar.set("Idle") - - def notifyTestStarted(self, test): - self.statusVar.set(str(test)) - self.top.update_idletasks() - - def notifyTestFailed(self, test, err): - self.failCountVar.set(1 + self.failCountVar.get()) - self.errorListbox.insert(tk.END, "Failure: %s" % test) - self.errorInfo.append((test,err)) - - def notifyTestErrored(self, test, err): - self.errorCountVar.set(1 + self.errorCountVar.get()) - self.errorListbox.insert(tk.END, "Error: %s" % test) - self.errorInfo.append((test,err)) - - def notifyTestSkipped(self, test, reason): - super(TkTestRunner, self).notifyTestSkipped(test, reason) - self.skipCountVar.set(1 + self.skipCountVar.get()) - - def notifyTestFailedExpectedly(self, test, err): - super(TkTestRunner, self).notifyTestFailedExpectedly(test, err) - self.expectFailCountVar.set(1 + self.expectFailCountVar.get()) - - - def notifyTestFinished(self, test): - self.remainingCountVar.set(self.remainingCountVar.get() - 1) - self.runCountVar.set(1 + self.runCountVar.get()) - fractionDone = float(self.runCountVar.get())/float(self.totalTests) - fillColor = len(self.errorInfo) and "red" or "green" - self.progressBar.setProgressFraction(fractionDone, fillColor) - - def showSelectedError(self): - selection = self.errorListbox.curselection() - if not selection: return - selected = int(selection[0]) - txt = self.errorListbox.get(selected) - window = tk.Toplevel(self.root) - window.title(txt) - window.protocol('WM_DELETE_WINDOW', window.quit) - test, error = self.errorInfo[selected] - tk.Label(window, text=str(test), - foreground="red", justify=tk.LEFT).pack(anchor=tk.W) - tracebackLines = traceback.format_exception(*error) - tracebackText = "".join(tracebackLines) - tk.Label(window, text=tracebackText, justify=tk.LEFT).pack() - tk.Button(window, text="Close", - command=window.quit).pack(side=tk.BOTTOM) - window.bind('', lambda e, w=window: w.quit()) - window.mainloop() - window.destroy() - - -class ProgressBar(tk.Frame): - """A simple progress bar that shows a percentage progress in - the given colour.""" - - def __init__(self, *args, **kwargs): - tk.Frame.__init__(self, *args, **kwargs) - self.canvas = tk.Canvas(self, height='20', width='60', - background='white', borderwidth=3) - self.canvas.pack(fill=tk.X, expand=1) - self.rect = self.text = None - self.canvas.bind('', self.paint) - self.setProgressFraction(0.0) - - def setProgressFraction(self, fraction, color='blue'): - self.fraction = fraction - self.color = color - self.paint() - self.canvas.update_idletasks() - - def paint(self, *args): - totalWidth = self.canvas.winfo_width() - width = int(self.fraction * float(totalWidth)) - height = self.canvas.winfo_height() - if self.rect is not None: self.canvas.delete(self.rect) - if self.text is not None: self.canvas.delete(self.text) - self.rect = self.canvas.create_rectangle(0, 0, width, height, - fill=self.color) - percentString = "%3.0f%%" % (100.0 * self.fraction) - self.text = self.canvas.create_text(totalWidth/2, height/2, - anchor=tk.CENTER, - text=percentString) - -def main(initialTestName=""): - root = tk.Tk() - root.title("PyUnit") - runner = TkTestRunner(root, initialTestName) - root.protocol('WM_DELETE_WINDOW', root.quit) - root.mainloop() - - -if __name__ == '__main__': - if len(sys.argv) == 2: - main(sys.argv[1]) - else: - main() From 2265fa924d8df396c37942d90b5a2429de73faf4 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sat, 25 May 2024 19:19:47 +0800 Subject: [PATCH 07/14] ? --- Tools/unittestgui/unittestgui.py | 480 ------------------------------- 1 file changed, 480 deletions(-) delete mode 100755 Tools/unittestgui/unittestgui.py diff --git a/Tools/unittestgui/unittestgui.py b/Tools/unittestgui/unittestgui.py deleted file mode 100755 index 70fff31754eb37..00000000000000 --- a/Tools/unittestgui/unittestgui.py +++ /dev/null @@ -1,480 +0,0 @@ -#!/usr/bin/env python3 -""" -GUI framework and application for use with Python unit testing framework. -Execute tests written using the framework provided by the 'unittest' module. - -Updated for unittest test discovery by Mark Roddy and Python 3 -support by Brian Curtin. - -Based on the original by Steve Purcell, from: - - http://pyunit.sourceforge.net/ - -Copyright (c) 1999, 2000, 2001 Steve Purcell -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -""" - -__author__ = "Steve Purcell (stephen_purcell@yahoo.com)" - -import sys -import traceback -import unittest - -import tkinter as tk -from tkinter import messagebox -from tkinter import filedialog -from tkinter import simpledialog - - -if sys.platform == 'win32': - from idlelib.util import fix_win_hidpi - fix_win_hidpi() - - -############################################################################## -# GUI framework classes -############################################################################## - -class BaseGUITestRunner(object): - """Subclass this class to create a GUI TestRunner that uses a specific - windowing toolkit. The class takes care of running tests in the correct - manner, and making callbacks to the derived class to obtain information - or signal that events have occurred. - """ - def __init__(self, *args, **kwargs): - self.currentResult = None - self.running = 0 - self.__rollbackImporter = RollbackImporter() - self.test_suite = None - - #test discovery variables - self.directory_to_read = '' - self.top_level_dir = '' - self.test_file_glob_pattern = 'test*.py' - - self.initGUI(*args, **kwargs) - - def errorDialog(self, title, message): - "Override to display an error arising from GUI usage" - pass - - def getDirectoryToDiscover(self): - "Override to prompt user for directory to perform test discovery" - pass - - def runClicked(self): - "To be called in response to user choosing to run a test" - if self.running: return - if not self.test_suite: - self.errorDialog("Test Discovery", "You discover some tests first!") - return - self.currentResult = GUITestResult(self) - self.totalTests = self.test_suite.countTestCases() - self.running = 1 - self.notifyRunning() - self.test_suite.run(self.currentResult) - self.running = 0 - self.notifyStopped() - - def stopClicked(self): - "To be called in response to user stopping the running of a test" - if self.currentResult: - self.currentResult.stop() - - def discoverClicked(self): - self.__rollbackImporter.rollbackImports() - directory = self.getDirectoryToDiscover() - if not directory: - return - self.directory_to_read = directory - try: - # Explicitly use 'None' value if no top level directory is - # specified (indicated by empty string) as discover() explicitly - # checks for a 'None' to determine if no tld has been specified - top_level_dir = self.top_level_dir or None - tests = unittest.defaultTestLoader.discover(directory, self.test_file_glob_pattern, top_level_dir) - self.test_suite = tests - except: - exc_type, exc_value, exc_tb = sys.exc_info() - traceback.print_exception(*sys.exc_info()) - self.errorDialog("Unable to run test '%s'" % directory, - "Error loading specified test: %s, %s" % (exc_type, exc_value)) - return - self.notifyTestsDiscovered(self.test_suite) - - # Required callbacks - - def notifyTestsDiscovered(self, test_suite): - "Override to display information about the suite of discovered tests" - pass - - def notifyRunning(self): - "Override to set GUI in 'running' mode, enabling 'stop' button etc." - pass - - def notifyStopped(self): - "Override to set GUI in 'stopped' mode, enabling 'run' button etc." - pass - - def notifyTestFailed(self, test, err): - "Override to indicate that a test has just failed" - pass - - def notifyTestErrored(self, test, err): - "Override to indicate that a test has just errored" - pass - - def notifyTestSkipped(self, test, reason): - "Override to indicate that test was skipped" - pass - - def notifyTestFailedExpectedly(self, test, err): - "Override to indicate that test has just failed expectedly" - pass - - def notifyTestStarted(self, test): - "Override to indicate that a test is about to run" - pass - - def notifyTestFinished(self, test): - """Override to indicate that a test has finished (it may already have - failed or errored)""" - pass - - -class GUITestResult(unittest.TestResult): - """A TestResult that makes callbacks to its associated GUI TestRunner. - Used by BaseGUITestRunner. Need not be created directly. - """ - def __init__(self, callback): - unittest.TestResult.__init__(self) - self.callback = callback - - def addError(self, test, err): - unittest.TestResult.addError(self, test, err) - self.callback.notifyTestErrored(test, err) - - def addFailure(self, test, err): - unittest.TestResult.addFailure(self, test, err) - self.callback.notifyTestFailed(test, err) - - def addSkip(self, test, reason): - super(GUITestResult,self).addSkip(test, reason) - self.callback.notifyTestSkipped(test, reason) - - def addExpectedFailure(self, test, err): - super(GUITestResult,self).addExpectedFailure(test, err) - self.callback.notifyTestFailedExpectedly(test, err) - - def stopTest(self, test): - unittest.TestResult.stopTest(self, test) - self.callback.notifyTestFinished(test) - - def startTest(self, test): - unittest.TestResult.startTest(self, test) - self.callback.notifyTestStarted(test) - - -class RollbackImporter: - """This tricky little class is used to make sure that modules under test - will be reloaded the next time they are imported. - """ - def __init__(self): - self.previousModules = sys.modules.copy() - - def rollbackImports(self): - for modname in sys.modules.copy().keys(): - if not modname in self.previousModules: - # Force reload when modname next imported - del(sys.modules[modname]) - - -############################################################################## -# Tkinter GUI -############################################################################## - -class DiscoverSettingsDialog(simpledialog.Dialog): - """ - Dialog box for prompting test discovery settings - """ - - def __init__(self, master, top_level_dir, test_file_glob_pattern, *args, **kwargs): - self.top_level_dir = top_level_dir - self.dirVar = tk.StringVar() - self.dirVar.set(top_level_dir) - - self.test_file_glob_pattern = test_file_glob_pattern - self.testPatternVar = tk.StringVar() - self.testPatternVar.set(test_file_glob_pattern) - - simpledialog.Dialog.__init__(self, master, title="Discover Settings", - *args, **kwargs) - - def body(self, master): - tk.Label(master, text="Top Level Directory").grid(row=0) - self.e1 = tk.Entry(master, textvariable=self.dirVar) - self.e1.grid(row = 0, column=1) - tk.Button(master, text="...", - command=lambda: self.selectDirClicked(master)).grid(row=0,column=3) - - tk.Label(master, text="Test File Pattern").grid(row=1) - self.e2 = tk.Entry(master, textvariable = self.testPatternVar) - self.e2.grid(row = 1, column=1) - return None - - def selectDirClicked(self, master): - dir_path = filedialog.askdirectory(parent=master) - if dir_path: - self.dirVar.set(dir_path) - - def apply(self): - self.top_level_dir = self.dirVar.get() - self.test_file_glob_pattern = self.testPatternVar.get() - -class TkTestRunner(BaseGUITestRunner): - """An implementation of BaseGUITestRunner using Tkinter. - """ - def initGUI(self, root, initialTestName): - """Set up the GUI inside the given root window. The test name entry - field will be pre-filled with the given initialTestName. - """ - self.root = root - - self.statusVar = tk.StringVar() - self.statusVar.set("Idle") - - #tk vars for tracking counts of test result types - self.runCountVar = tk.IntVar() - self.failCountVar = tk.IntVar() - self.errorCountVar = tk.IntVar() - self.skipCountVar = tk.IntVar() - self.expectFailCountVar = tk.IntVar() - self.remainingCountVar = tk.IntVar() - - self.top = tk.Frame() - self.top.pack(fill=tk.BOTH, expand=1) - self.createWidgets() - - def getDirectoryToDiscover(self): - return filedialog.askdirectory() - - def settingsClicked(self): - d = DiscoverSettingsDialog(self.top, self.top_level_dir, self.test_file_glob_pattern) - self.top_level_dir = d.top_level_dir - self.test_file_glob_pattern = d.test_file_glob_pattern - - def notifyTestsDiscovered(self, test_suite): - discovered = test_suite.countTestCases() - self.runCountVar.set(0) - self.failCountVar.set(0) - self.errorCountVar.set(0) - self.remainingCountVar.set(discovered) - self.progressBar.setProgressFraction(0.0) - self.errorListbox.delete(0, tk.END) - self.statusVar.set("Discovering tests from %s. Found: %s" % - (self.directory_to_read, discovered)) - self.stopGoButton['state'] = tk.NORMAL - - def createWidgets(self): - """Creates and packs the various widgets. - - Why is it that GUI code always ends up looking a mess, despite all the - best intentions to keep it tidy? Answers on a postcard, please. - """ - # Status bar - statusFrame = tk.Frame(self.top, relief=tk.SUNKEN, borderwidth=2) - statusFrame.pack(anchor=tk.SW, fill=tk.X, side=tk.BOTTOM) - tk.Label(statusFrame, width=1, textvariable=self.statusVar).pack(side=tk.TOP, fill=tk.X) - - # Area to enter name of test to run - leftFrame = tk.Frame(self.top, borderwidth=3) - leftFrame.pack(fill=tk.BOTH, side=tk.LEFT, anchor=tk.NW, expand=1) - suiteNameFrame = tk.Frame(leftFrame, borderwidth=3) - suiteNameFrame.pack(fill=tk.X) - - # Progress bar - progressFrame = tk.Frame(leftFrame, relief=tk.GROOVE, borderwidth=2) - progressFrame.pack(fill=tk.X, expand=0, anchor=tk.NW) - tk.Label(progressFrame, text="Progress:").pack(anchor=tk.W) - self.progressBar = ProgressBar(progressFrame, relief=tk.SUNKEN, - borderwidth=2) - self.progressBar.pack(fill=tk.X, expand=1) - - - # Area with buttons to start/stop tests and quit - buttonFrame = tk.Frame(self.top, borderwidth=3) - buttonFrame.pack(side=tk.LEFT, anchor=tk.NW, fill=tk.Y) - - tk.Button(buttonFrame, text="Discover Tests", - command=self.discoverClicked).pack(fill=tk.X) - - - self.stopGoButton = tk.Button(buttonFrame, text="Start", - command=self.runClicked, state=tk.DISABLED) - self.stopGoButton.pack(fill=tk.X) - - tk.Button(buttonFrame, text="Close", - command=self.top.quit).pack(side=tk.BOTTOM, fill=tk.X) - tk.Button(buttonFrame, text="Settings", - command=self.settingsClicked).pack(side=tk.BOTTOM, fill=tk.X) - - # Area with labels reporting results - for label, var in (('Run:', self.runCountVar), - ('Failures:', self.failCountVar), - ('Errors:', self.errorCountVar), - ('Skipped:', self.skipCountVar), - ('Expected Failures:', self.expectFailCountVar), - ('Remaining:', self.remainingCountVar), - ): - tk.Label(progressFrame, text=label).pack(side=tk.LEFT) - tk.Label(progressFrame, textvariable=var, - foreground="blue").pack(side=tk.LEFT, fill=tk.X, - expand=1, anchor=tk.W) - - # List box showing errors and failures - tk.Label(leftFrame, text="Failures and errors:").pack(anchor=tk.W) - listFrame = tk.Frame(leftFrame, relief=tk.SUNKEN, borderwidth=2) - listFrame.pack(fill=tk.BOTH, anchor=tk.NW, expand=1) - self.errorListbox = tk.Listbox(listFrame, foreground='red', - selectmode=tk.SINGLE, - selectborderwidth=0) - self.errorListbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, - anchor=tk.NW) - listScroll = tk.Scrollbar(listFrame, command=self.errorListbox.yview) - listScroll.pack(side=tk.LEFT, fill=tk.Y, anchor=tk.N) - self.errorListbox.bind("", - lambda e, self=self: self.showSelectedError()) - self.errorListbox.configure(yscrollcommand=listScroll.set) - - def errorDialog(self, title, message): - messagebox.showerror(parent=self.root, title=title, - message=message) - - def notifyRunning(self): - self.runCountVar.set(0) - self.failCountVar.set(0) - self.errorCountVar.set(0) - self.remainingCountVar.set(self.totalTests) - self.errorInfo = [] - while self.errorListbox.size(): - self.errorListbox.delete(0) - #Stopping seems not to work, so simply disable the start button - #self.stopGoButton.config(command=self.stopClicked, text="Stop") - self.stopGoButton.config(state=tk.DISABLED) - self.progressBar.setProgressFraction(0.0) - self.top.update_idletasks() - - def notifyStopped(self): - self.stopGoButton.config(state=tk.DISABLED) - #self.stopGoButton.config(command=self.runClicked, text="Start") - self.statusVar.set("Idle") - - def notifyTestStarted(self, test): - self.statusVar.set(str(test)) - self.top.update_idletasks() - - def notifyTestFailed(self, test, err): - self.failCountVar.set(1 + self.failCountVar.get()) - self.errorListbox.insert(tk.END, "Failure: %s" % test) - self.errorInfo.append((test,err)) - - def notifyTestErrored(self, test, err): - self.errorCountVar.set(1 + self.errorCountVar.get()) - self.errorListbox.insert(tk.END, "Error: %s" % test) - self.errorInfo.append((test,err)) - - def notifyTestSkipped(self, test, reason): - super(TkTestRunner, self).notifyTestSkipped(test, reason) - self.skipCountVar.set(1 + self.skipCountVar.get()) - - def notifyTestFailedExpectedly(self, test, err): - super(TkTestRunner, self).notifyTestFailedExpectedly(test, err) - self.expectFailCountVar.set(1 + self.expectFailCountVar.get()) - - - def notifyTestFinished(self, test): - self.remainingCountVar.set(self.remainingCountVar.get() - 1) - self.runCountVar.set(1 + self.runCountVar.get()) - fractionDone = float(self.runCountVar.get())/float(self.totalTests) - fillColor = len(self.errorInfo) and "red" or "green" - self.progressBar.setProgressFraction(fractionDone, fillColor) - - def showSelectedError(self): - selection = self.errorListbox.curselection() - if not selection: return - selected = int(selection[0]) - txt = self.errorListbox.get(selected) - window = tk.Toplevel(self.root) - window.title(txt) - window.protocol('WM_DELETE_WINDOW', window.quit) - test, error = self.errorInfo[selected] - tk.Label(window, text=str(test), - foreground="red", justify=tk.LEFT).pack(anchor=tk.W) - tracebackLines = traceback.format_exception(*error) - tracebackText = "".join(tracebackLines) - tk.Label(window, text=tracebackText, justify=tk.LEFT).pack() - tk.Button(window, text="Close", - command=window.quit).pack(side=tk.BOTTOM) - window.bind('', lambda e, w=window: w.quit()) - window.mainloop() - window.destroy() - - -class ProgressBar(tk.Frame): - """A simple progress bar that shows a percentage progress in - the given colour.""" - - def __init__(self, *args, **kwargs): - tk.Frame.__init__(self, *args, **kwargs) - self.canvas = tk.Canvas(self, height='20', width='60', - background='white', borderwidth=3) - self.canvas.pack(fill=tk.X, expand=1) - self.rect = self.text = None - self.canvas.bind('', self.paint) - self.setProgressFraction(0.0) - - def setProgressFraction(self, fraction, color='blue'): - self.fraction = fraction - self.color = color - self.paint() - self.canvas.update_idletasks() - - def paint(self, *args): - totalWidth = self.canvas.winfo_width() - width = int(self.fraction * float(totalWidth)) - height = self.canvas.winfo_height() - if self.rect is not None: self.canvas.delete(self.rect) - if self.text is not None: self.canvas.delete(self.text) - self.rect = self.canvas.create_rectangle(0, 0, width, height, - fill=self.color) - percentString = "%3.0f%%" % (100.0 * self.fraction) - self.text = self.canvas.create_text(totalWidth/2, height/2, - anchor=tk.CENTER, - text=percentString) - -def main(initialTestName=""): - root = tk.Tk() - root.title("PyUnit") - runner = TkTestRunner(root, initialTestName) - root.protocol('WM_DELETE_WINDOW', root.quit) - root.mainloop() - - -if __name__ == '__main__': - if len(sys.argv) == 2: - main(sys.argv[1]) - else: - main() From e69e8f8365bc5a047af455613650aedc9c593167 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sat, 25 May 2024 19:47:32 +0800 Subject: [PATCH 08/14] revert --- Misc/ACKS | 1 - ...-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst | 2 - Tools/unittestgui/unittestgui.py | 477 ++++++++++++++++++ 3 files changed, 477 insertions(+), 3 deletions(-) delete mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst create mode 100644 Tools/unittestgui/unittestgui.py diff --git a/Misc/ACKS b/Misc/ACKS index 9a08fd59607544..eaa7453aaade3e 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1096,7 +1096,6 @@ Ivan Levkivskyi Ben Lewis William Lewis Akira Li -Jia Hao Li Robert Li Xuanji Li Zekun Li diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst b/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst deleted file mode 100644 index 09d10d5af183f1..00000000000000 --- a/Misc/NEWS.d/next/Tools-Demos/2024-05-23-20-06-52.gh-issue-119459.BVA6Cw.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix high DPI causes Tools/unittestgui(PyUnit) window blurry on Windows -Patch by Wulian233 diff --git a/Tools/unittestgui/unittestgui.py b/Tools/unittestgui/unittestgui.py new file mode 100644 index 00000000000000..4fc5930adbbfc7 --- /dev/null +++ b/Tools/unittestgui/unittestgui.py @@ -0,0 +1,477 @@ +#!/usr/bin/env python3 +""" +GUI framework and application for use with Python unit testing framework. +Execute tests written using the framework provided by the 'unittest' module. + +Updated for unittest test discovery by Mark Roddy and Python 3 +support by Brian Curtin. + +Based on the original by Steve Purcell, from: + + http://pyunit.sourceforge.net/ + +Copyright (c) 1999, 2000, 2001 Steve Purcell +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +__author__ = "Steve Purcell (stephen_purcell@yahoo.com)" + +import sys +import traceback +import unittest + +import tkinter as tk +from tkinter import messagebox +from tkinter import filedialog +from tkinter import simpledialog + + + + +############################################################################## +# GUI framework classes +############################################################################## + +class BaseGUITestRunner(object): + """Subclass this class to create a GUI TestRunner that uses a specific + windowing toolkit. The class takes care of running tests in the correct + manner, and making callbacks to the derived class to obtain information + or signal that events have occurred. + """ + def __init__(self, *args, **kwargs): + self.currentResult = None + self.running = 0 + self.__rollbackImporter = RollbackImporter() + self.test_suite = None + + #test discovery variables + self.directory_to_read = '' + self.top_level_dir = '' + self.test_file_glob_pattern = 'test*.py' + + self.initGUI(*args, **kwargs) + + def errorDialog(self, title, message): + "Override to display an error arising from GUI usage" + pass + + def getDirectoryToDiscover(self): + "Override to prompt user for directory to perform test discovery" + pass + + def runClicked(self): + "To be called in response to user choosing to run a test" + if self.running: return + if not self.test_suite: + self.errorDialog("Test Discovery", "You discover some tests first!") + return + self.currentResult = GUITestResult(self) + self.totalTests = self.test_suite.countTestCases() + self.running = 1 + self.notifyRunning() + self.test_suite.run(self.currentResult) + self.running = 0 + self.notifyStopped() + + def stopClicked(self): + "To be called in response to user stopping the running of a test" + if self.currentResult: + self.currentResult.stop() + + def discoverClicked(self): + self.__rollbackImporter.rollbackImports() + directory = self.getDirectoryToDiscover() + if not directory: + return + self.directory_to_read = directory + try: + # Explicitly use 'None' value if no top level directory is + # specified (indicated by empty string) as discover() explicitly + # checks for a 'None' to determine if no tld has been specified + top_level_dir = self.top_level_dir or None + tests = unittest.defaultTestLoader.discover(directory, self.test_file_glob_pattern, top_level_dir) + self.test_suite = tests + except: + exc_type, exc_value, exc_tb = sys.exc_info() + traceback.print_exception(*sys.exc_info()) + self.errorDialog("Unable to run test '%s'" % directory, + "Error loading specified test: %s, %s" % (exc_type, exc_value)) + return + self.notifyTestsDiscovered(self.test_suite) + + # Required callbacks + + def notifyTestsDiscovered(self, test_suite): + "Override to display information about the suite of discovered tests" + pass + + def notifyRunning(self): + "Override to set GUI in 'running' mode, enabling 'stop' button etc." + pass + + def notifyStopped(self): + "Override to set GUI in 'stopped' mode, enabling 'run' button etc." + pass + + def notifyTestFailed(self, test, err): + "Override to indicate that a test has just failed" + pass + + def notifyTestErrored(self, test, err): + "Override to indicate that a test has just errored" + pass + + def notifyTestSkipped(self, test, reason): + "Override to indicate that test was skipped" + pass + + def notifyTestFailedExpectedly(self, test, err): + "Override to indicate that test has just failed expectedly" + pass + + def notifyTestStarted(self, test): + "Override to indicate that a test is about to run" + pass + + def notifyTestFinished(self, test): + """Override to indicate that a test has finished (it may already have + failed or errored)""" + pass + + +class GUITestResult(unittest.TestResult): + """A TestResult that makes callbacks to its associated GUI TestRunner. + Used by BaseGUITestRunner. Need not be created directly. + """ + def __init__(self, callback): + unittest.TestResult.__init__(self) + self.callback = callback + + def addError(self, test, err): + unittest.TestResult.addError(self, test, err) + self.callback.notifyTestErrored(test, err) + + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self.callback.notifyTestFailed(test, err) + + def addSkip(self, test, reason): + super(GUITestResult,self).addSkip(test, reason) + self.callback.notifyTestSkipped(test, reason) + + def addExpectedFailure(self, test, err): + super(GUITestResult,self).addExpectedFailure(test, err) + self.callback.notifyTestFailedExpectedly(test, err) + + def stopTest(self, test): + unittest.TestResult.stopTest(self, test) + self.callback.notifyTestFinished(test) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + self.callback.notifyTestStarted(test) + + +class RollbackImporter: + """This tricky little class is used to make sure that modules under test + will be reloaded the next time they are imported. + """ + def __init__(self): + self.previousModules = sys.modules.copy() + + def rollbackImports(self): + for modname in sys.modules.copy().keys(): + if not modname in self.previousModules: + # Force reload when modname next imported + del(sys.modules[modname]) + + +############################################################################## +# Tkinter GUI +############################################################################## + +class DiscoverSettingsDialog(simpledialog.Dialog): + """ + Dialog box for prompting test discovery settings + """ + + def __init__(self, master, top_level_dir, test_file_glob_pattern, *args, **kwargs): + self.top_level_dir = top_level_dir + self.dirVar = tk.StringVar() + self.dirVar.set(top_level_dir) + + self.test_file_glob_pattern = test_file_glob_pattern + self.testPatternVar = tk.StringVar() + self.testPatternVar.set(test_file_glob_pattern) + + simpledialog.Dialog.__init__(self, master, title="Discover Settings", + *args, **kwargs) + + def body(self, master): + tk.Label(master, text="Top Level Directory").grid(row=0) + self.e1 = tk.Entry(master, textvariable=self.dirVar) + self.e1.grid(row = 0, column=1) + tk.Button(master, text="...", + command=lambda: self.selectDirClicked(master)).grid(row=0,column=3) + + tk.Label(master, text="Test File Pattern").grid(row=1) + self.e2 = tk.Entry(master, textvariable = self.testPatternVar) + self.e2.grid(row = 1, column=1) + return None + + def selectDirClicked(self, master): + dir_path = filedialog.askdirectory(parent=master) + if dir_path: + self.dirVar.set(dir_path) + + def apply(self): + self.top_level_dir = self.dirVar.get() + self.test_file_glob_pattern = self.testPatternVar.get() + +class TkTestRunner(BaseGUITestRunner): + """An implementation of BaseGUITestRunner using Tkinter. + """ + def initGUI(self, root, initialTestName): + """Set up the GUI inside the given root window. The test name entry + field will be pre-filled with the given initialTestName. + """ + self.root = root + + self.statusVar = tk.StringVar() + self.statusVar.set("Idle") + + #tk vars for tracking counts of test result types + self.runCountVar = tk.IntVar() + self.failCountVar = tk.IntVar() + self.errorCountVar = tk.IntVar() + self.skipCountVar = tk.IntVar() + self.expectFailCountVar = tk.IntVar() + self.remainingCountVar = tk.IntVar() + + self.top = tk.Frame() + self.top.pack(fill=tk.BOTH, expand=1) + self.createWidgets() + + def getDirectoryToDiscover(self): + return filedialog.askdirectory() + + def settingsClicked(self): + d = DiscoverSettingsDialog(self.top, self.top_level_dir, self.test_file_glob_pattern) + self.top_level_dir = d.top_level_dir + self.test_file_glob_pattern = d.test_file_glob_pattern + + def notifyTestsDiscovered(self, test_suite): + discovered = test_suite.countTestCases() + self.runCountVar.set(0) + self.failCountVar.set(0) + self.errorCountVar.set(0) + self.remainingCountVar.set(discovered) + self.progressBar.setProgressFraction(0.0) + self.errorListbox.delete(0, tk.END) + self.statusVar.set("Discovering tests from %s. Found: %s" % + (self.directory_to_read, discovered)) + self.stopGoButton['state'] = tk.NORMAL + + def createWidgets(self): + """Creates and packs the various widgets. + + Why is it that GUI code always ends up looking a mess, despite all the + best intentions to keep it tidy? Answers on a postcard, please. + """ + # Status bar + statusFrame = tk.Frame(self.top, relief=tk.SUNKEN, borderwidth=2) + statusFrame.pack(anchor=tk.SW, fill=tk.X, side=tk.BOTTOM) + tk.Label(statusFrame, width=1, textvariable=self.statusVar).pack(side=tk.TOP, fill=tk.X) + + # Area to enter name of test to run + leftFrame = tk.Frame(self.top, borderwidth=3) + leftFrame.pack(fill=tk.BOTH, side=tk.LEFT, anchor=tk.NW, expand=1) + suiteNameFrame = tk.Frame(leftFrame, borderwidth=3) + suiteNameFrame.pack(fill=tk.X) + + # Progress bar + progressFrame = tk.Frame(leftFrame, relief=tk.GROOVE, borderwidth=2) + progressFrame.pack(fill=tk.X, expand=0, anchor=tk.NW) + tk.Label(progressFrame, text="Progress:").pack(anchor=tk.W) + self.progressBar = ProgressBar(progressFrame, relief=tk.SUNKEN, + borderwidth=2) + self.progressBar.pack(fill=tk.X, expand=1) + + + # Area with buttons to start/stop tests and quit + buttonFrame = tk.Frame(self.top, borderwidth=3) + buttonFrame.pack(side=tk.LEFT, anchor=tk.NW, fill=tk.Y) + + tk.Button(buttonFrame, text="Discover Tests", + command=self.discoverClicked).pack(fill=tk.X) + + + self.stopGoButton = tk.Button(buttonFrame, text="Start", + command=self.runClicked, state=tk.DISABLED) + self.stopGoButton.pack(fill=tk.X) + + tk.Button(buttonFrame, text="Close", + command=self.top.quit).pack(side=tk.BOTTOM, fill=tk.X) + tk.Button(buttonFrame, text="Settings", + command=self.settingsClicked).pack(side=tk.BOTTOM, fill=tk.X) + + # Area with labels reporting results + for label, var in (('Run:', self.runCountVar), + ('Failures:', self.failCountVar), + ('Errors:', self.errorCountVar), + ('Skipped:', self.skipCountVar), + ('Expected Failures:', self.expectFailCountVar), + ('Remaining:', self.remainingCountVar), + ): + tk.Label(progressFrame, text=label).pack(side=tk.LEFT) + tk.Label(progressFrame, textvariable=var, + foreground="blue").pack(side=tk.LEFT, fill=tk.X, + expand=1, anchor=tk.W) + + # List box showing errors and failures + tk.Label(leftFrame, text="Failures and errors:").pack(anchor=tk.W) + listFrame = tk.Frame(leftFrame, relief=tk.SUNKEN, borderwidth=2) + listFrame.pack(fill=tk.BOTH, anchor=tk.NW, expand=1) + self.errorListbox = tk.Listbox(listFrame, foreground='red', + selectmode=tk.SINGLE, + selectborderwidth=0) + self.errorListbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, + anchor=tk.NW) + listScroll = tk.Scrollbar(listFrame, command=self.errorListbox.yview) + listScroll.pack(side=tk.LEFT, fill=tk.Y, anchor=tk.N) + self.errorListbox.bind("", + lambda e, self=self: self.showSelectedError()) + self.errorListbox.configure(yscrollcommand=listScroll.set) + + def errorDialog(self, title, message): + messagebox.showerror(parent=self.root, title=title, + message=message) + + def notifyRunning(self): + self.runCountVar.set(0) + self.failCountVar.set(0) + self.errorCountVar.set(0) + self.remainingCountVar.set(self.totalTests) + self.errorInfo = [] + while self.errorListbox.size(): + self.errorListbox.delete(0) + #Stopping seems not to work, so simply disable the start button + #self.stopGoButton.config(command=self.stopClicked, text="Stop") + self.stopGoButton.config(state=tk.DISABLED) + self.progressBar.setProgressFraction(0.0) + self.top.update_idletasks() + + def notifyStopped(self): + self.stopGoButton.config(state=tk.DISABLED) + #self.stopGoButton.config(command=self.runClicked, text="Start") + self.statusVar.set("Idle") + + def notifyTestStarted(self, test): + self.statusVar.set(str(test)) + self.top.update_idletasks() + + def notifyTestFailed(self, test, err): + self.failCountVar.set(1 + self.failCountVar.get()) + self.errorListbox.insert(tk.END, "Failure: %s" % test) + self.errorInfo.append((test,err)) + + def notifyTestErrored(self, test, err): + self.errorCountVar.set(1 + self.errorCountVar.get()) + self.errorListbox.insert(tk.END, "Error: %s" % test) + self.errorInfo.append((test,err)) + + def notifyTestSkipped(self, test, reason): + super(TkTestRunner, self).notifyTestSkipped(test, reason) + self.skipCountVar.set(1 + self.skipCountVar.get()) + + def notifyTestFailedExpectedly(self, test, err): + super(TkTestRunner, self).notifyTestFailedExpectedly(test, err) + self.expectFailCountVar.set(1 + self.expectFailCountVar.get()) + + + def notifyTestFinished(self, test): + self.remainingCountVar.set(self.remainingCountVar.get() - 1) + self.runCountVar.set(1 + self.runCountVar.get()) + fractionDone = float(self.runCountVar.get())/float(self.totalTests) + fillColor = len(self.errorInfo) and "red" or "green" + self.progressBar.setProgressFraction(fractionDone, fillColor) + + def showSelectedError(self): + selection = self.errorListbox.curselection() + if not selection: return + selected = int(selection[0]) + txt = self.errorListbox.get(selected) + window = tk.Toplevel(self.root) + window.title(txt) + window.protocol('WM_DELETE_WINDOW', window.quit) + test, error = self.errorInfo[selected] + tk.Label(window, text=str(test), + foreground="red", justify=tk.LEFT).pack(anchor=tk.W) + tracebackLines = traceback.format_exception(*error) + tracebackText = "".join(tracebackLines) + tk.Label(window, text=tracebackText, justify=tk.LEFT).pack() + tk.Button(window, text="Close", + command=window.quit).pack(side=tk.BOTTOM) + window.bind('', lambda e, w=window: w.quit()) + window.mainloop() + window.destroy() + + +class ProgressBar(tk.Frame): + """A simple progress bar that shows a percentage progress in + the given colour.""" + + def __init__(self, *args, **kwargs): + tk.Frame.__init__(self, *args, **kwargs) + self.canvas = tk.Canvas(self, height='20', width='60', + background='white', borderwidth=3) + self.canvas.pack(fill=tk.X, expand=1) + self.rect = self.text = None + self.canvas.bind('', self.paint) + self.setProgressFraction(0.0) + + def setProgressFraction(self, fraction, color='blue'): + self.fraction = fraction + self.color = color + self.paint() + self.canvas.update_idletasks() + + def paint(self, *args): + totalWidth = self.canvas.winfo_width() + width = int(self.fraction * float(totalWidth)) + height = self.canvas.winfo_height() + if self.rect is not None: self.canvas.delete(self.rect) + if self.text is not None: self.canvas.delete(self.text) + self.rect = self.canvas.create_rectangle(0, 0, width, height, + fill=self.color) + percentString = "%3.0f%%" % (100.0 * self.fraction) + self.text = self.canvas.create_text(totalWidth/2, height/2, + anchor=tk.CENTER, + text=percentString) + +def main(initialTestName=""): + root = tk.Tk() + root.title("PyUnit") + runner = TkTestRunner(root, initialTestName) + root.protocol('WM_DELETE_WINDOW', root.quit) + root.mainloop() + + +if __name__ == '__main__': + if len(sys.argv) == 2: + main(sys.argv[1]) + else: + main() From fe0ed7f9aa7e36c7df559ac09ca6b0264da31db3 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sat, 25 May 2024 20:17:12 +0800 Subject: [PATCH 09/14] Update Doc/tools/templates/layout.html Co-authored-by: Nice Zombies --- Doc/tools/templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tools/templates/layout.html b/Doc/tools/templates/layout.html index 6d0c9c07bf61db..e96cbf70b1239e 100644 --- a/Doc/tools/templates/layout.html +++ b/Doc/tools/templates/layout.html @@ -67,7 +67,7 @@ } }, { - slug: "3.13", + slug: "pre (3.13)", urls: { documentation: "https://docs.python.org/3.13/", } From 52751a0ee11311eb2ae3968c1b1649ec5fba32b9 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 25 May 2024 19:00:25 +0300 Subject: [PATCH 10/14] Re-add +x permissions --- Tools/unittestgui/unittestgui.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Tools/unittestgui/unittestgui.py diff --git a/Tools/unittestgui/unittestgui.py b/Tools/unittestgui/unittestgui.py old mode 100644 new mode 100755 From e42606b3fefe445d47e6f917479dcd2da5d57ad2 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sun, 26 May 2024 06:28:56 +0800 Subject: [PATCH 11/14] update help.html --- Lib/idlelib/help.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html index 988eb80d1cbbfc..cebd6fe19faa34 100644 --- a/Lib/idlelib/help.html +++ b/Lib/idlelib/help.html @@ -5,7 +5,7 @@ - IDLE — Python 3.14.0a0 documentation + IDLE — Python 3.13.0a2 documentation @@ -184,7 +184,7 @@

    Navigation

  • - 3.14.0a0 Documentation » + 3.13.0a2 Documentation »
  • @@ -1136,7 +1136,7 @@

    Navigation

  • - 3.14.0a0 Documentation » + 3.13.0a2 Documentation »
  • From 673a2ffdc599c9db33cec47eaee4baaf434cd518 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sun, 26 May 2024 06:31:33 +0800 Subject: [PATCH 12/14] update help.html --- Lib/idlelib/help.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html index cebd6fe19faa34..827d230b54e159 100644 --- a/Lib/idlelib/help.html +++ b/Lib/idlelib/help.html @@ -18,7 +18,7 @@ @@ -1180,7 +1180,7 @@

    Navigation



    - Last updated on May 25, 2024 (10:33 UTC). + Last updated on Jan 17, 2024 (06:57 UTC). Found a bug?
    From 33e4fd45d43451fcc96433f6cab4ba4834b7c4fa Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sun, 26 May 2024 19:05:06 +0800 Subject: [PATCH 13/14] Update README.rst Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a4f8104fa20cbc..7a1eee78014c02 100644 --- a/README.rst +++ b/README.rst @@ -208,7 +208,7 @@ and ``make altinstall`` in the others. Release Schedule ---------------- -See :pep:`745` for Python 3.14 release details. +See `PEP 745 `__ for the Python 3.14 release details. Copyright and License Information From 25489a9e24bf8eaf517143357b8ab2529a504d02 Mon Sep 17 00:00:00 2001 From: Wulian233 <71213467+Wulian233@users.noreply.github.com> Date: Sun, 26 May 2024 19:10:59 +0800 Subject: [PATCH 14/14] Update README.rst Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7a1eee78014c02..e3163c5ff636ab 100644 --- a/README.rst +++ b/README.rst @@ -208,7 +208,7 @@ and ``make altinstall`` in the others. Release Schedule ---------------- -See `PEP 745 `__ for the Python 3.14 release details. +See `PEP 745 `__ for Python 3.14 release details. Copyright and License Information