| 
									
										
										
										
											2014-01-16 17:15:49 +02:00
										 |  |  | #!/usr/bin/env python3 | 
					
						
							| 
									
										
										
										
											2011-01-03 15:39:49 +00:00
										 |  |  | """
 | 
					
						
							|  |  |  | 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): | 
					
						
							| 
									
										
										
										
											2011-01-03 17:00:11 +00:00
										 |  |  |         discovered = test_suite.countTestCases() | 
					
						
							| 
									
										
										
										
											2011-01-03 15:39:49 +00:00
										 |  |  |         self.runCountVar.set(0) | 
					
						
							|  |  |  |         self.failCountVar.set(0) | 
					
						
							|  |  |  |         self.errorCountVar.set(0) | 
					
						
							| 
									
										
										
										
											2011-01-03 17:00:11 +00:00
										 |  |  |         self.remainingCountVar.set(discovered) | 
					
						
							| 
									
										
										
										
											2011-01-03 15:39:49 +00:00
										 |  |  |         self.progressBar.setProgressFraction(0.0) | 
					
						
							|  |  |  |         self.errorListbox.delete(0, tk.END) | 
					
						
							| 
									
										
										
										
											2011-01-03 17:00:11 +00:00
										 |  |  |         self.statusVar.set("Discovering tests from %s. Found: %s" % | 
					
						
							|  |  |  |             (self.directory_to_read, discovered)) | 
					
						
							| 
									
										
										
										
											2011-01-03 15:39:49 +00:00
										 |  |  |         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("<Double-1>", | 
					
						
							|  |  |  |                                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('<Key-Return>', 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('<Configure>', 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() |