00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 __author__ = "Werner Mayer (werner.wm.mayer@gmx.de)"
00030
00031 import string
00032 import traceback
00033
00034
00035
00036
00037
00038 import unittest
00039 import sys
00040 import traceback
00041 import string
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051 class BaseGUITestRunner:
00052 """Subclass this class to create a GUI TestRunner that uses a specific
00053 windowing toolkit. The class takes care of running tests in the correct
00054 manner, and making callbacks to the derived class to obtain information
00055 or signal that events have occurred.
00056 """
00057 def __init__(self, *args, **kwargs):
00058 self.currentResult = None
00059 self.running = 0
00060 self.__rollbackImporter = None
00061 apply(self.initGUI, args, kwargs)
00062
00063 def getSelectedTestName(self):
00064 "Override to return the name of the test selected to be run"
00065 pass
00066
00067 def errorDialog(self, title, message):
00068 "Override to display an error arising from GUI usage"
00069 pass
00070
00071 def runClicked(self):
00072 "To be called in response to user choosing to run a test"
00073 if self.running: return
00074 testName = self.getSelectedTestName()
00075 if not testName:
00076 self.errorDialog("Test name entry", "You must enter a test name")
00077 return
00078 if self.__rollbackImporter:
00079 self.__rollbackImporter.rollbackImports()
00080 self.__rollbackImporter = RollbackImporter()
00081 try:
00082 test = unittest.defaultTestLoader.loadTestsFromName(testName)
00083 except:
00084 exc_type, exc_value, exc_tb = sys.exc_info()
00085 apply(traceback.print_exception,sys.exc_info())
00086 self.errorDialog("Unable to run test '%s'" % testName,
00087 "Error loading specified test: %s, %s" % \
00088 (exc_type, exc_value))
00089 return
00090 self.currentResult = GUITestResult(self)
00091 self.totalTests = test.countTestCases()
00092 self.running = 1
00093 self.notifyRunning()
00094 test.run(self.currentResult)
00095 self.running = 0
00096 self.notifyStopped()
00097
00098 def stopClicked(self):
00099 "To be called in response to user stopping the running of a test"
00100 if self.currentResult:
00101 self.currentResult.stop()
00102
00103
00104
00105 def notifyRunning(self):
00106 "Override to set GUI in 'running' mode, enabling 'stop' button etc."
00107 pass
00108
00109 def notifyStopped(self):
00110 "Override to set GUI in 'stopped' mode, enabling 'run' button etc."
00111 pass
00112
00113 def notifyTestFailed(self, test, err):
00114 "Override to indicate that a test has just failed"
00115 pass
00116
00117 def notifyTestErrored(self, test, err):
00118 "Override to indicate that a test has just errored"
00119 pass
00120
00121 def notifyTestStarted(self, test):
00122 "Override to indicate that a test is about to run"
00123 pass
00124
00125 def notifyTestFinished(self, test):
00126 """Override to indicate that a test has finished (it may already have
00127 failed or errored)"""
00128 pass
00129
00130
00131 class GUITestResult(unittest.TestResult):
00132 """A TestResult that makes callbacks to its associated GUI TestRunner.
00133 Used by BaseGUITestRunner. Need not be created directly.
00134 """
00135 def __init__(self, callback):
00136 unittest.TestResult.__init__(self)
00137 self.callback = callback
00138
00139 def addError(self, test, err):
00140 unittest.TestResult.addError(self, test, err)
00141 self.callback.notifyTestErrored(test, err)
00142
00143 def addFailure(self, test, err):
00144 unittest.TestResult.addFailure(self, test, err)
00145 self.callback.notifyTestFailed(test, err)
00146
00147 def stopTest(self, test):
00148 unittest.TestResult.stopTest(self, test)
00149 self.callback.notifyTestFinished(test)
00150
00151 def startTest(self, test):
00152 unittest.TestResult.startTest(self, test)
00153 self.callback.notifyTestStarted(test)
00154
00155
00156 class RollbackImporter:
00157 """This tricky little class is used to make sure that modules under test
00158 will be reloaded the next time they are imported.
00159 """
00160 def __init__(self):
00161 self.previousModules = sys.modules.copy()
00162
00163 def rollbackImports(self):
00164 for modname in sys.modules.keys():
00165 if not self.previousModules.has_key(modname):
00166
00167 del(sys.modules[modname])
00168
00169
00170
00171
00172
00173
00174
00175
00176 class QtTestRunner(BaseGUITestRunner):
00177 """An implementation of BaseGUITestRunner using Qt.
00178 """
00179 def initGUI(self, root, initialTestName):
00180 """Set up the GUI inside the given root window. The test name entry
00181 field will be pre-filled with the given initialTestName.
00182 """
00183 self.root = root
00184
00185 import QtUnitGui
00186 self.gui=QtUnitGui.UnitTest()
00187 self.gui.setStatusText("Idle")
00188 self.runCountVar = 0
00189 self.failCountVar = 0
00190 self.errorCountVar = 0
00191 self.remainingCountVar = 0
00192
00193 def getSelectedTestName(self):
00194 return self.gui.getUnitTest()
00195
00196 def errorDialog(self, title, message):
00197 return self.gui.errorDialog(title, message)
00198
00199 def notifyRunning(self):
00200 self.runCountVar=0
00201 self.gui.setRunCount(0)
00202 self.failCountVar=0
00203 self.gui.setFailCount(0)
00204 self.errorCountVar=0
00205 self.gui.setErrorCount(0)
00206 self.remainingCountVar=self.totalTests
00207 self.gui.setRemainCount(self.totalTests)
00208 self.errorInfo = []
00209 self.gui.clearErrorList()
00210 self.gui.setProgressFraction(0.0)
00211 self.gui.updateGUI()
00212
00213 def notifyStopped(self):
00214 self.gui.setStatusText("Idle")
00215
00216 def notifyTestStarted(self, test):
00217 self.gui.setStatusText(str(test))
00218 self.gui.updateGUI()
00219
00220 def notifyTestFailed(self, test, err):
00221 self.failCountVar=self.failCountVar+1
00222 self.gui.setFailCount(self.failCountVar)
00223 tracebackLines = apply(traceback.format_exception, err + (10,))
00224 tracebackText = string.join(tracebackLines,'')
00225 self.gui.insertError("Failure: %s" % test,tracebackText)
00226 self.errorInfo.append((test,err))
00227
00228 def notifyTestErrored(self, test, err):
00229 self.errorCountVar=self.errorCountVar+1
00230 self.gui.setErrorCount(self.errorCountVar)
00231 tracebackLines = apply(traceback.format_exception, err + (10,))
00232 tracebackText = string.join(tracebackLines,'')
00233 self.gui.insertError("Error: %s" % test,tracebackText)
00234 self.errorInfo.append((test,err))
00235
00236 def notifyTestFinished(self, test):
00237 self.remainingCountVar=self.remainingCountVar-1
00238 self.gui.setRemainCount(self.remainingCountVar)
00239 self.runCountVar=self.runCountVar+1
00240 self.gui.setRunCount(self.runCountVar)
00241 fractionDone = float(self.runCountVar)/float(self.totalTests)
00242 fillColor = len(self.errorInfo) and "red" or "green"
00243 self.gui.setProgressFraction(fractionDone, fillColor)